TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

Classes/ --include='*.php' # PHP 8.5 implicit float-to-int\ngrep -rn 'data-toggle\\|data-dismiss\\|data-ride' Resources/ --include='*.html' # Bootstrap 4 in Fluid\ngrep -rn 'HashService\\|GeneralUtility::hmac(\\|->findBy[A-Z]\\|->findOneBy[A-Z]\\|->countBy[A-Z]' Classes/ --include='*.php' # v14 removals\n[ -f ext_tables.php ] && echo \"WARN: ext_tables.php deprecated (#109438)\" # v14.3 deprecation\n```\n\n## Scoring\n\n**Base (0-100):** Architecture(20) + Guidelines(20) + PHP(20) + Testing(20) + Practices(20). Excellence bonus up to 22. Critical issues block regardless.\n\n| Range | Level | Action |\n|-------|-------|--------|\n| 90+ | Excellent | Production/TER ready |\n| 80-89 | Good | Minor fixes |\n| 70-79 | Acceptable | Fix before release |\n| 50-69 | Needs Work | Significant effort |\n| \u003c50 | Critical | Block deployment |\n\n## References\n\nSee `references/`:\n\n- **Architecture & code:** `extension-architecture.md`, `directory-structure.md`, `php-architecture.md`, `coding-guidelines.md`, `best-practices.md`, `hooks-and-events.md`\n- **Validation:** `composer-validation.md`, `ext-emconf-validation.md`, `ext-files-validation.md`, `runtests-validation.md`, `version-requirements.md`, `testing-standards.md`\n- **Multi-version:** `dual-version-compatibility.md` (v12+v13), `v13-v14-dual-compatibility.md` (v13+v14), `multi-version-dependency-compatibility.md`, `v13-deprecations.md`, `v14-deprecations.md`\n- **Practices & environment:** `development-environment.md` (DDEV)\n- **Backend & publishing:** `backend-module-v13.md`, `ter-publishing.md`, `report-template.md`, `excellence-indicators.md`, `localization-coverage.md`, `crowdin-integration.md`\n\nAsset templates in `assets/Build/`: PHPStan, PHP-CS-Fixer, Rector, ESLint, Stylelint, TypoScript lint.\n\n---\n\n## Credits & Attribution\n\nThis skill is based on the excellent work by\n**[Netresearch DTT GmbH](https://www.netresearch.de/)**.\n\nOriginal repository: https://github.com/netresearch/typo3-conformance-skill\n\n**Copyright (c) Netresearch DTT GmbH** — Methodology and best practices (MIT / CC-BY-SA-4.0)\n\nSpecial thanks to [Netresearch DTT GmbH](https://www.netresearch.de/) for their generous open-source contributions to the TYPO3 community, which helped shape this skill collection.\nAdapted by webconsulting.at for this skill collection\n---","attachment_filenames":["assets/.github/workflows/publish-to-ter.yml","assets/Build/composer-unused/composer-unused.php","assets/Build/eslint/.eslintrc.json","assets/Build/php-cs-fixer/php-cs-fixer.php","assets/Build/rector/rector.php","assets/Build/stylelint/.stylelintrc.json","assets/Build/typoscript-lint/TypoScriptLint.yml","checkpoints.yaml","references/backend-module-v13.md","references/best-practices.md","references/coding-guidelines.md","references/composer-validation.md","references/crowdin-integration.md","references/development-environment.md","references/directory-structure.md","references/dual-version-compatibility.md","references/excellence-indicators.md","references/ext-emconf-validation.md","references/ext-files-validation.md","references/extension-architecture.md","references/hooks-and-events.md","references/localization-coverage.md","references/multi-version-dependency-compatibility.md","references/php-architecture.md","references/report-template.md","references/runtests-validation.md","references/ter-publishing.md","references/testing-standards.md","references/v13-deprecations.md","references/v13-v14-dual-compatibility.md","references/v14-deprecations.md","references/version-requirements.md","scripts/check-architecture.sh","scripts/check-coding-standards.sh","scripts/check-conformance.sh","scripts/check-documentation.sh","scripts/check-file-structure.sh","scripts/check-phpstan-baseline.sh","scripts/check-testing.sh","scripts/generate-report.sh"],"attachments":[{"filename":"assets/.github/workflows/publish-to-ter.yml","content":"name: Publish new extension version to TER\n\non:\n release:\n types: [published]\n\njobs:\n publish:\n name: Publish new version to TER\n runs-on: ubuntu-latest\n env:\n TYPO3_EXTENSION_KEY: ${{ secrets.TYPO3_EXTENSION_KEY }}\n TYPO3_API_TOKEN: ${{ secrets.TYPO3_TER_ACCESS_TOKEN }}\n steps:\n - name: Checkout repository\n uses: actions/checkout@v6\n\n - name: Validate tag format\n run: |\n if ! [[ \"${GITHUB_REF_NAME}\" =~ ^v[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$ ]]; then\n echo \"::error::Invalid tag format '${GITHUB_REF_NAME}'. Expected format: v1.2.3\"\n exit 1\n fi\n\n - name: Extract version\n id: version\n run: |\n # Strip 'v' prefix for TER (expects \"3.0.1\" not \"v3.0.1\")\n VERSION=\"${GITHUB_REF_NAME#v}\"\n echo \"version=${VERSION}\" >> \"$GITHUB_OUTPUT\"\n echo \"Extracted version: ${VERSION}\"\n\n - name: Prepare release comment\n id: comment\n env:\n RELEASE_BODY: ${{ github.event.release.body }}\n RELEASE_NAME: ${{ github.event.release.name }}\n RELEASE_URL: ${{ github.event.release.html_url }}\n run: |\n # TER Upload Comment Format:\n # - Plain text only (no HTML/Markdown)\n # - Newlines ARE supported (rendered as \u003cbr> on frontend)\n # - Allowed chars: word chars, whitespace, \" % & [ ] ( ) . , ; : / ? { } ! $ - @\n # - Stripped in XML export: # * + = ~ ^ | \\ \u003c >\n # See: references/ter-publishing.md\n\n if [[ -n \"${RELEASE_BODY}\" ]]; then\n # Preserve newlines (TER displays them as line breaks)\n # Limit to reasonable length (1000 chars)\n COMMENT=$(echo \"${RELEASE_BODY}\" | head -c 1000)\n elif [[ -n \"${RELEASE_NAME}\" ]]; then\n COMMENT=\"${RELEASE_NAME}\"\n else\n COMMENT=\"Release ${{ steps.version.outputs.version }}\"\n fi\n\n # Strip characters not supported in TER XML export\n COMMENT=\"${COMMENT//[#*+=~^|\\\\\u003c>]/}\"\n\n # Append release link on new line\n COMMENT=\"${COMMENT}\"

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

\\n\\n'\"Details: ${RELEASE_URL}\"\n\n # Set GitHub Actions output (HEREDOC preserves newlines)\n {\n echo \"comment\u003c\u003cEOF\"\n echo \"${COMMENT}\"\n echo \"EOF\"\n } >> \"$GITHUB_OUTPUT\"\n\n - name: Setup PHP\n uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # v2.37.1\n with:\n php-version: '8.3'\n extensions: intl, mbstring, json, zip, curl\n tools: composer:v2\n\n - name: Install tailor\n run: composer global require typo3/tailor --prefer-dist --no-progress\n\n - name: Publish to TER\n run: |\n TAILOR=\"$(composer global config bin-dir --absolute)/tailor\"\n \"${TAILOR}\" set-version \"${{ steps.version.outputs.version }}\"\n \"${TAILOR}\" ter:publish --comment \"${{ steps.comment.outputs.comment }}\" \"${{ steps.version.outputs.version }}\"\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":3011,"content_sha256":"185a153f673d35d21ece33e0533560db09fa62974cb941cf9f90985b4168ff92"},{"filename":"assets/Build/composer-unused/composer-unused.php","content":"\u003c?php\n\ndeclare(strict_types=1);\n\n/**\n * Composer Unused Configuration - TYPO3 Extension\n *\n * This configuration helps identify unused Composer dependencies.\n *\n * Some TYPO3 packages may be reported as unused even though they're required\n * (e.g., typo3/cms-fluid may not show explicit usage but is needed at runtime).\n * Add such packages to the filter list below.\n */\n\nuse ComposerUnused\\ComposerUnused\\Configuration\\Configuration;\nuse ComposerUnused\\ComposerUnused\\Configuration\\NamedFilter;\n\nreturn static function (Configuration $config): Configuration {\n // Add packages that should be ignored during unused checks\n // These are typically TYPO3 system extensions or runtime dependencies\n\n // Example: typo3/cms-fluid is often required but not directly referenced in code\n $config->addNamedFilter(NamedFilter::fromString('typo3/cms-fluid'));\n\n // Add more as needed for your extension:\n // $config->addNamedFilter(NamedFilter::fromString('typo3/cms-frontend'));\n // $config->addNamedFilter(NamedFilter::fromString('typo3/cms-extbase'));\n\n return $config;\n};\n","content_type":"text/plain; charset=utf-8","language":"php","size":1088,"content_sha256":"2aecb1ea9bf3fd05b95dc8fea630aacb8a1eba817a0ed3f044ab9ac5193a2251"},{"filename":"assets/Build/eslint/.eslintrc.json","content":"{\n \"$schema\": \"https://json.schemastore.org/eslintrc\",\n \"extends\": [\"eslint:recommended\"],\n \"env\": {\n \"browser\": true,\n \"es2021\": true\n },\n \"parserOptions\": {\n \"ecmaVersion\": 12,\n \"sourceType\": \"module\"\n },\n \"rules\": {\n \"no-console\": \"warn\",\n \"no-debugger\": \"error\",\n \"no-alert\": \"warn\",\n \"no-unused-vars\": [\"error\", { \"argsIgnorePattern\": \"^_\" }],\n \"prefer-const\": \"error\",\n \"no-var\": \"error\"\n },\n \"ignorePatterns\": [\n \"node_modules/\",\n \".Build/\",\n \"*.min.js\"\n ]\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":515,"content_sha256":"86af37e03841db4d9244d260854110ef488cebb2669fb6702366952afdc2349c"},{"filename":"assets/Build/php-cs-fixer/php-cs-fixer.php","content":"\u003c?php\n\ndeclare(strict_types=1);\n\n/**\n * PHP-CS-Fixer Configuration - TYPO3 Extension\n * Based on TYPO3 Best Practices: https://github.com/TYPO3BestPractices/tea\n *\n * This configuration uses the official TYPO3 Coding Standards with parallel execution.\n */\n\nuse PhpCsFixer\\Runner\\Parallel\\ParallelConfigFactory;\nuse TYPO3\\CodingStandards\\CsFixerConfig;\n\n$config = CsFixerConfig::create();\n\n// Enable parallel execution for faster performance\n// Automatically detects available CPU cores\n$config->setParallelConfig(ParallelConfigFactory::detect());\n\n// Define which directories to check\n$config->getFinder()\n ->in('Classes')\n ->in('Configuration')\n ->in('Tests')\n // CRITICAL: Exclude ext_emconf.php - TYPO3 does NOT want declare(strict_types=1) in this file\n // The ext_emconf.php is processed by TYPO3's extension manager in a special context\n // Adding strict_types breaks extension installation/updates\n ->notName('ext_emconf.php');\n\n// Optionally add more directories:\n// ->in('ext_localconf.php')\n// ->in('ext_tables.php')\n\nreturn $config;\n","content_type":"text/plain; charset=utf-8","language":"php","size":1065,"content_sha256":"3b8a280557135c14eea6acb9e09612551d2577752e5def329b471ef06dd74cc8"},{"filename":"assets/Build/rector/rector.php","content":"\u003c?php\n\ndeclare(strict_types=1);\n\n/**\n * Rector Configuration - TYPO3 Extension\n * Based on TYPO3 Best Practices: https://github.com/TYPO3BestPractices/tea\n *\n * This configuration enables:\n * - Automated TYPO3 migrations (version upgrades)\n * - PHP modernization (up to PHP 8.1+)\n * - PHPUnit test modernization\n * - Code quality improvements\n * - ExtEmConf automatic maintenance\n */\n\nuse Rector\\CodeQuality\\Rector\\If_\\ExplicitBoolCompareRector;\nuse Rector\\CodeQuality\\Rector\\Ternary\\SwitchNegatedTernaryRector;\nuse Rector\\Config\\RectorConfig;\nuse Rector\\PHPUnit\\Set\\PHPUnitSetList;\nuse Rector\\Set\\ValueObject\\LevelSetList;\nuse Rector\\Set\\ValueObject\\SetList;\nuse Rector\\Strict\\Rector\\Empty_\\DisallowedEmptyRuleFixerRector;\nuse Rector\\TypeDeclaration\\Rector\\ClassMethod\\AddVoidReturnTypeWhereNoReturnRector;\nuse Rector\\ValueObject\\PhpVersion;\nuse Ssch\\TYPO3Rector\\CodeQuality\\General\\ConvertImplicitVariablesToExplicitGlobalsRector;\nuse Ssch\\TYPO3Rector\\CodeQuality\\General\\ExtEmConfRector;\nuse Ssch\\TYPO3Rector\\Configuration\\Typo3Option;\nuse Ssch\\TYPO3Rector\\Set\\Typo3LevelSetList;\nuse Ssch\\TYPO3Rector\\Set\\Typo3SetList;\nuse Ssch\\Typo3RectorTestingFramework\\Set\\TYPO3TestingFrameworkSetList;\n\nreturn RectorConfig::configure()\n ->withPaths([\n __DIR__ . '/../../Classes/',\n __DIR__ . '/../../Configuration/',\n __DIR__ . '/../../Tests/',\n __DIR__ . '/../../ext_emconf.php',\n __DIR__ . '/../../ext_localconf.php',\n // __DIR__ . '/../../ext_tables.php', // Uncomment if you still use this (deprecated)\n ])\n // Minimum PHP version your extension supports\n ->withPhpVersion(PhpVersion::PHP_82)\n // Enable all PHP sets for modernization\n ->withPhpSets(\n true\n )\n // Note: We're enabling specific sets by default.\n // You can temporarily enable more sets as needed for larger refactorings.\n ->withSets([\n // Rector Core Sets (uncomment as needed for major refactorings)\n // LevelSetList::UP_TO_PHP_81,\n // SetList::CODE_QUALITY,\n // SetList::CODING_STYLE,\n // SetList::DEAD_CODE,\n // SetList::EARLY_RETURN,\n // SetList::TYPE_DECLARATION,\n\n // PHPUnit Sets - modernize tests\n PHPUnitSetList::PHPUNIT_100,\n // PHPUnitSetList::PHPUNIT_CODE_QUALITY,\n\n // TYPO3 Sets - CRITICAL for TYPO3 migrations\n // https://github.com/sabbelasichon/typo3-rector/blob/main/src/Set/Typo3LevelSetList.php\n // https://github.com/sabbelasichon/typo3-rector/blob/main/src/Set/Typo3SetList.php\n Typo3SetList::CODE_QUALITY,\n Typo3SetList::GENERAL,\n\n // TYPO3 Version Migration - ADJUST TO YOUR TARGET VERSION\n Typo3LevelSetList::UP_TO_TYPO3_12, // Change to UP_TO_TYPO3_13 when upgrading\n\n // TYPO3 Testing Framework (if using typo3/testing-framework)\n // TYPO3TestingFrameworkSetList::TYPO3_TESTING_FRAMEWORK_7,\n ])\n // To have a better analysis from PHPStan, we teach it here some more things\n ->withPHPStanConfigs([\n Typo3Option::PHPSTAN_FOR_RECTOR_PATH,\n ])\n // Additional useful rules\n ->withRules([\n AddVoidReturnTypeWhereNoReturnRector::class,\n ConvertImplicitVariablesToExplicitGlobalsRector::class,\n ])\n // Auto-import class names (removes need for full namespaces)\n ->withImportNames(true, true, false)\n // ExtEmConfRector: Automatically maintains ext_emconf.php\n ->withConfiguredRule(ExtEmConfRector::class, [\n // Adjust these constraints to match your extension requirements\n ExtEmConfRector::PHP_VERSION_CONSTRAINT => '8.2.0-8.5.99',\n ExtEmConfRector::TYPO3_VERSION_CONSTRAINT => '12.4.0-12.4.99', // or '13.0.0-13.99.99'\n ExtEmConfRector::ADDITIONAL_VALUES_TO_BE_REMOVED => [],\n ])\n // Skip specific rules if they cause issues\n ->withSkip([\n // Example: Skip specific rules\n // ExplicitBoolCompareRector::class,\n // SwitchNegatedTernaryRector::class,\n\n // Example: Skip specific paths\n // ExplicitBoolCompareRector::class => [\n // __DIR__ . '/../../Classes/Legacy/',\n // ],\n ]);\n","content_type":"text/plain; charset=utf-8","language":"php","size":4121,"content_sha256":"b4cb7b9d13b03e013654732093dad2b4582c49f28e216f245ae0b0b2cdc8596e"},{"filename":"assets/Build/stylelint/.stylelintrc.json","content":"{\n \"$schema\": \"https://json.schemastore.org/stylelintrc\",\n \"extends\": \"stylelint-config-standard\",\n \"rules\": {\n \"indentation\": 2,\n \"string-quotes\": \"single\",\n \"no-descending-specificity\": null,\n \"selector-class-pattern\": null,\n \"custom-property-pattern\": null\n },\n \"ignoreFiles\": [\n \"node_modules/**\",\n \".Build/**\",\n \"**/*.min.css\"\n ]\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":367,"content_sha256":"7f7433421879dc294577036fb75e189cbcf9ede67247dec14216f8c4ae628055"},{"filename":"assets/Build/typoscript-lint/TypoScriptLint.yml","content":"---\n# TypoScript Linting Configuration - TYPO3 Extension\n# Based on TYPO3 Best Practices: https://github.com/TYPO3BestPractices/tea\n#\n# This configuration ensures consistent TypoScript formatting and detects common issues.\n\nsniffs:\n # Indentation - enforce consistent formatting\n - class: Indentation\n parameters:\n indentConditions: true\n indentPerLevel: 2 # 2 spaces per level (TYPO3 standard)\n useSpaces: true # Use spaces, not tabs\n\n # Dead Code Detection - find unused TypoScript\n - class: DeadCode\n\n # Operator Whitespace - consistent spacing around operators\n - class: OperatorWhitespace\n\n # Repeating RValue - detect duplicate values (can be noisy, disabled by default)\n - class: RepeatingRValue\n disabled: true\n\n # Duplicate Assignment - detect duplicate property assignments\n - class: DuplicateAssignment\n\n # Empty Section - warn about empty sections (can be intentional, disabled by default)\n - class: EmptySection\n disabled: true\n\n # Nesting Consistency - ensure consistent object nesting\n - class: NestingConsistency\n parameters:\n commonPathPrefixThreshold: 1\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":1122,"content_sha256":"5a1b76f78f2981d7fae953954d90663d1b7be0703ca7c2878c9b3e5f693729d8"},{"filename":"checkpoints.yaml","content":"# Checkpoints for typo3-conformance skill\n# Validates TYPO3 extension structure, configuration, and coding standards\n\n# RELOCATED FROM php-modernization-skill (v1.16.0 refocus):\n# PM-26 -> TC-180 (PSR-3 logging compliance)\n# PM-27 -> TC-181 (PSR-14 event dispatch safety)\n# PM-28 -> TC-182 (factory pattern + baseline)\n# PM-33 -> TC-183 (GeneralUtility::makeInstance -> DI)\n\nversion: 1\nskill_id: typo3-conformance\n\nmechanical:\n # === ext_emconf.php CHECKS ===\n - id: TC-01\n type: file_exists\n target: ext_emconf.php\n severity: error\n desc: \"ext_emconf.php must exist for TYPO3 extension registration\"\n\n - id: TC-02\n type: contains\n target: ext_emconf.php\n pattern: \"'title'\"\n severity: error\n desc: \"ext_emconf.php must define 'title' field\"\n\n - id: TC-03\n type: contains\n target: ext_emconf.php\n pattern: \"'description'\"\n severity: error\n desc: \"ext_emconf.php must define 'description' field\"\n\n - id: TC-04\n type: contains\n target: ext_emconf.php\n pattern: \"'version'\"\n severity: error\n desc: \"ext_emconf.php must define 'version' field\"\n\n - id: TC-05\n type: contains\n target: ext_emconf.php\n pattern: \"'constraints'\"\n severity: error\n desc: \"ext_emconf.php must define 'constraints' with TYPO3 core dependency\"\n\n - id: TC-06\n type: regex\n target: ext_emconf.php\n pattern: \"['\\\"]state['\\\"][[:space:]]*=>[[:space:]]*['\\\"](stable|beta|alpha|experimental|test|obsolete|excludeFromUpdates)['\\\"]\"\n severity: warning\n desc: \"ext_emconf.php should define valid 'state' field\"\n\n # === composer.json CHECKS ===\n - id: TC-10\n type: file_exists\n target: composer.json\n severity: error\n desc: \"composer.json must exist for Composer-based installation\"\n\n - id: TC-11\n type: contains\n target: composer.json\n pattern: '\"typo3-cms-extension\"'\n severity: error\n desc: \"composer.json type must be 'typo3-cms-extension'\"\n\n - id: TC-12\n type: json_path\n target: composer.json\n pattern: '.require[\"typo3/cms-core\"]'\n severity: error\n desc: \"composer.json must require typo3/cms-core\"\n\n - id: TC-13\n type: json_path\n target: composer.json\n pattern: '.autoload[\"psr-4\"]'\n severity: error\n desc: \"composer.json must define PSR-4 autoloading\"\n\n - id: TC-14\n type: regex\n target: composer.json\n pattern: '\"[A-Z][a-zA-Z0-9_]*\\\\\\\\[A-Z][a-zA-Z0-9_]*\\\\\\\\\": \"Classes/?\"'\n severity: error\n desc: \"PSR-4 autoload must map vendor namespace to Classes/ directory\"\n\n - id: TC-15\n type: json_path\n target: composer.json\n pattern: '.extra[\"typo3/cms\"][\"extension-key\"]'\n severity: warning\n desc: \"composer.json should define extension-key in extra.typo3/cms\"\n\n - id: TC-16\n type: command\n pattern: \"composer validate --strict 2>&1 || true\"\n severity: warning\n desc: \"composer.json should pass strict validation\"\n\n # === DIRECTORY STRUCTURE CHECKS ===\n - id: TC-20\n type: file_exists\n target: Classes/\n severity: error\n desc: \"Classes/ directory must exist for PHP classes\"\n\n - id: TC-21\n type: file_exists\n target: Configuration/\n severity: warning\n desc: \"Configuration/ directory should exist for TCA, TypoScript, etc.\"\n\n - id: TC-22\n type: file_exists\n target: Resources/\n severity: warning\n desc: \"Resources/ directory should exist for templates, assets, translations\"\n\n - id: TC-23\n type: file_exists\n target: Resources/Private/\n severity: warning\n desc: \"Resources/Private/ should exist for templates and partials\"\n\n - id: TC-24\n type: file_exists\n target: Resources/Public/\n severity: info\n desc: \"Resources/Public/ should exist for CSS, JS, and images\"\n\n # === DEPENDENCY INJECTION CHECKS ===\n - id: TC-30\n type: file_exists\n target: Configuration/Services.yaml\n severity: warning\n desc: \"Services.yaml should exist for dependency injection configuration\"\n\n - id: TC-31\n type: contains\n target: Configuration/Services.yaml\n pattern: \"autowire: true\"\n severity: info\n desc: \"Services.yaml should enable autowiring\"\n\n - id: TC-32\n type: contains\n target: Configuration/Services.yaml\n pattern: \"autoconfigure: true\"\n severity: info\n desc: \"Services.yaml should enable autoconfiguration\"\n\n # === TCA CONFIGURATION CHECKS ===\n - id: TC-35\n type: file_exists\n target: Configuration/TCA/\n severity: info\n desc: \"Configuration/TCA/ should exist if extension defines database tables\"\n\n # === DEPRECATED API PATTERNS ===\n - id: TC-40\n type: not_contains\n target: Classes/**/*.php\n pattern: \"$GLOBALS['TYPO3_DB']\"\n severity: error\n desc: \"Must not use deprecated TYPO3_DB global (removed in TYPO3 v10)\"\n\n - id: TC-41\n type: not_contains\n target: Classes/**/*.php\n pattern: \"GeneralUtility::makeInstance(ObjectManager\"\n severity: warning\n desc: \"Should not instantiate ObjectManager directly; use dependency injection\"\n\n - id: TC-42\n type: not_contains\n target: Classes/**/*.php\n pattern: \"ObjectManager::get(\"\n severity: warning\n desc: \"Should not use ObjectManager::get(); use dependency injection\"\n\n - id: TC-43\n type: not_contains\n target: Classes/**/*.php\n pattern: \"GeneralUtility::_GP(\"\n severity: warning\n desc: \"Should not use deprecated _GP(); use request object instead\"\n\n - id: TC-44\n type: not_contains\n target: Classes/**/*.php\n pattern: \"GeneralUtility::_GET(\"\n severity: warning\n desc: \"Should not use deprecated _GET(); use request object instead\"\n\n - id: TC-45\n type: not_contains\n target: Classes/**/*.php\n pattern: \"GeneralUtility::_POST(\"\n severity: warning\n desc: \"Should not use deprecated _POST(); use request object instead\"\n\n - id: TC-46\n type: not_contains\n target: Classes/**/*.php\n pattern: \"$GLOBALS['TSFE']->fe_user\"\n severity: warning\n desc: \"Should not access fe_user via TSFE global; use Context API\"\n\n - id: TC-47\n type: not_contains\n target: Classes/**/*.php\n pattern: \"extRelPath(\"\n severity: error\n desc: \"Must not use deprecated extRelPath() (removed in TYPO3 v10)\"\n\n - id: TC-48\n type: not_contains\n target: Classes/**/*.php\n pattern: \"BackendUtility::getModuleUrl(\"\n severity: error\n desc: \"Must not use deprecated getModuleUrl(); use UriBuilder\"\n\n # === EXTENSION REGISTRATION ===\n - id: TC-50\n type: file_exists\n target: ext_localconf.php\n severity: info\n desc: \"ext_localconf.php should exist for frontend/backend registration\"\n\n # ext_tables.php is OPTIONAL in TYPO3 v13+ — backend modules / AJAX routes\n # are registered via Configuration/Backend/{Modules,AjaxRoutes,Routes}.php.\n # Accept any of the registration entry-points (or pure-service extensions\n # with no backend surface at all).\n - id: TC-51\n type: file_exists\n target: \"{ext_tables.php,Configuration/Backend/Modules.php,Configuration/Backend/AjaxRoutes.php,Configuration/Backend/Routes.php}\"\n severity: info\n desc: \"Backend modules / AJAX routes registered via either ext_tables.php (legacy) or Configuration/Backend/*.php (TYPO3 v13+ recommended). Pure-service extensions may have neither.\"\n\n # === STRICT TYPES DECLARATION ===\n - id: TC-07\n type: regex_not\n target: ext_emconf.php\n pattern: '^[[:space:]]*(\u003c\\?php[[:space:]]+)?declare[[:space:]]*\\([[:space:]]*strict_types'\n severity: error\n desc: \"ext_emconf.php MUST NOT contain declare(strict_types=1) - TER upload will fail\"\n\n - id: TC-08\n type: regex\n target: ext_emconf.php\n pattern: '\\$EM_CONF\\[\\$_EXTKEY\\]'\n severity: error\n desc: \"ext_emconf.php must use $EM_CONF[$_EXTKEY], not a hardcoded extension key\"\n\n - id: TC-09\n type: contains\n target: ext_emconf.php\n pattern: \"'category'\"\n severity: warning\n desc: \"ext_emconf.php should define valid 'category' field\"\n\n # === CODING STANDARDS: STRICT TYPES ===\n - id: TC-17\n type: regex\n target: Classes/**/*.php\n pattern: 'declare\\(strict_types=1\\)'\n severity: warning\n desc: \"All PHP files in Classes/ should declare strict_types=1\"\n\n # === COMPOSER.JSON ADDITIONAL CHECKS ===\n - id: TC-18\n type: json_path\n target: composer.json\n pattern: '.require[\"php\"]'\n severity: error\n desc: \"composer.json must declare PHP version constraint\"\n\n - id: TC-19\n type: json_path\n target: composer.json\n pattern: '.license'\n severity: warning\n desc: \"composer.json should declare license (GPL-2.0-or-later recommended)\"\n\n - id: TC-25\n type: json_path\n target: composer.json\n pattern: '.description'\n severity: warning\n desc: \"composer.json should have a meaningful description\"\n\n - id: TC-26\n type: regex\n target: composer.json\n pattern: '\"name\"\\s*:\\s*\"[a-z0-9-]+/[a-z0-9-]+\"'\n severity: error\n desc: \"composer.json name must follow vendor/package-name format\"\n\n - id: TC-27\n type: not_contains\n target: composer.json\n pattern: '\"typo3-ter/'\n severity: warning\n desc: \"composer.json should not contain deprecated typo3-ter replace entries\"\n\n # === ADDITIONAL PROHIBITED PATTERNS ===\n - id: TC-36\n type: not_contains\n target: Classes/**/*.php\n pattern: \"$GLOBALS['TYPO3_CONF_VARS']\"\n severity: warning\n desc: \"Should not access TYPO3_CONF_VARS via $GLOBALS; use ExtensionConfiguration\"\n\n - id: TC-37\n type: not_contains\n target: Classes/**/*.php\n pattern: \"$GLOBALS['BE_USER']\"\n severity: warning\n desc: \"Should not access BE_USER via $GLOBALS; use Context API\"\n\n - id: TC-38\n type: not_contains\n target: Classes/**/*.php\n pattern: \"$GLOBALS['LANG']\"\n severity: warning\n desc: \"Should not access LANG via $GLOBALS; use LanguageServiceFactory\"\n\n - id: TC-39\n type: not_contains\n target: Classes/**/*.php\n pattern: \"$GLOBALS['TCA']\"\n severity: warning\n desc: \"Should not access TCA via $GLOBALS; use TcaSchemaFactory (v14+) or inject TCA via DI\"\n\n # === BACKEND MODULE MODERNIZATION (v13+) ===\n - id: TC-52\n type: command\n pattern: \"! test -d Resources/Private/Templates/ || ! grep -rq '\u003cscript' Resources/Private/Templates/ 2>/dev/null\"\n severity: warning\n desc: \"Templates should not contain inline \u003cscript> tags; use ES6 modules\"\n\n - id: TC-53\n type: file_exists\n target: Configuration/Icons.php\n severity: info\n scope: backend\n desc: \"Configuration/Icons.php should exist for icon registration (v12+)\"\n\n # Configuration/Sets/ is REQUIRED for sitepackage extensions in TYPO3 v13+\n # (Site Sets) but is N/A for service/provider/library extensions. Detect\n # sitepackage by the presence of typical sitepackage markers (page\n # templates, page.tsconfig, Yaml/Site/). If none of those exist → skip;\n # otherwise require Configuration/Sets/.\n - id: TC-54\n type: command\n pattern: 'if [ -e Configuration/page.tsconfig ] || [ -d Configuration/Yaml/Site ] || [ -d Resources/Private/Templates/Page ] || [ -d Resources/Private/Layouts/Page ]; then [ -d Configuration/Sets ]; else exit 0; fi'\n severity: info\n desc: \"Configuration/Sets/ required for sitepackage extensions in TYPO3 v13+ (detected via page templates / page.tsconfig / Yaml/Site/). Skipped for service/provider extensions.\"\n\n # === CI TOOLING CENTRALIZATION ===\n - id: TC-90\n type: json_path\n target: composer.json\n pattern: '.[\"require-dev\"][\"netresearch/typo3-ci-workflows\"]'\n severity: warning\n desc: \"require-dev should use netresearch/typo3-ci-workflows for centralized CI tooling\"\n\n - id: TC-91\n type: not_contains\n target: composer.json\n pattern: '\"friendsofphp/php-cs-fixer\"'\n severity: info\n desc: \"require-dev should not individually list php-cs-fixer (provided by typo3-ci-workflows)\"\n\n - id: TC-92\n type: file_exists\n target: Build/Scripts/runTests.sh\n severity: warning\n desc: \"Build/Scripts/runTests.sh should exist for local test execution\"\n\n - id: TC-93\n type: regex\n target: \"{phpstan.neon,Build/phpstan.neon,Build/phpstan/phpstan.neon,phpstan.neon.dist}\"\n pattern: 'typo3-ci-workflows'\n severity: info\n desc: \"PHPStan config should include shared config from typo3-ci-workflows\"\n\n # === TCA v13 PATTERN CHECKS ===\n - id: TC-100\n type: regex_not\n target: Configuration/TCA/**/*.php\n pattern: \"eval.*trim\"\n severity: warning\n desc: \"TCA files should not contain eval=trim; use trim option in fieldControl instead (v13+)\"\n\n - id: TC-101\n type: regex_not\n target: Configuration/TCA/**/*.php\n pattern: \"l10n_parent.*type.*group\"\n severity: error\n desc: \"TCA translation l10n_parent must not use type=group; use type=select with renderType=selectSingle\"\n\n - id: TC-102\n type: regex_not\n target: Configuration/TCA/**/*.php\n pattern: \"(prependAtCopy|hideAtCopy)\"\n severity: warning\n desc: \"TCA should not contain prependAtCopy or hideAtCopy; removed in TYPO3 v13\"\n\n - id: TC-103\n type: command\n command: |\n found=$(find Configuration/TCA/ -name '*.php' -exec grep -lP 'foreign_table' {} + 2>/dev/null)\n [ -z \"$found\" ] && exit 0\n missing=$(echo \"$found\" | while IFS= read -r f; do\n grep -LP 'default' \"$f\" 2>/dev/null\n done)\n if [ -n \"$missing\" ]; then\n echo \"TCA files with foreign_table but no default value: $missing\"\n exit 1\n fi\n exit 0\n severity: warning\n desc: \"TCA select fields with foreign_table should define a default value to avoid NULL issues\"\n\n # === DI / ARCHITECTURE CHECKS ===\n - id: TC-110\n type: command\n pattern: \"set -o pipefail; ! grep -rn 'GeneralUtility::makeInstance' Classes/ 2>/dev/null | grep -v 'Classes/Task/' | grep -v 'Classes/Scheduler/' | grep -v 'Typo3QuerySettings' | grep -v 'ConnectionPool' | grep -v 'QueryBuilder' | grep -v 'FlashMessage' | grep -q .\"\n severity: warning\n desc: \"Classes/ should use dependency injection instead of GeneralUtility::makeInstance for services (excludes Scheduler tasks, ConnectionPool, QueryBuilder, FlashMessage)\"\n\n - id: TC-111\n type: regex_not\n target: Classes/**/*.php\n pattern: \"PersistenceManager[^I]\"\n severity: warning\n desc: \"Should use PersistenceManagerInterface in type hints, not concrete PersistenceManager class\"\n\n - id: TC-112\n type: not_contains\n target: Classes/**/*.php\n pattern: \"GeneralUtility::makeInstance(ExtensionConfiguration\"\n severity: warning\n desc: \"Should inject ExtensionConfiguration via constructor DI instead of using GeneralUtility::makeInstance\"\n\n # === SECURITY PATTERN CHECKS ===\n - id: TC-120\n type: command\n command: |\n found=$(find Classes/ -name '*.php' -exec grep -lP '(shell_exec|\\bexec\\s*\\(|\\bsystem\\s*\\(|passthru\\s*\\()' {} + 2>/dev/null)\n if [ -n \"$found\" ]; then\n echo \"Prohibited function usage found in: $found\"\n exit 1\n fi\n exit 0\n severity: error\n desc: \"Must not use shell_exec, exec, system, or passthru; use CommandUtility or Symfony Process\"\n\n - id: TC-121\n type: not_contains\n target: Classes/**/*.php\n pattern: \"md5(uniqid\"\n severity: error\n desc: \"Must not use md5(uniqid()); use random_bytes() or GeneralUtility::makeInstance(Random::class) — md5(uniqid()) is cryptographically weak and a security vulnerability\"\n\n - id: TC-122\n type: regex_not\n target: Classes/**/*.php\n pattern: \"\\\\bserialize\\\\s*\\\\(\"\n severity: error\n desc: \"Must not use serialize(); prefer json_encode/json_decode — serialize() enables object injection attacks\"\n\n - id: TC-122b\n type: regex_not\n target: Classes/**/*.php\n pattern: \"\\\\bunserialize\\\\s*\\\\(\"\n severity: error\n desc: \"Must not use unserialize(); use json_decode instead — unserialize() without allowed_classes is an object injection vulnerability\"\n\n - id: TC-123\n type: command\n pattern: \"grep -rn '{[a-zA-Z]\\\\+}\\\\|{[a-zA-Z]\\\\+\\\\.' Resources/Private/Templates/ 2>/dev/null | grep -v 'f:format.raw\\\\|f:format.html\\\\|f:uri\\\\|f:link\\\\|f:translate\\\\|f:image\\\\|f:if\\\\|f:for\\\\|f:render\\\\|f:section\\\\|f:layout\\\\|f:comment\\\\|f:count\\\\|f:format.htmlspecialchars\\\\|f:security\\\\|f:be\\\\.' | head -5 && echo 'INFO: Check that dynamic Fluid output uses f:format.htmlspecialchars where appropriate' || true\"\n severity: warning\n desc: \"Fluid templates should use f:format.htmlspecialchars on dynamic output to prevent XSS\"\n\n # === TEMPLATE / BOOTSTRAP 5 CHECKS ===\n - id: TC-130\n type: regex_not\n target: Resources/Private/**/*.html\n pattern: \"(form-inline|form-row|btn-default|form-group)\"\n severity: warning\n desc: \"Templates should not use Bootstrap 4 classes (form-inline, form-row, btn-default); migrate to Bootstrap 5\"\n\n # === CI CONFIGURATION CHECKS ===\n - id: TC-140\n type: command\n command: |\n # Only fail if push trigger exists WITHOUT branch restrictions\n grep -A2 '^on:' .github/workflows/ci.yml 2>/dev/null | grep -q 'push:' || exit 0\n grep -A5 'push:' .github/workflows/ci.yml 2>/dev/null | grep -q 'branches' && exit 0\n echo 'ci.yml has unrestricted push trigger — should restrict to specific branches'\n exit 1\n severity: warning\n desc: \"CI workflow push trigger should be restricted to specific branches, not bare push:\"\n\n - id: TC-141\n type: json_path\n target: composer.json\n pattern: '.[\"require-dev\"][\"netresearch/typo3-ci-workflows\"]'\n severity: info\n desc: \"require-dev should reference netresearch/typo3-ci-workflows for centralized CI tooling\"\n\n # === PHPSTAN BASELINE CHECKS ===\n - id: TC-150\n type: command\n pattern: \"if [ -f phpstan-baseline.neon ]; then count=$(grep -c 'message:' phpstan-baseline.neon 2>/dev/null || echo 0); if [ \\\"$count\\\" -gt 10 ]; then echo \\\"WARN: PHPStan baseline has $count suppressed errors (should trend toward 0)\\\" && exit 1; fi; elif [ -f Build/phpstan-baseline.neon ]; then count=$(grep -c 'message:' Build/phpstan-baseline.neon 2>/dev/null || echo 0); if [ \\\"$count\\\" -gt 10 ]; then echo \\\"WARN: PHPStan baseline has $count suppressed errors (should trend toward 0)\\\" && exit 1; fi; fi; exit 0\"\n severity: warning\n desc: \"PHPStan baseline should trend toward zero suppressed errors (>10 indicates significant technical debt)\"\n\n # === TCA SEARCHFIELDS AND SORTBY CHECKS ===\n - id: TC-151\n type: command\n pattern: \"find Configuration/TCA/ -name '*.php' 2>/dev/null | xargs grep -lP \\\"'ctrl'\\\" 2>/dev/null | while read f; do if ! grep -qP \\\"'searchFields'\\\" \\\"$f\\\" 2>/dev/null; then echo \\\"FAIL: $f missing searchFields in ctrl\\\" && exit 1; fi; done || exit 1\"\n severity: error\n desc: \"All TCA ctrl arrays must define searchFields for backend search functionality\"\n\n - id: TC-152\n type: command\n pattern: \"find Configuration/TCA/ -name '*.php' 2>/dev/null | xargs grep -lP \\\"['\\\\\\\"]sortby['\\\\\\\"]\\\" 2>/dev/null | while read f; do if ! grep -qP \\\"['\\\\\\\"]default_sortby['\\\\\\\"]\\\" \\\"$f\\\" 2>/dev/null; then echo \\\"WARN: $f has sortby but no default_sortby\\\" && exit 1; fi; done\"\n severity: warning\n desc: \"TCA tables with sortby should also define default_sortby for consistent ordering\"\n\n # === DI INTERFACE ALIAS CHECKS ===\n - id: TC-153\n type: command\n pattern: \"interfaces=$(grep -rhoP '[A-Z][a-zA-Z0-9_]*Interface' Classes/ 2>/dev/null | sort -u); if [ -n \\\"$interfaces\\\" ] && [ -f Configuration/Services.yaml ]; then missing=0; for iface in $interfaces; do case $iface in ServerRequestInterface|ResponseInterface|ContainerInterface|LoggerInterface|EventDispatcherInterface|CacheInterface|RequestFactoryInterface|UriFactoryInterface|StreamFactoryInterface) continue ;; esac; if ! grep -q \\\"$iface\\\" Configuration/Services.yaml 2>/dev/null; then echo \\\"WARN: Interface $iface used in Classes/ but not aliased in Services.yaml\\\"; missing=1; fi; done; if [ \\\"$missing\\\" -eq 1 ]; then exit 1; fi; fi; exit 0\"\n severity: warning\n desc: \"Interfaces used for constructor injection should have explicit aliases in Services.yaml\"\n\n # === CACHE DOUBLE-LOOKUP ANTI-PATTERN ===\n - id: TC-154\n type: command\n pattern: \"[ -d Classes/ ] || exit 0; set -o pipefail; ! grep -rnP '->has\\\\(' Classes/ 2>/dev/null | sed -E 's/^[^:]+:[0-9]+://' | grep -iq cache\"\n severity: warning\n desc: \"Cache get should not be preceded by has() -- use get() directly and check for false (path-prefix stripped to avoid false-positives from Cache/ directory names)\"\n\n # === REPOSITORY QUERY PROPERTY NAME CHECK ===\n # Test PHP source under Classes/Domain/Repository/ for snake_case names\n # passed to ->equals() / ->like() / ->contains(). Filenames are stripped\n # before the snake_case check (sed) so directory names like Cache_Foo/\n # don't generate false positives.\n - id: TC-155\n type: command\n pattern: \"[ -d Classes/Domain/Repository/ ] || exit 0; set -o pipefail; ! grep -rn '\\\\->equals(' Classes/Domain/Repository/ 2>/dev/null | sed -E 's/^[^:]+:[0-9]+://' | grep -Eq '_[a-z]'\"\n severity: error\n desc: \"Extbase repository queries should use model property names (camelCase), not database column names (snake_case)\"\n\n # === XLIFF COMPLETENESS CHECK ===\n - id: TC-156\n type: command\n pattern: \"lll_refs=$(grep -rhoP 'LLL:EXT:[^/]+/Resources/Private/Language/[^:]+:[a-zA-Z0-9_.]+' Configuration/ Classes/ Resources/Private/Templates/ 2>/dev/null | sed 's/.*://' | sort -u); if [ -n \\\"$lll_refs\\\" ]; then missing=0; for key in $lll_refs; do if ! grep -rq \\\"id=\\\\\\\"$key\\\\\\\"\\\" Resources/Private/Language/ 2>/dev/null; then echo \\\"MISSING XLIFF key: $key\\\"; missing=$((missing+1)); fi; done; if [ $missing -gt 0 ]; then echo \\\"FAIL: $missing LLL references have no XLIFF trans-unit\\\" && exit 1; fi; fi; exit 0\"\n severity: error\n desc: \"All LLL: references in TCA, classes, and templates must have corresponding XLIFF trans-unit entries\"\n\n # === DEPRECATED CONSTANTS WITH ENUM REPLACEMENTS ===\n # Walk PHP files: detect @deprecated docblocks followed by a `const`\n # declaration (typical pattern — @deprecated lives in the docblock above\n # the const, not on the same line). Single-line greps would miss this.\n - id: TC-157\n type: command\n pattern: \"[ -d Classes/ ] || exit 0; set -o pipefail; if find Classes/ -type f -name '*.php' 2>/dev/null | xargs awk 'BEGIN{in_doc=0; doc_deprecated=0; pending_deprecated=0} /^[[:space:]]*\\\\/\\\\*\\\\*/{in_doc=1; doc_deprecated=0} {if (in_doc && /@deprecated/) doc_deprecated=1} in_doc && /\\\\*\\\\//{in_doc=0; if (doc_deprecated) pending_deprecated=1; next} pending_deprecated && /^[[:space:]]*(public|protected|private)?[[:space:]]*const[[:space:]]+/ {found=1; exit} pending_deprecated && /^[[:space:]]*(\\\\/\\\\*\\\\*|#|\\\\/\\\\/|$)/ {next} pending_deprecated {pending_deprecated=0} END{exit found ? 0 : 1}' 2>/dev/null && grep -rlqP '\\\\benum\\\\b' Classes/ 2>/dev/null; then exit 1; else exit 0; fi\"\n severity: warning\n desc: \"Deprecated string constants (in @deprecated docblock) should not coexist with enum replacements in active code paths\"\n\n # === EXCELLENCE INDICATORS (bonus) ===\n - id: TC-58\n type: file_exists\n target: .gitattributes\n severity: info\n desc: \".gitattributes should exist with export-ignore for smaller TER packages\"\n\n - id: TC-59\n type: file_exists\n target: Makefile\n severity: info\n desc: \"Makefile should exist for task automation\"\n\n # === TER PUBLISHING CHECKS ===\n - id: TC-55\n type: json_path\n target: composer.json\n pattern: '.support.issues'\n severity: warning\n desc: \"composer.json should define support.issues URL for bug tracker link\"\n\n - id: TC-56\n type: json_path\n target: composer.json\n pattern: '.support.source'\n severity: warning\n desc: \"composer.json should define support.source URL for repository link\"\n\n - id: TC-57\n type: json_path\n target: composer.json\n pattern: '.homepage'\n severity: warning\n desc: \"composer.json should define homepage URL\"\n\n # === BOOTSTRAP 5 MIGRATION CHECKS ===\n - id: TC-65\n type: regex_not\n target: \"Resources/Private/**/*.html\"\n pattern: 'data-dismiss=|data-toggle=\"(collapse|modal|tab|dropdown|tooltip|popover)\"|data-target=|data-ride=|data-slide='\n severity: warning\n desc: \"Legacy Bootstrap 4 data attributes detected. Migrate to Bootstrap 5: data-dismiss→data-bs-dismiss, data-toggle→data-bs-toggle, data-target→data-bs-target, data-ride→data-bs-ride, data-slide→data-bs-slide\"\n\n - id: TC-66\n type: regex_not\n target: \"Resources/Private/**/*.html\"\n pattern: '\\bclass=\"[^\"]*\\b(btn-default|ml-(\\d+|auto)|mr-(\\d+|auto)|pl-(\\d+|auto)|pr-(\\d+|auto)|float-left|float-right|font-weight-bold|font-weight-normal|font-italic)\\b'\n severity: warning\n desc: \"Legacy Bootstrap 4 CSS classes detected. Migrate: btn-default→btn-secondary, ml-*→ms-*, mr-*→me-*, pl-*→ps-*, pr-*→pe-*, float-left→float-start, float-right→float-end\"\n\n # === TEMPLATE QUALITY CHECKS ===\n - id: TC-67\n type: regex_not\n target: \"Resources/Private/**/*.html\"\n pattern: 'includeJavaScriptFiles|includeCssFiles'\n severity: error\n desc: \"Wrong f:be.pageRenderer attribute name. Use includeJsFiles (NOT includeJavaScriptFiles) and includeCssLibs (NOT includeCssFiles). Wrong names silently fail to load assets\"\n\n - id: TC-68\n type: regex_not\n target: \"Resources/Private/**/*.html\"\n pattern: '\u003cscript(?![^>]*\\bsrc=)[^>]*>'\n severity: error\n desc: \"Inline \u003cscript> tags violate CSP. Move JavaScript to external files loaded via f:be.pageRenderer includeJsFiles or AssetCollector\"\n\n # === CODECOV PATCH TARGET CHECK ===\n - id: TC-160\n type: command\n command: \"if [ ! -f codecov.yml ]; then exit 0; fi; target=$(sed -n '/patch:/,/target:/{ s/.*target:\\\\s*//p; }' codecov.yml 2>/dev/null | head -1 || echo auto); case \\\"$target\\\" in auto|'') exit 0 ;; 100%) echo \\\"WARN: codecov patch target is 100% — use 80% or auto when only unit test coverage is uploaded\\\" && exit 1 ;; *) exit 0 ;; esac\"\n severity: warning\n desc: \"codecov patch target should not be 100% — use 80% or auto when only unit test coverage is uploaded\"\n tags: [ci, codecov, coverage]\n\n # === PHPSTAN LEVEL 10 CHECK ===\n - id: TC-176\n type: command\n command: \"if ! grep -q 'typo3-ci-workflows' composer.json 2>/dev/null; then exit 0; fi; for f in Build/phpstan.neon phpstan.neon; do if [ -f \\\"$f\\\" ] && grep -qP 'level:\\\\s*(10|max)' \\\"$f\\\" 2>/dev/null; then exit 0; fi; done; echo 'WARN: PHPStan should use level 10 (max) for extensions using typo3-ci-workflows shared config' && exit 1\"\n severity: warning\n desc: \"PHPStan should use level 10 (max) for extensions using typo3-ci-workflows shared config\"\n tags: [phpstan, ci, static-analysis]\n\n # === PHPSTAN EMPTY BASELINE (ASPIRATIONAL) ===\n - id: TC-177\n type: regex_not\n target: \"{Build/phpstan-baseline.neon,phpstan-baseline.neon}\"\n pattern: '^\\s+-\\s*$\\n\\s+message:'\n severity: info\n desc: \"PHPStan baseline should be empty — zero suppressed errors is the target\"\n tags: [phpstan, baseline, aspirational]\n # Note: TC-177 is aspirational; TC-150 catches excessive baselines\n\nllm_reviews:\n # === CODE QUALITY REVIEWS ===\n - id: TC-60\n domain: code-quality\n prompt: |\n Review the Classes/ directory for TYPO3 coding standards compliance:\n 1. Check for proper namespace declarations matching PSR-4 autoload\n 2. Verify constructor injection is used for dependencies\n 3. Check that controllers extend ActionController or appropriate base class\n 4. Verify services are stateless and do not hold mutable state\n 5. Check for proper use of TYPO3 attributes (#[AsController], etc.)\n severity: warning\n desc: \"Classes should follow TYPO3 coding standards\"\n\n - id: TC-61\n domain: code-quality\n prompt: |\n Check for modern TYPO3 API usage:\n 1. Verify PSR-7 Request/Response is used in controllers\n 2. Check for proper use of Site and SiteLanguage objects\n 3. Verify TypoScriptFrontendController is not accessed directly\n 4. Check that Context API is used for user/workspace info\n 5. Verify FlexForm handling uses modern approaches\n severity: warning\n desc: \"Extension should use modern TYPO3 APIs\"\n\n - id: TC-62\n domain: code-quality\n prompt: |\n Review TCA configuration in Configuration/TCA/:\n 1. Check for proper ctrl configuration\n 2. Verify columns use modern renderType instead of deprecated types\n 3. Check showitem syntax is correct\n 4. Verify locallang references exist\n 5. Check for proper icon registration\n severity: info\n desc: \"TCA configuration should follow current standards\"\n\n # === TRANSLATION REVIEWS ===\n - id: TC-63\n name: Translation keys complete for all features\n type: llm_review\n severity: info\n domain: i18n\n prompt: |\n Verify that all user-visible strings in the extension have\n corresponding XLIFF translation keys. Check:\n - Controller error messages (should use LLL references or trait)\n - Template labels and buttons\n - TCA column labels\n - Module labels\n - Configuration labels in ext_conf_template.txt\n tags: [xliff, translation, i18n, completeness]\n\n # === DOCUMENTATION REVIEWS ===\n - id: TC-64\n domain: documentation\n prompt: |\n Check extension documentation completeness:\n 1. Verify README.md or Documentation/ exists\n 2. Check for installation instructions specific to TYPO3\n 3. Verify configuration options are documented\n 4. Check if TypoScript/FlexForm options are explained\n 5. Verify version compatibility is clearly stated\n severity: info\n desc: \"Extension documentation should be complete\"\n\n # === PUBLIC LISTING VERIFICATION ===\n - id: TC-80\n domain: publishing\n prompt: |\n Verify public listing presence and completeness for the extension.\n Extract the composer package name from composer.json and the extension\n key from ext_emconf.php, then check:\n\n 1. TER Listing (extensions.typo3.org/extension/{extension_key}):\n - Extension page exists and is accessible\n - Sidebar links are populated (Extension Manual, Found an Issue,\n Code Insights, Packagist.org)\n - If links are missing, they must be set via:\n TYPO3_API_TOKEN=... tailor ter:update \\\n --composer=\"vendor/package-name\" \\\n --manual=\"https://github.com/...\" \\\n --issues=\"https://github.com/.../issues\" \\\n --repository=\"https://github.com/...\" \\\n {extension_key}\n - Version shown matches latest release\n\n 2. Packagist Listing (packagist.org/packages/{vendor/name}):\n - Package exists and is findable\n - Description is meaningful (not empty)\n - License is set\n - Latest version is published\n\n 3. Documentation (docs.typo3.org):\n - If Documentation/ directory with guides.xml exists, check if\n rendered documentation is available at docs.typo3.org\n - Alternatively, README or external docs linked from TER\n\n Report missing or incomplete listings as actionable items.\n severity: warning\n desc: \"Extension should have complete TER, Packagist, and documentation listings\"\n\n - id: TC-81\n domain: publishing\n prompt: |\n Check the TER publishing workflow for metadata setup completeness:\n\n 1. Verify .github/workflows/ contains a TER publish workflow\n 2. Check if the workflow includes a tailor ter:update step that sets:\n - --composer (package name for Packagist link)\n - --manual (documentation/repository link)\n - --issues (issue tracker link)\n - --repository (source code link)\n 3. If ter:update is missing from the workflow, recommend adding it\n as a post-publish step or as a separate one-time setup step\n\n The ter:update command sets the sidebar links on extensions.typo3.org.\n Without it, the TER page shows no links to documentation, issues,\n source code, or Packagist - making the extension appear incomplete.\n\n This is a MANDATORY step for initial TER setup. The links persist\n across releases, so it only needs to run once (or when URLs change).\n severity: warning\n desc: \"TER publish workflow should include metadata setup via tailor ter:update\"\n\n # === CONFIGURATION SYNC REVIEWS ===\n - id: TC-69\n domain: configuration\n prompt: |\n Compare TYPO3 version constraints between ext_emconf.php and composer.json:\n 1. Check that 'typo3' constraint in ext_emconf.php constraints.depends matches composer.json require.typo3/cms-core version range\n 2. Check that PHP version constraint matches between both files\n 3. Flag any mismatch that would cause installation failures\n severity: error\n desc: \"ext_emconf.php and composer.json version constraints must be synchronized to prevent installation failures\"\n\n # === DEPENDENCY INJECTION REVIEWS ===\n - id: TC-70\n domain: architecture\n prompt: |\n Review Services.yaml for proper dependency injection patterns:\n 1. Check factory wiring uses correct syntax: factory: ['@ServiceClass', 'methodName'] with arguments\n 2. Check interface alias bindings map interfaces to concrete implementations\n 3. Check EventDispatcherInterface is properly autowired for event-dispatching services\n 4. Flag any manual service definition that could use autowiring instead\n severity: warning\n desc: \"Services.yaml should use proper factory wiring, interface bindings, and EventDispatcher autowiring patterns\"\n\n # === SECURITY REVIEWS ===\n - id: TC-71\n domain: security\n prompt: |\n Review custom ViewHelper classes for XSS prevention:\n 1. Check that ALL dynamic attribute values in tag() or manual HTML construction use htmlspecialchars() with ENT_QUOTES\n 2. Check that render() methods don't concatenate user input into HTML without escaping\n 3. Check that TagBuilder is used instead of manual string concatenation where possible\n 4. Flag any unescaped attribute value as critical XSS risk\n severity: error\n desc: \"Custom ViewHelper tag attributes must use htmlspecialchars(value, ENT_QUOTES) to prevent XSS\"\n\n # === TRANSLATION HYGIENE REVIEWS ===\n - id: TC-72\n domain: quality\n prompt: |\n Check for unused XLIFF translation keys:\n 1. List all trans-unit id values in Resources/Private/Language/*.xlf files\n 2. Search for each key in Classes/**/*.php (LLL: references) and Resources/Private/Templates/**/*.html (f:translate references)\n 3. Flag keys that appear in XLIFF but are never referenced in code or templates\n 4. Also flag translation keys referenced in code but missing from XLIFF\n severity: info\n desc: \"Unused XLIFF translation keys should be removed to maintain translation hygiene\"\n\n # === ASSESSMENT AUDIT REVIEWS (from comprehensive audit session) ===\n - id: TC-73\n domain: architecture\n prompt: |\n Review dependency injection completeness:\n 1. List all constructor parameters with interface type-hints in Classes/\n 2. Check Configuration/Services.yaml for explicit alias bindings for each interface\n 3. PSR interfaces (Psr\\*) and core TYPO3 interfaces (LoggerInterface, EventDispatcherInterface)\n are autowired and don't need aliases -- skip these\n 4. Custom or extension-specific interfaces MUST have explicit aliases in Services.yaml\n 5. Flag any interface used in constructor injection that lacks a Services.yaml alias\n severity: error\n desc: \"All custom interfaces used for constructor injection must have explicit aliases in Services.yaml\"\n\n - id: TC-74\n domain: i18n\n prompt: |\n Cross-reference all LLL: translation key usage with XLIFF definitions:\n 1. Collect all LLL:EXT:*/Resources/Private/Language/*:key references from TCA files,\n PHP classes, and Fluid templates\n 2. Parse all .xlf files in Resources/Private/Language/ for trans-unit id values\n 3. Report keys referenced in code but missing from XLIFF (broken translations)\n 4. Report keys defined in XLIFF but never referenced (dead translations)\n 5. Check that default language XLIFF has all keys used in the extension\n severity: error\n desc: \"All LLL: references must have corresponding XLIFF entries; orphaned XLIFF keys should be removed\"\n\n - id: TC-75\n domain: code-quality\n prompt: |\n Check for deprecated constants that coexist with enum replacements:\n 1. Find all class constants marked @deprecated in Classes/\n 2. Check if enum classes exist that provide replacement values\n 3. Search for references to the deprecated constants in active (non-deprecated) code paths\n 4. Flag any active code that still uses deprecated constants when enum equivalents exist\n 5. Recommend migration path from constants to enums\n severity: warning\n desc: \"Deprecated constants should not be referenced in active code when enum replacements exist\"\n\n - id: TC-76\n domain: performance\n prompt: |\n Check for cache double-lookup anti-pattern:\n 1. Find all cache interaction code in Classes/ (FrontendInterface, CacheManager)\n 2. Identify patterns where cache->has() is called before cache->get() in the same method\n 3. This is an anti-pattern because it requires two cache lookups instead of one\n 4. The correct pattern is: $result = $cache->get($key); if ($result === false) { ... }\n 5. Flag all instances of has()-then-get() pattern with suggested fix\n severity: warning\n desc: \"Cache has()+get() double-lookup should be replaced with get() and false check\"\n\n # === CODING STANDARDS REVIEWS ===\n - id: TC-95\n domain: coding-standards\n prompt: |\n Check that ALL PHP files have a copyright/license header comment block.\n This includes ext_localconf.php, ext_tables.php, Configuration/**/*.php,\n and Build/*.php. Exception: ext_emconf.php (TER cannot parse strict_types).\n severity: warning\n desc: \"Copyright headers on all PHP files including configuration\"\n\n # === ASSESSMENT-DERIVED CHECKPOINTS (quality audit findings) ===\n\n # --- Mechanical checks from assessment findings ---\n # Note: TC-160 (makeInstance) removed — covered by existing TC-110\n\n - id: TC-161\n name: datahandler-hook-completeness\n type: command\n pattern: \"if grep -qP 'processDatamapClass' ext_localconf.php 2>/dev/null; then if ! grep -qP 'processCmdmapClass' ext_localconf.php 2>/dev/null; then echo 'WARNING: processDatamapClass registered but processCmdmapClass missing -- delete/copy operations will not be handled'; exit 1; fi; fi; exit 0\"\n severity: warning\n desc: \"If processDatamapClass is registered, processCmdmapClass should also be registered for delete/copy handling\"\n tags: [hooks, datahandler, completeness, assessment]\n\n - id: TC-162\n name: changelog-completeness\n type: command\n command: |\n [ ! -f CHANGELOG.md ] && exit 0\n missing=0\n for tag in $(git tag --sort=-v:refname 2>/dev/null | head -20); do\n version=$(echo \"$tag\" | sed 's/^v//')\n if ! grep -qF \"## [${version}]\" CHANGELOG.md 2>/dev/null; then\n echo \"WARNING: No CHANGELOG.md entry for tag $tag\"\n missing=$((missing+1))\n fi\n done\n if [ \"$missing\" -gt 0 ]; then\n echo \"WARNING: $missing tagged versions missing from CHANGELOG.md\"\n exit 1\n fi\n exit 0\n severity: warning\n desc: \"CHANGELOG.md should have entries for all tagged versions (compare git tags vs changelog headings)\"\n tags: [documentation, changelog, completeness, assessment]\n\n # --- LLM review checks from assessment findings ---\n\n - id: TC-96\n name: version-consistency\n domain: configuration\n prompt: |\n Compare the version declared in guides.xml (if it exists in Documentation/)\n with the version in ext_emconf.php:\n 1. Extract the \u003crelease> or \u003cversion> value from Documentation/guides.xml\n 2. Extract the 'version' value from ext_emconf.php\n 3. These MUST match exactly\n 4. Also check composer.json version field if present\n 5. Flag any mismatch as it causes documentation rendering to show wrong version\n severity: error\n desc: \"guides.xml version must match ext_emconf.php version to avoid documentation version mismatch\"\n tags: [documentation, version, sync, assessment]\n\n - id: TC-97\n name: n-plus-one-queries\n domain: performance\n prompt: |\n Detect N+1 query patterns in Classes/:\n 1. Look for loop constructs (foreach, for, while) that contain calls to\n repository methods (findBy*, findAll, findOneBy*) or adapter/client\n methods (retrieve, fetch, get, list, query) inside the loop body\n 2. The classic pattern is: list items in outer call, then retrieve details\n for each item inside a loop (list-then-retrieve anti-pattern)\n 3. Flag any repository or service method called inside a loop that could\n be batched into a single query with an IN-clause or bulk fetch\n 4. Check for DataMapper/QueryBuilder calls inside foreach loops\n 5. Suggest batching strategy for each finding\n severity: warning\n desc: \"Detect loop-inside-loop patterns where inner loop calls repository/adapter methods (N+1 query anti-pattern)\"\n tags: [performance, queries, n-plus-one, assessment]\n\n - id: TC-98\n name: interface-implementation-sync\n domain: architecture\n prompt: |\n Check that interface and implementation classes are synchronized:\n 1. Find all interfaces defined in Classes/ (files ending in Interface.php)\n 2. For each interface, find the implementing class(es)\n 3. Compare public methods: all public methods on the implementation that\n are part of the domain contract should be declared in the interface\n 4. Flag public methods on implementations that are missing from their interface\n (excluding __construct, framework-required methods, and getter/setter pairs\n for internal state)\n 5. This ensures the interface remains the complete contract for DI consumers\n severity: warning\n desc: \"All public domain methods on implementation classes should be declared in their interface\"\n tags: [architecture, interfaces, di, assessment]\n\n - id: TC-99\n name: hardcoded-template-strings\n domain: i18n\n prompt: |\n Check Fluid templates (.html) in Resources/Private/ for hardcoded English strings:\n 1. Scan all .html files for quoted string attributes containing 3+ English words\n (e.g. title=\"Click here to submit\", placeholder=\"Enter your name\")\n 2. Check for bare text content between HTML tags that is clearly English prose\n (not Fluid expressions, not HTML entities, not CSS class names)\n 3. These should use f:translate viewhelper or LLL: references instead\n 4. Exclude: Fluid namespace declarations, xmlns attributes, CSS class names,\n JavaScript code, and technical identifiers\n 5. Flag each finding with the file, line, and suggested f:translate replacement\n severity: warning\n desc: \"Fluid templates should use f:translate instead of hardcoded English strings (check quoted strings >3 words in attributes)\"\n tags: [i18n, templates, hardcoded-strings, assessment]\n\n - id: TC-100b\n name: makefile-agents-consistency\n domain: documentation\n prompt: |\n Cross-reference AGENTS.md and Makefile for consistency:\n 1. Parse AGENTS.md for any documented make targets or commands referencing\n 'make \u003ctarget>' patterns\n 2. Parse Makefile for actually defined targets\n 3. Flag targets mentioned in AGENTS.md that do not exist in Makefile\n 4. Flag important Makefile targets that are not documented in AGENTS.md\n 5. This ensures agent instructions match available automation\n severity: info\n desc: \"Targets documented in AGENTS.md should exist in Makefile and vice versa\"\n tags: [documentation, consistency, makefile, agents, assessment]\n\n - id: TC-100c\n name: upgrade-wizard-for-schema\n domain: architecture\n prompt: |\n Check if database schema changes have corresponding upgrade wizards:\n 1. Parse ext_tables.sql for column definitions\n 2. Check git history or compare with previous tagged versions for NEW columns\n (columns not present in earlier versions)\n 3. If new columns are added that require data migration (not just nullable\n additions), verify an UpgradeWizardInterface implementation exists in\n Classes/Updates/ or Classes/Upgrade/\n 4. New required columns (NOT NULL without DEFAULT), renamed columns, or\n type changes especially need upgrade wizards\n 5. Flag schema changes that likely need migration but lack an upgrade wizard\n severity: warning\n desc: \"New ext_tables.sql columns requiring data migration should have an UpgradeWizardInterface implementation\"\n tags: [architecture, upgrade, schema, assessment]\n\n - id: TC-100d\n name: ext-conf-template-alignment\n domain: configuration\n prompt: |\n Verify ext_conf_template.txt options match implemented functionality:\n 1. Parse all option keys from ext_conf_template.txt (or system.yaml\n extension configuration)\n 2. Search Classes/ for references to each configuration option via\n ExtensionConfiguration->get() or $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']\n 3. Flag options defined in ext_conf_template.txt but never read in code\n (phantom options for unimplemented features)\n 4. Flag options read in code but not defined in ext_conf_template.txt\n (missing configuration UI)\n 5. Check that option descriptions match actual behavior\n severity: warning\n desc: \"Options in ext_conf_template.txt should match implemented functionality (no phantom options)\"\n tags: [configuration, alignment, assessment]\n\n # === MULTI-VERSION DEPENDENCY COMPATIBILITY CHECKS ===\n - id: TC-170\n name: multi-version-direct-api-usage\n type: command\n command: |\n # Check if composer.json has multi-major-version constraints (^X || ^Y)\n multi_deps=$(grep -oP '\"[^\"]+\"\\s*:\\s*\"[^\"]*\\|\\|[^\"]*\"' composer.json 2>/dev/null | grep -vP '^\"php\"\\s*:|^\"typo3/cms-[^\"]*\"\\s*:')\n [ -z \"$multi_deps\" ] && exit 0\n # For each multi-version dep, check if version-specific classes are used outside Adapter/\n violations=0\n while IFS= read -r dep; do\n pkg=$(echo \"$dep\" | grep -oP '\"[^\"]+\"' | head -1 | tr -d '\"')\n # Use the vendor segment (before the first /) as a namespace prefix and search case-insensitively\n vendor=$(echo \"$pkg\" | cut -d'/' -f1)\n vendor_ns_prefix=$(echo \"$vendor\" | sed 's|/|\\\\\\\\|g')\n if grep -rinP \"use\\s+${vendor_ns_prefix}\\\\\\\\\\\\\\\\\" Classes/ 2>/dev/null | grep -vP 'Adapter/|Factory/' | head -3; then\n echo \"WARN: Direct use of multi-version dependency $pkg outside Adapter/ classes\"\n violations=1\n fi\n done \u003c\u003c\u003c\"$multi_deps\"\n [ \"$violations\" -gt 0 ] && exit 1\n exit 0\n severity: warning\n desc: \"Multi-version dependencies (^X || ^Y) should be accessed through adapter interfaces, not used directly in business logic\"\n tags: [architecture, multi-version, adapter, dependency]\n\n - id: TC-171\n name: multi-version-adapter-interface-exists\n type: command\n command: |\n multi_deps=$(grep -oP '\"[^\"]+\"\\s*:\\s*\"[^\"]*\\|\\|[^\"]*\"' composer.json 2>/dev/null | grep -vP '^\"php\"\\s*:|^\"typo3/cms-[^\"]*\"\\s*:')\n [ -z \"$multi_deps\" ] && exit 0\n # Only require adapters when TC-170 detected direct API usage (version-specific code)\n # Without direct API usage evidence, the dependency APIs may be identical across versions\n if [ -d Classes/Adapter ] || [ -d Classes/Service ]; then\n ifaces=$(find Classes/Adapter Classes/Service -name '*Interface.php' 2>/dev/null)\n if [ -z \"$ifaces\" ]; then\n echo \"HINT: Multi-version dependencies found but no adapter interfaces in Classes/Adapter/ or Classes/Service/ — consider adding adapters if APIs differ between versions\"\n fi\n else\n echo \"HINT: Multi-version dependencies found but no Classes/Adapter/ directory — consider adding adapters if APIs differ between versions\"\n fi\n exit 0\n severity: info\n desc: \"Extensions with multi-version dependencies may benefit from adapter interfaces when APIs differ between major versions\"\n tags: [architecture, multi-version, adapter, interface]\n\n - id: TC-172\n name: adapter-interface-wired-in-services-yaml\n type: command\n command: |\n # Find adapter interfaces in Classes/\n ifaces=$(find Classes/Adapter Classes/Service -name '*Interface.php' 2>/dev/null | sed 's|^Classes/||')\n [ -z \"$ifaces\" ] && exit 0\n [ ! -f Configuration/Services.yaml ] && echo \"FAIL: Adapter interfaces exist but no Services.yaml\" && exit 1\n missing=0\n # Iterate safely over newline-delimited interfaces and search by short interface name\n while IFS= read -r iface; do\n # Normalise any backslashes to forward slashes, then extract the short class name without extension\n iface_path=$(printf '%s\\n' \"$iface\" | sed 's|\\\\|/|g')\n classname=${iface_path##*/}\n classname=${classname%.php}\n if ! grep -q \"$classname\" Configuration/Services.yaml 2>/dev/null; then\n echo \"FAIL: Interface $iface not wired in Services.yaml\"\n missing=1\n fi\n done \u003c\u003c\u003c\"$ifaces\"\n [ \"$missing\" -eq 1 ] && exit 1\n exit 0\n severity: error\n desc: \"Adapter interfaces must be wired in Services.yaml (alias or factory) for dependency injection to resolve them\"\n tags: [architecture, di, services-yaml, adapter]\n\n - id: TC-173\n name: concrete-adapter-typehint\n type: command\n command: |\n # Find concrete adapter implementations (non-interface PHP files in Adapter/)\n adapters=$(find Classes/Adapter -name '*.php' ! -name '*Interface.php' ! -name '*Factory.php' 2>/dev/null | sed 's|Classes/||;s|\\.php||;s|/|\\\\\\\\|g')\n [ -z \"$adapters\" ] && exit 0\n violations=0\n for adapter in $adapters; do\n shortname=$(echo \"$adapter\" | grep -oP '[^\\\\]+

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

)\n # Check if concrete adapter class is type-hinted in constructors outside Adapter/\n if grep -rnP \"(private|protected|public)\\s+(readonly\\s+)?${shortname}\\s+\" Classes/ 2>/dev/null | grep -vP 'Adapter/|Factory/' | head -3; then\n echo \"WARN: Concrete adapter class $shortname type-hinted in constructor — use interface instead\"\n violations=1\n fi\n done\n [ \"$violations\" -gt 0 ] && exit 1\n exit 0\n severity: warning\n desc: \"Constructors should type-hint adapter interfaces, not concrete adapter implementations\"\n tags: [architecture, di, adapter, type-hint]\n\n # === PHPSTAN MULTI-VERSION COMPATIBILITY CHECKS ===\n - id: TC-174\n name: phpstan-version-specific-ignore\n type: command\n command: |\n # Check for @phpstan-ignore tags in PHP files that reference version-specific issues\n found=$(grep -rnP '@phpstan-ignore\\s+(method\\.notFound|call\\.notFound|argument\\.type)' Classes/ 2>/dev/null | head -5)\n if [ -n \"$found\" ]; then\n # Only flag if multi-version deps exist\n multi_deps=$(grep -oP '\"[^\"]+\"\\s*:\\s*\"[^\"]*\\|\\|[^\"]*\"' composer.json 2>/dev/null | grep -vP '^\"php\"\\s*:|^\"typo3/cms-[^\"]*\"\\s*:')\n if [ -n \"$multi_deps\" ]; then\n echo \"$found\"\n echo \"WARN: @phpstan-ignore tags for method/call/argument issues may be version-specific — use adapter pattern instead\"\n exit 1\n fi\n fi\n exit 0\n severity: warning\n desc: \"PHPStan ignore tags for method.notFound/call.notFound/argument.type suggest version-specific code — use adapter pattern instead\"\n tags: [phpstan, multi-version, ignore-tags]\n\n - id: TC-175\n name: method-exists-typed-parameter\n type: command\n command: |\n # Check for method_exists() calls with typed (non-object) first argument\n # This causes PHPStan type narrowing issues across versions\n found=$(grep -rnP 'method_exists\\s*\\(\\s*\\

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

Classes/ 2>/dev/null | head -10)\n [ -z \"$found\" ] && exit 0\n # Check if any of these are inside methods with typed parameters (not object type)\n violations=0\n while IFS= read -r line; do\n file=$(echo \"$line\" | cut -d: -f1)\n varname=$(echo \"$line\" | grep -oP 'method_exists\\s*\\(\\s*\\$\\K[a-zA-Z_]+')\n # Check if the variable has a concrete type hint (not object) in the method signature\n if grep -P \"(${varname})\\s*[,)]\" \"$file\" 2>/dev/null | grep -vP 'object\\s+\\

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

| grep -P '[A-Z][a-zA-Z]+\\s+\\

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

\"${varname}\" | head -1; then\n echo \"WARN: method_exists() used with typed parameter \\$varname — use 'object' type to avoid PHPStan narrowing issues\"\n violations=1\n fi\n done \u003c\u003c\u003c\"$found\"\n [ \"$violations\" -gt 0 ] && exit 1\n exit 0\n severity: warning\n desc: \"method_exists() for version detection should use 'object' type parameter to avoid PHPStan type narrowing across versions\"\n tags: [phpstan, multi-version, method-exists, type-narrowing]\n\n # === MULTI-VERSION DEPENDENCY LLM REVIEWS ===\n - id: TC-100f\n name: multi-version-api-divergence\n domain: architecture\n prompt: |\n Check for multi-version dependency compatibility:\n 1. Parse composer.json for dependencies with multi-major-version constraints\n (e.g., \"^3 || ^4\", \"^1.0 || ^2.0\") — exclude php and typo3/cms-* packages\n 2. For each such dependency, identify API differences between the major versions\n that could cause runtime errors (changed method signatures, removed classes,\n renamed methods, changed constructor arguments)\n 3. Check if the extension uses an adapter/interface pattern to abstract these\n differences, or if version-specific APIs are called directly in business logic\n 4. Verify that adapter implementations exist for EACH supported major version\n 5. Flag any direct usage of version-specific APIs outside of adapter classes\n severity: error\n desc: \"Multi-version dependencies must use adapter pattern when APIs differ between major versions\"\n tags: [architecture, multi-version, adapter, dependency, assessment]\n\n - id: TC-100g\n name: phpstan-multi-version-analysis\n domain: code-quality\n prompt: |\n Check PHPStan configuration and ignore tags for multi-version compatibility:\n 1. Check if PHPStan config includes analysis for all supported dependency versions\n 2. Find all @phpstan-ignore and @phpstan-ignore-next-line tags in Classes/\n 3. Determine if any ignore tags exist BECAUSE of version-specific API differences\n (e.g., ignoring method.notFound for a method that only exists in one version)\n 4. These version-specific ignores will cause PHPStan errors when analyzed against\n the other version — they must be replaced with adapter pattern\n 5. Check that PHPStan baseline does not contain version-specific suppressions\n severity: warning\n desc: \"PHPStan ignore tags must not be version-specific — replace with adapter pattern for multi-version deps\"\n tags: [phpstan, multi-version, static-analysis, assessment]\n\n - id: TC-100h\n name: services-yaml-adapter-wiring\n domain: architecture\n prompt: |\n Verify Services.yaml properly wires adapter interfaces for DI:\n 1. Find all interface files in Classes/Adapter/ or Classes/Service/ directories\n 2. Check Configuration/Services.yaml for explicit alias or factory wiring for\n each adapter interface\n 3. Verify that constructor parameters throughout the codebase type-hint the\n interface, NOT the concrete adapter implementation\n 4. If a factory pattern is used, verify the factory class exists and has a\n create() method that performs version detection\n 5. Flag: interfaces without Services.yaml wiring, concrete type-hints where\n interface exists, missing factory implementations\n severity: error\n desc: \"Adapter interfaces must be wired in Services.yaml and type-hinted in constructors (not concrete classes)\"\n tags: [architecture, di, services-yaml, adapter, assessment]\n\n - id: TC-100e\n name: domain-model-validation\n domain: architecture\n prompt: |\n Check domain models with cryptographic or hash fields for proper validation:\n 1. Find domain model classes in Classes/Domain/Model/ that contain properties\n for hashes, checksums, signatures, tokens, or cryptographic values\n 2. Check if these models have a named constructor (static factory method)\n that validates field consistency (e.g. verifying a checksum matches its\n source data, or a signature is valid for its payload)\n 3. If models only have a default constructor with setters, there is no\n guarantee of field consistency at creation time\n 4. Flag models with crypto fields that lack validation in their construction\n 5. Recommend implementing a named constructor pattern like\n ::createVerified() or ::fromPayload() that enforces invariants\n severity: warning\n desc: \"Domain models with cryptographic fields should have a named constructor that validates field consistency\"\n tags: [architecture, domain-model, validation, security, assessment]\n\n # === PSR-3 LOGGING COMPLIANCE (relocated from php-modernization PM-26) ===\n - id: TC-180\n name: psr3-logger-aware-nulllogger-default\n domain: code-quality\n prompt: |\n Review the TYPO3 extension codebase for PSR-3 logging compliance.\n\n Check for:\n 1. Classes implementing LoggerAwareInterface should use LoggerAwareTrait\n 2. Constructor should set $this->logger = new NullLogger() as default\n 3. A private getLogger(): LoggerInterface helper should exist to narrow\n the nullable trait property for PHPStan\n 4. No nullable $logger properties with null checks scattered in methods\n 5. Logger should NOT be a required constructor parameter\n\n Examine files in Classes/ directory. Report specific violations.\n severity: warning\n desc: \"TYPO3 services must use LoggerAwareInterface with NullLogger default (not nullable)\"\n tags: [psr-3, logging, logger-aware, null-logger, typo3]\n\n # === PSR-14 EVENT DISPATCH SAFETY (relocated from php-modernization PM-27) ===\n - id: TC-181\n name: psr14-event-dispatch-try-catch\n domain: code-quality\n prompt: |\n Review the TYPO3 extension codebase for PSR-14 event dispatch safety.\n\n Check for:\n 1. Event dispatch calls that are NOT wrapped in try/catch for post-processing\n or notification events (listener failures must not break main flow)\n 2. Validation events that correctly do NOT catch exceptions (listeners may\n legitimately stop flow)\n 3. Caught exceptions should be logged with context (event class, exception)\n 4. Event classes should be final with readonly constructor properties\n\n Examine files in Classes/ directory. Report unguarded dispatches that should\n be guarded, and guarded dispatches that should not be.\n severity: warning\n desc: \"PSR-14 event dispatch must be try/catch guarded for non-validation events\"\n tags: [psr-14, events, event-dispatcher, typo3]\n\n # === FACTORY PATTERN + PHPSTAN BASELINE PRACTICES (relocated from php-modernization PM-28) ===\n - id: TC-182\n name: factory-capability-fallback-baseline\n domain: code-quality\n prompt: |\n Review the TYPO3 extension for factory pattern and PHPStan baseline practices.\n\n Check for:\n 1. Factory classes that select implementations at runtime should have explicit\n fallback chains (e.g., Imagick -> GD -> RuntimeException)\n 2. Services.yaml should wire factories correctly using the factory key\n 3. PHPStan baseline (phpstan-baseline.neon) should be shrinking, not growing\n 4. No new baseline entries should be added — issues should be fixed immediately\n 5. ProcessorInterface pattern: services with process() + canProcess() for\n middleware-style decoupling\n\n Report specific issues with factory wiring, baseline growth, or missing\n processor interfaces.\n severity: info\n desc: \"Factory patterns must have capability fallback; PHPStan baseline must shrink\"\n tags: [factory, services-yaml, phpstan, baseline, processor-interface, typo3]\n\n # === MAKEINSTANCE TO DEPENDENCY INJECTION (relocated from php-modernization PM-33) ===\n - id: TC-183\n name: makeinstance-in-di-managed-classes\n domain: code-quality\n prompt: |\n Review the codebase for GeneralUtility::makeInstance() calls in classes\n that are registered in Configuration/Services.yaml (or Services.php).\n\n Classes managed by the DI container should use constructor injection instead\n of GeneralUtility::makeInstance(). Check for:\n 1. Classes listed in Services.yaml that call GeneralUtility::makeInstance()\n 2. Suggest constructor injection for each dependency resolved via makeInstance()\n 3. EXCEPTION: Scheduler task classes extending AbstractTask — these are\n unserialized by the TYPO3 scheduler, so constructor DI is incompatible.\n Do NOT flag makeInstance() in AbstractTask subclasses.\n 4. Also acceptable: makeInstance() for value objects or non-service classes\n that are not in the DI container (e.g., DataHandler, FlashMessage).\n\n Report specific files and line numbers where makeInstance() should be\n replaced with constructor injection.\n severity: warning\n desc: \"GeneralUtility::makeInstance() in DI-managed classes should use constructor injection (except AbstractTask)\"\n tags: [di, dependency-injection, makeinstance, typo3, services]\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":61727,"content_sha256":"2c7a536996c78b2325c4427010a2a27d83b9e29e9d88f24594d4ffcdd6f8becc"},{"filename":"references/backend-module-v13.md","content":"# TYPO3 v13 Backend Module Modernization\n\n**Purpose:** Comprehensive guide for modernizing TYPO3 backend modules to v13 LTS standards\n**Source:** Real-world modernization of nr_temporal_cache backend module (45/100 → 95/100 compliance)\n**Target:** TYPO3 v13.4 LTS with PSR-12, modern JavaScript, and accessibility compliance\n\n---\n\n## Critical Compliance Issues\n\n### 1. Extension Key Consistency\n\n**Problem:** Mixed extension keys throughout templates and JavaScript breaks translations and routing\n\n**Common Violations:**\n- Template translation keys using `EXT:wrong_name/` instead of `EXT:correct_name/`\n- JavaScript alert messages with hardcoded wrong extension names\n- Variable substitution using wrong extension prefix\n\n**Detection:**\n```bash\n# Find all extension key references\ngrep -rn \"EXT:temporal_cache/\" Resources/Private/Templates/\ngrep -rn \"EXT:temporal_cache/\" Resources/Public/JavaScript/\n\n# Verify correct key in ext_emconf.php\ngrep \"\\$EM_CONF\\[\" ext_emconf.php\n```\n\n**Example Violations:**\n```html\n\u003c!-- WRONG: Using temporal_cache instead of nr_temporal_cache -->\n\u003cf:translate key=\"LLL:EXT:temporal_cache/Resources/Private/Language/locallang_mod.xlf:dashboard.title\" />\n\n\u003c!-- CORRECT: Using proper extension key -->\n\u003cf:translate key=\"LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:dashboard.title\" />\n```\n\n```javascript\n// WRONG: Hardcoded wrong extension name in alert\nalert('Error in Temporal Cache extension');\n\n// CORRECT: Use TYPO3 Notification API with correct name\nNotification.error('Error', 'Failed in nr_temporal_cache');\n```\n\n**Impact:** Broken translations, 404 errors on static assets, module registration failures\n\n**Severity:** 🔴 Critical - Breaks basic functionality\n\n**Fix Priority:** Immediate - Fix before any other modernization work\n\n---\n\n### 2. CSP Compliance: No Inline Scripts or Styles\n\n**Problem:** Inline `\u003cscript>` and `\u003cstyle>` blocks violate Content Security Policy (CSP). TYPO3 v13+ backend enforces CSP by default, so inline code will be blocked silently.\n\n**TYPO3 v13 Standard:** Move ALL inline JavaScript and CSS to external files. Load via `f:be.pageRenderer` attributes or `PageRenderer` PHP API.\n\n**Before (CSP VIOLATION):**\n```html\n\u003cf:section name=\"Content\">\n \u003cstyle>\n .my-module .status { color: green; }\n \u003c/style>\n \u003c!-- template content -->\n\u003c/f:section>\n\u003cf:section name=\"FooterAssets\">\n \u003cscript>\n document.addEventListener('DOMContentLoaded', function() { /* ... */ });\n \u003c/script>\n\u003c/f:section>\n```\n\n**After (CSP-COMPLIANT):**\n```html\n\u003c!-- Resources/Private/Layouts/Module.html -->\n\u003cf:be.pageRenderer\n includeCssFiles=\"{0: 'EXT:my_extension/Resources/Public/Css/BackendModule.css'}\"\n includeJsFiles=\"{0: 'EXT:my_extension/Resources/Public/JavaScript/BackendModule.js'}\"\n/>\n```\n\nOr via PHP in the controller:\n```php\n$moduleTemplate->getPageRenderer()->addCssFile(\n 'EXT:my_extension/Resources/Public/Css/BackendModule.css'\n);\n$moduleTemplate->getPageRenderer()->loadJavaScriptModule(\n '@vendor/my-extension/backend-module.js'\n);\n```\n\n> **WARNING: `f:be.pageRenderer` Attribute Name Gotcha**\n>\n> The correct attribute for JavaScript files is **`includeJsFiles`**, NOT `includeJavaScriptFiles`.\n> Fluid silently ignores unknown ViewHelper attributes, so using the wrong name produces\n> **no error and no output** — your JS simply will not load. This is a common and hard-to-debug mistake.\n>\n> ```html\n> \u003c!-- WRONG: silently fails, no error, no JS loaded -->\n> \u003cf:be.pageRenderer includeJavaScriptFiles=\"{0: 'EXT:my_ext/...'}\" />\n>\n> \u003c!-- CORRECT: loads the JS file -->\n> \u003cf:be.pageRenderer includeJsFiles=\"{0: 'EXT:my_ext/...'}\" />\n> ```\n>\n> Similarly, use `includeCssFiles` (not `includeCssStylesheetFiles` or similar).\n\n**Detection:**\n```bash\n# Check for inline scripts/styles in templates (CSP violations)\ngrep -rn \"\u003cscript\" Resources/Private/Templates/\ngrep -rn \"\u003cstyle\" Resources/Private/Templates/\n\n# Check for wrong f:be.pageRenderer attribute names (silent failures)\ngrep -rn \"includeJavaScriptFiles\" Resources/Private/\n```\n\n**Impact:** CSP compliance, prevents silent JS loading failures, better caching, maintainability\n\n**Severity:** 🔴 Critical - TYPO3 v13 backend CSP enforcement blocks inline code silently\n\n---\n\n### 3. JavaScript Modernization (ES6 Modules)\n\n**Problem:** Inline JavaScript in templates is deprecated, not CSP-compliant, and hard to maintain\n\n**TYPO3 v13 Standard:** All JavaScript must be ES6 modules loaded via PageRenderer\n\n**Before (DEPRECATED):**\n```html\n\u003c!-- Resources/Private/Templates/Backend/TemporalCache/Content.html -->\n\u003cf:section name=\"Content\">\n \u003c!-- Template content -->\n\u003c/f:section>\n\n\u003cf:section name=\"FooterAssets\">\n \u003cscript type=\"text/javascript\">\n // 68 lines of inline JavaScript\n document.addEventListener('DOMContentLoaded', function() {\n const selectAll = document.getElementById('select-all');\n const checkboxes = document.querySelectorAll('.content-checkbox');\n const harmonizeBtn = document.getElementById('harmonize-btn');\n\n selectAll.addEventListener('change', function(e) {\n checkboxes.forEach(cb => cb.checked = e.target.checked);\n });\n\n harmonizeBtn.addEventListener('click', function() {\n if (confirm('Really harmonize?')) {\n // AJAX call with alert() feedback\n }\n });\n });\n \u003c/script>\n\u003c/f:section>\n```\n\n**After (MODERN v13):**\n\n**Step 1: Create ES6 Module** (`Resources/Public/JavaScript/BackendModule.js`)\n```javascript\n/**\n * Backend module JavaScript for nr_temporal_cache\n * TYPO3 v13 ES6 module\n */\nimport Modal from '@typo3/backend/modal.js';\nimport Notification from '@typo3/backend/notification.js';\n\nclass TemporalCacheModule {\n constructor() {\n this.initializeEventListeners();\n }\n\n initializeEventListeners() {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => this.init());\n } else {\n this.init();\n }\n }\n\n init() {\n this.initializeHarmonization();\n this.initializeKeyboardNavigation();\n }\n\n initializeHarmonization() {\n const selectAllCheckbox = document.getElementById('select-all');\n const contentCheckboxes = document.querySelectorAll('.content-checkbox');\n const harmonizeBtn = document.getElementById('harmonize-selected-btn');\n\n if (!harmonizeBtn) return;\n\n if (selectAllCheckbox) {\n selectAllCheckbox.addEventListener('change', (e) => {\n contentCheckboxes.forEach(checkbox => {\n checkbox.checked = e.target.checked;\n });\n this.updateHarmonizeButton();\n });\n }\n\n contentCheckboxes.forEach(checkbox => {\n checkbox.addEventListener('change', () => this.updateHarmonizeButton());\n });\n\n harmonizeBtn.addEventListener('click', () => this.performHarmonization());\n }\n\n async performHarmonization() {\n const selectedUids = Array.from(document.querySelectorAll('.content-checkbox:checked'))\n .map(cb => parseInt(cb.dataset.uid));\n\n if (selectedUids.length === 0) return;\n\n const harmonizeBtn = document.getElementById('harmonize-selected-btn');\n const harmonizeUri = harmonizeBtn.dataset.actionUri;\n\n // Use TYPO3 Modal instead of confirm()\n Modal.confirm(\n 'Confirm Harmonization',\n `Harmonize ${selectedUids.length} content elements?`,\n Modal.SeverityEnum.warning,\n [\n {\n text: 'Cancel',\n active: true,\n btnClass: 'btn-default',\n trigger: () => Modal.dismiss()\n },\n {\n text: 'Harmonize',\n btnClass: 'btn-warning',\n trigger: () => {\n Modal.dismiss();\n this.executeHarmonization(harmonizeUri, selectedUids);\n }\n }\n ]\n );\n }\n\n async executeHarmonization(uri, selectedUids) {\n try {\n const response = await fetch(uri, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ content: selectedUids, dryRun: false })\n });\n\n const data = await response.json();\n\n if (data.success) {\n // Use TYPO3 Notification API instead of alert()\n Notification.success('Harmonization Successful', data.message);\n setTimeout(() => window.location.reload(), 1500);\n } else {\n Notification.error('Harmonization Failed', data.message);\n }\n } catch (error) {\n Notification.error('Error', 'Failed to harmonize content: ' + error.message);\n }\n }\n\n initializeKeyboardNavigation() {\n document.addEventListener('keydown', (e) => {\n // Ctrl/Cmd + A: Select all\n if ((e.ctrlKey || e.metaKey) && e.key === 'a') {\n const selectAll = document.getElementById('select-all');\n if (selectAll && document.activeElement.tagName !== 'INPUT') {\n e.preventDefault();\n selectAll.checked = true;\n selectAll.dispatchEvent(new Event('change'));\n }\n }\n });\n }\n}\n\n// Initialize and export\nexport default new TemporalCacheModule();\n```\n\n**Step 2: Load Module in Controller** (`Classes/Controller/Backend/TemporalCacheController.php`)\n```php\nprivate function setupModuleTemplate(ModuleTemplate $moduleTemplate, string $currentAction): void\n{\n $moduleTemplate->setTitle(\n $this->getLanguageService()->sL('LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab')\n );\n\n // Load JavaScript module\n $moduleTemplate->getPageRenderer()->loadJavaScriptModule(\n '@netresearch/nr-temporal-cache/backend-module.js'\n );\n\n // Add DocHeader buttons\n $this->addDocHeaderButtons($moduleTemplate, $currentAction);\n // ...\n}\n```\n\n**Step 3: Remove Inline JavaScript from Templates**\n```html\n\u003c!-- Resources/Private/Templates/Backend/TemporalCache/Content.html -->\n\u003cf:section name=\"Content\">\n \u003c!-- Template content with data attributes for JavaScript -->\n \u003cbutton\n type=\"button\"\n class=\"btn btn-success\"\n id=\"harmonize-selected-btn\"\n disabled\n data-action=\"harmonize\"\n data-action-uri=\"{harmonizeActionUri}\"\n aria-label=\"{f:translate(key: '...:content.harmonize_selected')}\">\n \u003ccore:icon identifier=\"actions-synchronize\" size=\"small\" />\n \u003cf:translate key=\"LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:content.harmonize_selected\" />\n \u003c/button>\n\u003c/f:section>\n\n\u003c!-- FooterAssets section removed completely -->\n```\n\n**Validation:**\n```bash\n# Ensure NO inline JavaScript remains\ngrep -rn \"FooterAssets\" Resources/Private/Templates/\ngrep -rn \"\u003cscript\" Resources/Private/Templates/\n\n# Verify ES6 module exists\nls -lh Resources/Public/JavaScript/BackendModule.js\n\n# Check controller loads module\ngrep \"loadJavaScriptModule\" Classes/Controller/Backend/*.php\n```\n\n**Impact:** CSP compliance, better caching, maintainability, modern development patterns\n\n**Severity:** 🟡 Important - Required for TYPO3 v13 compliance\n\n---\n\n### 4. Module Layout Pattern\n\n**Problem:** Old `Default.html` layout is non-standard for TYPO3 v13\n\n**TYPO3 v13 Standard:** Use dedicated `Module.html` layout for backend modules\n\n**Before (NON-STANDARD):**\n```html\n\u003c!-- Resources/Private/Templates/Backend/TemporalCache/Dashboard.html -->\n\u003cf:layout name=\"Default\" />\n\n\u003cf:section name=\"Content\">\n \u003ch1>Dashboard\u003c/h1>\n \u003c!-- Content -->\n\u003c/f:section>\n```\n\n**After (MODERN v13):**\n\n**Step 1: Create Module Layout** (`Resources/Private/Layouts/Module.html`)\n```html\n\u003chtml xmlns:f=\"http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers\"\n xmlns:be=\"http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers\"\n xmlns:core=\"http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers\"\n data-namespace-typo3-fluid=\"true\">\n\n\u003cf:be.pageRenderer />\n\n\u003cdiv class=\"module\" data-module-name=\"temporal-cache\">\n \u003cf:render section=\"Before\" optional=\"true\" />\n\n \u003cdiv class=\"module-body\">\n \u003cf:flashMessages />\n \u003cf:render section=\"Content\" />\n \u003c/div>\n\n \u003cf:render section=\"After\" optional=\"true\" />\n\u003c/div>\n\n\u003c/html>\n```\n\n**Step 2: Update All Templates**\n```html\n\u003c!-- Resources/Private/Templates/Backend/TemporalCache/Dashboard.html -->\n\u003chtml xmlns:f=\"http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers\"\n xmlns:core=\"http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers\"\n data-namespace-typo3-fluid=\"true\">\n\n\u003cf:layout name=\"Module\" />\n\n\u003cf:section name=\"Content\">\n \u003ch1>\u003cf:translate key=\"LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:dashboard.title\" />\u003c/h1>\n \u003c!-- Content -->\n\u003c/f:section>\n\n\u003c/html>\n```\n\n**Validation:**\n```bash\n# Check all templates use Module layout\ngrep -n \"f:layout name=\" Resources/Private/Templates/Backend/**/*.html\n\n# Verify Module.html exists\nls -l Resources/Private/Layouts/Module.html\n\n# Ensure no Default.html dependencies\n! grep -r \"Default.html\" Resources/Private/Templates/\n```\n\n**Severity:** 🟡 Important - Standard TYPO3 v13 pattern\n\n---\n\n### 5. DocHeader Component Integration\n\n**Problem:** Backend modules should have standard DocHeader with refresh, shortcut, and action-specific buttons\n\n**TYPO3 v13 Standard:** Use ButtonBar, IconFactory for DocHeader components\n\n**Before (MISSING):**\n```php\n// Classes/Controller/Backend/TemporalCacheController.php\nprivate function setupModuleTemplate(ModuleTemplate $moduleTemplate, string $currentAction): void\n{\n $moduleTemplate->setTitle('Temporal Cache');\n // No DocHeader buttons\n}\n```\n\n**After (MODERN v13):**\n\n**Step 1: Add Required Imports**\n```php\nuse TYPO3\\CMS\\Backend\\Template\\Components\\ButtonBar;\nuse TYPO3\\CMS\\Core\\Imaging\\Icon;\nuse TYPO3\\CMS\\Core\\Imaging\\IconFactory;\n```\n\n**Step 2: Inject IconFactory**\n```php\npublic function __construct(\n private readonly ModuleTemplateFactory $moduleTemplateFactory,\n private readonly ExtensionConfiguration $extensionConfiguration,\n // ... other dependencies\n private readonly IconFactory $iconFactory // ADD THIS\n) {}\n```\n\n**Step 3: Add DocHeader Buttons Method**\n```php\nprivate function addDocHeaderButtons(ModuleTemplate $moduleTemplate, string $currentAction): void\n{\n if (!isset($this->uriBuilder)) {\n return; // Skip in tests\n }\n\n $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();\n\n // Refresh button (all actions)\n $refreshButton = $buttonBar->makeLinkButton()\n ->setHref($this->uriBuilder->reset()->uriFor($currentAction))\n ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.reload'))\n ->setIcon($this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL))\n ->setShowLabelText(false);\n $buttonBar->addButton($refreshButton, ButtonBar::BUTTON_POSITION_RIGHT, 1);\n\n // Shortcut/bookmark button (all actions)\n $shortcutButton = $buttonBar->makeShortcutButton()\n ->setRouteIdentifier('tools_TemporalCache')\n ->setDisplayName($this->getLanguageService()->sL('LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'))\n ->setArguments(['action' => $currentAction]);\n $buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT, 2);\n\n // Action-specific buttons\n switch ($currentAction) {\n case 'dashboard':\n // Quick access to content list\n $contentButton = $buttonBar->makeLinkButton()\n ->setHref($this->uriBuilder->reset()->uriFor('content'))\n ->setTitle($this->getLanguageService()->sL('LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:button.view_content'))\n ->setIcon($this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL))\n ->setShowLabelText(true);\n $buttonBar->addButton($contentButton, ButtonBar::BUTTON_POSITION_LEFT, 1);\n break;\n\n case 'wizard':\n // Help button for wizard\n $helpButton = $buttonBar->makeHelpButton()\n ->setFieldName('temporal_cache_wizard')\n ->setModuleName('_MOD_tools_TemporalCache');\n $buttonBar->addButton($helpButton, ButtonBar::BUTTON_POSITION_RIGHT, 3);\n break;\n }\n}\n```\n\n**Step 4: Call from setupModuleTemplate**\n```php\nprivate function setupModuleTemplate(ModuleTemplate $moduleTemplate, string $currentAction): void\n{\n $moduleTemplate->setTitle(\n $this->getLanguageService()->sL('LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab')\n );\n\n $moduleTemplate->getPageRenderer()->loadJavaScriptModule(\n '@netresearch/nr-temporal-cache/backend-module.js'\n );\n\n // Add DocHeader buttons\n $this->addDocHeaderButtons($moduleTemplate, $currentAction);\n\n // ... menu creation\n}\n```\n\n**Validation:**\n```bash\n# Check IconFactory injection\ngrep \"IconFactory\" Classes/Controller/Backend/*.php\n\n# Verify addDocHeaderButtons method exists\ngrep -A 5 \"addDocHeaderButtons\" Classes/Controller/Backend/*.php\n\n# Check button types used\ngrep \"makeLinkButton\\|makeShortcutButton\\|makeHelpButton\" Classes/Controller/Backend/*.php\n```\n\n**Common Button Types:**\n- `makeLinkButton()` - Navigate to URL\n- `makeShortcutButton()` - Bookmark module state\n- `makeHelpButton()` - Context-sensitive help\n- `makeInputButton()` - Form submission\n- `makeFullyRenderedButton()` - Custom HTML\n\n**Severity:** 🟡 Important - Standard TYPO3 UX pattern\n\n---\n\n### 6. TYPO3 Modal and Notification APIs\n\n**Problem:** Browser `alert()`, `confirm()`, `prompt()` are deprecated and not user-friendly\n\n**TYPO3 v13 Standard:** Use `@typo3/backend/modal.js` and `@typo3/backend/notification.js`\n\n**Before (DEPRECATED):**\n```javascript\n// Inline JavaScript using browser APIs\nif (confirm('Really delete this item?')) {\n fetch('/delete', { method: 'POST' })\n .then(() => alert('Deleted successfully'))\n .catch(() => alert('Error occurred'));\n}\n```\n\n**After (MODERN v13):**\n```javascript\nimport Modal from '@typo3/backend/modal.js';\nimport Notification from '@typo3/backend/notification.js';\n\n// Confirmation Modal\nModal.confirm(\n 'Delete Item',\n 'Really delete this item? This action cannot be undone.',\n Modal.SeverityEnum.warning,\n [\n {\n text: 'Cancel',\n active: true,\n btnClass: 'btn-default',\n trigger: () => Modal.dismiss()\n },\n {\n text: 'Delete',\n btnClass: 'btn-danger',\n trigger: () => {\n Modal.dismiss();\n performDelete();\n }\n }\n ]\n);\n\nasync function performDelete() {\n try {\n const response = await fetch('/delete', { method: 'POST' });\n const data = await response.json();\n\n if (data.success) {\n Notification.success('Success', 'Item deleted successfully');\n } else {\n Notification.error('Error', data.message);\n }\n } catch (error) {\n Notification.error('Error', 'Failed to delete: ' + error.message);\n }\n}\n```\n\n**Modal Severity Levels:**\n- `Modal.SeverityEnum.notice` - Info/notice (blue)\n- `Modal.SeverityEnum.info` - Information (blue)\n- `Modal.SeverityEnum.ok` - Success (green)\n- `Modal.SeverityEnum.warning` - Warning (yellow)\n- `Modal.SeverityEnum.error` - Error (red)\n\n**Notification Types:**\n- `Notification.success(title, message, duration)` - Green success message\n- `Notification.error(title, message, duration)` - Red error message\n- `Notification.warning(title, message, duration)` - Yellow warning\n- `Notification.info(title, message, duration)` - Blue information\n- `Notification.notice(title, message, duration)` - Gray notice\n\n**Validation:**\n```bash\n# Check for browser APIs (violations)\ngrep -rn \"alert(\" Resources/Public/JavaScript/\ngrep -rn \"confirm(\" Resources/Public/JavaScript/\ngrep -rn \"prompt(\" Resources/Public/JavaScript/\n\n# Verify TYPO3 APIs used\ngrep \"import.*Modal\" Resources/Public/JavaScript/*.js\ngrep \"import.*Notification\" Resources/Public/JavaScript/*.js\n```\n\n**Severity:** 🟡 Important - Modern UX and consistency\n\n---\n\n### 7. Bootstrap 5 Migration Patterns\n\n**Problem:** TYPO3 v13 backend uses Bootstrap 5. Extensions still using Bootstrap 4 data attributes and classes will have non-functional UI components (e.g., dismiss buttons, dropdowns, modals).\n\n**Bootstrap 4 → 5 Migration Map:**\n\n| Bootstrap 4 (WRONG) | Bootstrap 5 (CORRECT) | Impact |\n|---------------------|----------------------|--------|\n| `data-dismiss=\"modal\"` | `data-bs-dismiss=\"modal\"` | Dismiss buttons stop working |\n| `data-toggle=\"dropdown\"` | `data-bs-toggle=\"dropdown\"` | Dropdowns stop working |\n| `data-toggle=\"collapse\"` | `data-bs-toggle=\"collapse\"` | Collapsible panels stop working |\n| `data-target=\"#id\"` | `data-bs-target=\"#id\"` | Target references break |\n| `btn-default` | `btn-secondary` | Button renders unstyled |\n| `bg-success` + `style=\"color: #fff\"` | `text-bg-success` | Use combined text-bg utility |\n| `bg-warning text-dark` | `text-bg-warning` | Use combined text-bg utility |\n| `bg-danger` + inline color | `text-bg-danger` | Use combined text-bg utility |\n| `ml-*` / `mr-*` | `ms-*` / `me-*` | Margin classes renamed (logical properties) |\n| `pl-*` / `pr-*` | `ps-*` / `pe-*` | Padding classes renamed (logical properties) |\n| `float-left` / `float-right` | `float-start` / `float-end` | Float utilities renamed |\n\n**Before (BOOTSTRAP 4 - BROKEN in TYPO3 v13):**\n```html\n\u003cdiv class=\"alert alert-success bg-success\" style=\"color: #fff;\">\n \u003cbutton type=\"button\" class=\"close\" data-dismiss=\"alert\">×\u003c/button>\n Operation completed\n\u003c/div>\n\n\u003cbutton class=\"btn btn-default ml-2\">Cancel\u003c/button>\n```\n\n**After (BOOTSTRAP 5 - CORRECT):**\n```html\n\u003cdiv class=\"alert alert-success text-bg-success\">\n \u003cbutton type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\">\u003c/button>\n Operation completed\n\u003c/div>\n\n\u003cbutton class=\"btn btn-secondary ms-2\">Cancel\u003c/button>\n```\n\n**Detection:**\n```bash\n# Find Bootstrap 4 data attributes (must be migrated)\ngrep -rn 'data-dismiss\\|data-toggle\\|data-target\\|data-ride' Resources/Private/\n\n# Find deprecated Bootstrap 4 classes\ngrep -rn 'btn-default\\|ml-[0-9]\\|mr-[0-9]\\|pl-[0-9]\\|pr-[0-9]\\|float-left\\|float-right' Resources/Private/\n\n# Find inline color styles that should use text-bg-* utilities\ngrep -rn 'bg-success.*color\\|bg-warning.*text-dark\\|bg-danger.*color' Resources/Private/\n```\n\n**Severity:** 🟡 Important - Non-functional UI components in TYPO3 v13 backend\n\n---\n\n### 8. Accessibility (ARIA Labels and Roles)\n\n**Problem:** Backend modules must be accessible for screen readers and keyboard navigation\n\n**WCAG 2.1 AA Requirements:**\n- Semantic HTML roles\n- ARIA labels on interactive elements\n- Keyboard navigation support\n\n**Before (MISSING ACCESSIBILITY):**\n```html\n\u003ctable class=\"table table-striped\">\n \u003cthead>\n \u003ctr>\n \u003cth>\n \u003cinput type=\"checkbox\" id=\"select-all\">\n \u003c/th>\n \u003cth>Title\u003c/th>\n \u003c/tr>\n \u003c/thead>\n \u003ctbody>\n \u003ctr>\n \u003ctd>\u003cinput type=\"checkbox\" class=\"content-checkbox\">\u003c/td>\n \u003ctd>Content item\u003c/td>\n \u003c/tr>\n \u003c/tbody>\n\u003c/table>\n```\n\n**After (ACCESSIBLE v13):**\n```html\n\u003ctable class=\"table table-striped table-hover\" role=\"grid\" aria-label=\"Temporal Content List\">\n \u003cthead>\n \u003ctr role=\"row\">\n \u003cth style=\"width: 40px;\" role=\"columnheader\">\n \u003cinput\n type=\"checkbox\"\n id=\"select-all\"\n class=\"form-check-input\"\n aria-label=\"Select all content items\">\n \u003c/th>\n \u003cth role=\"columnheader\">\n \u003cf:translate key=\"LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:content.table.title\" />\n \u003c/th>\n \u003c/tr>\n \u003c/thead>\n \u003ctbody>\n \u003ctr>\n \u003ctd>\n \u003cinput\n type=\"checkbox\"\n class=\"form-check-input content-checkbox\"\n data-uid=\"{item.content.uid}\"\n aria-label=\"Select content item: {item.content.title}\">\n \u003c/td>\n \u003ctd>{item.content.title}\u003c/td>\n \u003c/tr>\n \u003c/tbody>\n\u003c/table>\n```\n\n**Accessible Button Example:**\n```html\n\u003cbutton\n type=\"button\"\n class=\"btn btn-success\"\n id=\"harmonize-selected-btn\"\n disabled\n data-action=\"harmonize\"\n aria-label=\"{f:translate(key: '...:content.harmonize_selected')}\">\n \u003ccore:icon identifier=\"actions-synchronize\" size=\"small\" />\n \u003cf:translate key=\"LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:content.harmonize_selected\" />\n\u003c/button>\n```\n\n**Required ARIA Attributes:**\n- `role=\"grid\"` - On data tables\n- `role=\"row\"` - On table rows\n- `role=\"columnheader\"` - On table headers\n- `aria-label=\"...\"` - On interactive elements without visible text\n- `aria-labelledby=\"...\"` - Reference to label element\n- `aria-describedby=\"...\"` - Additional description\n\n**Keyboard Navigation:**\n```javascript\n// Support Ctrl+A for select all\ndocument.addEventListener('keydown', (e) => {\n if ((e.ctrlKey || e.metaKey) && e.key === 'a') {\n const selectAll = document.getElementById('select-all');\n if (selectAll && document.activeElement.tagName !== 'INPUT') {\n e.preventDefault();\n selectAll.checked = true;\n selectAll.dispatchEvent(new Event('change'));\n }\n }\n});\n```\n\n**Validation:**\n```bash\n# Check for ARIA labels\ngrep -rn \"aria-label\" Resources/Private/Templates/\n\n# Check for semantic roles\ngrep -rn 'role=\"grid\\|row\\|columnheader\"' Resources/Private/Templates/\n\n# Verify keyboard navigation support\ngrep -rn \"keydown\\|keyup\\|keypress\" Resources/Public/JavaScript/\n```\n\n**Severity:** 🟢 Recommended - WCAG 2.1 AA compliance\n\n---\n\n### 9. Icon Registration (Configuration/Icons.php)\n\n**Problem:** Icon registration in `ext_localconf.php` using `IconRegistry` is deprecated in TYPO3 v13\n\n**TYPO3 v13 Standard:** Use `Configuration/Icons.php` return array\n\n**Before (DEPRECATED v13):**\n```php\n// ext_localconf.php - DEPRECATED\n$iconRegistry = \\TYPO3\\CMS\\Core\\Utility\\GeneralUtility::makeInstance(\n \\TYPO3\\CMS\\Core\\Imaging\\IconRegistry::class\n);\n\n$iconRegistry->registerIcon(\n 'temporal-cache-module',\n \\TYPO3\\CMS\\Core\\Imaging\\IconProvider\\SvgIconProvider::class,\n ['source' => 'EXT:nr_temporal_cache/Resources/Public/Icons/Extension.svg']\n);\n```\n\n**After (MODERN v13):**\n```php\n\u003c?php\n\n// Configuration/Icons.php\ndeclare(strict_types=1);\n\nuse TYPO3\\CMS\\Core\\Imaging\\IconProvider\\SvgIconProvider;\n\nreturn [\n 'temporal-cache-module' => [\n 'provider' => SvgIconProvider::class,\n 'source' => 'EXT:nr_temporal_cache/Resources/Public/Icons/Extension.svg',\n ],\n 'temporal-cache-harmonize' => [\n 'provider' => SvgIconProvider::class,\n 'source' => 'EXT:nr_temporal_cache/Resources/Public/Icons/Harmonize.svg',\n ],\n];\n```\n\n**Validation:**\n```bash\n# Check for deprecated IconRegistry usage\ngrep -rn \"IconRegistry\" ext_localconf.php ext_tables.php\n\n# Verify Configuration/Icons.php exists\nls -l Configuration/Icons.php\n\n# Check icon registration format\ngrep -A 3 \"return \\[\" Configuration/Icons.php\n```\n\n**Severity:** 🟡 Important - Removes deprecation warnings\n\n---\n\n### 10. CSRF Protection (URI Generation)\n\n**Problem:** Hardcoded action URLs bypass TYPO3 CSRF protection\n\n**TYPO3 v13 Standard:** Use `uriBuilder` for all action URIs\n\n**Before (INSECURE):**\n```html\n\u003cbutton\n id=\"harmonize-btn\"\n data-action-uri=\"/typo3/module/tools/temporal-cache/harmonize\">\n Harmonize\n\u003c/button>\n```\n\n```javascript\nconst uri = button.dataset.actionUri;\nfetch(uri, { method: 'POST', body: JSON.stringify(data) });\n```\n\n**After (SECURE v13):**\n\n**Controller:**\n```php\npublic function contentAction(?ServerRequestInterface $request = null, ...): ResponseInterface\n{\n // ...\n\n $moduleTemplate->assignMultiple([\n 'content' => $paginator->getPaginatedItems(),\n 'harmonizeActionUri' => isset($this->uriBuilder)\n ? $this->uriBuilder->reset()->uriFor('harmonize')\n : '',\n // ...\n ]);\n\n return $moduleTemplate->renderResponse('Backend/TemporalCache/Content');\n}\n```\n\n**Template:**\n```html\n\u003cbutton\n id=\"harmonize-selected-btn\"\n data-action-uri=\"{harmonizeActionUri}\">\n Harmonize\n\u003c/button>\n```\n\n**JavaScript:**\n```javascript\nconst harmonizeBtn = document.getElementById('harmonize-selected-btn');\nconst harmonizeUri = harmonizeBtn.dataset.actionUri;\n\n// URI includes CSRF token automatically\nconst response = await fetch(harmonizeUri, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ content: selectedUids })\n});\n```\n\n**Validation:**\n```bash\n# Check for hardcoded URLs (violations)\ngrep -rn '\"/typo3/' Resources/Private/Templates/\ngrep -rn '\"/typo3/' Resources/Public/JavaScript/\n\n# Verify uriBuilder usage in controller\ngrep \"uriFor(\" Classes/Controller/Backend/*.php\n\n# Check template receives URIs\ngrep \"Uri}\" Resources/Private/Templates/Backend/**/*.html\n```\n\n**Severity:** 🔴 Critical - Security vulnerability\n\n---\n\n## Complete Modernization Checklist\n\n### Phase 1: Extension Key Consistency (Critical)\n- [ ] Verify correct extension key in `ext_emconf.php`\n- [ ] Search and replace all `EXT:wrong_key/` → `EXT:correct_key/` in templates\n- [ ] Update JavaScript alert/console messages with correct extension name\n- [ ] Verify translation keys work in backend module\n- [ ] Check static asset paths (CSS, images, icons)\n\n**Validation:**\n```bash\ngrep -rn \"EXT:temporal_cache/\" Resources/ # Should find ZERO\ngrep -rn \"EXT:nr_temporal_cache/\" Resources/ | wc -l # Should find ALL\n```\n\n### Phase 2: CSP Compliance (Critical)\n- [ ] Remove ALL inline `\u003cscript>` blocks from templates\n- [ ] Remove ALL inline `\u003cstyle>` blocks from templates\n- [ ] Move CSS to `Resources/Public/Css/BackendModule.css`\n- [ ] Load CSS via `f:be.pageRenderer includeCssFiles` or `PageRenderer->addCssFile()`\n- [ ] Verify `f:be.pageRenderer` uses `includeJsFiles` (NOT `includeJavaScriptFiles`)\n\n**Validation:**\n```bash\ngrep -rn \"\u003cscript\" Resources/Private/Templates/ # Should find ZERO\ngrep -rn \"\u003cstyle\" Resources/Private/Templates/ # Should find ZERO\ngrep -rn \"includeJavaScriptFiles\" Resources/Private/ # Should find ZERO (wrong attribute)\n```\n\n### Phase 3: JavaScript Modernization (Important)\n- [ ] Create `Resources/Public/JavaScript/BackendModule.js` as ES6 module\n- [ ] Import `@typo3/backend/modal.js` and `@typo3/backend/notification.js`\n- [ ] Implement class-based structure with proper initialization\n- [ ] Replace all `alert()` with `Notification` API\n- [ ] Replace all `confirm()` with `Modal.confirm()`\n- [ ] Add keyboard navigation support (Ctrl+A, etc.)\n- [ ] Remove ALL `\u003cf:section name=\"FooterAssets\">` from templates\n- [ ] Load module via `$moduleTemplate->getPageRenderer()->loadJavaScriptModule()`\n\n**Validation:**\n```bash\ngrep -rn \"FooterAssets\" Resources/Private/Templates/ # Should find ZERO\ngrep -rn \"\u003cscript\" Resources/Private/Templates/ # Should find ZERO\nls -lh Resources/Public/JavaScript/BackendModule.js # Should exist\ngrep \"loadJavaScriptModule\" Classes/Controller/Backend/*.php # Should find usage\n```\n\n### Phase 4: Layout Pattern (Important)\n- [ ] Create `Resources/Private/Layouts/Module.html` with TYPO3 v13 structure\n- [ ] Add `xmlns:core` namespace to Module.html\n- [ ] Include `\u003cf:flashMessages />` in Module.html\n- [ ] Update ALL templates to use `\u003cf:layout name=\"Module\" />`\n- [ ] Add `xmlns:core` namespace to all templates\n- [ ] Remove any `Default.html` layout dependencies\n\n**Validation:**\n```bash\nls -l Resources/Private/Layouts/Module.html # Should exist\ngrep -n \"f:layout name=\" Resources/Private/Templates/Backend/**/*.html | grep -v \"Module\" # Should find ZERO\ngrep -n \"xmlns:core\" Resources/Private/Templates/Backend/**/*.html | wc -l # Should match template count\n```\n\n### Phase 5: DocHeader Integration (Important)\n- [ ] Add `use TYPO3\\CMS\\Backend\\Template\\Components\\ButtonBar;` import\n- [ ] Add `use TYPO3\\CMS\\Core\\Imaging\\Icon;` import\n- [ ] Add `use TYPO3\\CMS\\Core\\Imaging\\IconFactory;` import\n- [ ] Inject `IconFactory` into controller constructor\n- [ ] Create `addDocHeaderButtons()` method\n- [ ] Add refresh button (all actions)\n- [ ] Add shortcut/bookmark button (all actions)\n- [ ] Add action-specific buttons (view content, help, etc.)\n- [ ] Call `addDocHeaderButtons()` from `setupModuleTemplate()`\n\n**Validation:**\n```bash\ngrep \"IconFactory\" Classes/Controller/Backend/*.php # Should find injection\ngrep -A 5 \"addDocHeaderButtons\" Classes/Controller/Backend/*.php # Should find method\ngrep \"makeLinkButton\\|makeShortcutButton\" Classes/Controller/Backend/*.php # Should find usage\n```\n\n### Phase 6: TYPO3 APIs (Important)\n- [ ] Import `Modal` and `Notification` in ES6 module\n- [ ] Replace `confirm()` with `Modal.confirm()` with severity levels\n- [ ] Replace `alert()` success with `Notification.success()`\n- [ ] Replace `alert()` errors with `Notification.error()`\n- [ ] Use proper Modal button configurations (text, btnClass, trigger)\n- [ ] Set appropriate severity levels (notice, info, ok, warning, error)\n\n**Validation:**\n```bash\ngrep -rn \"alert(\" Resources/Public/JavaScript/ # Should find ZERO\ngrep -rn \"confirm(\" Resources/Public/JavaScript/ # Should find ZERO\ngrep \"Modal.confirm\" Resources/Public/JavaScript/*.js # Should find usage\ngrep \"Notification\\.(success\\|error)\" Resources/Public/JavaScript/*.js # Should find usage\n```\n\n### Phase 7: Bootstrap 5 Migration (Important)\n- [ ] Replace `data-dismiss` with `data-bs-dismiss` in all templates\n- [ ] Replace `data-toggle` with `data-bs-toggle` in all templates\n- [ ] Replace `data-target` with `data-bs-target` in all templates\n- [ ] Replace `btn-default` with `btn-secondary`\n- [ ] Replace `bg-success` + inline color with `text-bg-success`\n- [ ] Replace `bg-warning text-dark` with `text-bg-warning`\n- [ ] Replace `ml-*`/`mr-*` with `ms-*`/`me-*`\n- [ ] Replace `pl-*`/`pr-*` with `ps-*`/`pe-*`\n- [ ] Replace `float-left`/`float-right` with `float-start`/`float-end`\n\n**Validation:**\n```bash\ngrep -rn 'data-dismiss\\|data-toggle\\|data-target' Resources/Private/ # Should find ZERO\ngrep -rn 'btn-default' Resources/Private/ # Should find ZERO\ngrep -rn 'ml-[0-9]\\|mr-[0-9]' Resources/Private/ # Should find ZERO\n```\n\n### Phase 8: Accessibility (Recommended)\n- [ ] Add `role=\"grid\"` to data tables\n- [ ] Add `role=\"row\"` to table rows\n- [ ] Add `role=\"columnheader\"` to table headers\n- [ ] Add `aria-label` to checkboxes without visible labels\n- [ ] Add `aria-label` to buttons with icon-only content\n- [ ] Implement keyboard navigation (Ctrl+A for select all)\n- [ ] Test with screen reader\n- [ ] Verify all interactive elements are keyboard accessible\n\n**Validation:**\n```bash\ngrep -rn \"aria-label\" Resources/Private/Templates/ # Should find accessibility labels\ngrep -rn 'role=\"grid\\|row\\|columnheader\"' Resources/Private/Templates/ # Should find semantic roles\n```\n\n### Phase 9: Icon Registration (Important)\n- [ ] Create `Configuration/Icons.php` if missing\n- [ ] Migrate icon registration from `ext_localconf.php`\n- [ ] Use proper return array structure\n- [ ] Set correct `provider` class (SvgIconProvider, BitmapIconProvider, etc.)\n- [ ] Verify icon `source` paths are correct\n- [ ] Remove deprecated IconRegistry code from `ext_localconf.php`\n\n**Validation:**\n```bash\nls -l Configuration/Icons.php # Should exist\ngrep -rn \"IconRegistry\" ext_localconf.php # Should find ZERO\n```\n\n### Phase 10: CSRF Protection (Critical)\n- [ ] Use `uriBuilder->uriFor()` for all action URIs\n- [ ] Pass URIs to templates via `assignMultiple()`\n- [ ] Use data attributes in templates: `data-action-uri=\"{harmonizeActionUri}\"`\n- [ ] Read URIs from data attributes in JavaScript\n- [ ] Remove all hardcoded `/typo3/...` URLs\n\n**Validation:**\n```bash\ngrep -rn '\"/typo3/' Resources/ # Should find ZERO (except maybe comments)\ngrep \"uriFor(\" Classes/Controller/Backend/*.php # Should find URI generation\n```\n\n### Phase 11: Testing and Validation (Critical)\n- [ ] Run unit tests: `composer test:unit`\n- [ ] Run functional tests: `composer test:functional`\n- [ ] Check for PHP deprecation warnings\n- [ ] Test module in TYPO3 backend manually\n- [ ] Verify all buttons work (refresh, shortcut, action buttons)\n- [ ] Test harmonization/actions with Modal confirmations\n- [ ] Verify Notification API messages display correctly\n- [ ] Test keyboard navigation (Ctrl+A, Tab order)\n- [ ] Check browser console for JavaScript errors\n- [ ] Validate translation keys work\n\n**Validation:**\n```bash\ncomposer test # All tests should pass\nvendor/bin/typo3 cache:flush # Clear caches\n# Manual testing in backend\n```\n\n### Phase 12: Documentation (Important)\n- [ ] Document backend module usage in `Documentation/`\n- [ ] Add screenshots of module UI\n- [ ] Document keyboard shortcuts\n- [ ] Update README.md with backend module info\n- [ ] Create CHANGELOG entry for modernization\n- [ ] Update version in `ext_emconf.php`\n\n---\n\n## Conformance Scoring Impact\n\n| Category | Before | After | Improvement |\n|----------|--------|-------|-------------|\n| Extension Architecture | 15/20 | 18/20 | +3 (Fixed extension keys, layout pattern) |\n| Coding Guidelines | 18/20 | 20/20 | +2 (ES6 modules, icon registration) |\n| PHP Architecture | 16/20 | 18/20 | +2 (IconFactory DI, proper URI generation) |\n| Testing Standards | 18/20 | 18/20 | 0 (Already passing) |\n| Best Practices | 15/20 | 20/20 | +5 (Modern JS, accessibility, CSRF) |\n| **Total Base Score** | **82/100** | **94/100** | **+12 points** |\n| Excellence: Documentation | 0/4 | 1/4 | +1 (Added module docs) |\n| **Total Score** | **82/120** | **95/120** | **+13 points** |\n\n**Estimated Time Investment:**\n- Analysis: 1-2 hours\n- Phase 1-2 (Critical fixes): 2-3 hours\n- Phase 3-5 (JavaScript + Layout): 3-4 hours\n- Phase 6-8 (Accessibility + Icons + CSRF): 2-3 hours\n- Phase 9-10 (Testing + Docs): 2-3 hours\n- **Total:** 10-15 hours for complete modernization\n\n---\n\n## Common Pitfalls and Solutions\n\n### Pitfall 1: Extension Key Case Sensitivity\n**Problem:** `nr_temporal_cache` vs `nr-temporal-cache` vs `nrTemporalCache`\n**Solution:** Use snake_case (`nr_temporal_cache`) consistently everywhere. TYPO3 extension keys are always snake_case.\n\n### Pitfall 2: JavaScript Module Path\n**Problem:** `@vendor/extension-name/` vs `@vendor/extension_name/`\n**Solution:** Use hyphen-case in module paths: `@netresearch/nr-temporal-cache/backend-module.js`\n\n### Pitfall 3: Modal Not Dismissing\n**Problem:** Modal stays open after button click\n**Solution:** Always call `Modal.dismiss()` in trigger callbacks before performing action\n\n### Pitfall 4: Notification Duration\n**Problem:** Success notifications disappear too quickly\n**Solution:** Add duration parameter: `Notification.success(title, message, 3)` (3 seconds)\n\n### Pitfall 5: IconFactory Not Available in Tests\n**Problem:** Tests fail with \"Call to a member function getIcon() on null\"\n**Solution:** Check `isset($this->uriBuilder)` before calling button-related methods\n\n### Pitfall 6: ARIA Labels Not Translatable\n**Problem:** Hardcoded English text in aria-label\n**Solution:** Use Fluid translate ViewHelper: `aria-label=\"{f:translate(key: '...')}\"`\n\n---\n\n## Real-World Example: Complete Before/After\n\n**Extension:** `nr_temporal_cache` - Backend module for temporal content management\n**Modernization:** Complete v13 compliance (45/100 → 95/100)\n\n### Files Changed\n1. `Classes/Controller/Backend/TemporalCacheController.php` - Added IconFactory, DocHeader, JS module loading\n2. `Resources/Private/Layouts/Module.html` - Created new layout\n3. `Resources/Private/Templates/Backend/TemporalCache/*.html` - Fixed keys, removed inline JS, added ARIA\n4. `Resources/Public/JavaScript/BackendModule.js` - Created 246-line ES6 module\n5. `Configuration/Icons.php` - Created icon registration\n6. `ext_localconf.php` - Removed deprecated IconRegistry code\n\n### Results\n- ✅ 57 extension key references fixed\n- ✅ 95 lines of inline JavaScript removed\n- ✅ 246 lines of ES6 module created\n- ✅ DocHeader with 3 button types added\n- ✅ Modal and Notification APIs integrated\n- ✅ ARIA labels on 12+ interactive elements\n- ✅ Keyboard navigation (Ctrl+A) implemented\n- ✅ CSRF protection via uriBuilder\n- ✅ Zero deprecation warnings\n- ✅ 316 unit tests passing\n- ✅ 95/100 conformance score\n\n**Commit:** `79db9cf` - 6 files changed, +459/-204 lines, 8.1KB ES6 module created\n\n---\n\n## References\n\n- **TYPO3 Core API:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/\n- **Backend Module API:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Backend/BackendModules.html\n- **JavaScript API:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/JavaScript/\n- **Icon API:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Icon/Index.html\n- **WCAG 2.1:** https://www.w3.org/WAI/WCAG21/quickref/\n\n**Created:** 2025-11-21 based on real-world nr_temporal_cache modernization\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":41537,"content_sha256":"5612e6a4a12f9e589473c2eeabef483b4727a5d182815ff9f107a214b4a42f89"},{"filename":"references/best-practices.md","content":"# TYPO3 Extension Best Practices\n\n**Source:** TYPO3 Best Practices (Tea Extension) and Core API Standards\n**Purpose:** Real-world patterns and organizational best practices for TYPO3 extensions\n\n## Project Structure\n\n### Complete Extension Layout\n\n```\nmy_extension/\n├── .ddev/ # DDEV configuration\n│ └── config.yaml\n├── .github/ # GitHub Actions CI/CD\n│ └── workflows/\n│ └── tests.yml\n├── Build/ # Build tools and configs\n│ ├── phpunit/\n│ │ ├── UnitTests.xml\n│ │ └── FunctionalTests.xml\n│ └── Scripts/\n│ └── runTests.sh\n├── Classes/ # PHP source code\n│ ├── Controller/\n│ ├── Domain/\n│ │ ├── Model/\n│ │ └── Repository/\n│ ├── Service/\n│ ├── Utility/\n│ ├── EventListener/\n│ └── ViewHelper/\n├── Configuration/ # TYPO3 configuration\n│ ├── Backend/\n│ │ └── Modules.php\n│ ├── Services.yaml\n│ ├── TCA/\n│ ├── TypoScript/\n│ │ ├── setup.typoscript\n│ │ └── constants.typoscript\n│ └── Sets/ # TYPO3 v13+\n│ └── MySet/\n│ └── config.yaml\n├── Documentation/ # RST documentation\n│ ├── Index.rst\n│ ├── guides.xml # Modern (replaces Settings.cfg)\n│ ├── Introduction/\n│ ├── Installation/\n│ ├── Configuration/\n│ ├── Developer/\n│ └── Editor/\n├── Resources/\n│ ├── Private/\n│ │ ├── Language/\n│ │ │ ├── locallang.xlf\n│ │ │ └── de.locallang.xlf\n│ │ ├── Layouts/\n│ │ ├── Partials/\n│ │ └── Templates/\n│ └── Public/\n│ ├── Css/\n│ ├── Icons/\n│ ├── Images/\n│ └── JavaScript/\n├── Tests/\n│ ├── Unit/\n│ └── Functional/\n│ └── Fixtures/\n├── Build/\n│ ├── playwright.config.ts # Playwright E2E configuration\n│ ├── package.json # Node dependencies\n│ ├── .nvmrc # Node version (>=22.18)\n│ ├── phpunit/\n│ │ ├── UnitTests.xml\n│ │ └── FunctionalTests.xml\n│ ├── Scripts/\n│ │ └── runTests.sh\n│ └── tests/\n│ └── playwright/ # Playwright E2E tests\n│ ├── config.ts\n│ ├── e2e/\n│ ├── accessibility/\n│ ├── fixtures/\n│ └── helper/\n├── .editorconfig # Editor configuration\n├── .gitattributes # Git attributes\n├── .gitignore # Git ignore rules\n├── .php-cs-fixer.dist.php # PHP CS Fixer config\n├── composer.json # Composer configuration\n├── ext_emconf.php # Extension metadata\n├── ext_localconf.php # Global configuration\n├── LICENSE # License file\n├── phpstan.neon # PHPStan configuration\n└── README.md # Project README\n```\n\n## Best Practices by Category\n\n### 1. Dependency Management\n\n**composer.json Best Practices:**\n\n```json\n{\n \"name\": \"vendor/my-extension\",\n \"type\": \"typo3-cms-extension\",\n \"description\": \"Clear, concise extension description\",\n \"license\": \"GPL-2.0-or-later\",\n \"authors\": [\n {\n \"name\": \"Author Name\",\n \"email\": \"[email protected]\",\n \"role\": \"Developer\"\n }\n ],\n \"require\": {\n \"php\": \"^8.1\",\n \"typo3/cms-core\": \"^12.4 || ^13.0\",\n \"typo3/cms-backend\": \"^12.4 || ^13.0\",\n \"typo3/cms-extbase\": \"^12.4 || ^13.0\",\n \"typo3/cms-fluid\": \"^12.4 || ^13.0\"\n },\n \"require-dev\": {\n \"typo3/coding-standards\": \"^0.7\",\n \"typo3/testing-framework\": \"^8.0\",\n \"phpunit/phpunit\": \"^10.5\",\n \"phpstan/phpstan\": \"^1.10\",\n \"friendsofphp/php-cs-fixer\": \"^3.0\"\n },\n \"autoload\": {\n \"psr-4\": {\n \"Vendor\\\\MyExtension\\\\\": \"Classes/\"\n }\n },\n \"autoload-dev\": {\n \"psr-4\": {\n \"Vendor\\\\MyExtension\\\\Tests\\\\\": \"Tests/\"\n }\n },\n \"config\": {\n \"vendor-dir\": \".Build/vendor\",\n \"bin-dir\": \".Build/bin\",\n \"sort-packages\": true,\n \"allow-plugins\": {\n \"typo3/class-alias-loader\": true,\n \"typo3/cms-composer-installers\": true\n }\n },\n \"extra\": {\n \"typo3/cms\": {\n \"extension-key\": \"my_extension\",\n \"web-dir\": \".Build/Web\"\n }\n }\n}\n```\n\n### 2. Code Quality Tools\n\n**.php-cs-fixer.dist.php:**\n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\n$config = \\TYPO3\\CodingStandards\\CsFixerConfig::create();\n$config->getFinder()\n ->in(__DIR__ . '/Classes')\n ->in(__DIR__ . '/Configuration')\n ->in(__DIR__ . '/Tests');\n\nreturn $config;\n```\n\n**phpstan.neon:**\n\n```neon\nincludes:\n - .Build/vendor/phpstan/phpstan/conf/bleedingEdge.neon\n\nparameters:\n level: 9\n paths:\n - Classes\n - Configuration\n - Tests\n excludePaths:\n - .Build\n - vendor\n```\n\n#### PHPStan Level 10 Best Practices for TYPO3\n\n**Handling $GLOBALS['TCA'] in Tests:**\n\nPHPStan cannot infer types for runtime-configured `$GLOBALS` arrays. Use ignore annotations:\n\n```php\n// ✅ Right: Suppress offsetAccess warnings for $GLOBALS['TCA']\n/** @var array\u003cstring, mixed> $tcaConfig */\n$tcaConfig = [\n 'type' => 'text',\n 'enableRichtext' => true,\n];\n// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible\n$GLOBALS['TCA']['tt_content']['columns']['bodytext']['config'] = $tcaConfig;\n\n// ❌ Wrong: No type annotation or suppression\n$GLOBALS['TCA']['tt_content']['columns']['bodytext']['config'] = [\n 'type' => 'text',\n]; // PHPStan error: offsetAccess.nonOffsetAccessible\n```\n\n**Factory Methods vs Property Initialization:**\n\nAvoid uninitialized property errors in test classes:\n\n```php\n// ❌ Wrong: PHPStan warns about uninitialized property\nfinal class MyServiceTest extends UnitTestCase\n{\n private MyService $subject; // Uninitialized property\n\n protected function setUp(): void\n {\n parent::setUp();\n $this->subject = new MyService();\n }\n}\n\n// ✅ Right: Use factory method\nfinal class MyServiceTest extends UnitTestCase\n{\n private function createSubject(): MyService\n {\n return new MyService();\n }\n\n #[Test]\n public function testSomething(): void\n {\n $subject = $this->createSubject();\n // Use $subject\n }\n}\n```\n\n**Type Assertions for Dynamic Arrays:**\n\nWhen testing arrays modified by reference:\n\n```php\n// ❌ Wrong: PHPStan cannot verify type after modification\npublic function testFieldProcessing(): void\n{\n $fieldArray = ['bodytext' => '\u003cp>Test\u003c/p>'];\n $this->subject->processFields($fieldArray);\n\n // PHPStan error: Cannot access offset on mixed\n self::assertStringContainsString('Test', $fieldArray['bodytext']);\n}\n\n// ✅ Right: Add type assertions\npublic function testFieldProcessing(): void\n{\n $fieldArray = ['bodytext' => '\u003cp>Test\u003c/p>'];\n $this->subject->processFields($fieldArray);\n\n self::assertArrayHasKey('bodytext', $fieldArray);\n self::assertIsString($fieldArray['bodytext']);\n self::assertStringContainsString('Test', $fieldArray['bodytext']);\n}\n```\n\n**Intersection Types for Mocks:**\n\nUse intersection types for proper PHPStan analysis of mocks:\n\n```php\n// ✅ Right: Intersection type for mock\n/** @var ResourceFactory&MockObject $resourceFactoryMock */\n$resourceFactoryMock = $this->createMock(ResourceFactory::class);\n\n// Alternative: @phpstan-var annotation\n$resourceFactoryMock = $this->createMock(ResourceFactory::class);\n/** @phpstan-var ResourceFactory&MockObject $resourceFactoryMock */\n```\n\n**Common PHPStan Suppressions for TYPO3:**\n\n```php\n// Suppress $GLOBALS['TCA'] access\n// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible\n$GLOBALS['TCA']['table']['columns']['field'] = $config;\n\n// Suppress $GLOBALS['TYPO3_CONF_VARS'] access\n// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible\n$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['key'] = MyClass::class;\n\n// Suppress mixed type from legacy code\n// @phpstan-ignore-next-line argument.type\n$this->view->assign('data', $legacyArray);\n```\n\n**Type Hints for Service Container Retrieval:**\n\n```php\n// ✅ Right: Type hint service retrieval\n/** @var DataHandler $dataHandler */\n$dataHandler = $this->get(DataHandler::class);\n\n/** @var ResourceFactory $resourceFactory */\n$resourceFactory = $this->get(ResourceFactory::class);\n```\n\n### 3. Service Configuration\n\n**Configuration/Services.yaml:**\n\n```yaml\nservices:\n _defaults:\n autowire: true\n autoconfigure: true\n public: false\n\n # Auto-register all classes\n Vendor\\MyExtension\\:\n resource: '../Classes/*'\n\n # Exclude specific directories\n Vendor\\MyExtension\\Domain\\Model\\:\n resource: '../Classes/Domain/Model/*'\n autoconfigure: false\n\n # Explicit service configuration example\n Vendor\\MyExtension\\Service\\EmailService:\n arguments:\n $fromEmail: '%env(DEFAULT_FROM_EMAIL)%'\n $fromName: 'TYPO3 Extension'\n\n # Tag configuration example\n Vendor\\MyExtension\\Command\\ImportCommand:\n tags:\n - name: 'console.command'\n command: 'myext:import'\n description: 'Import data from external source'\n```\n\n### 4. Backend Module Configuration\n\n**Configuration/Backend/Modules.php:**\n\n```php\n\u003c?php\nreturn [\n 'web_myext' => [\n 'parent' => 'web',\n 'position' => ['after' => 'web_info'],\n 'access' => 'user',\n 'workspaces' => 'live',\n 'path' => '/module/web/myext',\n 'labels' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_mod.xlf',\n 'extensionName' => 'MyExtension',\n 'controllerActions' => [\n \\Vendor\\MyExtension\\Controller\\BackendController::class => [\n 'list',\n 'show',\n 'edit',\n 'update',\n ],\n ],\n ],\n];\n```\n\n### 5. Testing Infrastructure\n\n**Build/Scripts/runTests.sh:**\n\n```bash\n#!/usr/bin/env bash\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_DIR=\"$(cd \"${SCRIPT_DIR}/../..\" && pwd)\"\n\n# Run unit tests\nif [ \"$1\" = \"unit\" ]; then\n php vendor/bin/phpunit -c Build/phpunit/UnitTests.xml\nfi\n\n# Run functional tests\nif [ \"$1\" = \"functional\" ]; then\n typo3DatabaseDriver=pdo_sqlite \\\n php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml\nfi\n\n# Run all tests\nif [ \"$1\" = \"all\" ]; then\n php vendor/bin/phpunit -c Build/phpunit/UnitTests.xml\n typo3DatabaseDriver=pdo_sqlite \\\n php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml\nfi\n```\n\n### 6. CI/CD Configuration\n\n**.github/workflows/ci.yml** (OpenSSF Scorecard-optimized):\n\n```yaml\nname: CI\n\non:\n push:\n branches: [main]\n pull_request:\n workflow_dispatch:\n\n# CRITICAL: deny-all at top level scores 10/10 on Token-Permissions\npermissions: {}\n\njobs:\n lint:\n name: Lint (PHP ${{ matrix.php }})\n runs-on: ubuntu-latest\n permissions:\n contents: read # Only what this job needs\n strategy:\n fail-fast: false\n matrix:\n php: ['8.2', '8.3', '8.4']\n steps:\n - name: Harden Runner\n uses: step-security/harden-runner@SHA # vX.Y.Z\n with:\n egress-policy: audit\n\n - name: Checkout\n uses: actions/checkout@SHA # vX.Y.Z\n\n - name: Setup PHP\n uses: shivammathur/setup-php@SHA # vX.Y.Z\n with:\n php-version: ${{ matrix.php }}\n tools: php-cs-fixer\n coverage: none\n\n - name: Install dependencies\n run: composer install --prefer-dist --no-progress\n\n - name: PHP CS Fixer\n run: vendor/bin/php-cs-fixer fix --dry-run --diff\n\n unit:\n name: Unit Tests (PHP ${{ matrix.php }}, TYPO3 ${{ matrix.typo3 }})\n runs-on: ubuntu-latest\n permissions:\n contents: read\n strategy:\n fail-fast: false\n matrix:\n include:\n - php: '8.2'\n typo3: '^13.4'\n - php: '8.3'\n typo3: '^13.4'\n - php: '8.4'\n typo3: '^13.4'\n steps:\n - name: Harden Runner\n uses: step-security/harden-runner@SHA # vX.Y.Z\n with:\n egress-policy: audit\n\n - name: Checkout\n uses: actions/checkout@SHA # vX.Y.Z\n\n - name: Setup PHP\n uses: shivammathur/setup-php@SHA # vX.Y.Z\n with:\n php-version: ${{ matrix.php }}\n coverage: pcov\n\n - name: Install TYPO3\n run: |\n composer require --no-update \"typo3/cms-core:${{ matrix.typo3 }}\"\n composer install --prefer-dist --no-progress\n\n - name: Unit Tests\n run: vendor/bin/phpunit -c Build/phpunit/UnitTests.xml --coverage-clover=coverage.xml\n\n - name: Upload coverage\n uses: codecov/codecov-action@SHA # vX.Y.Z\n with:\n token: ${{ secrets.CODECOV_TOKEN }}\n files: coverage.xml\n fail_ci_if_error: false\n```\n\n**Key Scorecard requirements** (apply to ALL workflow files, not just `ci.yml`):\n\n| Requirement | Pattern | Scorecard Check |\n|-------------|---------|-----------------|\n| Deny-all permissions | `permissions: {}` at workflow top | Token-Permissions (0→10) |\n| Per-job permissions | `permissions: contents: read` per job | Token-Permissions |\n| SHA-pinned actions | `uses: action@SHA # vX.Y.Z` | Pinned-Dependencies (0→9) |\n| Harden Runner | `step-security/harden-runner` first step | Workflow Hardening |\n| Coverage upload | `codecov/codecov-action` with token | Enterprise readiness |\n| Security audit | `composer audit --abandoned=ignore` | Vulnerabilities |\n\n**Common mistake**: Adding `permissions: {}` only to `ci.yml` but forgetting `codeql.yml`, `scorecard.yml`, `dependency-review.yml`. Scorecard checks ALL workflow files.\n\n#### Required Supporting Workflows\n\nEvery TYPO3 extension should have these additional workflows:\n\n| Workflow | Purpose | Scorecard Impact |\n|----------|---------|------------------|\n| `codeql.yml` | Security scanning (JS + Actions) | SAST (0→10) |\n| `scorecard.yml` | OpenSSF Scorecard analysis | Enables scoring |\n| `dependency-review.yml` | PR dependency CVE check | Vulnerabilities |\n\n**Note**: CodeQL does NOT support PHP. Configure it for `javascript-typescript` and `actions` languages only.\n\n#### Scorecard Checks That Cannot Be Fixed\n\n| Check | Why | Score |\n|-------|-----|-------|\n| Fuzzing | Only recognizes OSS-Fuzz/ClusterFuzzLite, not PHPUnit fuzz | 0 |\n| Packaging | Requires GitHub Packages, not Packagist/TER | -1 |\n| Maintained | Based on recent commit frequency — penalizes stable projects | 0-10 |\n\n### 7. Documentation Standards\n\n**Documentation/Index.rst:**\n\n```rst\n.. include:: /Includes.rst.txt\n\nMy Extension\n\n:Extension key:\n my_extension\n\n:Package name:\n vendor/my-extension\n\n:Version:\n |release|\n\n:Language:\n en\n\n:Author:\n Author Name\n\n:License:\n This document is published under the\n `Creative Commons BY 4.0 \u003chttps://creativecommons.org/licenses/by/4.0/>`__\n license.\n\n:Rendered:\n |today|\n\n----\n\nClear and concise extension description explaining the purpose and main features.\n\n----\n\n**Table of Contents:**\n\n.. toctree::\n :maxdepth: 2\n :titlesonly:\n\n Introduction/Index\n Installation/Index\n Configuration/Index\n Editor/Index\n Developer/Index\n Sitemap\n```\n\n**Page Size Guidelines:**\n\nFollow TYPO3 documentation best practices for page organization and sizing:\n\n**Index.rst (Landing Page):**\n- **Target:** 80-150 lines\n- **Maximum:** 200 lines\n- **Purpose:** Entry point with metadata, brief description, and navigation only\n- **Contains:** Extension metadata, brief description, card-grid (optional), toctree, license\n- **Anti-pattern:** ❌ Embedding all content (introduction, requirements, contributing, credits, etc.)\n\n**Content Pages:**\n- **Target:** 100-300 lines per file\n- **Optimal:** 150-200 lines\n- **Maximum:** 400 lines (split if larger)\n- **Structure:** Focused on single topic or logically related concepts\n- **Split Strategy:** Create subdirectories for complex topics with multiple aspects\n\n**Red Flags:**\n- ❌ Index.rst >200 lines → Extract content to Introduction/, Contributing/, etc.\n- ❌ Single file >400 lines → Split into multiple focused pages\n- ❌ All content in Index.rst → Create proper section directories\n- ❌ Navigation by scrolling → Use card-grid + toctree structure\n\n**Proper Structure Example:**\n```\nDocumentation/\n├── Index.rst # Landing page (80-150 lines)\n├── Introduction/ # Getting started\n│ └── Index.rst # Features, requirements, quick start\n├── Installation/ # Setup instructions\n│ └── Index.rst\n├── Configuration/ # Configuration guides\n│ ├── Index.rst\n│ ├── Basic.rst\n│ └── Advanced.rst\n├── Contributing/ # Contribution guidelines\n│ └── Index.rst # Code, translations, credits, resources\n├── Examples/ # Usage examples\n├── Troubleshooting/ # Problem solving\n└── API/ # Developer reference\n```\n\n**Benefits:**\n- ✅ Better user experience (focused, scannable pages)\n- ✅ Easier maintenance (smaller, manageable files)\n- ✅ Improved search results (specific pages rank better)\n- ✅ Clear information architecture\n- ✅ Follows TYPO3 documentation standards\n- ✅ Mobile-friendly navigation\n\n**Reference:** [TYPO3 tea extension](https://github.com/TYPO3BestPractices/tea) - exemplary documentation structure\n\n### 8. Version Control Best Practices\n\n#### Default Branch Naming\n\n**✅ Use `main` as the default branch instead of `master`**\n\n**Rationale:**\n- **Industry Standard**: GitHub, GitLab, and Bitbucket all default to `main` for new repositories\n- **Modern Convention**: Aligns with current version control ecosystem standards\n- **Inclusive Language**: Part of broader industry shift toward inclusive terminology\n- **Consistency**: Matches TYPO3 Core and most modern TYPO3 extensions\n\n**Migration from `master` to `main`:**\n\nIf your extension currently uses `master`, migrate to `main`:\n\n```bash\n# 1. Create main branch from master\ngit checkout master\ngit pull origin master\ngit checkout -b main\ngit push -u origin main\n\n# 2. Change default branch on GitHub\ngh repo edit --default-branch main\n\n# 3. Update all branch references in codebase\n# - CI/CD workflows (.github/workflows/*.yml)\n# - Documentation (guides.xml, *.rst files)\n# - URLs in CONTRIBUTING.md, README.md\n\n# 4. Delete old master branch\ngit branch -d master\ngit push origin --delete master\n```\n\n**Example CI/CD workflow update:**\n\n```yaml\n# .github/workflows/tests.yml\non:\n push:\n branches: [main, develop] # Changed from: master\n pull_request:\n branches: [main] # Changed from: master\n```\n\n**Example documentation update:**\n\n```xml\n\u003c!-- Documentation/guides.xml -->\n\u003cextension edit-on-github-branch=\"main\" /> \u003c!-- Changed from: master -->\n```\n\n#### Branch Protection Enforcement\n\n**Prevent accidental `master` branch recreation** and **protect `main` branch** using GitHub Repository Rulesets.\n\n**Block master branch - prevents creation and pushes:**\n\nCreate `ruleset-block-master.json`:\n\n```json\n{\n \"name\": \"Block master branch\",\n \"target\": \"branch\",\n \"enforcement\": \"active\",\n \"conditions\": {\n \"ref_name\": {\n \"include\": [\"refs/heads/master\"],\n \"exclude\": []\n }\n },\n \"rules\": [\n {\n \"type\": \"creation\"\n },\n {\n \"type\": \"update\"\n },\n {\n \"type\": \"deletion\"\n }\n ],\n \"bypass_actors\": []\n}\n```\n\nApply the ruleset:\n\n```bash\ngh api -X POST repos/OWNER/REPO/rulesets \\\n --input ruleset-block-master.json\n```\n\n**Protect main branch - requires CI and prevents force pushes:**\n\nCreate `ruleset-protect-main.json`:\n\n```json\n{\n \"name\": \"Protect main branch\",\n \"target\": \"branch\",\n \"enforcement\": \"active\",\n \"conditions\": {\n \"ref_name\": {\n \"include\": [\"refs/heads/main\"],\n \"exclude\": []\n }\n },\n \"rules\": [\n {\n \"type\": \"required_status_checks\",\n \"parameters\": {\n \"required_status_checks\": [\n {\n \"context\": \"build\"\n }\n ],\n \"strict_required_status_checks_policy\": false\n }\n },\n {\n \"type\": \"non_fast_forward\"\n }\n ],\n \"bypass_actors\": [\n {\n \"actor_id\": 5,\n \"actor_type\": \"RepositoryRole\",\n \"bypass_mode\": \"always\"\n }\n ]\n}\n```\n\nApply the ruleset:\n\n```bash\ngh api -X POST repos/OWNER/REPO/rulesets \\\n --input ruleset-protect-main.json\n```\n\n**Verify rulesets are active:**\n\n```bash\n# List all rulesets\ngh api repos/OWNER/REPO/rulesets\n\n# Test master branch is blocked (should fail)\ngit push origin test-branch:master\n# Expected: remote: error: GH013: Repository rule violations found\n```\n\n**Benefits of Repository Rulesets:**\n- ✅ Prevents accidental `master` branch recreation\n- ✅ Enforces CI status checks before merging to `main`\n- ✅ Prevents force pushes to protected branches\n- ✅ Allows admin bypass for emergency situations\n- ✅ More flexible than legacy branch protection rules\n- ✅ Supports complex conditions and multiple rule types\n\n#### Required Conversation Resolution\n\nEnable `required_conversation_resolution` in branch protection to **enforce** that all PR review threads are resolved before merging. Without this, review feedback (including automated reviewers like GitHub Copilot) can be silently ignored.\n\n```bash\n# Check if enabled\ngh api repos/OWNER/REPO/branches/main/protection \\\n --jq 'if .required_conversation_resolution.enabled then \"✅ Enabled\" else \"❌ NOT enabled\" end'\n\n# Enable (include in full branch protection PUT)\n# This command safely fetches current settings, enables conversation resolution, and applies the change.\n# It correctly handles the GitHub API's different structures for GET and PUT.\ngh api repos/OWNER/REPO/branches/main/protection \\\n | jq 'del(.required_conversation_resolution) | . + {\"required_conversation_resolution\": true}' \\\n | gh api repos/OWNER/REPO/branches/main/protection -X PUT --input -\n```\n\nOr via GitHub UI: Settings → Branches → Edit → Check **\"Require conversation resolution before merging\"**\n\n#### .gitignore Best Practices\n\n**Standard .gitignore for TYPO3 Extensions:**\n\n```gitignore\n# Composer\ncomposer.lock\nvendor/\n\n# Build artifacts and caches\n.Build/\n.php-cs-fixer.cache\n.phpunit.result.cache\n\n# IDE and editors\n.idea/\n.vscode/\n*.sublime-*\n\n# OS files\n.DS_Store\nThumbs.db\n\n# Testing artifacts\nvar/\n.phpunit.cache/\n\n# Node.js (if using Playwright/frontend tools)\nnode_modules/\nBuild/node_modules/\n\n# Do not ignore public icons (example)\npublic/*\n!public/Icons/\n!public/Icons/**\n```\n\n**Key Patterns Explained:**\n\n| Pattern | Purpose |\n|---------|---------|\n| `.Build/` | Composer's vendor-dir and build artifacts when using `\"vendor-dir\": \".Build/vendor\"` |\n| `.php-cs-fixer.cache` | PHP-CS-Fixer cache file (regenerated on each run) |\n| `.phpunit.result.cache` | PHPUnit result cache |\n| `composer.lock` | **DO NOT commit in TYPO3 extensions** — extensions are libraries. Without a lock file, `composer install` resolves versions appropriate to the consumer's PHP version. A committed lock generated on a different PHP version in your dev environment will fail CI on older matrix entries with cryptic \"Your requirements could not be resolved\" errors. Rule only inverts for application projects (site packages, deployable apps), where reproducibility demands the lock. |\n| `vendor/` | Composer dependencies |\n| `var/` | TYPO3 testing framework temporary files |\n\n**Anti-pattern: Double-ignore**\n\n❌ **Wrong:** Adding patterns already covered by parent ignore\n```gitignore\n.Build/\n.Build/vendor/ # Redundant - already ignored by .Build/\n.Build/bin/ # Redundant - already ignored by .Build/\n```\n\n✅ **Right:** Single parent pattern covers all subdirectories\n```gitignore\n.Build/\n```\n\n**Tracking vs Ignoring:**\n\nIf a file was previously tracked and you add it to `.gitignore`, you must also remove it from git:\n\n```bash\n# Stop tracking a file that's now gitignored\ngit rm --cached .php-cs-fixer.cache\ngit commit -m \"chore: stop tracking php-cs-fixer cache\"\n\n# Stop tracking a directory\ngit rm -r --cached .Build/\ngit commit -m \"chore: stop tracking build artifacts\"\n```\n\n**DDEV-specific ignores (optional):**\n\nIf using DDEV, consider also ignoring:\n\n```gitignore\n# DDEV (optional - some teams commit .ddev/)\n.ddev/.gitignore\n.ddev/db_snapshots/\n.ddev/import-db/\n```\n\n### 9. Language File Organization\n\n**Resources/Private/Language/locallang.xlf:**\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003cxliff version=\"1.2\" xmlns=\"urn:oasis:names:tc:xliff:document:1.2\">\n \u003cfile source-language=\"en\" datatype=\"plaintext\"\n original=\"EXT:my_extension/Resources/Private/Language/locallang.xlf\"\n date=\"2024-01-01T12:00:00Z\"\n product-name=\"my_extension\">\n \u003cheader/>\n \u003cbody>\n \u003ctrans-unit id=\"plugin.title\" resname=\"plugin.title\">\n \u003csource>My Extension Plugin\u003c/source>\n \u003c/trans-unit>\n \u003ctrans-unit id=\"plugin.description\" resname=\"plugin.description\">\n \u003csource>Displays product list with filters\u003c/source>\n \u003c/trans-unit>\n \u003c/body>\n \u003c/file>\n\u003c/xliff>\n```\n\n### 10. TCA Best Practices\n\n**Configuration/TCA/tx_myext_domain_model_product.php:**\n\n```php\n\u003c?php\nreturn [\n 'ctrl' => [\n 'title' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product',\n 'label' => 'title',\n 'tstamp' => 'tstamp',\n 'crdate' => 'crdate',\n 'delete' => 'deleted',\n 'sortby' => 'sorting',\n 'versioningWS' => true,\n 'origUid' => 't3_origuid',\n 'languageField' => 'sys_language_uid',\n 'transOrigPointerField' => 'l10n_parent',\n 'transOrigDiffSourceField' => 'l10n_diffsource',\n 'translationSource' => 'l10n_source',\n 'enablecolumns' => [\n 'disabled' => 'hidden',\n 'starttime' => 'starttime',\n 'endtime' => 'endtime',\n ],\n 'searchFields' => 'title,description',\n 'iconfile' => 'EXT:my_extension/Resources/Public/Icons/product.svg',\n ],\n 'types' => [\n '1' => [\n 'showitem' => '\n --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,\n title, description,\n --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,\n hidden, starttime, endtime\n ',\n ],\n ],\n 'columns' => [\n 'title' => [\n 'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product.title',\n 'config' => [\n 'type' => 'input',\n 'size' => 30,\n 'eval' => 'trim,required',\n 'max' => 255,\n ],\n ],\n 'description' => [\n 'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product.description',\n 'config' => [\n 'type' => 'text',\n 'enableRichtext' => true,\n 'richtextConfiguration' => 'default',\n ],\n ],\n ],\n];\n```\n\n### 11. Security Best Practices\n\n**✅ Input Validation:**\n```php\nuse TYPO3\\CMS\\Core\\Utility\\GeneralUtility;\nuse TYPO3\\CMS\\Core\\Utility\\MathUtility;\n\n// Validate integer input\nif (!MathUtility::canBeInterpretedAsInteger($input)) {\n throw new \\InvalidArgumentException('Invalid integer value');\n}\n\n// Sanitize email\n$email = GeneralUtility::validEmail($input) ? $input : '';\n\n// Escape output in templates\n{product.title -> f:format.htmlspecialchars()}\n```\n\n**✅ SQL Injection Prevention:**\n```php\n// Use QueryBuilder with bound parameters\n$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)\n ->getQueryBuilderForTable('tx_myext_domain_model_product');\n\n$products = $queryBuilder\n ->select('*')\n ->from('tx_myext_domain_model_product')\n ->where(\n $queryBuilder->expr()->eq(\n 'uid',\n $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)\n )\n )\n ->executeQuery()\n ->fetchAllAssociative();\n```\n\n**✅ CSRF Protection:**\n```html\n\u003c!-- Always include form protection token -->\n\u003cf:form.hidden property=\"__trustedProperties\" value=\"{formProtection}\" />\n```\n\n### 12. GrumPHP Configuration\n\n**Pre-commit Hook Best Practices:**\n\nWhen using GrumPHP with PHPStan, configure it to fail gracefully when no PHP files are staged:\n\n```yaml\n# grumphp.yml\ngrumphp:\n tasks:\n phpstan:\n configuration: Build/phpstan.neon\n use_grumphp_paths: true\n triggered_by: ['php']\n```\n\n**Common Issue: \"No files found\" Error**\n\nGrumPHP+PHPStan fails with \"No files found to process\" when staging only non-PHP files:\n\n```\n❌ WRONG: Commit with only .md, .yaml, or config file changes fails PHPStan\n```\n\n**Solutions:**\n\n1. **Trigger PHPStan only for PHP files** (recommended):\n ```yaml\n phpstan:\n triggered_by: ['php']\n ```\n\n2. **Skip PHPStan for commits without PHP changes**:\n ```yaml\n phpstan:\n skip_on_empty_paths: true\n ```\n\n3. **Use `--error-format=raw` with ignore pattern**:\n ```yaml\n phpstan:\n extra_args:\n - '--error-format=raw'\n ```\n\n**CI/CD Consideration:**\n\nWhen running php-cs-fixer in CI, exclude directories that aren't PHP-focused:\n\n```yaml\n# .github/workflows/ci.yml\n- name: PHP CS Fixer\n run: |\n vendor/bin/php-cs-fixer fix --dry-run --diff \\\n --path-mode=intersection \\\n -- Classes Tests Configuration\n```\n\n## Common Anti-Patterns to Avoid\n\n### ❌ Don't: Use GeneralUtility::makeInstance() for Services\n```php\n// Old way (deprecated)\n$repository = GeneralUtility::makeInstance(ProductRepository::class);\n```\n\n### ✅ Do: Use Dependency Injection\n```php\n// Modern way\npublic function __construct(\n private readonly ProductRepository $repository\n) {}\n```\n\n### ❌ Don't: Access $GLOBALS directly\n```php\n// Avoid global state\n$user = $GLOBALS['BE_USER'];\n$tsfe = $GLOBALS['TSFE'];\n```\n\n### ✅ Do: Inject Context and Services\n```php\npublic function __construct(\n private readonly Context $context,\n private readonly TypoScriptService $typoScriptService\n) {}\n```\n\n### ❌ Don't: Use ext_tables.php for configuration\n```php\n// ext_tables.php (deprecated for most uses)\n```\n\n### ✅ Do: Use dedicated configuration files\n```php\n// Configuration/Backend/Modules.php\n// Configuration/TCA/\n// Configuration/Services.yaml\n```\n\n## Netresearch CI Integration\n\nNetresearch extensions use the shared `netresearch/typo3-ci-workflows` package to standardize tooling and CI pipelines across all TYPO3 projects.\n\n### Single require-dev Dependency\n\nExtensions should use `netresearch/typo3-ci-workflows` as the **single** `require-dev` dependency instead of listing individual tools (phpstan, php-cs-fixer, rector, etc.) separately.\n\n```json\n{\n \"require-dev\": {\n \"netresearch/typo3-ci-workflows\": \"^1.0\"\n }\n}\n```\n\n**Do NOT add individual tool packages to require-dev:**\n\n```json\n// ❌ Wrong: Individual tool packages\n{\n \"require-dev\": {\n \"phpstan/phpstan\": \"^1.10\",\n \"friendsofphp/php-cs-fixer\": \"^3.0\",\n \"rector/rector\": \"^1.0\",\n \"phpunit/phpunit\": \"^10.5\"\n }\n}\n```\n\n### Required allowed-plugins\n\nAll allowed-plugins from the CI workflow must be listed in `composer.json`:\n\n```json\n{\n \"config\": {\n \"allow-plugins\": {\n \"typo3/class-alias-loader\": true,\n \"typo3/cms-composer-installers\": true,\n \"phpstan/extension-installer\": true,\n \"a9f/fractor-extension-installer\": true,\n \"infection/extension-installer\": true,\n \"captainhook/hook-installer\": true,\n \"dg/bypass-finals\": true\n }\n }\n}\n```\n\n### Reusable CI Workflows\n\nCI workflows must only use **reusable workflows** from `netresearch/typo3-ci-workflows`. Never use direct actions or define jobs inline.\n\n```yaml\n# ✅ Right: Reusable workflow\njobs:\n ci:\n uses: netresearch/typo3-ci-workflows/.github/workflows/ci.yml@main\n```\n\n```yaml\n# ❌ Wrong: Direct actions and inline jobs\njobs:\n phpstan:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: vendor/bin/phpstan analyse\n```\n\n### Push Trigger Restriction\n\nThe `push` trigger in `ci.yml` must be restricted to `branches: [main]` to avoid duplicate runs on pull requests (PRs already trigger their own workflow run).\n\n```yaml\non:\n push:\n branches: [main] # ✅ Only main — prevents duplicate runs on PRs\n pull_request:\n```\n\n```yaml\n# ❌ Wrong: Unrestricted push trigger causes duplicate CI runs\non:\n push:\n pull_request:\n```\n\n## TYPO3 v13 TCA Requirements\n\n### l10n_parent Field Type\n\nThe `l10n_parent` field **MUST** use `type=select` / `renderType=selectSingle` with `foreign_table`, **NOT** `type=group`.\n\n```php\n// ✅ Right: select/selectSingle for l10n_parent\n'l10n_parent' => [\n 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',\n 'config' => [\n 'type' => 'select',\n 'renderType' => 'selectSingle',\n 'items' => [\n ['label' => '', 'value' => 0],\n ],\n 'foreign_table' => 'tx_myext_domain_model_item',\n 'foreign_table_where' =>\n 'AND {#tx_myext_domain_model_item}.{#pid}=###CURRENT_PID###'\n . ' AND {#tx_myext_domain_model_item}.{#sys_language_uid} IN (-1,0)',\n 'default' => 0,\n ],\n],\n```\n\n```php\n// ❌ Wrong: type=group for l10n_parent (not supported in v13)\n'l10n_parent' => [\n 'config' => [\n 'type' => 'group',\n 'allowed' => 'tx_myext_domain_model_item',\n ],\n],\n```\n\n### Deprecated eval Values\n\n`eval=trim` is **deprecated** for `type=input` and `type=text` in TYPO3 v13. Remove it from all TCA column definitions.\n\n```php\n// ✅ Right: No eval=trim in v13\n'title' => [\n 'config' => [\n 'type' => 'input',\n 'size' => 30,\n 'max' => 255,\n 'required' => true, // Use 'required' as standalone config key\n ],\n],\n\n// ❌ Wrong: eval=trim is deprecated\n'title' => [\n 'config' => [\n 'type' => 'input',\n 'eval' => 'trim,required',\n ],\n],\n```\n\n### Removed ctrl Options\n\n`prependAtCopy` and `hideAtCopy` were **removed** in TYPO3 v13. Remove them from `ctrl` sections.\n\n```php\n// ❌ Wrong: Removed in v13\n'ctrl' => [\n 'prependAtCopy' => 'LLL:EXT:core/...:LGL.prependAtCopy',\n 'hideAtCopy' => true,\n],\n```\n\n### Select Field Defaults\n\nSelect fields referencing foreign tables should have an explicit `default => 0`:\n\n```php\n'category' => [\n 'config' => [\n 'type' => 'select',\n 'renderType' => 'selectSingle',\n 'foreign_table' => 'tx_myext_category',\n 'default' => 0, // ✅ Explicit default\n ],\n],\n```\n\n### System Column Definitions\n\nSystem columns (`uid`, `pid`, `tstamp`, `crdate`, `deleted`, `hidden`) should **NOT** have explicit column definitions in TCA. TYPO3 v13 auto-creates them from `ctrl` settings.\n\n```php\n// ✅ Right: Only declare in ctrl, not in columns\n'ctrl' => [\n 'tstamp' => 'tstamp',\n 'crdate' => 'crdate',\n 'delete' => 'deleted',\n 'enablecolumns' => [\n 'disabled' => 'hidden',\n ],\n],\n'columns' => [\n // Do NOT add uid, pid, tstamp, crdate, deleted, hidden here\n 'title' => [ /* ... */ ],\n],\n```\n\n```php\n// ❌ Wrong: Explicit column definitions for system fields\n'columns' => [\n 'hidden' => [\n 'label' => 'Hidden',\n 'config' => ['type' => 'check'],\n ],\n 'tstamp' => [\n 'label' => 'Timestamp',\n 'config' => ['type' => 'passthrough'],\n ],\n],\n```\n\n## Security Patterns\n\n### Shell Execution\n\nNever use `shell_exec()` or similar shell functions. Use PHP file functions instead.\n\n```php\n// ✅ Right: PHP file functions\n$content = file_get_contents($filePath);\n$files = scandir($directory);\ncopy($source, $destination);\nunlink($tempFile);\n\n// ❌ Wrong: Shell execution\n$content = shell_exec('cat ' . $filePath);\n$files = shell_exec('ls ' . $directory);\n```\n\n### Secure Random Data\n\nUse `random_bytes()` for generating tokens and secrets, not `md5(uniqid())`.\n\n```php\n// ✅ Right: Cryptographically secure\n$token = bin2hex(random_bytes(32));\n\n// ❌ Wrong: Predictable, not cryptographically secure\n$token = md5(uniqid('', true));\n```\n\n### Data Serialization\n\nUse `json_encode`/`json_decode` for data storage, not `serialize`/`unserialize` (which can lead to object injection attacks).\n\n```php\n// ✅ Right: JSON for data storage\n$stored = json_encode($data, JSON_THROW_ON_ERROR);\n$restored = json_decode($stored, true, 512, JSON_THROW_ON_ERROR);\n\n// ❌ Wrong: PHP serialization (object injection risk)\n$stored = serialize($data);\n$restored = unserialize($stored);\n```\n\n### Template Output Escaping\n\nAll template output of user-controlled data must use `f:format.htmlspecialchars()` or rely on Fluid auto-escaping.\n\n```html\n\u003c!-- ✅ Right: Explicit escaping -->\n\u003cspan>{entry.title -> f:format.htmlspecialchars()}\u003c/span>\n\n\u003c!-- ✅ Right: Fluid auto-escaping (default for inline notation) -->\n\u003cspan>{entry.title}\u003c/span>\n\n\u003c!-- ❌ Wrong: Raw output of user data -->\n\u003cf:format.raw>{entry.userInput}\u003c/f:format.raw>\n```\n\n### XML Export Safety\n\nWhen generating XML output, escape attributes properly and handle CDATA breakout:\n\n```php\n// ✅ Right: Proper XML attribute escaping\n$attr = htmlspecialchars($value, ENT_XML1 | ENT_QUOTES, 'UTF-8');\necho '\u003celement attr=\"' . $attr . '\">';\n\n// ✅ Right: Handle CDATA ]]> breakout\n$cdataContent = str_replace(']]>', ']]]]>\u003c![CDATA[>', $value);\necho '\u003c![CDATA[' . $cdataContent . ']]>';\n```\n\n### File Upload Validation\n\nAlways check the upload error code before processing uploaded files:\n\n```php\n// ✅ Right: Check error code first\nif ($uploadedFile->getError() === UPLOAD_ERR_OK) {\n $stream = $uploadedFile->getStream();\n // Process file...\n}\n\n// ❌ Wrong: Skip error check\n$stream = $uploadedFile->getStream(); // May fail silently\n```\n\n### libxml Error Cleanup\n\nAlways call `libxml_clear_errors()` after `libxml_get_errors()` to prevent memory leaks:\n\n```php\n// ✅ Right: Clear errors after retrieval\n$errors = libxml_get_errors();\nlibxml_clear_errors();\nforeach ($errors as $error) {\n // Handle error...\n}\n\n// ❌ Wrong: Errors accumulate in memory\n$errors = libxml_get_errors();\n// Missing libxml_clear_errors()\n```\n\n## Performance Patterns\n\n### TYPO3 Cache Configuration — No Hardcoded Backend\n\nWhen declaring a cache frontend in `ext_localconf.php`, **never specify a `'backend'` array key**. Only declare `frontend`, `options`, and `groups`. TYPO3 uses the instance's default backend.\n\n**Bad — hardcodes backend, defeats admin's Redis/Valkey/Memcached setup:**\n\n```php\n$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['myext_cache'] = [\n 'backend' => \\TYPO3\\CMS\\Core\\Cache\\Backend\\SimpleFileBackend::class, // ← no\n 'frontend' => \\TYPO3\\CMS\\Core\\Cache\\Frontend\\VariableFrontend::class,\n 'options' => ['defaultLifetime' => 3600],\n];\n```\n\n**Good — follows the instance default (Redis, DB, file, whatever the admin configured):**\n\n```php\n$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['myext_cache'] = [\n 'frontend' => \\TYPO3\\CMS\\Core\\Cache\\Frontend\\VariableFrontend::class,\n 'options' => ['defaultLifetime' => 3600],\n 'groups' => ['myext'],\n // NO 'backend' key — TYPO3 uses the instance default.\n];\n```\n\nIf you need a *specific* backend for correctness reasons (e.g., `TransientMemoryBackend` because the cache must be per-request), state that in a code comment so the choice is clear to reviewers.\n\n### In-Memory Caching\n\nAdd in-memory translation and result caches in services, keyed by lookup parameters:\n\n```php\nfinal class TranslationService\n{\n /** @var array\u003cstring, string> */\n private array $translationCache = [];\n\n public function translate(string $key, string $language): string\n {\n $cacheKey = $key . ':' . $language;\n if (!isset($this->translationCache[$cacheKey])) {\n $this->translationCache[$cacheKey] = $this->performLookup($key, $language);\n }\n return $this->translationCache[$cacheKey];\n }\n}\n```\n\n### Config Lookup Caching\n\nCache configuration lookups (e.g., `getConfiguredPageId()`) in instance properties to avoid repeated database or configuration queries:\n\n```php\nfinal class ConfigService\n{\n private ?int $configuredPageId = null;\n\n public function getConfiguredPageId(): int\n {\n if ($this->configuredPageId === null) {\n $this->configuredPageId = (int)$this->extensionConfiguration->get(\n 'my_extension',\n 'storagePid'\n );\n }\n return $this->configuredPageId;\n }\n}\n```\n\n### Efficient Query Result Checks\n\nUse `getFirst()` with a null check instead of `count() === 0` followed by `getFirst()`. The count pattern executes an extra `COUNT` SQL query.\n\n```php\n// ✅ Right: Single query\n$result = $repository->findByIdentifier($id);\nif ($result === null) {\n return; // Not found\n}\n// Use $result...\n\n// ✅ Right: getFirst() null check\n$first = $queryResult->getFirst();\nif ($first === null) {\n return;\n}\n\n// ❌ Wrong: Two queries (COUNT + SELECT)\nif ($queryResult->count() === 0) {\n return;\n}\n$first = $queryResult->getFirst();\n```\n\n### Batch Persistence\n\nBatch `persistAll()` calls at the end of an import operation, not per-entry:\n\n```php\n// ✅ Right: Single persistAll at end\nforeach ($entries as $entry) {\n $model = $this->mapToModel($entry);\n $this->repository->add($model);\n}\n$this->persistenceManager->persistAll(); // One flush\n\n// ❌ Wrong: persistAll per entry (N database flushes)\nforeach ($entries as $entry) {\n $model = $this->mapToModel($entry);\n $this->repository->add($model);\n $this->persistenceManager->persistAll(); // Flush per entry\n}\n```\n\n### ViewHelper Delegation\n\nViewHelpers should delegate to cached service methods, not duplicate lookup logic:\n\n```php\n// ✅ Right: Delegate to cached service\nfinal class TranslateViewHelper extends AbstractViewHelper\n{\n public function __construct(\n private readonly TranslationService $translationService\n ) {}\n\n public function render(): string\n {\n return $this->translationService->translate(\n $this->arguments['key'],\n $this->arguments['language']\n );\n }\n}\n\n// ❌ Wrong: Duplicate lookup logic in ViewHelper\nfinal class TranslateViewHelper extends AbstractViewHelper\n{\n public function render(): string\n {\n // Performs uncached DB query on every render call\n $repository = GeneralUtility::makeInstance(TranslationRepository::class);\n return $repository->findByKey($this->arguments['key'])->getValue();\n }\n}\n```\n\n## Template Patterns (TYPO3 v13 / Bootstrap 5)\n\n### Bootstrap 5 Class Migration\n\nUse Bootstrap 5 utility classes. Many Bootstrap 3/4 classes are removed or renamed:\n\n| Bootstrap 3/4 (Wrong) | Bootstrap 5 (Right) | Notes |\n|----------------------|---------------------|-------|\n| `form-inline` | `d-flex gap-3` | Inline forms use flex utilities |\n| `form-row` | `row g-3` | Form rows use gutter utilities |\n| `btn-default` | `btn-secondary` | Default button renamed |\n| `form-group` | `mb-3` | Form groups use margin utilities |\n| `form-control-file` | `form-control` | File inputs use standard class |\n\n```html\n\u003c!-- ✅ Right: Bootstrap 5 -->\n\u003cdiv class=\"d-flex gap-3 align-items-end mb-3\">\n \u003cdiv>\n \u003clabel class=\"form-label\">Filter\u003c/label>\n \u003cinput class=\"form-control\" type=\"text\" />\n \u003c/div>\n \u003cbutton class=\"btn btn-secondary\">Apply\u003c/button>\n\u003c/div>\n\n\u003c!-- ❌ Wrong: Bootstrap 3/4 classes -->\n\u003cdiv class=\"form-inline form-group\">\n \u003cinput class=\"form-control\" type=\"text\" />\n \u003cbutton class=\"btn btn-default\">Apply\u003c/button>\n\u003c/div>\n```\n\n### Table Semantics\n\nDo not use `role=\"grid\"` on data tables. Use native HTML table semantics:\n\n```html\n\u003c!-- ✅ Right: Native table semantics -->\n\u003ctable class=\"table table-striped\" aria-label=\"Product list\">\n \u003cthead>\n \u003ctr>\n \u003cth scope=\"col\">Name\u003c/th>\n \u003cth scope=\"col\">Price\u003c/th>\n \u003c/tr>\n \u003c/thead>\n \u003ctbody>\n \u003cf:for each=\"{products}\" as=\"product\">\n \u003ctr>\n \u003ctd>{product.name -> f:format.htmlspecialchars()}\u003c/td>\n \u003ctd>{product.price}\u003c/td>\n \u003c/tr>\n \u003c/f:for>\n \u003c/tbody>\n\u003c/table>\n\n\u003c!-- ❌ Wrong: role=\"grid\" on a data table -->\n\u003ctable role=\"grid\">\n \u003ctr>\u003cth>Name\u003c/th>\u003c/tr>\n\u003c/table>\n```\n\n### Accessibility Attributes\n\nAdd proper accessibility attributes to interactive elements:\n\n- `scope=\"col\"` on all `\u003cth>` elements\n- `aria-label` on `\u003cform>`, `\u003cnav>`, and `\u003ctable>` elements\n- `f:format.htmlspecialchars()` on all user-controlled output\n\n```html\n\u003cnav aria-label=\"{f:translate(key: 'pagination.label')}\">\n \u003c!-- pagination markup -->\n\u003c/nav>\n\n\u003cform aria-label=\"{f:translate(key: 'filter.label')}\" method=\"post\">\n \u003c!-- form fields -->\n\u003c/form>\n\n\u003ctable class=\"table\" aria-label=\"{f:translate(key: 'results.label')}\">\n \u003cthead>\n \u003ctr>\n \u003cth scope=\"col\">{f:translate(key: 'column.name')}\u003c/th>\n \u003cth scope=\"col\">{f:translate(key: 'column.status')}\u003c/th>\n \u003c/tr>\n \u003c/thead>\n\u003c/table>\n```\n\n||||||| parent of 4cac7a1 (feat: add learnings from 60+ agent review cycles):references/best-practices.md\n## XLIFF Translation Hygiene\n\nPeriodically audit for unused translation keys defined in `locallang.xlf` (and language variants like `de.locallang.xlf`) but never referenced in PHP or Fluid templates. Dead keys accumulate during refactoring and clutter translation files.\n\n**Detection:**\n```bash\n# Extract all trans-unit IDs from XLIFF files\ngrep -ohP 'id=\"[^\"]+\"' Resources/Private/Language/locallang*.xlf | \\\n sed 's/id=\"//;s/\"//' | sort -u > /tmp/xliff_keys.txt\n\n# Search for each key in PHP and Fluid files\nwhile IFS= read -r key; do\n if ! grep -rq \"$key\" Classes/ Resources/Private/Templates/ Resources/Private/Layouts/ Resources/Private/Partials/ Configuration/ 2>/dev/null; then\n echo \"UNUSED: $key\"\n fi\ndone \u003c /tmp/xliff_keys.txt\n```\n\n**Action:** Remove unused keys from ALL translation files (base `locallang.xlf` and every language variant). Do not leave orphaned keys in language-specific files when the base key is removed.\n\n**Severity:** 🟢 Recommended - Keeps translation files clean and reduces translator workload\n\n---\n\n## CI Workflow: Prevent Duplicate Runs\n\nWhen a GitHub Actions workflow has both `push:` and `pull_request:` triggers, pushes to PR branches trigger **two** CI runs (one for push, one for PR event). Restrict the `push:` trigger to `branches: [main]` to prevent duplicate runs.\n\n**Before (DUPLICATE RUNS):**\n```yaml\non:\n push: # Triggers on ALL branch pushes\n pull_request:\n branches: [main]\n```\n\n**After (CORRECT):**\n```yaml\non:\n push:\n branches: [main] # Only trigger push on main (release/merge)\n pull_request:\n branches: [main] # PR events handle feature branch CI\n```\n\n**Detection:**\n```bash\n# Check for push trigger without branch restriction\ngrep -A 2 'push:' .github/workflows/*.yml | grep -v 'branches:'\n```\n\n**Severity:** 🟡 Important - Duplicate runs waste CI minutes and create confusing status checks\n\n---\n\n## Conformance Checklist\n\n- [ ] Complete directory structure following best practices\n- [ ] composer.json with proper PSR-4 autoloading\n- [ ] Quality tools configured (php-cs-fixer, phpstan)\n- [ ] CI/CD pipeline (GitHub Actions or GitLab CI)\n- [ ] CI push trigger restricted to `branches: [main]` when `pull_request:` is also configured\n- [ ] Comprehensive test coverage (unit, functional, acceptance)\n- [ ] Complete documentation in RST format\n- [ ] Service configuration in Services.yaml\n- [ ] Backend modules in Configuration/Backend/\n- [ ] TCA files in Configuration/TCA/\n- [ ] Language files in XLIFF format\n- [ ] No unused XLIFF translation keys (periodic audit)\n- [ ] Dependency injection throughout\n- [ ] No global state access\n- [ ] Security best practices followed\n- [ ] .editorconfig for consistent formatting\n- [ ] .gitignore with standard patterns (caches, build artifacts, vendor)\n- [ ] README.md with clear instructions\n- [ ] LICENSE file present\n- [ ] Branch protection with required conversation resolution enabled\n- [ ] Netresearch CI workflows used (not individual tool packages)\n- [ ] TCA l10n_parent uses select/selectSingle (not group)\n- [ ] No eval=trim on type=input or type=text fields\n- [ ] No prependAtCopy or hideAtCopy in ctrl\n- [ ] System columns not redefined in TCA columns\n- [ ] No shell_exec usage\n- [ ] random_bytes() used for token generation\n- [ ] json_encode/json_decode used (not serialize/unserialize)\n- [ ] Template output properly escaped\n- [ ] In-memory caches for repeated lookups\n- [ ] Batch persistAll() for import operations\n- [ ] Bootstrap 5 classes used (not 3/4)\n- [ ] Proper table semantics (no role=\"grid\" on data tables)\n- [ ] Accessibility attributes on forms, nav, and tables\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":49503,"content_sha256":"88401a8fc5f677dd66395deda5bc825981bbb356e99c82d9367e56ba71987379"},{"filename":"references/coding-guidelines.md","content":"# TYPO3 Coding Guidelines\n\n**Source:** TYPO3 Core API Reference - Coding Guidelines\n**Purpose:** PHP code style, formatting standards, and PSR-12 compliance for TYPO3 extensions\n\n## PSR-12 Compliance\n\nTYPO3 follows **PSR-12: Extended Coding Style** as the foundation for PHP code style.\n\n**Key PSR-12 Requirements:**\n- 4 spaces for indentation (NO tabs)\n- Unix line endings (LF)\n- Maximum line length: 120 characters (soft limit), 80 recommended\n- Opening braces for classes/methods on same line\n- One statement per line\n- Visibility MUST be declared on all properties and methods\n\n## Identifier Naming Conventions\n\n### Variables and Methods: camelCase\n\n```php\n// ✅ Right\n$userName = 'John';\n$totalPrice = 100;\npublic function calculateTotal() {}\npublic function getUserData() {}\n\n// ❌ Wrong\n$user_name = 'John'; // snake_case\n$UserName = 'John'; // PascalCase\npublic function CalculateTotal() {} // PascalCase\npublic function get_user_data() {} // snake_case\n```\n\n### Classes: UpperCamelCase (PascalCase)\n\n```php\n// ✅ Right\nclass UserController {}\nclass PaymentService {}\nclass ProductRepository {}\n\n// ❌ Wrong\nclass userController {} // camelCase\nclass payment_service {} // snake_case\nclass productRepository {} // camelCase\n```\n\n### Constants: SCREAMING_SNAKE_CASE\n\n```php\n// ✅ Right\nconst MAX_UPLOAD_SIZE = 1024;\nconst API_ENDPOINT = 'https://api.example.com';\nprivate const DEFAULT_TIMEOUT = 30;\n\n// ❌ Wrong\nconst maxUploadSize = 1024; // camelCase\nconst ApiEndpoint = '...'; // PascalCase\n```\n\n### Namespaces: UpperCamelCase\n\n```php\n// ✅ Right\nnamespace Vendor\\ExtensionKey\\Domain\\Model;\nnamespace Vendor\\ExtensionKey\\Controller;\n\n// ❌ Wrong\nnamespace vendor\\extension_key\\domain\\model;\nnamespace Vendor\\extension_key\\Controller;\n```\n\n## Function and Method Naming\n\n### Descriptive Names with Verbs\n\n```php\n// ✅ Right: Verb + noun, descriptive\npublic function getUserById(int $id): ?User {}\npublic function calculateTotalPrice(array $items): float {}\npublic function isValidEmail(string $email): bool {}\npublic function hasPermission(string $action): bool {}\n\n// ❌ Wrong: No verb, ambiguous\npublic function user(int $id) {}\npublic function price(array $items) {}\npublic function email(string $email) {}\npublic function permission(string $action) {}\n```\n\n### Boolean Methods: is/has/can/should\n\n```php\n// ✅ Right\npublic function isActive(): bool {}\npublic function hasAccess(): bool {}\npublic function canEdit(): bool {}\npublic function shouldRender(): bool {}\n\n// ❌ Wrong\npublic function active(): bool {}\npublic function access(): bool {}\npublic function checkEdit(): bool {}\n```\n\n## Array Formatting\n\n### Short Syntax Only\n\n```php\n// ✅ Right: Short array syntax\n$items = [];\n$config = ['foo' => 'bar'];\n$users = [\n ['name' => 'John', 'age' => 30],\n ['name' => 'Jane', 'age' => 25],\n];\n\n// ❌ Wrong: Long array syntax (deprecated)\n$items = array();\n$config = array('foo' => 'bar');\n```\n\n### Multi-line Array Formatting\n\n```php\n// ✅ Right: Proper indentation and trailing comma\n$configuration = [\n 'key1' => 'value1',\n 'key2' => 'value2',\n 'nested' => [\n 'subkey1' => 'subvalue1',\n 'subkey2' => 'subvalue2',\n ], // Trailing comma\n];\n\n// ❌ Wrong: No trailing comma, inconsistent indentation\n$configuration = [\n 'key1' => 'value1',\n 'key2' => 'value2',\n 'nested' => [\n 'subkey1' => 'subvalue1',\n 'subkey2' => 'subvalue2'\n ]\n];\n```\n\n## Conditional Statement Layout\n\n### If/ElseIf/Else\n\n```php\n// ✅ Right: Proper spacing and braces\nif ($condition) {\n doSomething();\n} elseif ($otherCondition) {\n doSomethingElse();\n} else {\n doDefault();\n}\n\n// ❌ Wrong: Missing spaces, wrong brace placement\nif($condition){\n doSomething();\n}\nelse if ($otherCondition) {\n doSomethingElse();\n}\nelse {\n doDefault();\n}\n```\n\n### Switch Statements\n\n```php\n// ✅ Right\nswitch ($status) {\n case 'active':\n processActive();\n break;\n case 'pending':\n processPending();\n break;\n default:\n processDefault();\n}\n\n// ❌ Wrong: Inconsistent indentation\nswitch ($status) {\ncase 'active':\n processActive();\n break;\ncase 'pending':\nprocessPending();\nbreak;\ndefault:\nprocessDefault();\n}\n```\n\n## String Handling\n\n### Single Quotes Default\n\n```php\n// ✅ Right: Single quotes for simple strings\n$message = 'Hello, World!';\n$path = 'path/to/file.php';\n\n// ❌ Wrong: Unnecessary double quotes\n$message = \"Hello, World!\"; // No variable interpolation\n$path = \"path/to/file.php\";\n```\n\n### Double Quotes for Interpolation\n\n```php\n// ✅ Right: Double quotes when interpolating\n$name = 'John';\n$message = \"Hello, {$name}!\";\n\n// ❌ Wrong: Concatenation instead of interpolation\n$message = 'Hello, ' . $name . '!'; // Less readable\n```\n\n### String Concatenation\n\n```php\n// ✅ Right: Spaces around concatenation operator\n$fullPath = $basePath . '/' . $filename;\n$message = 'Hello ' . $name . ', welcome!';\n\n// ❌ Wrong: No spaces around operator\n$fullPath = $basePath.'/'.$filename;\n$message = 'Hello '.$name.', welcome!';\n```\n\n## PHPDoc Comment Standards\n\n### Class Documentation\n\n```php\n// ✅ Right: Complete class documentation\n/**\n * Service for calculating product prices with tax and discounts\n *\n * This service handles complex price calculations including:\n * - Tax rates based on country\n * - Quantity discounts\n * - Promotional codes\n *\n * @author John Doe \[email protected]>\n * @license GPL-2.0-or-later\n */\nfinal class PriceCalculationService\n{\n // ...\n}\n```\n\n### Method Documentation\n\n```php\n// ✅ Right: Complete method documentation\n/**\n * Calculate total price with tax for given items\n *\n * @param array\u003cint, array{product: Product, quantity: int}> $items\n * @param string $countryCode ISO 3166-1 alpha-2 country code\n * @param float $discountPercent Discount percentage (0-100)\n * @return float Total price including tax\n * @throws \\InvalidArgumentException If country code is invalid\n */\npublic function calculateTotal(\n array $items,\n string $countryCode,\n float $discountPercent = 0.0\n): float {\n // ...\n}\n\n// ❌ Wrong: Missing or incomplete documentation\n/**\n * Calculates total\n */\npublic function calculateTotal($items, $countryCode, $discountPercent = 0.0) {\n // Missing param types, descriptions, return type\n}\n```\n\n### Property Documentation\n\n```php\n// ✅ Right\n/**\n * @var UserRepository User data repository\n */\nprivate readonly UserRepository $userRepository;\n\n/**\n * @var array\u003cstring, mixed> Configuration options\n */\nprivate array $config = [];\n\n// ❌ Wrong: No type hint or description\n/**\n * @var mixed\n */\nprivate $userRepository;\n```\n\n## Curly Brace Placement\n\n### Classes and Methods: Same Line\n\n```php\n// ✅ Right: Opening brace on same line\nclass MyController\n{\n public function indexAction(): ResponseInterface\n {\n // ...\n }\n}\n\n// ❌ Wrong: Opening brace on new line (K&R style)\nclass MyController {\n public function indexAction(): ResponseInterface {\n // ...\n }\n}\n```\n\n### Control Structures: Same Line\n\n```php\n// ✅ Right\nif ($condition) {\n doSomething();\n}\n\nforeach ($items as $item) {\n processItem($item);\n}\n\n// ❌ Wrong: Opening brace on new line\nif ($condition)\n{\n doSomething();\n}\n```\n\n## Namespace and Use Statements\n\n### Namespace Structure\n\n```php\n// ✅ Right: Proper namespace declaration\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Domain\\Model;\n\nuse TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity;\n\nclass Product extends AbstractEntity\n{\n // ...\n}\n```\n\n### Use Statements Organization\n\n```php\n// ✅ Right: Grouped and sorted\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Controller;\n\nuse Psr\\Http\\Message\\ResponseInterface;\nuse TYPO3\\CMS\\Backend\\Template\\ModuleTemplateFactory;\nuse TYPO3\\CMS\\Core\\Imaging\\IconFactory;\nuse TYPO3\\CMS\\Extbase\\Mvc\\Controller\\ActionController;\nuse Vendor\\ExtensionKey\\Domain\\Repository\\ProductRepository;\n\n// ❌ Wrong: Unsorted, mixed\nuse TYPO3\\CMS\\Extbase\\Mvc\\Controller\\ActionController;\nuse Vendor\\ExtensionKey\\Domain\\Repository\\ProductRepository;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse TYPO3\\CMS\\Core\\Imaging\\IconFactory;\n```\n\n## Type Declarations\n\n### Strict Types\n\n```php\n// ✅ Right: declare(strict_types=1) at the top\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Service;\n\nclass MyService\n{\n public function calculate(int $value): float\n {\n return $value * 1.19;\n }\n}\n\n// ❌ Wrong: No strict types declaration\n\u003c?php\nnamespace Vendor\\ExtensionKey\\Service;\n\nclass MyService\n{\n public function calculate($value) // No type hints\n {\n return $value * 1.19;\n }\n}\n```\n\n### Property Type Declarations (PHP 7.4+)\n\n```php\n// ✅ Right: Typed properties\nclass User\n{\n private string $username;\n private int $id;\n private ?string $email = null;\n private array $roles = [];\n}\n\n// ❌ Wrong: No type declarations\nclass User\n{\n private $username;\n private $id;\n private $email;\n private $roles;\n}\n```\n\n### PHP 8.4 Explicit Nullable Types (Critical)\n\nPHP 8.4 deprecates **implicit nullable parameters**. Parameters with `null` default values MUST use explicit nullable syntax.\n\n**Search Pattern:**\n```bash\n# Find implicit nullable parameters\ngrep -rn '\\(.*\\$[a-zA-Z_]* = null\\)' Classes/ | grep -v '?[a-zA-Z_\\\\]*\\s*\\

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

\n```\n\n**Severity:** High (E_DEPRECATED in PHP 8.4, Error in PHP 9.0)\n\n```php\n// ❌ Wrong: Implicit nullable (deprecated in PHP 8.4)\npublic function process(string $data = null): void {}\npublic function configure(array $options = null): self {}\npublic function setLogger(LoggerInterface $logger = null): void {}\n\n// ✅ Right: Explicit nullable type\npublic function process(?string $data = null): void {}\npublic function configure(?array $options = null): self {}\npublic function setLogger(?LoggerInterface $logger = null): void {}\n```\n\n**Common Occurrences:**\n- FlexForm configuration parameters\n- Optional constructor dependencies\n- Setter methods with optional values\n- Hook/callback method signatures\n\n**Rector Rule:** Use `NullableTypeDeclarationRector` to auto-fix.\n\n## File Structure\n\n### Standard File Template\n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\n/*\n * This file is part of the TYPO3 CMS project.\n *\n * It is free software; you can redistribute it and/or modify it under\n * the terms of the GNU General Public License, either version 2\n * of the License, or any later version.\n *\n * For the full copyright and license information, please read the\n * LICENSE.txt file that was distributed with this source code.\n *\n * The TYPO3 project - inspiring people to share!\n */\n\nnamespace Vendor\\ExtensionKey\\Domain\\Model;\n\nuse TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity;\n\n/**\n * Product model\n */\nclass Product extends AbstractEntity\n{\n /**\n * @var string Product title\n */\n private string $title = '';\n\n public function getTitle(): string\n {\n return $this->title;\n }\n\n public function setTitle(string $title): void\n {\n $this->title = $title;\n }\n}\n```\n\n## PHPStan and Static Analysis\n\nTYPO3 extensions should use **PHPStan level 10** (strictest) for maximum type safety and code quality.\n\n### PHPStan Baseline Hygiene\n\n**Critical Rule:** New code must NEVER add errors to `phpstan-baseline.neon`.\n\nThe baseline file exists only for legacy code that hasn't been refactored yet. All new code must pass PHPStan level 10 without baseline suppression.\n\n**Validation:**\n```bash\n# Check if your changes added to baseline\ngit diff HEAD~1 Build/phpstan-baseline.neon\n\n# If count increased, you MUST fix the underlying issues\n# Example: count: 8 → count: 9 means you added 1 new error\n```\n\n### Type-Safe Mixed Value Handling\n\n**Common PHPStan Error:** \"Cannot cast mixed to int/string/bool\"\n\n**Occurs with:** TypoScript configuration, user input, API responses\n\n**❌ Wrong (adds to baseline):**\n```php\n// PHPStan: Cannot cast mixed to int\n$maxSize = (int) ($conf['maxSize'] ?? 0);\n```\n\n**✅ Right (passes level 10):**\n```php\n// Type-guard before casting\n$value = $conf['maxSize'] ?? 0;\nif (is_numeric($value)) {\n $maxSize = (int) $value;\n} else {\n $maxSize = 0;\n}\n```\n\n### Common Mixed Type Patterns\n\n**Arrays from configuration:**\n```php\n// ❌ Wrong\n$items = (array) $conf['items'];\n\n// ✅ Right\n$items = [];\nif (isset($conf['items']) && is_array($conf['items'])) {\n $items = $conf['items'];\n}\n```\n\n**Strings from user input:**\n```php\n// ❌ Wrong\n$name = (string) $_POST['name'];\n\n// ✅ Right\n$name = '';\nif (isset($_POST['name']) && is_string($_POST['name'])) {\n $name = $_POST['name'];\n}\n```\n\n**Boolean from configuration:**\n```php\n// ❌ Wrong\n$enabled = (bool) $conf['enabled'];\n\n// ✅ Right\n$enabled = isset($conf['enabled']) && (bool) $conf['enabled'];\n```\n\n### Pre-Commit PHPStan Check\n\nAlways run PHPStan before committing:\n\n```bash\n# Run PHPStan\ncomposer ci:php:stan\n\n# Verify no new baseline entries\ngit diff Build/phpstan-baseline.neon\n\n# If baseline changed, fix the issues instead of committing the baseline\n```\n\n## php-cs-fixer Configuration\n\n### CRITICAL: ext_emconf.php Exclusion\n\n**Problem:** When `declare_strict_types` rule is enabled in php-cs-fixer, it adds `declare(strict_types=1);` to ALL PHP files, including `ext_emconf.php`. However, TYPO3 specifically requires that `ext_emconf.php` does NOT have strict types declaration.\n\n**Why:** The `ext_emconf.php` file is processed by TYPO3's Extension Manager in a special context during extension installation and updates. Adding strict types can break this process.\n\n**Solution:** Always exclude `ext_emconf.php` from php-cs-fixer processing:\n\n```php\n// Using TYPO3 CodingStandards package\n$config->getFinder()\n ->in('Classes')\n ->in('Configuration')\n ->in('Tests')\n ->notName('ext_emconf.php'); // CRITICAL: Must exclude\n\n// Using custom PhpCsFixer\\Finder\nPhpCsFixer\\Finder::create()\n ->exclude('.build')\n ->exclude('config')\n ->exclude('node_modules')\n ->exclude('var')\n ->notName('ext_emconf.php') // CRITICAL: Must exclude\n ->in(__DIR__ . '/../');\n```\n\n**CI Failure Pattern:** If you see CGL (Coding Guidelines Linter) failures with exit code 8 and changes to `ext_emconf.php` involving `declare(strict_types=1)`, add the `->notName('ext_emconf.php')` exclusion.\n\n### Recommended php-cs-fixer Rules for TYPO3\n\nUse the TYPO3 Coding Standards package for the most up-to-date configuration:\n\n```bash\ncomposer require --dev typo3/coding-standards\n```\n\nSee `assets/Build/php-cs-fixer/php-cs-fixer.php` for a complete template.\n\n## Conformance Checklist\n\n- [ ] All PHP files use 4 spaces for indentation (NO tabs)\n- [ ] Variables and methods use camelCase\n- [ ] Classes use UpperCamelCase\n- [ ] Constants use SCREAMING_SNAKE_CASE\n- [ ] Array short syntax [] used (not array())\n- [ ] Multi-line arrays have trailing commas\n- [ ] Strings use single quotes by default\n- [ ] String concatenation has spaces around `.` operator\n- [ ] All classes have PHPDoc comments\n- [ ] All public methods have PHPDoc with @param and @return\n- [ ] Opening braces on same line for classes/methods\n- [ ] declare(strict_types=1) at top of all PHP files\n- [ ] Proper namespace structure matching directory\n- [ ] Use statements grouped and sorted\n- [ ] Type declarations on all properties and method parameters\n- [ ] Maximum line length 120 characters\n- [ ] Unix line endings (LF)\n- [ ] PHPStan level 10 passes with zero errors\n- [ ] No new errors added to phpstan-baseline.neon\n- [ ] Type-guards before casting mixed values (is_numeric, is_string, is_array)\n- [ ] php-cs-fixer config excludes ext_emconf.php via `->notName('ext_emconf.php')`\n- [ ] CGL vs PHPStan conflicts resolved in favor of CGL (see below)\n\n## CGL vs PHPStan Conflict Resolution\n\nWhen CGL (php-cs-fixer) and PHPStan disagree, **CGL is authoritative for code style**.\n\n### Static Assertions in PHPUnit 11\n\nCGL enforces `self::assertEquals()` but PHPUnit 11 marks assertion methods as non-static. PHPStan reports false positives.\n\n**Resolution:** Suppress PHPStan, not CGL:\n\n```yaml\n# Build/phpstan/phpstan.neon\nparameters:\n ignoreErrors:\n -\n message: '#Call to an undefined static method .+::(assert[A-Z]\\w*|fail|markTest\\w*)#'\n reportUnmatched: false\n```\n\n- Use `reportUnmatched: false` — on TYPO3 12.4 with testing-framework v8 (PHPUnit 10), the pattern has no matches\n- This pattern is safe across the full TYPO3 12.4/13.4/14.0 matrix\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":16426,"content_sha256":"dcb9d2b033f5918295af6266ec6e26b799186dca2316defb6882693740189e53"},{"filename":"references/composer-validation.md","content":"# Composer.json Validation Standards (TYPO3 v13)\n\n**Source:** TYPO3 Core API Reference v13.4 - FileStructure/ComposerJson.html\n**Purpose:** Complete validation rules for composer.json in TYPO3 extensions\n\n## Mandatory Fields\n\n### name\n**Format:** `\u003cvendor>/\u003cdashed-extension-key>`\n\n**Examples:**\n```json\n\"name\": \"vendor-name/my-extension\"\n\"name\": \"johndoe/some-extension\"\n```\n\n**Validation:**\n```bash\njq -r '.name' composer.json | grep -E '^[a-z0-9-]+/[a-z0-9-]+

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

&& echo \"✅ Valid\" || echo \"❌ Invalid format\"\n```\n\n### type\n**Required Value:** `typo3-cms-extension` (for third-party extensions)\n\n**Validation:**\n```bash\njq -r '.type' composer.json | grep -q \"typo3-cms-extension\" && echo \"✅ Correct type\" || echo \"❌ Wrong type\"\n```\n\n### description\n**Format:** Single-line summary describing what the extension does\n\n**Requirements:**\n- Clear, concise description of extension functionality\n- Should identify the vendor/company for professional extensions\n- Avoid vague descriptions like \"An extension\" or \"Utility tools\"\n\n**Good Examples:**\n```json\n\"description\": \"Adds image support to CKEditor5 RTE - by Netresearch\"\n\"description\": \"TYPO3 extension for advanced content management by Vendor GmbH\"\n\"description\": \"Provides custom form elements for newsletter subscription\"\n```\n\n**Bad Examples:**\n```json\n\"description\": \"Extension\" // Too vague\n\"description\": \"Some tools\" // Meaningless\n\"description\": \"\" // Empty\n```\n\n**Validation:**\n```bash\n# Check description exists and is not empty\njq -r '.description' composer.json | grep -q . && echo \"✅ Has description\" || echo \"❌ Missing description\"\n\n# Check description length (should be meaningful, >20 chars)\nDESC_LEN=$(jq -r '.description | length' composer.json)\n[[ $DESC_LEN -gt 20 ]] && echo \"✅ Description is meaningful\" || echo \"⚠️ Description too short\"\n```\n\n### license\n**Recommended:** `GPL-2.0-only` or `GPL-2.0-or-later`\n\n**Validation:**\n```bash\njq -r '.license' composer.json | grep -qE \"GPL-2.0-(only|or-later)\" && echo \"✅ GPL license\" || echo \"⚠️ Check license\"\n```\n\n### require\n**Minimum:** Must specify `typo3/cms-core` with version constraints\n\n**Version Constraint Format:**\n- `^12.4 || ^13.4` - Multiple major versions (recommended for v12/v13 compat)\n- `^12.4` - Single major version\n- `>=12.4` ❌ - NO upper bound (not recommended)\n\n**Validation:**\n```bash\n# Check typo3/cms-core present\njq -r '.require[\"typo3/cms-core\"]' composer.json | grep -q . && echo \"✅ TYPO3 core required\" || echo \"❌ Missing typo3/cms-core\"\n\n# Check for upper bound (^ or specific upper version)\njq -r '.require[\"typo3/cms-core\"]' composer.json | grep -qE '(\\^|[0-9]+\\.[0-9]+\\.[0-9]+-[0-9]+\\.[0-9]+\\.[0-9]+)' && echo \"✅ Has upper bound\" || echo \"⚠️ Missing upper bound\"\n```\n\n### autoload\n**Format:** PSR-4 mapping to Classes/ directory\n\n**Example:**\n```json\n\"autoload\": {\n \"psr-4\": {\n \"Vendor\\\\ExtensionName\\\\\": \"Classes/\"\n }\n}\n```\n\n**Validation:**\n```bash\njq -r '.autoload[\"psr-4\"]' composer.json | grep -q \"Classes\" && echo \"✅ PSR-4 autoload configured\" || echo \"❌ Missing autoload\"\n```\n\n### extra.typo3/cms.extension-key\n**Required:** Maps to underscored extension key\n\n**Example:**\n```json\n\"extra\": {\n \"typo3/cms\": {\n \"extension-key\": \"my_extension\"\n }\n}\n```\n\n**Validation:**\n```bash\njq -r '.extra.\"typo3/cms\".\"extension-key\"' composer.json | grep -q . && echo \"✅ Extension key defined\" || echo \"❌ Missing extension-key\"\n```\n\n---\n\n## Recommended Fields (Professional Extensions)\n\n### authors\n**Format:** Array of author objects with name, email, role, homepage\n\n**Example:**\n```json\n\"authors\": [\n {\n \"name\": \"Developer Name\",\n \"email\": \"[email protected]\",\n \"role\": \"Developer\",\n \"homepage\": \"https://www.company.com/\"\n }\n]\n```\n\n**Required Sub-Fields:**\n| Field | Format | Purpose |\n|-------|--------|---------|\n| `name` | String | Developer's full name |\n| `email` | Email address | Contact email |\n| `role` | String | `Developer`, `Maintainer`, `Lead Developer` |\n| `homepage` | URL | Company or personal website |\n\n**Validation:**\n```bash\n# Check authors array exists\njq -r '.authors' composer.json | grep -q \"name\" && echo \"✅ Has authors\" || echo \"⚠️ Missing authors\"\n\n# Check authors have email\njq -r '.authors[].email' composer.json | grep -q \"@\" && echo \"✅ Has author emails\" || echo \"⚠️ Missing author emails\"\n\n# Check authors have homepage\njq -r '.authors[].homepage' composer.json | grep -q \"http\" && echo \"✅ Has author homepage\" || echo \"⚠️ Missing author homepage\"\n```\n\n### homepage\n**Format:** URL to project repository or documentation\n\n**Example:**\n```json\n\"homepage\": \"https://github.com/vendor/extension-name\"\n```\n\n**Validation:**\n```bash\njq -r '.homepage' composer.json | grep -qE \"^https?://\" && echo \"✅ Has homepage\" || echo \"⚠️ Missing homepage\"\n```\n\n### support\n**Format:** Object with support channels\n\n**Example:**\n```json\n\"support\": {\n \"issues\": \"https://github.com/vendor/extension/issues\",\n \"source\": \"https://github.com/vendor/extension\"\n}\n```\n\n**Validation:**\n```bash\njq -r '.support.issues' composer.json | grep -q \"http\" && echo \"✅ Has issues URL\" || echo \"⚠️ Missing issues URL\"\n```\n\n### keywords\n**Format:** Array of relevant keywords for discoverability\n\n**Example:**\n```json\n\"keywords\": [\n \"TYPO3\",\n \"extension\",\n \"content\",\n \"management\"\n]\n```\n\n**Validation:**\n```bash\njq -r '.keywords | length' composer.json | grep -qE '^[1-9]' && echo \"✅ Has keywords\" || echo \"⚠️ Missing keywords\"\n```\n\n---\n\n## Complete Required Fields Checklist\n\n**Mandatory (MUST have):**\n- [ ] `name` - vendor/package format\n- [ ] `type` - must be `typo3-cms-extension`\n- [ ] `description` - clear, concise description\n- [ ] `license` - SPDX identifier (GPL-2.0-or-later, AGPL-3.0-or-later)\n- [ ] `require.typo3/cms-core` - with upper bound constraint\n- [ ] `require.php` - PHP version constraint\n- [ ] `autoload.psr-4` - mapping to Classes/\n- [ ] `extra.typo3/cms.extension-key` - underscored extension key\n\n**Recommended (SHOULD have):**\n- [ ] `authors` - with name, email, role, homepage\n- [ ] `homepage` - project repository URL\n- [ ] `support.issues` - issue tracker URL\n- [ ] `keywords` - for discoverability\n\n---\n\n## Deprecated Properties\n\n### replace with typo3-ter vendor\n**Status:** DEPRECATED - Legacy TER integration approach\n\n**Detection:**\n```bash\njq -r '.replace' composer.json | grep -q \"typo3-ter\" && echo \"⚠️ Deprecated: typo3-ter in replace\" || echo \"✅ No deprecated replace\"\n```\n\n### replace with \"ext_key\": \"self.version\"\n**Status:** DEPRECATED - Legacy dependency specification\n\n**Detection:**\n```bash\njq -r '.replace' composer.json | grep -qE '\"[a-z_]+\": \"self.version\"' && echo \"⚠️ Deprecated: self.version replace\" || echo \"✅ No self.version\"\n```\n\n---\n\n## TYPO3 v12-v13 Version Constraints\n\n### Recommended Format\n```json\n\"require\": {\n \"typo3/cms-core\": \"^12.4 || ^13.4\",\n \"php\": \"^8.1\"\n}\n```\n\n### PHP Version Constraints\n```json\n\"require\": {\n \"php\": \"^8.1\" // TYPO3 v12: PHP 8.1-8.4\n}\n```\n\n**Validation:**\n```bash\n# Check PHP constraint\njq -r '.require.php' composer.json | grep -qE '\\^8\\.[1-4]' && echo \"✅ Valid PHP constraint\" || echo \"⚠️ Check PHP version\"\n```\n\n---\n\n## Synchronization with ext_emconf.php\n\n**Critical:** `composer.json` and `ext_emconf.php` must have matching dependency constraints.\n\n**Mapping:**\n\n| composer.json | ext_emconf.php |\n|--------------|----------------|\n| `require.typo3/cms-core` | `constraints.depends.typo3` |\n| `require.php` | `constraints.depends.php` |\n| `require.*` | `constraints.depends.*` |\n\n**Example Synchronization:**\n\ncomposer.json:\n```json\n\"require\": {\n \"typo3/cms-core\": \"^12.4 || ^13.4\",\n \"php\": \"^8.1\",\n \"typo3/cms-fluid\": \"^12.4 || ^13.4\"\n}\n```\n\next_emconf.php:\n```php\n'constraints' => [\n 'depends' => [\n 'typo3' => '12.4.0-13.4.99',\n 'php' => '8.1.0-8.4.99',\n 'fluid' => '12.4.0-13.4.99',\n ],\n],\n```\n\n---\n\n## Complete Validation Script\n\n```bash\n#!/bin/bash\n# validate-composer.sh\n\nERRORS=0\n\necho \"=== Composer.json Validation ====\"\n\n# Check mandatory fields\njq -r '.name' composer.json > /dev/null 2>&1 || { echo \"❌ Missing 'name'\"; ((ERRORS++)); }\njq -r '.type' composer.json | grep -q \"typo3-cms-extension\" || { echo \"❌ Wrong or missing 'type'\"; ((ERRORS++)); }\njq -r '.description' composer.json | grep -q . || { echo \"❌ Missing 'description'\"; ((ERRORS++)); }\n\n# Check description is meaningful (>20 chars)\nDESC_LEN=$(jq -r '.description | length' composer.json 2>/dev/null)\n[[ $DESC_LEN -lt 20 ]] && { echo \"⚠️ Description too short (should be >20 chars)\"; ((WARNINGS++)); }\n\n# Check typo3/cms-core\njq -r '.require[\"typo3/cms-core\"]' composer.json | grep -q . || { echo \"❌ Missing typo3/cms-core\"; ((ERRORS++)); }\n\n# Check version constraints have upper bounds\njq -r '.require[\"typo3/cms-core\"]' composer.json | grep -qE '(\\^|[0-9]+\\.[0-9]+\\.[0-9]+-[0-9]+\\.[0-9]+\\.[0-9]+)' || { echo \"⚠️ TYPO3 constraint missing upper bound\"; ((ERRORS++)); }\n\n# Check autoload\njq -r '.autoload[\"psr-4\"]' composer.json | grep -q \"Classes\" || { echo \"❌ Missing PSR-4 autoload\"; ((ERRORS++)); }\n\n# Check extension-key\njq -r '.extra.\"typo3/cms\".\"extension-key\"' composer.json | grep -q . || { echo \"❌ Missing extension-key\"; ((ERRORS++)); }\n\n# Check for deprecated replace\njq -r '.replace' composer.json 2>/dev/null | grep -q \"typo3-ter\\|self.version\" && echo \"⚠️ Deprecated replace property found\"\n\necho \"\"\necho \"Validation complete: $ERRORS critical errors\"\nexit $ERRORS\n```\n\n---\n\n## Optional but Recommended Fields\n\n### require-dev\n**Purpose:** Development dependencies not needed in production\n\n**Example:**\n```json\n\"require-dev\": {\n \"typo3/coding-standards\": \"^0.7\",\n \"phpstan/phpstan\": \"^1.10\",\n \"phpunit/phpunit\": \"^10.0\"\n}\n```\n\n### suggest\n**Purpose:** Optional packages that enhance functionality\n\n**Example:**\n```json\n\"suggest\": {\n \"typo3/cms-filelist\": \"For file browser functionality\",\n \"typo3/cms-reactions\": \"For webhook support\"\n}\n```\n\n---\n\n## Best Practices\n\n1. **Packagist Publication:** Publishing to Packagist makes extensions available in TYPO3 Extension Repository automatically\n2. **Documentation Rendering:** `composer.json` is **REQUIRED** for extensions with documentation on docs.typo3.org\n3. **Version Constraint Strategy:**\n - Use `^` for flexible upper bounds\n - Specify both major version ranges for v12/v13 compatibility\n - Always include upper bounds (avoid `>=` without upper limit)\n4. **Namespace Alignment:** PSR-4 namespace should match vendor/extension structure\n5. **Composer Priority:** Composer-based installations prioritize `composer.json` over `ext_emconf.php` for dependency resolution\n\n---\n\n## Common Violations and Fixes\n\n### Missing extra.typo3/cms.extension-key\n\n❌ **Before:**\n```json\n{\n \"name\": \"vendor/my-extension\",\n \"type\": \"typo3-cms-extension\"\n}\n```\n\n✅ **After:**\n```json\n{\n \"name\": \"vendor/my-extension\",\n \"type\": \"typo3-cms-extension\",\n \"extra\": {\n \"typo3/cms\": {\n \"extension-key\": \"my_extension\"\n }\n }\n}\n```\n\n### Version Constraint Without Upper Bound\n\n❌ **Before:**\n```json\n\"require\": {\n \"typo3/cms-core\": \">=12.4\"\n}\n```\n\n✅ **After:**\n```json\n\"require\": {\n \"typo3/cms-core\": \"^12.4 || ^13.4\"\n}\n```\n\n### Deprecated replace Property\n\n❌ **Before:**\n```json\n\"replace\": {\n \"typo3-ter/my-extension\": \"self.version\"\n}\n```\n\n✅ **After:**\n```json\n// Remove replace property entirely\n```\n\n---\n\n## Additional Validation Commands\n\n### Check all required dependencies have upper bounds\n```bash\njq -r '.require | to_entries[] | select(.value | test(\">=\") and (test(\"\\\\^\") | not)) | .key' composer.json\n```\n\n### Verify package type\n```bash\njq -r '.type' composer.json | grep -q \"typo3-cms-extension\" && echo \"✅\" || echo \"❌ Wrong package type\"\n```\n\n### Check PSR-4 namespace format\n```bash\njq -r '.autoload[\"psr-4\"] | keys[]' composer.json | grep -E '^[A-Z][a-zA-Z0-9]*\\\\\\\\[A-Z][a-zA-Z0-9]*\\\\\\\\

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

&& echo \"✅ Valid namespace\" || echo \"⚠️ Check namespace format\"\n```\n\n### Validate JSON syntax\n```bash\njq . composer.json > /dev/null && echo \"✅ Valid JSON\" || echo \"❌ JSON syntax error\"\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":12218,"content_sha256":"6542761396e7bc706925a84745e043e46d310d10d9e2a3ebfb754cd69e369c3e"},{"filename":"references/crowdin-integration.md","content":"# TYPO3 Crowdin Integration Validation\n\n**Purpose**: Validate TYPO3 extension Crowdin integration against official TYPO3 standards for centralized translation management.\n\n**Official Reference**: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Localization/TranslationServer/Crowdin/ExtensionIntegration.html\n\n## Overview\n\nTYPO3 uses a **centralized translation server architecture** where extensions integrate with TYPO3's Crowdin organization, not standalone Crowdin projects. This ensures consistency across the TYPO3 ecosystem and enables the TYPO3 community's translation workflow.\n\n## Critical Architecture Understanding\n\n```\nExtension Repository (GitHub/GitLab/BitBucket)\n ↓ Push to main branch\nGitHub Actions: Upload sources to Crowdin\n ↓\nTYPO3 Central Crowdin Organization\n ↓ Translators work via TYPO3's Crowdin instance\nCrowdin Native Integration (Option A) OR GitHub Actions Download (Option B)\n ↓ Creates PR with translations via service branch (l10n_*)\nExtension Repository\n ↓ Maintainer reviews and merges PR\nTranslations committed to main branch\n```\n\n**Key Difference from Standalone Crowdin**:\n- ❌ Extensions do NOT create their own Crowdin projects\n- ✅ Extensions are added to TYPO3's centralized Crowdin organization\n- ✅ TYPO3 localization team manages project setup\n- ✅ Translations flow through TYPO3's established ecosystem\n\n## Prerequisites\n\n1. **Repository Hosting**: GitHub, GitLab (SaaS or self-managed), or BitBucket\n2. **TYPO3 Org Membership**: Extension must be added by TYPO3 localization team\n3. **Contact Channel**: Slack `#typo3-localization-team`\n4. **Branch Support**: TYPO3 currently supports one branch/version (typically `main`)\n\n## Configuration File Validation (crowdin.yml)\n\n### Required Structure\n\n```yaml\npreserve_hierarchy: 1\nfiles:\n - source: /Resources/Private/Language/*.xlf\n translation: /%original_path%/%two_letters_code%.%original_file_name%\n ignore:\n - /**/%two_letters_code%.%original_file_name%\n```\n\n### Validation Checklist\n\n**✅ MUST HAVE:**\n- [ ] `preserve_hierarchy: 1` at top level (maintains directory structure)\n- [ ] Source pattern uses wildcard: `/Resources/Private/Language/*.xlf`\n- [ ] Translation pattern uses variables: `/%original_path%/%two_letters_code%.%original_file_name%`\n- [ ] Ignore directive present: `/**/%two_letters_code%.%original_file_name%`\n\n**❌ MUST NOT HAVE:**\n- [ ] `project_id_env` or `api_token_env` (belongs in GitHub Actions workflow)\n- [ ] `languages_mapping` (Crowdin handles automatically)\n- [ ] `type: xliff` (unnecessary, detected automatically)\n- [ ] `update_option`, `content_segmentation`, `save_translations` (defaults work)\n- [ ] Hardcoded filenames like `locallang_be.xlf` (use wildcard `*.xlf`)\n\n### Common Mistakes\n\n**Mistake 1: Hardcoded Single File**\n```yaml\n# ❌ WRONG - Not extensible\n- source: /Resources/Private/Language/locallang_be.xlf\n translation: /Resources/Private/Language/%two_letters_code%.locallang_be.xlf\n```\n\n```yaml\n# ✅ CORRECT - Supports multiple files\n- source: /Resources/Private/Language/*.xlf\n translation: /%original_path%/%two_letters_code%.%original_file_name%\n```\n\n**Mistake 2: Over-Configuration**\n```yaml\n# ❌ WRONG - Unnecessary complexity\nproject_id_env: CROWDIN_PROJECT_ID\napi_token_env: CROWDIN_PERSONAL_TOKEN\npreserve_hierarchy: true\nfiles:\n - source: /Resources/Private/Language/locallang_be.xlf\n translation: /Resources/Private/Language/%two_letters_code%.locallang_be.xlf\n languages_mapping:\n two_letters_code:\n de: de\n es: es\n type: xliff\n update_option: update_as_unapproved\n content_segmentation: true\n```\n\n```yaml\n# ✅ CORRECT - Simple TYPO3 standard\npreserve_hierarchy: 1\nfiles:\n - source: /Resources/Private/Language/*.xlf\n translation: /%original_path%/%two_letters_code%.%original_file_name%\n ignore:\n - /**/%two_letters_code%.%original_file_name%\n```\n\n**Mistake 3: Missing Ignore Directive**\n```yaml\n# ❌ WRONG - Will re-upload translations as sources\n- source: /Resources/Private/Language/*.xlf\n translation: /%original_path%/%two_letters_code%.%original_file_name%\n```\n\n```yaml\n# ✅ CORRECT - Ignores existing translations\n- source: /Resources/Private/Language/*.xlf\n translation: /%original_path%/%two_letters_code%.%original_file_name%\n ignore:\n - /**/%two_letters_code%.%original_file_name%\n```\n\n### Validation Script\n\n```bash\n#!/bin/bash\n# Validate crowdin.yml against TYPO3 standards\n\necho \"Validating crowdin.yml for TYPO3 compliance...\"\n\n# Check file exists\nif [[ ! -f crowdin.yml ]]; then\n echo \"❌ crowdin.yml not found\"\n exit 1\nfi\n\n# Check preserve_hierarchy\nif grep -q \"preserve_hierarchy: 1\" crowdin.yml; then\n echo \"✅ preserve_hierarchy: 1 present\"\nelse\n echo \"❌ Missing preserve_hierarchy: 1\"\nfi\n\n# Check wildcard source pattern\nif grep -q \"/Resources/Private/Language/\\*.xlf\" crowdin.yml; then\n echo \"✅ Wildcard source pattern (*.xlf) present\"\nelse\n echo \"❌ Missing wildcard pattern or hardcoded filename\"\nfi\n\n# Check translation pattern with variables\nif grep -q \"%original_path%.*%two_letters_code%.*%original_file_name%\" crowdin.yml; then\n echo \"✅ Translation pattern uses TYPO3 variables\"\nelse\n echo \"❌ Translation pattern not TYPO3-compliant\"\nfi\n\n# Check ignore directive\nif grep -q \"ignore:\" crowdin.yml; then\n echo \"✅ Ignore directive present\"\nelse\n echo \"❌ Missing ignore directive\"\nfi\n\n# Check for non-standard fields (should not be present)\nif grep -qE \"project_id_env|api_token_env|languages_mapping|type:|update_option|content_segmentation\" crowdin.yml; then\n echo \"⚠️ Non-standard fields detected (remove for TYPO3 compliance)\"\nfi\n\n# Check file length (should be ~5-7 lines)\nline_count=$(wc -l \u003c crowdin.yml)\nif [[ $line_count -le 10 ]]; then\n echo \"✅ Configuration is concise ($line_count lines)\"\nelse\n echo \"⚠️ Configuration is verbose ($line_count lines, TYPO3 standard is ~6 lines)\"\nfi\n```\n\n## GitHub Actions Workflow Validation\n\n### TYPO3-Standard Workflow\n\n**Option A: Simple Upload (Recommended for GitHub)**\n```yaml\nname: Crowdin\n\non:\n push:\n branches:\n - main\n\njobs:\n sync:\n name: Synchronize with Crowdin\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - name: Upload sources\n uses: crowdin/github-action@v2\n env:\n GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n with:\n config: 'crowdin.yml'\n project_id: ${{ secrets.CROWDIN_PROJECT_ID }}\n token: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}\n```\n\n**Option B: Native Crowdin-GitHub Integration (No workflow needed)**\n- Configure in Crowdin project settings → Integrations\n- Crowdin handles both upload and download automatically\n- Creates PRs via service branch (e.g., `l10n_main`)\n\n### Validation Checklist\n\n**✅ CORRECT PATTERNS:**\n- [ ] Single job named \"sync\" or similar\n- [ ] Triggers on push to main branch only\n- [ ] Uses `actions/checkout@v4` (or latest)\n- [ ] Uses `crowdin/github-action@v2` (or latest)\n- [ ] Provides `CROWDIN_PROJECT_ID` and `CROWDIN_PERSONAL_TOKEN` via secrets\n- [ ] References `crowdin.yml` config file\n- [ ] GITHUB_TOKEN provided (for action permissions)\n\n**❌ INCORRECT PATTERNS:**\n- [ ] Multiple jobs (upload AND download)\n- [ ] Cron schedule for downloading translations\n- [ ] `create_pull_request: true` in workflow (handled by Crowdin's native integration)\n- [ ] Path filters for source files (unnecessary complexity)\n- [ ] Manual PR creation logic in workflow\n- [ ] Complex if conditions based on event types\n\n### Common Mistakes\n\n**Mistake 1: Download Job in Workflow**\n```yaml\n# ❌ WRONG - Conflicts with Crowdin's native GitHub integration\njobs:\n upload-sources:\n # ... upload logic\n\n download-translations: # ← Should NOT exist\n # ... download and PR creation logic\n```\n\n```yaml\n# ✅ CORRECT - Single job for upload only\njobs:\n sync:\n name: Synchronize with Crowdin\n # ... upload sources only\n```\n\n**Mistake 2: Cron-Based Translation Downloads**\n```yaml\n# ❌ WRONG - TYPO3 uses Crowdin's native integration for downloads\non:\n push:\n branches: [main]\n schedule: # ← Should NOT exist for TYPO3 extensions\n - cron: '0 2 * * *'\n```\n\n```yaml\n# ✅ CORRECT - Upload on push only\non:\n push:\n branches:\n - main\n```\n\n**Mistake 3: Path Filters**\n```yaml\n# ❌ UNNECESSARY - Adds complexity without benefit\non:\n push:\n branches: [main]\n paths:\n - 'Resources/Private/Language/locallang_be.xlf'\n```\n\n```yaml\n# ✅ BETTER - Simple trigger\non:\n push:\n branches:\n - main\n```\n\n### Validation Script\n\n```bash\n#!/bin/bash\n# Validate GitHub workflow for TYPO3 Crowdin compliance\n\nworkflow_file=\".github/workflows/crowdin.yml\"\n[[ ! -f \"$workflow_file\" ]] && workflow_file=\".github/workflows/crowdin.yaml\"\n[[ ! -f \"$workflow_file\" ]] && workflow_file=\".github/workflows/crowdin-sync.yml\"\n\nif [[ ! -f \"$workflow_file\" ]]; then\n echo \"⚠️ No Crowdin workflow found (using native integration?)\"\n exit 0\nfi\n\necho \"Validating $workflow_file for TYPO3 compliance...\"\n\n# Check for download job (should NOT exist)\nif grep -q \"download.*translation\" \"$workflow_file\"; then\n echo \"❌ Download job detected (conflicts with TYPO3's Crowdin integration)\"\nelse\n echo \"✅ No download job (correct - handled by Crowdin)\"\nfi\n\n# Check for cron schedule (should NOT exist)\nif grep -q \"schedule:\" \"$workflow_file\"; then\n echo \"❌ Cron schedule detected (not needed for TYPO3 extensions)\"\nelse\n echo \"✅ No cron schedule (correct)\"\nfi\n\n# Check for single job\njob_count=$(grep -c \"^ [a-z_-]*:$\" \"$workflow_file\")\nif [[ $job_count -eq 1 ]]; then\n echo \"✅ Single job present (correct)\"\nelse\n echo \"⚠️ Multiple jobs detected ($job_count jobs)\"\nfi\n\n# Check for required secrets\nif grep -q \"CROWDIN_PROJECT_ID\" \"$workflow_file\" && grep -q \"CROWDIN_PERSONAL_TOKEN\" \"$workflow_file\"; then\n echo \"✅ Required secrets referenced\"\nelse\n echo \"❌ Missing required secrets (CROWDIN_PROJECT_ID, CROWDIN_PERSONAL_TOKEN)\"\nfi\n\n# Check for checkout action version\nif grep -q \"actions/checkout@v4\" \"$workflow_file\"; then\n echo \"✅ Using checkout@v4 (current)\"\nelif grep -q \"actions/checkout@v3\" \"$workflow_file\"; then\n echo \"⚠️ Using checkout@v3 (consider upgrading to v4)\"\nfi\n\n# Check for crowdin action version\nif grep -q \"crowdin/github-action@v2\" \"$workflow_file\"; then\n echo \"✅ Using crowdin/github-action@v2\"\nelif grep -q \"crowdin/github-action@v1\" \"$workflow_file\"; then\n echo \"⚠️ Using crowdin/github-action@v1 (upgrade to v2)\"\nfi\n```\n\n## Translation File Structure\n\n### File Naming Convention\n\n**Source files** (English):\n```\nResources/Private/Language/locallang.xlf\nResources/Private/Language/locallang_be.xlf\nResources/Private/Language/locallang_db.xlf\n```\n\n**Translation files**:\n```\nResources/Private/Language/de.locallang.xlf\nResources/Private/Language/de.locallang_be.xlf\nResources/Private/Language/de.locallang_db.xlf\n```\n\nPattern: `{two_letter_code}.{original_filename}.xlf`\n\n### XLIFF Structure Requirements\n\n**Critical**: Translation files MUST include both `\u003csource>` and `\u003ctarget>` elements for optimal Crowdin import.\n\n```xml\n\u003c!-- ✅ CORRECT - Has both source and target -->\n\u003ctrans-unit id=\"labels.ckeditor.title\" xml:space=\"preserve\">\n \u003csource>Image Title\u003c/source>\n \u003ctarget>Bildtitel\u003c/target>\n\u003c/trans-unit>\n\n\u003c!-- ❌ WRONG - Missing source element -->\n\u003ctrans-unit id=\"labels.ckeditor.title\" xml:space=\"preserve\">\n \u003ctarget>Bildtitel\u003c/target>\n\u003c/trans-unit>\n```\n\n### Validation Script\n\n```bash\n#!/bin/bash\n# Validate translation file structure\n\necho \"Validating translation file structure...\"\n\n# Check source files (no language prefix)\nsource_files=$(find Resources/Private/Language/ -maxdepth 1 -name \"*.xlf\" ! -name \"*.*\\.xlf\" 2>/dev/null)\nif [[ -n \"$source_files\" ]]; then\n echo \"✅ Source files found:\"\n echo \"$source_files\" | sed 's/^/ /'\nelse\n echo \"❌ No source files found in Resources/Private/Language/\"\nfi\n\n# Check translation files (with language prefix)\ntranslation_files=$(find Resources/Private/Language/ -maxdepth 1 -name \"[a-z][a-z].*.xlf\" 2>/dev/null)\nif [[ -n \"$translation_files\" ]]; then\n echo \"✅ Translation files found:\"\n echo \"$translation_files\" | sed 's/^/ /'\nelse\n echo \"⚠️ No translation files found (expected for new extensions)\"\nfi\n\n# Validate XLIFF structure (both source and target elements)\nfor xlf in Resources/Private/Language/[a-z][a-z].*.xlf; do\n [[ ! -f \"$xlf\" ]] && continue\n\n if grep -q \"\u003csource>\" \"$xlf\" && grep -q \"\u003ctarget>\" \"$xlf\"; then\n echo \"✅ $xlf has both \u003csource> and \u003ctarget> elements\"\n else\n echo \"❌ $xlf missing \u003csource> or \u003ctarget> elements\"\n fi\ndone\n```\n\n## Project Setup Workflow\n\n### Step 1: Initial Contact\n\nContact TYPO3 localization team via Slack:\n- Channel: `#typo3-localization-team`\n- Provide: Extension name, maintainer email, repository URL\n- Request: Add extension to TYPO3's Crowdin organization\n\n### Step 2: Integration Choice\n\n**Option A: Native Crowdin-GitHub Integration** (Recommended)\n1. In Crowdin project settings → Integrations\n2. Select \"Source and translation files mode\"\n3. Authorize GitHub access and select repository\n4. Configure branch (typically `main`)\n5. Accept service branch name (e.g., `l10n_main`)\n6. Specify `crowdin.yml` as configuration file\n7. Enable \"One-time translation import\" for existing translations\n8. Disable \"Push Sources\" (managed in extension repository)\n\n**Option B: GitHub Actions Workflow**\n1. Create `.github/workflows/crowdin.yml` with upload job\n2. Configure `CROWDIN_PROJECT_ID` and `CROWDIN_PERSONAL_TOKEN` secrets\n3. Translations still downloaded via Crowdin's native integration\n\n### Step 3: Initial Translation Upload\n\nFor existing translations, prepare ZIP file:\n```bash\nzip translations.zip Resources/Private/Language/*.*.xlf\n```\nUpload via Crowdin UI → Translations tab\n\n### Step 4: Verify Workflow\n\n1. Push source file change to main branch\n2. Verify GitHub Actions uploads to Crowdin (if using Option B)\n3. Wait for translations to complete in Crowdin\n4. Verify Crowdin creates PR with translations via service branch\n5. Review and merge translation PR\n\n## Scoring Criteria\n\n### Base Scoring (0-2 points)\n\n**0 points**: No Crowdin integration\n- `crowdin.yml` not present\n- No translation automation\n\n**+1 point**: Basic Crowdin integration\n- `crowdin.yml` exists\n- Some configuration present\n- May not follow TYPO3 standards\n\n**+2 points**: TYPO3-compliant Crowdin integration\n- `crowdin.yml` follows TYPO3 standard format\n- `preserve_hierarchy: 1` present\n- Wildcard source patterns (`*.xlf`)\n- Proper translation pattern with variables\n- Ignore directive present\n- No unnecessary fields\n- GitHub workflow (if present) only uploads sources\n- No download job or cron schedule\n\n### Excellence Validation\n\nFor full +2 points, ALL of the following must be true:\n\n1. **Configuration Compliance**:\n - ✅ `preserve_hierarchy: 1`\n - ✅ Source: `/Resources/Private/Language/*.xlf`\n - ✅ Translation: `/%original_path%/%two_letters_code%.%original_file_name%`\n - ✅ Ignore directive present\n - ✅ No project_id_env or api_token_env in crowdin.yml\n - ✅ No languages_mapping or unnecessary fields\n\n2. **Workflow Compliance** (if GitHub Actions used):\n - ✅ Single job for upload\n - ✅ No download job\n - ✅ No cron schedule\n - ✅ Triggers on push to main\n - ✅ Uses latest action versions\n\n3. **Translation Structure**:\n - ✅ Source files in `Resources/Private/Language/` (no language prefix)\n - ✅ Translation files follow `{lang}.{filename}.xlf` pattern\n - ✅ XLIFF files have both `\u003csource>` and `\u003ctarget>` elements\n\n## Automated Validation Command\n\n```bash\n#!/bin/bash\n# Comprehensive TYPO3 Crowdin integration validation\n\nscore=0\nmax_score=2\n\necho \"=== TYPO3 Crowdin Integration Validation ===\"\necho\n\n# Check crowdin.yml existence\nif [[ ! -f crowdin.yml ]]; then\n echo \"❌ crowdin.yml not found (0/2 points)\"\n exit 0\nfi\necho \"✅ crowdin.yml found\"\n\n# Validate configuration\nconfig_valid=true\n\nif ! grep -q \"preserve_hierarchy: 1\" crowdin.yml; then\n echo \"❌ Missing preserve_hierarchy: 1\"\n config_valid=false\nfi\n\nif ! grep -q \"/Resources/Private/Language/\\*.xlf\" crowdin.yml; then\n echo \"❌ Missing wildcard source pattern (*.xlf)\"\n config_valid=false\nfi\n\nif ! grep -q \"%original_path%.*%two_letters_code%.*%original_file_name%\" crowdin.yml; then\n echo \"❌ Translation pattern not TYPO3-compliant\"\n config_valid=false\nfi\n\nif ! grep -q \"ignore:\" crowdin.yml; then\n echo \"❌ Missing ignore directive\"\n config_valid=false\nfi\n\nif grep -qE \"project_id_env|api_token_env|languages_mapping\" crowdin.yml; then\n echo \"⚠️ Non-standard fields detected\"\n config_valid=false\nfi\n\nif [[ \"$config_valid\" == true ]]; then\n score=2\n echo \"✅ Configuration fully TYPO3-compliant (+2 points)\"\nelse\n score=1\n echo \"⚠️ Configuration present but not fully compliant (+1 point)\"\nfi\n\n# Check GitHub workflow (optional)\nworkflow_file=\"\"\nfor f in .github/workflows/crowdin.yml .github/workflows/crowdin.yaml .github/workflows/crowdin-sync.yml; do\n [[ -f \"$f\" ]] && workflow_file=\"$f\" && break\ndone\n\nif [[ -n \"$workflow_file\" ]]; then\n echo\n echo \"=== GitHub Workflow Validation ===\"\n\n if grep -q \"download.*translation\\|schedule:\" \"$workflow_file\"; then\n echo \"⚠️ Workflow has download job or cron schedule (not TYPO3 standard)\"\n score=1 # Downgrade to +1 point\n else\n echo \"✅ Workflow follows TYPO3 standards (upload only)\"\n fi\nfi\n\necho\necho \"=== Final Score: $score/$max_score points ===\"\n```\n\n## Common Anti-Patterns\n\n### Anti-Pattern 1: Standalone Crowdin Project Mindset\n❌ Creating independent Crowdin project\n❌ Using personal Crowdin account\n❌ Configuring download workflows in GitHub Actions\n✅ Contacting TYPO3 localization team for setup\n✅ Using TYPO3's centralized Crowdin organization\n✅ Letting Crowdin's native integration handle downloads\n\n### Anti-Pattern 2: Over-Engineering Configuration\n❌ 90-line `crowdin.yml` with every possible option\n❌ Explicit language mapping for all supported languages\n❌ Complex PR templates and commit messages in config\n✅ Simple 6-line `crowdin.yml` following TYPO3 standard\n✅ Letting Crowdin handle language detection automatically\n✅ Using Crowdin's default PR behavior\n\n### Anti-Pattern 3: Dual-Job Workflows\n❌ Upload job + Download job + Cron schedule\n❌ Manual PR creation in GitHub Actions\n❌ Complex conditional logic for different event types\n✅ Single upload job triggered on main branch push\n✅ Crowdin's native integration creates PRs automatically\n✅ Simple, maintainable workflow\n\n## References\n\n- **Official TYPO3 Documentation**: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Localization/TranslationServer/Crowdin/ExtensionIntegration.html\n- **Crowdin GitHub Action**: https://github.com/marketplace/actions/crowdin-action\n- **TYPO3 Slack**: `#typo3-localization-team` channel\n- **Example Extension**: Check `EXT:news` or other popular extensions for reference implementations\n\n## Translation Quality Validation\n\n### Detecting Untranslated Strings\n\n**Problem**: Translation files may contain strings where `\u003csource>` equals `\u003ctarget>`, indicating untranslated content.\n\n**Detection Script**:\n```python\nimport re\nfrom pathlib import Path\n\ndef find_untranslated(lang_files):\n \"\"\"Find strings where source == target (untranslated)\"\"\"\n for filepath in lang_files:\n with open(filepath, 'r', encoding='utf-8') as f:\n content = f.read()\n \n # Extract source/target pairs\n units = re.findall(\n r'\u003ctrans-unit id=\"([^\"]+)\"[^>]*>.*?\u003csource>([^\u003c]+)\u003c/source>\\s*\u003ctarget>([^\u003c]+)\u003c/target>.*?\u003c/trans-unit>',\n content,\n re.DOTALL\n )\n \n untranslated = []\n for unit_id, source, target in units:\n if source.strip() == target.strip():\n untranslated.append((unit_id, source.strip()))\n \n if untranslated:\n print(f\"{filepath.name}: {len(untranslated)} untranslated\")\n for unit_id, text in untranslated[:3]:\n print(f\" - {text}\")\n```\n\n**Validation Points**:\n- Brand names (e.g., \"Retina\", \"Ultra\") may legitimately remain unchanged\n- Technical terms may be international (e.g., \"Standard\")\n- All other matches should be reviewed for proper translation\n\n## Minimizing Crowdin PR Noise\n\n### Problem: Excessive Formatting Changes\n\nCrowdin PRs often contain many cosmetic changes that make review difficult:\n- Whitespace differences\n- Attribute reordering\n- Added `state` attributes\n- Line ending inconsistencies\n\n### Solution: Preemptive Configuration\n\n#### 1. .editorconfig\n\nAdd XLIFF/XML formatting rules to match Crowdin's export format:\n\n```ini\n[*.{xlf,xml}]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\nend_of_line = lf\n```\n\n**Purpose**: Ensures consistent formatting between local files and Crowdin exports (Crowdin uses 2-space indentation)\n\n#### 2. .gitattributes\n\nAdd line ending handling:\n\n```\n*.xlf text eol=lf linguist-language=XML\n*.xml text eol=lf\n```\n\n**Purpose**: Enforces LF line endings for XLIFF files, prevents CRLF/LF mixing\n\n#### 3. crowdin.yml Export Options\n\nAdd export configuration:\n\n```yaml\npreserve_hierarchy: 1\nfiles:\n - source: /Resources/Private/Language/*.xlf\n translation: /%original_path%/%two_letters_code%.%original_file_name%\n ignore:\n - /**/%two_letters_code%.%original_file_name%\n # Minimize formatting changes\n export_options:\n export_quotes: none\n export_pattern: default\n export_approved_only: false\n```\n\n**Purpose**: Controls Crowdin's export formatting behavior\n\n#### 4. Preemptive state=\"translated\" Attributes\n\nAdd `state=\"translated\"` to all target elements **before** Crowdin does:\n\n```bash\n# Add to all translated targets\nfind Resources/Private/Language -name \"*.locallang_be.xlf\" \\\n -exec sed -i 's/\u003ctarget>/\u003ctarget state=\"translated\">/g' {} \\;\n```\n\nOr via Python:\n\n```python\nimport re\nfrom pathlib import Path\n\nfor filepath in Path('Resources/Private/Language').glob('*.locallang_be.xlf'):\n with open(filepath, 'r', encoding='utf-8') as f:\n content = f.read()\n \n # Add state=\"translated\" to targets without it\n new_content = re.sub(\n r'\u003ctarget(?![^>]*state=)([^>]*)>',\n r'\u003ctarget state=\"translated\"\\1>',\n content\n )\n \n if new_content != content:\n with open(filepath, 'w', encoding='utf-8') as f:\n f.write(new_content)\n print(f\"{filepath.name}: Added state attributes\")\n```\n\n**Purpose**: Prevents Crowdin from adding `state=\"translated\"` in its PRs\n\n### Unavoidable Changes\n\nSome formatting differences cannot be prevented:\n\n- **XML attribute reordering**: Crowdin's XML parser may reorder attributes\n - Example: `target-language=\"de\"` position may change\n - This is cosmetic and doesn't affect functionality\n\n- **Comment preservation**: Crowdin may remove or reformat XML comments\n\n**Recommendation**: Accept these cosmetic changes and focus PR reviews on actual translation content.\n\n### Impact Assessment\n\n**Before optimization**:\n```diff\n- \u003cfile source-language=\"en\" target-language=\"de\" datatype=\"plaintext\">\n+ \u003cfile source-language=\"en\" datatype=\"plaintext\" target-language=\"de\">\n \n- \u003ctarget>Translated text\u003c/target>\n+ \u003ctarget state=\"translated\">Translated text\u003c/target>\n\n(Plus many whitespace and line ending changes)\n```\n\n**After optimization**:\n```diff\n(Minimal diff showing only actual translation changes or unavoidable attribute reordering)\n```\n\n## XLIFF Version Standards\n\n### XLIFF 1.2 vs 1.0\n\n**TYPO3 Recommendation**: Use XLIFF 1.2 for all translation files\n\n**Benefits of XLIFF 1.2**:\n- Better tooling support across translation platforms\n- Enhanced validation capabilities with proper namespace\n- Recommended by OASIS XLIFF Technical Committee\n- Improved interoperability with CAT (Computer-Assisted Translation) tools\n- Required for modern Crowdin features and validation\n\n### Version Detection\n\nCheck current XLIFF version in translation files:\n\n```bash\ngrep -h \"xliff version\" Resources/Private/Language/*.xlf | sort -u\n```\n\n**XLIFF 1.0 format** (deprecated):\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n\u003cxliff version=\"1.0\">\n \u003cfile source-language=\"en\" datatype=\"plaintext\" product-name=\"rte_ckeditor_image\">\n```\n\n**XLIFF 1.2 format** (recommended):\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n\u003cxliff version=\"1.2\" xmlns=\"urn:oasis:names:tc:xliff:document:1.2\">\n \u003cfile source-language=\"en\" datatype=\"plaintext\" product-name=\"rte_ckeditor_image\">\n```\n\n### Key Differences\n\n1. **Version attribute**: `version=\"1.0\"` → `version=\"1.2\"`\n2. **Namespace declaration**: Must add `xmlns=\"urn:oasis:names:tc:xliff:document:1.2\"`\n3. **Validation**: XLIFF 1.2 has stricter schema validation\n4. **Tooling**: Modern CAT tools prefer XLIFF 1.2\n\n### Upgrade Script\n\nAutomated XLIFF 1.0 → 1.2 conversion:\n\n```python\n#!/usr/bin/env python3\nimport re\nfrom pathlib import Path\n\ndef upgrade_xliff_to_1_2(filepath):\n \"\"\"Upgrade XLIFF file from version 1.0 to 1.2\"\"\"\n with open(filepath, 'r', encoding='utf-8') as f:\n content = f.read()\n\n # Check if already 1.2\n if 'version=\"1.2\"' in content:\n return False\n\n # Upgrade version and add namespace\n new_content = content.replace(\n '\u003cxliff version=\"1.0\">',\n '\u003cxliff version=\"1.2\" xmlns=\"urn:oasis:names:tc:xliff:document:1.2\">'\n )\n\n if new_content != content:\n with open(filepath, 'w', encoding='utf-8') as f:\n f.write(new_content)\n return True\n\n return False\n\n# Upgrade all XLIFF files\nupgraded_count = 0\nfor filepath in Path('Resources/Private/Language').glob('*.xlf'):\n if upgrade_xliff_to_1_2(filepath):\n print(f\"✅ Upgraded: {filepath.name}\")\n upgraded_count += 1\n else:\n print(f\"⏭️ Already XLIFF 1.2: {filepath.name}\")\n\nprint(f\"\\nUpgraded {upgraded_count} files to XLIFF 1.2\")\n```\n\n**Bash alternative** (simple sed replacement):\n\n```bash\n#!/bin/bash\n# Upgrade all XLIFF files from 1.0 to 1.2\n\nfor file in Resources/Private/Language/*.xlf; do\n if grep -q 'version=\"1.0\"' \"$file\"; then\n sed -i 's|\u003cxliff version=\"1.0\">|\u003cxliff version=\"1.2\" xmlns=\"urn:oasis:names:tc:xliff:document:1.2\">|g' \"$file\"\n echo \"✅ Upgraded: $file\"\n else\n echo \"⏭️ Already XLIFF 1.2: $file\"\n fi\ndone\n```\n\n### Validation\n\nVerify XLIFF 1.2 compliance after upgrade:\n\n```bash\n# Check all files have version 1.2\nif grep -q 'version=\"1.0\"' Resources/Private/Language/*.xlf 2>/dev/null; then\n echo \"❌ Some files still use XLIFF 1.0\"\n grep -l 'version=\"1.0\"' Resources/Private/Language/*.xlf\n exit 1\nelse\n echo \"✅ All files use XLIFF 1.2\"\nfi\n\n# Check all files have namespace\nif grep -L 'xmlns=\"urn:oasis:names:tc:xliff:document:1.2\"' Resources/Private/Language/*.xlf 2>/dev/null | grep -q .; then\n echo \"❌ Some files missing XLIFF 1.2 namespace\"\n grep -L 'xmlns=\"urn:oasis:names:tc:xliff:document:1.2\"' Resources/Private/Language/*.xlf\n exit 1\nelse\n echo \"✅ All files have proper XLIFF 1.2 namespace\"\nfi\n```\n\n### Testing After Upgrade\n\n1. **TYPO3 Backend**: Verify translations still load correctly\n2. **Crowdin Upload**: Test that Crowdin accepts XLIFF 1.2 files\n3. **Translation Export**: Confirm Crowdin exports maintain XLIFF 1.2 format\n4. **XML Validation**: Use xmllint or online validators to check schema compliance\n\n```bash\n# Validate XML syntax\nfor file in Resources/Private/Language/*.xlf; do\n xmllint --noout \"$file\" 2>&1 && echo \"✅ $file\" || echo \"❌ $file\"\ndone\n```\n\n### Best Practices\n\n1. **All files same version**: Upgrade all XLIFF files together, don't mix versions\n2. **Test before commit**: Verify in TYPO3 backend before committing\n3. **Document in PR**: Mention XLIFF version upgrade in PR description\n4. **Crowdin compatibility**: Confirm Crowdin continues to work after upgrade\n\n## Translator Notes for Multilingual Terms\n\n### Problem: Brand Names and International Terms\n\nSome translation strings contain terms that may not need translation in all languages:\n- **Brand names**: Retina, Ultra, Standard\n- **International terms**: DPI, PDF, SVG\n- **Technical terms**: widely understood across languages\n\n**Challenge**: Different languages handle these differently:\n- Some keep as-is (Latin script languages)\n- Some transliterate (Arabic, Chinese, Japanese, Thai, Hindi)\n- Some translate (context-dependent)\n\n### ❌ Wrong Approach: `translate=\"no\"`\n\n```xml\n\u003c!-- DON'T DO THIS -->\n\u003ctrans-unit id=\"labels.ckeditor.quality.retina\" translate=\"no\">\n \u003csource>Retina (2.0x)\u003c/source>\n \u003ctarget>Retina (2.0x)\u003c/target>\n\u003c/trans-unit>\n```\n\n**Why this fails**:\n- Hides string from translators entirely in Crowdin\n- Prevents legitimate localization choices\n- Example: Arabic transliterates \"Retina\" → \"ريتينا\" (correct!)\n- Blocks cultural adaptation\n\n### ✅ Correct Approach: `\u003cnote>` Elements\n\n```xml\n\u003c!-- CORRECT - Provides guidance, allows translator judgment -->\n\u003ctrans-unit id=\"labels.ckeditor.quality.retina\" xml:space=\"preserve\">\n \u003csource>Retina (2.0x)\u003c/source>\n \u003cnote>Multilingual term - keep as-is or transliterate if more natural in your language\u003c/note>\n\u003c/trans-unit>\n\n\u003ctrans-unit id=\"labels.ckeditor.quality.standard\" xml:space=\"preserve\">\n \u003csource>Standard (1.0x)\u003c/source>\n \u003cnote>Multilingual term - keep as-is unless your language has a more natural equivalent\u003c/note>\n\u003c/trans-unit>\n```\n\n**Benefits**:\n- Translators see the note in Crowdin UI\n- Provides guidance without restricting choices\n- Allows appropriate localization decisions\n- Respects translator expertise\n\n### Identifying Multilingual Terms\n\n**Detection script**:\n\n```python\n#!/usr/bin/env python3\nimport re\nfrom pathlib import Path\nfrom collections import defaultdict\n\nlang_dir = Path('Resources/Private/Language')\nmatches_by_id = defaultdict(list)\n\nfor filepath in sorted(lang_dir.glob('??.locallang_be.xlf')):\n content = filepath.read_text(encoding='utf-8')\n\n units = re.findall(\n r'\u003ctrans-unit id=\"([^\"]+)\"[^>]*>.*?\u003csource>([^\u003c]+)\u003c/source>\\s*\u003ctarget[^>]*>([^\u003c]+)\u003c/target>.*?\u003c/trans-unit>',\n content,\n re.DOTALL\n )\n\n for unit_id, source, target in units:\n if source.strip() == target.strip():\n matches_by_id[unit_id].append((filepath.name, source.strip()))\n\n# Show strings matching in 3+ languages (likely multilingual)\nprint(\"Multilingual candidates (source == target in 3+ languages):\\n\")\nfor unit_id, matches in sorted(matches_by_id.items()):\n if len(matches) >= 3:\n print(f\"{unit_id}: {matches[0][1]}\")\n print(f\" Languages: {len(matches)} keep as-is\")\n```\n\n**Criteria for adding notes**:\n- Term unchanged in 3+ languages → Likely multilingual\n- Brand names (Retina, Ultra) → Add note\n- Technical acronyms (DPI, SVG) → Add note\n- Common international words (Standard) → Add note\n\n### Note Text Guidelines\n\n**For brand names and transliterable terms**:\n```xml\n\u003cnote>Multilingual term - keep as-is or transliterate if more natural in your language\u003c/note>\n```\n\n**For international terms with possible equivalents**:\n```xml\n\u003cnote>Multilingual term - keep as-is unless your language has a more natural equivalent\u003c/note>\n```\n\n**For technical acronyms**:\n```xml\n\u003cnote>Technical acronym - commonly understood internationally\u003c/note>\n```\n\n### Adding Notes to Source File\n\n**Important**: Only add `\u003cnote>` elements to **source file** (`locallang_be.xlf`), not translation files.\n\n```bash\n# Add notes manually in source file\nvim Resources/Private/Language/locallang_be.xlf\n```\n\nOr via script:\n\n```python\nimport re\nfrom pathlib import Path\n\nsource_file = Path('Resources/Private/Language/locallang_be.xlf')\ncontent = source_file.read_text(encoding='utf-8')\n\n# Add note after \u003c/source> for specific trans-units\nnote = '\\t\\t\\t\\t\u003cnote>Multilingual term - keep as-is or transliterate if more natural in your language\u003c/note>'\n\n# Example: Add to Retina trans-unit\ncontent = re.sub(\n r'(\u003ctrans-unit id=\"labels\\.ckeditor\\.quality\\.retina\"[^>]*>.*?\u003csource>Retina \\(2\\.0x\\)\u003c/source>)',\n r'\\1\\n' + note,\n content,\n flags=re.DOTALL\n)\n\nsource_file.write_text(content, encoding='utf-8')\n```\n\n**Crowdin behavior**:\n- Notes appear in translator UI\n- Visible during translation process\n- Helps translators make informed decisions\n\n## XLIFF Validation in CI\n\n### Why Validate XLIFF Files\n\n**Quality gates**:\n- Catch XML syntax errors early\n- Enforce XLIFF version consistency\n- Verify proper namespace declarations\n- Detect encoding issues\n- Warn about potential untranslated strings\n\n**Benefits**:\n- Prevents broken translations from being merged\n- Ensures Crowdin compatibility\n- Maintains translation quality standards\n- Catches errors before they reach production\n\n### Validation Script\n\nCreate `Build/Scripts/validate-xliff.sh`:\n\n```bash\n#!/bin/bash\nset -e\n\necho \"=== XLIFF Translation File Validation ===\"\necho\n\nLANG_DIR=\"Resources/Private/Language\"\nERRORS=0\n\n# Check if xmllint is available\nif ! command -v xmllint &> /dev/null; then\n echo \"⚠️ xmllint not found, skipping XML syntax validation\"\n XMLLINT_AVAILABLE=false\nelse\n XMLLINT_AVAILABLE=true\nfi\n\n# 1. Validate XML syntax\nif [ \"$XMLLINT_AVAILABLE\" = true ]; then\n echo \"=== 1. XML Syntax Validation ===\"\n for file in \"$LANG_DIR\"/*.xlf; do\n if xmllint --noout \"$file\" 2>&1; then\n echo \"✅ $file - Valid XML\"\n else\n echo \"❌ $file - Invalid XML syntax\"\n ERRORS=$((ERRORS + 1))\n fi\n done\n echo\nfi\n\n# 2. Check XLIFF version consistency\necho \"=== 2. XLIFF Version Consistency ===\"\nVERSION_CHECK=$(grep -h 'xliff version=' \"$LANG_DIR\"/*.xlf | sort -u)\nVERSION_COUNT=$(echo \"$VERSION_CHECK\" | wc -l)\n\nif [ \"$VERSION_COUNT\" -eq 1 ]; then\n if echo \"$VERSION_CHECK\" | grep -q 'version=\"1.2\"'; then\n echo \"✅ All files use XLIFF 1.2\"\n else\n echo \"❌ Files not using XLIFF 1.2:\"\n echo \"$VERSION_CHECK\"\n ERRORS=$((ERRORS + 1))\n fi\nelse\n echo \"❌ Mixed XLIFF versions detected:\"\n echo \"$VERSION_CHECK\"\n ERRORS=$((ERRORS + 1))\nfi\necho\n\n# 3. Check namespace declarations\necho \"=== 3. XLIFF 1.2 Namespace Validation ===\"\nMISSING_NS=$(grep -L 'xmlns=\"urn:oasis:names:tc:xliff:document:1.2\"' \"$LANG_DIR\"/*.xlf || true)\nif [ -z \"$MISSING_NS\" ]; then\n echo \"✅ All files have proper XLIFF 1.2 namespace\"\nelse\n echo \"❌ Files missing XLIFF 1.2 namespace:\"\n echo \"$MISSING_NS\"\n ERRORS=$((ERRORS + 1))\nfi\necho\n\n# 4. Check for UTF-8 encoding (ASCII is valid UTF-8)\necho \"=== 4. UTF-8 Encoding Validation ===\"\nNON_UTF8=$(file \"$LANG_DIR\"/*.xlf | grep -vE \"(UTF-8|ASCII)\" || true)\nif [ -z \"$NON_UTF8\" ]; then\n echo \"✅ All files are UTF-8 compatible\"\nelse\n echo \"❌ Files not UTF-8 compatible:\"\n echo \"$NON_UTF8\"\n ERRORS=$((ERRORS + 1))\nfi\necho\n\n# 5. Warn about potential untranslated strings (source == target)\necho \"=== 5. Translation Completeness Check ===\"\necho \"(Warning only - some matches are legitimate multilingual terms)\"\necho\n\npython3 \u003c\u003c 'PYEOF'\nimport re\nfrom pathlib import Path\n\nlang_dir = Path('Resources/Private/Language')\n\n# Known multilingual terms that are OK to match\nMULTILINGUAL_TERMS = {\n 'Retina', 'Retina (2.0x)',\n 'Ultra', 'Ultra (3.0x)',\n 'Standard', 'Standard (1.0x)',\n 'DPI', 'SVG', 'PDF'\n}\n\nwarnings = 0\nfor filepath in sorted(lang_dir.glob('??.locallang_be.xlf')):\n content = filepath.read_text(encoding='utf-8')\n\n units = re.findall(\n r'\u003ctrans-unit id=\"([^\"]+)\"[^>]*>.*?\u003csource>([^\u003c]+)\u003c/source>\\s*\u003ctarget[^>]*>([^\u003c]+)\u003c/target>.*?\u003c/trans-unit>',\n content,\n re.DOTALL\n )\n\n untranslated = []\n for unit_id, source, target in units:\n if source.strip() == target.strip() and source.strip() not in MULTILINGUAL_TERMS:\n untranslated.append((unit_id, source.strip()))\n\n if untranslated:\n warnings += 1\n print(f\"⚠️ {filepath.name}: {len(untranslated)} potential untranslated strings\")\n for unit_id, text in untranslated[:3]:\n print(f\" - {unit_id}: {text}\")\n if len(untranslated) > 3:\n print(f\" ... and {len(untranslated)-3} more\")\n\nif warnings == 0:\n print(\"✅ No unexpected untranslated strings found\")\nelse:\n print(f\"\\n⚠️ Found {warnings} files with potential untranslated strings\")\n print(\"(This is a warning only - review manually)\")\nPYEOF\n\necho\necho \"=== Validation Summary ===\"\nif [ $ERRORS -eq 0 ]; then\n echo \"✅ All validation checks passed!\"\n exit 0\nelse\n echo \"❌ Found $ERRORS validation error(s)\"\n exit 1\nfi\n```\n\n**Make executable**:\n```bash\nchmod +x Build/Scripts/validate-xliff.sh\n```\n\n### CI Integration\n\nAdd to `.github/workflows/ci.yml`:\n\n```yaml\njobs:\n build:\n runs-on: ubuntu-latest\n\n steps:\n - name: Checkout\n uses: actions/checkout@v5\n\n # Add XLIFF validation BEFORE composer install\n - name: Validate XLIFF Translation Files\n run: |\n bash Build/Scripts/validate-xliff.sh\n\n # ... rest of CI jobs (composer install, lint, tests, etc.)\n```\n\n**Why before composer install**:\n- No PHP dependencies required\n- Fails fast if translation files broken\n- Saves CI time by catching errors early\n- xmllint available in ubuntu-latest\n\n### Validation Checklist\n\n**✅ XML Syntax**: All files are well-formed XML\n- Uses xmllint for validation\n- Catches malformed tags, encoding issues\n\n**✅ Version Consistency**: All files use same XLIFF version\n- Enforces XLIFF 1.2 standard\n- Detects mixed versions (1.0 and 1.2)\n\n**✅ Namespace Declarations**: Proper XLIFF 1.2 namespace\n- Checks for `xmlns=\"urn:oasis:names:tc:xliff:document:1.2\"`\n- Required for XLIFF 1.2 compliance\n\n**✅ Encoding**: UTF-8 compatible\n- Accepts UTF-8 and ASCII (ASCII is UTF-8 subset)\n- Detects non-UTF-8 encodings\n\n**⚠️ Translation Completeness**: Source != target check\n- Warning only, doesn't fail CI\n- Excludes known multilingual terms\n- Helps catch accidental untranslated strings\n\n### Customizing Multilingual Term List\n\nUpdate the `MULTILINGUAL_TERMS` set in validation script:\n\n```python\n# Add your extension's multilingual terms\nMULTILINGUAL_TERMS = {\n 'Retina', 'Retina (2.0x)',\n 'Ultra', 'Ultra (3.0x)',\n 'Standard', 'Standard (1.0x)',\n 'DPI', 'SVG', 'PDF',\n # Add extension-specific terms\n 'WebP', 'AVIF', 'HEIF',\n 'API', 'REST', 'JSON'\n}\n```\n\n### Testing Locally\n\n```bash\n# Run validation locally before pushing\nbash Build/Scripts/validate-xliff.sh\n\n# Expected output on clean repository\n# ✅ All validation checks passed!\n```\n\n## Validation Scoring Impact\n\n### Translation Quality (+2 points)\n\n- **Basic state attributes** (+1): All translated targets have `state=\"translated\"`\n- **Complete translations** (+1): No untranslated strings (source != target except for brand names)\n\n### Configuration Quality (+1 point)\n\n- **Formatting rules** (+0.5): .editorconfig and .gitattributes present\n- **Export options** (+0.5): crowdin.yml includes export_options configuration\n\n### XLIFF Version (+1 point)\n\n- **XLIFF 1.2 compliance** (+1): All translation files use XLIFF 1.2 with proper namespace declaration\n\n### Translator Guidance (+1 point)\n\n- **Multilingual term notes** (+1): Source file contains `\u003cnote>` elements for brand names and international terms\n\n### CI Validation (+1 point)\n\n- **XLIFF validation in CI** (+1): Automated validation checks in GitHub Actions/CI pipeline\n\n**Total possible improvement**: +6 points to conformance score\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":39574,"content_sha256":"92625735ff042ad0e99e0ec40732a430b2777653a53f15f381f5f725a54633ed"},{"filename":"references/development-environment.md","content":"# Development Environment Standards\n\n**Purpose:** Validate development environment setup for consistent, reproducible TYPO3 extension development\n\n## Why Development Environment Matters\n\nA properly configured development environment ensures:\n\n- ✅ **Consistency** - All developers work with identical PHP/TYPO3/database versions\n- ✅ **Onboarding** - New contributors can start immediately without complex setup\n- ✅ **CI/CD Parity** - Local environment matches production/staging\n- ✅ **Reproducibility** - Bugs are reproducible across all environments\n- ✅ **Cross-Platform** - Works on macOS, Linux, Windows (WSL)\n\nWithout standardized dev environment:\n- ❌ \"Works on my machine\" syndrome\n- ❌ Inconsistent PHP/database versions causing bugs\n- ❌ Complex setup discourages contributions\n- ❌ CI failures that don't reproduce locally\n\n## TYPO3 Community Standards\n\n### DDEV - Primary Recommendation\n\n**DDEV** is the **de facto standard** for TYPO3 development:\n\n- ✅ Official TYPO3 core development uses DDEV\n- ✅ TYPO3 Best Practices (Tea extension) uses DDEV\n- ✅ TYPO3 documentation recommends DDEV\n- ✅ Cross-platform support (Docker-based)\n- ✅ Preconfigured for TYPO3 (`ddev config --project-type=typo3`)\n\n**Alternative:** Docker Compose (acceptable, more manual configuration)\n\n## Validation Checklist\n\n### 1. DDEV Configuration\n\n**Check for `.ddev/` directory:**\n\n```bash\nls -la .ddev/\n```\n\n**Required files:**\n- `.ddev/config.yaml` - Core DDEV configuration\n- `.ddev/.gitignore` - Excludes dynamic files (import-db, .ddev-docker-compose-*.yaml)\n\n**Optional but recommended:**\n- `.ddev/config.typo3.yaml` - TYPO3-specific settings\n- `.ddev/commands/` - Custom DDEV commands\n- `.ddev/docker-compose.*.yaml` - Additional services\n\n**Severity if missing:** 🟡 **Medium** - Indicates no standardized dev environment\n\n### 2. DDEV config.yaml Structure\n\n**Minimum DDEV Configuration:**\n\n```yaml\nname: extension-name\ntype: typo3\ndocroot: .Build/public\nphp_version: \"8.2\" # Match composer.json minimum\nwebserver_type: nginx-fpm\nrouter_http_port: \"80\"\nrouter_https_port: \"443\"\nxdebug_enabled: false\nadditional_hostnames: []\nadditional_fqdns: []\ndatabase:\n type: mariadb\n version: \"10.11\"\nomit_containers: [ddev-ssh-agent]\n```\n\n**Validation Rules:**\n\n| Field | Validation | Example | Severity |\n|-------|-----------|---------|----------|\n| `name` | Should match extension key or composer name | `rte-ckeditor-image` | Low |\n| `type` | Must be `typo3` | `typo3` | High |\n| `docroot` | Should match composer.json web-dir | `.Build/public` | High |\n| `php_version` | Should match composer.json minimum PHP | `\"8.2\"` | High |\n| `database.type` | Should be `mariadb` (TYPO3 standard) | `mariadb` | Medium |\n| `database.version` | Should be LTS version (10.11 or 11.x) | `\"10.11\"` | Medium |\n\n**Example Check:**\n\n```bash\n# Extension composer.json\n\"require\": {\n \"php\": \"^8.2 || ^8.3 || ^8.4\",\n \"typo3/cms-core\": \"^13.4\"\n}\n\"extra\": {\n \"typo3/cms\": {\n \"web-dir\": \".Build/public\"\n }\n}\n\n# DDEV config.yaml SHOULD have:\nphp_version: \"8.2\" # ✅ Matches minimum\ndocroot: .Build/public # ✅ Matches web-dir\ntype: typo3 # ✅ Correct type\n\n# DDEV config.yaml SHOULD NOT have:\nphp_version: \"7.4\" # ❌ Below minimum\ndocroot: public # ❌ Doesn't match web-dir\ntype: php # ❌ Wrong type\n```\n\n### 3. Docker Compose (Alternative)\n\nIf DDEV not present, check for `docker-compose.yml`:\n\n**Minimum Docker Compose Configuration:**\n\n```yaml\nversion: '3.8'\n\nservices:\n web:\n image: ghcr.io/typo3/core-testing-php82:latest\n volumes:\n - .:/var/www/html\n working_dir: /var/www/html\n ports:\n - \"8000:80\"\n environment:\n TYPO3_CONTEXT: Development\n\n db:\n image: mariadb:10.11\n environment:\n MYSQL_ROOT_PASSWORD: root\n MYSQL_DATABASE: typo3\n volumes:\n - db_data:/var/lib/mysql\n\nvolumes:\n db_data:\n```\n\n**Validation Rules:**\n\n| Service | Validation | Severity |\n|---------|-----------|----------|\n| `web` service exists | Required | High |\n| PHP version matches composer.json | Required | High |\n| `db` service exists | Required | Medium |\n| Database type is MariaDB/MySQL | Recommended | Low |\n| Volumes preserve database data | Required | High |\n\n**Severity if missing:** 🟡 **Medium** - Harder to onboard, but not critical\n\n### 4. DevContainer (VS Code Remote Containers)\n\nCheck for `.devcontainer/devcontainer.json`:\n\n**Example DevContainer Configuration:**\n\n```json\n{\n \"name\": \"TYPO3 Extension Development\",\n \"dockerComposeFile\": [\"../docker-compose.yml\"],\n \"service\": \"web\",\n \"workspaceFolder\": \"/var/www/html\",\n \"customizations\": {\n \"vscode\": {\n \"extensions\": [\n \"bmewburn.vscode-intelephense-client\",\n \"xdebug.php-debug\",\n \"EditorConfig.EditorConfig\"\n ],\n \"settings\": {\n \"php.validate.executablePath\": \"/usr/local/bin/php\"\n }\n }\n },\n \"features\": {\n \"ghcr.io/devcontainers/features/common-utils:2\": {},\n \"ghcr.io/devcontainers/features/node:1\": {}\n }\n}\n```\n\n**Validation:**\n- File exists: ✅ Good (VS Code support)\n- References docker-compose.yml or DDEV: ✅ Integrated approach\n- Empty directory: ⚠️ Incomplete setup\n\n**Severity if missing:** 🟢 **Low** - Nice to have, not required\n\n## DDEV-Specific Best Practices\n\n### TYPO3-Optimized Settings\n\n**`.ddev/config.typo3.yaml`:**\n\n```yaml\n# TYPO3-specific DDEV configuration\noverride_config: false\nweb_extra_daemons:\n - name: \"typo3-backend-lock-handler\"\n command: \"/var/www/html/.Build/bin/typo3 scheduler:run\"\n directory: /var/www/html\n\nhooks:\n post-start:\n - exec: composer install\n - exec: .Build/bin/typo3 cache:flush\n\n# Additional PHP settings for TYPO3\nphp_ini:\n memory_limit: 512M\n max_execution_time: 240\n upload_max_filesize: 32M\n post_max_size: 32M\n```\n\n### Custom DDEV Commands\n\n**`.ddev/commands/web/typo3`:**\n\n```bash\n#!/bin/bash\n## Description: Run TYPO3 CLI commands\n## Usage: typo3 [args]\n## Example: \"ddev typo3 cache:flush\"\n\n.Build/bin/typo3 \"$@\"\n```\n\n**`.ddev/commands/web/test-unit`:**\n\n```bash\n#!/bin/bash\n## Description: Run unit tests\n## Usage: test-unit [args]\n\n.Build/bin/phpunit -c Build/phpunit/UnitTests.xml \"$@\"\n```\n\n**`.ddev/commands/web/test-functional`:**\n\n```bash\n#!/bin/bash\n## Description: Run functional tests\n## Usage: test-functional [args]\n\n.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml \"$@\"\n```\n\n## Conformance Evaluation Workflow\n\n### Step 1: Detect Development Environment Type\n\n```bash\n# Check for DDEV\nif [ -f \".ddev/config.yaml\" ]; then\n DEV_ENV=\"ddev\"\n SCORE=20 # Full points for DDEV\n\n# Check for Docker Compose\nelif [ -f \"docker-compose.yml\" ]; then\n DEV_ENV=\"docker-compose\"\n SCORE=15 # Good, but manual\n\n# Check for DevContainer only\nelif [ -f \".devcontainer/devcontainer.json\" ]; then\n DEV_ENV=\"devcontainer\"\n SCORE=10 # VS Code specific\n\n# No dev environment\nelse\n DEV_ENV=\"none\"\n SCORE=0\nfi\n```\n\n### Step 2: Validate Configuration Against Extension\n\n**For DDEV:**\n\n```bash\n# Extract extension requirements\nMIN_PHP=$(jq -r '.require.php' composer.json | grep -oE '[0-9]+\\.[0-9]+' | head -1)\nWEB_DIR=$(jq -r '.extra.typo3.cms.\"web-dir\"' composer.json)\n\n# Validate DDEV config\nDDEV_PHP=$(grep 'php_version:' .ddev/config.yaml | awk '{print $2}' | tr -d '\"')\nDDEV_DOCROOT=$(grep 'docroot:' .ddev/config.yaml | awk '{print $2}')\nDDEV_TYPE=$(grep 'type:' .ddev/config.yaml | awk '{print $2}')\n\n# Compare\nif [ \"${DDEV_PHP}\" != \"${MIN_PHP}\" ]; then\n echo \"⚠️ PHP version mismatch: DDEV ${DDEV_PHP} vs required ${MIN_PHP}\"\nfi\n\nif [ \"${DDEV_DOCROOT}\" != \"${WEB_DIR}\" ]; then\n echo \"⚠️ Docroot mismatch: DDEV ${DDEV_DOCROOT} vs composer ${WEB_DIR}\"\nfi\n\nif [ \"${DDEV_TYPE}\" != \"typo3\" ]; then\n echo \"❌ DDEV type should be 'typo3', found '${DDEV_TYPE}'\"\nfi\n```\n\n### Step 3: Check for Recommended Enhancements\n\n```bash\n# DDEV commands\nif [ -d \".ddev/commands/web\" ]; then\n COMMANDS=$(ls .ddev/commands/web/ 2>/dev/null | wc -l)\n echo \"✅ DDEV has ${COMMANDS} custom commands\"\nelse\n echo \"ℹ️ No custom DDEV commands (consider adding typo3, test-unit, test-functional)\"\nfi\n\n# TYPO3-specific config\nif [ -f \".ddev/config.typo3.yaml\" ]; then\n echo \"✅ TYPO3-specific DDEV configuration present\"\nelse\n echo \"ℹ️ No TYPO3-specific config (optional)\"\nfi\n```\n\n## Conformance Report Integration\n\n### When Evaluating Development Environment:\n\n**In \"Best Practices\" Section:**\n\n```markdown\n### Development Environment\n\n**Configuration:**\n\n- ✅ DDEV configured (.ddev/config.yaml present)\n- ✅ PHP version matches composer.json minimum (8.2)\n- ✅ Docroot matches composer.json web-dir (.Build/public)\n- ✅ Type set to 'typo3' for TYPO3-optimized setup\n- ✅ MariaDB 10.11 (LTS) configured\n- ✅ Custom DDEV commands for testing (test-unit, test-functional)\n- ℹ️ Optional: TYPO3-specific config (.ddev/config.typo3.yaml) could enhance setup\n\n**Or with issues:**\n\n- ❌ No development environment configuration\n - Missing: .ddev/config.yaml, docker-compose.yml\n - Impact: Inconsistent development environments, difficult onboarding\n - Severity: Medium\n - Recommendation: Add DDEV configuration from Tea extension pattern\n - Reference: https://github.com/TYPO3BestPractices/tea/tree/main/.ddev\n\n- ⚠️ DDEV PHP version mismatch\n - File: .ddev/config.yaml\n - Current: php_version: \"7.4\"\n - Expected: php_version: \"8.2\" (from composer.json)\n - Severity: High\n - Fix: Update php_version to match minimum requirement\n\n- ⚠️ DDEV docroot mismatch\n - File: .ddev/config.yaml\n - Current: docroot: public\n - Expected: docroot: .Build/public (from composer.json extra.typo3.cms.web-dir)\n - Severity: High\n - Fix: Update docroot to match web-dir\n```\n\n## Scoring Impact\n\n**Best Practices Score Components (out of 20):**\n\n| Component | Max Points | DDEV | Docker Compose | None |\n|-----------|-----------|------|----------------|------|\n| **Dev Environment Exists** | 6 | 6 | 4 | 0 |\n| **Configuration Correct** | 4 | 4 | 3 | 0 |\n| **Version Matching** | 3 | 3 | 2 | 0 |\n| **Documentation** | 2 | 2 | 1 | 0 |\n| **Custom Commands/Enhancements** | 2 | 2 | 0 | 0 |\n| **Other Best Practices** | 3 | 3 | 3 | 3 |\n| **Total** | 20 | 20 | 13 | 3 |\n\n**Deductions:**\n\n| Issue | Severity | Score Impact |\n|-------|----------|--------------|\n| No dev environment at all | High | -6 points |\n| PHP version mismatch | High | -3 points |\n| Docroot mismatch | High | -3 points |\n| Wrong type (not 'typo3') | Medium | -2 points |\n| Missing custom commands | Low | -1 point |\n| No documentation | Low | -1 point |\n\n## Tea Extension Reference\n\n**Source:** https://github.com/TYPO3BestPractices/tea/tree/main/.ddev\n\n**Tea DDEV Structure:**\n\n```\n.ddev/\n├── .gitignore\n├── config.yaml # Main configuration\n├── config.typo3.yaml # TYPO3-specific settings\n└── commands/\n └── web/\n ├── typo3 # TYPO3 CLI wrapper\n ├── test-unit # Run unit tests\n └── test-functional # Run functional tests\n```\n\n**Tea config.yaml (simplified):**\n\n```yaml\nname: tea\ntype: typo3\ndocroot: .Build/public\nphp_version: \"8.2\"\nwebserver_type: nginx-fpm\ndatabase:\n type: mariadb\n version: \"10.11\"\nxdebug_enabled: false\n```\n\n**Usage Examples:**\n\n```bash\n# Start DDEV\nddev start\n\n# Install dependencies\nddev composer install\n\n# Run TYPO3 CLI\nddev typo3 cache:flush\n\n# Run unit tests\nddev test-unit\n\n# Run functional tests\nddev test-functional\n\n# Access database\nddev mysql\n\n# SSH into container\nddev ssh\n```\n\n## Quick Reference Checklist\n\n**When evaluating development environment:**\n\n```\n□ .ddev/config.yaml exists (preferred)\n□ OR docker-compose.yml exists (acceptable)\n□ OR .devcontainer/devcontainer.json exists (VS Code only)\n□ Configuration type is 'typo3' (DDEV) or uses TYPO3 image (Docker Compose)\n□ PHP version matches composer.json minimum\n□ Docroot matches composer.json web-dir\n□ Database is MariaDB 10.11+ or MySQL 8.0+\n□ Custom commands for common tasks (DDEV)\n□ Documentation exists (README.md mentions DDEV/Docker setup)\n□ .ddev/.gitignore present (excludes dynamic files)\n□ Post-start hooks run composer install (optional but nice)\n```\n\n## Common Issues\n\n### Issue: Empty .devcontainer/\n\n**Diagnosis:**\n```bash\nls -la .devcontainer/\n# total 8\n# drwxr-sr-x 2 user user 4096 Oct 20 20:05 .\n```\n\n**Severity:** 🟢 Low (incomplete setup, doesn't help or hurt)\n\n**Fix:** Either populate with devcontainer.json or remove directory\n\n### Issue: DDEV but no .gitignore\n\n**Diagnosis:**\n```bash\nls -la .ddev/.gitignore\n# No such file or directory\n```\n\n**Problem:** DDEV generates dynamic files that shouldn't be committed\n\n**Fix:** Create `.ddev/.gitignore`:\n```\n/*.yaml\n.ddev-docker-compose-*.yaml\n.homeadditions\n.sshimagename\ncommands/web/.ddev-docker-compose-*.yaml\nimport-db/\n```\n\n### Issue: Wrong DDEV project type\n\n**Diagnosis:**\n```yaml\n# .ddev/config.yaml\ntype: php # ❌ Wrong\n```\n\n**Problem:** Misses TYPO3-specific optimizations (URL structure, etc.)\n\n**Fix:** Change to `type: typo3`\n\n## Resources\n\n- **DDEV Documentation:** https://ddev.readthedocs.io/\n- **DDEV TYPO3 Quickstart:** https://ddev.readthedocs.io/en/stable/users/quickstart/#typo3\n- **Tea Extension DDEV Setup:** https://github.com/TYPO3BestPractices/tea/tree/main/.ddev\n- **TYPO3 Docker Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/LocalDevelopment/\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13474,"content_sha256":"6036bfb46a66e58fdb8f22f99461fa50a92582e1e788c960742dee1c1b60456d"},{"filename":"references/directory-structure.md","content":"# Directory Structure Standards\n\n**Purpose:** Validate proper separation of committed configuration vs generated/temporary files in TYPO3 extensions\n\n## Why Directory Structure Matters\n\nProper directory organization prevents common issues:\n\n- ✅ **Version Control Hygiene** - Only configuration committed, not generated files\n- ✅ **Build Reproducibility** - Clean installs work without artifacts\n- ✅ **CI/CD Clarity** - Clear separation of what's tracked vs generated\n- ✅ **Onboarding Efficiency** - New contributors understand structure instantly\n- ✅ **Maintenance Simplicity** - Updates don't conflict with local artifacts\n\nWithout proper structure:\n- ❌ Bloated repositories with vendor/, cache files committed\n- ❌ Git conflicts in generated files\n- ❌ CI failures from missing .gitignore patterns\n- ❌ Confusion about what files are source vs generated\n\n## TYPO3 Standard Directory Pattern\n\n### Build/ - Committed Configuration\n\n**Purpose:** Project-specific configuration files that define how to build, test, and validate code\n\n**Characteristics:**\n- ✅ Committed to git\n- ✅ Shared across all developers\n- ✅ Version controlled\n- ✅ Defines project standards\n\n**Standard Contents:**\n\n```\nBuild/\n├── phpstan.neon # Static analysis config\n├── phpstan-baseline.neon # Known issues baseline (optional)\n├── php-cs-fixer.php # Code style config\n├── rector.php # Refactoring rules (optional, can be in Build/rector/)\n├── phpunit/\n│ ├── UnitTests.xml # Unit test configuration\n│ ├── FunctionalTests.xml # Functional test configuration\n│ └── bootstrap.php # Test bootstrap (if needed)\n└── Scripts/\n └── runTests.sh # Test orchestration script\n```\n\n**Alternative Rector Location:**\n```\nBuild/\n└── rector/\n └── rector.php # Rector config in subdirectory\n```\n\n**Git Status:** All files tracked (not in .gitignore)\n\n### .Build/ - Generated/Temporary Files\n\n**Purpose:** Composer-generated files, caches, runtime artifacts, test outputs\n\n**Characteristics:**\n- ❌ NOT committed to git (gitignored)\n- ✅ Generated by composer/build tools\n- ✅ Recreatable from configuration\n- ✅ Developer-specific caches allowed\n\n**Standard Contents:**\n\n```\n.Build/\n├── bin/ # Composer bin-dir (phpunit, phpstan, etc.)\n├── public/ # Web root (TYPO3 web-dir)\n│ ├── index.php\n│ ├── typo3/\n│ └── typo3conf/\n├── vendor/ # Composer dependencies\n├── .php-cs-fixer.cache # php-cs-fixer cache file\n├── .phpunit.result.cache # PHPUnit cache\n└── var/ # Runtime cache/logs (optional)\n```\n\n**Git Status:** Entire directory in .gitignore\n\n**Standard .gitignore Entry:**\n\n```gitignore\n# Composer generated files\n.Build/\n```\n\n## Validation Checklist\n\n### 1. Build/ Directory Structure\n\n**Check for committed configuration files:**\n\n```bash\nls -la Build/\n```\n\n**Required files for comprehensive quality:**\n- `Build/phpstan.neon` - Static analysis configuration\n- `Build/php-cs-fixer.php` - Code style configuration\n- `Build/phpunit/UnitTests.xml` - Unit test configuration\n- `Build/Scripts/runTests.sh` - Test orchestration\n\n**Optional but recommended:**\n- `Build/phpunit/FunctionalTests.xml` - Functional test configuration\n- `Build/phpstan-baseline.neon` - Known issues baseline\n- `Build/rector.php` or `Build/rector/rector.php` - Refactoring rules\n\n**Severity if missing:** 🟡 **Medium** - Quality tools not standardized\n\n### 2. .Build/ Directory Gitignore\n\n**Check .gitignore for .Build/ exclusion:**\n\n```bash\ngrep -E '^\\\\.Build/' .gitignore\n```\n\n**Expected pattern:**\n\n```gitignore\n.Build/\n```\n\n**Alternative patterns (also valid):**\n\n```gitignore\n/.Build/\n.Build/*\n```\n\n**Check for accidental commits:**\n\n```bash\ngit ls-files .Build/\n# Should return empty - no files from .Build/ should be tracked\n```\n\n**Severity if misconfigured:** 🔴 **High** - Can lead to repository bloat\n\n### 3. Composer Configuration Alignment\n\n**Validate composer.json paths match directory structure:**\n\n```bash\n# Extract composer paths\ncat composer.json | jq -r '.config.\"bin-dir\"' # Should be .Build/bin\ncat composer.json | jq -r '.config.\"vendor-dir\"' # Should be .Build/vendor\ncat composer.json | jq -r '.extra.typo3.cms.\"web-dir\"' # Should be .Build/public\n```\n\n**Expected composer.json:**\n\n```json\n{\n \"config\": {\n \"vendor-dir\": \".Build/vendor\",\n \"bin-dir\": \".Build/bin\"\n },\n \"extra\": {\n \"typo3/cms\": {\n \"web-dir\": \".Build/public\"\n }\n }\n}\n```\n\n**Severity if mismatched:** 🔴 **High** - Build will fail or create wrong structure\n\n### 4. Quality Tool Configuration Paths\n\n**Validate tool configs reference correct cache locations:**\n\n```bash\n# Check PHPUnit cache location\ngrep -r \"\\.phpunit\\.result\\.cache\" Build/phpunit/*.xml\n# Should reference .Build/.phpunit.result.cache or no cache directive\n\n# Check php-cs-fixer cache\ngrep -r \"cache-file\" composer.json Build/php-cs-fixer.php\n# Should reference .Build/.php-cs-fixer.cache if specified\n\n# Check PHPStan cache (usually auto-managed)\ngrep -r \"tmpDir\" Build/phpstan.neon\n# Should reference .Build/phpstan if specified, or auto temp\n```\n\n**Example CORRECT patterns:**\n\n```json\n// composer.json\n{\n \"scripts\": {\n \"ci:cgl\": [\n \"php-cs-fixer fix --cache-file .Build/.php-cs-fixer.cache --dry-run --diff\"\n ]\n }\n}\n```\n\n```php\n// Build/php-cs-fixer.php\nreturn (new PhpCsFixer\\Config())\n ->setCacheFile('.Build/.php-cs-fixer.cache')\n ->setRules([...]);\n```\n\n```neon\n# Build/phpstan.neon\nparameters:\n tmpDir: .Build/phpstan\n```\n\n**Severity if wrong:** 🟡 **Medium** - Works but creates clutter in Build/\n\n### 5. Tea Extension Reference\n\n**Source:** https://github.com/TYPO3BestPractices/tea\n\n**Tea Directory Structure:**\n\n```\ntea/\n├── .gitignore # Excludes .Build/\n├── Build/\n│ ├── phpstan.neon\n│ ├── phpstan-baseline.neon\n│ ├── php-cs-fixer.php\n│ ├── phpunit/\n│ │ ├── FunctionalTests.xml\n│ │ └── UnitTests.xml\n│ └── Scripts/\n│ └── runTests.sh\n├── .Build/ # Gitignored, generated by composer\n│ ├── bin/\n│ ├── public/\n│ └── vendor/\n└── composer.json # Defines .Build/ paths\n```\n\n**Tea .gitignore (relevant excerpt):**\n\n```gitignore\n# Composer-generated files\n.Build/\ncomposer.lock\n```\n\n**Tea composer.json (relevant excerpt):**\n\n```json\n{\n \"config\": {\n \"vendor-dir\": \".Build/vendor\",\n \"bin-dir\": \".Build/bin\"\n },\n \"extra\": {\n \"typo3/cms\": {\n \"web-dir\": \".Build/public\"\n }\n }\n}\n```\n\n## Common Issues and Fixes\n\n### Issue 1: Cache Files in Build/ Directory\n\n**Diagnosis:**\n\n```bash\nls -la Build/.php-cs-fixer.cache Build/.phpunit.result.cache\n# If these files exist, they're in the WRONG location\n```\n\n**Problem:** Cache files committed or in wrong directory\n\n**Fix:**\n\n```bash\n# Remove from wrong location\nrm Build/.php-cs-fixer.cache Build/.phpunit.result.cache\n\n# Update configuration to use .Build/\n```\n\n**Update php-cs-fixer config:**\n\n```php\n// Build/php-cs-fixer.php\nreturn (new PhpCsFixer\\Config())\n ->setCacheFile('.Build/.php-cs-fixer.cache') // ✅ Correct location\n // ...\n```\n\n**Update composer scripts:**\n\n```json\n{\n \"scripts\": {\n \"ci:cgl\": [\n \"php-cs-fixer fix --cache-file .Build/.php-cs-fixer.cache --dry-run --diff\"\n ]\n }\n}\n```\n\n### Issue 2: .Build/ Files Committed to Git\n\n**Diagnosis:**\n\n```bash\ngit ls-files .Build/\n# Should be empty\n```\n\n**Problem:** Generated files tracked in git (vendor/, bin/, etc.)\n\n**Fix:**\n\n```bash\n# Remove from git tracking\ngit rm -r --cached .Build/\n\n# Ensure .gitignore has entry\necho \".Build/\" >> .gitignore\n\n# Commit the cleanup\ngit add .gitignore\ngit commit -m \"fix: remove .Build/ from git tracking, add to .gitignore\"\n```\n\n### Issue 3: Missing Build/ Directory\n\n**Diagnosis:**\n\n```bash\nls -la Build/\n# Directory doesn't exist\n```\n\n**Problem:** No standardized quality tool configuration\n\n**Fix:**\n\n```bash\n# Create Build/ directory structure\nmkdir -p Build/{phpunit,Scripts}\n\n# Add quality tool configs (see templates below)\n# Then commit\ngit add Build/\ngit commit -m \"feat: add Build/ directory with quality tool configurations\"\n```\n\n### Issue 4: Rector in Wrong Location\n\n**Diagnosis:**\n\n```bash\n# Check for rector.php in project root\nls -la rector.php\n\n# Should be in Build/ or Build/rector/ instead\n```\n\n**Problem:** Configuration file in project root instead of Build/\n\n**Fix:**\n\n```bash\n# Option 1: Move to Build/\nmv rector.php Build/rector.php\n\n# Option 2: Move to Build/rector/ (preferred for complex configs)\nmkdir -p Build/rector\nmv rector.php Build/rector/rector.php\n\n# Update paths in rector.php\n# Then commit\ngit add Build/\ngit rm rector.php\ngit commit -m \"refactor: move rector config to Build/ directory\"\n```\n\n## Conformance Report Integration\n\n### When Evaluating Directory Structure:\n\n**In \"Best Practices\" Section:**\n\n```markdown\n### Directory Structure\n\n**Analysis:**\n\n- ✅ Build/ directory present with committed configurations\n- ✅ Build/phpstan.neon, Build/php-cs-fixer.php present\n- ✅ Build/phpunit/ directory with test configs\n- ✅ Build/Scripts/runTests.sh present\n- ✅ .Build/ properly gitignored (entire directory)\n- ✅ Composer paths correctly reference .Build/\n- ✅ Cache files located in .Build/, not Build/\n- ✅ No .Build/ files committed to git\n\n**Or with issues:**\n\n- ❌ Cache files in wrong location\n - Files: Build/.php-cs-fixer.cache, Build/.phpunit.result.cache\n - Expected: .Build/.php-cs-fixer.cache, .Build/.phpunit.result.cache\n - Severity: Medium\n - Fix: Move cache files to .Build/ and update configs\n\n- ❌ .Build/ files committed to git\n - Files: .Build/vendor/, .Build/bin/\n - Command: `git ls-files .Build/` shows tracked files\n - Severity: High\n - Fix: `git rm -r --cached .Build/` and ensure .gitignore has `.Build/`\n\n- ⚠️ Missing Build/ directory\n - Impact: No standardized quality tool configuration\n - Severity: Medium\n - Recommendation: Create Build/ with phpstan.neon, php-cs-fixer.php, phpunit configs\n```\n\n## Scoring Impact\n\n**Best Practices Score Deductions:**\n\n| Issue | Severity | Score Impact |\n|-------|----------|--------------|\n| .Build/ files committed | High | -4 points |\n| Cache files in Build/ | Medium | -2 points |\n| Missing .gitignore for .Build/ | High | -3 points |\n| Composer paths don't match structure | High | -3 points |\n| Missing Build/ directory | Medium | -2 points |\n| Rector/configs in project root | Low | -1 point |\n\n**Maximum deduction for directory issues:** -6 points (out of 20 for Best Practices)\n\n## Automated Validation Script\n\nCreate `scripts/validate-directory-structure.sh`:\n\n```bash\n#!/bin/bash\n\nset -e\n\necho \"🔍 Validating directory structure...\"\n\nISSUES=0\n\n# Check 1: .Build/ should be gitignored\nif ! grep -qE '^\\\\.Build/' .gitignore; then\n echo \"❌ .gitignore missing '.Build/' entry\"\n ISSUES=$((ISSUES + 1))\nelse\n echo \"✅ .Build/ properly gitignored\"\nfi\n\n# Check 2: No .Build/ files should be tracked\nTRACKED_BUILD=$(git ls-files .Build/ 2>/dev/null | wc -l)\nif [ \"${TRACKED_BUILD}\" -gt 0 ]; then\n echo \"❌ .Build/ files are committed to git:\"\n git ls-files .Build/\n ISSUES=$((ISSUES + 1))\nelse\n echo \"✅ No .Build/ files tracked in git\"\nfi\n\n# Check 3: Build/ directory should exist\nif [ ! -d \"Build\" ]; then\n echo \"⚠️ Build/ directory missing\"\n ISSUES=$((ISSUES + 1))\nelse\n echo \"✅ Build/ directory exists\"\n\n # Check for standard files\n [ -f \"Build/phpstan.neon\" ] && echo \" ✅ phpstan.neon\" || echo \" ⚠️ phpstan.neon missing\"\n [ -f \"Build/php-cs-fixer.php\" ] && echo \" ✅ php-cs-fixer.php\" || echo \" ⚠️ php-cs-fixer.php missing\"\n [ -d \"Build/phpunit\" ] && echo \" ✅ phpunit/\" || echo \" ⚠️ phpunit/ missing\"\n [ -f \"Build/Scripts/runTests.sh\" ] && echo \" ✅ runTests.sh\" || echo \" ⚠️ runTests.sh missing\"\nfi\n\n# Check 4: Cache files should NOT be in Build/\nif [ -f \"Build/.php-cs-fixer.cache\" ] || [ -f \"Build/.phpunit.result.cache\" ]; then\n echo \"❌ Cache files in wrong location (Build/ instead of .Build/):\"\n ls -la Build/.*.cache 2>/dev/null || true\n ISSUES=$((ISSUES + 1))\nelse\n echo \"✅ No cache files in Build/\"\nfi\n\n# Check 5: Composer paths should reference .Build/\nBIN_DIR=$(jq -r '.config.\"bin-dir\" // \".Build/bin\"' composer.json)\nVENDOR_DIR=$(jq -r '.config.\"vendor-dir\" // \".Build/vendor\"' composer.json)\nWEB_DIR=$(jq -r '.extra.typo3.cms.\"web-dir\" // \".Build/public\"' composer.json)\n\nif [ \"${BIN_DIR}\" = \".Build/bin\" ]; then\n echo \"✅ Composer bin-dir: ${BIN_DIR}\"\nelse\n echo \"⚠️ Composer bin-dir: ${BIN_DIR} (expected .Build/bin)\"\n ISSUES=$((ISSUES + 1))\nfi\n\nif [ \"${VENDOR_DIR}\" = \".Build/vendor\" ]; then\n echo \"✅ Composer vendor-dir: ${VENDOR_DIR}\"\nelse\n echo \"⚠️ Composer vendor-dir: ${VENDOR_DIR} (expected .Build/vendor)\"\n ISSUES=$((ISSUES + 1))\nfi\n\nif [ \"${WEB_DIR}\" = \".Build/public\" ]; then\n echo \"✅ TYPO3 web-dir: ${WEB_DIR}\"\nelse\n echo \"⚠️ TYPO3 web-dir: ${WEB_DIR} (expected .Build/public)\"\n ISSUES=$((ISSUES + 1))\nfi\n\necho \"\"\nif [ ${ISSUES} -eq 0 ]; then\n echo \"✅ Directory structure validation complete - no issues found\"\n exit 0\nelse\n echo \"❌ Directory structure validation found ${ISSUES} issue(s)\"\n exit 1\nfi\n```\n\n## Quick Reference Checklist\n\n**When evaluating directory structure:**\n\n```\n□ .gitignore contains .Build/ entry\n□ Build/ directory exists and contains configs\n□ Build/phpstan.neon exists\n□ Build/php-cs-fixer.php exists\n□ Build/phpunit/ directory exists with XML configs\n□ Build/Scripts/runTests.sh exists\n□ .Build/ is NOT tracked in git (git ls-files .Build/ is empty)\n□ Cache files are in .Build/, not Build/\n□ Composer bin-dir = .Build/bin\n□ Composer vendor-dir = .Build/vendor\n□ TYPO3 web-dir = .Build/public\n□ No configuration files in project root (rector.php, phpstan.neon, etc.)\n□ Root-only configs stay at root (renovate.json, grumphp.yml, infection.json5)\n```\n\n## Root vs Build/ Configuration Files\n\nSome tools **require** configuration at project root due to auto-discovery conventions:\n\n| File | Location | Reason |\n|------|----------|--------|\n| `composer.json` | Root ✅ | Composer requirement |\n| `renovate.json` | Root ✅ | Renovate Bot auto-discovery |\n| `grumphp.yml` | Root ✅ | GrumPHP convention |\n| `infection.json5` | Root ✅ | Infection auto-discovery |\n| `.editorconfig` | Root ✅ | IDE convention |\n| `phpstan.neon` | Build/ ✅ | Supports `--config` flag |\n| `php-cs-fixer.php` | Build/ ✅ | Supports `--config` flag |\n| `rector.php` | Build/ ✅ | Supports `--config` flag |\n| `fractor.php` | Build/ ✅ | Supports `--config` flag |\n| `phpcs.xml` | Build/ ✅ | Supports `--standard` flag |\n\n**Rule**: Tools with `--config` flags → Build/. Tools with auto-discovery only → Root.\n\n## DDEV public/ Directory Exclusion\n\nWhen using DDEV, a `public/` directory is created with TYPO3 entry points:\n\n```\npublic/\n├── index.php # Frontend entry point\n└── typo3/\n └── index.php # Backend entry point\n```\n\n**These are TYPO3 Core files, not extension code.** Exclude from linting:\n\n```php\n// Build/php-cs-fixer.php\n$finder = PhpCsFixer\\Finder::create()\n ->in(__DIR__ . '/..')\n ->exclude([\n '.Build',\n '.ddev',\n 'Build',\n 'public', // ← TYPO3 entry points from DDEV\n 'vendor',\n ]);\n```\n\n**Why exclude?**\n- Files are TYPO3 Core, following TYPO3's conventions (not extension's)\n- php-cs-fixer would want to add `declare(strict_types=1)` which breaks TYPO3 bootstrap\n- Not part of extension distribution\n\n## Configuration File Templates\n\n### Build/phpstan.neon\n\n```neon\nincludes:\n - vendor/phpstan/phpstan-strict-rules/rules.neon\n - vendor/saschaegerer/phpstan-typo3/extension.neon\n\nparameters:\n level: max\n paths:\n - Classes\n - Tests\n tmpDir: .Build/phpstan\n reportUnmatchedIgnoredErrors: true\n```\n\n### Build/php-cs-fixer.php\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\n$finder = (new PhpCsFixer\\Finder())\n ->in(__DIR__ . '/../Classes')\n ->in(__DIR__ . '/../Tests');\n\nreturn (new PhpCsFixer\\Config())\n ->setRules([\n '@PSR12' => true,\n '@PhpCsFixer' => true,\n 'declare_strict_types' => true,\n ])\n ->setCacheFile('.Build/.php-cs-fixer.cache')\n ->setRiskyAllowed(true)\n ->setFinder($finder);\n```\n\n### Build/rector/rector.php\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nuse Rector\\Config\\RectorConfig;\nuse Rector\\ValueObject\\PhpVersion;\nuse Ssch\\TYPO3Rector\\Set\\Typo3LevelSetList;\n\nreturn RectorConfig::configure()\n ->withPaths([\n __DIR__ . '/../../Classes/',\n __DIR__ . '/../../Tests/',\n ])\n ->withPhpVersion(PhpVersion::PHP_82)\n ->withPhpSets(true)\n ->withSets([\n Typo3LevelSetList::UP_TO_TYPO3_13,\n ]);\n```\n\n## Resources\n\n- **Tea Extension Structure:** https://github.com/TYPO3BestPractices/tea\n- **Composer Documentation:** https://getcomposer.org/doc/06-config.md\n- **TYPO3 Extension Structure:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/FileStructure/\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":17456,"content_sha256":"95cac62d082e470f0031ba7f4b76d3c6409d0658ea35cf9b56df696694505004"},{"filename":"references/dual-version-compatibility.md","content":"# Dual Version (v12 + v13) Compatibility Patterns\n\n> **Source**: netresearch/contexts extension conformance for TYPO3 12.4 + 13.4 LTS (2024-12)\n\n## Version Constraint Strategy\n\n### composer.json\n```json\n{\n \"require\": {\n \"php\": \"^8.2\",\n \"typo3/cms-core\": \"^12.4 || ^13.4\"\n }\n}\n```\n\n### ext_emconf.php\n```php\n'constraints' => [\n 'depends' => [\n 'typo3' => '12.4.0-13.4.99',\n 'php' => '8.2.0-8.4.99',\n ],\n],\n```\n\n## Critical Rector Configuration\n\n**DO NOT** use `UP_TO_TYPO3_13` when supporting both versions:\n\n```php\n// rector.php - CORRECT for dual v12+v13\n$rectorConfig->sets([\n LevelSetList::UP_TO_PHP_82,\n Typo3LevelSetList::UP_TO_TYPO3_12, // NOT UP_TO_TYPO3_13!\n Typo3SetList::CODE_QUALITY,\n Typo3SetList::GENERAL,\n]);\n```\n\n**Reason**: `UP_TO_TYPO3_13` introduces v13-only APIs that break v12 compatibility.\n\n## API Compatibility Decision Matrix\n\n| Purpose | Use This (v12 Compatible) | Avoid (v13 Only) |\n|---------|---------------------------|------------------|\n| Frontend user session | `$TSFE->fe_user->getKey()` | `$request->getAttribute('frontend.user')` |\n| Page information | `$data['pObj']->rootLine` | `$request->getAttribute('frontend.page.information')` |\n| Language | `$TSFE->sys_language_uid` | `$request->getAttribute('language')` |\n| Site | `$TSFE->getSite()` | Works in both |\n\n## Compatibility Layer Pattern\n\nWhen you need different behavior for v12 vs v13:\n\n```php\nuse TYPO3\\CMS\\Core\\Information\\Typo3Version;\n\nclass CompatibilityHelper\n{\n public static function isTypo3v13OrHigher(): bool\n {\n return (new Typo3Version())->getMajorVersion() >= 13;\n }\n\n public static function getPageId(ServerRequestInterface $request): int\n {\n if (self::isTypo3v13OrHigher()) {\n $pageInfo = $request->getAttribute('frontend.page.information');\n return $pageInfo?->getId() ?? 0;\n }\n\n // v12 fallback\n $tsfe = $GLOBALS['TSFE'] ?? null;\n return $tsfe?->id ?? 0;\n }\n}\n```\n\n## Testing Matrix Requirements\n\nFor enterprise-grade dual-version support:\n\n| Test Type | Coverage Target |\n|-----------|-----------------|\n| Unit Tests | 70%+ |\n| Functional Tests | Key integrations |\n| E2E Tests | Critical user journeys |\n\n### CI Matrix\n```yaml\nmatrix:\n include:\n - php: '8.2'\n typo3: '^12.4'\n - php: '8.3'\n typo3: '^12.4'\n - php: '8.2'\n typo3: '^13.4'\n - php: '8.3'\n typo3: '^13.4'\n - php: '8.4'\n typo3: '^13.4'\n```\n\n## Conformance Scoring Adjustments\n\n### Base Score Modifications\nWhen evaluating dual-version extensions:\n\n| Criterion | Single Version | Dual Version |\n|-----------|---------------|--------------|\n| Uses v12 APIs only | Full points | Full points |\n| Uses v13-only APIs | Full points | -10 points |\n| Has version detection | +0 | +5 bonus |\n| CI tests both versions | N/A | Required |\n\n### Excellence Indicators\nAdditional excellence points for dual-version:\n\n| Indicator | Points |\n|-----------|--------|\n| Matrix CI (both versions) | +3 |\n| Compatibility layer documented | +2 |\n| Version-specific documentation | +2 |\n\n## Documentation Requirements\n\nDual-version extensions must document:\n\n1. **Supported versions** prominently in README\n2. **Installation differences** (if any)\n3. **Feature parity** (any v13-only features)\n4. **Migration path** from single to dual version\n\n### Example README Section\n```markdown\n## Compatibility\n\n| TYPO3 | PHP | Status |\n|-------|-----|--------|\n| 13.4 LTS | 8.2 - 8.4 | Supported |\n| 12.4 LTS | 8.2 - 8.3 | Supported |\n| 11.5 LTS | 7.4 - 8.1 | Use v3.x |\n```\n\n## Checklist for Dual-Version Extensions\n\n- [ ] `composer.json` has `^12.4 || ^13.4` constraint\n- [ ] `ext_emconf.php` has `12.4.0-13.4.99` constraint\n- [ ] Rector uses `UP_TO_TYPO3_12` only\n- [ ] No v13-only request attributes used directly\n- [ ] CI matrix tests both versions\n- [ ] All tests pass on both versions\n- [ ] Documentation states supported versions\n- [ ] PHP minimum is 8.2 (required for v13)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3997,"content_sha256":"962f0f53ed6d5b251ec419562cec0997616206eb8e7ebab0219e45bee18c63c7"},{"filename":"references/excellence-indicators.md","content":"# Excellence Indicators Reference\n\n**Purpose:** Document optional features that indicate exceptional TYPO3 extension quality beyond basic conformance\n\n## Overview\n\nExcellence Indicators are **optional** features that demonstrate exceptional project quality, community engagement, and professional development practices. Extensions are **not penalized** for missing these features, but **earn bonus points** when present.\n\n**Key Principle:** Base conformance (0-100 points) measures adherence to TYPO3 standards. Excellence indicators (0-22 bonus points) reward exceptional quality.\n\n---\n\n## Scoring System\n\n**Total Excellence Points: 0-22 (bonus)**\n\n| Category | Max Points | Purpose |\n|----------|-----------|---------|\n| Community & Internationalization | 6 | Engagement, accessibility, distribution |\n| Advanced Quality Tooling | 9 | Automation, code quality, TER workflow |\n| Documentation Excellence | 4 | Comprehensive docs, modern tooling |\n| Extension Configuration | 3 | Professional setup, flexibility |\n\n---\n\n## Category 1: Community & Internationalization (0-6 points)\n\n### 1.1 Crowdin Integration (+2 points)\n\n**File:** `crowdin.yml`\n\n**Purpose:** Community-driven translation management platform integration\n\n**Example (georgringer/news):**\n```yaml\nfiles:\n - source: /Resources/Private/Language/locallang*.xlf\n translation: /Resources/Private/Language/%two_letters_code%.%original_file_name%\n```\n\n**Benefits:**\n- Enables community translators to contribute\n- Automated translation synchronization\n- Professional multilingual support\n- Reduces maintenance burden for translations\n\n**Validation:**\n```bash\n[ -f \"crowdin.yml\" ] && echo \"✅ Crowdin integration (+2)\"\n```\n\n**Reference:** [Crowdin TYPO3 Integration](https://crowdin.com/)\n\n---\n\n### 1.2 GitHub Issue Templates (+1 point)\n\n**Files:** `.github/ISSUE_TEMPLATE/`\n- `Bug_report.md`\n- `Feature_request.md`\n- `Support_question.md`\n\n**Purpose:** Structured community contribution and issue reporting\n\n**Example (georgringer/news):**\n```markdown\n---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: 'bug'\nassignees: ''\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n...\n```\n\n**Benefits:**\n- Ensures complete bug reports\n- Reduces back-and-forth communication\n- Categorizes issues automatically\n- Professional project impression\n\n**Validation:**\n```bash\nls -1 .github/ISSUE_TEMPLATE/*.md 2>/dev/null | wc -l\n# 3 files = +1 point\n```\n\n---\n\n### 1.3 .gitattributes Export Optimization (+1 point)\n\n**File:** `.gitattributes`\n\n**Purpose:** Reduce TER (TYPO3 Extension Repository) package size by excluding development files\n\n**Example (georgringer/news):**\n```gitattributes\n/.github/ export-ignore\n/Build/ export-ignore\n/Tests/ export-ignore\n/.editorconfig export-ignore\n/.gitattributes export-ignore\n/.gitignore export-ignore\n/.styleci.yml export-ignore\n/Makefile export-ignore\n```\n\n**Benefits:**\n- Smaller download size for production installations\n- Faster `composer install` in production\n- Professional package distribution\n- Security (doesn't ship development files)\n\n**Validation:**\n```bash\ngrep -q \"export-ignore\" .gitattributes && echo \"✅ Export optimization (+1)\"\n```\n\n**Impact Example:**\n- Repository size: 15 MB (with tests, CI configs)\n- TER package size: 2 MB (production files only)\n- **Reduction:** ~87%\n\n---\n\n### 1.4 Professional README with Badges (+2 points)\n\n**File:** `README.md`\n\n**Purpose:** Comprehensive project overview with status indicators\n\n**Required Elements (all 4 required for points):**\n1. Stability badge (Packagist or TER)\n2. CI/Build status badge (GitHub Actions, GitLab CI)\n3. Download stats (Packagist downloads)\n4. Compatibility matrix table\n\n**Example (georgringer/news):**\n```markdown\n[![Latest Stable Version](https://poser.pugx.org/georgringer/news/v/stable)](https://extensions.typo3.org/extension/news/)\n[![TYPO3 12](https://img.shields.io/badge/TYPO3-12-orange.svg)](https://get.typo3.org/version/12)\n[![TYPO3 13](https://img.shields.io/badge/TYPO3-13-orange.svg)](https://get.typo3.org/version/13)\n[![Total Downloads](https://poser.pugx.org/georgringer/news/d/total)](https://packagist.org/packages/georgringer/news)\n![Build v12](https://github.com/georgringer/news/actions/workflows/core12.yml/badge.svg)\n[![Crowdin](https://badges.crowdin.net/typo3-extension-news/localized.svg)](https://crowdin.com/project/typo3-extension-news)\n\n## Compatibility\n\n| News | TYPO3 | PHP | Support / Development |\n|------|-----------|-----------|--------------------------------------|\n| 12 | 12 - 13 | 8.1 - 8.3 | features, bugfixes, security updates |\n| 11 | 11 - 12 | 7.4 - 8.3 | security updates |\n```\n\n**Validation:**\n```bash\n# Check for at least 3 badges and a compatibility table\ngrep -c \"!\\[\" README.md # Badge count\ngrep -c \"^|\" README.md # Table rows\n```\n\n---\n\n## Category 2: Advanced Quality Tooling (0-9 points)\n\n### 2.1 Fractor Configuration (+2 points)\n\n**File:** `Build/fractor/fractor.php`\n\n**Purpose:** Automated refactoring for TypoScript and XML configuration files\n\n**What is Fractor?**\n- Rector handles PHP code refactoring\n- **Fractor handles TypoScript and XML** file refactoring\n- Automates TYPO3 configuration migrations\n\n**Example (georgringer/news):**\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nuse a9f\\Fractor\\Configuration\\FractorConfiguration;\nuse a9f\\FractorTypoScript\\Configuration\\TypoScriptProcessorOption;\nuse a9f\\FractorXml\\Configuration\\XmlProcessorOption;\nuse a9f\\Typo3Fractor\\Set\\Typo3LevelSetList;\n\nreturn FractorConfiguration::configure()\n ->withPaths([\n __DIR__ . '/../../Classes',\n __DIR__ . '/../../Configuration/',\n __DIR__ . '/../../Resources',\n ])\n ->withSets([\n Typo3LevelSetList::UP_TO_TYPO3_12,\n ])\n ->withOptions([\n TypoScriptProcessorOption::INDENT_CHARACTER => 'auto',\n XmlProcessorOption::INDENT_CHARACTER => Indent::STYLE_TAB,\n ]);\n```\n\n**Benefits:**\n- Automates TypoScript configuration migrations\n- Modernizes FlexForm XML structures\n- Reduces manual refactoring effort\n- Catches TYPO3 API changes in configuration\n\n**Required Packages:**\n```json\n{\n \"require-dev\": {\n \"a9f/fractor\": \"^1.0\",\n \"a9f/typo3-fractor\": \"^1.0\"\n }\n}\n```\n\n**Validation:**\n```bash\n[ -f \"Build/fractor/fractor.php\" ] && echo \"✅ Fractor configuration (+2)\"\n```\n\n---\n\n### 2.2 TYPO3 CodingStandards Package (+1 point)\n\n**File:** `Build/php-cs-fixer/php-cs-fixer.php`\n\n**Purpose:** Official TYPO3 community coding standards package (not custom config)\n\n**Example (georgringer/news):**\n```php\n\u003c?php\n\nuse PhpCsFixer\\Finder;\nuse TYPO3\\CodingStandards\\CsFixerConfig;\n\n$config = CsFixerConfig::create();\n$config->setHeader(\n 'This file is part of the \"news\" Extension for TYPO3 CMS.\n\nFor the full copyright and license information, please read the\nLICENSE.txt file that was distributed with this source code.',\n true\n);\n```\n\n**Benefits:**\n- Official TYPO3 community standards\n- Automatic copyright header injection\n- PER Coding Style (PSR-12 successor)\n- Consistent with TYPO3 core\n\n**Required Package:**\n```json\n{\n \"require-dev\": {\n \"typo3/coding-standards\": \"^0.5\"\n }\n}\n```\n\n**Validation:**\n```bash\ngrep -q \"TYPO3\\\\\\\\CodingStandards\" Build/php-cs-fixer/php-cs-fixer.php && echo \"✅ TYPO3 CodingStandards (+1)\"\n```\n\n**Alternative (no points):** Custom php-cs-fixer config (still good, but not official package)\n\n---\n\n### 2.3 StyleCI Integration (+1 point)\n\n**File:** `.styleci.yml`\n\n**Purpose:** Cloud-based automatic code style checking on pull requests\n\n**Example (georgringer/news):**\n```yaml\npreset: psr12\n\nenabled:\n - no_unused_imports\n - ordered_imports\n - single_quote\n - short_array_syntax\n - hash_to_slash_comment\n - native_function_casing\n\nfinder:\n name:\n - \"*.php\"\n not-path:\n - \".Build\"\n - \"Build/php-cs-fixer\"\n - \"Documentation\"\n```\n\n**Benefits:**\n- Automatic PR code style checks (no local setup needed)\n- Visual code review integration\n- Reduces reviewer burden\n- Enforces consistency across contributors\n\n**Validation:**\n```bash\n[ -f \".styleci.yml\" ] && echo \"✅ StyleCI integration (+1)\"\n```\n\n**Note:** Alternative to local php-cs-fixer CI checks, not replacement\n\n---\n\n### 2.4 Makefile Task Automation (+1 point)\n\n**File:** `Makefile`\n\n**Purpose:** Self-documenting task automation and workflow management\n\n**Example (georgringer/news):**\n```makefile\n.PHONY: help\nhelp: ## Displays this list of targets with descriptions\n\t@echo \"The following commands are available:\\n\"\n\t@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = \":.*?## \"}; {printf \"\\033[32m%-30s\\033[0m %s\\n\", $1, $2}'\n\n.PHONY: docs\ndocs: ## Generate projects docs (from \"Documentation\" directory)\n\tmkdir -p Documentation-GENERATED-temp\n\tdocker run --rm --pull always -v \"$(shell pwd)\":/project -t ghcr.io/typo3-documentation/render-guides:latest --config=Documentation\n```\n\n**Benefits:**\n- Discoverable commands (`make help`)\n- Consistent workflow across contributors\n- Reduces documentation for common tasks\n- Docker-based documentation rendering\n\n**Validation:**\n```bash\n[ -f \"Makefile\" ] && grep -q \"^help:.*##\" Makefile && echo \"✅ Makefile automation (+1)\"\n```\n\n---\n\n### 2.5 Comprehensive CI Matrix (+2 points)\n\n**Files:** `.github/workflows/*.yml` or `.gitlab-ci.yml`\n\n**Purpose:** Test across multiple PHP versions and dependency scenarios\n\n**Required for +2 points:**\n- At least 3 PHP versions tested\n- Both `composerInstallLowest` and `composerInstallHighest` strategies\n- Multiple TYPO3 versions if extension supports multiple\n\n**Example (georgringer/news):**\n```yaml\nname: core 12\non: [ push, pull_request ]\n\njobs:\n tests:\n runs-on: ubuntu-22.04\n strategy:\n fail-fast: false\n matrix:\n php: [ '8.1', '8.2', '8.3', '8.4' ] # 4 PHP versions\n composerInstall: [ 'composerInstallLowest', 'composerInstallHighest' ]\n steps:\n - name: Checkout\n uses: actions/checkout@v3\n - name: Install testing system\n run: Build/Scripts/runTests.sh -t 12 -p ${{ matrix.php }} -s ${{ matrix.composerInstall }}\n - name: Lint PHP\n run: Build/Scripts/runTests.sh -t 12 -p ${{ matrix.php }} -s lint\n```\n\n**Benefits:**\n- Catches dependency conflicts early\n- Ensures compatibility across PHP versions\n- Tests minimum and maximum dependency versions\n- Professional CI/CD setup\n\n**Validation:**\n```bash\n# Check for matrix with multiple PHP versions and composerInstall strategies\ngrep -A 5 \"matrix:\" .github/workflows/*.yml | grep -c \"composerInstall\"\n```\n\n---\n\n### 2.6 TER Publishing Workflow (+2 points)\n\n**File:** `.github/workflows/publish-to-ter.yml`\n\n**Purpose:** Automated extension publishing to TYPO3 Extension Repository on releases\n\n**Reference:** `references/ter-publishing.md`\n\n**Required Elements:**\n- Triggers on `release: [published]` event\n- Tag format validation (vX.Y.Z)\n- Version extraction (strips 'v' prefix)\n- Proper upload comment handling\n- Uses `typo3/tailor` for publishing\n\n**Example:**\n```yaml\nname: Publish to TER\non:\n release:\n types: [published]\n\njobs:\n publish:\n runs-on: ubuntu-latest\n env:\n TYPO3_EXTENSION_KEY: ${{ secrets.TYPO3_EXTENSION_KEY }}\n TYPO3_API_TOKEN: ${{ secrets.TYPO3_TER_ACCESS_TOKEN }}\n steps:\n - uses: actions/checkout@v4\n - name: Validate tag\n run: |\n [[ \"${GITHUB_REF_NAME}\" =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+$ ]] || exit 1\n - uses: shivammathur/setup-php@v2\n with:\n php-version: '8.3'\n - run: composer global require typo3/tailor\n - run: |\n VERSION=\"${GITHUB_REF_NAME#v}\"\n tailor set-version \"$VERSION\"\n tailor ter:publish --comment \"${{ github.event.release.body }}\" \"$VERSION\"\n```\n\n**Upload Comment Format:**\n- Plain text only (no HTML/Markdown)\n- Newlines supported (rendered as `\u003cbr>` on frontend)\n- Allowed: word chars, whitespace, `\" % & [ ] ( ) . , ; : / ? { } ! $ - @`\n- Stripped in XML export: `# * + = ~ ^ | \\ \u003c >`\n\n**Benefits:**\n- Automated releases reduce manual errors\n- Consistent versioning across ext_emconf and TER\n- Professional release workflow\n- Release notes automatically sync to TER\n\n**Validation:**\n```bash\n[ -f \".github/workflows/publish-to-ter.yml\" ] && echo \"✅ TER publish workflow (+2)\"\n# Or check for alternative naming\nls .github/workflows/*ter*.yml 2>/dev/null && echo \"✅ TER workflow found\"\n```\n\n---\n\n## Category 3: Documentation Excellence (0-4 points)\n\n### 3.1 Complete Documentation Structure (+3 points)\n\n**Directory:** `Documentation/`\n\n**Purpose:** Documentation appropriate for extension complexity with all required sections\n\n**Required Sections (scale with extension complexity):**\n\n| Extension Complexity | Required Sections | Example |\n|---------------------|-------------------|---------|\n| **Simple** (1-2 features) | Index, Installation, Configuration | Single-purpose utility extension |\n| **Medium** (3-5 features) | + Administration, Reference | Content element, backend module |\n| **Complex** (6+ features) | + Tutorials, FAQ, API docs | Full CMS feature like EXT:news |\n\n**Scoring:**\n- Complete documentation for extension scope: +3 points\n - All required sections present for complexity level\n - Each section has meaningful content (not stub/placeholder)\n - Configuration options documented with examples\n- Partial documentation: +2 points\n - Most required sections present\n - Some sections incomplete or missing examples\n- Basic documentation: +1 point\n - Index.rst and Installation present\n - Minimal configuration documentation\n- No documentation: 0 points\n\n**Example Structure (medium complexity):**\n```\nDocumentation/\n├── Index.rst # Overview and quick links\n├── Installation/ # Setup and requirements\n│ └── Index.rst\n├── Configuration/ # All options with examples\n│ └── Index.rst\n├── Administration/ # Backend usage guide\n│ └── Index.rst\n├── Reference/ # Technical reference\n│ └── Index.rst\n└── Images/ # Visual assets\n```\n\n**Validation:**\n```bash\n# Check for required documentation sections\nREQUIRED_SECTIONS=(\"Index.rst\" \"Installation\" \"Configuration\")\nSCORE=0\nfor section in \"${REQUIRED_SECTIONS[@]}\"; do\n if [ -e \"Documentation/$section\" ] || [ -e \"Documentation/${section}/Index.rst\" ]; then\n ((SCORE++))\n fi\ndone\n\n# Assess completeness relative to extension complexity\nCLASSES_COUNT=$(find Classes -name \"*.php\" 2>/dev/null | wc -l)\nif [ $CLASSES_COUNT -gt 20 ]; then\n COMPLEXITY=\"complex\"\n REQUIRED_SECTIONS+=(\"Administration\" \"Reference\" \"Tutorials\")\nelif [ $CLASSES_COUNT -gt 5 ]; then\n COMPLEXITY=\"medium\"\n REQUIRED_SECTIONS+=(\"Administration\" \"Reference\")\nfi\n\n# Score based on completeness for scope\necho \"Extension complexity: $COMPLEXITY\"\necho \"Documentation score based on completeness for scope\"\n```\n\n**Benefits:**\n- Documentation appropriate for extension scope\n- Quality over quantity approach\n- Reduces user confusion\n- Improves onboarding efficiency\n\n---\n\n### 3.2 Modern Documentation Tooling (+1 point)\n\n**Files:**\n- `Documentation/guides.xml`\n- `Documentation/screenshots.json`\n\n**Purpose:** Modern TYPO3 documentation rendering and screenshot management\n\n**Example (georgringer/news):**\n```xml\n\u003c!-- guides.xml -->\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003cguides xmlns=\"https://guides.typo3.org/ns/1.0\">\n \u003cproject>\n \u003ctitle>News System\u003c/title>\n \u003crelease>12.0\u003c/release>\n \u003cvendor>georgringer\u003c/vendor>\n \u003c/project>\n\u003c/guides>\n```\n\n```json\n// screenshots.json\n{\n \"screenshots\": [\n {\n \"file\": \"Images/Administration/BackendModule.png\",\n \"caption\": \"News administration module\"\n }\n ]\n}\n```\n\n**Benefits:**\n- Automated documentation rendering\n- Screenshot management and regeneration\n- Consistent with TYPO3 documentation standards\n- Future-proof documentation setup\n\n**Validation:**\n```bash\n[ -f \"Documentation/guides.xml\" ] && echo \"✅ Modern documentation tooling (+1)\"\n```\n\n---\n\n## Category 4: Extension Configuration (0-3 points)\n\n### 4.1 Extension Configuration Template (+1 point)\n\n**File:** `ext_conf_template.txt`\n\n**Purpose:** Backend extension configuration interface with categorized settings\n\n**Example (georgringer/news):**\n```\n# Records\n###########################\n# cat=records/enable/103; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.prependAtCopy\nprependAtCopy = 1\n\n# cat=records/enable/101; type=string; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.tagPid\ntagPid = 1\n\n# cat=records/enable/26; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.rteForTeaser\nrteForTeaser = 0\n\n# Backend module\n# cat=backend module/enable/10; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.showAdministrationModule\nshowAdministrationModule = 1\n```\n\n**Benefits:**\n- User-friendly backend configuration\n- Categorized settings for clarity\n- Localized labels\n- No PHP knowledge required for configuration\n\n**Validation:**\n```bash\n[ -f \"ext_conf_template.txt\" ] && echo \"✅ Extension configuration template (+1)\"\n```\n\n**Note:** Not required for modern TYPO3 extensions using Site Sets, but still valuable for global extension settings\n\n---\n\n### 4.2 Composer Documentation Scripts (+1 point)\n\n**File:** `composer.json`\n\n**Purpose:** Automated documentation rendering and watching\n\n**Required Scripts (at least 2 of 3):**\n- `doc-init` - Initialize documentation rendering\n- `doc-make` - Render documentation\n- `doc-watch` - Watch and auto-render documentation\n\n**Example (georgringer/news):**\n```json\n{\n \"scripts\": {\n \"doc-init\": \"docker run --rm --pull always -v $(pwd):/project -it ghcr.io/typo3-documentation/render-guides:latest --config=Documentation\",\n \"doc-make\": \"make docs\",\n \"doc-watch\": \"docker run --rm -it --pull always -v \\\"./Documentation:/project/Documentation\\\" -v \\\"./Documentation-GENERATED-temp:/project/Documentation-GENERATED-temp\\\" -p 5173:5173 ghcr.io/garvinhicking/typo3-documentation-browsersync:latest\"\n },\n \"scripts-descriptions\": {\n \"doc-init\": \"Initialize documentation rendering\",\n \"doc-make\": \"Render documentation\",\n \"doc-watch\": \"Render documentation including a watcher\"\n }\n}\n```\n\n**Benefits:**\n- Easy documentation development\n- Live preview during writing\n- Docker-based (no local dependencies)\n- Consistent with TYPO3 documentation workflow\n\n**Validation:**\n```bash\ngrep -q \"doc-init.*doc-make\" composer.json && echo \"✅ Composer doc scripts (+1)\"\n```\n\n---\n\n### 4.3 Multiple Configuration Sets (TYPO3 13) (+1 point)\n\n**Directory:** `Configuration/Sets/`\n\n**Purpose:** Multiple configuration presets for different use cases\n\n**Required:** At least 2 different Sets (not just one default)\n\n**Example (georgringer/news has 5 Sets):**\n```\nConfiguration/Sets/\n├── News/ # Base news functionality\n├── RecordLinks/ # Record link handling\n├── Sitemap/ # Sitemap generation\n├── Twb4/ # Twitter Bootstrap 4 templates\n└── Twb5/ # Twitter Bootstrap 5 templates\n```\n\n**Benefits:**\n- Quick setup for different scenarios\n- Reusable configuration patterns\n- Modern TYPO3 13 architecture\n- Flexible deployment\n\n**Validation:**\n```bash\nSET_COUNT=$(find Configuration/Sets -mindepth 1 -maxdepth 1 -type d | wc -l)\n[ $SET_COUNT -ge 2 ] && echo \"✅ Multiple configuration Sets (+1)\"\n```\n\n---\n\n## Excellence Indicators Conformance Report Format\n\n### Example Report Section\n\n```markdown\n## Excellence Indicators (Bonus Score: 12/20)\n\n### ✅ Community & Internationalization (4/6)\n- ✅ Crowdin integration (crowdin.yml): +2 points\n- ✅ GitHub issue templates (3 templates): +1 point\n- ❌ .gitattributes export optimization: 0 points\n- ✅ Professional README with badges: +2 points\n - Stability badge: ✅\n - CI status badge: ✅\n - Download stats: ✅\n - Compatibility matrix: ✅\n\n### ✅ Advanced Quality Tooling (5/7)\n- ✅ Fractor configuration (Build/fractor/fractor.php): +2 points\n- ❌ TYPO3 CodingStandards package: 0 points (uses custom config)\n- ✅ StyleCI integration (.styleci.yml): +1 point\n- ❌ Makefile automation: 0 points\n- ✅ Comprehensive CI matrix (4 PHP versions, composerInstallLowest/Highest): +2 points\n\n### ✅ Documentation Excellence (3/4)\n- ✅ Extensive documentation (183 RST files): +3 points\n- ❌ Modern documentation tooling (guides.xml): 0 points\n\n### ❌ Extension Configuration (0/3)\n- ❌ ext_conf_template.txt: 0 points\n- ❌ Composer documentation scripts: 0 points\n- ❌ Multiple Configuration Sets: 0 points (only 1 Set present)\n\n### Summary\nThis extension demonstrates exceptional quality in documentation and CI/CD practices. Consider adding:\n- .gitattributes with export-ignore for smaller TER packages\n- TYPO3 CodingStandards package for official community standards\n- Makefile for task automation\n- Modern documentation tooling (guides.xml, screenshots.json)\n- Extension configuration template for backend settings\n```\n\n---\n\n## Quick Reference Validation Checklist\n\n**When evaluating excellence indicators:**\n\n```\nCommunity & Internationalization (0-6):\n□ crowdin.yml present (+2)\n□ 3 GitHub issue templates (+1)\n□ .gitattributes with export-ignore (+1)\n□ README with 4+ badges + compatibility table (+2)\n\nAdvanced Quality Tooling (0-9):\n□ Build/fractor/fractor.php present (+2)\n□ TYPO3\\CodingStandards in php-cs-fixer config (+1)\n□ .styleci.yml present (+1)\n□ Makefile with help target (+1)\n□ CI matrix: 3+ PHP versions + composerInstall variants (+2)\n□ TER publish workflow (.github/workflows/*ter*.yml) (+2)\n\nDocumentation Excellence (0-4):\n□ 50-99 RST files (+1) / 100-149 (+2) / 150+ (+3)\n□ guides.xml + screenshots.json (+1)\n\nExtension Configuration (0-3):\n□ ext_conf_template.txt present (+1)\n□ Composer doc scripts (doc-init, doc-make, doc-watch) (+1)\n□ 2+ Configuration Sets in Configuration/Sets/ (+1)\n```\n\n---\n\n## Implementation Notes\n\n**For Conformance Skill:**\n\n1. **Never penalize** missing excellence indicators\n2. **Always report** excellence indicators separately from base conformance\n3. **Score format:** `Base: 94/100 | Excellence: 12/20 | Total: 106/120`\n4. **Optional evaluation:** Can be disabled with flag if user only wants base conformance\n\n**Example CLI:**\n```bash\n# Full evaluation (base + excellence)\ncheck-conformance --with-excellence\n\n# Base conformance only\ncheck-conformance\n\n# Excellence only (for established extensions)\ncheck-conformance --excellence-only\n```\n\n---\n\n## Resources\n\n- **georgringer/news:** https://github.com/georgringer/news (primary reference for excellence patterns)\n- **TYPO3 Best Practices (Tea):** https://github.com/TYPO3BestPractices/tea (primary reference for base conformance)\n- **Fractor:** https://github.com/andreaswolf/fractor\n- **TYPO3 CodingStandards:** https://github.com/TYPO3/coding-standards\n- **StyleCI:** https://styleci.io/\n- **Crowdin:** https://crowdin.com/\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":23104,"content_sha256":"b9ac203988e9813d9687dac7c69e61ae0faa521eb1e3bdcf4a3df51dba2136a8"},{"filename":"references/ext-emconf-validation.md","content":"# ext_emconf.php Validation Standards (TYPO3 v13)\n\n**Source:** TYPO3 Core API Reference v13.4 - FileStructure/ExtEmconf.html\n**Purpose:** Complete validation rules for ext_emconf.php including critical TER restrictions\n\n## CRITICAL RESTRICTIONS\n\n### ❌ MUST NOT use declare(strict_types=1)\n\n**CRITICAL:** The TYPO3 Extension Repository (TER) upload **WILL FAIL** if `declare(strict_types=1)` is present in ext_emconf.php.\n\n❌ **WRONG:**\n```php\n\u003c?php\ndeclare(strict_types=1);\n$EM_CONF[$_EXTKEY] = [\n // configuration\n];\n```\n\n✅ **CORRECT:**\n```php\n\u003c?php\n$EM_CONF[$_EXTKEY] = [\n // configuration\n];\n```\n\n**Detection:**\n```bash\ngrep \"declare(strict_types\" ext_emconf.php && echo \"❌ CRITICAL: TER upload will FAIL\" || echo \"✅ No strict_types\"\n```\n\n### ⚠️ MUST exclude from PHP-CS-Fixer\n\n**CRITICAL:** PHP-CS-Fixer will RE-ADD `declare(strict_types=1)` unless ext_emconf.php is explicitly excluded!\n\n**Build/.php-cs-fixer.dist.php:**\n```php\n$finder = PhpCsFixer\\Finder::create()\n ->in($repoRoot)\n // CRITICAL: ext_emconf.php must NOT have strict_types - TER cannot parse it\n ->notPath('ext_emconf.php');\n```\n\n**Why this matters:** Even if you manually remove strict_types, running `php-cs-fixer fix` or a pre-commit hook will add it back, causing TER upload failures.\n\n### ✅ MUST use $_EXTKEY variable\n\nExtensions must reference the global `$_EXTKEY` variable, not hardcode the extension key.\n\n❌ **WRONG:**\n```php\n$EM_CONF['my_extension'] = [\n // configuration\n];\n```\n\n✅ **CORRECT:**\n```php\n$EM_CONF[$_EXTKEY] = [\n // configuration\n];\n```\n\n**Detection:**\n```bash\ngrep '\\$EM_CONF\\[$_EXTKEY\\]' ext_emconf.php && echo \"✅ Uses $_EXTKEY\" || echo \"❌ Hardcoded key\"\n```\n\n### ❌ MUST NOT contain custom code\n\nThe ext_emconf.php file must only contain the `$EM_CONF` array assignment. No additional functions, classes, or executable code is allowed.\n\n❌ **WRONG:**\n```php\n\u003c?php\nfunction getVersion() { return '1.0.0'; }\n$EM_CONF[$_EXTKEY] = [\n 'version' => getVersion(),\n];\n```\n\n❌ **WRONG:**\n```php\n\u003c?php\n$EM_CONF[$_EXTKEY] = [\n 'title' => 'My Extension',\n];\n// Additional initialization code\nrequire_once 'setup.php';\n```\n\n✅ **CORRECT:**\n```php\n\u003c?php\n$EM_CONF[$_EXTKEY] = [\n 'title' => 'My Extension',\n 'version' => '1.0.0',\n];\n```\n\n**Detection:**\n```bash\n# Check for function/class definitions\ngrep -E '^(function|class|interface|trait|require|include)' ext_emconf.php && echo \"❌ Contains custom code\" || echo \"✅ No custom code\"\n```\n\n---\n\n## Mandatory Fields\n\n### title\n**Format:** English extension name\n\n**Example:**\n```php\n'title' => 'My Extension',\n```\n\n**Validation:**\n```bash\ngrep \"'title' =>\" ext_emconf.php && echo \"✅ Has title\" || echo \"❌ Missing title\"\n```\n\n### description\n**Format:** Short, precise English description of extension functionality\n\n**Requirements:**\n- Clear explanation of what the extension does\n- Should identify the vendor/company for professional extensions\n- Must be meaningful (not just \"Extension\" or empty)\n- Keep concise but informative (typically 50-150 characters)\n\n**Good Examples:**\n```php\n'description' => 'Adds image support to CKEditor5 RTE - by Netresearch',\n'description' => 'Provides advanced content management features for TYPO3 editors',\n'description' => 'Custom form elements for newsletter subscription by Vendor GmbH',\n```\n\n**Bad Examples:**\n```php\n'description' => '', // Empty\n'description' => 'Extension', // Too vague\n'description' => 'Some tools', // Meaningless\n```\n\n**Validation:**\n```bash\n# Check description exists\ngrep \"'description' =>\" ext_emconf.php && echo \"✅ Has description\" || echo \"❌ Missing description\"\n\n# Check description is not empty or trivial\nDESC=$(grep -oP \"'description' => '\\K[^']+(?=')\" ext_emconf.php)\n[[ ${#DESC} -gt 20 ]] && echo \"✅ Description is meaningful\" || echo \"⚠️ Description too short or vague\"\n```\n\n### version\n**Format:** `[int].[int].[int]` (semantic versioning)\n\n**Examples:**\n- `1.0.0` ✅\n- `2.5.12` ✅\n- `v1.0.0` ❌ (no 'v' prefix)\n- `1.0` ❌ (must have three parts)\n\n**Validation:**\n```bash\ngrep -oP \"'version' => '\\K[0-9]+\\.[0-9]+\\.[0-9]+\" ext_emconf.php && echo \"✅ Valid version format\" || echo \"❌ Invalid version\"\n```\n\n### author\n**Format:** Developer name(s), comma-separated for multiple\n\n**Example:**\n```php\n'author' => 'Sebastian Koschel, Sebastian Mendel, Rico Sonntag',\n```\n\n**Validation:**\n```bash\ngrep \"'author' =>\" ext_emconf.php && echo \"✅ Has author\" || echo \"❌ Missing author\"\n```\n\n### author_email\n**Format:** Email address(es), comma-separated for multiple\n\n**Example:**\n```php\n'author_email' => '[email protected], [email protected]',\n```\n\n**Validation:**\n```bash\ngrep \"'author_email' =>\" ext_emconf.php | grep -q \"@\" && echo \"✅ Has author_email\" || echo \"❌ Missing author_email\"\n```\n\n### author_company\n**Format:** Company name\n\n**Example:**\n```php\n'author_company' => 'Company Name GmbH',\n```\n\n**Validation:**\n```bash\ngrep \"'author_company' =>\" ext_emconf.php && echo \"✅ Has author_company\" || echo \"⚠️ Missing author_company\"\n```\n\n---\n\n## Category Options\n\n**Valid categories:**\n\n| Category | Purpose |\n|----------|---------|\n| `be` | Backend-oriented functionality |\n| `module` | Backend modules |\n| `fe` | Frontend-oriented functionality |\n| `plugin` | Frontend plugins |\n| `misc` | Miscellaneous utilities |\n| `services` | TYPO3 services |\n| `templates` | Website templates |\n| `example` | Example/demonstration extensions |\n| `doc` | Documentation |\n| `distribution` | Full site distributions/kickstarters |\n\n**Example:**\n```php\n'category' => 'fe',\n```\n\n**Validation:**\n```bash\ngrep -oP \"'category' => '\\K[a-z]+(?=')\" ext_emconf.php | grep -qE '^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

&& echo \"✅ Valid category\" || echo \"❌ Invalid category\"\n```\n\n---\n\n## State Values\n\n**Valid states:**\n\n| State | Meaning |\n|-------|---------|\n| `alpha` | Initial development phase, unstable |\n| `beta` | Functional but incomplete or under active development |\n| `stable` | Production-ready (author commits to maintenance) |\n| `experimental` | Exploratory work, may be abandoned |\n| `test` | Demonstration or testing purposes only |\n| `obsolete` | Deprecated or unmaintained |\n| `excludeFromUpdates` | Prevents Extension Manager from updating |\n\n**Example:**\n```php\n'state' => 'stable',\n```\n\n**Validation:**\n```bash\ngrep -oP \"'state' => '\\K[a-z]+(?=')\" ext_emconf.php | grep -qE '^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

&& echo \"✅ Valid state\" || echo \"❌ Invalid state\"\n```\n\n---\n\n## Constraints Structure\n\n### Format\n```php\n'constraints' => [\n 'depends' => [\n 'typo3' => '13.4.0-13.4.99',\n 'php' => '8.2.0-8.4.99',\n ],\n 'conflicts' => [\n 'incompatible_ext' => '',\n ],\n 'suggests' => [\n 'recommended_ext' => '1.0.0-2.99.99',\n ],\n],\n```\n\n### depends\n**Purpose:** Required dependencies loaded before this extension\n\n**Mandatory entries:**\n- `typo3` - TYPO3 version range\n- `php` - PHP version range\n\n**Example:**\n```php\n'depends' => [\n 'typo3' => '12.4.0-13.4.99',\n 'php' => '8.1.0-8.4.99',\n 'fluid' => '12.4.0-13.4.99',\n],\n```\n\n**Validation:**\n```bash\ngrep -A 5 \"'depends' =>\" ext_emconf.php | grep -q \"'typo3'\" && echo \"✅ TYPO3 dependency\" || echo \"❌ Missing TYPO3 dep\"\ngrep -A 5 \"'depends' =>\" ext_emconf.php | grep -q \"'php'\" && echo \"✅ PHP dependency\" || echo \"❌ Missing PHP dep\"\n```\n\n### conflicts\n**Purpose:** Extensions incompatible with this one\n\n**Example:**\n```php\n'conflicts' => [\n 'old_extension' => '',\n],\n```\n\n### suggests\n**Purpose:** Recommended companion extensions (loaded before current extension)\n\n**Example:**\n```php\n'suggests' => [\n 'news' => '12.1.0-12.99.99',\n],\n```\n\n---\n\n## Version Constraint Format\n\n### TYPO3 Version\n**Format:** `major.minor.patch-major.minor.patch`\n\n**Examples:**\n- `12.4.0-12.4.99` - TYPO3 12 LTS only\n- `13.4.0-13.4.99` - TYPO3 13 LTS only\n- `12.4.0-13.4.99` - Both v12 and v13 (recommended for compatibility)\n\n### PHP Version\n**Format:** `major.minor.patch-major.minor.patch`\n\n**TYPO3 Compatibility:**\n- TYPO3 12 LTS: PHP 8.1-8.5\n- TYPO3 13 LTS: PHP 8.2-8.5\n- TYPO3 14: PHP 8.3-8.5\n\n**Example:**\n```php\n'php' => '8.1.0-8.5.99', // For v12/v13 compatibility\n'php' => '8.2.0-8.5.99', // For v13 only\n'php' => '8.3.0-8.5.99', // For v14 only\n```\n\n---\n\n## Synchronization with composer.json\n\n**Critical:** ext_emconf.php and composer.json must have matching constraints.\n\n### Mapping Table\n\n| composer.json | ext_emconf.php | Example |\n|--------------|----------------|---------|\n| `\"typo3/cms-core\": \"^12.4 \\|\\| ^13.4\"` | `'typo3' => '12.4.0-13.4.99'` | TYPO3 version |\n| `\"php\": \"^8.1\"` | `'php' => '8.1.0-8.4.99'` | PHP version |\n| `\"typo3/cms-fluid\": \"^12.4\"` | `'fluid' => '12.4.0-12.4.99'` | Extension dependency |\n\n### Validation Strategy\n```bash\n# Compare TYPO3 versions\nCOMPOSER_TYPO3=$(jq -r '.require.\"typo3/cms-core\"' composer.json)\nEMCONF_TYPO3=$(grep -oP \"'typo3' => '\\K[0-9.-]+\" ext_emconf.php)\necho \"Composer: $COMPOSER_TYPO3\"\necho \"ext_emconf: $EMCONF_TYPO3\"\n# Manual comparison required for ^x.y vs x.y.z-x.y.z format\n\n# Compare PHP versions\nCOMPOSER_PHP=$(jq -r '.require.php' composer.json)\nEMCONF_PHP=$(grep -oP \"'php' => '\\K[0-9.-]+\" ext_emconf.php)\necho \"Composer PHP: $COMPOSER_PHP\"\necho \"ext_emconf PHP: $EMCONF_PHP\"\n```\n\n### Common Mismatch Scenarios\n\n| Scenario | composer.json | ext_emconf.php | Problem |\n|----------|--------------|----------------|---------|\n| PHP range drift | `\"php\": \"^8.2\"` | `'php' => '8.1.0-8.4.99'` | ext_emconf allows PHP 8.1 but composer.json does not |\n| TYPO3 range drift | `\"typo3/cms-core\": \"^13.4\"` | `'typo3' => '12.4.0-13.4.99'` | ext_emconf claims v12 support but composer.json does not |\n| Missing upper bound | `\"php\": \">=8.2\"` | `'php' => '8.2.0-8.4.99'` | composer.json has no upper bound, ext_emconf does |\n\n**Severity:** 🔴 Critical - Mismatched constraints cause installation failures and misleading TER listings.\n\n---\n\n## Complete Required Fields Checklist\n\n**Mandatory (MUST have):**\n- [ ] `title` - Extension name in English\n- [ ] `description` - Short, precise description\n- [ ] `version` - Semantic version (x.y.z format)\n- [ ] `category` - Valid category (be, fe, plugin, misc, etc.)\n- [ ] `state` - Valid state (stable, beta, alpha, etc.)\n- [ ] `constraints.depends.typo3` - TYPO3 version range\n- [ ] `constraints.depends.php` - PHP version range\n\n**Recommended (SHOULD have):**\n- [ ] `author` - Developer name(s)\n- [ ] `author_email` - Contact email(s)\n- [ ] `author_company` - Company name\n- [ ] `constraints.conflicts` - Conflicting extensions (even if empty array)\n- [ ] `constraints.suggests` - Suggested companion extensions\n\n**Complete Example:**\n```php\n\u003c?php\n$EM_CONF[$_EXTKEY] = [\n 'title' => 'My Extension Title',\n 'description' => 'Provides specific functionality for TYPO3.',\n 'category' => 'fe',\n 'author' => 'Developer Name',\n 'author_email' => '[email protected]',\n 'author_company' => 'Company Name GmbH',\n 'state' => 'stable',\n 'version' => '1.0.0',\n 'constraints' => [\n 'depends' => [\n 'typo3' => '12.4.0-13.4.99',\n 'php' => '8.1.0-8.4.99',\n ],\n 'conflicts' => [],\n 'suggests' => [],\n ],\n];\n```\n\n---\n\n## Complete Validation Script\n\n```bash\n#!/bin/bash\n# validate-ext-emconf.sh\n\nERRORS=0\nWARNINGS=0\n\necho \"=== ext_emconf.php Validation ====\"\necho \"\"\n\n# CRITICAL: Check for strict_types\nif grep -q \"declare(strict_types\" ext_emconf.php 2>/dev/null; then\n echo \"❌ CRITICAL: ext_emconf.php has declare(strict_types=1)\"\n echo \" TER upload will FAIL!\"\n ((ERRORS++))\nfi\n\n# CRITICAL: Check for $_EXTKEY usage\nif ! grep -q '\\$EM_CONF\\[$_EXTKEY\\]' ext_emconf.php 2>/dev/null; then\n echo \"❌ CRITICAL: Must use \\$EM_CONF[\\$_EXTKEY], not hardcoded key\"\n ((ERRORS++))\nfi\n\n# CRITICAL: Check for custom code\nif grep -E '^(function|class|interface|trait|require|include)' ext_emconf.php 2>/dev/null; then\n echo \"❌ CRITICAL: ext_emconf.php contains custom code (functions/classes/requires)\"\n ((ERRORS++))\nfi\n\n# Check mandatory fields\ngrep -q \"'title' =>\" ext_emconf.php || { echo \"❌ Missing title\"; ((ERRORS++)); }\ngrep -q \"'description' =>\" ext_emconf.php || { echo \"❌ Missing description\"; ((ERRORS++)); }\ngrep -qP \"'version' => '[0-9]+\\.[0-9]+\\.[0-9]+\" ext_emconf.php || { echo \"❌ Missing or invalid version\"; ((ERRORS++)); }\n\n# Check description is meaningful (>20 chars)\nDESC=$(grep -oP \"'description' => '\\K[^']+(?=')\" ext_emconf.php)\n[[ ${#DESC} -lt 20 ]] && { echo \"⚠️ Description too short (should be >20 chars)\"; ((WARNINGS++)); }\n\n# Check category\nCATEGORY=$(grep -oP \"'category' => '\\K[a-z]+(?=')\" ext_emconf.php)\nif [[ ! \"$CATEGORY\" =~ ^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)$ ]]; then\n echo \"❌ Invalid category: $CATEGORY\"\n ((ERRORS++))\nfi\n\n# Check state\nSTATE=$(grep -oP \"'state' => '\\K[a-z]+(?=')\" ext_emconf.php)\nif [[ ! \"$STATE\" =~ ^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)$ ]]; then\n echo \"❌ Invalid state: $STATE\"\n ((ERRORS++))\nfi\n\n# Check constraints\ngrep -A 5 \"'depends' =>\" ext_emconf.php | grep -q \"'typo3'\" || { echo \"❌ Missing TYPO3 dependency\"; ((ERRORS++)); }\ngrep -A 5 \"'depends' =>\" ext_emconf.php | grep -q \"'php'\" || { echo \"❌ Missing PHP dependency\"; ((ERRORS++)); }\n\n# Check recommended author fields\ngrep -q \"'author' =>\" ext_emconf.php || { echo \"⚠️ Missing author\"; ((WARNINGS++)); }\ngrep \"'author_email' =>\" ext_emconf.php | grep -q \"@\" || { echo \"⚠️ Missing or invalid author_email\"; ((WARNINGS++)); }\ngrep -q \"'author_company' =>\" ext_emconf.php || { echo \"⚠️ Missing author_company\"; ((WARNINGS++)); }\n\necho \"\"\necho \"Validation complete: $ERRORS errors, $WARNINGS warnings\"\nexit $ERRORS\n```\n\n---\n\n## Common Violations and Fixes\n\n### 1. Using declare(strict_types=1)\n\n❌ **WRONG - TER upload FAILS:**\n```php\n\u003c?php\ndeclare(strict_types=1);\n$EM_CONF[$_EXTKEY] = [\n 'title' => 'My Extension',\n];\n```\n\n✅ **CORRECT:**\n```php\n\u003c?php\n$EM_CONF[$_EXTKEY] = [\n 'title' => 'My Extension',\n];\n```\n\n### 2. Hardcoded Extension Key\n\n❌ **WRONG:**\n```php\n$EM_CONF['my_extension'] = [\n 'title' => 'My Extension',\n];\n```\n\n✅ **CORRECT:**\n```php\n$EM_CONF[$_EXTKEY] = [\n 'title' => 'My Extension',\n];\n```\n\n### 3. Invalid Category\n\n❌ **WRONG:**\n```php\n'category' => 'utility', // Not a valid category\n```\n\n✅ **CORRECT:**\n```php\n'category' => 'misc', // Use 'misc' for utilities\n```\n\n### 4. Invalid Version Format\n\n❌ **WRONG:**\n```php\n'version' => 'v1.0.0', // No 'v' prefix\n'version' => '1.0', // Must have 3 parts\n```\n\n✅ **CORRECT:**\n```php\n'version' => '1.0.0',\n```\n\n### 5. Missing PHP/TYPO3 Constraints\n\n❌ **WRONG:**\n```php\n'constraints' => [\n 'depends' => [\n 'extbase' => '12.4.0-12.4.99',\n ],\n],\n```\n\n✅ **CORRECT:**\n```php\n'constraints' => [\n 'depends' => [\n 'typo3' => '12.4.0-13.4.99',\n 'php' => '8.1.0-8.4.99',\n 'extbase' => '12.4.0-12.4.99',\n ],\n],\n```\n\n### 6. Mismatched composer.json Constraints\n\n❌ **WRONG:**\n\ncomposer.json:\n```json\n\"require\": {\n \"typo3/cms-core\": \"^13.4\"\n}\n```\n\next_emconf.php:\n```php\n'typo3' => '12.4.0-12.4.99', // Mismatch!\n```\n\n✅ **CORRECT:**\n\ncomposer.json:\n```json\n\"require\": {\n \"typo3/cms-core\": \"^13.4\"\n}\n```\n\next_emconf.php:\n```php\n'typo3' => '13.4.0-13.4.99', // Matches!\n```\n\n---\n\n## Quick Reference\n\n### Critical Checks\n```bash\n# Will TER upload fail?\ngrep \"declare(strict_types\" ext_emconf.php && echo \"❌ TER FAIL\"\n\n# Uses $_EXTKEY?\ngrep '\\$EM_CONF\\[$_EXTKEY\\]' ext_emconf.php && echo \"✅ OK\"\n\n# Valid category?\ngrep -oP \"'category' => '\\K[a-z]+(?=')\" ext_emconf.php | grep -qE '^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

&& echo \"✅ OK\"\n\n# Valid state?\ngrep -oP \"'state' => '\\K[a-z]+(?=')\" ext_emconf.php | grep -qE '^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

&& echo \"✅ OK\"\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":16030,"content_sha256":"ee5fe41f2f858d912f787aebd1383588ada2f90f353769557243831130458e32"},{"filename":"references/ext-files-validation.md","content":"# Extension Files Validation Standards (TYPO3 v13)\n\n**Sources:** TYPO3 Core API Reference v13.4\n**Purpose:** Validation rules for ext_localconf.php, ext_tables.php, ext_tables.sql, ext_tables_static+adt.sql, ext_conf_template.txt\n\n## ext_localconf.php\n\n### Purpose\nGlobal configuration file loaded during TYPO3 bootstrap in frontend, backend, and CLI contexts.\n\n### Required Structure\n```php\n\u003c?php\ndeclare(strict_types=1);\ndefined('TYPO3') or die();\n\n// Configuration code here\n```\n\n### What SHOULD Be Included\n✅ Registering hooks, XCLASSes, array assignments to `$GLOBALS['TYPO3_CONF_VARS']`\n✅ Registering Request Handlers\n✅ Adding default TypoScript via ExtensionManagementUtility APIs\n✅ Registering Scheduler Tasks\n✅ Adding reports to reports module\n✅ Registering Services via Service API\n\n### What Should NOT Be Included\n❌ Function and class definitions (use services/utility classes)\n❌ Class loader or package manager configuration\n❌ Cache/config manager settings\n❌ Log manager configuration\n❌ Time zone, memory limit, locale settings\n❌ Icon registration (use `Icons.php` instead)\n\n### TYPO3 v13 Deprecations\n\n**❌ DEPRECATED:** `\\TYPO3\\CMS\\Core\\Utility\\ExtensionManagementUtility::addUserTSConfig()`\n- **Removal:** TYPO3 v14.0\n- **Alternative:** Use `Configuration/user.tsconfig` file instead\n\n**❌ DEPRECATED (since v12):** Page TSconfig in ext_localconf.php\n- **Alternative:** Use `Configuration/page.tsconfig` file instead\n\n### Validation Commands\n```bash\n# Check required structure\nhead -5 ext_localconf.php | grep \"declare(strict_types=1)\" && echo \"✅ Has strict_types\"\nhead -5 ext_localconf.php | grep \"defined('TYPO3')\" && echo \"✅ Has TYPO3 guard\"\n\n# Check for deprecated addUserTSConfig\ngrep \"addUserTSConfig\" ext_localconf.php && echo \"⚠️ DEPRECATED: Use Configuration/user.tsconfig\"\n```\n\n---\n\n## ext_tables.php\n\n### Deprecation Status\n**PHASING OUT:** Increasingly replaced by modern configuration approaches.\n\n### What Should NOT Be in ext_tables.php (v13)\n\n❌ **TCA configurations** → Use `Configuration/TCA/tablename.php`\n❌ **TCA overrides** → Use `Configuration/TCA/Overrides/somefile.php`\n❌ **Insert records** → Move to TCA Overrides files\n❌ **Static files** → Move to `Configuration/TCA/Overrides/sys_template.php`\n❌ **Backend modules** → Moved to `Configuration/Backend/` in v13.0\n\n### Appropriate Uses (Remaining)\n✅ Registering scheduler tasks with localization labels\n✅ Registering custom page types\n✅ Extending backend user settings\n\n### v13 Migration\n\n**Backend Module Registration:**\n```php\n❌ OLD (ext_tables.php):\nExtensionUtility::registerModule(...);\n\n✅ NEW (Configuration/Backend/Modules.php):\nreturn [\n 'web_myext' => [\n 'parent' => 'web',\n 'position' => ['after' => 'web_list'],\n // ...\n ],\n];\n```\n\n### Validation Commands\n```bash\n# Check for TCA modifications (should be in TCA/Overrides/)\ngrep -E \"addTCAcolumns|addToAllTCAtypes\" ext_tables.php && echo \"⚠️ WARNING: Move to TCA/Overrides/\"\n\n# Check for backend module registration (should be in Configuration/Backend/)\ngrep \"registerModule\" ext_tables.php && echo \"⚠️ WARNING: Move to Configuration/Backend/Modules.php\"\n```\n\n---\n\n## ext_tables.sql\n\n### Purpose\nDefines database tables and columns for extensions. Parsed when extensions are enabled.\n\n### SQL Syntax Requirements\n\n**Format:** Follow `mysqldump` utility output style\n- TYPO3 parses and converts to target DBMS (MySQL, MariaDB, PostgreSQL, SQLite)\n- Partial definitions allowed when extending existing tables\n\n### Table Naming Conventions\n```sql\n-- Extension tables with prefix\nCREATE TABLE tx_myextension_domain_model_table (\n field_name varchar(255) DEFAULT '' NOT NULL,\n);\n\n-- Extending core tables\nCREATE TABLE pages (\n tx_myextension_field int(11) DEFAULT '0' NOT NULL,\n);\n```\n\n### Auto-Generated Columns\nIf TCA exists, TYPO3 automatically creates:\n- `uid` with PRIMARY KEY\n- `pid` (unsigned) with default index `parent`\n- System fields based on TCA `ctrl` properties\n\n### New in v13: Empty Table Definitions\n```sql\n-- Valid when TCA enriches fields\nCREATE TABLE tx_myextension_table (\n);\n```\n\n### v13.4 CHAR/BINARY Handling\n\n**WARNING:** Fixed-length types now properly flagged\n- Use only with ensured fixed-length values (hash identifiers)\n- **Avoid with Extbase ORM** (cannot ensure fixed-length in queries)\n- Test extensively across database platforms\n\n**Best Practice:**\n```sql\n✅ VARCHAR(255) -- Variable length (preferred)\n⚠️ CHAR(32) -- Fixed length (use cautiously)\n✅ VARBINARY(255) -- Variable binary (preferred)\n⚠️ BINARY(16) -- Fixed binary (use cautiously)\n```\n\n### Validation Commands\n```bash\n# Check table naming\ngrep \"CREATE TABLE\" ext_tables.sql | grep -E \"tx_[a-z_]+\" && echo \"✅ Proper naming\"\n\n# Check for CHAR usage (potential issue)\ngrep -E \"CHAR\\([0-9]+\\)\" ext_tables.sql && echo \"⚠️ WARNING: CHAR type found - verify fixed-length\"\n\n# Validate syntax\nphp -r \"file_get_contents('ext_tables.sql');\" && echo \"✅ File readable\"\n```\n\n---\n\n## ext_tables_static+adt.sql\n\n### Purpose\nStores static SQL INSERT statements for pre-populated data.\n\n### Critical Restrictions\n\n**❌ ONLY INSERT statements allowed**\n- No CREATE TABLE\n- No ALTER TABLE\n- No UPDATE/DELETE\n\n**⚠️ Warning:** \"Static data is not meant to be extended by other extensions. On re-import all extended fields and data is lost.\"\n\n### When to Use\n- Initial data required during installation\n- Lookup tables, predefined categories\n- Default configuration data\n\n### Re-import Behavior\n- Data truncated and reimported when file contents change\n- Executed via:\n - `bin/typo3 extension:setup`\n - Admin Tools > Extensions reload\n\n### Generation Command\n```bash\nmysqldump --user=[user] --password [database] [tablename] > ./ext_tables_static+adt.sql\n```\n\n### Validation Commands\n```bash\n# Check file exists\n[ -f \"ext_tables_static+adt.sql\" ] && echo \"✅ Static data file present\"\n\n# Verify only INSERT statements\ngrep -v \"^INSERT\" ext_tables_static+adt.sql | grep -E \"^(CREATE|ALTER|UPDATE|DELETE)\" && echo \"❌ CRITICAL: Only INSERT allowed\"\n\n# Check corresponding table definition exists\ngrep \"CREATE TABLE\" ext_tables.sql && echo \"✅ Table definitions present\"\n```\n\n---\n\n## ext_conf_template.txt\n\n### Purpose\nDefines extension configuration options in Admin Tools > Settings module.\n\n### Syntax Format\n```\n# cat=Category; type=fieldtype; label=LLL:EXT:key/path.xlf:label\noptionName = defaultValue\n```\n\n### Field Types\n\n| Type | Purpose | Example |\n|------|---------|---------\n| `boolean` | Checkbox | `type=boolean` |\n| `string` | Text field | `type=string` |\n| `int` / `integer` | Whole number | `type=int` |\n| `int+` | Positive integers | `type=int+` |\n| `color` | Color picker | `type=color` |\n| `options` | Select dropdown | `type=options[Val1=1,Val2=2]` |\n| `user` | Custom function | `type=user[Vendor\\Class->method]` |\n| `small` | Compact text field | `type=small` |\n| `wrap` | Wrapper field | `type=wrap` |\n| `offset` | Offset value | `type=offset` |\n\n### Options Syntax\n```\n# cat=basic; type=options[Option 1=value1,Option 2=value2]; label=Select Option\nvariable = value1\n```\n\n### User Function Syntax\n```\n# cat=advanced; type=user[Vendor\\Extension\\Class->methodName]; label=Custom Field\nvariable = 1\n```\n\n### Nested Structure\n```\ndirectories {\n # cat=paths; type=string; label=Temp directory\n tmp = /tmp\n\n # cat=paths; type=string; label=Upload directory\n uploads = /uploads\n}\n```\n\n**Access:** `$config['directories']['tmp']`\n\n### Localization\n```\n# Use LLL references for multi-language support\n# cat=basic; type=string; label=LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:config.title\ntitle = Default Title\n```\n\n### Validation Commands\n```bash\n# Check file exists\n[ -f \"ext_conf_template.txt\" ] && echo \"✅ Configuration template present\"\n\n# Check syntax format\ngrep -E \"^#.*cat=.*type=.*label=\" ext_conf_template.txt && echo \"✅ Valid syntax found\"\n\n# Check for localization\ngrep \"LLL:EXT:\" ext_conf_template.txt && echo \"✅ Uses localized labels\"\n\n# Validate field types\ngrep -E \"type=(boolean|string|int|int\\+|color|options|user|small|wrap|offset)\" ext_conf_template.txt && echo \"✅ Valid field types\"\n```\n\n### Accessing Configuration in Code\n```php\nuse TYPO3\\CMS\\Core\\Configuration\\ExtensionConfiguration;\n\npublic function __construct(\n private readonly ExtensionConfiguration $extensionConfiguration\n) {}\n\n// Get all configuration\n$config = $this->extensionConfiguration->get('extension_key');\n\n// Get specific value\n$value = $this->extensionConfiguration->get('extension_key', 'optionName');\n```\n\n---\n\n## Validation Checklist\n\n### ext_localconf.php\n- [ ] Has `declare(strict_types=1)` at top\n- [ ] Has `defined('TYPO3') or die();` guard\n- [ ] No function/class definitions\n- [ ] **NOT** using deprecated `addUserTSConfig()`\n- [ ] **NOT** adding page TSconfig (use Configuration/page.tsconfig)\n\n### ext_tables.php\n- [ ] No TCA definitions (use Configuration/TCA/)\n- [ ] No TCA overrides (use Configuration/TCA/Overrides/)\n- [ ] No backend module registration (use Configuration/Backend/)\n- [ ] Only contains appropriate v13 use cases\n\n### ext_tables.sql\n- [ ] Follows mysqldump syntax\n- [ ] Tables prefixed with `tx_\u003cextensionkey>_`\n- [ ] Uses VARCHAR/VARBINARY (not CHAR/BINARY unless necessary)\n- [ ] Empty table definitions if TCA provides fields\n\n### ext_tables_static+adt.sql (if present)\n- [ ] **ONLY** INSERT statements (no CREATE/ALTER)\n- [ ] Corresponding table structure in ext_tables.sql\n- [ ] Static data is truly static (not extended by other extensions)\n\n### ext_conf_template.txt (if present)\n- [ ] Syntax: `# cat=; type=; label=`\n- [ ] Valid field types used\n- [ ] Localized labels with LLL: references\n- [ ] Proper categorization\n- [ ] Sensible default values\n\n---\n\n## Common Violations and Fixes\n\n### ext_localconf.php: Using Deprecated addUserTSConfig\n\n❌ Before:\n```php\n\\TYPO3\\CMS\\Core\\Utility\\ExtensionManagementUtility::addUserTSConfig('\n options.pageTree.showPageIdWithTitle = 1\n');\n```\n\n✅ After:\n```\n// Create Configuration/user.tsconfig\noptions.pageTree.showPageIdWithTitle = 1\n```\n\n### ext_tables.php: Backend Module in ext_tables.php\n\n❌ Before (ext_tables.php):\n```php\nExtensionUtility::registerModule('MyExt', 'web', 'mymodule', ...);\n```\n\n✅ After (Configuration/Backend/Modules.php):\n```php\nreturn [\n 'web_myext_mymodule' => [\n 'parent' => 'web',\n 'position' => ['after' => 'web_list'],\n 'access' => 'user',\n 'path' => '/module/web/myext',\n 'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',\n 'extensionName' => 'MyExt',\n 'controllerActions' => [\n \\Vendor\\MyExt\\Controller\\ModuleController::class => ['list', 'detail'],\n ],\n ],\n];\n```\n\n### ext_tables.sql: Using CHAR Inappropriately\n\n❌ Before:\n```sql\nCREATE TABLE tx_myext_table (\n name CHAR(255) DEFAULT '' NOT NULL, -- Variable content!\n);\n```\n\n✅ After:\n```sql\nCREATE TABLE tx_myext_table (\n name VARCHAR(255) DEFAULT '' NOT NULL, -- Use VARCHAR\n);\n```\n\n### ext_tables_static+adt.sql: Including CREATE Statements\n\n❌ Before:\n```sql\nCREATE TABLE tx_myext_categories (\n uid int(11) NOT NULL auto_increment,\n title varchar(255) DEFAULT '' NOT NULL,\n PRIMARY KEY (uid)\n);\nINSERT INTO tx_myext_categories VALUES (1, 'Category 1');\n```\n\n✅ After:\n```sql\n-- Move CREATE to ext_tables.sql\n-- Only INSERT in ext_tables_static+adt.sql\nINSERT INTO tx_myext_categories (uid, title) VALUES (1, 'Category 1');\nINSERT INTO tx_myext_categories (uid, title) VALUES (2, 'Category 2');\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":11599,"content_sha256":"14856d9c803ca69d8c9628c86297e88b19b48dc12d409d7aae8a59b5956c6fd6"},{"filename":"references/extension-architecture.md","content":"# TYPO3 Extension Architecture Standards\n\n**Source:** TYPO3 Core API Reference - Extension Architecture\n**Purpose:** File structure, directory hierarchy, and required files for TYPO3 extensions\n\n## Required Files\n\n### Essential Files\n\n**composer.json**\n- REQUIRED for all extensions\n- Defines package metadata, dependencies, PSR-4 autoloading\n- Example:\n```json\n{\n \"name\": \"vendor/extension-key\",\n \"type\": \"typo3-cms-extension\",\n \"require\": {\n \"typo3/cms-core\": \"^12.4 || ^13.0\"\n },\n \"autoload\": {\n \"psr-4\": {\n \"Vendor\\\\ExtensionKey\\\\\": \"Classes/\"\n }\n }\n}\n```\n\n**ext_emconf.php**\n- REQUIRED for TER (TYPO3 Extension Repository) publication\n- Contains extension metadata\n- Example:\n```php\n\u003c?php\n$EM_CONF[$_EXTKEY] = [\n 'title' => 'Extension Title',\n 'description' => 'Extension description',\n 'category' => 'fe',\n 'author' => 'Author Name',\n 'author_email' => '[email protected]',\n 'state' => 'stable',\n 'version' => '1.0.0',\n 'constraints' => [\n 'depends' => [\n 'typo3' => '12.4.0-13.9.99',\n ],\n ],\n];\n```\n\n**Documentation/Index.rst**\n- REQUIRED for docs.typo3.org publication\n- Main documentation entry point\n- Must follow reStructuredText format\n\n**Documentation/guides.xml**\n- REQUIRED for docs.typo3.org publication (modern PHP-based rendering)\n- Contains documentation project settings\n- Example:\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003cguides xmlns=\"https://www.phpdoc.org/guides\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"https://www.phpdoc.org/guides vendor/phpdocumentor/guides-cli/resources/schema/guides.xsd\"\n theme=\"typo3docs\">\n \u003cproject title=\"Extension Name\" version=\"1.0\" release=\"1.0.0\" copyright=\"since 2024\"/>\n \u003cextension class=\"\\T3Docs\\Typo3DocsTheme\\DependencyInjection\\Typo3DocsThemeExtension\"\n project-home=\"https://github.com/vendor/extension\"\n project-repository=\"https://github.com/vendor/extension\"/>\n\u003c/guides>\n```\n\n> **Note:** `Settings.cfg` is legacy (Sphinx-based) and should be migrated to `guides.xml`.\n\n## Directory Structure\n\n### Core Directories\n\n**Classes/**\n- Contains all PHP classes\n- MUST follow PSR-4 autoloading structure\n- Namespace: `\\VendorName\\ExtensionKey\\`\n- Common subdirectories:\n - `Classes/Controller/` - Extbase/backend controllers\n - `Classes/Domain/Model/` - Domain models\n - `Classes/Domain/Repository/` - Repositories\n - `Classes/Service/` - Service classes\n - `Classes/Utility/` - Utility classes\n - `Classes/ViewHelper/` - Fluid ViewHelpers\n - `Classes/EventListener/` - PSR-14 event listeners\n\n**Configuration/**\n- Contains all configuration files\n- Required subdirectories:\n - `Configuration/TCA/` - Table Configuration Array definitions\n - `Configuration/Backend/` - Backend module configuration\n - `Configuration/TypoScript/` - TypoScript configuration\n - `Configuration/Sets/` - Configuration sets (TYPO3 v13+)\n- Optional files:\n - `Configuration/Services.yaml` - Dependency injection configuration\n - `Configuration/TsConfig/` - Page/User TSconfig\n - `Configuration/RequestMiddlewares.php` - PSR-15 middlewares\n\n**Resources/**\n- Contains all frontend/backend resources\n- Structure:\n - `Resources/Private/` - Non-public files\n - `Resources/Private/Templates/` - Fluid templates\n - `Resources/Private/Partials/` - Fluid partials\n - `Resources/Private/Layouts/` - Fluid layouts\n - `Resources/Private/Language/` - Translation files (XLIFF)\n - `Resources/Public/` - Publicly accessible files\n - `Resources/Public/Css/` - Stylesheets\n - `Resources/Public/JavaScript/` - JavaScript files\n - `Resources/Public/Icons/` - Extension icons\n - `Resources/Public/Images/` - Images\n\n**Tests/**\n- Contains PHP test files\n- Structure:\n - `Tests/Unit/` - PHPUnit unit tests\n - `Tests/Functional/` - PHPUnit functional tests\n- MUST mirror `Classes/` structure\n\n**Build/tests/playwright/** (E2E Testing)\n- Contains Playwright E2E and accessibility tests\n- Structure:\n - `e2e/` - End-to-end tests (`.spec.ts`)\n - `accessibility/` - Accessibility tests with axe-core\n - `fixtures/` - Page Object Models\n - `helper/` - Authentication setup\n- Requires Node.js ≥22.18, `@playwright/test`, `@axe-core/playwright`\n\n**Documentation/**\n- Contains RST documentation\n- MUST include `Index.rst` and `guides.xml`\n- Common structure:\n - `Documentation/Introduction/`\n - `Documentation/Installation/`\n - `Documentation/Configuration/`\n - `Documentation/Developer/`\n - `Documentation/Editor/`\n\n## Reserved File Prefixes\n\nFiles with the `ext_*` prefix are reserved for special purposes:\n\n**ext_emconf.php**\n- Extension metadata (REQUIRED for TER)\n\n**ext_localconf.php**\n- Global configuration executed in both frontend and backend\n- Register hooks, event listeners, XCLASSes\n- Add plugins, content elements\n- Register services\n\n**ext_tables.php**\n- Backend-specific configuration\n- Register backend modules\n- Add TCA modifications\n- DEPRECATED in favor of dedicated configuration files\n\n**ext_tables.sql**\n- Database table definitions\n- Executed during extension installation\n- Contains CREATE TABLE and ALTER TABLE statements\n\n**ext_conf_template.txt**\n- Extension configuration template\n- Defines settings available in Extension Configuration\n- TypoScript-like syntax\n\n## File Naming Conventions\n\n### PHP Classes\n- File name MUST match class name exactly\n- PSR-4 compliant\n- Example: `Classes/Controller/MyController.php` → `class MyController`\n\n### Database Tables\n- Pattern: `tx_\u003cextensionkeyprefix>_\u003ctablename>`\n- Example: `tx_myext_domain_model_product`\n- Extension key must be converted to lowercase, underscores allowed\n\n### TCA Files\n- Pattern: `Configuration/TCA/\u003ctablename>.php`\n- Returns TCA array\n- Example: `Configuration/TCA/tx_myext_domain_model_product.php`\n\n### Language Files\n- Pattern: `Resources/Private/Language/\u003ccontext>.xlf`\n- XLIFF 1.2 format\n- Example: `Resources/Private/Language/locallang.xlf`\n\n## Architecture Best Practices\n\n### PSR-4 Autoloading\n- All classes in `Classes/` directory\n- Namespace structure MUST match directory structure\n- Example:\n - Class: `Vendor\\ExtensionKey\\Domain\\Model\\Product`\n - File: `Classes/Domain/Model/Product.php`\n\n### Dependency Injection\n- Use constructor injection for dependencies\n- Register services in `Configuration/Services.yaml`\n- Example:\n```yaml\nservices:\n _defaults:\n autowire: true\n autoconfigure: true\n public: false\n\n Vendor\\ExtensionKey\\:\n resource: '../Classes/*'\n```\n\n### Configuration Files\n- Separate concerns into dedicated configuration files\n- Use `Configuration/Backend/` for backend modules (not ext_tables.php)\n- Use `Configuration/TCA/` for table definitions\n- Use `Configuration/TypoScript/` for TypoScript\n\n### Testing Structure\n- Mirror `Classes/` structure in `Tests/Unit/` and `Tests/Functional/`\n- Example:\n - Class: `Classes/Service/CalculationService.php`\n - Unit Test: `Tests/Unit/Service/CalculationServiceTest.php`\n - Functional Test: `Tests/Functional/Service/CalculationServiceTest.php`\n\n## Common Issues\n\n### ❌ Wrong: Mixed file types in root\n```\nmy_extension/\n├── MyController.php # WRONG: PHP in root\n├── config.yaml # WRONG: Config in root\n└── styles.css # WRONG: CSS in root\n```\n\n### ✅ Right: Proper directory structure\n```\nmy_extension/\n├── Classes/Controller/MyController.php\n├── Configuration/Services.yaml\n└── Resources/Public/Css/styles.css\n```\n\n### ❌ Wrong: Non-standard directory names\n```\nClasses/\n├── Controllers/ # WRONG: Plural\n├── Services/ # WRONG: Should be Service\n└── Helpers/ # WRONG: Use Utility\n```\n\n### ✅ Right: Standard TYPO3 directory names\n```\nClasses/\n├── Controller/ # Singular\n├── Service/ # Singular\n└── Utility/ # Standard naming\n```\n\n## Extension Key Naming\n\n- Lowercase letters and underscores only\n- Must start with a letter\n- 3-30 characters\n- Cannot start with `tx_`, `user_`, `pages`, `tt_`, `sys_`\n- Example: `my_extension`, `blog_example`, `news`\n\n## Conformance Checklist\n\n- [ ] composer.json present with correct structure\n- [ ] ext_emconf.php present with complete metadata\n- [ ] Documentation/Index.rst and Documentation/guides.xml present\n- [ ] Classes/ directory follows PSR-4 structure\n- [ ] Configuration/ subdirectories properly organized\n- [ ] Resources/ separated into Private/ and Public/\n- [ ] Tests/ mirror Classes/ structure\n- [ ] No PHP files in extension root (except ext_* files)\n- [ ] File naming follows conventions\n- [ ] Database table names use tx_\u003cextensionkey>_ prefix\n- [ ] Extension key follows naming rules\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":8763,"content_sha256":"436de0fc0693ae2661fd58c0a1c14c901cb952d0025e96ce3010208557c37737"},{"filename":"references/hooks-and-events.md","content":"# TYPO3 Hooks and PSR-14 Events\n\n**Source:** TYPO3 Core API Reference - Hooks, Events, and Signals\n**Purpose:** Understanding TYPO3 hook system, PSR-14 events, and migration strategies\n\n## SC_OPTIONS Hooks Status in TYPO3 13\n\n### ⚠️ Common Misconception\n\n**INCORRECT:** \"SC_OPTIONS hooks are deprecated in TYPO3 13\"\n\n**CORRECT:** SC_OPTIONS hooks are **NOT deprecated** in TYPO3 13. They remain the **official pattern** for specific use cases.\n\n### SC_OPTIONS Hooks That Are Still Official\n\nThe following SC_OPTIONS hooks remain the official TYPO3 13 pattern:\n\n#### DataHandler Hooks (Still Official)\n\n```php\n// Configuration/Services.yaml\nVendor\\Extension\\Database\\MyDataHandlerHook:\n public: true\n tags:\n - name: event.listener\n identifier: 'my-extension/datahandler-hook'\n method: 'processDatamap_postProcessFieldArray'\n```\n\n**Still Official in ext_localconf.php:**\n```php\n\u003c?php\n// TYPO3 13+ DataHandler hooks remain official pattern\n$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] =\n \\Vendor\\Extension\\Database\\MyDataHandlerHook::class;\n\n$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][] =\n \\Vendor\\Extension\\Database\\MyDataHandlerHook::class;\n```\n\n**Key DataHandler Hook Methods (TYPO3 13+):**\n- `processDatamap_preProcessFieldArray()` - Before field processing\n- `processDatamap_postProcessFieldArray()` - After field processing\n- `processDatamap_afterDatabaseOperations()` - After DB operations\n- `processCmdmap_preProcess()` - Before command processing\n- `processCmdmap_postProcess()` - After command processing\n- `processCmdmap_afterFinish()` - After all commands finished\n\n#### RTE Transformation Hooks (Still Official)\n\n```php\n\u003c?php\n// TYPO3 13+ RTE transformation hooks remain official pattern\n$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['transformation'][] =\n \\Vendor\\Extension\\RteTransformation\\MyRteTransformationHook::class;\n```\n\n**Required Methods:**\n- `transform_rte()` - Transform content from database to RTE\n- `transform_db()` - Transform content from RTE to database\n\n### When to Use SC_OPTIONS vs PSR-14 Events\n\n| Scenario | Use SC_OPTIONS Hook | Use PSR-14 Event |\n|----------|-------------------|------------------|\n| DataHandler field processing | ✅ Yes (official) | ❌ No event available |\n| RTE content transformation | ✅ Yes (official) | ❌ No event available |\n| Backend user authentication | ❌ Migrated | ✅ Use PSR-14 events |\n| Frontend rendering | ❌ Migrated | ✅ Use PSR-14 events |\n| Page generation | ❌ Migrated | ✅ Use PSR-14 events |\n| Cache clearing | ❌ Migrated | ✅ Use PSR-14 events |\n\n## PSR-14 Event Listeners (Preferred)\n\nFor most scenarios, PSR-14 events are the modern TYPO3 13+ approach.\n\n### Event Listener Configuration\n\n```yaml\n# Configuration/Services.yaml\nservices:\n _defaults:\n autowire: true\n autoconfigure: true\n public: false\n\n Vendor\\Extension\\:\n resource: '../Classes/*'\n\n # PSR-14 Event Listener\n Vendor\\Extension\\EventListener\\MyEventListener:\n tags:\n - name: event.listener\n identifier: 'my-extension/my-event-listener'\n event: TYPO3\\CMS\\Core\\Authentication\\Event\\AfterUserLoggedInEvent\n```\n\n### Event Listener Implementation\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace Vendor\\Extension\\EventListener;\n\nuse TYPO3\\CMS\\Core\\Authentication\\Event\\AfterUserLoggedInEvent;\n\n/**\n * PSR-14 Event Listener for user login.\n */\nfinal class MyEventListener\n{\n public function __invoke(AfterUserLoggedInEvent $event): void\n {\n $user = $event->getUser();\n\n // Your logic here\n }\n}\n```\n\n### Common TYPO3 13 Events\n\n**Authentication Events:**\n- `AfterUserLoggedInEvent`\n- `BeforeUserLogoutEvent`\n- `AfterUserLoggedOutEvent`\n\n**Backend Events:**\n- `ModifyButtonBarEvent`\n- `ModifyDatabaseQueryForContentEvent`\n- `BeforePagePreviewUriGeneratedEvent`\n\n**DataHandler Events:**\n- `AfterDataInsertedEvent`\n- `AfterDataUpdatedEvent`\n- `AfterRecordDeletedEvent`\n\n**Page Events:**\n- `AfterPageTreeItemsPreparedEvent`\n- `ModifyPageLayoutContentEvent`\n\n**Complete Reference:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html\n\n## Migration Strategy\n\n### Step 1: Identify Hook Type\n\n```php\n// Check if hook is in ext_localconf.php\n$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['...']['...'][]\n```\n\n### Step 2: Check Official Documentation\n\n- **DataHandler hooks:** Still official, keep using SC_OPTIONS\n- **RTE transformation:** Still official, keep using SC_OPTIONS\n- **Other hooks:** Check if PSR-14 event exists\n\n### Step 3: Migrate or Modernize\n\n**If PSR-14 event exists:**\n```php\n// OLD: ext_localconf.php\n$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'][]\n = \\Vendor\\Extension\\Hook\\MyHook::class;\n\n// NEW: Configuration/Services.yaml + EventListener class\nVendor\\Extension\\EventListener\\MyEventListener:\n tags:\n - name: event.listener\n identifier: 'my-extension/after-login'\n event: TYPO3\\CMS\\Core\\Authentication\\Event\\AfterUserLoggedInEvent\n```\n\n**If no PSR-14 event exists (DataHandler, RTE):**\n```php\n// KEEP: Still official in TYPO3 13+\n$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][]\n = \\Vendor\\Extension\\Database\\MyDataHandlerHook::class;\n\n// MODERNIZE: Add dependency injection\n// Configuration/Services.yaml\nVendor\\Extension\\Database\\MyDataHandlerHook:\n public: true\n arguments:\n $resourceFactory: '@TYPO3\\CMS\\Core\\Resource\\ResourceFactory'\n $context: '@TYPO3\\CMS\\Core\\Context\\Context'\n $logManager: '@TYPO3\\CMS\\Core\\Log\\LogManager'\n```\n\n## Best Practices\n\n### 1. Constructor Dependency Injection\n\nEven for SC_OPTIONS hooks, use constructor injection (TYPO3 13+):\n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\nnamespace Vendor\\Extension\\Database;\n\nuse TYPO3\\CMS\\Core\\Context\\Context;\nuse TYPO3\\CMS\\Core\\Log\\LogManager;\nuse TYPO3\\CMS\\Core\\Resource\\ResourceFactory;\n\n/**\n * DataHandler hook with dependency injection.\n */\nfinal class MyDataHandlerHook\n{\n public function __construct(\n private readonly ResourceFactory $resourceFactory,\n private readonly Context $context,\n private readonly LogManager $logManager,\n ) {}\n\n public function processDatamap_postProcessFieldArray(\n string $status,\n string $table,\n string $id,\n array &$fieldArray,\n \\TYPO3\\CMS\\Core\\DataHandling\\DataHandler &$dataHandler,\n ): void {\n // Use injected dependencies\n $file = $this->resourceFactory->getFileObject($fileId);\n }\n}\n```\n\n### 2. Avoid GeneralUtility::makeInstance()\n\n```php\n// ❌ BAD: Using makeInstance (legacy pattern)\n$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);\n\n// ✅ GOOD: Constructor injection (TYPO3 13+ pattern)\npublic function __construct(\n private readonly ResourceFactory $resourceFactory,\n) {}\n```\n\n### 3. Configure Services Explicitly\n\n```yaml\n# Configuration/Services.yaml\nservices:\n Vendor\\Extension\\Database\\MyDataHandlerHook:\n public: true # Required for SC_OPTIONS hooks\n arguments:\n $resourceFactory: '@TYPO3\\CMS\\Core\\Resource\\ResourceFactory'\n $context: '@TYPO3\\CMS\\Core\\Context\\Context'\n $logManager: '@TYPO3\\CMS\\Core\\Log\\LogManager'\n```\n\n## Acceptable $GLOBALS Usage\n\nEven in TYPO3 13+, certain `$GLOBALS` usage is acceptable:\n\n### ✅ Acceptable $GLOBALS\n\n```php\n// TCA access (no alternative available)\n$GLOBALS['TCA']['tt_content']['columns']['bodytext']\n\n// Current request (framework-provided)\n$GLOBALS['TYPO3_REQUEST']\n\n// Backend user context (framework-provided)\n$GLOBALS['BE_USER']\n\n// Frontend user context (framework-provided)\n$GLOBALS['TSFE']\n```\n\n### ❌ Avoid $GLOBALS\n\n```php\n// Database connection (use ConnectionPool)\n$GLOBALS['TYPO3_DB']\n\n// Extension configuration (use ExtensionConfiguration)\n$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['my_ext']\n\n// Object instantiation (use dependency injection)\nGeneralUtility::makeInstance(SomeClass::class)\n```\n\n## Resources\n\n- [TYPO3 Hooks Documentation](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Hooks/Index.html)\n- [PSR-14 Events](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html)\n- [DataHandler Hooks](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Hooks/DataHandler/Index.html)\n- [Dependency Injection](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/Index.html)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":8608,"content_sha256":"84b4b535d3ac6f8a7908a576cda9f1a05b3658804fcd3130ebd5f33f7d20b59e"},{"filename":"references/localization-coverage.md","content":"# Localization Coverage & XLIFF Hygiene\n\nChecks and patterns for ensuring TYPO3 extension localization is complete, consistent, and not silently broken across languages.\n\n## The Problem\n\nExtensions ship with `Resources/Private/Language/locallang.xlf` (the English source) and per-language files `\u003clang>.locallang.xlf`. Over time:\n\n- New keys are added to English but not to translations → missing strings show the key ID in the UI\n- Translations become orphaned when English keys are removed but localized files keep them\n- Some languages have 100% coverage, others drift to 30% — often unnoticed until a user reports broken UI\n- Empty `\u003ctarget>` elements ship as \"translated\" but render as blank\n\nNone of this is caught by PHPUnit, Rector, or PHPStan. It shows up in production.\n\n## Required Layout\n\n```\nResources/Private/Language/\n├── locallang.xlf # English source\n├── de.locallang.xlf # German\n├── fr.locallang.xlf # French\n├── \u003clang>.locallang.xlf # One per supported language\n└── ...\n```\n\nLanguage codes follow the TYPO3 system-language convention (lowercase two-letter codes, e.g. `de.locallang.xlf`, `fr.locallang.xlf`). Region-specific variants should match what's declared in the TYPO3 site configuration (`config/sites/\u003cidentifier>/config.yaml`) — common forms are `pt_BR`/`zh_CN` (underscore + uppercase region) following the Locale format used in `languages[*].locale`. Check an extension's existing XLIFF filenames before adding new locales to keep naming consistent.\n\n## Coverage Check Script\n\nDrop-in script that reports coverage per language and fails CI if any language is below threshold:\n\n```bash\n#!/usr/bin/env bash\n# scripts/check-localization-coverage.sh\nset -euo pipefail\n\nLANG_DIR=\"Resources/Private/Language\"\nMIN_COVERAGE=\"${MIN_COVERAGE:-80}\" # override via env\nBASELINE=\"$LANG_DIR/locallang.xlf\"\nXLIFF_NS=\"urn:oasis:names:tc:xliff:document:1.2\"\n\nif [[ ! -f \"$BASELINE\" ]]; then\n echo \"No baseline $BASELINE — skipping coverage check\"\n exit 0\nfi\n\nSOURCE_KEYS=$(xmlstarlet sel -N x=\"$XLIFF_NS\" \\\n -t -v 'count(//x:trans-unit)' \"$BASELINE\")\n\nif [[ -z \"$SOURCE_KEYS\" || \"$SOURCE_KEYS\" -eq 0 ]]; then\n echo \"Baseline $BASELINE has no trans-units — skipping\" >&2\n exit 0\nfi\n\necho \"Baseline keys: $SOURCE_KEYS\"\n\n# Collect baseline IDs once for accurate orphan detection\nBASELINE_IDS=$(xmlstarlet sel -N x=\"$XLIFF_NS\" \\\n -t -m '//x:trans-unit' -v '@id' -n \"$BASELINE\" | sort -u)\n\nFAIL=0\nshopt -s nullglob\nLANG_FILES=(\"$LANG_DIR\"/*.locallang.xlf)\nshopt -u nullglob\n\nif [[ ${#LANG_FILES[@]} -eq 0 ]]; then\n echo \"No localized files matching $LANG_DIR/*.locallang.xlf\"\n exit 0\nfi\n\nfor f in \"${LANG_FILES[@]}\"; do\n [[ \"$f\" == \"$BASELINE\" ]] && continue\n LANG=$(basename \"$f\" | cut -d. -f1)\n\n TRANSLATED=$(xmlstarlet sel -N x=\"$XLIFF_NS\" \\\n -t -v 'count(//x:trans-unit[x:target and normalize-space(x:target)!=\"\"])' \"$f\")\n EMPTY=$(xmlstarlet sel -N x=\"$XLIFF_NS\" \\\n -t -v 'count(//x:trans-unit[x:target and normalize-space(x:target)=\"\"])' \"$f\")\n\n # Accurate orphan count: IDs present here but not in baseline\n LANG_IDS=$(xmlstarlet sel -N x=\"$XLIFF_NS\" \\\n -t -m '//x:trans-unit' -v '@id' -n \"$f\" | sort -u)\n ORPHANED=$(comm -23 \u003c(echo \"$LANG_IDS\") \u003c(echo \"$BASELINE_IDS\") | wc -l)\n\n PCT=$((TRANSLATED * 100 / SOURCE_KEYS))\n printf '%-6s %4d/%4d (%d%%) translated, %d empty, %d orphaned\\n' \\\n \"$LANG\" \"$TRANSLATED\" \"$SOURCE_KEYS\" \"$PCT\" \"$EMPTY\" \"$ORPHANED\"\n\n if (( PCT \u003c MIN_COVERAGE )); then\n echo \" FAIL: $LANG below $MIN_COVERAGE% threshold\" >&2\n FAIL=1\n fi\ndone\n\nexit $FAIL\n```\n\nWire into CI as a required status check. Adjust `MIN_COVERAGE` per extension (70% minimum, 90% for user-facing extensions).\n\n## Common XLIFF Violations\n\n### Empty targets shipped as translations\n\n```xml\n\u003c!-- WRONG — ships as blank in the UI -->\n\u003ctrans-unit id=\"welcome\">\n \u003csource>Welcome\u003c/source>\n \u003ctarget>\u003c/target>\n\u003c/trans-unit>\n\n\u003c!-- RIGHT — either translate or omit the trans-unit so TYPO3 falls back to source -->\n\u003ctrans-unit id=\"welcome\">\n \u003csource>Welcome\u003c/source>\n \u003ctarget>Bienvenue\u003c/target>\n\u003c/trans-unit>\n```\n\n### Orphaned keys\n\nA trans-unit in `de.locallang.xlf` whose `id` no longer exists in `locallang.xlf`. TYPO3 silently ignores them, but they bloat the file and create confusion.\n\nDetect: diff trans-unit IDs between source and localized file; flag localized-only IDs.\n\n### Missing translations that shouldn't fall back\n\nFor UI labels, missing = fallback to English = acceptable. For regulatory/legal text, missing = broken. Mark critical keys:\n\n```xml\n\u003ctrans-unit id=\"privacy_notice\" resname=\"critical\">\n \u003csource>Your data is processed...\u003c/source>\n \u003ctarget>...\u003c/target>\n\u003c/trans-unit>\n```\n\nAnd fail CI if any `resname=\"critical\"` trans-unit has an empty target.\n\n## Raw HTML vs Fluid/TYPO3 Form Elements\n\nTYPO3 backend modules and frontend forms should use Fluid ViewHelpers and TYPO3 form elements — not raw `\u003cinput>`, `\u003cform>`, `\u003cbutton>` markup. Reasons:\n\n- CSRF tokens are automatically injected by `\u003cf:form>` / FormEngine; raw forms have none.\n- `\u003cf:translate>` + XLIFF integrates with the localization pipeline above; raw `\u003clabel>Login\u003c/label>` is invisible to Crowdin.\n- Accessibility attributes (`aria-labelledby`, `aria-describedby`, `role`) are handled consistently by ViewHelpers; raw markup varies per author.\n- Backend styling (t3js-\\*, typo3-backend-module-\\* classes) is applied by FormEngine; raw forms look foreign.\n\n### Detection pattern\n\nGrep pattern for backend/frontend templates that should be upgraded:\n\n```bash\n# Raw \u003cinput> / \u003cform> / \u003cbutton> in Fluid templates — should use ViewHelpers.\n# Matches anywhere on a line (not anchored to start), so inline markup is caught too.\nrg -t html --glob 'Resources/Private/**/*.html' \\\n '\u003c(form|input|button|textarea|select)(\\s|>)' \\\n --line-number\n```\n\nAllowable exceptions:\n\n- `\u003cinput type=\"hidden\">` for routing params inside an `\u003cf:form>`\n- `\u003cbutton type=\"button\">` for pure JS interactions where no form submission happens\n- Raw elements inside a `\u003cf:format.raw>` wrapping trusted HTML\n\nEverything else should be a ViewHelper or FormEngine field.\n\n### Replacement table\n\n| Raw | Fluid / FormEngine equivalent |\n|-----|-------------------------------|\n| `\u003cform method=\"post\">` | `\u003cf:form action=\"...\" method=\"post\">` |\n| `\u003cinput type=\"text\" name=\"x\">` | `\u003cf:form.textfield name=\"x\" />` |\n| `\u003cinput type=\"submit\" value=\"Save\">` | `\u003cf:form.submit value=\"{f:translate(key: 'save')}\" />` |\n| `\u003cbutton>Save\u003c/button>` | `\u003cf:form.submit value=\"{f:translate(key: 'save')}\" />` |\n| `\u003ca href=\"?id=1\">Edit\u003c/a>` | `\u003cf:link.action action=\"edit\" arguments=\"{id: 1}\">...\u003c/f:link.action>` |\n| Hard-coded label text | `\u003cf:translate key=\"...\" />` |\n\n### Checkpoint\n\nAdd to the skill's `checkpoints.yaml` as a `regex` mechanical check scoped to `Resources/Private/**/*.html`, or as an LLM review with the rubric above.\n\n## XLIFF Encoding and Structure\n\n- Always UTF-8, no BOM\n- Namespace: `xmlns=\"urn:oasis:names:tc:xliff:document:1.2\"` (v1.2 for TYPO3 core compatibility; v2.x is not yet supported)\n- `\u003cfile source-language=\"en\" target-language=\"de\">` — must match the filename\n- One `\u003cfile>` element per XLIFF file (not multiple)\n- `\u003ctrans-unit id=\"...\">` must match the key used in `LLL:EXT:...` references\n\n### Lint\n\n```bash\nxmllint --noout Resources/Private/Language/*.xlf\n```\n\nNon-well-formed XML breaks TYPO3 silently (falls back to source language). Make this part of CI.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7476,"content_sha256":"a88af168c3b9cb1e9dddd6a209619e069fad5e835d3818f5d93ac9cf70d47b1e"},{"filename":"references/multi-version-dependency-compatibility.md","content":"# Multi-Version Dependency Compatibility\n\n> **Source**: Real-world conformance findings from extensions supporting multiple major versions of dependencies (e.g., `intervention/image ^3 || ^4`, `psr/http-message ^1 || ^2`)\n\n## Problem Statement\n\nWhen `composer.json` allows multiple major versions of a dependency (`^3 || ^4`), the extension code **must work with all allowed versions**. Direct usage of version-specific APIs is a conformance violation because:\n\n1. Users on the older version get runtime errors\n2. PHPStan may pass on one version but fail on another\n3. CI may only test one version, hiding breakage\n\n## Adapter/Interface Pattern (Required)\n\nWhen a dependency's API differs between major versions, use an **adapter interface** to abstract the differences.\n\n### Architecture\n\n```\nClasses/\n├── Adapter/\n│ ├── ImageProcessorInterface.php # Version-agnostic contract\n│ ├── ImageProcessorV3.php # Implementation for v3 API\n│ └── ImageProcessorV4.php # Implementation for v4 API\n```\n\n### Interface Definition\n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\Extension\\Adapter;\n\ninterface ImageProcessorInterface\n{\n public function resize(string $path, int $width, int $height): string;\n public function getImageSize(string $path): array;\n}\n```\n\n### Version-Specific Implementations\n\n```php\n// V3 implementation — uses v3-specific API\nfinal class ImageProcessorV3 implements ImageProcessorInterface\n{\n public function resize(string $path, int $width, int $height): string\n {\n $image = Image::make($path); // v3 API\n // ...\n }\n}\n\n// V4 implementation — uses v4-specific API\nfinal class ImageProcessorV4 implements ImageProcessorInterface\n{\n public function resize(string $path, int $width, int $height): string\n {\n $manager = new ImageManager(new Driver()); // v4 API\n $image = $manager->read($path);\n // ...\n }\n}\n```\n\n### Services.yaml Wiring\n\nThe interface **must** be wired in `Configuration/Services.yaml` to the correct implementation:\n\n```yaml\nservices:\n _defaults:\n autowire: true\n autoconfigure: true\n\n Vendor\\Extension\\Adapter\\ImageProcessorInterface:\n factory: ['@Vendor\\Extension\\Adapter\\ImageProcessorFactory', 'create']\n\n Vendor\\Extension\\Adapter\\ImageProcessorFactory:\n public: true\n```\n\nOr with explicit alias:\n\n```yaml\nservices:\n Vendor\\Extension\\Adapter\\ImageProcessorInterface:\n alias: Vendor\\Extension\\Adapter\\ImageProcessorV4\n```\n\n## Direct Version-Specific Usage (Violation)\n\n```php\n// ❌ WRONG: Direct usage of v4-only API when composer.json allows ^3 || ^4\nuse Intervention\\Image\\ImageManager;\nuse Intervention\\Image\\Drivers\\Gd\\Driver;\n\n$manager = new ImageManager(new Driver()); // Fails on v3\n$image = $manager->read($path); // v4-only method\n```\n\n```php\n// ❌ WRONG: Type-hinting concrete class instead of adapter interface\npublic function __construct(\n private readonly ImageProcessorV4 $processor // Should be ImageProcessorInterface\n) {}\n```\n\n## PHPStan Compatibility\n\n### Version-Specific `@phpstan-ignore` Tags\n\nWhen using `method_exists()` for version detection, PHPStan may require ignore tags. These tags **must not be version-specific** — they must work across all supported versions:\n\n```php\n// ❌ WRONG: @phpstan-ignore tag that only works on one version\n/** @phpstan-ignore method.notFound */\n$result = $object->v4OnlyMethod(); // Errors on v3 PHPStan analysis\n\n// ✅ RIGHT: Use adapter pattern instead, no ignore tags needed\n$result = $this->adapter->process();\n```\n\n### `method_exists()` with Typed Parameters\n\nPHPStan narrows parameter types after `method_exists()` calls. When checking method existence for version detection, use `object` type to prevent narrowing issues:\n\n```php\n// ❌ WRONG: Typed parameter gets narrowed by method_exists()\npublic function process(ImageManager $manager): void\n{\n if (method_exists($manager, 'read')) {\n // PHPStan narrows $manager type — may conflict across versions\n $manager->read($path);\n }\n}\n\n// ✅ RIGHT: Use object type for version detection bridges\npublic function process(object $manager): void\n{\n if (method_exists($manager, 'read')) {\n $manager->read($path); // No type narrowing conflict\n }\n}\n\n// ✅ BEST: Use adapter pattern, no method_exists() needed\npublic function process(ImageProcessorInterface $processor): void\n{\n $processor->resize($path); // Clean, testable, version-agnostic\n}\n```\n\n### PHPStan Must Pass All Versions\n\nPHPStan analysis **must pass against each supported major version** of the dependency. CI should test PHPStan with each version:\n\n```yaml\n# CI matrix example\nstrategy:\n matrix:\n include:\n - dependency-version: '^3'\n phpstan-config: 'Build/phpstan/phpstan.neon'\n - dependency-version: '^4'\n phpstan-config: 'Build/phpstan/phpstan.neon'\n```\n\n## Services.yaml / DI Wiring Rules\n\n### Interface Must Be Wired\n\nWhen using the adapter pattern, the interface **must** be wired in `Services.yaml`:\n\n```yaml\n# ❌ WRONG: Interface not wired — DI container cannot resolve it\nservices:\n _defaults:\n autowire: true\n\n Vendor\\Extension\\:\n resource: '../Classes/*'\n\n# ✅ RIGHT: Interface explicitly wired\nservices:\n _defaults:\n autowire: true\n\n Vendor\\Extension\\:\n resource: '../Classes/*'\n\n Vendor\\Extension\\Adapter\\ImageProcessorInterface:\n alias: Vendor\\Extension\\Adapter\\ImageProcessorV4\n```\n\n### Constructor Type-Hints\n\nConstructors **must** type-hint the interface, not the concrete class, when an adapter interface exists:\n\n```php\n// ❌ WRONG: Concrete class type-hint bypasses adapter pattern\npublic function __construct(\n private readonly ImageProcessorV4 $processor\n) {}\n\n// ✅ RIGHT: Interface type-hint enables version switching\npublic function __construct(\n private readonly ImageProcessorInterface $processor\n) {}\n```\n\n## Conformance Checklist\n\n- [ ] All dependencies with `||` multi-major constraints have adapter interfaces\n- [ ] No direct usage of version-specific APIs outside adapter classes\n- [ ] `Services.yaml` wires adapter interfaces to concrete implementations\n- [ ] Constructors type-hint interfaces, not concrete adapter classes\n- [ ] No `@phpstan-ignore` tags that are version-specific\n- [ ] PHPStan passes against each supported major version\n- [ ] CI matrix tests with each major version of multi-version dependencies\n- [ ] `method_exists()` version detection uses `object` type, not concrete types\n\n## Scoring Impact\n\n| Finding | Severity | Points |\n|---------|----------|--------|\n| Direct version-specific API usage with multi-version constraint | error | -10 |\n| Missing adapter interface for API-divergent dependency | error | -10 |\n| Interface not wired in Services.yaml | error | -5 |\n| Concrete class type-hint instead of interface | warning | -3 |\n| Version-specific `@phpstan-ignore` tag | warning | -3 |\n| PHPStan not tested against all dependency versions | warning | -3 |\n| Adapter pattern properly implemented | excellence | +3 |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7060,"content_sha256":"e15ae52e9642443b6fc125953110c48bf86c84da904bc27197e1f9cd7208714e"},{"filename":"references/php-architecture.md","content":"# TYPO3 PHP Architecture Standards\n\n**Source:** TYPO3 Core API Reference - PHP Architecture\n**Purpose:** Dependency injection, services, events, Extbase, middleware patterns\n\n## Dependency Injection\n\nTYPO3 uses **Symfony's Dependency Injection Container** for service management.\n\n### Constructor Injection (Preferred)\n\n```php\n// ✅ Right: Constructor injection with readonly properties\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Controller;\n\nuse Psr\\Http\\Message\\ResponseInterface;\nuse TYPO3\\CMS\\Extbase\\Mvc\\Controller\\ActionController;\nuse Vendor\\ExtensionKey\\Domain\\Repository\\UserRepository;\n\nfinal class UserController extends ActionController\n{\n public function __construct(\n private readonly UserRepository $userRepository\n ) {}\n\n public function listAction(): ResponseInterface\n {\n $users = $this->userRepository->findAll();\n $this->view->assign('users', $users);\n return $this->htmlResponse();\n }\n}\n```\n\n### Method Injection (inject* Methods)\n\n```php\n// ✅ Right: Method injection for abstract classes\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Controller;\n\nuse TYPO3\\CMS\\Extbase\\Mvc\\Controller\\ActionController;\nuse Vendor\\ExtensionKey\\Domain\\Repository\\UserRepository;\n\nclass UserController extends ActionController\n{\n protected ?UserRepository $userRepository = null;\n\n public function injectUserRepository(UserRepository $userRepository): void\n {\n $this->userRepository = $userRepository;\n }\n}\n```\n\n**When to Use Method Injection:**\n- Extending abstract core classes (ActionController, AbstractValidator)\n- Avoiding breaking changes when base class constructor changes\n- Optional dependencies\n\n**When to Use Constructor Injection:**\n- All new code (preferred)\n- Required dependencies\n- Better testability\n\n### Interface Injection\n\n```php\n// ✅ Right: Depend on interfaces, not implementations\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Controller;\n\nuse Psr\\Http\\Message\\ResponseInterface;\nuse TYPO3\\CMS\\Extbase\\Mvc\\Controller\\ActionController;\nuse Vendor\\ExtensionKey\\Domain\\Repository\\UserRepositoryInterface;\n\nfinal class UserController extends ActionController\n{\n public function __construct(\n private readonly UserRepositoryInterface $userRepository\n ) {}\n}\n```\n\n## Service Configuration\n\n### Configuration/Services.yaml\n\n```yaml\n# ✅ Right: Proper service configuration\nservices:\n _defaults:\n autowire: true\n autoconfigure: true\n public: false\n\n # Auto-register all classes\n Vendor\\ExtensionKey\\:\n resource: '../Classes/*'\n\n # Explicit service configuration\n Vendor\\ExtensionKey\\Service\\MyService:\n arguments:\n $configValue: '%env(MY_CONFIG_VALUE)%'\n\n # Factory pattern for Connection\n Vendor\\ExtensionKey\\Domain\\Repository\\MyTableRepository:\n factory: ['@TYPO3\\CMS\\Core\\Database\\ConnectionPool', 'getConnectionForTable']\n arguments:\n - 'my_table'\n\n # Interface binding\n Vendor\\ExtensionKey\\Domain\\Repository\\UserRepositoryInterface:\n class: Vendor\\ExtensionKey\\Domain\\Repository\\UserRepository\n```\n\n### Autowire Attribute (TYPO3 v12+)\n\n```php\n// ✅ Right: Inject configuration using Autowire attribute\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Service;\n\nuse TYPO3\\CMS\\Core\\DependencyInjection\\Attribute\\Autowire;\n\nfinal class MyService\n{\n public function __construct(\n #[Autowire(expression: 'service(\"configuration.extension\").get(\"my_extension\", \"mySetting\")')]\n private readonly string $myExtensionSetting\n ) {}\n}\n```\n\n## PSR-14 Event Dispatcher\n\n### Defining Custom Events\n\n```php\n// ✅ Right: Immutable event class with getters/setters\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Event;\n\nfinal class BeforeUserCreatedEvent\n{\n public function __construct(\n private string $username,\n private string $email,\n private array $additionalData = []\n ) {}\n\n public function getUsername(): string\n {\n return $this->username;\n }\n\n public function getEmail(): string\n {\n return $this->email;\n }\n\n public function getAdditionalData(): array\n {\n return $this->additionalData;\n }\n\n public function setAdditionalData(array $additionalData): void\n {\n $this->additionalData = $additionalData;\n }\n}\n```\n\n### Dispatching Events\n\n```php\n// ✅ Right: Inject and dispatch events\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Service;\n\nuse Psr\\EventDispatcher\\EventDispatcherInterface;\nuse Vendor\\ExtensionKey\\Event\\BeforeUserCreatedEvent;\n\nfinal class UserService\n{\n public function __construct(\n private readonly EventDispatcherInterface $eventDispatcher\n ) {}\n\n public function createUser(string $username, string $email): void\n {\n $event = new BeforeUserCreatedEvent($username, $email);\n $event = $this->eventDispatcher->dispatch($event);\n\n // Use potentially modified data from event\n $finalUsername = $event->getUsername();\n $finalEmail = $event->getEmail();\n\n // Create user with final data\n }\n}\n```\n\n### Event Listeners\n\n```php\n// ✅ Right: Event listener with AsEventListener attribute\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\EventListener;\n\nuse TYPO3\\CMS\\Core\\Attribute\\AsEventListener;\nuse Vendor\\ExtensionKey\\Event\\BeforeUserCreatedEvent;\n\n#[AsEventListener(\n identifier: 'vendor/extension-key/validate-user-creation',\n event: BeforeUserCreatedEvent::class\n)]\nfinal class ValidateUserCreationListener\n{\n public function __invoke(BeforeUserCreatedEvent $event): void\n {\n // Validate email format\n if (!filter_var($event->getEmail(), FILTER_VALIDATE_EMAIL)) {\n throw new \\InvalidArgumentException('Invalid email format');\n }\n\n // Add custom data\n $event->setAdditionalData([\n 'validated_at' => time(),\n 'validator' => 'ValidateUserCreationListener',\n ]);\n }\n}\n```\n\n### Event Listener Registration (Services.yaml)\n\n```yaml\n# Alternative: Register event listeners in Services.yaml\nservices:\n Vendor\\ExtensionKey\\EventListener\\ValidateUserCreationListener:\n tags:\n - name: event.listener\n identifier: 'vendor/extension-key/validate-user-creation'\n event: Vendor\\ExtensionKey\\Event\\BeforeUserCreatedEvent\n method: '__invoke'\n```\n\n### PSR-14 Event Class Standards (TYPO3 13+)\n\nModern event classes should follow these quality standards:\n\n```php\n// ✅ Right: Modern event class with final keyword and readonly properties\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Event;\n\nuse Psr\\Http\\Message\\ServerRequestInterface;\n\nfinal class NewsListActionEvent // ✅ Use 'final' keyword\n{\n public function __construct(\n private NewsController $newsController,\n private array $assignedValues,\n private readonly ServerRequestInterface $request // ✅ Use 'readonly' for immutable properties\n ) {}\n\n public function getNewsController(): NewsController\n {\n return $this->newsController;\n }\n\n public function getAssignedValues(): array\n {\n return $this->assignedValues;\n }\n\n public function setAssignedValues(array $assignedValues): void\n {\n $this->assignedValues = $assignedValues;\n }\n\n public function getRequest(): ServerRequestInterface\n {\n return $this->request; // Read-only, no setter\n }\n}\n```\n\n**Event Class Quality Checklist:**\n- [ ] Use `final` keyword (prevents inheritance, ensures immutability)\n- [ ] Use `readonly` for properties that should never change after construction\n- [ ] Provide getters for all properties\n- [ ] Provide setters ONLY for properties that should be modifiable\n- [ ] Type hint all properties and methods\n- [ ] Document the purpose and usage of the event\n\n**Why `final` for Events?**\n- Events are data carriers, not meant to be extended\n- Prevents unexpected behavior from inheritance\n- Makes event behavior predictable and testable\n- Follows modern PHP best practices\n\n**Why `readonly` for Properties?**\n- Some event data should never change (e.g., original request, user context)\n- Explicit immutability prevents accidental modifications\n- Clearly communicates intent to event listeners\n- Available in PHP 8.1+ (TYPO3 13 minimum is PHP 8.1)\n\n## TYPO3 13 Site Sets\n\n**Purpose:** Modern configuration distribution system replacing static TypoScript includes\n\n### Site Sets Structure\n\n```\nConfiguration/Sets/\n├── MyExtension/ # Base configuration set\n│ ├── config.yaml # Set metadata and dependencies\n│ ├── setup.typoscript # Frontend TypoScript\n│ ├── constants.typoscript\n│ └── settings.definitions.yaml # Setting definitions for extension configuration\n├── RecordLinks/ # Optional feature set\n│ ├── config.yaml\n│ └── setup.typoscript\n└── Bootstrap5/ # Frontend framework preset\n ├── config.yaml\n ├── setup.typoscript\n └── settings.yaml\n```\n\n### config.yaml Structure\n\n```yaml\n# ✅ Right: Proper Site Set configuration\nname: vendor/extension-key\nlabel: Extension Name Base Configuration\n\n# Dependencies on other sets\ndependencies:\n - typo3/fluid-styled-content\n - vendor/extension-key-styles\n\n# Load order priority (optional)\npriority: 50\n\n# Settings that can be overridden\nsettings:\n mySetting:\n value: 'default value'\n type: string\n label: 'My Setting Label'\n description: 'Description of what this setting does'\n```\n\n### settings.definitions.yaml\n\n```yaml\n# ✅ Right: Define extension settings with validation\nsettings:\n # Text input\n mySetting:\n type: string\n default: 'default value'\n label: 'LLL:EXT:extension_key/Resources/Private/Language/locallang.xlf:settings.mySetting'\n description: 'LLL:EXT:extension_key/Resources/Private/Language/locallang.xlf:settings.mySetting.description'\n\n # Boolean checkbox\n enableFeature:\n type: bool\n default: false\n label: 'Enable Feature'\n\n # Integer input\n itemsPerPage:\n type: int\n default: 10\n label: 'Items per page'\n validators:\n - name: NumberRange\n options:\n minimum: 1\n maximum: 100\n\n # Select dropdown\n layout:\n type: string\n default: 'default'\n label: 'Layout'\n enum:\n default: 'Default'\n compact: 'Compact'\n detailed: 'Detailed'\n```\n\n### Benefits of Site Sets\n\n1. **Modular Configuration**: Split configuration into focused, reusable sets\n2. **Dependency Management**: Declare dependencies on other sets\n3. **Override Capability**: Sites can override set settings without editing files\n4. **Type Safety**: Settings are validated with defined types\n5. **Better UX**: Settings UI auto-generated from definitions\n6. **Version Control**: Configuration changes tracked properly\n\n### Migration from Static TypoScript\n\n```php\n// ❌ Old: Static TypoScript includes (TYPO3 12 and earlier)\nConfiguration/TCA/Overrides/sys_template.php:\n\u003c?php\n\\TYPO3\\CMS\\Core\\Utility\\ExtensionManagementUtility::addStaticFile(\n 'extension_key',\n 'Configuration/TypoScript',\n 'Extension Name'\n);\n```\n\n```yaml\n# ✅ New: Site Sets (TYPO3 13+)\nConfiguration/Sets/ExtensionKey/config.yaml:\nname: vendor/extension-key\nlabel: Extension Name\n```\n\n**Site Sets Conformance Checklist:**\n- [ ] Configuration/Sets/ directory exists\n- [ ] At least one base set with config.yaml\n- [ ] settings.definitions.yaml defines all extension settings\n- [ ] Set names follow vendor/package naming convention\n- [ ] Dependencies declared in config.yaml\n- [ ] Labels use LLL: references for translations\n- [ ] Settings have appropriate type validation\n\n## Advanced Services.yaml Patterns\n\nBeyond basic service registration, modern TYPO3 extensions use advanced Services.yaml patterns.\n\n### Event Listeners\n\n```yaml\n# ✅ Right: Event listener registration\nservices:\n Vendor\\ExtensionKey\\EventListener\\HrefLangEventListener:\n tags:\n - name: event.listener\n identifier: 'ext-extension-key/modify-hreflang'\n event: TYPO3\\CMS\\Frontend\\Event\\ModifyHrefLangTagsEvent\n method: '__invoke'\n\n # Multiple listeners for same event\n Vendor\\ExtensionKey\\EventListener\\PageCacheListener:\n tags:\n - name: event.listener\n identifier: 'ext-extension-key/cache-before'\n event: TYPO3\\CMS\\Core\\Cache\\Event\\BeforePageCacheIdentifierIsHashedEvent\n - name: event.listener\n identifier: 'ext-extension-key/cache-after'\n event: TYPO3\\CMS\\Core\\Cache\\Event\\AfterPageCacheIdentifierIsHashedEvent\n```\n\n### Console Commands\n\n```yaml\n# ✅ Right: Console command registration\nservices:\n Vendor\\ExtensionKey\\Command\\ProxyClassRebuildCommand:\n tags:\n - name: 'console.command'\n command: 'extension:rebuildProxyClasses'\n description: 'Rebuild Extbase proxy classes'\n schedulable: false # Cannot be run via scheduler\n\n Vendor\\ExtensionKey\\Command\\CleanupCommand:\n tags:\n - name: 'console.command'\n command: 'extension:cleanup'\n description: 'Clean up old records'\n schedulable: true # Can be run via scheduler\n hidden: false # Visible in command list\n```\n\n### Data Processors\n\n```yaml\n# ✅ Right: Data processor registration for Fluid templates\nservices:\n Vendor\\ExtensionKey\\DataProcessing\\AddNewsToMenuProcessor:\n tags:\n - name: 'data.processor'\n identifier: 'add-news-to-menu'\n\n Vendor\\ExtensionKey\\DataProcessing\\CategoryProcessor:\n tags:\n - name: 'data.processor'\n identifier: 'category-processor'\n```\n\n### Cache Services\n\n```yaml\n# ✅ Right: Cache service configuration\nservices:\n cache.extension_custom:\n class: TYPO3\\CMS\\Core\\Cache\\Frontend\\VariableFrontend\n factory:\n - '@TYPO3\\CMS\\Core\\Cache\\CacheManager'\n - 'getCache'\n arguments:\n - 'extension_custom'\n```\n\n### Advanced Service Patterns\n\n```yaml\n# ✅ Right: Comprehensive Services.yaml with advanced patterns\nservices:\n _defaults:\n autowire: true\n autoconfigure: true\n public: false\n\n # Auto-register all classes\n Vendor\\ExtensionKey\\:\n resource: '../Classes/*'\n exclude:\n - '../Classes/Domain/Model/*' # Exclude Extbase models\n\n # Event Listeners\n Vendor\\ExtensionKey\\EventListener\\NewsListActionListener:\n tags:\n - name: event.listener\n identifier: 'ext-extension-key/news-list'\n event: Vendor\\ExtensionKey\\Event\\NewsListActionEvent\n\n # Console Commands\n Vendor\\ExtensionKey\\Command\\ImportCommand:\n tags:\n - name: 'console.command'\n command: 'news:import'\n description: 'Import news from external source'\n schedulable: true\n\n # Data Processors\n Vendor\\ExtensionKey\\DataProcessing\\MenuProcessor:\n tags:\n - name: 'data.processor'\n identifier: 'news-menu-processor'\n\n # Cache Factory\n cache.news_category:\n class: TYPO3\\CMS\\Core\\Cache\\Frontend\\VariableFrontend\n factory: ['@TYPO3\\CMS\\Core\\Cache\\CacheManager', 'getCache']\n arguments: ['news_category']\n\n # ViewHelper registration (if needed for testing)\n Vendor\\ExtensionKey\\ViewHelpers\\FormatViewHelper:\n public: true\n```\n\n### Factory Wiring\n\n```yaml\n# ✅ Right: Factory method to create a service via another service\nservices:\n cache.my_extension:\n class: TYPO3\\CMS\\Core\\Cache\\Frontend\\VariableFrontend\n factory: ['@TYPO3\\CMS\\Core\\Cache\\CacheManager', 'getCache']\n arguments: ['my_extension']\n\n Vendor\\ExtensionKey\\Service\\ConnectionService:\n factory: ['@TYPO3\\CMS\\Core\\Database\\ConnectionPool', 'getConnectionForTable']\n arguments: ['tx_myext_domain_model_item']\n```\n\nThe `factory` shorthand `['@ServiceClass', 'methodName']` calls `ServiceClass->methodName()` with the listed arguments to produce the service instance.\n\n### Interface Aliases\n\n```yaml\n# ✅ Right: Bind interface to concrete implementation\nservices:\n Vendor\\ExtensionKey\\Service\\ImageOptimizerInterface:\n alias: Vendor\\ExtensionKey\\Service\\DefaultImageOptimizer\n```\n\nThis allows constructor injection via the interface type-hint, resolved to the concrete class at runtime.\n\n### EventDispatcher Autowiring\n\nTYPO3 core provides `Psr\\EventDispatcher\\EventDispatcherInterface` via `#[AsAlias]` — **no Services.yaml entry is needed**. Simply type-hint the interface in your constructor:\n\n```php\n// ✅ Right: Just inject — no YAML registration required\npublic function __construct(\n private readonly EventDispatcherInterface $eventDispatcher,\n) {}\n```\n\n```yaml\n# ❌ Wrong: Unnecessary manual alias for EventDispatcher\nservices:\n Psr\\EventDispatcher\\EventDispatcherInterface:\n alias: TYPO3\\CMS\\Core\\EventDispatcher\\EventDispatcher\n```\n\n**Advanced Services.yaml Conformance Checklist:**\n- [ ] Event listeners registered with proper tags\n- [ ] Console commands tagged with schedulable flag\n- [ ] Data processors registered with unique identifiers\n- [ ] Cache services use factory pattern\n- [ ] ViewHelpers marked public if needed externally\n- [ ] Service tags include all required attributes (identifier, event, method)\n- [ ] Commands have meaningful names and descriptions\n- [ ] Factory wiring uses `['@ServiceClass', 'method']` shorthand\n- [ ] Interface aliases used for swappable implementations\n- [ ] No unnecessary YAML entries for services autowired by TYPO3 core (e.g., EventDispatcher)\n\n## PSR-15 Middleware\n\n### Middleware Structure\n\n```php\n// ✅ Right: PSR-15 middleware implementation\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Middleware;\n\nuse Psr\\Http\\Message\\ResponseFactoryInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Server\\MiddlewareInterface;\nuse Psr\\Http\\Server\\RequestHandlerInterface;\n\nfinal class StatusCheckMiddleware implements MiddlewareInterface\n{\n public function __construct(\n private readonly ResponseFactoryInterface $responseFactory\n ) {}\n\n public function process(\n ServerRequestInterface $request,\n RequestHandlerInterface $handler\n ): ResponseInterface {\n // Check for specific condition\n if (($request->getQueryParams()['status'] ?? null) === 'check') {\n $response = $this->responseFactory->createResponse(200, 'OK');\n $response->getBody()->write(json_encode([\n 'status' => 'ok',\n 'message' => 'System is healthy'\n ]));\n return $response->withHeader('Content-Type', 'application/json');\n }\n\n // Pass to next middleware\n return $handler->handle($request);\n }\n}\n```\n\n### Middleware Registration\n\n```php\n// Configuration/RequestMiddlewares.php\n\u003c?php\nreturn [\n 'frontend' => [\n 'vendor/extension-key/status-check' => [\n 'target' => \\Vendor\\ExtensionKey\\Middleware\\StatusCheckMiddleware::class,\n 'before' => [\n 'typo3/cms-frontend/page-resolver',\n ],\n 'after' => [\n 'typo3/cms-core/normalized-params-attribute',\n ],\n ],\n ],\n];\n```\n\n## Extbase Architecture\n\n### Domain Models\n\n```php\n// ✅ Right: Extbase domain model\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Domain\\Model;\n\nuse TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity;\n\nclass Product extends AbstractEntity\n{\n protected string $title = '';\n protected float $price = 0.0;\n protected bool $available = true;\n\n public function getTitle(): string\n {\n return $this->title;\n }\n\n public function setTitle(string $title): void\n {\n $this->title = $title;\n }\n\n public function getPrice(): float\n {\n return $this->price;\n }\n\n public function setPrice(float $price): void\n {\n $this->price = $price;\n }\n\n public function isAvailable(): bool\n {\n return $this->available;\n }\n\n public function setAvailable(bool $available): void\n {\n $this->available = $available;\n }\n}\n```\n\n### Repositories\n\n```php\n// ✅ Right: Extbase repository with dependency injection\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Domain\\Repository;\n\nuse TYPO3\\CMS\\Extbase\\Persistence\\Repository;\nuse Vendor\\ExtensionKey\\Domain\\Model\\Product;\n\nclass ProductRepository extends Repository\n{\n /**\n * Find products by price range\n *\n * @param float $minPrice\n * @param float $maxPrice\n * @return array\u003cProduct>\n */\n public function findByPriceRange(float $minPrice, float $maxPrice): array\n {\n $query = $this->createQuery();\n $query->matching(\n $query->logicalAnd(\n $query->greaterThanOrEqual('price', $minPrice),\n $query->lessThanOrEqual('price', $maxPrice)\n )\n );\n return $query->execute()->toArray();\n }\n}\n```\n\n### Controllers\n\n```php\n// ✅ Right: Extbase controller with dependency injection\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Controller;\n\nuse Psr\\Http\\Message\\ResponseInterface;\nuse TYPO3\\CMS\\Extbase\\Mvc\\Controller\\ActionController;\nuse Vendor\\ExtensionKey\\Domain\\Repository\\ProductRepository;\n\nfinal class ProductController extends ActionController\n{\n public function __construct(\n private readonly ProductRepository $productRepository\n ) {}\n\n public function listAction(): ResponseInterface\n {\n $products = $this->productRepository->findAll();\n $this->view->assign('products', $products);\n return $this->htmlResponse();\n }\n\n public function showAction(int $productId): ResponseInterface\n {\n $product = $this->productRepository->findByUid($productId);\n $this->view->assign('product', $product);\n return $this->htmlResponse();\n }\n}\n```\n\n### Validators\n\n```php\n// ✅ Right: Extbase validator with dependency injection\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Domain\\Validator;\n\nuse TYPO3\\CMS\\Extbase\\Validation\\Validator\\AbstractValidator;\nuse Vendor\\ExtensionKey\\Domain\\Repository\\ProductRepository;\n\nclass UniqueProductTitleValidator extends AbstractValidator\n{\n public function __construct(\n private readonly ProductRepository $productRepository\n ) {}\n\n protected function isValid(mixed $value): void\n {\n if (!is_string($value)) {\n $this->addError('Value must be a string', 1234567890);\n return;\n }\n\n $existingProduct = $this->productRepository->findOneByTitle($value);\n if ($existingProduct !== null) {\n $this->addError(\n 'Product with title \"%s\" already exists',\n 1234567891,\n [$value]\n );\n }\n }\n}\n```\n\n## Common Patterns\n\n## Dependency Injection Patterns\n\n### Service Instantiation\n\nNever use `GeneralUtility::makeInstance()` for services. Always use constructor dependency injection.\n\n```php\n// ✅ Right: Constructor DI\nfinal class ImportService\n{\n public function __construct(\n private readonly ProductRepository $productRepository,\n private readonly PersistenceManagerInterface $persistenceManager,\n ) {}\n}\n\n// ❌ Wrong: Service locator\nfinal class ImportService\n{\n public function doImport(): void\n {\n $repo = GeneralUtility::makeInstance(ProductRepository::class);\n }\n}\n```\n\n### Interface-Based Injection\n\nUse `PersistenceManagerInterface` not the concrete `PersistenceManager` class:\n\n```php\n// ✅ Right: Interface injection\nuse TYPO3\\CMS\\Extbase\\Persistence\\PersistenceManagerInterface;\n\npublic function __construct(\n private readonly PersistenceManagerInterface $persistenceManager,\n) {}\n\n// ❌ Wrong: Concrete class\nuse TYPO3\\CMS\\Extbase\\Persistence\\Generic\\PersistenceManager;\n\npublic function __construct(\n private readonly PersistenceManager $persistenceManager,\n) {}\n```\n\n### PHPStan Extension Discovery\n\nPHPStan extensions are auto-discovered via `phpstan/extension-installer` (included in `netresearch/typo3-ci-workflows`). Do NOT manually include extension configs in `phpstan.neon`:\n\n```neon\n# ✅ Right: Only project-specific config\nparameters:\n level: 9\n paths:\n - Classes\n - Configuration\n```\n\n```neon\n# ❌ Wrong: Manual extension includes (auto-discovered)\nincludes:\n - vendor/phpstan/phpstan-strict-rules/rules.neon\n - vendor/saschaegerer/phpstan-typo3/extension.neon\nparameters:\n level: 9\n```\n\n### Repository Cache Scope\n\nRepository and service caches should be instance properties, not static properties:\n\n```php\n// ✅ Right: Instance property cache\nfinal class TranslationRepository extends Repository\n{\n /** @var array\u003cstring, Translation|null> */\n private array $cache = [];\n\n public function findByKey(string $key): ?Translation\n {\n if (!array_key_exists($key, $this->cache)) {\n $this->cache[$key] = $this->findOneBy(['lookupKey' => $key]);\n }\n return $this->cache[$key];\n }\n}\n\n// ❌ Wrong: Static cache (persists across requests in long-running processes)\nfinal class TranslationRepository extends Repository\n{\n private static array $cache = [];\n}\n```\n\n### Singleton Flag Safety\n\nWhen using `setCreateIfMissing(true)` on shared singleton query settings, always reset in a `finally` block to prevent leaking state:\n\n```php\n// ✅ Right: Reset in finally block\n$querySettings = $this->createQuery()->getQuerySettings();\n$querySettings->setCreateIfMissing(true);\ntry {\n $result = $this->findByCondition($criteria);\n} finally {\n $querySettings->setCreateIfMissing(false);\n}\n\n// ❌ Wrong: No reset — leaks state to subsequent queries\n$querySettings->setCreateIfMissing(true);\n$result = $this->findByCondition($criteria);\n// If findByCondition throws, flag stays true for all subsequent queries\n```\n\n### Factory Pattern\n\n```php\n// ✅ Right: Factory for Connection objects\nservices:\n Vendor\\ExtensionKey\\Domain\\Repository\\MyRepository:\n factory: ['@TYPO3\\CMS\\Core\\Database\\ConnectionPool', 'getConnectionForTable']\n arguments:\n - 'my_table'\n```\n\n### Singleton Services\n\n```php\n// ✅ Right: Use DI container, not Singleton pattern\n// Services are automatically singleton by default\n\n// ❌ Wrong: Don't use GeneralUtility::makeInstance() for new code\nuse TYPO3\\CMS\\Core\\Utility\\GeneralUtility;\n$service = GeneralUtility::makeInstance(MyService::class); // Deprecated\n```\n\n### PSR Interfaces\n\n```php\n// ✅ Right: Use PSR interfaces\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Client\\ClientInterface;\nuse Psr\\EventDispatcher\\EventDispatcherInterface;\nuse Psr\\Log\\LoggerInterface;\nuse Psr\\Clock\\ClockInterface;\n\n// Inject PSR-compliant services\npublic function __construct(\n private readonly LoggerInterface $logger,\n private readonly ClockInterface $clock\n) {}\n```\n\n## Prohibited Patterns (Mandatory Compliance)\n\nThese patterns are **strictly forbidden** in modern TYPO3 extensions (v12+). Using them will result in conformance failure.\n\n### ❌ PROHIBITED: $GLOBALS Access\n\n**All `$GLOBALS` access is forbidden.** Use proper TYPO3 APIs instead.\n\n| Prohibited Pattern | Modern Alternative |\n|-------------------|-------------------|\n| `$GLOBALS['TCA']` | `TcaSchemaFactory` (TYPO3 v14+) or inject TCA via DI |\n| `$GLOBALS['BE_USER']` | `Context` API or `BackendUserAuthentication` via DI |\n| `$GLOBALS['TSFE']` | PSR-7 Request attributes, middleware, or `TypoScriptFrontendController` via DI |\n| `$GLOBALS['TYPO3_CONF_VARS']` | `ExtensionConfiguration` or Site Settings |\n| `$GLOBALS['LANG']` | `LanguageServiceFactory` or `LocalizationUtility` |\n\n```php\n// ❌ PROHIBITED: Direct $GLOBALS access\n$tca = $GLOBALS['TCA']['tx_myext_table'];\n$user = $GLOBALS['BE_USER'];\n$tsfe = $GLOBALS['TSFE'];\n$config = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['my_ext'];\n\n// ✅ REQUIRED: Use TYPO3 APIs with dependency injection\nuse TYPO3\\CMS\\Core\\Schema\\TcaSchemaFactory;\nuse TYPO3\\CMS\\Core\\Context\\Context;\nuse TYPO3\\CMS\\Core\\Configuration\\ExtensionConfiguration;\n\nfinal class MyService\n{\n public function __construct(\n private readonly TcaSchemaFactory $tcaSchemaFactory,\n private readonly Context $context,\n private readonly ExtensionConfiguration $extensionConfiguration,\n ) {}\n\n public function getTableSchema(string $table): ?TcaSchema\n {\n if (!$this->tcaSchemaFactory->has($table)) {\n return null;\n }\n return $this->tcaSchemaFactory->get($table);\n }\n\n public function getCurrentUser(): ?BackendUserAuthentication\n {\n return $this->context->getAspect('backend.user')?->get('user');\n }\n\n public function getExtensionConfig(): array\n {\n return $this->extensionConfiguration->get('my_ext');\n }\n}\n```\n\n**Detection Command:**\n```bash\n# Find all $GLOBALS usage in Classes/\ngrep -rn '\\$GLOBALS\\[' Classes/\n\n# Should return NO results for compliant extensions\n```\n\n### ❌ PROHIBITED: GeneralUtility::makeInstance() for Services\n\n**Service locator pattern is forbidden.** All dependencies must be injected via constructor.\n\n```php\n// ❌ PROHIBITED: Service locator pattern\nuse TYPO3\\CMS\\Core\\Utility\\GeneralUtility;\n\nclass MyController\n{\n public function listAction(): ResponseInterface\n {\n // WRONG: Service locator anti-pattern\n $repository = GeneralUtility::makeInstance(ProductRepository::class);\n $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);\n $vaultService = GeneralUtility::makeInstance(VaultServiceInterface::class);\n }\n}\n\n// ✅ REQUIRED: Constructor dependency injection\nfinal class MyController extends ActionController\n{\n public function __construct(\n private readonly ProductRepository $repository,\n private readonly LoggerInterface $logger,\n private readonly VaultServiceInterface $vaultService,\n ) {}\n\n public function listAction(): ResponseInterface\n {\n // Dependencies already available via $this->\n $products = $this->repository->findAll();\n $this->logger->info('Products loaded');\n }\n}\n```\n\n**Allowed Exceptions (only these cases):**\n| Context | Reason | Example |\n|---------|--------|---------|\n| Form Elements | FormEngine instantiation | `VaultSecretElement extends AbstractFormElement` |\n| Scheduler Tasks | Serialization constraints | `OrphanCleanupTask extends AbstractTask` |\n| ext_localconf.php | Bootstrap context | Configuration registration |\n| TCA files | No DI available | `TCA/Overrides/*.php` |\n\n**Detection Command:**\n```bash\n# Find GeneralUtility::makeInstance in Classes/\ngrep -rn 'GeneralUtility::makeInstance' Classes/ \\\n --include='*.php' \\\n | grep -v 'Form/Element/' \\\n | grep -v 'Task/'\n\n# Should return NO results for compliant extensions (except allowed exceptions)\n```\n\n### ❌ PROHIBITED: Direct Class Instantiation\n\n```php\n// ❌ PROHIBITED: Direct instantiation bypasses DI\n$repository = new ProductRepository(); // Missing dependencies!\n$service = new MyService($dep1, $dep2); // Manual wiring!\n\n// ✅ REQUIRED: Let container handle instantiation\npublic function __construct(\n private readonly ProductRepository $repository,\n private readonly MyService $service,\n) {}\n```\n\n## Anti-Patterns to Avoid\n\n### Service Locator in Hooks\n\nTYPO3 hooks support constructor injection when autowiring is enabled:\n\n```php\n// ❌ Wrong: Service locator in hooks\nfinal class DataHandlerHook\n{\n public function processDatamap_afterDatabaseOperations(...): void\n {\n $vaultService = GeneralUtility::makeInstance(VaultServiceInterface::class);\n }\n}\n\n// ✅ Right: Constructor injection in hooks\nfinal class DataHandlerHook\n{\n public function __construct(\n private readonly VaultServiceInterface $vaultService,\n ) {}\n\n public function processDatamap_afterDatabaseOperations(...): void\n {\n $this->vaultService->store(...);\n }\n}\n```\n\n### Static Method Calls for Services\n\n```php\n// ❌ Wrong: Static calls hide dependencies\n$result = MyUtility::processData($input);\n$config = ConfigurationManager::getConfiguration();\n\n// ✅ Right: Inject services\npublic function __construct(\n private readonly DataProcessor $dataProcessor,\n private readonly ConfigurationManager $configManager,\n) {}\n```\n\n### Lazy Service Resolution\n\n```php\n// ❌ Wrong: Lazy resolution defeats DI benefits\nprivate ?MyService $service = null;\n\nprivate function getService(): MyService\n{\n return $this->service ??= GeneralUtility::makeInstance(MyService::class);\n}\n\n// ✅ Right: Eager constructor injection\npublic function __construct(\n private readonly MyService $service,\n) {}\n```\n\n## PSR-17/PSR-18 HTTP Client Integration\n\nTYPO3 provides HTTP client functionality through factory classes, not direct PSR interface aliases.\n\n### Correct Pattern: Use TYPO3 Core Factories\n\n```php\n// ✅ Right: Inject TYPO3 Core factories\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Service;\n\nuse Psr\\Http\\Message\\RequestInterface;\nuse TYPO3\\CMS\\Core\\Http\\RequestFactory;\n\nfinal class ApiClientService\n{\n public function __construct(\n private readonly RequestFactory $requestFactory,\n ) {}\n\n public function fetchData(string $url): array\n {\n $response = $this->requestFactory->request($url, 'GET', [\n 'headers' => ['Accept' => 'application/json'],\n ]);\n\n return json_decode($response->getBody()->getContents(), true);\n }\n}\n```\n\n### Services.yaml Configuration\n\n```yaml\n# ✅ Right: Use TYPO3 Core factories (no custom aliases needed)\nservices:\n Vendor\\ExtensionKey\\Service\\ApiClientService:\n arguments:\n $requestFactory: '@TYPO3\\CMS\\Core\\Http\\RequestFactory'\n\n# ❌ Wrong: Don't create custom PSR-17 interface aliases\n# TYPO3 Core doesn't register these interfaces directly\n# services:\n# Psr\\Http\\Message\\RequestFactoryInterface:\n# class: GuzzleHttp\\Psr7\\HttpFactory\n```\n\n### Why This Matters\n\nTYPO3 uses a **factory pattern** for HTTP client functionality:\n\n| What You Need | Use This |\n|---------------|----------|\n| Create HTTP requests | `TYPO3\\CMS\\Core\\Http\\RequestFactory` |\n| Create PSR-7 responses | `TYPO3\\CMS\\Core\\Http\\ResponseFactory` |\n| Create streams | `TYPO3\\CMS\\Core\\Http\\StreamFactory` |\n| Send HTTP requests | `RequestFactory::request()` method |\n\n**Common Mistake:** Creating custom `Psr\\Http\\Message\\RequestFactoryInterface` aliases in `Services.yaml` conflicts with TYPO3 Core's existing service definitions and causes container compilation errors.\n\n### For Custom HTTP Client Wrappers\n\n```php\n// ✅ Right: Use TYPO3's GuzzleClientFactory for advanced configuration\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\ExtensionKey\\Service;\n\nuse GuzzleHttp\\ClientInterface;\nuse TYPO3\\CMS\\Core\\Http\\Client\\GuzzleClientFactory;\n\nfinal class CustomHttpClient\n{\n private ClientInterface $client;\n\n public function __construct(\n GuzzleClientFactory $clientFactory,\n ) {\n $this->client = $clientFactory->getClient([\n 'timeout' => 30,\n 'verify' => true,\n 'headers' => [\n 'User-Agent' => 'MyExtension/1.0',\n ],\n ]);\n }\n}\n```\n\n## Conformance Checklist\n\n### Prohibited Patterns (Zero Tolerance)\n- [ ] **No `$GLOBALS['TCA']`** - Use `TcaSchemaFactory` instead\n- [ ] **No `$GLOBALS['BE_USER']`** - Use `Context` API instead\n- [ ] **No `$GLOBALS['TSFE']`** - Use PSR-7 Request or DI instead\n- [ ] **No `$GLOBALS['TYPO3_CONF_VARS']`** - Use `ExtensionConfiguration` instead\n- [ ] **No `$GLOBALS['LANG']`** - Use `LanguageServiceFactory` instead\n- [ ] **No `GeneralUtility::makeInstance()`** in Classes/ (except Form Elements and Tasks)\n- [ ] **No direct `new Service()`** instantiation for DI-managed classes\n\n### Dependency Injection (Mandatory)\n- [ ] Constructor injection for ALL service dependencies\n- [ ] `readonly` properties for injected dependencies\n- [ ] Services registered in Configuration/Services.yaml\n- [ ] `autowire: true` and `autoconfigure: true` in Services.yaml\n- [ ] PSR interfaces used (LoggerInterface, EventDispatcherInterface, etc.)\n- [ ] Hooks use constructor injection (TYPO3 supports this with autowiring)\n\n### PSR-17/PSR-18 HTTP Client (Important)\n- [ ] Use TYPO3\\CMS\\Core\\Http\\RequestFactory for HTTP requests\n- [ ] No custom PSR-17 interface aliases in Services.yaml\n- [ ] Use GuzzleClientFactory for custom client configuration\n\n### PSR-14 Events (Mandatory)\n- [ ] PSR-14 events used instead of hooks\n- [ ] Event classes are immutable with proper getters/setters\n- [ ] Event listeners use #[AsEventListener] attribute or Services.yaml tags\n- [ ] Event classes use `final` keyword (TYPO3 13+)\n- [ ] Event classes use `readonly` for immutable properties (TYPO3 13+)\n\n### TYPO3 13 Site Sets (Mandatory for TYPO3 13)\n- [ ] Configuration/Sets/ directory exists\n- [ ] Base set has config.yaml with proper metadata\n- [ ] settings.definitions.yaml defines extension settings with types\n- [ ] Set names follow vendor/package convention\n- [ ] Dependencies declared in config.yaml\n\n### Advanced Services.yaml (Mandatory)\n- [ ] Event listeners registered with proper tags\n- [ ] Console commands tagged with schedulable flag\n- [ ] Data processors registered with unique identifiers\n- [ ] Cache services use factory pattern\n- [ ] Service tags include all required attributes\n\n### PSR-15 Middleware\n- [ ] PSR-15 middlewares registered in RequestMiddlewares.php\n- [ ] Middleware ordering defined with before/after\n\n### Extbase Architecture\n- [ ] Extbase models extend AbstractEntity\n- [ ] Repositories extend Repository base class\n- [ ] Controllers use constructor injection\n- [ ] Validators extend AbstractValidator\n\n### Factory Pattern\n- [ ] Factory pattern for complex object creation (e.g., Connection objects)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":37672,"content_sha256":"e3164d2cadd371db91f6ec3c87006531853099ebe1ee4f0833f94d5a9d155ad1"},{"filename":"references/report-template.md","content":"# TYPO3 Extension Conformance Report Template\n\n## Report Structure\n\n```markdown\n# TYPO3 Extension Conformance Report\n\n**Extension:** {extension_name} (v{version})\n**Evaluation Date:** {date}\n**TYPO3 Compatibility:** {typo3_versions}\n\n---\n\n## Executive Summary\n\n**Base Conformance Score:** {score}/100\n**Excellence Indicators:** {excellence_score}/22 (Bonus)\n**Total Score:** {total_score}/122\n\n### Base Conformance Breakdown (0-100 points)\n- Extension Architecture: {score}/20\n- Coding Guidelines: {score}/20\n- PHP Architecture: {score}/20\n- Testing Standards: {score}/20\n- Best Practices: {score}/20\n\n### Excellence Indicators (0-22 bonus points)\n- Community & Internationalization: {score}/6\n- Advanced Quality Tooling: {score}/9\n- Documentation Excellence: {score}/4\n- Extension Configuration: {score}/3\n\n**Priority Issues:** {count_critical}\n**Recommendations:** {count_recommendations}\n\n---\n\n## 1. Extension Architecture ({score}/20)\n\n### ✅ Strengths\n- {list strengths}\n\n### ❌ Critical Issues\n- {list critical issues with file:line references}\n\n### ⚠️ Warnings\n- {list warnings}\n\n### 💡 Recommendations\n1. {specific actionable recommendations}\n\n---\n\n## 2. Coding Guidelines ({score}/20)\n\n{repeat same structure}\n\n---\n\n## 3. PHP Architecture ({score}/20)\n\n{repeat same structure}\n\n---\n\n## 4. Testing Standards ({score}/20)\n\n{repeat same structure}\n\n---\n\n## 5. Best Practices ({score}/20)\n\n{repeat same structure}\n\n---\n\n## Priority Action Items\n\n### High Priority (Fix Immediately)\n1. {critical issues that break functionality or security}\n\n### Medium Priority (Fix Soon)\n1. {important conformance issues}\n\n### Low Priority (Improve When Possible)\n1. {minor style and optimization issues}\n\n---\n\n## Detailed Issue List\n\n| Category | Severity | File | Line | Issue | Recommendation |\n|----------|----------|------|------|-------|----------------|\n| Architecture | Critical | ext_tables.php | - | Using deprecated ext_tables.php | Migrate to Configuration/Backend/Modules.php |\n| Coding | High | Classes/Controller/ProductController.php | 12 | Missing declare(strict_types=1) | Add at top of file |\n| Architecture | High | Classes/Service/EmailService.php | 45 | Using GeneralUtility::makeInstance() | Switch to constructor injection |\n| Testing | Medium | Tests/ | - | No functional tests | Create Tests/Functional/ with fixtures |\n```\n\n## Example Output Formats\n\n### Category Report Section\n\n```markdown\n## Coding Standards Conformance\n\n### ✅ Strengths\n- All classes use UpperCamelCase naming\n- Proper type declarations on methods\n- PHPDoc comments present and complete\n\n### ❌ Violations\n- 15 files missing `declare(strict_types=1)`\n - Classes/Controller/ProductController.php:1\n - Classes/Service/CalculationService.php:1\n- 8 instances of old array syntax `array()`\n - Classes/Utility/ArrayHelper.php:45\n - Classes/Domain/Model/Product.php:78\n- 3 methods missing PHPDoc comments\n - Classes/Service/EmailService.php:calculate()\n- **5 instances of non-inclusive terminology**\n - Classes/Service/FilterService.php:12 - \"whitelist\" → use \"allowlist\"\n - Classes/Service/FilterService.php:45 - \"blacklist\" → use \"blocklist\"\n\n### ⚠️ Style Issues\n- Inconsistent spacing around concatenation operators (12 instances)\n- Some variables using snake_case (5 instances)\n```\n\n### Excellence Indicators Section\n\n```markdown\n**Excellence Indicators:** 14/22 (Bonus)\n- Community & Internationalization: 5/6\n - ✅ Crowdin integration (+2)\n - ✅ Professional README badges (+2)\n - ✅ GitHub issue templates (+1)\n - ❌ No .gitattributes export-ignore\n\n- Advanced Quality Tooling: 7/9\n - ✅ Fractor configuration (+2)\n - ✅ TYPO3 CodingStandards (+2)\n - ✅ Makefile with help (+1)\n - ✅ TER publishing workflow (+2)\n - ❌ No StyleCI\n - ❌ No CI testing matrix\n\n- Documentation Excellence: 2/4\n - ✅ 75 RST files (+1)\n - ✅ Modern tooling (guides.xml) (+1)\n\n- Extension Configuration: 1/3\n - ✅ Composer doc scripts (+1)\n - ❌ No ext_conf_template.txt\n - ❌ Only one Configuration/Sets/ preset\n```\n\n## Migration Code Examples\n\n### Migrating from ext_tables.php\n\n```php\n// Before (ext_tables.php) - DEPRECATED\nExtensionUtility::registerModule(...);\n\n// After (Configuration/Backend/Modules.php) - MODERN\nreturn [\n 'web_myext' => [\n 'parent' => 'web',\n ...\n ],\n];\n```\n\n### Converting to Constructor Injection\n\n```php\n// Before - DEPRECATED\nuse TYPO3\\CMS\\Core\\Utility\\GeneralUtility;\n$repository = GeneralUtility::makeInstance(ProductRepository::class);\n\n// After - MODERN\npublic function __construct(\n private readonly ProductRepository $repository\n) {}\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4620,"content_sha256":"d0cd4818c814261b34fc7464a5e82a51015d4c38c2cf1a0eb5e2d9bdce72814b"},{"filename":"references/runtests-validation.md","content":"# runTests.sh Validation Guide\n\n**Purpose:** Validate Build/Scripts/runTests.sh against TYPO3 Best Practices (Tea extension reference)\n\n## Why Validate runTests.sh?\n\nThe `runTests.sh` script is the **central orchestration tool** for TYPO3 extension quality workflows. An outdated or misconfigured script can lead to:\n\n- ❌ Testing with wrong PHP/TYPO3 versions (false positives/negatives)\n- ❌ Missing database compatibility issues\n- ❌ Inconsistent local vs CI environments\n- ❌ Developer confusion with incorrect defaults\n\n## Reference Implementation\n\n**Source of Truth:** https://github.com/TYPO3BestPractices/tea/blob/main/Build/Scripts/runTests.sh\n\nThe Tea extension maintains the canonical runTests.sh implementation, updated for latest TYPO3 standards.\n\n## Critical Validation Points\n\n### 1. PHP Version Configuration\n\n**Check Lines ~318 and ~365:**\n\n```bash\n# Default PHP version\nPHP_VERSION=\"X.X\"\n\n# PHP version validation regex\nif ! [[ ${PHP_VERSION} =~ ^(X.X|X.X|X.X)$ ]]; then\n```\n\n**Validation:**\n1. Read extension's composer.json `require.php` constraint\n2. Extract minimum PHP version (e.g., `^8.2` → minimum 8.2)\n3. Verify runTests.sh default matches minimum\n4. Verify version regex includes all supported versions\n\n**Example Check:**\n\n```bash\n# Extension composer.json\n\"require\": {\n \"php\": \"^8.2 || ^8.3 || ^8.4\"\n}\n\n# runTests.sh SHOULD have:\nPHP_VERSION=\"8.2\" # ✅ Matches minimum\nif ! [[ ${PHP_VERSION} =~ ^(8.2|8.3|8.4)$ ]]; then # ✅ All supported\n\n# runTests.sh SHOULD NOT have:\nPHP_VERSION=\"7.4\" # ❌ Below minimum\nif ! [[ ${PHP_VERSION} =~ ^(7.4|8.0|8.1|8.2|8.3)$ ]]; then # ❌ Includes unsupported\n```\n\n**Severity:** 🔴 **High** - Testing with wrong PHP version invalidates results\n\n### 2. TYPO3 Version Configuration\n\n**Check Lines ~315 and ~374:**\n\n```bash\n# Default TYPO3 version\nTYPO3_VERSION=\"XX\"\n\n# TYPO3 version validation\nif ! [[ ${TYPO3_VERSION} =~ ^(11|12|13)$ ]]; then\n```\n\n**Validation:**\n1. Read extension's composer.json TYPO3 core dependency\n2. Extract target TYPO3 version (e.g., `^13.4` → TYPO3 13)\n3. Verify runTests.sh default matches target\n4. Check composerInstallHighest/Lowest version constraints\n\n**Example Check:**\n\n```bash\n# Extension composer.json\n\"require\": {\n \"typo3/cms-core\": \"^13.4\"\n}\n\n# runTests.sh SHOULD have:\nTYPO3_VERSION=\"13\" # ✅ Matches target\n\n# In composerInstallHighest (line ~530):\nif [ ${TYPO3_VERSION} -eq 13 ]; then\n composer require --no-ansi --no-interaction --no-progress --no-install \\\n typo3/cms-core:^13.4 # ✅ Matches composer.json\n\n# runTests.sh SHOULD NOT have:\nTYPO3_VERSION=\"11\" # ❌ Below target\n```\n\n**Severity:** 🔴 **High** - Testing against wrong TYPO3 version\n\n### 3. Database Version Support\n\n**Check Lines ~48-107 (handleDbmsOptions function):**\n\n```bash\nmariadb)\n [ -z \"${DBMS_VERSION}\" ] && DBMS_VERSION=\"X.X\"\n if ! [[ ${DBMS_VERSION} =~ ^(10.2|10.3|...|11.1)$ ]]; then\n```\n\n**Validation:**\n1. Check MariaDB, MySQL, PostgreSQL version lists are current\n2. Verify default versions are maintained (not EOL)\n3. Cross-reference with TYPO3 core database support matrix\n\n**Current Database Support (TYPO3 13):**\n\n| DBMS | Supported Versions | Default | EOL Status |\n|------|-------------------|---------|------------|\n| MariaDB | 10.4-10.11, 11.0-11.4 | 10.11 | 10.4+ maintained |\n| MySQL | 8.0, 8.1, 8.2, 8.3, 8.4 | 8.0 | 8.0 maintained until 2026 |\n| PostgreSQL | 10-16 | 16 | 10-11 EOL, 12+ maintained |\n| SQLite | 3.x | 3.x | Always latest |\n\n**Example Check:**\n\n```bash\n# runTests.sh MariaDB (line ~48)\n[ -z \"${DBMS_VERSION}\" ] && DBMS_VERSION=\"10.11\" # ✅ LTS version\nif ! [[ ${DBMS_VERSION} =~ ^(10.4|10.5|10.6|10.11|11.0|11.1|11.2|11.3|11.4)$ ]]; then\n\n# ❌ BAD - EOL version as default:\n[ -z \"${DBMS_VERSION}\" ] && DBMS_VERSION=\"10.2\" # EOL 2023\n\n# runTests.sh PostgreSQL (line ~79)\n[ -z \"${DBMS_VERSION}\" ] && DBMS_VERSION=\"16\" # ✅ Latest stable\nif ! [[ ${DBMS_VERSION} =~ ^(10|11|12|13|14|15|16)$ ]]; then\n```\n\n**Severity:** 🟡 **Medium** - May miss database-specific compatibility issues\n\n### 4. Network Name Configuration\n\n**Check Line ~331:**\n\n```bash\nNETWORK=\"extension-name-${SUFFIX}\"\n```\n\n**Validation:**\n1. Should match extension key or composer package name\n2. Should NOT be hardcoded to \"friendsoftypo3-tea\" (copy-paste artifact)\n\n**Example Check:**\n\n```bash\n# Extension key: rte_ckeditor_image\n# Composer package: netresearch/rte-ckeditor-image\n\n# ✅ Good options:\nNETWORK=\"rte-ckeditor-image-${SUFFIX}\"\nNETWORK=\"netresearch-rte-ckeditor-image-${SUFFIX}\"\n\n# ❌ Bad (copy-paste from Tea):\nNETWORK=\"friendsoftypo3-tea-${SUFFIX}\"\n```\n\n**Severity:** 🟢 **Low** - Cosmetic, but indicates lack of customization\n\n### 5. Test Suite Commands\n\n**Check Lines ~580, ~620 (functional and unit test commands):**\n\n```bash\nfunctional)\n COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml ...)\n\nunit)\n COMMAND=(.Build/bin/phpunit -c Build/phpunit/UnitTests.xml ...)\n```\n\n**Validation:**\n1. Paths match actual PHPUnit config locations\n2. Config files exist and are properly named\n3. Exclude groups match available database types\n\n**Example Check:**\n\n```bash\n# Verify config files exist:\nls -la Build/phpunit/UnitTests.xml # Must exist\nls -la Build/phpunit/FunctionalTests.xml # Must exist\n\n# Check command paths:\nCOMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml ...)\n └─────┬─────┘ └──────────┬───────────┘\n ✅ Matches ✅ Matches actual\n .Build/bin/ Build/phpunit/\n from composer.json directory structure\n```\n\n**Severity:** 🔴 **High** - Tests won't run if paths are wrong\n\n### 6. Container Image Versions\n\n**Check Lines ~446-451:**\n\n```bash\nIMAGE_PHP=\"ghcr.io/typo3/core-testing-$(echo \"php${PHP_VERSION}\" | sed -e 's/\\.//'):latest\"\nIMAGE_ALPINE=\"docker.io/alpine:3.8\"\nIMAGE_DOCS=\"ghcr.io/typo3-documentation/render-guides:latest\"\nIMAGE_MARIADB=\"docker.io/mariadb:${DBMS_VERSION}\"\nIMAGE_MYSQL=\"docker.io/mysql:${DBMS_VERSION}\"\nIMAGE_POSTGRES=\"docker.io/postgres:${DBMS_VERSION}-alpine\"\n```\n\n**Validation:**\n1. PHP testing image uses official TYPO3 images\n2. Alpine version is reasonably current (not ancient)\n3. Documentation renderer is latest official TYPO3 image\n\n**Severity:** 🟢 **Low** - Usually works, but outdated Alpine may have issues\n\n## Conformance Evaluation Workflow\n\n### Step 1: Extract Extension Requirements\n\n```bash\n# Read composer.json\ncat composer.json | jq -r '.require.php' # e.g., \"^8.2 || ^8.3 || ^8.4\"\ncat composer.json | jq -r '.require.\"typo3/cms-core\"' # e.g., \"^13.4\"\n\n# Parse minimum versions\nMIN_PHP=$(echo \"^8.2 || ^8.3\" | grep -oE '[0-9]+\\.[0-9]+' | head -1) # 8.2\nTARGET_TYPO3=$(echo \"^13.4\" | grep -oE '[0-9]+') # 13\n```\n\n### Step 2: Validate runTests.sh Defaults\n\n```bash\n# Check PHP version default (line ~318)\ngrep '^PHP_VERSION=' Build/Scripts/runTests.sh\n# Expected: PHP_VERSION=\"8.2\" (matches MIN_PHP)\n\n# Check TYPO3 version default (line ~315)\ngrep '^TYPO3_VERSION=' Build/Scripts/runTests.sh\n# Expected: TYPO3_VERSION=\"13\" (matches TARGET_TYPO3)\n```\n\n### Step 3: Validate PHP Version Regex\n\n```bash\n# Extract PHP version regex (line ~365)\ngrep -A 2 'if ! \\[\\[ \\${PHP_VERSION}' Build/Scripts/runTests.sh\n\n# Expected pattern for \"^8.2 || ^8.3 || ^8.4\":\n# ^(8.2|8.3|8.4)$\n\n# ❌ Outdated pattern:\n# ^(7.4|8.0|8.1|8.2|8.3)$\n```\n\n### Step 4: Validate TYPO3 Version Constraints in Composer Install\n\n```bash\n# Check composerInstallHighest TYPO3 13 block (line ~530)\nsed -n '/if \\[ \\${TYPO3_VERSION} -eq 13 \\];/,/fi/p' Build/Scripts/runTests.sh\n\n# Should match composer.json requirements:\n# typo3/cms-core:^13.4\n# typo3/cms-backend:^13.4\n# etc.\n```\n\n### Step 5: Validate Network Name\n\n```bash\n# Check network name (line ~331)\ngrep '^NETWORK=' Build/Scripts/runTests.sh\n\n# Extract extension key from composer.json or ext_emconf.php\nEXT_KEY=$(jq -r '.extra.typo3.cms.\"extension-key\"' composer.json)\n\n# Expected: NETWORK=\"${EXT_KEY}-${SUFFIX}\" or similar\n# ❌ Wrong: NETWORK=\"friendsoftypo3-tea-${SUFFIX}\"\n```\n\n## Automated Validation Script\n\nCreate `scripts/validate-runtests.sh`:\n\n```bash\n#!/bin/bash\n\nset -e\n\necho \"🔍 Validating Build/Scripts/runTests.sh against extension requirements...\"\n\n# Extract requirements\nMIN_PHP=$(jq -r '.require.php' composer.json | grep -oE '[0-9]+\\.[0-9]+' | head -1)\nTARGET_TYPO3=$(jq -r '.require.\"typo3/cms-core\"' composer.json | grep -oE '^[0-9]+' | head -1)\nEXT_KEY=$(jq -r '.extra.typo3.cms.\"extension-key\"' composer.json)\n\necho \"📋 Extension Requirements:\"\necho \" PHP: ${MIN_PHP}+\"\necho \" TYPO3: ${TARGET_TYPO3}\"\necho \" Extension Key: ${EXT_KEY}\"\necho \"\"\n\n# Validate PHP version default\nRUNTESTS_PHP=$(grep '^PHP_VERSION=' Build/Scripts/runTests.sh | cut -d'\"' -f2)\nif [ \"${RUNTESTS_PHP}\" != \"${MIN_PHP}\" ]; then\n echo \"❌ PHP version mismatch: runTests.sh uses ${RUNTESTS_PHP}, should be ${MIN_PHP}\"\n exit 1\nelse\n echo \"✅ PHP version default: ${RUNTESTS_PHP}\"\nfi\n\n# Validate TYPO3 version default\nRUNTESTS_TYPO3=$(grep '^TYPO3_VERSION=' Build/Scripts/runTests.sh | cut -d'\"' -f2)\nif [ \"${RUNTESTS_TYPO3}\" != \"${TARGET_TYPO3}\" ]; then\n echo \"❌ TYPO3 version mismatch: runTests.sh uses ${RUNTESTS_TYPO3}, should be ${TARGET_TYPO3}\"\n exit 1\nelse\n echo \"✅ TYPO3 version default: ${RUNTESTS_TYPO3}\"\nfi\n\n# Validate network name\nNETWORK_NAME=$(grep '^NETWORK=' Build/Scripts/runTests.sh | cut -d'\"' -f2 | sed 's/-${SUFFIX}$//')\nif [[ \"${NETWORK_NAME}\" == \"friendsoftypo3-tea\" ]]; then\n echo \"⚠️ Network name is copy-paste from Tea extension: ${NETWORK_NAME}\"\n echo \" Should be: ${EXT_KEY}-\\${SUFFIX}\"\nelse\n echo \"✅ Network name: ${NETWORK_NAME}-\\${SUFFIX}\"\nfi\n\necho \"\"\necho \"✅ runTests.sh validation complete\"\n```\n\n## Conformance Report Integration\n\n### When evaluating runTests.sh:\n\n**In \"Best Practices\" Section:**\n\n```markdown\n### Build Scripts\n\n**runTests.sh Analysis:**\n\n- ✅ Script present and executable\n- ✅ PHP version default matches composer.json minimum (8.2)\n- ✅ TYPO3 version default matches target (13)\n- ✅ PHP version regex includes all supported versions (8.2, 8.3, 8.4)\n- ⚠️ Network name uses Tea extension default (cosmetic issue)\n- ✅ Test suite commands match actual file structure\n- ✅ Database version support is current\n\n**Or with issues:**\n\n- ❌ PHP version default (7.4) below extension minimum (8.2)\n - File: Build/Scripts/runTests.sh:318\n - Severity: High\n - Fix: Change `PHP_VERSION=\"7.4\"` to `PHP_VERSION=\"8.2\"`\n\n- ❌ TYPO3 version default (11) below extension target (13)\n - File: Build/Scripts/runTests.sh:315\n - Severity: High\n - Fix: Change `TYPO3_VERSION=\"11\"` to `TYPO3_VERSION=\"13\"`\n\n- ❌ PHP version regex includes unsupported versions\n - File: Build/Scripts/runTests.sh:365\n - Current: `^(7.4|8.0|8.1|8.2|8.3) typo3-conformance — Skillopedia \n - Expected: `^(8.2|8.3|8.4) typo3-conformance — Skillopedia \n - Severity: Medium\n - Fix: Remove unsupported versions from regex\n```\n\n## Scoring Impact\n\n**Best Practices Score Deductions:**\n\n| Issue | Severity | Score Impact |\n|-------|----------|--------------|\n| PHP version default outdated | High | -3 points |\n| TYPO3 version default outdated | High | -3 points |\n| PHP version regex includes unsupported | Medium | -2 points |\n| Database versions EOL | Medium | -2 points |\n| Network name copy-paste | Low | -1 point |\n| Missing runTests.sh | Critical | -10 points |\n\n**Maximum deduction for runTests.sh issues:** -6 points (out of 20 for Best Practices)\n\n## Quick Reference Checklist\n\n**When evaluating Build/Scripts/runTests.sh:**\n\n```\n□ File exists and is executable\n□ PHP_VERSION default matches composer.json minimum\n□ TYPO3_VERSION default matches composer.json target\n□ PHP version regex matches composer.json constraint exactly\n□ TYPO3_VERSION regex includes supported versions only\n□ Database version lists are current (not EOL)\n□ Database version defaults are maintained LTS versions\n□ Network name is customized (not \"friendsoftypo3-tea\")\n□ Test suite paths match actual directory structure\n□ Container images use official TYPO3 testing images\n```\n\n**Comparison Strategy:**\n\n1. Download latest Tea runTests.sh as reference\n2. Compare line-by-line for structural differences\n3. Validate version-specific values against extension requirements\n4. Flag any outdated patterns or hardcoded Tea-specific values\n\n## Resources\n\n- **Tea Extension runTests.sh:** https://github.com/TYPO3BestPractices/tea/blob/main/Build/Scripts/runTests.sh\n- **TYPO3 Testing Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/\n- **Database Compatibility:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Database/\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":12702,"content_sha256":"e6cb139eb19592c42d8b1c87950632245e67fb34476110d63d7d70358900858b"},{"filename":"references/ter-publishing.md","content":"# TER Publishing Reference\n\n**Purpose:** Document requirements and best practices for publishing TYPO3 extensions to the TER (TYPO3 Extension Repository)\n\n## Overview\n\nThe TYPO3 Extension Repository (TER) at [extensions.typo3.org](https://extensions.typo3.org) is the official distribution platform for TYPO3 extensions. Publishing is typically automated via GitHub Actions using the official `typo3/tailor` tool.\n\n---\n\n## Upload Comment Format\n\nThe \"Last upload comment\" field displayed on extension pages has specific format requirements:\n\n### Allowed Content\n- **Type:** Plain text only (no HTML, Markdown, or rich formatting)\n- **Letters, numbers, whitespace**\n- **Basic punctuation:** `\" % & [ ] ( ) . , ; : / ? { } ! $ - @`\n- **Newlines:** Supported and displayed as line breaks (`\u003cbr>`)\n\n### Validation\n- **Required:** Cannot be empty (throws `NoUploadCommentException`)\n- **Storage:** `TEXT` field in database (no enforced length limit)\n\n### Characters Stripped in XML Export\nWhen exported to `extensions.xml` feed, the following regex filters the comment:\n```\n/[^\\w\\s\"%&\\[\\]\\(\\)\\.\\,\\;\\:\\/\\?\\{\\}!\\$\\-\\/\\@]/u\n```\n\n**Stripped characters include:** `# * + = ~ ^ | \\ \u003c >` and non-ASCII special characters\n\n### Rendering Contexts\n| Context | Processing |\n|---------|------------|\n| Frontend (extension page) | `\u003cf:format.nl2br>` - newlines become `\u003cbr>` |\n| Email notifications | Raw text |\n| XML feed (extensions.xml) | Sanitized via `xmlentities()` |\n\n### Best Practices\n1. **Keep it concise:** Focus on key changes\n2. **Use line breaks:** Structure with newlines for readability\n3. **Avoid special characters:** Stick to basic punctuation\n4. **Be descriptive:** Summarize the release, not just \"Bug fixes\"\n\n**Good Example:**\n```\nFixed critical bug in authentication module\n- Resolved token expiration issue\n- Updated dependency versions\n- Improved error messages for version constraints\n```\n\n**Avoid:**\n```\nBug fixes & improvements! See CHANGELOG.md for details...\n```\n(The `&` will be escaped in the XML feed. ASCII `...` is fine — the XML-export regex above permits `.` — but the Unicode ellipsis `…` gets stripped since it's not in the allow-list.)\n\n---\n\n## CI TER Compatibility Check\n\n**CRITICAL:** Add this check to your CI workflow to catch ext_emconf.php issues BEFORE release attempts!\n\n**Add to `.github/workflows/ci.yml`:**\n```yaml\n- id: ter-compatibility\n name: TER Compatibility Check\n run: |\n # ext_emconf.php must NOT contain strict_types - TER cannot parse it\n # Use regex to match actual declaration (not comments mentioning it)\n if grep -qE \"^[[:space:]]*declare\\(strict_types\" ext_emconf.php; then\n echo \"::error file=ext_emconf.php::ext_emconf.php contains strict_types declaration which breaks TER publishing\"\n exit 1\n fi\n echo \"TER compatibility check passed\"\n```\n\n**Why this matters:** This check runs on every PR and push, catching strict_types issues before they reach a release attempt. Without this, you may only discover the problem when TER upload fails.\n\n---\n\n## Version Synchronization Check\n\n**CRITICAL:** The `typo3/tailor` tool rejects uploads where the tag version does not match `ext_emconf.php` `'version'`. This is a common release failure.\n\n### The Problem\n\nWhen creating a release tag (e.g. `0.4.1`), `ext_emconf.php` must already contain `'version' => '0.4.1'`. If the version was not bumped before tagging, `tailor ter:publish` fails with \"configured version does not match\".\n\nGitHub.com does not support custom pre-receive hooks, so server-side tag validation is not possible. Use **defense-in-depth**: local git hook + CI validation step.\n\n### CI Workflow Step\n\nAdd this step to your TER publish workflow **after checkout and before publishing**:\n\n```yaml\n- name: Validate ext_emconf.php version\n env:\n TAG_VERSION: ${{ env.version }}\n run: |\n EMCONF_VERSION=$(grep -oP \"'version'\\s*=>\\s*'\\K[^']+\" ext_emconf.php)\n if [[ \"${TAG_VERSION}\" != \"${EMCONF_VERSION}\" ]]; then\n echo \"::error file=ext_emconf.php::Tag version (${TAG_VERSION}) does not match ext_emconf.php version (${EMCONF_VERSION}). Update ext_emconf.php before tagging.\"\n exit 1\n fi\n echo \"Version validated: ${TAG_VERSION} matches ext_emconf.php\"\n```\n\n### CaptainHook Pre-Push Hook\n\nFor local protection, add a pre-push hook script that checks any semver tag at HEAD:\n\n```bash\n#!/usr/bin/env bash\nset -euo pipefail\n\nTAGS=$(git tag --points-at HEAD | grep -E '^[0-9]+\\.[0-9]+\\.[0-9]+

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

|| true)\n[[ -z \"${TAGS}\" ]] && exit 0\n\nEMCONF_VERSION=$(grep -oP \"'version'\\s*=>\\s*'\\K[^']+\" ext_emconf.php)\n\nwhile IFS= read -r TAG; do\n if [[ \"${TAG}\" != \"${EMCONF_VERSION}\" ]]; then\n echo \"ERROR: Tag ${TAG} does not match ext_emconf.php version ${EMCONF_VERSION}\"\n exit 1\n fi\ndone \u003c\u003c\u003c \"${TAGS}\"\n```\n\nRegister in `captainhook.json` under `pre-push.actions[]`:\n```json\n{ \"action\": \"Build/Scripts/check-tag-version.sh\" }\n```\n\n### Release Process Checklist Addition\n\n```\n[ ] ext_emconf.php version bumped BEFORE creating tag\n[ ] CI validates ext_emconf.php version matches tag\n[ ] Pre-push hook validates locally\n```\n\n---\n\n## CRITICAL: Upload Comment Source\n\n**NEVER use git tag message for upload comments!**\n\n### The Problem\n\nWhen using `gh release create`, GitHub creates a **lightweight tag** (not annotated). Lightweight tags have no message, so `git tag -n1` returns the **commit message** instead:\n\n```bash\n# BAD: Gets commit message like \"chore(release): v1.2.0 (#123)\"\ngit tag -n1 -l \"v${VERSION}\" | sed \"s/^v[0-9.]*[ ]*//g\"\n```\n\nThis results in TER showing unprofessional upload comments like:\n- `chore(release): v13.2.0 (#508)`\n- `Merge pull request #123 from feature/xyz`\n\n### The Solution\n\n**Always use `github.event.release.body`** (the GitHub release notes) as the comment source:\n\n```yaml\nenv:\n RELEASE_BODY: ${{ github.event.release.body }}\nrun: |\n if [[ -n \"${RELEASE_BODY}\" ]]; then\n COMMENT=$(echo \"${RELEASE_BODY}\" | head -c 1000)\n # Strip unsupported characters\n COMMENT=$(echo \"${COMMENT}\" | sed 's/[#*+=~^|\\\\\u003c>]//g')\n else\n COMMENT=\"Released version ${VERSION}\"\n fi\n```\n\n### Conformance Check\n\nWhen auditing extensions, verify `publish-to-ter.yml` uses:\n- ✅ `github.event.release.body` - Release notes (CORRECT)\n- ✅ `github.event.release.name` - Release title (fallback)\n- ❌ `git tag -n1` - Tag/commit message (WRONG)\n\n---\n\n## GitHub Actions Workflow\n\n### Recommended Workflow Template\n\n**File:** `.github/workflows/publish-to-ter.yml`\n\n**Template:** Copy from `assets/.github/workflows/publish-to-ter.yml`\n\n```yaml\nname: Publish new extension version to TER\n\non:\n release:\n types: [published]\n\njobs:\n publish:\n name: Publish new version to TER\n runs-on: ubuntu-latest\n env:\n TYPO3_EXTENSION_KEY: ${{ secrets.TYPO3_EXTENSION_KEY }}\n TYPO3_API_TOKEN: ${{ secrets.TYPO3_TER_ACCESS_TOKEN }}\n steps:\n - name: Checkout repository\n uses: actions/checkout@v4\n\n - name: Validate tag format\n run: |\n if ! [[ \"${GITHUB_REF_NAME}\" =~ ^v[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$ ]]; then\n echo \"::error::Invalid tag format '${GITHUB_REF_NAME}'. Expected format: v1.2.3\"\n exit 1\n fi\n\n - name: Extract version\n id: version\n run: |\n # Strip 'v' prefix for TER (expects \"3.0.1\" not \"v3.0.1\")\n VERSION=\"${GITHUB_REF_NAME#v}\"\n echo \"version=${VERSION}\" >> $GITHUB_OUTPUT\n echo \"Extracted version: ${VERSION}\"\n\n - name: Prepare release comment\n id: comment\n env:\n RELEASE_BODY: ${{ github.event.release.body }}\n RELEASE_NAME: ${{ github.event.release.name }}\n RELEASE_URL: ${{ github.event.release.html_url }}\n run: |\n # Build comment from release body or name\n if [[ -n \"${RELEASE_BODY}\" ]]; then\n # Preserve newlines for TER display, limit length\n # TER supports newlines - they render as \u003cbr> on the frontend\n COMMENT=$(echo \"${RELEASE_BODY}\" | head -c 1000)\n elif [[ -n \"${RELEASE_NAME}\" ]]; then\n COMMENT=\"${RELEASE_NAME}\"\n else\n COMMENT=\"Release ${{ steps.version.outputs.version }}\"\n fi\n\n # Strip characters not supported in TER XML export\n # Allowed: word chars, whitespace, \" % & [ ] ( ) . , ; : / ? { } ! $ - @\n COMMENT=$(echo \"${COMMENT}\" | sed 's/[#*+=~^|\\\\\u003c>]//g')\n\n # Append release link on new line\n COMMENT=\"${COMMENT}\n\nDetails: ${RELEASE_URL}\"\n\n # Escape for GitHub Actions output (preserve newlines)\n {\n echo \"comment\u003c\u003cEOF\"\n echo \"${COMMENT}\"\n echo \"EOF\"\n } >> $GITHUB_OUTPUT\n\n - name: Setup PHP\n uses: shivammathur/setup-php@v2\n with:\n php-version: '8.3'\n extensions: intl, mbstring, json, zip, curl\n tools: composer:v2\n\n - name: Install tailor\n run: composer global require typo3/tailor --prefer-dist --no-progress\n\n - name: Publish to TER\n run: |\n TAILOR=\"$(composer global config bin-dir --absolute)/tailor\"\n \"${TAILOR}\" set-version \"${{ steps.version.outputs.version }}\"\n \"${TAILOR}\" ter:publish --comment \"${{ steps.comment.outputs.comment }}\" \"${{ steps.version.outputs.version }}\"\n```\n\n### Required Secrets\n\n| Secret | Description | Where to Get |\n|--------|-------------|--------------|\n| `TYPO3_EXTENSION_KEY` | Your registered extension key | [my.typo3.org](https://my.typo3.org) |\n| `TYPO3_TER_ACCESS_TOKEN` | API token for TER uploads | [extensions.typo3.org/my-extensions](https://extensions.typo3.org/my-extensions) |\n\n### Tag Format Requirements\n- **Format:** `vMAJOR.MINOR.PATCH` (e.g., `v1.2.3`)\n- **Note:** TER expects version without `v` prefix internally\n- **Validation:** Workflow should validate tag format before publishing\n\n---\n\n## MANDATORY: TER Metadata Setup (Sidebar Links)\n\n**CRITICAL:** The TER extension page sidebar links (Extension Manual, Found an Issue?, Code Insights, Packagist.org) are **NOT** populated from `composer.json`. They must be set explicitly via `tailor ter:update`.\n\nWithout this step, the TER page looks incomplete — no links to documentation, issues, source code, or Packagist.\n\n### The Problem\n\nNew extensions uploaded to TER show an empty sidebar with no links:\n- No \"Extension Manual\" button\n- No \"Found an Issue?\" button\n- No \"Code Insights\" button\n- No \"Packagist.org\" button\n\nThis makes the extension appear unprofessional and hard to use.\n\n### The Solution\n\nRun `tailor ter:update` after the first TER upload:\n\n```bash\nTYPO3_API_TOKEN=your-token tailor ter:update \\\n --composer=\"vendor/package-name\" \\\n --manual=\"https://github.com/vendor/repo\" \\\n --issues=\"https://github.com/vendor/repo/issues\" \\\n --repository=\"https://github.com/vendor/repo\" \\\n --tags=\"tag1,tag2,tag3\" \\\n extension_key\n```\n\n### Available Metadata Fields\n\n| Option | TER Sidebar Button | Required |\n|--------|--------------------|----------|\n| `--manual` | Extension Manual | **YES** |\n| `--issues` | Found an Issue? | **YES** |\n| `--repository` | Code Insights | **YES** |\n| `--composer` | Packagist.org (auto-linked) | **YES** |\n| `--tags` | Search tags | Recommended |\n| `--paypal` | Sponsoring link | Optional |\n\n### When to Run\n\n- **First release:** MANDATORY after the first `ter:publish`\n- **URL changes:** Only when repository, docs, or issue tracker URLs change\n- **Links persist:** Once set, they carry across all future version uploads\n\n### Automation via CI\n\nAdd to your TER publish workflow as a post-publish step:\n\n```yaml\n- name: Update TER metadata\n if: env.FIRST_RELEASE == 'true' # or always, idempotent\n run: |\n TAILOR=\"$(composer global config bin-dir --absolute)/tailor\"\n \"${TAILOR}\" ter:update \\\n --composer=\"${COMPOSER_NAME}\" \\\n --manual=\"${REPO_URL}\" \\\n --issues=\"${REPO_URL}/issues\" \\\n --repository=\"${REPO_URL}\" \\\n \"${TYPO3_EXTENSION_KEY}\"\n```\n\n### Verification\n\nAfter setting metadata, verify on the TER page:\n\n```bash\n# Check that all 4 sidebar links are present\ncurl -s \"https://extensions.typo3.org/extension/${EXT_KEY}\" | \\\n grep -c 'Extension Manual\\|Found an Issue\\|Code Insights\\|Packagist\\.org'\n# Expected: 4\n```\n\n---\n\n## Packagist Listing\n\nExtensions with a `composer.json` should be listed on [Packagist](https://packagist.org):\n\n1. **Submit package** at https://packagist.org/packages/submit\n2. **Enable auto-update** via GitHub webhook (Packagist Settings > Enable GitHub Hook)\n3. **Verify** the package appears with correct description and version\n\nThe `--composer` flag in `ter:update` creates the Packagist link on the TER page.\n\n---\n\n## Documentation Rendering on docs.typo3.org\n\nExtensions with a `Documentation/` directory and `guides.xml` can have their documentation rendered at docs.typo3.org:\n\n1. **Create** `Documentation/guides.xml` with project metadata\n2. **Add RST files** following TYPO3 documentation standards\n3. **Webhook** is automatically triggered when the extension is on Packagist\n4. **Verify** at `https://docs.typo3.org/p/vendor/package-name/main/en-us/`\n\nIf not using docs.typo3.org, set `--manual` to the GitHub repository URL or a hosted documentation site.\n\n---\n\n## Release Comment Best Practices\n\n### Writing Effective Release Notes\n\n**Structure:**\n```\nBrief summary of the release (one line)\n\n- Key change 1\n- Key change 2\n- Key change 3\n```\n\n**Do:**\n- Start with a clear summary line\n- Use bullet points for individual changes\n- Group by type (Features, Fixes, Breaking)\n- Keep each point concise\n- Include TYPO3 version compatibility changes\n\n**Don't:**\n- Use Markdown formatting (not rendered)\n- Use special characters like `#`, `*`, `\u003c`, `>`\n- Write excessively long descriptions\n- Just say \"Bug fixes\" without details\n\n### Example Release Notes\n\n**Good:**\n```\nTYPO3 13 compatibility and performance improvements\n\nBreaking changes:\n- Minimum PHP version is now 8.2\n- Removed deprecated API methods\n\nNew features:\n- Added support for native lazy loading\n- Improved caching for list views\n\nBug fixes:\n- Fixed pagination in backend module\n- Resolved translation loading issue\n```\n\n**Transformed for TER:**\n```\nTYPO3 13 compatibility and performance improvements\n\nBreaking changes:\n- Minimum PHP version is now 8.2\n- Removed deprecated API methods\n\nNew features:\n- Added support for native lazy loading\n- Improved caching for list views\n\nBug fixes:\n- Fixed pagination in backend module\n- Resolved translation loading issue\n\nDetails: https://github.com/vendor/extension/releases/tag/v2.0.0\n```\n\n---\n\n## Troubleshooting\n\n### Common Issues\n\n| Issue | Cause | Solution |\n|-------|-------|----------|\n| \"No upload comment\" error | Empty comment passed to tailor | Ensure fallback comment in workflow |\n| Special characters in XML feed | Unsupported chars in comment | Strip `#*+=~^|\\\\\u003c>` from comments |\n| Version mismatch | Tag doesn't match ext_emconf | Use `tailor set-version` before publish |\n| Authentication failed | Invalid/expired API token | Regenerate token at extensions.typo3.org |\n| Published with wrong / outdated upload comment | Publish ran before the release body was finalized | Re-run `tailor ter:publish --comment \"...\" vX.Y.Z` on the same version — TER overwrites the upload comment on republish. The ZIP contents are unchanged (the version number doesn't let you ship different code under the same version), only the comment updates. Safe to re-trigger the publish workflow after editing the GitHub release body. |\n\n### Validation Script\n\nAdd to your workflow for pre-publish validation:\n```bash\n# Validate upload comment format\nvalidate_comment() {\n local comment=\"$1\"\n\n # Check not empty\n if [[ -z \"${comment// }\" ]]; then\n echo \"::error::Upload comment cannot be empty\"\n return 1\n fi\n\n # Check for unsupported characters\n if [[ \"$comment\" =~ [#*+=~^\\|\\\\\u003c>] ]]; then\n echo \"::warning::Comment contains characters that will be stripped in XML export\"\n fi\n\n return 0\n}\n```\n\n---\n\n## Technical Details\n\n### TER API Endpoints\n- **SOAP API:** Legacy, still supported (`ter_soap` extension)\n- **REST API:** Modern interface (`ter_rest` extension)\n\n### Comment Processing Pipeline\n1. **Input:** Raw text from tailor CLI\n2. **Validation:** Non-empty check (`ExtensionVersion.php`)\n3. **Storage:** `TEXT` field in `tx_terfe2_domain_model_version`\n4. **Frontend:** Fluid template with `\u003cf:format.nl2br>`\n5. **XML Export:** `xmlentities()` sanitization\n\n### Source Code References (TER codebase)\n- Validation: `extensions/ter/Classes/Api/ExtensionVersion.php:350`\n- Storage: `extensions/ter_fe2/ext_tables.sql:84`\n- TCA: `extensions/ter_fe2/Configuration/TCA/tx_terfe2_domain_model_version.php:80`\n- Frontend: `extensions/ter_fe2/Resources/Private/Templates/Extension/Show.html:132`\n- XML Export: `extensions/ter_fe2/Classes/Service/ExtensionIndexService.php:192`\n\n---\n\n## Conformance Checklist\n\n### TER Publishing Excellence Indicators\n\n```\nGitHub Actions Workflow:\n[ ] publish-to-ter.yml exists in .github/workflows/\n[ ] Triggers on release published event\n[ ] Validates tag format (vX.Y.Z)\n[ ] Extracts version correctly (strips 'v' prefix)\n[ ] Handles release body for comment\n[ ] Has fallback comment if body empty\n[ ] Uses typo3/tailor for publishing\n[ ] Secrets properly configured\n\nTER Metadata (MANDATORY for initial setup):\n[ ] tailor ter:update --manual has been run (Extension Manual link)\n[ ] tailor ter:update --issues has been run (Found an Issue link)\n[ ] tailor ter:update --repository has been run (Code Insights link)\n[ ] tailor ter:update --composer has been run (Packagist.org link)\n[ ] All 4 sidebar links visible on extensions.typo3.org\n\nPublic Listings:\n[ ] Extension page exists on extensions.typo3.org\n[ ] Package exists on packagist.org\n[ ] Documentation available (docs.typo3.org or linked from TER)\n[ ] composer.json has support.issues URL\n[ ] composer.json has support.source URL\n[ ] composer.json has homepage URL\n\nRelease Process:\n[ ] Semantic versioning followed\n[ ] CHANGELOG.md updated before release\n[ ] ext_emconf.php version bumped and committed BEFORE tagging\n[ ] CI validates ext_emconf.php version matches tag (early fail)\n[ ] composer.json version (if present) matches tag\n[ ] Release notes follow TER format guidelines\n[ ] Pre-push hook validates ext_emconf.php version locally\n```\n\n---\n\n## Resources\n\n- **Tailor CLI:** https://github.com/TYPO3/tailor\n- **TER API Documentation:** https://extensions.typo3.org/help/api\n- **Extension Registration:** https://my.typo3.org\n- **TER Frontend:** https://extensions.typo3.org\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":18550,"content_sha256":"b92a27c033ec0055c7bb2ea963be2dd320d813b3168e70046c5148900f727fae"},{"filename":"references/testing-standards.md","content":"# TYPO3 Testing Standards\n\n**Purpose:** Conformance validation criteria for TYPO3 extension testing\n**Implementation Guide:** Use the `typo3-testing` skill for templates and patterns\n\n## Testing Framework Requirements\n\nExtensions MUST use **typo3/testing-framework** for comprehensive testing:\n\n```bash\ncomposer require --dev \"typo3/testing-framework:^9.0\"\n```\n\n## runTests.sh Requirements (REQUIRED)\n\nExtensions **MUST** have a Docker-based `Build/Scripts/runTests.sh` following TYPO3 core conventions:\n\n### Required Capabilities\n- [ ] Uses `ghcr.io/typo3/core-testing-php*` images\n- [ ] Supports `-s unit` for unit tests\n- [ ] Supports `-s functional` for functional tests\n- [ ] Supports `-d sqlite|mariadb|mysql|postgres` for database selection\n- [ ] Supports `-p 8.2|8.3|8.4|8.5` for PHP version selection\n- [ ] Auto-detects CI/non-TTY environments\n- [ ] Handles database container orchestration\n\n### Verification\n```bash\n# Check script exists and is executable\ntest -x Build/Scripts/runTests.sh\n\n# Verify help output\nBuild/Scripts/runTests.sh -h | grep -q \"sqlite\"\n\n# Run unit tests\nBuild/Scripts/runTests.sh -s unit\n\n# Run functional tests (SQLite default)\nBuild/Scripts/runTests.sh -s functional\n```\n\n### Makefile Integration (Recommended)\n```makefile\nRUNTESTS = Build/Scripts/runTests.sh\n\ntest: unit\nunit:\n\t$(RUNTESTS) -s unit\n\nfunctional:\n\t$(RUNTESTS) -s functional\n\nci: lint phpstan unit\n```\n\n## Unit Test Conformance\n\n### Required Structure\n- [ ] Tests extend `TYPO3\\TestingFramework\\Core\\Unit\\UnitTestCase`\n- [ ] Located in `Tests/Unit/` mirroring `Classes/` structure\n- [ ] Files named `\u003cClassName>Test.php`\n- [ ] PHPUnit config at `Build/phpunit/UnitTests.xml`\n\n### Quality Criteria\n- [ ] No database access\n- [ ] No file system access\n- [ ] No external HTTP requests\n- [ ] All public methods tested\n- [ ] Edge cases and boundaries covered\n- [ ] Uses `#[Test]` attribute (PHP 8.0+) or `@test` annotation\n- [ ] Descriptive test method names: `methodName\u003cCondition>Returns\u003cExpected>`\n\n## Functional Test Conformance\n\n### Required Structure\n- [ ] Tests extend `TYPO3\\TestingFramework\\Core\\Functional\\FunctionalTestCase`\n- [ ] Located in `Tests/Functional/`\n- [ ] PHPUnit config at `Build/phpunit/FunctionalTests.xml`\n- [ ] Fixtures in `Tests/Functional/Fixtures/` (CSV format)\n\n### Quality Criteria\n- [ ] `setUp()` calls `parent::setUp()` first\n- [ ] Extensions loaded via `$testExtensionsToLoad`\n- [ ] Test data loaded via `$this->importCSVDataSet()`\n- [ ] Backend user initialized when testing backend operations\n- [ ] Tests database operations with proper assertions\n- [ ] Fixtures minimal and well-documented\n- [ ] Singleton instances reset between tests (see Singleton Testability below)\n- [ ] `tearDown()` uses try-catch for incomplete setUp() scenarios\n\n### Singleton Testability Pattern\n\nSingletons can cause test isolation failures when state persists between tests.\n\n**Problem Detection:**\n```bash\n# Find singleton usage that may need reset\ngrep -rn \"GeneralUtility::makeInstance.*Singleton\" Tests/\ngrep -rn \"::getInstance()\" Tests/\n```\n\n**Solution Pattern:**\n```php\nprotected function setUp(): void\n{\n parent::setUp();\n // Reset singleton before each test\n GeneralUtility::purgeInstances();\n // Or reset specific singleton\n GeneralUtility::removeSingletonInstance(\n MyService::class,\n GeneralUtility::makeInstance(MyService::class)\n );\n}\n```\n\n**Safe tearDown() Pattern:**\n```php\nprotected function tearDown(): void\n{\n try {\n // Cleanup that depends on setUp() completion\n $this->cleanupTestData();\n } catch (\\Throwable $e) {\n // setUp() may have failed, ignore cleanup errors\n }\n parent::tearDown();\n}\n```\n\n**Severity:** Medium (causes intermittent test failures)\n\n## E2E Test Conformance (Playwright)\n\n**Reference:** [TYPO3 Core Playwright Tests](https://github.com/TYPO3/typo3/tree/main/Build/tests/playwright)\n\n### Required Structure\n- [ ] Configuration at `Build/playwright.config.ts`\n- [ ] Tests in `Build/tests/playwright/e2e/`\n- [ ] Files named `\u003cfeature>.spec.ts`\n- [ ] Node.js version >=22.18 in `.nvmrc`\n- [ ] Dependencies in `Build/package.json`\n\n### Required Dependencies\n```json\n{\n \"devDependencies\": {\n \"@playwright/test\": \"^1.56.1\",\n \"@axe-core/playwright\": \"^4.9.0\"\n }\n}\n```\n\n### Quality Criteria\n- [ ] Authentication setup in `helper/login.setup.ts`\n- [ ] Page Object Models in `fixtures/` directory\n- [ ] Storage state for session persistence\n- [ ] Tests verify user-visible behavior\n- [ ] Uses specific waits, not `waitForTimeout()`\n- [ ] Stable selectors (`data-testid`, roles, not CSS classes)\n\n## Accessibility Test Conformance\n\n### Required Structure\n- [ ] Tests in `Build/tests/playwright/accessibility/`\n- [ ] Uses `@axe-core/playwright` for WCAG validation\n- [ ] Tests all backend modules\n\n### Quality Criteria\n- [ ] Runs axe-core analysis on module content\n- [ ] Reports violations with severity levels\n- [ ] No critical or serious violations in new code\n- [ ] Documents any disabled rules with justification\n\n## CI/CD Requirements\n\n### Minimum Pipeline\n- [ ] Uses `Build/Scripts/runTests.sh` for all test execution\n- [ ] PHP lint check (`-s lint`)\n- [ ] Unit tests (`-s unit`)\n- [ ] Functional tests (`-s functional`, SQLite default)\n- [ ] PHPStan analysis (`-s phpstan`, level 5+)\n- [ ] Code style check (`-s cgl -n`)\n\n### GitHub Actions Example\n```yaml\njobs:\n tests:\n runs-on: ubuntu-latest\n strategy:\n matrix:\n php: ['8.2', '8.3', '8.4']\n steps:\n - uses: actions/checkout@v4\n - run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s unit\n - run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s functional\n```\n\n### E2E Pipeline (when Playwright tests exist)\n- [ ] Node.js 22.x setup\n- [ ] Playwright browser installation\n- [ ] TYPO3 instance running\n- [ ] Test report artifact upload\n\n## Conformance Scoring\n\n| Criteria | Points |\n|----------|--------|\n| Docker-based `runTests.sh` present | 10 |\n| Unit tests present and passing | 10 |\n| Functional tests present and passing | 10 |\n| E2E tests present and passing | 5 |\n| Accessibility tests present | 5 |\n| CI/CD using runTests.sh | 5 |\n| Test coverage >70% | 5 |\n| **Total** | **50** |\n\n### runTests.sh Scoring Details\n| Capability | Points |\n|------------|--------|\n| Script exists and executable | 2 |\n| Supports `-s unit` and `-s functional` | 2 |\n| Supports database selection (`-d`) | 2 |\n| Supports PHP version (`-p`) | 2 |\n| CI/non-TTY auto-detection | 2 |\n\n### Rating\n- **Excellent**: 45-50 points\n- **Good**: 35-44 points\n- **Acceptable**: 25-34 points\n- **Needs Work**: \u003c25 points\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6608,"content_sha256":"270a8722e20e62f368442a5c4f6f716dc1a6b64fd94ea02f3f7dde78a73a83cc"},{"filename":"references/v13-deprecations.md","content":"# TYPO3 v13 Deprecations and Modern Alternatives\n\n**Sources:** TYPO3 Core API Reference v13.4\n**Purpose:** Track v13 deprecations, migration paths, and modern configuration approaches\n\n## Deprecated Files (v13.1+)\n\n### ext_typoscript_constants.typoscript\n**Status:** DEPRECATED since TYPO3 v13.1\n\n**Purpose:** Provided global TypoScript constants\n\n**Migration Paths:**\n\n**1. Preferred: Site Settings Definitions**\n```yaml\n# Configuration/Sets/MySet/settings.definitions.yaml\nsettings:\n myext:\n itemsPerPage:\n type: int\n default: 10\n label: 'Items per page'\n```\n\n**2. For Global Constants:**\n```php\n// ext_localconf.php\n\\TYPO3\\CMS\\Core\\Utility\\ExtensionManagementUtility::addTypoScript(\n 'my_extension',\n 'constants',\n '@import \"EXT:my_extension/Configuration/TypoScript/constants.typoscript\"'\n);\n```\n\n**Detection:**\n```bash\n[ -f \"ext_typoscript_constants.typoscript\" ] && echo \"⚠️ DEPRECATED: Migrate to Site sets\"\n```\n\n**Impact:** \"This file takes no effect in sites that use Site sets.\"\n\n---\n\n### ext_typoscript_setup.typoscript\n**Status:** DEPRECATED since TYPO3 v13.1\n\n**Purpose:** Provided global TypoScript setup\n\n**Migration Paths:**\n\n**1. Preferred: Site Sets**\n```yaml\n# Configuration/Sets/MySet/config.yaml\nname: my-vendor/my-set\nlabel: 'My Extension Set'\n\nimports:\n - { resource: 'EXT:fluid_styled_content/Configuration/Sets/FluidStyledContent/config.yaml' }\n```\n\n```typoscript\n# Configuration/Sets/MySet/setup.typoscript\nplugin.tx_myextension {\n settings {\n itemsPerPage = 10\n }\n}\n```\n\n**2. For Global Loading:**\n```php\n// ext_localconf.php\n\\TYPO3\\CMS\\Core\\Utility\\ExtensionManagementUtility::addTypoScript(\n 'my_extension',\n 'setup',\n '@import \"EXT:my_extension/Configuration/TypoScript/setup.typoscript\"'\n);\n```\n\n**Detection:**\n```bash\n[ -f \"ext_typoscript_setup.typoscript\" ] && echo \"⚠️ DEPRECATED: Migrate to Site sets\"\n```\n\n**Impact:** \"This file takes no effect in sites that use Site sets. This file works for backward compatibility reasons only in installations that depend on TypoScript records only.\"\n\n---\n\n## Deprecated Methods (Removal in v14)\n\n### ExtensionManagementUtility::addUserTSConfig()\n**Status:** DEPRECATED, will be removed with TYPO3 v14.0\n\n**Old Approach:**\n```php\n// ext_localconf.php - DEPRECATED\n\\TYPO3\\CMS\\Core\\Utility\\ExtensionManagementUtility::addUserTSConfig('\n options.pageTree.showPageIdWithTitle = 1\n options.defaultUploadFolder = 1:/user_uploads/\n');\n```\n\n**Modern Approach:**\n```\n# Configuration/user.tsconfig\noptions.pageTree.showPageIdWithTitle = 1\noptions.defaultUploadFolder = 1:/user_uploads/\n```\n\n**Detection:**\n```bash\ngrep \"addUserTSConfig\" ext_localconf.php && echo \"❌ DEPRECATED: Use Configuration/user.tsconfig\"\n```\n\n---\n\n## Modern Configuration Files (v12+)\n\n### Configuration/user.tsconfig\n**Since:** TYPO3 v12\n**Purpose:** User TSconfig loaded for all backend users\n\n**Location:** `Configuration/user.tsconfig`\n\n**Example:**\n```\n# Default user settings\noptions.pageTree.showPageIdWithTitle = 1\noptions.defaultUploadFolder = 1:/user_uploads/\n\n# Hide modules\noptions.hideModules = web_layout, web_info\n```\n\n**Validation:**\n```bash\n[ -f \"Configuration/user.tsconfig\" ] && echo \"✅ Modern user TSconfig\" || echo \"⚠️ Consider adding user TSconfig\"\n```\n\n---\n\n### Configuration/page.tsconfig\n**Since:** TYPO3 v12\n**Purpose:** Page TSconfig loaded globally\n\n**Location:** `Configuration/page.tsconfig`\n\n**Example:**\n```\n# Default page configuration\nTCEFORM.pages.layout.disabled = 1\nTCEMAIN.table.pages.disablePrependAtCopy = 1\n\n# Backend layout\nmod.web_layout.BackendLayouts {\n standard {\n title = Standard Layout\n icon = EXT:my_ext/Resources/Public/Icons/layout.svg\n config {\n backend_layout {\n colCount = 2\n rowCount = 1\n rows {\n 1 {\n columns {\n 1 {\n name = Main\n colPos = 0\n }\n 2 {\n name = Sidebar\n colPos = 1\n }\n }\n }\n }\n }\n }\n }\n}\n```\n\n**Validation:**\n```bash\n[ -f \"Configuration/page.tsconfig\" ] && echo \"✅ Modern page TSconfig\" || echo \"⚠️ Consider adding page TSconfig\"\n```\n\n---\n\n## Modern Backend Configuration (v13)\n\n### Configuration/Backend/Modules.php\n**Since:** TYPO3 v13.0\n**Purpose:** Backend module registration (replaces ext_tables.php)\n\n**Location:** `Configuration/Backend/Modules.php`\n\n**Example:**\n```php\n\u003c?php\n\nreturn [\n 'web_myext' => [\n 'parent' => 'web',\n 'position' => ['after' => 'web_list'],\n 'access' => 'user',\n 'workspaces' => 'live',\n 'path' => '/module/web/myext',\n 'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',\n 'extensionName' => 'MyExt',\n 'controllerActions' => [\n \\Vendor\\MyExt\\Controller\\BackendController::class => [\n 'list',\n 'detail',\n 'update',\n ],\n ],\n ],\n];\n```\n\n**Old Approach (DEPRECATED):**\n```php\n// ext_tables.php - DEPRECATED\n\\TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::registerModule(\n 'MyExt',\n 'web',\n 'mymodule',\n 'after:list',\n [\n \\Vendor\\MyExt\\Controller\\BackendController::class => 'list,detail,update',\n ],\n [\n 'access' => 'user,group',\n 'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',\n ]\n);\n```\n\n**Migration Script:** TYPO3 provides \"Check TCA in ext_tables.php\" upgrade tool\n\n**Validation:**\n```bash\n[ -f \"Configuration/Backend/Modules.php\" ] && echo \"✅ Modern backend modules\" || echo \"⚠️ Check for modules in ext_tables.php\"\n```\n\n---\n\n## Site Sets (v13 Recommended Approach)\n\n### Configuration/Sets Structure\n\n```\nConfiguration/Sets/\n└── MySet/\n ├── config.yaml (REQUIRED)\n ├── settings.definitions.yaml\n ├── setup.typoscript\n ├── constants.typoscript (optional)\n ├── page.tsconfig\n └── user.tsconfig\n```\n\n### config.yaml (Required)\n```yaml\nname: my-vendor/my-set\nlabel: 'My Extension Configuration Set'\n\n# Dependencies\nimports:\n - { resource: 'EXT:fluid_styled_content/Configuration/Sets/FluidStyledContent/config.yaml' }\n\n# Settings with defaults\nsettings:\n myext:\n itemsPerPage: 10\n showImages: true\n```\n\n### settings.definitions.yaml\n```yaml\nsettings:\n myext:\n itemsPerPage:\n type: int\n default: 10\n label: 'Items per page'\n description: 'Number of items displayed per page in list view'\n\n showImages:\n type: bool\n default: true\n label: 'Show images'\n description: 'Display images in list view'\n\n templateLayout:\n type: string\n default: 'default'\n label: 'Template layout'\n enum:\n default: 'Default Layout'\n grid: 'Grid Layout'\n list: 'List Layout'\n```\n\n### setup.typoscript\n```typoscript\nplugin.tx_myextension {\n view {\n templateRootPaths.0 = EXT:my_extension/Resources/Private/Templates/\n partialRootPaths.0 = EXT:my_extension/Resources/Private/Partials/\n layoutRootPaths.0 = EXT:my_extension/Resources/Private/Layouts/\n }\n\n settings {\n itemsPerPage = {$settings.myext.itemsPerPage}\n showImages = {$settings.myext.showImages}\n }\n}\n```\n\n### Activation in Site Configuration\n```yaml\n# config/sites/mysite/config.yaml\nbase: 'https://example.com/'\nrootPageId: 1\nsets:\n - my-vendor/my-set # Activates the set\n```\n\n---\n\n## Migration Checklist\n\n### For v12 → v13 Migration\n- [ ] Move backend module registration from ext_tables.php to Configuration/Backend/Modules.php\n- [ ] Replace `addUserTSConfig()` with Configuration/user.tsconfig\n- [ ] Move page TSconfig from ext_localconf.php to Configuration/page.tsconfig\n- [ ] Deprecate ext_typoscript_constants.typoscript (use Site sets)\n- [ ] Deprecate ext_typoscript_setup.typoscript (use Site sets)\n\n### For Modern v13 Extensions\n- [ ] Use Configuration/Sets/ for TypoScript configuration\n- [ ] Use settings.definitions.yaml for extension settings\n- [ ] Use Configuration/Backend/Modules.php for backend modules\n- [ ] Use Configuration/user.tsconfig for user TSconfig\n- [ ] Use Configuration/page.tsconfig for page TSconfig\n- [ ] Use Configuration/Icons.php for icon registration\n\n---\n\n## Validation Commands\n\n```bash\n#!/bin/bash\n# check-v13-deprecations.sh\n\necho \"=== Checking for TYPO3 v13 Deprecations ===\"\necho \"\"\n\n# Check deprecated files\nif [ -f \"ext_typoscript_constants.typoscript\" ]; then\n echo \"⚠️ DEPRECATED: ext_typoscript_constants.typoscript (v13.1)\"\n echo \" → Migrate to Configuration/Sets/ with settings.definitions.yaml\"\nfi\n\nif [ -f \"ext_typoscript_setup.typoscript\" ]; then\n echo \"⚠️ DEPRECATED: ext_typoscript_setup.typoscript (v13.1)\"\n echo \" → Migrate to Configuration/Sets/ with setup.typoscript\"\nfi\n\n# Check deprecated methods\nif grep -q \"addUserTSConfig\" ext_localconf.php 2>/dev/null; then\n echo \"❌ DEPRECATED: addUserTSConfig() - Removal in v14\"\n echo \" → Use Configuration/user.tsconfig\"\nfi\n\n# Check for backend modules in ext_tables.php\nif grep -q \"registerModule\" ext_tables.php 2>/dev/null; then\n echo \"⚠️ DEPRECATED: Backend modules in ext_tables.php\"\n echo \" → Migrate to Configuration/Backend/Modules.php\"\nfi\n\n# Check modern files presence\necho \"\"\necho \"=== Modern Configuration Files ===\" [ -d \"Configuration/Sets\" ] && echo \"✅ Configuration/Sets/ present\" || echo \"⚠️ Consider adding Site sets\"\n[ -f \"Configuration/user.tsconfig\" ] && echo \"✅ Configuration/user.tsconfig present\"\n[ -f \"Configuration/page.tsconfig\" ] && echo \"✅ Configuration/page.tsconfig present\"\n[ -f \"Configuration/Backend/Modules.php\" ] && echo \"✅ Configuration/Backend/Modules.php present\"\n\necho \"\"\necho \"Deprecation check complete\"\n```\n\n---\n\n## Quick Reference Matrix\n\n| Old Approach | Modern Approach (v13) | Status |\n|--------------|----------------------|--------|\n| ext_typoscript_constants.typoscript | Configuration/Sets/*/settings.definitions.yaml | Deprecated v13.1 |\n| ext_typoscript_setup.typoscript | Configuration/Sets/*/setup.typoscript | Deprecated v13.1 |\n| addUserTSConfig() in ext_localconf.php | Configuration/user.tsconfig | Removal in v14 |\n| Page TSconfig in ext_localconf.php | Configuration/page.tsconfig | Modern v12+ |\n| registerModule() in ext_tables.php | Configuration/Backend/Modules.php | Modern v13+ |\n| Static files in ext_tables.php | Configuration/TCA/Overrides/sys_template.php | Modern |\n| TCA in ext_tables.php | Configuration/TCA/*.php | Modern |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10800,"content_sha256":"a6374eaf550050c52292a7d6187d044a7990222ec107f0f076948e4c4498d224"},{"filename":"references/v13-v14-dual-compatibility.md","content":"# Dual Version (v13 + v14) Compatibility Patterns\n\n> **Companion to:** `dual-version-compatibility.md` (v12 + v13).\n> **Context:** TYPO3 v14.3 LTS released 2026-04-21. Many Netresearch extensions continue supporting v13 LTS alongside v14 during the v13 → v14 migration window (v13 free-support end ≈ 2027-10-31).\n\n---\n\n## Version constraint strategy\n\n### composer.json\n\n```json\n{\n \"require\": {\n \"php\": \"^8.2\",\n \"typo3/cms-core\": \"^13.4 || ^14.3\"\n }\n}\n```\n\nAlternative allowing pre-14.3 sprint releases (not recommended for prod):\n\n```json\n\"typo3/cms-core\": \"^13.4 || ^14.0\"\n```\n\n### ext_emconf.php\n\n```php\n'constraints' => [\n 'depends' => [\n 'typo3' => '13.4.0-14.3.99',\n 'php' => '8.2.0-8.5.99',\n ],\n],\n```\n\n> Note: `ext_emconf.php` itself is deprecated in v14.2 (#108345). Keep it for classic-mode support, but mirror all metadata in `composer.json` — v15 will remove `ext_emconf.php` entirely.\n\n---\n\n## Rector configuration\n\n```php\n// rector.php - DUAL v13+v14\n$rectorConfig->sets([\n LevelSetList::UP_TO_PHP_82,\n Typo3LevelSetList::UP_TO_TYPO3_13, // NOT UP_TO_TYPO3_14\n Typo3SetList::CODE_QUALITY,\n Typo3SetList::GENERAL,\n]);\n```\n\n**Reason:** `UP_TO_TYPO3_14` applies the v14 Fluid 5 strict-typing + FAL strict-typing + `HashService` removal rules that would break v13. Use the v14 rule set on a dedicated v14-only branch.\n\nTo run just a single v14 rule against the dual codebase:\n\n```bash\nvendor/bin/rector process --only=Ssch\\\\TYPO3Rector\\\\Rules\\\\v14\\\\v0\\\\MigrateRemovedMailMessageSendRector Classes/\n```\n\n---\n\n## API compatibility decision matrix\n\n| Purpose | v13 + v14 (dual-safe) | v14-only (avoid) |\n|---|---|---|\n| Frontend page information | `$request->getAttribute('frontend.page.information')` | (same — identical API) |\n| TypoScriptFrontendController | **avoid entirely** (removed in v14) | — |\n| Site | `$request->getAttribute('site')` | — |\n| Language | `$request->getAttribute('language')` | — |\n| Password hashing | `PasswordHashFactory` + `BackendPasswordHasher` / `FrontendPasswordHasher` | — |\n| HMAC / signing | `HashService` from `TYPO3\\\\CMS\\\\Extbase\\\\Security\\\\Cryptography\\\\HashService` in v13 **only** — class is **removed in v14** | use `TYPO3\\\\CMS\\\\Core\\\\Crypto\\\\HashService` in v14 |\n| Magic repo finders (`findByX()`) | **avoid** — removed in v14; use `createQuery()` | |\n| Fluid view rendering | `Core\\\\View\\\\ViewFactoryInterface` (available v13+, preferred); `StandaloneView` only if v13-only | `StandaloneView` removed in v14 |\n| TCA search | use explicit search TCA (#106972 pattern); `control.searchFields` still works in v13, removed in v14 | `control.searchFields` |\n| Extbase annotations | PHP attributes (`#[Validate]`) | docblock `@Validate` (removed in v14) |\n| ext_tables.php | **still supported in v14.3** but deprecated — split now into `Configuration/Backend/Modules.php` + `Routes.php` | |\n| Plugin registration via `list_type` | v13 still accepts; v14 removed. Use CType-only plugins (dual-safe in v13.4+) | `list_type` |\n\n---\n\n## HashService replacement (breaking for v14)\n\nv13 usage:\n\n```php\nuse TYPO3\\CMS\\Extbase\\Security\\Cryptography\\HashService;\n\nclass Foo {\n public function __construct(private readonly HashService $hashService) {}\n}\n```\n\nv14: `HashService` is gone. Use `Core\\Crypto\\HashService` if you only need HMAC, or the new symmetric cipher service (#108002) for encryption:\n\n```php\n// dual-safe adapter pattern\nif (class_exists(\\TYPO3\\CMS\\Core\\Crypto\\HashService::class)) {\n // v14+: Core HashService\n $hashService = GeneralUtility::makeInstance(\\TYPO3\\CMS\\Core\\Crypto\\HashService::class);\n} else {\n // v13: Extbase HashService\n $hashService = GeneralUtility::makeInstance(\\TYPO3\\CMS\\Extbase\\Security\\Cryptography\\HashService::class);\n}\n```\n\nBetter: inject via constructor with a shim service defined in `Services.yaml` per TYPO3 major.\n\n---\n\n## Fluid 5 dual-mode templates\n\n**v13 (Fluid 4.x)** vs **v14 (Fluid 5)** differences that bite:\n\n1. **Variable names starting with `_`** — forbidden in v14.\n - `{_myVar}` → rename to `{myVar}` or `{my_var}`.\n2. **Strict VH argument types** — v14 requires typed args. v13 is forgiving.\n - Dual-safe: add types to all custom ViewHelpers now.\n3. **`renderStatic()`** — removed in Fluid 5. Replace with non-static `render()`.\n4. **CDATA sections** — preserved in Fluid 5 (v13 stripped them). Only matters if your templates embed `\u003c![CDATA[…]]>`.\n\n**ViewHelper pattern that works in both v13 and v14:**\n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\nnamespace Vendor\\Ext\\ViewHelpers;\n\nuse TYPO3Fluid\\Fluid\\Core\\ViewHelper\\AbstractViewHelper;\n\nfinal class FooViewHelper extends AbstractViewHelper\n{\n public function initializeArguments(): void\n {\n $this->registerArgument('value', 'string', 'The value', true);\n }\n\n public function render(): string\n {\n return htmlspecialchars((string)$this->arguments['value']);\n }\n}\n```\n\n---\n\n## TypoLink / TSFE migration\n\nEvery v13 extension using `$GLOBALS['TSFE']` must switch before v14:\n\n```php\n// DUAL-SAFE (works in v13 and v14)\n$pageInfo = $request->getAttribute('frontend.page.information');\n$pageId = $pageInfo?->getId() ?? 0;\n\n// v14 dropped $GLOBALS['TSFE'] entirely — NO FALLBACK available in v14\n```\n\nIf you must support both v13 AND v12 (tri-compat), use the `Typo3Version` check from `dual-version-compatibility.md` §Compatibility Layer Pattern.\n\n---\n\n## Security attributes (v14 excellence bonus)\n\nv14 introduces `#[Authorize]` and `#[RateLimit]` attributes in `TYPO3\\CMS\\Extbase\\Attribute`. These classes **do not exist in v13**. Naively adding `use` + the attribute to a dual-version extension will:\n\n- Not break at `use`-statement parse time (PHP `use` aliases are lazy; they don't autoload).\n- Break the first time v13 **reflects** the attributes on the method (e.g., `ReflectionMethod::getAttributes()` with `newInstance()` triggers autoload of the missing class → fatal `Class not found`).\n\n**If v13 never reflects these attributes on your controllers, you can add them without guard** — v13 will simply ignore them at dispatch time because v13's dispatcher doesn't know to read them. Verify for your specific code path.\n\n**Safer: ship a polyfill stub for v13.** In a file conditionally loaded in `ext_localconf.php`:\n\n```php\n// Classes/Compat/AttributeStubs.php (loaded only on v13)\nif (!class_exists(\\TYPO3\\CMS\\Extbase\\Attribute\\Authorize::class)) {\n #[\\Attribute(\\Attribute::TARGET_METHOD | \\Attribute::TARGET_CLASS)]\n final class Authorize { public function __construct(bool $requireLogin = true, array $requireGroups = []) {} }\n}\nif (!class_exists(\\TYPO3\\CMS\\Extbase\\Attribute\\RateLimit::class)) {\n #[\\Attribute(\\Attribute::TARGET_METHOD)]\n final class RateLimit { public function __construct(int $limit, string $interval) {} }\n}\n```\n\n```php\n// ext_localconf.php\nif ((new \\TYPO3\\CMS\\Core\\Information\\Typo3Version())->getMajorVersion() \u003c 14) {\n require __DIR__ . '/../Classes/Compat/AttributeStubs.php';\n}\n```\n\nThen in your controller — works on both versions:\n\n```php\nuse TYPO3\\CMS\\Extbase\\Attribute\\Authorize;\nuse TYPO3\\CMS\\Extbase\\Attribute\\RateLimit;\n\nfinal class LoginController\n{\n #[Authorize(requireLogin: false)]\n #[RateLimit(limit: 5, interval: '1m')]\n public function submitAction(): ResponseInterface { /* ... */ }\n}\n```\n\nTreat this as **progressive enhancement**: v14 users get real rate-limiting; v13 users get no-op stubs (the attributes are present but v13's dispatcher ignores them). v15 will remove the stub loader when v13 support is dropped.\n\n> **Why not `class_exists()` at runtime?** PHP attributes are declarative syntax attached to methods/classes at parse time. You can't wrap an attribute in a runtime conditional. The only valid alternatives are: (a) ship the stub classes, (b) branch the whole class definition (two controller files selected via service factory), (c) forgo the attribute on v13.\n\n---\n\n## CI matrix\n\n```yaml\nmatrix:\n include:\n - php: '8.2'\n typo3: '^13.4'\n - php: '8.3'\n typo3: '^13.4'\n - php: '8.4'\n typo3: '^13.4'\n - php: '8.2'\n typo3: '^14.3'\n - php: '8.3'\n typo3: '^14.3'\n - php: '8.4'\n typo3: '^14.3'\n - php: '8.5'\n typo3: '^14.3'\n```\n\nPHPUnit major is unified: testing-framework `^9` works with both v13 and v14 cores.\n\n---\n\n## Conformance scoring adjustments\n\n| Criterion | Single v14 | Dual v13+v14 |\n|---|---|---|\n| Uses v13-only API (e.g. `HashService`, magic finders) | N/A | -10 points |\n| Uses v14-only API with guard | N/A | +0 |\n| Uses v14-only API without guard | N/A | Critical — blocks v13 install |\n| No deprecated API in v14 cycle | +0 | +0 |\n| `#[Authorize]` with guard for v13 | N/A | +3 excellence |\n| `#[RateLimit]` with guard for v13 | N/A | +2 excellence |\n| CI tests v13 AND v14 on PHP 8.2 + 8.3 minimum | Required | Required |\n| CI also tests PHP 8.5 on v14 | +1 excellence | +1 excellence |\n\n---\n\n## Documentation example\n\n```markdown\n## Compatibility\n\n| TYPO3 | PHP | Status |\n|---|---|---|\n| 14.3 LTS | 8.2 – 8.5 | Supported (current LTS; gold standard) |\n| 13.4 LTS | 8.2 – 8.4 | Supported |\n| 12.4 LTS | 8.2 – 8.3 | Use v2.x of this extension |\n```\n\n---\n\n## Checklist for v13+v14 dual support\n\n- [ ] `composer.json`: `\"typo3/cms-core\": \"^13.4 || ^14.3\"`\n- [ ] `composer.json` carries full metadata (don't rely on `ext_emconf.php` only — deprecated)\n- [ ] `ext_emconf.php`: constraint `13.4.0-14.3.99`, PHP `8.2.0-8.5.99`\n- [ ] No `$GLOBALS['TSFE']` anywhere — use `$request` attributes\n- [ ] No `HashService` from Extbase — use adapter or Core HashService\n- [ ] No magic Extbase repo finders (`findByX`, `findOneByX`, `countByX`)\n- [ ] Fluid VHs strict-typed (`registerArgument` with type, `render(): string`)\n- [ ] No leading-underscore variable names in Fluid templates\n- [ ] No removed TCA options (`subtype_value_field`, `control.searchFields`, `eval=year`, `is_static`, `pages.url`, `tt_content.list_type`)\n- [ ] No removed hooks in EXT:form (migrated to PSR-14 events)\n- [ ] Rector set: `UP_TO_TYPO3_13` (NOT `UP_TO_TYPO3_14`)\n- [ ] CI matrix exercises both v13 and v14\n- [ ] `ext_tables.php` split preview into `Configuration/Backend/*.php` (future-proof for v15)\n- [ ] `#[Authorize]` / `#[RateLimit]` on sensitive actions (guarded with `class_exists()`)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10334,"content_sha256":"6843dc0b5039881370baf8b0c8a1575c1fabd5badba7bfa9c54c72b527c32b66"},{"filename":"references/v14-deprecations.md","content":"# TYPO3 v14 Removals & Deprecations — Conformance Reference\n\n**Sources:** TYPO3 Core Changelog [14.0](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/14.0/Index.html) · [14.1](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/14.1/Index.html) · [14.2](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/14.2/Index.html) · [14.3](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/14.3/Index.html)\n**Purpose:** Score extensions against v14 removals (must fix now) and v14 deprecations (must fix before v15).\n**Release context:** v14.3 LTS released 2026-04-21. v14.0 landed all 98 breaking changes; v14.1/14.2/14.3 are feature-freeze / stabilization.\n\n---\n\n## Part 1 — Removed in v14.0 (must fix before v14 upgrade)\n\nBreak the code. Conformance: if found, flag as **critical issue** for any extension claiming v14 support.\n\n### 1.1 Fluid 5 strict typing (#108148)\n\n**Removed:**\n- `\\TYPO3\\CMS\\Fluid\\View\\StandaloneView`\n- `\\TYPO3\\CMS\\Fluid\\View\\TemplateView`\n- `\\TYPO3\\CMS\\Fluid\\View\\AbstractTemplateView`\n- `renderStatic()` on `AbstractViewHelper`\n- ViewHelpers without typed arguments/return types\n- Variable names starting with `_` in Fluid templates\n- `ViewHelperResolver` standalone methods (#104223)\n- `overrideArgument` in Standalone (#104463)\n\n**Detection:**\n```bash\ngrep -rn 'StandaloneView\\|AbstractTemplateView\\|renderStatic' Classes/ --include='*.php'\ngrep -rn 'class.*ViewHelper' Classes/ViewHelpers/ --include='*.php' | grep -vE 'function render\\(.*\\):'\ngrep -rEn '\\{_[a-zA-Z]' Resources/Private --include='*.html'\n```\n\n**Replacement:** Inject `\\TYPO3\\CMS\\Core\\View\\ViewFactoryInterface`; typed `render(): string` / arguments.\n\n---\n\n### 1.2 Extbase annotation + magic finders (#107229, #105377)\n\n**Removed:**\n- Docblock annotations namespace (`@TYPO3\\CMS\\Extbase\\Annotation\\...`) — use PHP attributes\n- `findByX()`, `findOneByX()`, `countByX()` magic methods\n- `TYPO3.CMS.Extbase` shorthand namespace\n- `Extbase\\Security\\Cryptography\\HashService` class\n- `Backend\\Toolbar\\Enumeration\\InformationStatus`\n- `Core\\Type\\Enumeration`, `Core\\Type\\Icon\\IconState`\n- `GeneralUtility::hmac()`, `MathUtility::convertToPositiveInteger()`\n- `BackendUtility::getTcaFieldConfiguration()`, `BackendUtility::thumbCode()`\n- `ExtensionManagementUtility::addPItoST43/addPageTSConfig/addUserTSConfig/getExtensionIcon`\n- `ExtensionUtility::PLUGIN_TYPE_PLUGIN` constant\n- `Icon::SIZE_*`, `AbstractFile::FILETYPE_*` constants\n\n**Detection:**\n```bash\ngrep -rn '@Extbase\\\\Annotation\\|->findBy[A-Z]\\|->findOneBy[A-Z]\\|->countBy[A-Z]' Classes/ --include='*.php'\ngrep -rn 'HashService\\|GeneralUtility::hmac\\|Icon::SIZE_\\|FILETYPE_' Classes/ --include='*.php'\n```\n\n**Replacement:** `#[Validate]`, `#[IgnoreValidation]` attributes; `createQuery()` builder; symmetric cipher service (#108002); enum types.\n\n---\n\n### 1.3 TypoScriptFrontendController removal (#107831)\n\n**Removed:**\n- `\\TYPO3\\CMS\\Frontend\\Controller\\TypoScriptFrontendController` class\n- `$GLOBALS['TSFE']` access\n- `$request->getAttribute('frontend.controller')`\n- `$contentObjectRenderer->getTypoScriptFrontendController()`\n\n**Detection:**\n```bash\ngrep -rn \"GLOBALS\\['TSFE'\\]\\|TypoScriptFrontendController\\|getAttribute('frontend.controller')\" Classes/ --include='*.php'\n```\n\n**Replacement:**\n- Page data: `$request->getAttribute('frontend.page.information')`\n- Header/footer: `PageRenderer::addHeaderData()` / `addFooterData()`\n- Site: `$request->getAttribute('site')`\n- Language: `$request->getAttribute('language')`\n\n---\n\n### 1.4 FAL strong-typing (#106427, #107403, #107735)\n\n**Removed:**\n- `AbstractFile::getIdentifier()` / `setIdentifier()`\n- `FileInterface::rename()` (moved to concrete `File`)\n- `FileNameValidator` custom regex in `__construct()`\n- Metadata extractor registration via `registerExtractionService()`\n- Backend avatar provider registration via `$GLOBALS`\n- `LocalPreviewHelper`, `LocalCropScaleMaskHelper` classes\n\n**Replacement:** Use concrete `File::rename()`; implement `MetadataExtractorInterface`; register via autoconfigure tags; `Folder->getSubFolder()` throws `FolderDoesNotExistException`.\n\n---\n\n### 1.5 Cache interfaces strict-typed (#107315)\n\n**Removed:**\n- `AbstractBackend::__construct($context, $options)` signature — `$context` dropped\n- `FreezableBackendInterface` (#107310)\n- `CacheHashCalculator` public methods (#108277)\n\n**Detection:** Any extension implementing a custom cache backend must align with new strict-typed interfaces (`BackendInterface`, `PhpCapableBackendInterface`, `TransientBackendInterface`, `FrontendInterface`).\n\n---\n\n### 1.6 TCA sweep (#105377, #106863, #106972, #107047)\n\n**Removed TCA options:**\n| Option | Replacement |\n|---|---|\n| `types.subtype_value_field` | record-type flex-form handling |\n| `types.subtypes_addlist` | — |\n| `types.subtypes_excludelist` | — |\n| `control.searchFields` | configurable search TCA (#106972, #106976) |\n| `control.is_static` (#106863) | — |\n| `eval=year` (#98070) | — |\n| value picker `prepend`/`append` modes (#107677) | — |\n| `interface` settings for list view (#106412) | — |\n| `pages.url` field (#17406) | typolink page type |\n| `tt_content.list_type` + `tt_content.list` (#105538, #105377) | Register plugins as CTypes instead — use `ExtensionUtility::registerPlugin()` (v13+) or direct TCA overrides; remove `addPItoST43()` and `addPlugin()` with a plugin-type argument (both removed in v14). |\n| flex pointer field functionality (#107047) | — |\n| duplicate doktype restriction config (#106949) | — |\n| Scheduler frequency options (moved to TCA #107488) | native TCA table (#106739) |\n\n**Detection:**\n```bash\ngrep -rn 'subtype_value_field\\|subtypes_addlist\\|searchFields\\|is_static\\|eval.*year\\|list_type' Configuration/TCA/ --include='*.php'\n```\n\n---\n\n### 1.7 Backend / UI\n\n**Removed:**\n- Modal migrated from Bootstrap to native `\u003cdialog>` (#107443) — JS API changed\n- \"Database Relations\" module (#97151)\n- Reports interfaces (#107791)\n- `@typo3/backend/document-save-actions.js`, `@typo3/backend/wizard.js`, `@typo3/t3editor/*`\n- Backend layout data provider `$GLOBALS` registration (#107784)\n- `LoginProviderInterface::render()` → implement `modifyView()`\n- Workspace \"Freeze Editing\" (#107323)\n- Button API reworked (#107884, #107823)\n\n---\n\n### 1.8 EXT:form hooks (use PSR-14 events)\n\nAll **removed** in v14.0:\n- `afterBuildingFinished` (#98239)\n- `beforeFormCreate` (#107343)\n- `beforeFormDuplicate` (#107380)\n- `beforeFormDelete` (#107382)\n- `beforeFormSave` (#107388)\n- `initializeFormElement` (#107518)\n- `beforeRemoveFromParentRenderable` (#107528)\n- `afterInitializeCurrentPage` (#107566)\n- `afterSubmit` (#107568)\n- `beforeRendering` (#107569)\n- Legacy form templates (#106596)\n\n---\n\n### 1.9 TypoScript / TSconfig\n\n**Removed:**\n- `\u003cINCLUDE_TYPOSCRIPT: ...>` — use `@import`\n- TypoScript condition `getTSFE()` (#107473)\n- `config.tx_extbase.persistence.updateReferenceIndex` toggle (#106041)\n- TSconfig `options.pageTree.backgroundColor`\n- `$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig']`, `defaultUserTSconfig`\n\n**New opt-in required:**\n- TypoScript/TSconfig callables (userFunc) now require explicit allow-listing (#108054)\n\n---\n\n### 1.10 Security / crypto changes\n\n**Changed behavior:**\n- HMAC algorithm strengthened: SHA1 → SHA256 family (#106307). Persisted HMACs minted on v13 may need regeneration.\n\n**Removed:**\n- `AuthenticationService` static function parameter (#106869)\n- `AfterMailerInitializationEvent` (#105809)\n- `MailMessage->send()` (#108097)\n- `Extbase HashService` — replaced by `TYPO3\\CMS\\Core\\Crypto\\HashService` + built-in symmetric cipher service (#108002)\n\n---\n\n### 1.11 Install / packaging\n\n**Removed:**\n- `typo3/install.php` entry point — integrated into backend routing (#107536, BC preserved)\n- `typo3/index.php` legacy entry + composer.json setting\n- `Environment::getComposerRootPath()` (#107482)\n\n**New requirements:**\n- **Classic mode: `composer.json` is now required** (#108310). Extensions detected via composer.\n- Extension title auto-populated from `composer.json` (#108304).\n\n---\n\n## Part 2 — Deprecated in v14 (must fix before v15)\n\nNo removals; all removed in v15.0.\n\n### 2.1 v14.0 deprecations\n\n| Ticket | Item | Replacement |\n|---|---|---|\n| #93981 | `GraphicalFunctions->gif_or_jpg` | native-format decisions |\n| #97559 | Array-of-config to Extbase attributes | individual attrs |\n| #97857 | `__inheritances` form config operator | explicit inheritance |\n| #98453 | Scheduler task via `SC_OPTIONS` | `#[AsScheduledTask]` attribute |\n| #106393 | Various `BackendUtility` methods | see ticket |\n| #106405 | `AbstractTypolinkBuilder->build()` | `TypolinkBuilderInterface::build()` |\n| #106527 | `markFieldAsChanged()` location | FormEngine main module |\n| #106618 | `GeneralUtility::resolveBackPath` | `PathUtility::resolveBackPath` |\n| #106947 | Upgrade wizard interfaces | moved to EXT:core |\n| #106969 | TSconfig `auth.BE.redirectToURL` | new mechanism |\n| #107047 | `ExtensionManagementUtility::addPiFlexFormValue()` | — |\n| #107057 | Auto-render assets sections | explicit render |\n| #107225 | Boolean sort direction in `FileList->start()` | enum |\n| #107229 | Annotation namespace of Extbase attributes | PHP attrs |\n| #107287 | `FileCollectionRegistry->addTypeToTCA()` | — |\n| #107413 | `PathUtility::getRelativePath*` methods | — |\n| #107436 | Custom localization Parsers | Symfony Translation Loaders |\n| #107537 | `GeneralUtility::createVersionNumberedFilename`, `FilePathSanitizer`, `PathUtility::getPublicResourceWebPath` | System Resource API |\n| #107550 | Table GC task config via `$GLOBALS` | TCA |\n| #107562 | IP Anonymization task config via `$GLOBALS` | TCA |\n| #107648 | `InfoboxViewHelper::STATE_*` constants | enum |\n| #107725 | Array for password in Redis cache backend auth | username+password fields |\n| #107813 | `MetaInformation` API in DocHeader | new API |\n| #107823 | ButtonBar/Menu/MenuRegistry `make*` methods | ComponentFactory |\n| #107938 | Unused XLIFF files | — |\n| #107963 | sys_redirect default type renamed to `default` | — |\n| #108008 | Manual shortcut button creation | — |\n| #108148 | Fluid `LenientArgumentProcessor` | strict arg processing |\n| #108227 | `#[IgnoreValidation]` / `#[Validate]` at **method level** | attach to parameter instead |\n\n### 2.2 v14.1 deprecations\n\n| Ticket | Item |\n|---|---|\n| #108086 | Deprecation error on using deprecated labels |\n| #108524 | Fluid namespaces in `TYPO3_CONF_VARS` |\n| #108667 | `CommandNameAlreadyInUseException` |\n\n### 2.3 v14.2 deprecations\n\n| Ticket | Item | Replacement |\n|---|---|---|\n| #69190 | Random password generator for FE/BE users | new wizard |\n| #100887 | `useNonce` argument in `f:asset:css`/`script` | CSP hashes |\n| #107068 | `fieldExplanationText` | `description` |\n| #107208 | `\u003cf:debug.render>` ViewHelper | `\u003cf:debug>` |\n| #107802 | Array for password in Redis session backend | username+password |\n| **#108345** | **`ext_emconf.php` as primary metadata** | **`composer.json`** |\n| #108557 | TCA `allowedRecordTypes` for Page Types | `isViewable` (#97898) |\n| #108568 | `BackendUserAuthentication::recordEditAccessInternals()` | — |\n| #108653 | **Form file-based (YAML) storage** | **DB storage** |\n| #108761 | `BackendUtility` TSconfig methods | — |\n| #108810 | `BackendUtility` localization methods | — |\n| #108843 | `ExtensionManagementUtility::addFieldsToUserSettings` | TCA |\n| #108963 | `PageRenderer->addInlineLanguageDomain()` | — |\n| #109027 | `language:update` command moved to EXT:core | — |\n| #109029 | FormEngine `doSave` hidden field | — |\n| #109102 | FormEngine `additionalHiddenFields` key | — |\n| #109152 | Form DatePicker element | — |\n| **#109171** | **Bootstrap tab events** | native events |\n| #109192 | FormEngine OuterWrapContainer | — |\n| #109196 | `doktypesToShowInNewPageDragArea` user TSconfig | — |\n| #109230 | FormResultCompiler | — |\n| #109280 | FormEngine TcaDescription fieldInformation | — |\n| #109286 | Explicit request handling in PageRenderer | — |\n| #109295 | `DatabaseWriter::setLogTable()/getLogTable()` | — |\n| #109306 | Form editor stage template rendering | — |\n| #109329 | `PageRenderer` get() methods | — |\n| #109409 | Arbitrary resource access + \"Allowed paths\" config | System Resource API |\n| #109412 | TypoScript-based form YAML registration | auto-discovery |\n\n### 2.4 v14.3 deprecations (v15-preparation, last chance)\n\n| Ticket | Item | Replacement |\n|---|---|---|\n| #107931 | Lowlevel `DatabaseIntegrityCheck` | — |\n| #109107 | CacheAction key `href` | JSON response |\n| **#109438** | **`ext_tables.php` in extensions** | **`Configuration/Backend/Modules.php`, `Routes.php`, `TCA/Overrides/be_users.php` + `pages.php`** |\n| #109517 | `AddJavaScriptModulesEvent` in EXT:setup (ext merged into backend) | — |\n| #109519 | `BackendUtility` item list label methods | — |\n| #109523 | `GeneralUtility::isOnCurrentHost()` without PSR-7 request | pass `$request` |\n| #109529 | Page module section markup events | — |\n| #109544 | `GeneralUtility::sanitizeLocalUrl()` without PSR-7 request | pass `$request` |\n| #109548 | `GeneralUtility::locationHeaderUrl()` without PSR-7 request | pass `$request` |\n| #109551 | `GeneralUtility::getIndpEnv()` | PSR-7 request attributes |\n| #109575 | Various `ContentObjectRenderer` properties/methods | — |\n\n**Direction of travel:** v15 drives out superglobals and static state in favor of injected PSR-7 request + stateless services.\n\n---\n\n## Part 3 — New v14 capabilities to recommend (excellence bonus)\n\nAward excellence bonus for extensions that adopt these early:\n\n| Feature | Ticket | Bonus |\n|---|---|---|\n| `#[Authorize(requireLogin: true, requireGroups: [...])]` on sensitive actions | #107826 | +2 |\n| `#[RateLimit(limit: N, interval: 'Ns')]` on login / password-reset / import | #108982, #109080 | +2 |\n| TCA `type=country` for country fields | #99911 | +1 |\n| `allowedRecordTypes` per colPos (content element restrictions) | #108623 | +1 |\n| Symfony Validators via Extbase attributes (`#[Assert\\\\...]`) | #106945 | +1 |\n| SRI `integrity` attribute on `f:asset:css/script` | #109187 | +1 |\n| `f:render.contentArea` / `f:render.record` / `f:render.text` | #108726, #108868 | +1 |\n| Symfony Translation Component integration + XLIFF 2.x | #107436, #107710 | +1 |\n| `fluid:analyze` CLI clean | #108763 | +1 |\n| `composer.json`-only metadata (no `ext_emconf.php`) | #108345 | +2 |\n| No `ext_tables.php` (split to `Configuration/Backend/*.php`) | #109438 | +2 |\n| Site configurations shipped as Site Sets | v13+ | +1 |\n\n---\n\n## Part 4 — v14-specific post-upgrade operations\n\n### Important #109585 — serialized credential data fix\n\n**Scope:** Any site that ran **v14.2** (not 14.0, not 14.1).\n**Issue:** Password change during v14.2 runtime may have persisted serialized plaintext into `be_users.uc`/`user_settings`.\n**Action:** Run the v14.3 upgrade wizard that unserializes, strips password fields, re-serializes.\n**Detection:** Install Tool → Upgrade → Upgrade Wizards (the wizard auto-appears if applicable).\n\nSee [Important-109585-SerializedCredentialDataInBeUsersDatabaseTable](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/14.3/Important-109585-SerializedCredentialDataInBeUsersDatabaseTable.html).\n\n---\n\n## Part 5 — Quick conformance grep recipes for v14\n\n```bash\n# Removals (must fix)\ngrep -rn 'HashService\\|GeneralUtility::hmac(' Classes/ --include='*.php'\ngrep -rn '->findBy[A-Z]\\|->findOneBy[A-Z]\\|->countBy[A-Z]' Classes/ --include='*.php'\ngrep -rn \"GLOBALS\\['TSFE'\\]\\|TypoScriptFrontendController\" Classes/ --include='*.php'\ngrep -rn 'StandaloneView\\|AbstractTemplateView\\|renderStatic' Classes/ --include='*.php'\ngrep -rn '@Extbase\\\\Annotation' Classes/ --include='*.php'\ngrep -rn 'subtype_value_field\\|subtypes_addlist\\|control.searchFields\\|eval.*year\\|list_type' Configuration/TCA/ --include='*.php'\n\n# Deprecations (fix before v15)\n[ -f ext_tables.php ] && echo \"WARN: ext_tables.php deprecated (#109438)\"\n[ -f ext_emconf.php ] && echo \"INFO: ext_emconf.php present - ensure composer.json has complete metadata (#108345)\"\ngrep -rn 'useNonce' Resources/Private --include='*.html'\ngrep -rn 'GeneralUtility::getIndpEnv\\|GeneralUtility::sanitizeLocalUrl\\|GeneralUtility::locationHeaderUrl\\|GeneralUtility::isOnCurrentHost' Classes/ --include='*.php'\n\n# Excellence / adoption\ngrep -rn '#\\[Authorize\\|#\\[RateLimit' Classes/Controller --include='*.php'\ngrep -rn 'f:render.contentArea\\|f:render.record\\|f:render.text' Resources/Private --include='*.html'\n```\n\n---\n\n## Part 6 — Migration tooling\n\n- **TYPO3 Rector** — 47 v14 rules in `rules/TYPO314/v{0,2}/`. Invoke with `vendor/bin/rector process`; target individual rules with `--only \u003cRuleClass>` (new CLI in v14 Rector).\n- **TYPO3 Fractor** (`a9f/typo3-fractor`) — handles TypoScript, FlexForm XML, `composer.json` normalization, `.htaccess`, `ext_emconf.php` → `composer.json` migration, `ext_tables.php` split-off helpers.\n- **Extension Scanner** — Install Tool → Upgrade → Scan Extension Files. Matchers cover the #105377 umbrella, Fluid 5 strictness, FAL strong-typing, cache interface strict-typing.\n- **PHPStan** — `saschaegerer/phpstan-typo3` bundle covers strict-types expectations.\n\n---\n\n## Scoring impact\n\n| Situation | Score effect |\n|---|---|\n| Any Part 1 removal present | Critical issue — **block v14 support** |\n| Extension supports v14 but uses HashService/TSFE/magic finders | -20 points (architecture) |\n| `ext_tables.php` present in extension claiming v14.3+ | -5 points + deprecation warning |\n| `ext_emconf.php` still sole metadata source | -3 points |\n| Uses `#[Authorize]`/`#[RateLimit]` on sensitive actions | +4 excellence bonus |\n| No removed/deprecated usage, full v14 adoption | +8 excellence bonus |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":17785,"content_sha256":"df638722aa0b657b1071f1858594521991db8836ff75530991dfab6ddce4085f"},{"filename":"references/version-requirements.md","content":"# TYPO3 and PHP Version Requirements\n\n**Purpose:** Definitive version compatibility matrix for TYPO3 conformance checking\n**Last Updated:** 2025-01-18\n\n## Official Version Support Matrix\n\n### TYPO3 12 LTS\n\n**Release:** April 2022\n**End of Life:** October 2026\n**PHP Support:** 8.1 - 8.4\n\n| PHP Version | Support Status | Since TYPO3 Version |\n|------------|----------------|---------------------|\n| 8.1 | ✅ Supported | 12.0.0 |\n| 8.2 | ✅ Supported | 12.1.0 |\n| 8.3 | ✅ Supported | 12.4.0 |\n| 8.4 | ✅ Supported | 12.4.24 (Dec 2024) |\n\n**Minimum Requirements:**\n- PHP: 8.1.0\n- Database: MariaDB 10.4+ / MySQL 8.0+ / PostgreSQL 10.0+ / SQLite 3.8.3+\n\n### TYPO3 13 LTS\n\n**Release:** October 2024\n**End of Life:** April 2028\n**PHP Support:** 8.2 - 8.4\n\n| PHP Version | Support Status | Since TYPO3 Version |\n|------------|----------------|---------------------|\n| 8.1 | ❌ Not Supported | - |\n| 8.2 | ✅ Supported | 13.0.0 |\n| 8.3 | ✅ Supported | 13.0.0 |\n| 8.4 | ✅ Supported | 13.4.0 |\n\n**Minimum Requirements:**\n- PHP: 8.2.0\n- Database: MariaDB 10.4+ / MySQL 8.0+ / PostgreSQL 10.0+ / SQLite 3.8.3+\n\n## Conformance Checker Standards\n\nThe TYPO3 conformance checker validates extensions against:\n\n**Target Versions:**\n- TYPO3: 12.4 LTS / 13.x\n- PHP: 8.1 / 8.2 / 8.3 / 8.4\n- PSR Standards: PSR-11 (DI), PSR-12 (Coding Style), PSR-14 (Events), PSR-15 (Middleware)\n\n**Why This Range:**\n- Covers both TYPO3 12 LTS and 13 LTS\n- PHP 8.1+ ensures support for all modern PHP features used in TYPO3 extensions\n- Extensions can target TYPO3 12 (PHP 8.1+) and/or TYPO3 13 (PHP 8.2+)\n\n## Extension composer.json Examples\n\n### TYPO3 12 LTS Only\n```json\n{\n \"require\": {\n \"php\": \"^8.1 || ^8.2 || ^8.3 || ^8.4\",\n \"typo3/cms-core\": \"^12.4\"\n }\n}\n```\n\n### TYPO3 13 LTS Only\n```json\n{\n \"require\": {\n \"php\": \"^8.2 || ^8.3 || ^8.4\",\n \"typo3/cms-core\": \"^13.4\"\n }\n}\n```\n\n### TYPO3 12 and 13 LTS (Recommended for New Extensions)\n```json\n{\n \"require\": {\n \"php\": \"^8.2 || ^8.3 || ^8.4\",\n \"typo3/cms-core\": \"^12.4 || ^13.4\"\n }\n}\n```\n\n**Note:** When targeting both TYPO3 12 and 13, use PHP 8.2+ as minimum to satisfy TYPO3 13's requirements.\n\n## PHP Feature Availability\n\n### PHP 8.1 Features (TYPO3 12+)\n- Enumerations\n- Readonly properties\n- First-class callable syntax\n- New in initializers\n- Pure intersection types\n- Never return type\n- Final class constants\n- Fibers\n\n### PHP 8.2 Features (TYPO3 13+)\n- Readonly classes\n- Disjunctive Normal Form (DNF) types\n- Null, false, and true as standalone types\n- Constants in traits\n- Deprecated dynamic properties\n\n### PHP 8.3 Features (TYPO3 12.4+ / 13+)\n- Typed class constants\n- Dynamic class constant fetch\n- `#[\\Override]` attribute\n- `json_validate()` function\n\n### PHP 8.4 Features (TYPO3 12.4.24+ / 13.4+)\n- Property hooks\n- Asymmetric visibility\n- New array functions\n- HTML5 support in DOM extension\n\n### PHP 8.5 Features (TYPO3 13.4+ / 14.0+)\n- Pipe operator (`|>`) for functional composition\n- `Locale` class for locale handling\n- `array_first()` / `array_last()` helper functions\n- Closures from callable strings improvements\n- Deprecated: implicit `float` to `int` conversions with precision loss\n\n**Conformance impact:**\n- Update `ext_emconf.php` PHP range to include 8.5: `'php' => '8.2.0-8.5.99'`\n- Update `composer.json` PHP constraint: `\"php\": \"^8.2\"`\n- Check for implicit float-to-int conversions (`(int)$floatVar` without `round()`)\n- Test extension on PHP 8.5 before declaring support\n\n## Migration Paths\n\n### From TYPO3 11 to 12\n1. Update PHP to 8.1+ (recommended: 8.2+)\n2. Update extension to TYPO3 12 compatibility\n3. Test thoroughly on PHP 8.2+ for future TYPO3 13 compatibility\n\n### From TYPO3 12 to 13\n1. Ensure PHP 8.2+ is already in use\n2. Update TYPO3 dependencies to ^13.4\n3. Remove deprecated API usage\n4. Update Services.yaml for TYPO3 13 changes (if any)\n\n### From TYPO3 13 to 14\n1. Ensure PHP 8.3+ is already in use (recommended: 8.5+)\n2. Update TYPO3 dependencies to ^14.0\n3. Replace `$GLOBALS['TCA']` with `TcaSchemaFactory` DI\n4. Remove all v13-deprecated APIs (ext_localconf.php addService, etc.)\n5. Test on PHP 8.5\n\n## Deprecation Timeline\n\n**PHP Versions:**\n- PHP 8.0: End of Life - November 2023 (Not supported by TYPO3 12/13)\n- PHP 8.1: Security fixes until November 2025 (TYPO3 12 minimum)\n- PHP 8.2: Security fixes until December 2026 (TYPO3 13 minimum)\n- PHP 8.3: Security fixes until December 2027\n- PHP 8.4: Security fixes until December 2028\n- PHP 8.5: Security fixes until December 2029\n\n**Recommendation:** Target PHP 8.2+ for extensions supporting TYPO3 13. Target PHP 8.3+ for TYPO3 14 readiness.\n\n## References\n\n- [TYPO3 12 System Requirements](https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/Installation/Index.html)\n- [TYPO3 13 System Requirements](https://docs.typo3.org/m/typo3/reference-coreapi/13.4/en-us/Administration/Installation/SystemRequirements/Index.html)\n- [PHP Release Cycles](https://www.php.net/supported-versions.php)\n- [TYPO3 Roadmap](https://typo3.org/cms/roadmap)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5006,"content_sha256":"57cd90ceebac001875c1a36fcd4c8ba989c2b7ba6522963659b5665b5e73f961"},{"filename":"scripts/check-architecture.sh","content":"#!/usr/bin/env bash\n\n#\n# TYPO3 PHP Architecture Conformance Checker\n#\n# Validates dependency injection, services, events, and architectural patterns\n#\n\nset -e\n\nPROJECT_DIR=\"${1:-.}\"\ncd \"${PROJECT_DIR}\"\n\necho \"## 3. PHP Architecture Conformance\"\necho \"\"\n\nhas_issues=0\n\n### Check for Services.yaml\necho \"### Dependency Injection Configuration\"\necho \"\"\n\nif [ -f \"Configuration/Services.yaml\" ]; then\n echo \"- ✅ Configuration/Services.yaml present\"\n\n # Check if it has basic DI configuration\n if grep -q \"autowire: true\" Configuration/Services.yaml; then\n echo \" - ✅ Autowiring enabled\"\n else\n echo \" - ⚠️ Autowiring not enabled\"\n fi\n\n if grep -q \"autoconfigure: true\" Configuration/Services.yaml; then\n echo \" - ✅ Autoconfiguration enabled\"\n else\n echo \" - ⚠️ Autoconfiguration not enabled\"\n fi\nelse\n echo \"- ❌ Configuration/Services.yaml missing (CRITICAL)\"\n has_issues=1\nfi\n\n### Check for PROHIBITED patterns\necho \"\"\necho \"### Prohibited Pattern Detection (Zero Tolerance)\"\necho \"\"\n\n# Check for $GLOBALS usage (ALWAYS prohibited in Classes/)\nglobals_count=$(grep -rn '\\$GLOBALS\\[' Classes/ 2>/dev/null | wc -l)\nif [ $globals_count -eq 0 ]; then\n echo \"- ✅ No \\$GLOBALS access found\"\nelse\n echo \"- ❌ **CRITICAL:** ${globals_count} instances of \\$GLOBALS access found\"\n echo \" - \\$GLOBALS['TCA'] → Use TcaSchemaFactory\"\n echo \" - \\$GLOBALS['BE_USER'] → Use Context API\"\n echo \" - \\$GLOBALS['TSFE'] → Use PSR-7 Request or DI\"\n echo \" - \\$GLOBALS['TYPO3_CONF_VARS'] → Use ExtensionConfiguration\"\n echo \"\"\n grep -rn '\\$GLOBALS\\[' Classes/ 2>/dev/null | head -10 || true\n has_issues=1\nfi\n\n# Check for GeneralUtility::makeInstance (prohibited except Form Elements and Tasks)\n# First check total count\ntotal_makeinstance=$(grep -r \"GeneralUtility::makeInstance\" Classes/ 2>/dev/null | wc -l)\n\nif [ $total_makeinstance -eq 0 ]; then\n echo \"- ✅ No GeneralUtility::makeInstance() usage found\"\nelse\n # Count allowed exceptions (Form/Element/ and Task/)\n allowed_makeinstance=$(grep -r \"GeneralUtility::makeInstance\" Classes/ 2>/dev/null | grep -E '(Form/Element/|Task/)' | wc -l)\n prohibited_makeinstance=$((total_makeinstance - allowed_makeinstance))\n\n if [ $prohibited_makeinstance -eq 0 ]; then\n echo \"- ✅ GeneralUtility::makeInstance() only in allowed contexts (Form Elements, Tasks)\"\n echo \" - ${allowed_makeinstance} allowed occurrences in Form/Element/ or Task/\"\n else\n echo \"- ❌ **CRITICAL:** ${prohibited_makeinstance} prohibited instances of GeneralUtility::makeInstance() found\"\n echo \" - Use constructor dependency injection instead\"\n echo \" - Allowed only in: Form/Element/, Task/ directories\"\n echo \"\"\n grep -rn \"GeneralUtility::makeInstance\" Classes/ 2>/dev/null | grep -vE '(Form/Element/|Task/)' | head -10 || true\n has_issues=1\n fi\nfi\n\n### Check for constructor injection\necho \"\"\necho \"### Dependency Injection Patterns\"\necho \"\"\n\n# Check for constructors with dependencies\nconstructors=$(grep -r \"public function __construct\" Classes/ 2>/dev/null | wc -l)\nif [ $constructors -gt 0 ]; then\n echo \"- ✅ ${constructors} classes use constructors (potential DI)\"\nelse\n echo \"- ⚠️ No constructor injection found\"\nfi\n\n# Check for method injection (inject* methods)\ninject_methods=$(grep -r \"public function inject[A-Z]\" Classes/ 2>/dev/null | wc -l)\nif [ $inject_methods -gt 0 ]; then\n echo \"- ⚠️ ${inject_methods} method injection patterns found (inject*)\"\n echo \" - Consider using constructor injection instead (more modern)\"\nfi\n\n### Check for PSR-14 events\necho \"\"\necho \"### Event System\"\necho \"\"\n\n# Check for event classes\nevent_classes=$(find Classes/ -type d -name \"Event\" 2>/dev/null || echo \"\")\nif [ -n \"$event_classes\" ]; then\n event_count=$(find Classes/ -path \"*/Event/*.php\" 2>/dev/null | wc -l)\n echo \"- ✅ ${event_count} event classes found in Classes/Event/\"\nelse\n echo \"- ⚠️ No Classes/Event/ directory found\"\nfi\n\n# Check for event listeners\nlistener_classes=$(find Classes/ -type d -name \"EventListener\" 2>/dev/null || echo \"\")\nif [ -n \"$listener_classes\" ]; then\n listener_count=$(find Classes/ -path \"*/EventListener/*.php\" 2>/dev/null | wc -l)\n echo \"- ✅ ${listener_count} event listeners found in Classes/EventListener/\"\nelse\n echo \"- ⚠️ No Classes/EventListener/ directory found\"\nfi\n\n### Check for Extbase patterns\necho \"\"\necho \"### Extbase Architecture\"\necho \"\"\n\n# Check for domain models\nif [ -d \"Classes/Domain/Model\" ]; then\n model_count=$(find Classes/Domain/Model/ -name \"*.php\" 2>/dev/null | wc -l)\n echo \"- ✅ ${model_count} domain models found\"\nelse\n echo \"- ℹ️ No Classes/Domain/Model/ (not using Extbase models)\"\nfi\n\n# Check for repositories\nif [ -d \"Classes/Domain/Repository\" ]; then\n repo_count=$(find Classes/Domain/Repository/ -name \"*.php\" 2>/dev/null | wc -l)\n echo \"- ✅ ${repo_count} repositories found\"\n\n # Check if repositories extend Repository\n proper_repos=$(grep -r \"extends.*Repository\" Classes/Domain/Repository/ 2>/dev/null | wc -l)\n if [ $proper_repos -gt 0 ]; then\n echo \" - ✅ Repositories extend base Repository class\"\n fi\nelse\n echo \"- ℹ️ No Classes/Domain/Repository/ (not using Extbase repositories)\"\nfi\n\n# Check for controllers\nif [ -d \"Classes/Controller\" ]; then\n controller_count=$(find Classes/Controller/ -name \"*.php\" 2>/dev/null | wc -l)\n echo \"- ✅ ${controller_count} controllers found\"\n\n # Check if controllers extend ActionController\n proper_controllers=$(grep -r \"extends ActionController\" Classes/Controller/ 2>/dev/null | wc -l)\n if [ $proper_controllers -gt 0 ]; then\n echo \" - ✅ Controllers extend ActionController\"\n fi\nfi\n\n### Check for PSR-15 middleware\necho \"\"\necho \"### Middleware\"\necho \"\"\n\nif [ -f \"Configuration/RequestMiddlewares.php\" ]; then\n echo \"- ✅ Configuration/RequestMiddlewares.php present\"\n\n middleware_count=$(find Classes/ -path \"*/Middleware/*.php\" 2>/dev/null | wc -l)\n if [ $middleware_count -gt 0 ]; then\n echo \" - ✅ ${middleware_count} middleware classes found\"\n fi\nelse\n echo \"- ℹ️ No Configuration/RequestMiddlewares.php (not using custom middleware)\"\nfi\n\necho \"\"\necho \"### Summary\"\necho \"\"\n\nif [ $has_issues -eq 0 ]; then\n echo \"- ✅ **PHP Architecture: PASSED**\"\nelse\n echo \"- ⚠️ **PHP Architecture: ISSUES FOUND**\"\nfi\n\necho \"\"\necho \"---\"\necho \"\"\n\nexit $has_issues\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6554,"content_sha256":"5a1de57f7de0fd88e53bba38a9c9bb1ca064cce880104cd7b9b80e033d8ea4f6"},{"filename":"scripts/check-coding-standards.sh","content":"#!/usr/bin/env bash\n\n#\n# TYPO3 Coding Standards Conformance Checker\n#\n# Validates PSR-12 compliance and TYPO3-specific code style\n#\n\nset -e\n\nPROJECT_DIR=\"${1:-.}\"\ncd \"${PROJECT_DIR}\"\n\necho \"## 2. Coding Standards Conformance\"\necho \"\"\n\nhas_issues=0\n\n# Find all PHP files in Classes/\nif [ ! -d \"Classes\" ]; then\n echo \"- ❌ Classes/ directory not found\"\n echo \"\"\n echo \"---\"\n echo \"\"\n exit 1\nfi\n\nphp_files=$(find Classes/ -name \"*.php\" 2>/dev/null || echo \"\")\n\nif [ -z \"$php_files\" ]; then\n echo \"- ⚠️ No PHP files found in Classes/\"\n echo \"\"\n echo \"---\"\n echo \"\"\n exit 0\nfi\n\ntotal_files=$(echo \"$php_files\" | wc -l)\necho \"**Total PHP files:** $total_files\"\necho \"\"\n\n### Check for strict types\necho \"### Strict Types Declaration\"\necho \"\"\nmissing_strict=0\nfor file in $php_files; do\n if ! grep -q \"declare(strict_types=1)\" \"$file\"; then\n missing_strict=$((missing_strict + 1))\n fi\ndone\n\nif [ $missing_strict -eq 0 ]; then\n echo \"- ✅ All files have declare(strict_types=1)\"\nelse\n echo \"- ❌ ${missing_strict} files missing declare(strict_types=1)\"\n has_issues=1\nfi\n\n### Check for old array syntax\necho \"\"\necho \"### Array Syntax\"\necho \"\"\nold_array_count=$(grep -r \"array(\" Classes/ 2>/dev/null | wc -l)\nif [ $old_array_count -eq 0 ]; then\n echo \"- ✅ No old array() syntax found\"\nelse\n echo \"- ❌ ${old_array_count} instances of old array() syntax (should use [])\"\n has_issues=1\nfi\n\n### Check for proper namespace\necho \"\"\necho \"### Namespace Structure\"\necho \"\"\nfiles_without_namespace=0\nfor file in $php_files; do\n if ! grep -q \"^namespace \" \"$file\"; then\n files_without_namespace=$((files_without_namespace + 1))\n fi\ndone\n\nif [ $files_without_namespace -eq 0 ]; then\n echo \"- ✅ All files have namespace declaration\"\nelse\n echo \"- ❌ ${files_without_namespace} files missing namespace declaration\"\n has_issues=1\nfi\n\n### Check for PHPDoc comments on classes\necho \"\"\necho \"### PHPDoc Comments\"\necho \"\"\nclasses_without_doc=0\nfor file in $php_files; do\n # Simple check: look for /** before class declaration\n if grep -q \"^class \" \"$file\" || grep -q \"^final class \" \"$file\"; then\n if ! grep -B 5 \"^class \\|^final class \" \"$file\" | grep -q \"/\\*\\*\"; then\n classes_without_doc=$((classes_without_doc + 1))\n fi\n fi\ndone\n\nif [ $classes_without_doc -eq 0 ]; then\n echo \"- ✅ All classes have PHPDoc comments\"\nelse\n echo \"- ⚠️ ${classes_without_doc} classes missing PHPDoc comments\"\nfi\n\n### Check naming conventions\necho \"\"\necho \"### Naming Conventions\"\necho \"\"\n\n# Check for snake_case in class names (should be UpperCamelCase)\nsnake_case_classes=$(grep -rE \"^(final )?class [a-z][a-z0-9_]*\" Classes/ 2>/dev/null | wc -l)\nif [ $snake_case_classes -gt 0 ]; then\n echo \"- ❌ ${snake_case_classes} classes using incorrect naming (should be UpperCamelCase)\"\n has_issues=1\nelse\n echo \"- ✅ Class naming follows UpperCamelCase convention\"\nfi\n\n### Check for tabs instead of spaces\necho \"\"\necho \"### Indentation\"\necho \"\"\nfiles_with_tabs=0\nfor file in $php_files; do\n if grep -qP \"\\t\" \"$file\"; then\n files_with_tabs=$((files_with_tabs + 1))\n fi\ndone\n\nif [ $files_with_tabs -eq 0 ]; then\n echo \"- ✅ No tabs found (using spaces for indentation)\"\nelse\n echo \"- ❌ ${files_with_tabs} files using tabs instead of spaces\"\n has_issues=1\nfi\n\n### Check for proper use statements\necho \"\"\necho \"### Use Statements\"\necho \"\"\n\n# Check if use statements are present and not duplicated\nduplicate_uses=$(grep -rh \"^use \" Classes/ 2>/dev/null | sort | uniq -d | wc -l)\nif [ $duplicate_uses -gt 0 ]; then\n echo \"- ⚠️ ${duplicate_uses} duplicate use statements found\"\nelse\n echo \"- ✅ No duplicate use statements\"\nfi\n\necho \"\"\necho \"### Summary\"\necho \"\"\n\nif [ $has_issues -eq 0 ]; then\n echo \"- ✅ **Coding standards: PASSED**\"\nelse\n echo \"- ⚠️ **Coding standards: ISSUES FOUND**\"\nfi\n\necho \"\"\necho \"---\"\necho \"\"\n\nexit $has_issues\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3986,"content_sha256":"22d162f56484650d1a801f7e418103408dfb643086c36d1684f289f4d5a81dd4"},{"filename":"scripts/check-conformance.sh","content":"#!/usr/bin/env bash\n\n#\n# TYPO3 Extension Conformance Checker\n#\n# Main script to orchestrate all conformance checks\n#\n\nset -e\n\n# Colors\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m'\n\n# Configuration\nPROJECT_DIR=\"${1:-.}\"\nREPORT_DIR=\"${PROJECT_DIR}/.conformance-reports\"\nTIMESTAMP=$(date +\"%Y%m%d_%H%M%S\")\nREPORT_FILE=\"${REPORT_DIR}/conformance_${TIMESTAMP}.md\"\n\n# Script directory\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\necho -e \"${BLUE}╔════════════════════════════════════════════════════════════╗${NC}\"\necho -e \"${BLUE}║ TYPO3 Extension Conformance Checker ║${NC}\"\necho -e \"${BLUE}╚════════════════════════════════════════════════════════════╝${NC}\"\necho \"\"\necho -e \"${BLUE}Standards Compliance Check:${NC}\"\necho -e \" • TYPO3 Version: ${YELLOW}12.4 LTS / 13.x${NC}\"\necho -e \" • PHP Version: ${YELLOW}8.1 / 8.2 / 8.3 / 8.4${NC}\"\necho -e \" • PSR Standard: ${YELLOW}PSR-12 (Extended Coding Style)${NC}\"\necho -e \" • Architecture: ${YELLOW}Dependency Injection, PSR-14 Events${NC}\"\necho \"\"\n\n# Create report directory\nmkdir -p \"${REPORT_DIR}\"\n\n# Check if directory exists\nif [ ! -d \"${PROJECT_DIR}\" ]; then\n echo -e \"${RED}✗ Error: Directory ${PROJECT_DIR} not found${NC}\"\n exit 1\nfi\n\ncd \"${PROJECT_DIR}\"\n\n# Check if this is a TYPO3 extension\nif [ ! -f \"composer.json\" ] && [ ! -f \"ext_emconf.php\" ]; then\n echo -e \"${RED}✗ Error: Not a TYPO3 extension (composer.json or ext_emconf.php not found)${NC}\"\n exit 1\nfi\n\necho -e \"${GREEN}✓ TYPO3 Extension detected${NC}\"\necho \"\"\n\n# Initialize report\ncat > \"${REPORT_FILE}\" \u003c\u003c'EOF'\n# TYPO3 Extension Conformance Report\n\n**Generated:** $(date -u +\"%Y-%m-%d %H:%M:%S UTC\")\n**Project:** $(basename \"$(pwd)\")\n\n## Standards Checked\n\nThis conformance check validates your extension against the following standards:\n\n| Standard | Version/Specification |\n|----------|----------------------|\n| **TYPO3 Core** | 12.4 LTS / 13.x |\n| **PHP** | 8.1 / 8.2 / 8.3 / 8.4 |\n| **Coding Style** | PSR-12 (Extended Coding Style) |\n| **Architecture** | Dependency Injection (PSR-11), PSR-14 Events, PSR-15 Middleware |\n| **Testing** | PHPUnit 10+, TYPO3 Testing Framework |\n| **Documentation** | reStructuredText (RST), TYPO3 Documentation Standards |\n\n**Reference Documentation:**\n- [TYPO3 Extension Architecture](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/)\n- [TYPO3 Coding Guidelines](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/)\n- [PHP Architecture](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/PhpArchitecture/)\n- [Testing Standards](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/)\n\n---\n\n## Summary\n\n| Category | Score | Status |\n|----------|-------|--------|\nEOF\n\n# Initialize scores\ntotal_score=0\nmax_score=100\n\necho -e \"${YELLOW}Running conformance checks...${NC}\"\necho \"\"\n\n# 1. File Structure Check\necho -e \"${BLUE}[1/7] Checking file structure...${NC}\"\nif bash \"${SCRIPT_DIR}/check-file-structure.sh\" \"${PROJECT_DIR}\" >> \"${REPORT_FILE}\"; then\n echo -e \"${GREEN} ✓ File structure check complete${NC}\"\n structure_score=18\nelse\n echo -e \"${YELLOW} ⚠ File structure issues found${NC}\"\n structure_score=10\nfi\necho \"\"\n\n# 2. Documentation Structure Check (for full validation, invoke typo3-docs skill)\necho -e \"${BLUE}[2/7] Checking documentation structure...${NC}\"\nif bash \"${SCRIPT_DIR}/check-documentation.sh\" \"${PROJECT_DIR}\" >> \"${REPORT_FILE}\"; then\n echo -e \"${GREEN} ✓ Documentation check complete${NC}\"\n docs_score=10\nelse\n echo -e \"${YELLOW} ⚠ Documentation issues found${NC}\"\n docs_score=5\nfi\necho \"\"\n\n# 3. Coding Standards Check\necho -e \"${BLUE}[3/7] Checking coding standards...${NC}\"\nif bash \"${SCRIPT_DIR}/check-coding-standards.sh\" \"${PROJECT_DIR}\" >> \"${REPORT_FILE}\"; then\n echo -e \"${GREEN} ✓ Coding standards check complete${NC}\"\n coding_score=18\nelse\n echo -e \"${YELLOW} ⚠ Coding standards issues found${NC}\"\n coding_score=12\nfi\necho \"\"\n\n# 4. Architecture Check\necho -e \"${BLUE}[4/7] Checking PHP architecture...${NC}\"\nif bash \"${SCRIPT_DIR}/check-architecture.sh\" \"${PROJECT_DIR}\" >> \"${REPORT_FILE}\"; then\n echo -e \"${GREEN} ✓ Architecture check complete${NC}\"\n arch_score=18\nelse\n echo -e \"${YELLOW} ⚠ Architecture issues found${NC}\"\n arch_score=10\nfi\necho \"\"\n\n# 5. Testing Check\necho -e \"${BLUE}[5/7] Checking testing infrastructure...${NC}\"\nif bash \"${SCRIPT_DIR}/check-testing.sh\" \"${PROJECT_DIR}\" >> \"${REPORT_FILE}\"; then\n echo -e \"${GREEN} ✓ Testing check complete${NC}\"\n test_score=16\nelse\n echo -e \"${YELLOW} ⚠ Testing issues found${NC}\"\n test_score=8\nfi\necho \"\"\n\n# 6. PHPStan Baseline Check\necho -e \"${BLUE}[6/7] Checking PHPStan baseline hygiene...${NC}\"\nif bash \"${SCRIPT_DIR}/check-phpstan-baseline.sh\" \"${PROJECT_DIR}\"; then\n echo -e \"${GREEN} ✓ PHPStan baseline hygiene check passed${NC}\"\n baseline_score=10\nelse\n echo -e \"${RED} ✗ PHPStan baseline violation detected${NC}\"\n baseline_score=0\nfi\necho \"\"\n\n# 7. Generate comprehensive report\necho -e \"${BLUE}[7/7] Generating final report...${NC}\"\nbash \"${SCRIPT_DIR}/generate-report.sh\" \"${PROJECT_DIR}\" \"${REPORT_FILE}\" \\\n \"${structure_score}\" \"${coding_score}\" \"${arch_score}\" \"${test_score}\"\necho \"\"\n\n# Calculate total (including all scores)\ntotal_score=$((structure_score + docs_score + coding_score + arch_score + test_score + baseline_score))\n\n# Display summary\necho -e \"${BLUE}╔════════════════════════════════════════════════════════════╗${NC}\"\necho -e \"${BLUE}║ Conformance Results ║${NC}\"\necho -e \"${BLUE}╚════════════════════════════════════════════════════════════╝${NC}\"\necho \"\"\necho -e \" File Structure: ${structure_score}/18\"\necho -e \" Documentation: ${docs_score}/10\"\necho -e \" Coding Standards: ${coding_score}/18\"\necho -e \" PHP Architecture: ${arch_score}/18\"\necho -e \" Testing Standards: ${test_score}/16\"\necho -e \" Baseline Hygiene: ${baseline_score}/10\"\necho -e \" Best Practices: 10/10\"\necho \"\"\necho -e \" ${BLUE}Total Score: ${total_score}/100${NC}\"\necho \"\"\n\nif [ ${total_score} -ge 80 ]; then\n echo -e \"${GREEN}✓ EXCELLENT conformance level${NC}\"\nelif [ ${total_score} -ge 60 ]; then\n echo -e \"${YELLOW}⚠ GOOD conformance level (some improvements recommended)${NC}\"\nelif [ ${total_score} -ge 40 ]; then\n echo -e \"${YELLOW}⚠ FAIR conformance level (several issues to address)${NC}\"\nelse\n echo -e \"${RED}✗ POOR conformance level (major improvements needed)${NC}\"\nfi\n\necho \"\"\necho -e \"${GREEN}Report saved to: ${REPORT_FILE}${NC}\"\necho \"\"\n\n# Exit with appropriate code\nif [ ${total_score} -ge 60 ]; then\n exit 0\nelse\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":7311,"content_sha256":"34241744fb421caa11d17ca1c2a3e8e6ef16854c6dfd6849178ccc7fa7ee6803"},{"filename":"scripts/check-documentation.sh","content":"#!/usr/bin/env bash\n\n#\n# TYPO3 Documentation Structure Checker\n#\n# Validates basic documentation file structure.\n# For comprehensive RST validation, Claude should invoke the typo3-docs skill.\n#\n\nset -e\n\nPROJECT_DIR=\"${1:-.}\"\ncd \"${PROJECT_DIR}\"\n\necho \"## Documentation Structure\"\necho \"\"\necho \"> Note: For comprehensive RST validation, use the typo3-docs skill\"\necho \"\"\n\necho \"### Required Files\"\necho \"\"\n\n# Check Documentation directory exists\nif [ ! -d \"Documentation\" ]; then\n echo \"- ❌ Documentation/ directory missing\"\n echo \"\"\n exit 1\nfi\n\n# Check Documentation/Index.rst (required entry point)\nif [ -f \"Documentation/Index.rst\" ]; then\n echo \"- ✅ Documentation/Index.rst present\"\nelif [ -f \"Documentation/Index.md\" ]; then\n echo \"- ✅ Documentation/Index.md present (Markdown mode)\"\nelse\n echo \"- ❌ No documentation entry point (need Index.rst or Index.md)\"\nfi\n\n# Check guides.xml (modern) vs Settings.cfg (legacy)\nif [ -f \"Documentation/guides.xml\" ]; then\n echo \"- ✅ Documentation/guides.xml present (modern)\"\nelif [ -f \"Documentation/Settings.cfg\" ]; then\n echo \"- ⚠️ Documentation/Settings.cfg present (legacy - migrate to guides.xml)\"\nelse\n echo \"- ❌ No documentation config (need guides.xml)\"\nfi\n\necho \"\"\necho \"### Structure\"\necho \"\"\n\n# Check for Index.rst in subdirectories\nif [ -d \"Documentation\" ]; then\n missing_index=()\n for dir in Documentation/*/; do\n if [ -d \"$dir\" ]; then\n dirname=$(basename \"$dir\")\n # Skip special directories\n if [[ \"$dirname\" == _* ]] || [[ \"$dirname\" == \"Images\" ]]; then\n continue\n fi\n if [ ! -f \"${dir}Index.rst\" ] && [ ! -f \"${dir}Index.md\" ]; then\n missing_index+=(\"$dirname\")\n fi\n fi\n done\n\n if [ ${#missing_index[@]} -eq 0 ]; then\n echo \"- ✅ All subdirectories have Index.rst\"\n else\n echo \"- ⚠️ Missing Index.rst in: ${missing_index[*]}\"\n fi\nfi\n\n# Check for Images directory\nif [ -d \"Documentation/Images\" ]; then\n img_count=$(find \"Documentation/Images\" -type f \\( -name \"*.png\" -o -name \"*.jpg\" -o -name \"*.gif\" -o -name \"*.svg\" \\) 2>/dev/null | wc -l)\n if [ \"$img_count\" -gt 0 ]; then\n echo \"- ✅ Documentation/Images/ ($img_count image(s))\"\n else\n echo \"- ⚠️ Documentation/Images/ exists but empty\"\n fi\nelse\n echo \"- ℹ️ No Documentation/Images/ (optional)\"\nfi\n\n# Check for .editorconfig\nif [ -f \"Documentation/.editorconfig\" ]; then\n echo \"- ✅ Documentation/.editorconfig present\"\nfi\n\necho \"\"\necho \"---\"\necho \"\"\n\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2610,"content_sha256":"78a11ad8f33b357698d1b13db5d3db746d50a15a66bb12db8283c0ac69b7ef5b"},{"filename":"scripts/check-file-structure.sh","content":"#!/usr/bin/env bash\n\n#\n# TYPO3 File Structure Conformance Checker\n#\n# Validates extension directory structure and required files\n#\n\nset -e\n\nPROJECT_DIR=\"${1:-.}\"\ncd \"${PROJECT_DIR}\"\n\necho \"## 1. File Structure Conformance\"\necho \"\"\n\n# Track issues\nhas_issues=0\n\necho \"### Required Files\"\necho \"\"\n\n# Check required files\nif [ -f \"composer.json\" ]; then\n echo \"- ✅ composer.json present\"\nelse\n echo \"- ❌ composer.json missing (CRITICAL)\"\n has_issues=1\nfi\n\nif [ -f \"ext_emconf.php\" ]; then\n echo \"- ✅ ext_emconf.php present\"\nelse\n echo \"- ⚠️ ext_emconf.php missing (required for TER publication)\"\nfi\n\n# Documentation checks delegated to check-documentation.sh (typo3-docs skill standards)\nif [ -d \"Documentation\" ]; then\n echo \"- ✅ Documentation/ directory present (details in Documentation check)\"\nelse\n echo \"- ⚠️ Documentation/ directory missing\"\nfi\n\necho \"\"\necho \"### Directory Structure\"\necho \"\"\n\n# Check core directories\nif [ -d \"Classes\" ]; then\n echo \"- ✅ Classes/ directory present\"\n # Check for common subdirectories\n if [ -d \"Classes/Controller\" ]; then\n echo \" - ✅ Classes/Controller/ found\"\n fi\n if [ -d \"Classes/Domain/Model\" ]; then\n echo \" - ✅ Classes/Domain/Model/ found\"\n fi\n if [ -d \"Classes/Domain/Repository\" ]; then\n echo \" - ✅ Classes/Domain/Repository/ found\"\n fi\nelse\n echo \"- ❌ Classes/ directory missing (CRITICAL)\"\n has_issues=1\nfi\n\nif [ -d \"Configuration\" ]; then\n echo \"- ✅ Configuration/ directory present\"\n if [ -d \"Configuration/TCA\" ]; then\n echo \" - ✅ Configuration/TCA/ found\"\n fi\n if [ -f \"Configuration/Services.yaml\" ]; then\n echo \" - ✅ Configuration/Services.yaml found\"\n else\n echo \" - ⚠️ Configuration/Services.yaml missing (recommended)\"\n fi\n if [ -d \"Configuration/Backend\" ]; then\n echo \" - ✅ Configuration/Backend/ found\"\n fi\nelse\n echo \"- ⚠️ Configuration/ directory missing\"\nfi\n\nif [ -d \"Resources\" ]; then\n echo \"- ✅ Resources/ directory present\"\n if [ -d \"Resources/Private\" ] && [ -d \"Resources/Public\" ]; then\n echo \" - ✅ Resources/Private/ and Resources/Public/ properly separated\"\n else\n echo \" - ⚠️ Resources/ not properly separated into Private/ and Public/\"\n fi\nelse\n echo \"- ⚠️ Resources/ directory missing\"\nfi\n\nif [ -d \"Tests\" ]; then\n echo \"- ✅ Tests/ directory present\"\n if [ -d \"Tests/Unit\" ]; then\n echo \" - ✅ Tests/Unit/ found\"\n else\n echo \" - ⚠️ Tests/Unit/ missing\"\n fi\n if [ -d \"Tests/Functional\" ]; then\n echo \" - ✅ Tests/Functional/ found\"\n else\n echo \" - ⚠️ Tests/Functional/ missing\"\n fi\nelse\n echo \"- ⚠️ Tests/ directory missing\"\nfi\n\necho \"\"\necho \"### Anti-Patterns Check\"\necho \"\"\n\n# Check for PHP files in root (except allowed config files)\n# Show all files but distinguish between tracked (issues) and untracked (info)\ntracked_files=()\nuntracked_files=()\nall_root_php_files=()\n\n# Allowed PHP config files in root (standard tool configurations)\nallowed_root_php=(\n \".php-cs-fixer.php\"\n \".php-cs-fixer.dist.php\"\n \"php-cs-fixer.php\"\n \"rector.php\"\n \"fractor.php\"\n \"ecs.php\"\n \"phpstan.php\"\n \"phpunit.php\"\n \"captainhook.php\"\n \"grumphp.php\"\n \"pint.php\"\n \"scoper.inc.php\"\n)\n\n# Find all PHP files in root (except ext_* files and allowed config files)\nwhile IFS= read -r file; do\n filename=$(basename \"$file\")\n # Skip ext_* files\n if [[ \"$filename\" == ext_*.php ]]; then\n continue\n fi\n # Check if file is in allowed list\n is_allowed=0\n for allowed in \"${allowed_root_php[@]}\"; do\n if [[ \"$filename\" == \"$allowed\" ]]; then\n is_allowed=1\n break\n fi\n done\n if [ $is_allowed -eq 0 ]; then\n all_root_php_files+=(\"$filename\")\n fi\ndone \u003c \u003c(find . -maxdepth 1 -name \"*.php\" 2>/dev/null || true)\n\n# Check if files are tracked in git (if git repository exists)\nif [ -d \".git\" ]; then\n for file in \"${all_root_php_files[@]}\"; do\n if git ls-files --error-unmatch \"$file\" >/dev/null 2>&1; then\n tracked_files+=(\"$file\")\n else\n untracked_files+=(\"$file\")\n fi\n done\nelse\n # No git repository - treat all files as tracked (potential issues)\n tracked_files=(\"${all_root_php_files[@]}\")\nfi\n\n# Report tracked files (these are issues)\nif [ ${#tracked_files[@]} -gt 0 ]; then\n if [ -d \".git\" ]; then\n echo \"- ❌ ${#tracked_files[@]} PHP file(s) in root directory committed to repository:\"\n else\n echo \"- ❌ ${#tracked_files[@]} PHP file(s) found in root directory:\"\n fi\n for file in \"${tracked_files[@]}\"; do\n echo \" - ${file} (ISSUE: should be in Classes/ or Build/)\"\n done\n has_issues=1\nfi\n\n# Report untracked files (informational only)\nif [ ${#untracked_files[@]} -gt 0 ]; then\n echo \"- ℹ️ ${#untracked_files[@]} untracked PHP file(s) in root (ignored, not committed):\"\n for file in \"${untracked_files[@]}\"; do\n echo \" - ${file} (local file, not in repository)\"\n done\nfi\n\n# Success message if no files found\nif [ ${#all_root_php_files[@]} -eq 0 ]; then\n echo \"- ✅ No PHP files in root (except ext_* files)\"\nfi\n\n# Check for deprecated ext_tables.php\nif [ -f \"ext_tables.php\" ]; then\n echo \"- ⚠️ ext_tables.php present (consider migrating to Configuration/Backend/)\"\nfi\n\n# Check for wrong directory naming\nif [ -d \"Classes/Controllers\" ]; then\n echo \"- ❌ Classes/Controllers/ found (should be Controller/ singular)\"\n has_issues=1\nfi\n\nif [ -d \"Classes/Helpers\" ]; then\n echo \"- ⚠️ Classes/Helpers/ found (should use Utility/ instead)\"\nfi\n\necho \"\"\necho \"---\"\necho \"\"\n\n# Return appropriate exit code\nif [ ${has_issues} -eq 0 ]; then\n exit 0\nelse\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5866,"content_sha256":"be6196478023570846bc211fdf50aa8ae8174ea09c352c4cee036ffa743c3ba6"},{"filename":"scripts/check-phpstan-baseline.sh","content":"#!/usr/bin/env bash\n\n# TYPO3 Extension Conformance Checker - PHPStan Baseline Validation\n# Verifies that new code does not add errors to phpstan-baseline.neon\n#\n# Usage:\n# ./check-phpstan-baseline.sh [path-to-extension]\n#\n# Returns:\n# 0 = No baseline additions detected\n# 1 = New errors added to baseline (violation)\n\nset -euo pipefail\n\n# Colors for output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nNC='\\033[0m' # No Color\n\n# Parse arguments\nPROJECT_ROOT=\"${1:-.}\"\ncd \"$PROJECT_ROOT\" || exit 1\n\necho \"Checking PHPStan baseline hygiene in: $PROJECT_ROOT\"\necho\n\n# Check if git repository\nif [ ! -d \".git\" ]; then\n echo -e \"${YELLOW}⚠️ Not a git repository - skipping baseline check${NC}\"\n exit 0\nfi\n\n# Find baseline file\nBASELINE_FILE=\"\"\nfor path in \"Build/phpstan-baseline.neon\" \"phpstan-baseline.neon\" \".phpstan/baseline.neon\"; do\n if [ -f \"$path\" ]; then\n BASELINE_FILE=\"$path\"\n break\n fi\ndone\n\nif [ -z \"$BASELINE_FILE\" ]; then\n echo -e \"${GREEN}✅ No baseline file found - all code passes PHPStan level 10!${NC}\"\n exit 0\nfi\n\necho \"Found baseline file: $BASELINE_FILE\"\necho\n\n# Check if baseline is modified in current changes\nif ! git diff --quiet \"$BASELINE_FILE\" 2>/dev/null; then\n echo -e \"${YELLOW}⚠️ Baseline file has uncommitted changes${NC}\"\n echo\n\n # Extract error counts from diff\n BEFORE_COUNT=$(git show \"HEAD:$BASELINE_FILE\" 2>/dev/null | grep -E \"^\\s+count:\\s+[0-9]+\" | head -1 | grep -oE \"[0-9]+\" || echo \"0\")\n AFTER_COUNT=$(grep -E \"^\\s+count:\\s+[0-9]+\" \"$BASELINE_FILE\" | head -1 | grep -oE \"[0-9]+\" || echo \"0\")\n\n if [ \"$AFTER_COUNT\" -gt \"$BEFORE_COUNT\" ]; then\n ADDED=$((AFTER_COUNT - BEFORE_COUNT))\n echo -e \"${RED}❌ BASELINE VIOLATION DETECTED${NC}\"\n echo\n echo \"Error count increased: $BEFORE_COUNT → $AFTER_COUNT (+$ADDED errors)\"\n echo\n echo \"New code added $ADDED errors to the baseline!\"\n echo\n echo \"The baseline exists only for legacy code.\"\n echo \"All new code MUST pass PHPStan level 10 without baseline suppression.\"\n echo\n echo -e \"${YELLOW}How to fix:${NC}\"\n echo \"1. Run: composer ci:php:stan\"\n echo \"2. Review the new errors reported\"\n echo \"3. Fix the underlying issues (see coding-guidelines.md for patterns)\"\n echo \"4. Revert baseline changes: git checkout $BASELINE_FILE\"\n echo \"5. Verify: composer ci:php:stan should pass with original baseline\"\n echo\n exit 1\n elif [ \"$AFTER_COUNT\" -lt \"$BEFORE_COUNT\" ]; then\n REMOVED=$((BEFORE_COUNT - AFTER_COUNT))\n echo -e \"${GREEN}✅ Excellent! Baseline reduced by $REMOVED errors${NC}\"\n echo\n echo \"You fixed existing baseline issues - great work!\"\n echo\n else\n echo -e \"${YELLOW}⚠️ Baseline modified but count unchanged${NC}\"\n echo\n echo \"Review the baseline diff to ensure changes are intentional:\"\n echo \" git diff $BASELINE_FILE\"\n echo\n fi\nelse\n echo -e \"${GREEN}✅ No changes to baseline file${NC}\"\nfi\n\n# Check for baseline in staged changes\nif git diff --cached --quiet \"$BASELINE_FILE\" 2>/dev/null; then\n echo -e \"${GREEN}✅ No baseline changes staged for commit${NC}\"\nelse\n echo\n echo -e \"${YELLOW}⚠️ Warning: Baseline file is staged for commit${NC}\"\n echo\n echo \"Review staged baseline changes:\"\n echo \" git diff --cached $BASELINE_FILE\"\n echo\nfi\n\necho\necho -e \"${GREEN}✅ PHPStan baseline hygiene check passed${NC}\"\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3543,"content_sha256":"e297e01caee0ecb47715e2c4631ba03b62e1fb9e24567c58d6c23f3539841735"},{"filename":"scripts/check-testing.sh","content":"#!/usr/bin/env bash\n\n#\n# TYPO3 Testing Standards Conformance Checker\n#\n# Validates testing infrastructure and test coverage\n#\n\nset -e\n\nPROJECT_DIR=\"${1:-.}\"\ncd \"${PROJECT_DIR}\"\n\necho \"## 4. Testing Standards Conformance\"\necho \"\"\n\nhas_issues=0\n\n### Check for Tests directory\necho \"### Test Infrastructure\"\necho \"\"\n\nif [ ! -d \"Tests\" ]; then\n echo \"- ❌ Tests/ directory missing (CRITICAL)\"\n has_issues=1\n echo \"\"\n echo \"---\"\n echo \"\"\n exit 1\nfi\n\necho \"- ✅ Tests/ directory present\"\n\n### Check for PHPUnit configuration\necho \"\"\necho \"### PHPUnit Configuration\"\necho \"\"\n\nif [ -f \"Build/phpunit/UnitTests.xml\" ] || [ -f \"phpunit.xml\" ]; then\n echo \"- ✅ Unit test configuration found\"\nelse\n echo \"- ❌ No Unit test configuration (Build/phpunit/UnitTests.xml or phpunit.xml)\"\n has_issues=1\nfi\n\nif [ -f \"Build/phpunit/FunctionalTests.xml\" ]; then\n echo \"- ✅ Functional test configuration found\"\nelse\n echo \"- ⚠️ No Functional test configuration (Build/phpunit/FunctionalTests.xml)\"\nfi\n\n### Unit Tests\necho \"\"\necho \"### Unit Tests\"\necho \"\"\n\nif [ -d \"Tests/Unit\" ]; then\n unit_test_count=$(find Tests/Unit/ -name \"*Test.php\" 2>/dev/null | wc -l)\n echo \"- ✅ Tests/Unit/ directory present\"\n echo \" - **${unit_test_count} unit test files found**\"\n\n if [ $unit_test_count -eq 0 ]; then\n echo \" - ⚠️ No unit tests found\"\n fi\n\n # Check if tests mirror Classes structure\n if [ -d \"Classes/Controller\" ] && [ ! -d \"Tests/Unit/Controller\" ]; then\n echo \" - ⚠️ Tests/Unit/Controller/ missing (Classes/Controller/ exists)\"\n fi\n\n if [ -d \"Classes/Service\" ] && [ ! -d \"Tests/Unit/Service\" ]; then\n echo \" - ⚠️ Tests/Unit/Service/ missing (Classes/Service/ exists)\"\n fi\n\n if [ -d \"Classes/Domain/Repository\" ] && [ ! -d \"Tests/Unit/Domain/Repository\" ]; then\n echo \" - ⚠️ Tests/Unit/Domain/Repository/ missing (Classes/Domain/Repository/ exists)\"\n fi\nelse\n echo \"- ❌ Tests/Unit/ directory missing\"\n has_issues=1\nfi\n\n### Functional Tests\necho \"\"\necho \"### Functional Tests\"\necho \"\"\n\nif [ -d \"Tests/Functional\" ]; then\n func_test_count=$(find Tests/Functional/ -name \"*Test.php\" 2>/dev/null | wc -l)\n echo \"- ✅ Tests/Functional/ directory present\"\n echo \" - **${func_test_count} functional test files found**\"\n\n # Check for fixtures\n if [ -d \"Tests/Functional/Fixtures\" ]; then\n fixture_count=$(find Tests/Functional/Fixtures/ -name \"*.csv\" -o -name \"*.xml\" 2>/dev/null | wc -l)\n echo \" - ✅ Tests/Functional/Fixtures/ found (${fixture_count} fixture files)\"\n else\n if [ $func_test_count -gt 0 ]; then\n echo \" - ⚠️ No Tests/Functional/Fixtures/ (functional tests may need fixtures)\"\n fi\n fi\nelse\n echo \"- ⚠️ Tests/Functional/ directory missing\"\n echo \" - Functional tests recommended for repository and database operations\"\nfi\n\n### E2E Tests (Playwright)\necho \"\"\necho \"### E2E Tests (Playwright)\"\necho \"\"\n\nif [ -d \"Build/tests/playwright\" ]; then\n e2e_test_count=$(find Build/tests/playwright/ -name \"*.spec.ts\" 2>/dev/null | wc -l)\n echo \"- ✅ Build/tests/playwright/ directory present\"\n echo \" - **${e2e_test_count} E2E test files found**\"\n\n if [ -f \"Build/playwright.config.ts\" ]; then\n echo \" - ✅ playwright.config.ts configuration found\"\n else\n echo \" - ⚠️ playwright.config.ts configuration missing\"\n fi\n\n if [ -d \"Build/tests/playwright/e2e\" ]; then\n echo \" - ✅ E2E tests directory present\"\n fi\n\n if [ -d \"Build/tests/playwright/accessibility\" ]; then\n echo \" - ✅ Accessibility tests directory present (axe-core)\"\n fi\n\n if [ -d \"Build/tests/playwright/fixtures\" ]; then\n echo \" - ✅ Page Object Model fixtures present\"\n fi\n\n if [ -f \"Build/tests/playwright/helper/login.setup.ts\" ]; then\n echo \" - ✅ Authentication setup found\"\n fi\n\n if [ -f \"Build/.nvmrc\" ]; then\n node_version=$(cat Build/.nvmrc 2>/dev/null || echo \"unknown\")\n echo \" - ✅ Node version specified: ${node_version}\"\n else\n echo \" - ⚠️ .nvmrc missing (recommend Node >=22.18)\"\n fi\nelif [ -f \"Build/playwright.config.ts\" ]; then\n echo \"- ⚠️ playwright.config.ts found but tests directory missing\"\nelse\n echo \"- ℹ️ Playwright E2E tests not found (optional, recommended for backend modules)\"\nfi\n\n### Test Coverage Estimate\necho \"\"\necho \"### Test Coverage Estimate\"\necho \"\"\n\nif [ -d \"Classes\" ]; then\n class_count=$(find Classes/ -name \"*.php\" 2>/dev/null | wc -l)\n\n if [ -d \"Tests/Unit\" ]; then\n unit_count=$(find Tests/Unit/ -name \"*Test.php\" 2>/dev/null | wc -l)\n else\n unit_count=0\n fi\n\n if [ -d \"Tests/Functional\" ]; then\n func_count=$(find Tests/Functional/ -name \"*Test.php\" 2>/dev/null | wc -l)\n else\n func_count=0\n fi\n\n total_tests=$((unit_count + func_count))\n\n echo \"- **Total Classes:** $class_count\"\n echo \"- **Total Tests:** $total_tests\"\n\n if [ $class_count -gt 0 ]; then\n coverage_ratio=$((total_tests * 100 / class_count))\n echo \"- **Test Ratio:** ${coverage_ratio}%\"\n\n if [ $coverage_ratio -ge 70 ]; then\n echo \" - ✅ Good test coverage (≥70%)\"\n elif [ $coverage_ratio -ge 50 ]; then\n echo \" - ⚠️ Moderate test coverage (50-70%)\"\n else\n echo \" - ❌ Low test coverage (\u003c50%)\"\n has_issues=1\n fi\n fi\nfi\n\n### Check for testing framework dependency\necho \"\"\necho \"### Testing Framework Dependency\"\necho \"\"\n\nif [ -f \"composer.json\" ]; then\n if grep -q \"typo3/testing-framework\" composer.json; then\n echo \"- ✅ typo3/testing-framework in composer.json\"\n else\n echo \"- ⚠️ typo3/testing-framework not found in composer.json\"\n fi\n\n if grep -q \"phpunit/phpunit\" composer.json; then\n echo \"- ✅ phpunit/phpunit in composer.json\"\n else\n echo \"- ⚠️ phpunit/phpunit not found in composer.json\"\n fi\nfi\n\necho \"\"\necho \"### Summary\"\necho \"\"\n\nif [ $has_issues -eq 0 ]; then\n echo \"- ✅ **Testing Standards: PASSED**\"\nelse\n echo \"- ⚠️ **Testing Standards: ISSUES FOUND**\"\nfi\n\necho \"\"\necho \"---\"\necho \"\"\n\nexit $has_issues\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6282,"content_sha256":"9493fc1deb2384a4edccce18557e1f3b6f0e8a37a1dece52e867a3cb33736fba"},{"filename":"scripts/generate-report.sh","content":"#!/usr/bin/env bash\n\n#\n# Generate final conformance report with recommendations\n#\n\nset -e\n\nPROJECT_DIR=\"${1}\"\nREPORT_FILE=\"${2}\"\nSTRUCTURE_SCORE=\"${3:-15}\"\nCODING_SCORE=\"${4:-15}\"\nARCH_SCORE=\"${5:-15}\"\nTEST_SCORE=\"${6:-15}\"\n\ncd \"${PROJECT_DIR}\"\n\n# Calculate total\nTOTAL_SCORE=$((STRUCTURE_SCORE + CODING_SCORE + ARCH_SCORE + TEST_SCORE + 10))\n\n# Update summary table\nsed -i \"s/| Extension Architecture .*/| Extension Architecture | ${STRUCTURE_SCORE}\\/20 | $(if [ ${STRUCTURE_SCORE} -ge 15 ]; then echo \"✅ Passed\"; else echo \"⚠️ Issues\"; fi) |/\" \"${REPORT_FILE}\"\nsed -i \"/| Extension Architecture /a | Coding Guidelines | ${CODING_SCORE}/20 | $(if [ ${CODING_SCORE} -ge 15 ]; then echo \"✅ Passed\"; else echo \"⚠️ Issues\"; fi) |\" \"${REPORT_FILE}\" 2>/dev/null || true\nsed -i \"/| Coding Guidelines /a | PHP Architecture | ${ARCH_SCORE}/20 | $(if [ ${ARCH_SCORE} -ge 15 ]; then echo \"✅ Passed\"; else echo \"⚠️ Issues\"; fi) |\" \"${REPORT_FILE}\" 2>/dev/null || true\nsed -i \"/| PHP Architecture /a | Testing Standards | ${TEST_SCORE}/20 | $(if [ ${TEST_SCORE} -ge 15 ]; then echo \"✅ Passed\"; else echo \"⚠️ Issues\"; fi) |\" \"${REPORT_FILE}\" 2>/dev/null || true\nsed -i \"/| Testing Standards /a | Best Practices | 10/20 | ℹ️ Partial |\" \"${REPORT_FILE}\" 2>/dev/null || true\nsed -i \"/| Best Practices /a | **TOTAL** | **${TOTAL_SCORE}/100** | $(if [ ${TOTAL_SCORE} -ge 80 ]; then echo \"✅ Excellent\"; elif [ ${TOTAL_SCORE} -ge 60 ]; then echo \"✅ Good\"; else echo \"⚠️ Fair\"; fi) |\" \"${REPORT_FILE}\" 2>/dev/null || true\n\n# Add final sections\ncat >> \"${REPORT_FILE}\" \u003c\u003cEOF\n\n---\n\n## 5. Best Practices Assessment\n\n### Project Infrastructure\n- **README.md:** $(if [ -f \"README.md\" ]; then echo \"✅ Present\"; else echo \"❌ Missing\"; fi)\n- **LICENSE:** $(if [ -f \"LICENSE\" ]; then echo \"✅ Present\"; else echo \"❌ Missing\"; fi)\n- **.editorconfig:** $(if [ -f \".editorconfig\" ]; then echo \"✅ Present\"; else echo \"⚠️ Missing\"; fi)\n- **.gitignore:** $(if [ -f \".gitignore\" ]; then echo \"✅ Present\"; else echo \"⚠️ Missing\"; fi)\n\n### Code Quality Tools\n- **php-cs-fixer:** $(if [ -f \".php-cs-fixer.dist.php\" ] || [ -f \".php-cs-fixer.php\" ] || [ -f \"Build/.php-cs-fixer.dist.php\" ] || [ -f \"Build/.php-cs-fixer.php\" ]; then echo \"✅ Configured\"; else echo \"⚠️ Not configured\"; fi)\n- **phpstan:** $(if [ -f \"phpstan.neon\" ] || [ -f \"phpstan.neon.dist\" ] || [ -f \"Build/phpstan.neon\" ] || [ -f \"Build/phpstan.neon.dist\" ]; then echo \"✅ Configured\"; else echo \"⚠️ Not configured\"; fi)\n- **rector:** $(if [ -f \"rector.php\" ] || [ -f \"Build/rector.php\" ]; then echo \"✅ Configured\"; else echo \"ℹ️ Not configured\"; fi)\n\n### CI/CD Pipeline\n- **GitHub Actions:** $(if [ -d \".github/workflows\" ]; then echo \"✅ Configured\"; else echo \"⚠️ Not found\"; fi)\n- **GitLab CI:** $(if [ -f \".gitlab-ci.yml\" ]; then echo \"✅ Configured\"; else echo \"ℹ️ Not found\"; fi)\n\n---\n\n## Overall Assessment\n\n**Total Score: ${TOTAL_SCORE}/100**\n\n$(if [ ${TOTAL_SCORE} -ge 80 ]; then\ncat \u003c\u003cEND\n### ✅ EXCELLENT Conformance Level\n\nYour TYPO3 extension demonstrates strong adherence to official standards and best practices.\n\n**Strengths:**\n- Well-structured architecture following TYPO3 conventions\n- Modern PHP patterns with dependency injection\n- Good code quality and testing coverage\n- Proper documentation and infrastructure\n\n**Minor Improvements:**\n- Continue maintaining high standards\n- Keep dependencies updated\n- Monitor code coverage trends\nEND\nelif [ ${TOTAL_SCORE} -ge 60 ]; then\ncat \u003c\u003cEND\n### ✅ GOOD Conformance Level\n\nYour TYPO3 extension follows most standards with some areas for improvement.\n\n**Next Steps:**\n1. Address critical issues identified above\n2. Improve test coverage\n3. Add missing configuration files\n4. Update deprecated patterns\n\n**Timeline:** 2-4 weeks for improvements\nEND\nelse\ncat \u003c\u003cEND\n### ⚠️ FAIR Conformance Level\n\nYour TYPO3 extension requires significant improvements to meet TYPO3 standards.\n\n**Priority Actions:**\n1. Fix critical file structure issues\n2. Migrate deprecated patterns (GeneralUtility::makeInstance, \\$GLOBALS)\n3. Add comprehensive testing infrastructure\n4. Improve code quality (strict types, PHPDoc, PSR-12)\n5. Add project infrastructure (CI/CD, quality tools)\n\n**Timeline:** 4-8 weeks for comprehensive improvements\nEND\nfi)\n\n---\n\n## Quick Action Checklist\n\n### High Priority (Fix Now)\n$(if [ ${STRUCTURE_SCORE} -lt 15 ]; then echo \"- [ ] Fix critical file structure issues (missing required files/directories)\"; fi)\n$(if grep -q \"GeneralUtility::makeInstance\" Classes/ 2>/dev/null; then echo \"- [ ] Migrate GeneralUtility::makeInstance to constructor injection\"; fi)\n$(if grep -q '\\$GLOBALS\\[' Classes/ 2>/dev/null; then echo \"- [ ] Remove \\$GLOBALS access, use dependency injection\"; fi)\n$(if [ ! -f \"Configuration/Services.yaml\" ]; then echo \"- [ ] Add Configuration/Services.yaml with DI configuration\"; fi)\n\n### Medium Priority (Fix Soon)\n$(if [ ${CODING_SCORE} -lt 15 ]; then echo \"- [ ] Add declare(strict_types=1) to all PHP files\"; fi)\n$(if [ ${CODING_SCORE} -lt 15 ]; then echo \"- [ ] Replace array() with [] short syntax\"; fi)\n$(if [ ${TEST_SCORE} -lt 15 ]; then echo \"- [ ] Add unit tests for untested classes\"; fi)\n$(if [ ! -d \"Tests/Functional\" ]; then echo \"- [ ] Add functional tests for repositories\"; fi)\n\n### Low Priority (Improve When Possible)\n$(if [ ! -f \".php-cs-fixer.dist.php\" ] && [ ! -f \".php-cs-fixer.php\" ] && [ ! -f \"Build/.php-cs-fixer.dist.php\" ] && [ ! -f \"Build/.php-cs-fixer.php\" ]; then echo \"- [ ] Configure PHP CS Fixer\"; fi)\n$(if [ ! -f \"phpstan.neon\" ] && [ ! -f \"phpstan.neon.dist\" ] && [ ! -f \"Build/phpstan.neon\" ] && [ ! -f \"Build/phpstan.neon.dist\" ]; then echo \"- [ ] Configure PHPStan for static analysis\"; fi)\n$(if [ ! -d \".github/workflows\" ]; then echo \"- [ ] Set up CI/CD pipeline (GitHub Actions)\"; fi)\n- [ ] Improve PHPDoc comments coverage\n- [ ] Add .editorconfig for consistent formatting\n\n---\n\n## Resources\n\n- **TYPO3 Extension Architecture:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/\n- **Coding Guidelines:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/\n- **Dependency Injection:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/\n- **Testing Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/\n- **Tea Extension (Best Practice):** https://github.com/TYPO3BestPractices/tea\n\n---\n\n*Report generated by TYPO3 Extension Conformance Checker*\nEOF\n\necho \"Final report generated successfully\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6589,"content_sha256":"5792c0770a616fa9b23073005626a46e81d85c14c05c46c77aeca2c7b84bd832"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"TYPO3 Extension Conformance Checker","type":"text"}]},{"type":"paragraph","content":[{"text":"Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extension quality / TER readiness","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scored conformance reports","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Modernization to v12/v13/v14 (","type":"text"},{"text":"v14.3 LTS is default/gold standard","type":"text","marks":[{"type":"strong"}]},{"text":")","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Delegation","type":"text"}]},{"type":"paragraph","content":[{"text":"Testing -> ","type":"text"},{"text":"typo3-testing","type":"text","marks":[{"type":"code_inline"}]},{"text":" | Docs -> ","type":"text"},{"text":"typo3-docs","type":"text","marks":[{"type":"code_inline"}]},{"text":" | OpenSSF -> ","type":"text"},{"text":"enterprise-readiness","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 0: Context","type":"text"}]},{"type":"paragraph","content":[{"text":"Read ext_emconf.php + composer.json for version, type, scope.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Steps 1-11: Checks","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Metadata","type":"text","marks":[{"type":"strong"}]},{"text":" -- key, TYPO3 version, type","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Structure","type":"text","marks":[{"type":"strong"}]},{"text":" -- composer.json, ext_emconf.php, Classes/, Configuration/, Resources/","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Coding","type":"text","marks":[{"type":"strong"}]},{"text":" -- strict_types, PSR-12, PHP 8.4 explicit nullable, PHP 8.5 float-to-int","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prohibited","type":"text","marks":[{"type":"strong"}]},{"text":" -- no ","type":"text"},{"text":"$GLOBALS","type":"text","marks":[{"type":"code_inline"}]},{"text":", no ","type":"text"},{"text":"GeneralUtility::makeInstance()","type":"text","marks":[{"type":"code_inline"}]},{"text":" for services","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Architecture","type":"text","marks":[{"type":"strong"}]},{"text":" -- constructor DI, Services.yaml, PSR-14 events (try/catch), PSR-3 logging (LoggerAware+NullLogger), factory fallback","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Backend","type":"text","marks":[{"type":"strong"}]},{"text":" -- ES6, Modal API, CSRF, CSP (v13+)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing","type":"text","marks":[{"type":"strong"}]},{"text":" -- PHPUnit, Playwright E2E, coverage >70%","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Practices","type":"text","marks":[{"type":"strong"}]},{"text":" -- DDEV, runTests.sh, CI/CD","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"TER","type":"text","marks":[{"type":"strong"}]},{"text":" -- publish workflow, upload comment","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Audit","type":"text","marks":[{"type":"strong"}]},{"text":" -- PHPStan baseline, TCA searchFields, XLIFF, cache has()+get(), query properties, multi-version adapters","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"v14 readiness","type":"text","marks":[{"type":"strong"}]},{"text":" -- no ","type":"text"},{"text":"ext_tables.php","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"HashService","type":"text","marks":[{"type":"code_inline"}]},{"text":"/magic finders; Fluid VHs strict; XLF 2-space. See ","type":"text"},{"text":"references/v14-deprecations.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 12: Verify","type":"text"}]},{"type":"paragraph","content":[{"text":"Re-run after fixes. Document score delta.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Grep Recipes","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"grep -rL 'strict_types' Classes/ --include='*.php' # missing strict_types\ngrep -rn '\\$GLOBALS' Classes/ --include='*.php' # prohibited $GLOBALS\ngrep -rn 'GeneralUtility::makeInstance' Classes/ --include='*.php' # makeInstance for services\ngrep -rPn '\\(\\s*[A-Za-z\\\\]+\\s+\\$\\w+\\s*=\\s*null' Classes/ --include='*.php' | grep -v '?' # PHP 8.4 implicit nullable\ngrep -rn '->has(' Classes/ --include='*.php' # cache has()+get() anti-pattern\ngrep -l 'strict_types' ext_emconf.php # ext_emconf must NOT have strict_types\ngrep -rn '(int)\\s*\\

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…

Classes/ --include='*.php' # PHP 8.5 implicit float-to-int\ngrep -rn 'data-toggle\\|data-dismiss\\|data-ride' Resources/ --include='*.html' # Bootstrap 4 in Fluid\ngrep -rn 'HashService\\|GeneralUtility::hmac(\\|->findBy[A-Z]\\|->findOneBy[A-Z]\\|->countBy[A-Z]' Classes/ --include='*.php' # v14 removals\n[ -f ext_tables.php ] && echo \"WARN: ext_tables.php deprecated (#109438)\" # v14.3 deprecation","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Scoring","type":"text"}]},{"type":"paragraph","content":[{"text":"Base (0-100):","type":"text","marks":[{"type":"strong"}]},{"text":" Architecture(20) + Guidelines(20) + PHP(20) + Testing(20) + Practices(20). Excellence bonus up to 22. Critical issues block regardless.","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Range","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Level","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Action","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"90+","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Excellent","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Production/TER ready","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"80-89","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Good","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Minor fixes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"70-79","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Acceptable","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix before release","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"50-69","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Needs Work","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Significant effort","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c50","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Critical","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Block deployment","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"References","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"references/","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Architecture & code:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"extension-architecture.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"directory-structure.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"php-architecture.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"coding-guidelines.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"best-practices.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"hooks-and-events.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validation:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"composer-validation.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ext-emconf-validation.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ext-files-validation.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"runtests-validation.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"version-requirements.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"testing-standards.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Multi-version:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"dual-version-compatibility.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (v12+v13), ","type":"text"},{"text":"v13-v14-dual-compatibility.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (v13+v14), ","type":"text"},{"text":"multi-version-dependency-compatibility.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"v13-deprecations.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"v14-deprecations.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Practices & environment:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"development-environment.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (DDEV)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Backend & publishing:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"backend-module-v13.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"ter-publishing.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"report-template.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"excellence-indicators.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"localization-coverage.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"crowdin-integration.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"Asset templates in ","type":"text"},{"text":"assets/Build/","type":"text","marks":[{"type":"code_inline"}]},{"text":": PHPStan, PHP-CS-Fixer, Rector, ESLint, Stylelint, TypoScript lint.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Credits & Attribution","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill is based on the excellent work by ","type":"text"},{"text":"Netresearch DTT GmbH","type":"text","marks":[{"type":"link","attrs":{"href":"https://www.netresearch.de/","title":null}},{"type":"strong"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Original repository: https://github.com/netresearch/typo3-conformance-skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Copyright (c) Netresearch DTT GmbH","type":"text","marks":[{"type":"strong"}]},{"text":" — Methodology and best practices (MIT / CC-BY-SA-4.0)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Special thanks to ","type":"text"},{"text":"Netresearch DTT GmbH","type":"text","marks":[{"type":"link","attrs":{"href":"https://www.netresearch.de/","title":null}}]},{"text":" for their generous open-source contributions to the TYPO3 community, which helped shape this skill collection. Adapted by webconsulting.at for this skill collection","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"typo3-conformance","author":"@skillopedia","source":{"stars":29,"repo_name":"webconsulting-skills","origin_url":"https://github.com/dirnbauer/webconsulting-skills/blob/HEAD/skills/typo3-conformance/SKILL.md","repo_owner":"dirnbauer","body_sha256":"347ed434ead24f0693d6e1e662202821476c77f8f38a2d43b871f91d01b10752","cluster_key":"b1a05295e75e34c63f60687bd8523f2d452b67788a9389ff40ecdf89b9ecce81","clean_bundle":{"format":"clean-skill-bundle-v1","source":"dirnbauer/webconsulting-skills/skills/typo3-conformance/SKILL.md","attachments":[{"id":"760a4fc6-cfc2-5d36-9111-537eafca1b6a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/760a4fc6-cfc2-5d36-9111-537eafca1b6a/attachment.yml","path":"assets/.github/workflows/publish-to-ter.yml","size":3011,"sha256":"185a153f673d35d21ece33e0533560db09fa62974cb941cf9f90985b4168ff92","contentType":"application/yaml; charset=utf-8"},{"id":"e6390f5f-a966-5464-b161-f83aba95abe2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e6390f5f-a966-5464-b161-f83aba95abe2/attachment.php","path":"assets/Build/composer-unused/composer-unused.php","size":1088,"sha256":"2aecb1ea9bf3fd05b95dc8fea630aacb8a1eba817a0ed3f044ab9ac5193a2251","contentType":"text/plain; charset=utf-8"},{"id":"0fdf6648-66b2-5105-a91c-5fbf2bd81f4d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0fdf6648-66b2-5105-a91c-5fbf2bd81f4d/attachment.json","path":"assets/Build/eslint/.eslintrc.json","size":515,"sha256":"86af37e03841db4d9244d260854110ef488cebb2669fb6702366952afdc2349c","contentType":"application/json; charset=utf-8"},{"id":"d109cb96-6243-53d9-9764-f1d3e0a18ba9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d109cb96-6243-53d9-9764-f1d3e0a18ba9/attachment.php","path":"assets/Build/php-cs-fixer/php-cs-fixer.php","size":1065,"sha256":"3b8a280557135c14eea6acb9e09612551d2577752e5def329b471ef06dd74cc8","contentType":"text/plain; charset=utf-8"},{"id":"cf21b66d-3f3e-540f-b2e3-7c46b0485180","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cf21b66d-3f3e-540f-b2e3-7c46b0485180/attachment.neon","path":"assets/Build/phpstan/phpstan-baseline.neon","size":530,"sha256":"4c36e591239bfedb4bf76386b4711b0f8f1855c35603e4c57be92681bf108cf0","contentType":"text/plain; charset=utf-8"},{"id":"fdcb9f19-06ff-5845-b806-2b9f365698dd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fdcb9f19-06ff-5845-b806-2b9f365698dd/attachment.neon","path":"assets/Build/phpstan/phpstan.neon","size":3627,"sha256":"f05bad1165e629a319df8ecdbf43b26e71a34699419f16aaab4db5216ff07919","contentType":"text/plain; charset=utf-8"},{"id":"c66c9029-73d4-5e38-a621-af897f2a8e75","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c66c9029-73d4-5e38-a621-af897f2a8e75/attachment.php","path":"assets/Build/rector/rector.php","size":4121,"sha256":"b4cb7b9d13b03e013654732093dad2b4582c49f28e216f245ae0b0b2cdc8596e","contentType":"text/plain; charset=utf-8"},{"id":"08aefd41-71d3-5173-896b-4656c735e1f4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/08aefd41-71d3-5173-896b-4656c735e1f4/attachment.json","path":"assets/Build/stylelint/.stylelintrc.json","size":367,"sha256":"7f7433421879dc294577036fb75e189cbcf9ede67247dec14216f8c4ae628055","contentType":"application/json; charset=utf-8"},{"id":"5a8c8fe0-622e-54fc-b338-6ed0a2747bad","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5a8c8fe0-622e-54fc-b338-6ed0a2747bad/attachment.yml","path":"assets/Build/typoscript-lint/TypoScriptLint.yml","size":1122,"sha256":"5a1b76f78f2981d7fae953954d90663d1b7be0703ca7c2878c9b3e5f693729d8","contentType":"application/yaml; charset=utf-8"},{"id":"55bdc633-6291-5ff7-b37f-bc52a224296f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/55bdc633-6291-5ff7-b37f-bc52a224296f/attachment.yaml","path":"checkpoints.yaml","size":61727,"sha256":"2c7a536996c78b2325c4427010a2a27d83b9e29e9d88f24594d4ffcdd6f8becc","contentType":"application/yaml; charset=utf-8"},{"id":"f8ce95bf-be0b-5492-803b-404e367633d0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f8ce95bf-be0b-5492-803b-404e367633d0/attachment.md","path":"references/backend-module-v13.md","size":41537,"sha256":"5612e6a4a12f9e589473c2eeabef483b4727a5d182815ff9f107a214b4a42f89","contentType":"text/markdown; charset=utf-8"},{"id":"4a12b778-e249-5c46-848f-7b0767858119","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4a12b778-e249-5c46-848f-7b0767858119/attachment.md","path":"references/best-practices.md","size":49503,"sha256":"88401a8fc5f677dd66395deda5bc825981bbb356e99c82d9367e56ba71987379","contentType":"text/markdown; charset=utf-8"},{"id":"1e043b44-d0e3-54df-b69f-a3fd3ca7de57","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1e043b44-d0e3-54df-b69f-a3fd3ca7de57/attachment.md","path":"references/coding-guidelines.md","size":16426,"sha256":"dcb9d2b033f5918295af6266ec6e26b799186dca2316defb6882693740189e53","contentType":"text/markdown; charset=utf-8"},{"id":"2b8a4bf0-13e5-5800-aaac-0ee20d365ab9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2b8a4bf0-13e5-5800-aaac-0ee20d365ab9/attachment.md","path":"references/composer-validation.md","size":12218,"sha256":"6542761396e7bc706925a84745e043e46d310d10d9e2a3ebfb754cd69e369c3e","contentType":"text/markdown; charset=utf-8"},{"id":"9c9f353b-d0f9-517a-bbc8-b12ad1bf4d95","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9c9f353b-d0f9-517a-bbc8-b12ad1bf4d95/attachment.md","path":"references/crowdin-integration.md","size":39574,"sha256":"92625735ff042ad0e99e0ec40732a430b2777653a53f15f381f5f725a54633ed","contentType":"text/markdown; charset=utf-8"},{"id":"605d2241-33db-57b9-9c2f-93744fc763df","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/605d2241-33db-57b9-9c2f-93744fc763df/attachment.md","path":"references/development-environment.md","size":13474,"sha256":"6036bfb46a66e58fdb8f22f99461fa50a92582e1e788c960742dee1c1b60456d","contentType":"text/markdown; charset=utf-8"},{"id":"410592fc-1e29-5634-af5a-7562576cf2cb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/410592fc-1e29-5634-af5a-7562576cf2cb/attachment.md","path":"references/directory-structure.md","size":17456,"sha256":"95cac62d082e470f0031ba7f4b76d3c6409d0658ea35cf9b56df696694505004","contentType":"text/markdown; charset=utf-8"},{"id":"d7bc3575-1cea-5ca5-b391-4eee0be404ed","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d7bc3575-1cea-5ca5-b391-4eee0be404ed/attachment.md","path":"references/dual-version-compatibility.md","size":3997,"sha256":"962f0f53ed6d5b251ec419562cec0997616206eb8e7ebab0219e45bee18c63c7","contentType":"text/markdown; charset=utf-8"},{"id":"12706ee8-9b82-54b4-aa65-ba7b34165b23","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/12706ee8-9b82-54b4-aa65-ba7b34165b23/attachment.md","path":"references/excellence-indicators.md","size":23104,"sha256":"b9ac203988e9813d9687dac7c69e61ae0faa521eb1e3bdcf4a3df51dba2136a8","contentType":"text/markdown; charset=utf-8"},{"id":"a34936dd-ad34-5bd9-82a4-cfc13035e2eb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a34936dd-ad34-5bd9-82a4-cfc13035e2eb/attachment.md","path":"references/ext-emconf-validation.md","size":16030,"sha256":"ee5fe41f2f858d912f787aebd1383588ada2f90f353769557243831130458e32","contentType":"text/markdown; charset=utf-8"},{"id":"a43c91fc-b64e-54f4-b646-efa8b3ed89f3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a43c91fc-b64e-54f4-b646-efa8b3ed89f3/attachment.md","path":"references/ext-files-validation.md","size":11599,"sha256":"14856d9c803ca69d8c9628c86297e88b19b48dc12d409d7aae8a59b5956c6fd6","contentType":"text/markdown; charset=utf-8"},{"id":"80b7672f-3ad7-5d8a-829d-57e4b3d7054f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/80b7672f-3ad7-5d8a-829d-57e4b3d7054f/attachment.md","path":"references/extension-architecture.md","size":8763,"sha256":"436de0fc0693ae2661fd58c0a1c14c901cb952d0025e96ce3010208557c37737","contentType":"text/markdown; charset=utf-8"},{"id":"5f515b70-e94a-5cb1-ae26-3ed44c441c2e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5f515b70-e94a-5cb1-ae26-3ed44c441c2e/attachment.md","path":"references/hooks-and-events.md","size":8608,"sha256":"84b4b535d3ac6f8a7908a576cda9f1a05b3658804fcd3130ebd5f33f7d20b59e","contentType":"text/markdown; charset=utf-8"},{"id":"59d1f929-bc3f-5ef7-90a5-7d6d3c429707","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/59d1f929-bc3f-5ef7-90a5-7d6d3c429707/attachment.md","path":"references/localization-coverage.md","size":7476,"sha256":"a88af168c3b9cb1e9dddd6a209619e069fad5e835d3818f5d93ac9cf70d47b1e","contentType":"text/markdown; charset=utf-8"},{"id":"b8da5a54-9699-5f9f-b84b-647d76857e6d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b8da5a54-9699-5f9f-b84b-647d76857e6d/attachment.md","path":"references/multi-version-dependency-compatibility.md","size":7060,"sha256":"e15ae52e9642443b6fc125953110c48bf86c84da904bc27197e1f9cd7208714e","contentType":"text/markdown; charset=utf-8"},{"id":"68edddca-5057-541c-9087-f973586cdef3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/68edddca-5057-541c-9087-f973586cdef3/attachment.md","path":"references/php-architecture.md","size":37672,"sha256":"e3164d2cadd371db91f6ec3c87006531853099ebe1ee4f0833f94d5a9d155ad1","contentType":"text/markdown; charset=utf-8"},{"id":"6bcf7b02-43d6-558e-b636-455630c1d929","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6bcf7b02-43d6-558e-b636-455630c1d929/attachment.md","path":"references/report-template.md","size":4620,"sha256":"d0cd4818c814261b34fc7464a5e82a51015d4c38c2cf1a0eb5e2d9bdce72814b","contentType":"text/markdown; charset=utf-8"},{"id":"af68a00d-ac76-554c-930b-ada0076004d7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/af68a00d-ac76-554c-930b-ada0076004d7/attachment.md","path":"references/runtests-validation.md","size":12702,"sha256":"e6cb139eb19592c42d8b1c87950632245e67fb34476110d63d7d70358900858b","contentType":"text/markdown; charset=utf-8"},{"id":"a6b0b31f-a22c-5ab1-891b-29dfab5c2254","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a6b0b31f-a22c-5ab1-891b-29dfab5c2254/attachment.md","path":"references/ter-publishing.md","size":18550,"sha256":"b92a27c033ec0055c7bb2ea963be2dd320d813b3168e70046c5148900f727fae","contentType":"text/markdown; charset=utf-8"},{"id":"1cbc28c3-b3ae-5c97-84ff-d9e83501cfee","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1cbc28c3-b3ae-5c97-84ff-d9e83501cfee/attachment.md","path":"references/testing-standards.md","size":6608,"sha256":"270a8722e20e62f368442a5c4f6f716dc1a6b64fd94ea02f3f7dde78a73a83cc","contentType":"text/markdown; charset=utf-8"},{"id":"d67ed2d4-d2ab-5fd0-97fb-9133f183e4bf","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d67ed2d4-d2ab-5fd0-97fb-9133f183e4bf/attachment.md","path":"references/v13-deprecations.md","size":10800,"sha256":"a6374eaf550050c52292a7d6187d044a7990222ec107f0f076948e4c4498d224","contentType":"text/markdown; charset=utf-8"},{"id":"60d50259-9961-5259-a25a-636cc66df5b3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/60d50259-9961-5259-a25a-636cc66df5b3/attachment.md","path":"references/v13-v14-dual-compatibility.md","size":10334,"sha256":"6843dc0b5039881370baf8b0c8a1575c1fabd5badba7bfa9c54c72b527c32b66","contentType":"text/markdown; charset=utf-8"},{"id":"8426f7ec-6644-5e42-9284-686cb8dfea93","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8426f7ec-6644-5e42-9284-686cb8dfea93/attachment.md","path":"references/v14-deprecations.md","size":17785,"sha256":"df638722aa0b657b1071f1858594521991db8836ff75530991dfab6ddce4085f","contentType":"text/markdown; charset=utf-8"},{"id":"5878af40-9b6f-5910-8d88-39bcc68453d2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5878af40-9b6f-5910-8d88-39bcc68453d2/attachment.md","path":"references/version-requirements.md","size":5006,"sha256":"57cd90ceebac001875c1a36fcd4c8ba989c2b7ba6522963659b5665b5e73f961","contentType":"text/markdown; charset=utf-8"},{"id":"d657b36e-dd82-50e8-bc44-5385078c884e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d657b36e-dd82-50e8-bc44-5385078c884e/attachment.sh","path":"scripts/check-architecture.sh","size":6554,"sha256":"5a1de57f7de0fd88e53bba38a9c9bb1ca064cce880104cd7b9b80e033d8ea4f6","contentType":"application/x-sh; charset=utf-8"},{"id":"962904e4-b433-55e2-bf22-f9833c4bcf45","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/962904e4-b433-55e2-bf22-f9833c4bcf45/attachment.sh","path":"scripts/check-coding-standards.sh","size":3986,"sha256":"22d162f56484650d1a801f7e418103408dfb643086c36d1684f289f4d5a81dd4","contentType":"application/x-sh; charset=utf-8"},{"id":"67606361-493c-5a53-be0f-88b9a0b043b3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/67606361-493c-5a53-be0f-88b9a0b043b3/attachment.sh","path":"scripts/check-conformance.sh","size":7311,"sha256":"34241744fb421caa11d17ca1c2a3e8e6ef16854c6dfd6849178ccc7fa7ee6803","contentType":"application/x-sh; charset=utf-8"},{"id":"420c4bee-9b6e-5331-88ba-7e45c3c9e65e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/420c4bee-9b6e-5331-88ba-7e45c3c9e65e/attachment.sh","path":"scripts/check-documentation.sh","size":2610,"sha256":"78a11ad8f33b357698d1b13db5d3db746d50a15a66bb12db8283c0ac69b7ef5b","contentType":"application/x-sh; charset=utf-8"},{"id":"459a1fd2-cac5-5212-bd40-7b0acab1650d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/459a1fd2-cac5-5212-bd40-7b0acab1650d/attachment.sh","path":"scripts/check-file-structure.sh","size":5866,"sha256":"be6196478023570846bc211fdf50aa8ae8174ea09c352c4cee036ffa743c3ba6","contentType":"application/x-sh; charset=utf-8"},{"id":"83ff2fa8-72b0-5d01-ba58-1c5aa856716c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/83ff2fa8-72b0-5d01-ba58-1c5aa856716c/attachment.sh","path":"scripts/check-phpstan-baseline.sh","size":3543,"sha256":"e297e01caee0ecb47715e2c4631ba03b62e1fb9e24567c58d6c23f3539841735","contentType":"application/x-sh; charset=utf-8"},{"id":"93983bb8-5aec-5d1e-afa1-863540bd05be","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/93983bb8-5aec-5d1e-afa1-863540bd05be/attachment.sh","path":"scripts/check-testing.sh","size":6282,"sha256":"9493fc1deb2384a4edccce18557e1f3b6f0e8a37a1dece52e867a3cb33736fba","contentType":"application/x-sh; charset=utf-8"},{"id":"fb02cc32-b908-56ae-995b-a8e5ac746084","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fb02cc32-b908-56ae-995b-a8e5ac746084/attachment.sh","path":"scripts/generate-report.sh","size":6589,"sha256":"5792c0770a616fa9b23073005626a46e81d85c14c05c46c77aeca2c7b84bd832","contentType":"application/x-sh; charset=utf-8"}],"bundle_sha256":"71a40d104a150936b6697ffdeaa1efb3fa14cbe75ed45193b17a0cdad52a39bf","attachment_count":42,"text_attachments":40,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":2,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/typo3-conformance/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"security","import_tag":"clean-skills-v1","description":"Use when assessing TYPO3 extension quality, conformance checking, standards compliance, modernization to v12/v13/v14 (v14.3 LTS is the default/gold standard), TER readiness, or best practices review. Also triggers on: extension audit, quality score, full assessment, fix all findings, conformance audit, Fluid 5 strict ViewHelpers, ext_tables.php removal, Extbase attributes (Authorize/RateLimit), HashService removal, Bootstrap 5 migration, CSP compliance, ViewHelper security, XLIFF hygiene, PHP 8.4/8.5 compat."}},"renderedAt":1782988283081}

TYPO3 Extension Conformance Checker Evaluate TYPO3 extensions against TYPO3 coding standards, architecture patterns, and best practices. When to Use - Extension quality / TER readiness - Scored conformance reports - Modernization to v12/v13/v14 ( v14.3 LTS is default/gold standard ) Delegation Testing - | Docs - | OpenSSF - Workflow Step 0: Context Read ext emconf.php + composer.json for version, type, scope. Steps 1-11: Checks 1. Metadata -- key, TYPO3 version, type 2. Structure -- composer.json, ext emconf.php, Classes/, Configuration/, Resources/ 3. Coding -- strict types, PSR-12, PHP 8.4…