From 0696b6e07b0ae2ac824e660f80be9469596ae6c5 Mon Sep 17 00:00:00 2001
From: Benjamin Franzke <ben@bnf.dev>
Date: Mon, 9 Sep 2024 20:55:15 +0200
Subject: [PATCH] [TASK] Add integrity check for set and setting labels
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Labels for site sets and site settings – as added in #104831 - are
now verified for completenes.

The script checkIntegritySetLabels.php checks for required
and optional labels to ensure labels.xlf and settings.definitions.yaml
are in sync.

Resolves: #104867
Related: #104831
Releases: main
Change-Id: I7c91d9ce1fe79ac3137a06b53010fe36418cc896
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/85931
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Garvin Hicking <gh@faktor-e.de>
Tested-by: Oliver Bartsch <bo@cedev.de>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Garvin Hicking <gh@faktor-e.de>
---
 Build/Scripts/checkIntegritySetLabels.php | 135 ++++++++++++++++++++++
 Build/Scripts/runTests.sh                 |   5 +
 Build/gitlab-ci/nightly/integrity.yml     |   1 +
 Build/gitlab-ci/pre-merge/integrity.yml   |   1 +
 4 files changed, 142 insertions(+)
 create mode 100755 Build/Scripts/checkIntegritySetLabels.php

diff --git a/Build/Scripts/checkIntegritySetLabels.php b/Build/Scripts/checkIntegritySetLabels.php
new file mode 100755
index 000000000000..b001664560f7
--- /dev/null
+++ b/Build/Scripts/checkIntegritySetLabels.php
@@ -0,0 +1,135 @@
+#!/usr/bin/env php
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Symfony\Component\Console\Helper\Table;
+use Symfony\Component\Console\Output\ConsoleOutput;
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Yaml\Yaml;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+if (PHP_SAPI !== 'cli') {
+    die('Script must be called from command line.' . chr(10));
+}
+
+final readonly class CheckIntegritySetLabels
+{
+    public function execute(): int
+    {
+        $filesToProcess = $this->findSetLabels();
+        $output = new ConsoleOutput();
+
+        $resultAcrossAllFiles = 0;
+        $testResults = [];
+        /** @var \SplFileInfo $labelFile */
+        foreach ($filesToProcess as $labelFile) {
+            $fullFilePath = $labelFile->getRealPath();
+            $result = $this->checkValidLabels($fullFilePath);
+            if ($result !== null) {
+                $testResults[] = $result;
+            }
+        }
+        if ($testResults === []) {
+            return 0;
+        }
+
+        $table = new Table($output);
+        $table->setHeaders([
+            'EXT',
+            'Set',
+            'Invalid set labels',
+            'Missing set labels',
+        ]);
+        foreach ($testResults as $result) {
+            $table->addRow([
+                $result['ext'],
+                $result['set'],
+                implode("\n", $result['invalid']),
+                implode("\n", $result['missing']),
+            ]);
+        }
+        $table->render();
+        return 1;
+    }
+
+    private function findSetLabels(): Finder
+    {
+        $finder = new Finder();
+        $labelFiles = $finder
+            ->files()
+            ->in(__DIR__ . '/../../typo3/sysext/*/Configuration/Sets/*')
+            ->name('labels.xlf');
+        return $labelFiles;
+    }
+
+    private function checkValidLabels(string $labelFile): ?array
+    {
+        $extensionKey = $this->extractExtensionKey($labelFile);
+        $doc = new DOMDocument();
+        if (!$doc->load($labelFile)) {
+            throw new \RuntimeException('Failed to load xlf file: ' . $labelFile, 1725902515);
+        }
+
+        $requiredLabels = [
+            'label',
+        ];
+        $optionalLabels = [
+            'description',
+        ];
+
+        $settingsDefinitions = Yaml::parseFile(dirname($labelFile) . '/settings.definitions.yaml');
+        foreach ($settingsDefinitions['settings'] as $key => $settingsDefinition) {
+            $requiredLabels[] = 'settings.' . $key;
+            $optionalLabels[] = 'settings.description.' . $key;
+        }
+
+        $setName = Yaml::parseFile(dirname($labelFile) . '/config.yaml')['name'];
+
+        $availableLabels = [];
+        foreach ($doc->getElementsByTagName('trans-unit') as $tu) {
+            $availableLabels[] = $tu->getAttribute('id');
+        }
+
+        $allowedLabels = [
+            ...$requiredLabels,
+            ...$optionalLabels,
+        ];
+        $missing = array_diff($requiredLabels, $availableLabels);
+        $invalid = array_diff($availableLabels, $allowedLabels);
+
+        if ($missing === [] && $invalid === []) {
+            return null;
+        }
+
+        return [
+            'ext' => $extensionKey,
+            'set' => $setName,
+            'invalid' => $invalid,
+            'missing' => $missing,
+        ];
+    }
+
+    private function extractExtensionKey(string $filename): string
+    {
+        $pattern = '/typo3\/sysext\/(?<extName>[a-z].+?)\//';
+        preg_match_all($pattern, $filename, $matches, PREG_SET_ORDER, 0);
+        return $matches[0]['extName'];
+    }
+}
+
+exit((new CheckIntegritySetLabels())->execute());
diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh
index b6d78fb7c9d4..9034dc226445 100755
--- a/Build/Scripts/runTests.sh
+++ b/Build/Scripts/runTests.sh
@@ -198,6 +198,7 @@ Options:
             - cglHeaderGit: test and fix latest committed patch for CGL file header compliance
             - checkBom: check UTF-8 files do not contain BOM
             - checkComposer: check composer.json files for version integrity
