Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 147
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
tao_actions_form_RestForm
0.00% covered (danger)
0.00%
0 / 147
0.00% covered (danger)
0.00%
0 / 15
4160
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 initFormProperties
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
182
 getData
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 bind
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 validate
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
272
 save
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 getClassProperties
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getTopClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWidgetProperty
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPropertyValidators
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getRangeData
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 getFieldValue
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
72
 doesExist
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isNew
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 prepareValuesToSave
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
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) 2017 (original work) Open Assessment Technologies SA;
19 *
20 */
21
22use oat\generis\model\OntologyAwareTrait;
23use oat\tao\helpers\form\ValidationRuleRegistry;
24use oat\oatbox\validator\ValidatorInterface;
25use oat\tao\model\TaoOntology;
26
27/**
28 * Class tao_actions_form_RestForm
29 *
30 * Form object to handle rdf resource
31 * It manages data to create and edit a resource into a given class
32 */
33class tao_actions_form_RestForm
34{
35    use OntologyAwareTrait;
36
37    public const PROPERTIES = 'properties';
38    public const RANGES = 'ranges';
39
40    /** @var core_kernel_classes_Class|null The class where the resource is */
41    protected $class = null;
42
43    /** @var core_kernel_classes_Resource|null The resource to manage */
44    protected $resource = null;
45
46    /** @var array Resource properties formatted for the form */
47    protected $formProperties = [];
48
49    /** @var array An array to store field ranges */
50    protected $ranges = [];
51
52    /**
53     * tao_actions_form_RestForm constructor.
54     *
55     * Create a form for a given $instance
56     * If $instance is a resource, it's an edition. The class is the resource class
57     * Otherwise the $instance is a class where to create a new resource
58     * Init the form itself
59     *
60     * @param $instance
61     * @throws common_exception_NotFound
62     * @throws common_Exception
63     */
64    public function __construct($instance)
65    {
66        if ($instance instanceof core_kernel_classes_Class) {
67            $this->class = $instance;
68        } elseif ($instance instanceof core_kernel_classes_Resource) {
69            if (!$instance->exists()) {
70                throw new common_exception_NotFound('Resource requested does not exist');
71            }
72            $this->resource = $instance;
73            foreach ($instance->getTypes() as $type) {
74                $this->class = $type;
75                break;
76            }
77        }
78
79        if (is_null($this->class)) {
80            throw new common_Exception(__METHOD__ . ' requires a valid class');
81        }
82
83        $this->initFormProperties($this->getClassProperties());
84    }
85
86    /**
87     * Initialize form properties.
88     * - Only if resource property has a widget property
89     * - Fetch property validator
90     * - If necessary fetch property ranges
91     * - In case of edition, retrieve property values
92     * - Sort properties with GUI order property
93     *
94     * @param array $properties
95     */
96    public function initFormProperties(array $properties)
97    {
98        /** @var core_kernel_classes_Property $property */
99        foreach ($properties as $property) {
100            $property->feed();
101            $widget = $this->getWidgetProperty($property);
102
103            if (is_null($widget) || $widget instanceof core_kernel_classes_Literal) {
104                continue;
105            }
106
107            $propertyData = [
108                'uri' => $property->getUri(),
109                'label' => $property->getLabel(),
110                'widget' => $widget->getUri(),
111            ];
112
113            // Validators
114            $validators = $this->getPropertyValidators($property);
115            if (!empty($validators)) {
116                $propertyData['validators'] = $validators;
117            }
118
119            // Range values
120            /** @var core_kernel_classes_Class $range */
121            $range = $property->getRange();
122            if (!is_null($range) && $range->getUri() != 'http://www.w3.org/2000/01/rdf-schema#Literal') {
123                $propertyData['range'] = $range->getUri();
124                $this->ranges[$range->getUri()] = $this->getRangeData($range);
125            }
126
127            // Existing values
128            if (
129                $this->doesExist()
130                && !is_null($value = $this->getFieldValue($property, $propertyData['range'] ?? null))
131            ) {
132                $propertyData['value'] = $value;
133            }
134
135            // Field position in the form
136            $guiPropertyOrder = $property->getOnePropertyValue($this->getProperty(TaoOntology::PROPERTY_GUI_ORDER));
137            if (!is_null($guiPropertyOrder)) {
138                $position = intval((string)$guiPropertyOrder);
139                $propertyData['position'] = $position;
140                $i = 0;
141                while (
142                    $i < count($this->formProperties)
143                    && (
144                        isset($this->formProperties[$i]['position'])
145                        && $position >= $this->formProperties[$i]['position']
146                    )
147                ) {
148                    $i++;
149                }
150                array_splice($this->formProperties, $i, 0, [$propertyData]);
151            } else {
152                $this->formProperties[] = $propertyData;
153            }
154        }
155    }
156
157    /**
158     * Get the form data, properties and ranges formatted to escape uri
159     *
160     * @return array
161     */
162    public function getData()
163    {
164        return [
165            self::PROPERTIES => $this->formProperties,
166            self::RANGES => $this->ranges,
167        ];
168    }
169
170    /**
171     * Populate the form properties with given $parameters.
172     *
173     * @param array $parameters
174     * @return $this
175     */
176    public function bind(array $parameters = [])
177    {
178        foreach ($this->formProperties as $key => $property) {
179            if (isset($parameters[$property['uri']])) {
180                $value = $parameters[$property['uri']];
181            } else {
182                $value = '';
183            }
184            $this->formProperties[$key]['formValue'] = $value;
185        }
186        return $this;
187    }
188
189    /**
190     * Validate the form against the property validators.
191     * In case of range, check if value belong to associated ranges list
192     * Return failure report if one or more field is invalid
193     *
194     * @return common_report_Report
195     * @throws common_Exception In case of runtime error
196     */
197    public function validate()
198    {
199        $report = common_report_Report::createInfo();
200
201        foreach ($this->formProperties as $property) {
202            try {
203                $value = $property['formValue'];
204
205                if (isset($property['validators'])) {
206                    foreach ($property['validators'] as $validatorName) {
207                        $validatorClass = 'tao_helpers_form_validators_' . $validatorName;
208                        if (!class_exists($validatorClass)) {
209                            throw new common_Exception('Validator is not correctly set (unknown)');
210                        }
211                        /** @var ValidatorInterface $validator */
212                        $validator = new $validatorClass();
213                        if (!$validator->evaluate($value)) {
214                            throw new common_exception_ValidationFailed(
215                                $property['uri'],
216                                $validator->getMessage()
217                            );
218                        }
219                    }
220                }
221
222                if (isset($property['range'])) {
223                    if (!isset($this->ranges[$property['range']])) {
224                        throw new common_Exception($property['label'] . ' : Range is unknown');
225                    }
226                    $rangeValidated = false;
227                    foreach ($this->ranges[$property['range']] as $rangeData) {
228                        if (is_array($value)) {
229                            foreach ($value as $k => $v) {
230                                if ($rangeData['uri'] == $v) {
231                                    unset($value[$k]);
232                                }
233                            }
234                            if (empty($value)) {
235                                $rangeValidated = true;
236                                break;
237                            }
238                        } else {
239                            if ($rangeData['uri'] == $value) {
240                                $rangeValidated = true;
241                                break;
242                            }
243                        }
244                    }
245                    if (!$rangeValidated) {
246                        throw new common_exception_ValidationFailed(
247                            $property['uri'],
248                            'Range "' . $value . '" for field "' . $property['label'] . '" is not recognized.'
249                        );
250                    }
251                }
252            } catch (common_exception_ValidationFailed $e) {
253                $subReport = common_report_Report::createFailure($e->getMessage());
254                $subReport->setData($property['uri']);
255                $report->add($subReport);
256            }
257        }
258
259        return $report;
260    }
261
262    /**
263     * Save the form resource.
264     * If $resource is set, use GenerisFormDataBinder to update resource with form properties
265     * If only $class is set, create a new resource under it
266     *
267     * @return core_kernel_classes_Resource|null
268     * @throws common_Exception
269     */
270    public function save()
271    {
272        $values = $this->prepareValuesToSave();
273
274        if ($this->isNew()) {
275            if (!$resource = $this->class->createInstanceWithProperties($values)) {
276                throw new common_Exception(__('Unable to save resource.'));
277            }
278            $this->resource = $resource;
279        } else {
280            $binder = new tao_models_classes_dataBinding_GenerisFormDataBinder($this->resource);
281            $binder->bind($values);
282        }
283
284        return $this->resource;
285    }
286
287    /**
288     * Get the class properties from GenersFormFactory
289     *
290     * @return array
291     */
292    protected function getClassProperties()
293    {
294        return array_merge(
295            tao_helpers_form_GenerisFormFactory::getDefaultProperties(),
296            tao_helpers_form_GenerisFormFactory::getClassProperties($this->class, $this->getTopClass())
297        );
298    }
299
300    /**
301     * Get the class associated to current form
302     *
303     * @return core_kernel_classes_Class
304     */
305    protected function getTopClass()
306    {
307        return $this->getClass(TaoOntology::CLASS_URI_OBJECT);
308    }
309
310    /**
311     * Get the the property widget property
312     *
313     * @param core_kernel_classes_Property $property
314     * @return core_kernel_classes_Property
315     */
316    protected function getWidgetProperty(core_kernel_classes_Property $property)
317    {
318        return $property->getWidget();
319    }
320
321    /**
322     * Get property validators
323     *
324     * @param core_kernel_classes_Property $property
325     * @return array
326     */
327    protected function getPropertyValidators(core_kernel_classes_Property $property)
328    {
329        $validators = [];
330        /** @var ValidatorInterface $validator */
331        foreach (ValidationRuleRegistry::getRegistry()->getValidators($property) as $validator) {
332            $validators[] = $validator->getName();
333        }
334        return $validators;
335    }
336
337    /**
338     * Get the list of resource associated to the given $range
339     *
340     * @param core_kernel_classes_Class $range
341     * @return array
342     */
343    protected function getRangeData(core_kernel_classes_Class $range)
344    {
345        if (is_null($range) || $range instanceof core_kernel_classes_Literal) {
346            return [];
347        }
348
349        $options = [];
350
351        /** @var core_kernel_classes_Resource $rangeInstance */
352        foreach ($range->getInstances(true) as $rangeInstance) {
353            $options[] = [
354                'uri' => $rangeInstance->getUri(),
355                'label' => $rangeInstance->getLabel(),
356            ];
357        }
358
359        if (!empty($options)) {
360            ksort($options);
361            return $options;
362        }
363
364        return [];
365    }
366
367    /**
368     * Get the value of the given property.
369     * If $range is not null, return the value $uri
370     *
371     * @param core_kernel_classes_Property $property
372     * @param null $range
373     * @return array|mixed|null
374     */
375    protected function getFieldValue(core_kernel_classes_Property $property, $range = null)
376    {
377        $propertyValues = [];
378
379        /** @var core_kernel_classes_Resource $resource */
380        $values = $this->resource->getPropertyValuesCollection($property);
381        foreach ($values->getIterator() as $value) {
382            if (is_null($value)) {
383                continue;
384            }
385
386            if ($value instanceof core_kernel_classes_Resource) {
387                if (!is_null($range)) {
388                    $propertyValues[] = $value->getUri();
389                } else {
390                    $propertyValues[] = $value->getLabel();
391                }
392            } elseif ($value instanceof core_kernel_classes_Literal) {
393                $propertyValues[] = (string)$value;
394            }
395        }
396
397        if (!empty($propertyValues)) {
398            if (count($propertyValues) == 1) {
399                return $propertyValues[0];
400            } else {
401                return $propertyValues;
402            }
403        }
404
405        return null;
406    }
407
408    /**
409     * Check if current form exists
410     *
411     * @return bool
412     */
413    protected function doesExist()
414    {
415        return !is_null($this->resource);
416    }
417
418    /**
419     * Check if current form is does not exist
420     *
421     * @return bool
422     */
423    protected function isNew()
424    {
425        return is_null($this->resource);
426    }
427
428
429    /**
430     * Format the form properties to save them
431     *
432     * @return array
433     */
434    protected function prepareValuesToSave()
435    {
436        $values = [];
437        foreach ($this->formProperties as $property) {
438            $values[$property['uri']] = $property['formValue'];
439        }
440        return $values;
441    }
442}