Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
51.14% covered (warning)
51.14%
45 / 88
50.00% covered (danger)
50.00%
7 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
tao_helpers_form_FormContainer
51.14% covered (warning)
51.14%
45 / 88
50.00% covered (danger)
50.00%
7 / 14
216.45
0.00% covered (danger)
0.00%
0 / 1
 __construct
82.76% covered (warning)
82.76%
24 / 29
0.00% covered (danger)
0.00%
0 / 1
10.51
 __destruct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getForm
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addSanitizerValidator
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 initForm
n/a
0 / 0
n/a
0 / 0
0
 initElements
n/a
0 / 0
n/a
0 / 0
0
 validate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPostData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 initCsrfProtection
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 applyAdditionalValidationRules
37.50% covered (danger)
37.50%
3 / 8
0.00% covered (danger)
0.00%
0 / 1
5.20
 applyAttributeValidators
23.08% covered (danger)
23.08%
3 / 13
0.00% covered (danger)
0.00%
0 / 1
11.28
 propagateElement
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 configureFormValidators
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 getSanitizerValidationFeeder
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 withServiceManager
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getContainer
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2008-2010 (original work) Deutsche Institut für Internationale Pädagogische Forschung
19 *                         (under the project TAO-TRANSFER);
20 *               2009-2012 (update and modification) Public Research Centre Henri Tudor
21 *                         (under the project TAO-SUSTAIN & TAO-DEV);
22 *               2020-2022 (original work) Open Assessment Technologies SA.
23 */
24
25declare(strict_types=1);
26
27use oat\oatbox\service\ServiceManager;
28use oat\oatbox\validator\ValidatorInterface;
29use oat\tao\model\form\Modifier\AbstractFormModifier;
30use oat\tao\model\security\xsrf\TokenService;
31use Psr\Container\ContainerInterface;
32use tao_helpers_form_FormFactory as FormFactory;
33use oat\tao\helpers\form\elements\xhtml\CsrfToken;
34use oat\tao\helpers\form\elements\FormElementAware;
35use oat\tao\helpers\form\Feeder\SanitizerValidationFeeder;
36use oat\tao\helpers\form\validators\CrossElementEvaluationAware;
37use oat\tao\helpers\form\Feeder\SanitizerValidationFeederInterface;
38
39/**
40 * This class provide a container for a specific form instance.
41 * Its subclasses instantiate a form.
42 *
43 * @author Cédric Alfonsi, <cedric.alfonsi@tudor.lu>
44 */
45abstract class tao_helpers_form_FormContainer
46{
47    public const CSRF_PROTECTION_OPTION = 'csrf_protection';
48    public const IS_DISABLED = 'is_disabled';
49    public const ADDITIONAL_VALIDATORS = 'extraValidators';
50    public const ATTRIBUTE_VALIDATORS = 'attributeValidators';
51    public const FORM_MODIFIERS = 'formModifiers';
52
53    public const WITH_SERVICE_MANAGER = 'withServiceManager';
54
55    /**
56     * the form instance contained
57     *
58     * @var tao_helpers_form_Form
59     */
60    protected $form;
61
62    /**
63     * the data of the form
64     *
65     * @var array
66     */
67    protected $data = [];
68
69    /**
70     * the form options
71     *
72     * @var array
73     */
74    protected $options = [];
75
76    /**
77     * static list of all instantiated forms
78     *
79     * @var array
80     */
81    protected static $forms = [];
82
83    /**
84     * @var array
85     */
86    private $postData = [];
87
88    /** @var SanitizerValidationFeederInterface */
89    private $sanitizerValidationFeeder;
90
91    /** @var ServiceManager */
92    private $serviceManager;
93
94    /**
95     * The constructor, initialize and build the form
96     * regarding the initForm and initElements methods
97     * to be overridden
98     *
99     * @throws common_Exception
100     * @author Cédric Alfonsi, <cedric.alfonsi@tudor.lu>
101     */
102    public function __construct(array $data = [], array $options = [])
103    {
104        $this->withServiceManager($options);
105
106        $this->data = $data;
107        $this->options = $options;
108
109        // initialize the form attribute
110        $this->initForm();
111
112        if ($this->form !== null) {
113            // set the refs of all the forms there
114            self::$forms[$this->form->getName()] = $this->form;
115
116            $this
117                ->getSanitizerValidationFeeder()
118                ->setForm($this->form);
119        }
120
121        // initialize the elements of the form
122        $this->initElements();
123
124        foreach ($options[self::FORM_MODIFIERS] ?? [] as $modifierClass) {
125            $modifier = $this->getContainer()->get($modifierClass);
126
127            if ($modifier instanceof AbstractFormModifier) {
128                $modifier->modify($this->form, $options);
129            }
130        }
131
132        if ($this->form !== null) {
133            $this->form->evaluateInputValues();
134
135            $this->applyAdditionalValidationRules($options);
136            $this->applyAttributeValidators($options);
137
138            if (($options[self::CSRF_PROTECTION_OPTION] ?? false) === true) {
139                $this->initCsrfProtection();
140            }
141
142            // set the values in case of default values
143            if (is_array($this->data) && !empty($this->data)) {
144                $this->form->setValues($this->data);
145            }
146
147            if ($options[self::IS_DISABLED] ?? false) {
148                $this->form->disable();
149            }
150
151            $this->getSanitizerValidationFeeder()->feed();
152
153            // evaluate the form
154            $this->form->evaluate();
155            //validate global form rules
156            $this->validate();
157        }
158
159        if (!empty($_POST)) {
160            $this->postData = $_POST;
161        }
162    }
163
164    /**
165     * Destructor (remove the current form in the static list)
166     *
167     * @author Cédric Alfonsi, <cedric.alfonsi@tudor.lu>
168     */
169    public function __destruct()
170    {
171        if ($this->form !== null) {
172            unset(self::$forms[$this->form->getName()]);
173        }
174    }
175
176    /**
177     * get the form instance
178     *
179     * @author Cédric Alfonsi, <cedric.alfonsi@tudor.lu>
180     */
181    public function getForm(): ?tao_helpers_form_Form
182    {
183        return $this->form;
184    }
185
186    public function addSanitizerValidator(tao_helpers_form_Validator $validator, array $elements): self
187    {
188        $this->getSanitizerValidationFeeder()->addValidator($validator);
189
190        foreach ($elements as $element) {
191            if ($element instanceof tao_helpers_form_FormElement) {
192                $this->getSanitizerValidationFeeder()->addElement($element);
193
194                continue;
195            }
196
197            if (is_string($element)) {
198                $this->getSanitizerValidationFeeder()->addElementByUri($element);
199
200                continue;
201            }
202
203            throw new InvalidArgumentException(
204                sprintf(
205                    'Element provided to sanitizer must be an instance of %s or a string',
206                    tao_helpers_form_FormElement::class
207                )
208            );
209        }
210
211        return $this;
212    }
213
214    /**
215     * Must be overridden and must instantiate the form instance and put it in
216     * form attribute
217     *
218     * @return void
219     * @author Cédric Alfonsi, <cedric.alfonsi@tudor.lu>
220     */
221    abstract protected function initForm();
222
223    /**
224     * Used to create the form elements and bind them to the form instance
225     *
226     * @return void
227     * @author Cédric Alfonsi, <cedric.alfonsi@tudor.lu>
228     */
229    abstract protected function initElements();
230
231    /**
232     * Allow global form validation.
233     * Override this function to do it.
234     *
235     * @author Cédric Alfonsi, <cedric.alfonsi@tudor.lu>
236     */
237    protected function validate(): bool
238    {
239        return true;
240    }
241
242    /**
243     * Return the posted form data.
244     */
245    protected function getPostData(): array
246    {
247        return $this->postData;
248    }
249
250    /**
251     * Initialize the CSRF protection element for the form.
252     * @throws common_Exception
253     */
254    private function initCsrfProtection(): void
255    {
256        $csrfTokenElement = FormFactory::getElement(TokenService::CSRF_TOKEN_HEADER, CsrfToken::class);
257        $this->form->addElement($csrfTokenElement, true);
258    }
259
260    private function applyAdditionalValidationRules(array $options): void
261    {
262        $validationRules = $options[self::ADDITIONAL_VALIDATORS] ?? [];
263
264        if (empty($validationRules)) {
265            return;
266        }
267
268        foreach ($this->getForm()->getElements() as $element) {
269            $validators = $validationRules[$element->getName()] ?? [];
270            $element->addValidators($validators);
271            $this->configureFormValidators($validators, $this->getForm());
272            $this->getForm()->addElement($element);
273        }
274    }
275
276    private function applyAttributeValidators(array $options): void
277    {
278        $attributeValidators = $options[self::ATTRIBUTE_VALIDATORS] ?? [];
279
280        if (empty($attributeValidators)) {
281            return;
282        }
283
284        foreach ($this->getForm()->getElements() as $element) {
285            $attributes = $element->getAttributes();
286            $validators = array_intersect_key($attributeValidators, $attributes);
287
288            if (empty($validators)) {
289                continue;
290            }
291
292            /** @var ValidatorInterface[] $validators */
293            $validators = array_merge(...array_values($validators));
294
295            $this->propagateElement($validators, $element);
296            $this->configureFormValidators($validators, $this->getForm());
297
298            $element->addValidators($validators);
299            $this->getForm()->addElement($element);
300        }
301    }
302
303    /**
304     * @param ValidatorInterface[]|FormElementAware[] $validators
305     */
306    private function propagateElement(iterable &$validators, tao_helpers_form_FormElement $element): void
307    {
308        foreach ($validators as &$validator) {
309            if ($validator instanceof FormElementAware) {
310                $validator = clone $validator;
311                $validator->setElement($element);
312            }
313        }
314    }
315
316    /**
317     * @param ValidatorInterface[]|CrossElementEvaluationAware[] $validators
318     */
319    private function configureFormValidators(iterable $validators, tao_helpers_form_Form $form): void
320    {
321        foreach ($validators as $validator) {
322            if ($validator instanceof CrossElementEvaluationAware) {
323                $validator->acknowledge($form);
324            }
325        }
326    }
327
328    private function getSanitizerValidationFeeder(): SanitizerValidationFeederInterface
329    {
330        if (!isset($this->sanitizerValidationFeeder)) {
331            $this->sanitizerValidationFeeder = $this->getContainer()->get(SanitizerValidationFeeder::class);
332        }
333
334        return $this->sanitizerValidationFeeder;
335    }
336
337    private function withServiceManager(array &$options): void
338    {
339        if (
340            isset($options[self::WITH_SERVICE_MANAGER])
341            && $options[self::WITH_SERVICE_MANAGER] instanceof ServiceManager
342        ) {
343            $this->serviceManager = $options[self::WITH_SERVICE_MANAGER];
344        }
345
346        unset($options[self::WITH_SERVICE_MANAGER]);
347    }
348
349    private function getContainer(): ContainerInterface
350    {
351        $serviceManager = $this->serviceManager ?? ServiceManager::getServiceManager();
352
353        return $serviceManager->getContainer();
354    }
355}