1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
19
20use TYPO3\CMS\Core\Utility\ArrayUtility;
21use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
22
23/**
24 * @internal
25 */
26class CreatableFormElementPropertiesValidator extends ElementBasedValidator
27{
28
29    /**
30     * Checks if the form element property is defined within the form editor setup
31     * or if the property is defined within the "predefinedDefaults" in the form editor setup
32     * and the property value matches the predefined value
33     * or if there is a valid hmac hash for the value.
34     * If the form element property is defined within the form editor setup
35     * and there is no valid hmac hash for the value
36     * and is the form element property configured to only allow a limited set of values,
37     * check the current (submitted) value against the allowed set of values (defined within the form setup).
38     *
39     * @param string $key
40     * @param mixed $value
41     */
42    public function __invoke(string $key, $value)
43    {
44        $dto = $this->validationDto->withPropertyPath($key);
45
46        if ($this->getConfigurationService()->isFormElementPropertyDefinedInFormEditorSetup($dto)) {
47            if ($this->getConfigurationService()->formElementPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup($dto)) {
48                $this->validateFormElementValue($value, $dto);
49            }
50        } elseif (
51            $this->getConfigurationService()->isFormElementPropertyDefinedInPredefinedDefaultsInFormEditorSetup($dto)
52            && !ArrayUtility::isValidPath($this->currentElement, $this->buildHmacDataPath($dto->getPropertyPath()), '.')
53        ) {
54            $this->validateFormElementPredefinedDefaultValue($value, $dto);
55        } else {
56            $this->validateFormElementPropertyValueByHmacData(
57                $this->currentElement,
58                $value,
59                $this->sessionToken,
60                $dto
61            );
62        }
63    }
64
65    /**
66     * Throws an exception if the value from a form element property
67     * does not match the default value from the form editor setup.
68     *
69     * @param mixed $value
70     * @param ValidationDto $dto
71     * @throws PropertyException
72     */
73    protected function validateFormElementPredefinedDefaultValue(
74        $value,
75        ValidationDto $dto
76    ): void {
77        // If the form element is newly created, we have to compare the $value (form definition) with $predefinedDefaultValue (form setup)
78        // to check the integrity (at this time we don't have a hmac for the $value to check the integrity)
79        $predefinedDefaultValue = $this->getConfigurationService()->getFormElementPredefinedDefaultValueFromFormEditorSetup($dto);
80        if ($value !== $predefinedDefaultValue) {
81            $throwException = true;
82
83            if (is_string($predefinedDefaultValue)) {
84                // Last chance:
85                // Get all translations (from all backend languages) for the untranslated! $predefinedDefaultValue and
86                // compare the (already translated) $value (from the form definition) against the possible
87                // translations from $predefinedDefaultValue.
88                // Usecase:
89                //   * backend language is EN
90                //   * open the form editor and add a ContentElement form element
91                //   * switch to another browser tab and change the backend language to DE
92                //   * clear the cache
93                //   * go back to the form editor and click the save button
94                // Out of scope:
95                //   * the same scenario as above + delete the previous chosen backend language within the maintenance tool
96                $untranslatedPredefinedDefaultValue = $this->getConfigurationService()->getFormElementPredefinedDefaultValueFromFormEditorSetup($dto, false);
97                $translations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKey(
98                    $untranslatedPredefinedDefaultValue,
99                    $dto->getPrototypeName()
100                );
101
102                if (in_array($value, $translations, true)) {
103                    $throwException = false;
104                }
105            }
106
107            if ($throwException) {
108                $message = 'The value "%s" of property "%s" (form element "%s") is not equal to the default value "%s" #1528588035';
109                throw new PropertyException(
110                    sprintf(
111                        $message,
112                        $value,
113                        $dto->getPropertyPath(),
114                        $dto->getFormElementIdentifier(),
115                        $predefinedDefaultValue
116                    ),
117                    1528588035
118                );
119            }
120        }
121    }
122
123    /**
124     * Throws an exception if the value from a form element property
125     * does not match the allowed set of values (defined within the form setup).
126     *
127     * @param mixed $value
128     * @param ValidationDto $dto
129     * @throws PropertyException
130     */
131    protected function validateFormElementValue(
132        $value,
133        ValidationDto $dto
134    ): void {
135        $allowedValues = $this->getConfigurationService()->getAllowedValuesForFormElementPropertyFromFormEditorSetup($dto);
136
137        if (!in_array($value, $allowedValues, true)) {
138            $untranslatedAllowedValues = $this->getConfigurationService()->getAllowedValuesForFormElementPropertyFromFormEditorSetup($dto, false);
139            // Compare the $value against the untranslated set of allowed values
140            if (in_array($value, $untranslatedAllowedValues, true)) {
141                // All good, $value is within the untranslated set of allowed values
142                return;
143            }
144            // Get all translations (from all backend languages) for the untranslated! $allowedValues and
145            // compare the (already translated) $value (from the form definition) against all possible
146            // translations for $untranslatedAllowedValues.
147            $allPossibleAllowedValuesTranslations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKeys(
148                $untranslatedAllowedValues,
149                $dto->getPrototypeName()
150            );
151
152            foreach ($allPossibleAllowedValuesTranslations as $translations) {
153                if (in_array($value, $translations, true)) {
154                    // All good, $value is within the set of translated allowed values
155                    return;
156                }
157            }
158
159            // Last chance:
160            // If $value is not configured within the form setup as an allowed value
161            // but was written within the form definition by hand (and therefore contains a hmac),
162            // check if $value is manipulated.
163            // If $value has no hmac or if the hmac exists but is not valid,
164            // then $this->validatePropertyCollectionElementPropertyValueByHmacData() will
165            // throw an exception.
166            $this->validateFormElementPropertyValueByHmacData(
167                $this->currentElement,
168                $value,
169                $this->sessionToken,
170                $dto
171            );
172        }
173    }
174}
175