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