Skip to content

Commit ba3b018

Browse files
bnffe-hicking
andauthored
[TASK] Allow settings labels to be provided in xlf files (#734)
Required prerequisite for: https://review.typo3.org/c/Packages/TYPO3.CMS/+/85869 https://forge.typo3.org/issues/104831 --------- Co-authored-by: Garvin Hicking <[email protected]>
1 parent 23cc28e commit ba3b018

File tree

6 files changed

+1510
-17
lines changed

6 files changed

+1510
-17
lines changed

packages/typo3-docs-theme/src/Directives/SiteSetSettingsDirective.php

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ public function processNode(
5252
Directive $directive,
5353
): Node {
5454
try {
55-
$contents = $this->loadFileFromDocumentation($blockContext, $directive);
55+
// The path delivered via the directive like:
56+
// .. typo3:site-set-settings:: PROJECT:/Configuration/Sets/FluidStyledContent/settings.definitions.yaml
57+
$setConfigurationFile = $directive->getData();
58+
$contents = $this->loadFileFromDocumentation($blockContext, $setConfigurationFile);
5659
// Parse the YAML content
5760
$yamlData = Yaml::parse($contents);
5861

@@ -63,14 +66,60 @@ public function processNode(
6366
$this->logger->warning($exception->getMessage(), $blockContext->getLoggerInformation());
6467
return $this->getErrorNode();
6568
}
66-
return $this->buildConfvalMenu($directive, $yamlData['settings']);
69+
70+
71+
$labelsFile = null;
72+
try {
73+
$configYamlFile = dirname($setConfigurationFile) . '/config.yaml';
74+
$contents = $this->loadFileFromDocumentation($blockContext, $configYamlFile);
75+
// Parse the YAML content
76+
$configYamlData = Yaml::parse($contents);
77+
78+
if (is_array($configYamlData)) {
79+
$labelsFile = $configYamlData['labels'] ?? null;
80+
}
81+
} catch (FileLoadingException $exception) {
82+
// ignore, config.yaml isn't required
83+
}
84+
85+
$labelContents = '';
86+
if ($labelsFile) {
87+
// Asume all EXT: references are relative to the rendered PROJECT
88+
$labelsFile = preg_replace('/^EXT:[^\/]*\//', 'PROJECT:/', $labelsFile);
89+
try {
90+
$labelContents = $this->loadFileFromDocumentation($blockContext, $labelsFile);
91+
} catch (FileLoadingException $exception) {
92+
// ignore, config.yaml isn't required
93+
}
94+
}
95+
$labels = [];
96+
$descriptions = [];
97+
if ($labelContents) {
98+
$xml = new \DOMDocument();
99+
if ($xml->loadXML($labelContents)) {
100+
foreach ($xml->getElementsByTagName('trans-unit') as $label) {
101+
$id = $label->getAttribute('id');
102+
$value = ($label->getElementsByTagName('source')[0] ?? null)?->textContent ?? '';
103+
if (!$value) {
104+
continue;
105+
}
106+
if (str_starts_with($id, 'settings.description.')) {
107+
$descriptions[substr($id, 21)] = $value;
108+
} elseif (str_starts_with($id, 'settings.')) {
109+
$labels[substr($id, 9)] = $value;
110+
}
111+
}
112+
}
113+
}
114+
115+
return $this->buildConfvalMenu($directive, $yamlData['settings'], $labels, $descriptions);
67116
}
68117

69118
/**
70119
* @throws \League\Flysystem\FileNotFoundException
71120
* @throws FileLoadingException
72121
*/
73-
public function loadFileFromDocumentation(BlockContext $blockContext, Directive $directive): string
122+
public function loadFileFromDocumentation(BlockContext $blockContext, string $filename): string
74123
{
75124
$parser = $blockContext->getDocumentParserContext()->getParser();
76125
$parserContext = $parser->getParserContext();
@@ -80,10 +129,6 @@ public function loadFileFromDocumentation(BlockContext $blockContext, Directive
80129
$adapter = $origin->getAdapter();
81130
$pathPrefix = (string)$adapter->getPathPrefix();
82131

83-
// The path delivered via the directive like:
84-
// .. typo3:site-set-settings:: PROJECT:/Configuration/Sets/FluidStyledContent/settings.definitions.yaml
85-
$setConfigurationFile = $directive->getData();
86-
87132
// By default, the RST files are placed inside a "Documentation" subdirectory.
88133
// When using the docker container, this origin root path is then set to "/project/Documentation".
89134
// No files on the "/project/" directory level can usually be accessed, even though they may belong
@@ -92,19 +137,19 @@ public function loadFileFromDocumentation(BlockContext $blockContext, Directive
92137
// a special string "PROJECT:" is evaluated here.
93138
// If a path starts with that notation, it will be referenced from the "/project/..." directory level.
94139
// It will not break out of the "/project/" mapping!
95-
if (str_starts_with($setConfigurationFile, 'PROJECT:')) {
140+
if (str_starts_with($filename, 'PROJECT:')) {
96141
// This will replace "PROJECT:/Configuration/Sets/File.yaml" with "/Configuration/Sets/File.yaml"
97142
// and is then passed to absoluteRelativePath() which will set $path = "/Configuration/Sets/File.yaml",
98143
// but ensure no "../../../" or other path traversal is allowed.
99-
$path = $parserContext->absoluteRelativePath(str_replace('PROJECT:', '', $setConfigurationFile));
144+
$path = $parserContext->absoluteRelativePath(str_replace('PROJECT:', '', $filename));
100145

101146
// Get the current origin Path, usually "/project/Documentation/", and go one level up.
102147
$newOriginPath = dirname($pathPrefix) . '/';
103148

104149
// Temporarily change the path prefix now to "/project/"
105150
$adapter->setPathPrefix($newOriginPath);
106151
} else {
107-
$path = $parserContext->absoluteRelativePath($setConfigurationFile);
152+
$path = $parserContext->absoluteRelativePath($filename);
108153
}
109154

110155
if (!$origin->has($path)) {
@@ -133,8 +178,10 @@ private function getErrorNode(): ParagraphNode
133178

134179
/**
135180
* @param array<string, array<string, string>> $settings
181+
* @param array<string, string> $labels
182+
* @param array<string, string> $descriptions
136183
*/
137-
public function buildConfvalMenu(Directive $directive, array $settings): ConfvalMenuNode
184+
public function buildConfvalMenu(Directive $directive, array $settings, array $labels, array $descriptions): ConfvalMenuNode
138185
{
139186
$idPrefix = '';
140187
if ($directive->getOptionString('name') !== '') {
@@ -143,7 +190,7 @@ public function buildConfvalMenu(Directive $directive, array $settings): Confval
143190

144191
$confvals = [];
145192
foreach ($settings as $key => $setting) {
146-
$confvals[] = $this->buildConfval($setting, $idPrefix, $key, $directive);
193+
$confvals[] = $this->buildConfval($setting, $idPrefix, $key, $directive, $labels, $descriptions);
147194
}
148195
$reservedParameterNames = [
149196
'name',
@@ -182,22 +229,26 @@ public function buildConfvalMenu(Directive $directive, array $settings): Confval
182229

183230
/**
184231
* @param array<string, scalar|array<string, scalar>> $setting
232+
* @param array<string, string> $labels
233+
* @param array<string, string> $descriptions
185234
*/
186-
public function buildConfval(array $setting, string $idPrefix, string $key, Directive $directive): ConfvalNode
235+
public function buildConfval(array $setting, string $idPrefix, string $key, Directive $directive, array $labels, array $descriptions): ConfvalNode
187236
{
188237
$content = [];
189-
if (is_string($setting['description'] ?? false)) {
238+
$description = $setting['description'] ?? $descriptions[$key] ?? false;
239+
if (is_string($description)) {
190240
$content[] = new ParagraphNode([
191-
new InlineCompoundNode([new PlainTextInlineNode((string)$setting['description'])]),
241+
new InlineCompoundNode([new PlainTextInlineNode($description)]),
192242
]);
193243
}
194244
$default = null;
195245
if (($setting['default'] ?? '') !== '') {
196246
$default = new InlineCompoundNode([new CodeInlineNode($this->customPrint(($setting['default'])), '')]);
197247
}
198248
$additionalFields = [];
199-
if (is_string($setting['label'] ?? false)) {
200-
$additionalFields['Label'] = new InlineCompoundNode([new PlainTextInlineNode($setting['label'] ?? '')]);
249+
$label = $setting['label'] ?? $labels[$key] ?? false;
250+
if (is_string($label)) {
251+
$additionalFields['Label'] = new InlineCompoundNode([new PlainTextInlineNode($label)]);
201252
}
202253
if (is_array($setting['enum'] ?? false)) {
203254
$additionalFields['Enum'] = new InlineCompoundNode([new PlainTextInlineNode((string) json_encode($setting['enum'], JSON_PRETTY_PRINT))]);

0 commit comments

Comments
 (0)