+            - checkIntegritySetLabels: check labels.xlf file integrity of site sets
             - checkExtensionScannerRst: test all .rst files referenced by extension scanner exist
             - checkFilePathLength: test core file paths do not exceed maximum length
             - checkGitSubmodule: test core git has no sub modules defined
@@ -893,6 +894,10 @@ case ${TEST_SUITE} in
         ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-utf8bom-${SUFFIX} ${IMAGE_PHP} Build/Scripts/checkUtf8Bom.sh
         SUITE_EXIT_CODE=$?
         ;;
+    checkIntegritySetLabels)
+        ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-integrity-set-labels-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/checkIntegritySetLabels.php
+        SUITE_EXIT_CODE=$?
+        ;;
     checkComposer)
         ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name check-composer-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/checkIntegrityComposer.php
         SUITE_EXIT_CODE=$?
diff --git a/Build/gitlab-ci/nightly/integrity.yml b/Build/gitlab-ci/nightly/integrity.yml
index 32ae1708fd68..3ce3cc76bb7b 100644
--- a/Build/gitlab-ci/nightly/integrity.yml
+++ b/Build/gitlab-ci/nightly/integrity.yml
@@ -29,6 +29,7 @@ integration various php 8.4:
     - Build/Scripts/runTests.sh -s checkBom -p 8.4
     - Build/Scripts/runTests.sh -s checkComposer -p 8.4
     - Build/Scripts/runTests.sh -s checkIntegrityPhp -p 8.4
+    - Build/Scripts/runTests.sh -s checkIntegritySetLabels -p 8.4
     - Build/Scripts/runTests.sh -s lintServicesYaml -p 8.4
     - Build/Scripts/runTests.sh -s lintYaml -p 8.4
 
diff --git a/Build/gitlab-ci/pre-merge/integrity.yml b/Build/gitlab-ci/pre-merge/integrity.yml
index 163f518a3f31..a2e949ac0e23 100644
--- a/Build/gitlab-ci/pre-merge/integrity.yml
+++ b/Build/gitlab-ci/pre-merge/integrity.yml
@@ -31,6 +31,7 @@ integration various php 8.2 pre-merge:
     - Build/Scripts/runTests.sh -s checkBom -p 8.2
     - Build/Scripts/runTests.sh -s checkComposer -p 8.2
     - Build/Scripts/runTests.sh -s checkIntegrityPhp -p 8.2
+    - Build/Scripts/runTests.sh -s checkIntegritySetLabels -p 8.2
     - Build/Scripts/runTests.sh -s lintServicesYaml -p 8.2
     - Build/Scripts/runTests.sh -s lintYaml -p 8.2
 
-- 
GitLab