1<?php
2declare(strict_types = 1);
3namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
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
18use TYPO3\CMS\Core\Utility\ArrayUtility;
19use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
20
21/**
22 * @internal
23 */
24class CreatablePropertyCollectionElementPropertiesValidator extends CollectionBasedValidator
25{
26
27    /**
28     * Checks if the property collection element property is defined
29     * within the form editor setup or if the property is definied within
30     * the "predefinedDefaults" in the form editor setup
31     * and the property value matches the predefined value
32     * or if there is a valid hmac hash for the value.
33     * If the property collection element property is defined within the form editor setup
34     * and there is no valid hmac hash for the value
35     * and is the form property collection element property configured to only allow a limited set of values,
36     * check the current (submitted) value against the allowed set of values (defined within the form setup).
37     *
38     * @param string $key
39     * @param mixed $value
40     */
41    public function __invoke(string $key, $value)
42    {
43        $dto = $this->validationDto->withPropertyPath($key);
44
45        if ($this->getConfigurationService()->isPropertyCollectionPropertyDefinedInFormEditorSetup($dto)) {
46            if ($this->getConfigurationService()->propertyCollectionPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup($dto)) {
47                $this->validatePropertyCollectionPropertyValue($value, $dto);
48            }
49        } elseif (
50            $this->getConfigurationService()->isPropertyCollectionPropertyDefinedInPredefinedDefaultsInFormEditorSetup($dto)
51            && !ArrayUtility::isValidPath($this->currentElement, $this->buildHmacDataPath($dto->getPropertyPath()), '.')
52        ) {
53            $this->validatePropertyCollectionElementPredefinedDefaultValue($value, $dto);
54        } else {
55            $this->validatePropertyCollectionElementPropertyValueByHmacData(
56                $this->currentElement,
57                $value,
58                $this->sessionToken,
59                $dto
60            );
61        }
62    }
63
64    /**
65     * Throws an exception if the value from a property collection property
66     * does not match the default value from the form editor setup.
67     *
68     * @param mixed $value
69     * @param ValidationDto $dto
70     * @throws PropertyException
71     */
72    protected function validatePropertyCollectionElementPredefinedDefaultValue(
73        $value,
74        ValidationDto $dto
75    ): void {
76        // If the property collection element is newely created, we have to compare the $value (form definition) with $predefinedDefaultValue (form setup)
77        // to check the integrity (at this time we don't have a hmac on the value to check the integrity)
78        $predefinedDefaultValue = $this->getConfigurationService()->getPropertyCollectionPredefinedDefaultValueFromFormEditorSetup($dto);
79        if ($value !== $predefinedDefaultValue) {
80            $throwException = true;
81
82            if (is_string($predefinedDefaultValue)) {
83                // Last chance:
84                // Get all translations (from all backend languages) for the untranslated! $predefinedDefaultValue and
85                // compare the (already translated) $value (from the form definition) against the possible
86                // translations from $predefinedDefaultValue.
87                $untranslatedPredefinedDefaultValue = $this->getConfigurationService()->getPropertyCollectionPredefinedDefaultValueFromFormEditorSetup($dto, false);
88                $translations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKey(
89                    $untranslatedPredefinedDefaultValue,
90                    $dto->getPrototypeName()
91                );
92
93                if (in_array($value, $translations, true)) {
94                    $throwException = false;
95                }
96            }
97
98            if ($throwException) {
99                $message = 'The value "%s" of property "%s" (form element "%s" / "%s.%s") is not equal to the default value "%s" #1528591502';
100                throw new PropertyException(
101                    sprintf(
102                        $message,
103                        $value,
104                        $dto->getPropertyPath(),
105                        $dto->getFormElementIdentifier(),
106                        $dto->getPropertyCollectionName(),
107                        $dto->getPropertyCollectionElementIdentifier(),
108                        $predefinedDefaultValue
109                    ),
110                    1528591502
111                );
112            }
113        }
114    }
115
116    /**
117     * Throws an exception if the value from a property collection property
118     * does not match the allowed set of values (defined within the form setup).
119     *
120     * @param mixed $value
121     * @param ValidationDto $dto
122     * @throws PropertyException
123     */
124    protected function validatePropertyCollectionPropertyValue(
125        $value,
126        ValidationDto $dto
127    ): void {
128        $allowedValues = $this->getConfigurationService()->getAllowedValuesForPropertyCollectionPropertyFromFormEditorSetup($dto);
129
130        if (!in_array($value, $allowedValues, true)) {
131            $untranslatedAllowedValues = $this->getConfigurationService()->getAllowedValuesForPropertyCollectionPropertyFromFormEditorSetup($dto, false);
132            // Compare the $value against the untranslated set of allowed values
133            if (in_array($value, $untranslatedAllowedValues, true)) {
134                // All good, $value is within the untranslated set of allowed values
135                return;
136            }
137            // Get all translations (from all backend languages) for the untranslated! $allowedValues and
138            // compare the (already translated) $value (from the form definition) against all possible
139            // translations for $untranslatedAllowedValues.
140            $allPossibleAllowedValuesTranslations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKeys(
141                $untranslatedAllowedValues,
142                $dto->getPrototypeName()
143            );
144
145            foreach ($allPossibleAllowedValuesTranslations as $translations) {
146                if (in_array($value, $translations, true)) {
147                    // All good, $value is within the set of translated allowed values
148                    return;
149                }
150            }
151
152            // Last chance:
153            // If $value is not configured within the form setup as an allowed value
154            // but was written within the form definition by hand (and therefore contains a hmac),
155            // check if $value is manipulated.
156            // If $value has no hmac or if the hmac exists but is not valid,
157            // then $this->validatePropertyCollectionElementPropertyValueByHmacData() will
158            // throw an exception.
159            $this->validatePropertyCollectionElementPropertyValueByHmacData(
160                $this->currentElement,
161                $value,
162                $this->sessionToken,
163                $dto
164            );
165        }
166    }
167}
168