Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 148
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
tao_helpers_data_GenerisAdapterCsv
0.00% covered (danger)
0.00%
0 / 148
0.00% covered (danger)
0.00%
0 / 10
3192
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
72
 load
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 import
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 1
272
 export
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 evaluateValues
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
56
 applyCallbacks
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
56
 attachResource
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 onResourceImported
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 validate
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
 getResult
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
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 *               2013-2018 (update and modification) Open Assessment Technologies SA
23 */
24
25use oat\generis\model\OntologyRdfs;
26use oat\oatbox\service\ServiceManager;
27use oat\tao\helpers\data\ValidationException;
28use oat\tao\model\upload\UploadService;
29use oat\oatbox\filesystem\File;
30
31/**
32 * Adapter for CSV format
33 *
34 * @access public
35 * @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
36 * @deprecated
37 * @package tao
38 */
39class tao_helpers_data_GenerisAdapterCsv extends tao_helpers_data_GenerisAdapter
40{
41    /**
42     * Short description of attribute loadedFile
43     *
44     * @var tao_helpers_data_CsvFile
45     */
46    private $loadedFile = null;
47
48    /**
49     * Contains the callback functions to be applied on created resources.
50     *
51     * @var array
52     */
53    protected $resourceImported = [];
54
55    /**
56     * Instantiates a new tao_helpers_data_GenerisAdapterCSV. The $options array
57     * an associative array formated like this:
58     * array('field_delimiter' => 'a delimiter char', default is ;,
59     * 'field_encloser' => 'a field encloser char, default is "',
60     * 'multi_values_delimiter' => 'a multi values delimiter, default is empty string - do not use multi values',
61     * 'first_row_column_names' => 'boolean value describing if the first row
62     * column names').
63     *
64     * @access public
65     * @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
66     * @param  array $options
67     * @return mixed
68     */
69    public function __construct($options = [])
70    {
71        parent::__construct($options);
72
73        if (!isset($this->options['field_delimiter'])) {
74            $this->options['field_delimiter'] = ';';
75        }
76        if (!isset($this->options['field_encloser'])) {
77            $this->options['field_encloser'] = '"';        //double quote
78        }
79        if (!isset($this->options['multi_values_delimiter'])) {
80            $this->options['multi_values_delimiter'] = '';
81        }
82        if (!isset($this->options['first_row_column_names'])) {
83            $this->options['first_row_column_names'] = true;
84        }
85
86        // Bind resource callbacks.
87        if (isset($this->options['onResourceImported']) && is_array($this->options['onResourceImported'])) {
88            foreach ($this->options['onResourceImported'] as $callback) {
89                $this->onResourceImported($callback);
90                common_Logger::d("onResourceImported callback added to CSV Adapter");
91            }
92        }
93    }
94
95    /**
96     * enable you to load the data in the csvFile to an associative array
97     * the options
98     *
99     * @access public
100     * @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
101     * @param  string $csvFile
102     * @return tao_helpers_data_CsvFile
103     */
104    public function load($csvFile)
105    {
106        $returnValue = null;
107
108        $csv = new tao_helpers_data_CsvFile($this->options);
109        $csv->load($csvFile);
110        $this->loadedFile = $csv;
111        $returnValue = $this->loadedFile;
112
113        return $returnValue;
114    }
115
116    /**
117     * Imports the currently loaded CsvFile into the destination Class.
118     * The map should be set in the options before executing it.
119     *
120     * @access public
121     * @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
122     * @param  string $source
123     * @param  core_kernel_classes_Class $destination
124     * @return common_report_Report
125     * @throws \BadFunctionCallException
126     * @throws \InvalidArgumentException
127     * @throws \oat\oatbox\service\ServiceNotFoundException
128     * @throws \common_Exception
129     */
130    public function import($source, core_kernel_classes_Class $destination = null)
131    {
132        if (!isset($this->options['map'])) {
133            throw new BadFunctionCallException("import map not set");
134        }
135        if (is_null($destination)) {
136            throw new InvalidArgumentException("${destination} must be a valid core_kernel_classes_Class");
137        }
138
139        /** @var UploadService $uploadService */
140        $uploadService = ServiceManager::getServiceManager()->get(UploadService::SERVICE_ID);
141
142        if (!$source instanceof File) {
143            $file = $uploadService->getUploadedFlyFile($source);
144        } else {
145            $file = $source;
146        }
147
148        if (@preg_match('//u', $file->read()) === false) {
149            return new \common_report_Report(
150                \common_report_Report::TYPE_ERROR,
151                __("The imported file is not properly UTF-8 encoded.")
152            );
153        }
154
155        $csvData = $this->load($file);
156
157        $createdResources = 0;
158        $toImport = $csvData->count();
159        $report = new common_report_Report(
160            common_report_Report::TYPE_ERROR,
161            __('Data not imported. All records are invalid.')
162        );
163
164        for ($rowIterator = 0; $rowIterator < $csvData->count(); $rowIterator++) {
165            helpers_TimeOutHelper::setTimeOutLimit(helpers_TimeOutHelper::SHORT);
166            common_Logger::d("CSV - Importing CSV row ${rowIterator}.");
167
168            $resource = null;
169            $csvRow = $csvData->getRow($rowIterator);
170
171            try {
172                // default values
173                $evaluatedData = $this->options['staticMap'];
174
175                // validate csv values
176                foreach ($this->options['map'] as $propUri => $csvColumn) {
177                    $this->validate($destination, $propUri, $csvRow, $csvColumn, $evaluatedData);
178                }
179
180                // evaluate csv values
181                foreach ($this->options['map'] as $propUri => $csvColumn) {
182                    if ($csvColumn != 'csv_null' && $csvColumn != 'csv_select') {
183                        // process value
184                        if (isset($csvRow[$csvColumn]) && !is_null($csvRow[$csvColumn])) {
185                            $property = new core_kernel_classes_Property($propUri);
186                            $evaluatedData[$propUri] = $this->evaluateValues(
187                                $csvColumn,
188                                $property,
189                                $csvRow[$csvColumn]
190                            );
191                        }
192                    }
193                }
194
195                // create resource
196                $resource = $destination->createInstanceWithProperties($evaluatedData);
197
198                // Apply 'resourceImported' callbacks.
199                foreach ($this->resourceImported as $callback) {
200                    $callback($resource);
201                }
202
203                $report->add(
204                    new common_report_Report(
205                        common_report_Report::TYPE_SUCCESS,
206                        __('Imported resource "%s"', $resource->getLabel()),
207                        $resource
208                    )
209                );
210                $createdResources++;
211            } catch (ValidationException $valExc) {
212                $failure = common_report_Report::createFailure(
213                    __('Row %s', $rowIterator + 1) . ' ' . $valExc->getProperty()->getLabel() . ': '
214                        . $valExc->getUserMessage() . ' "' . $valExc->getValue() . '"'
215                );
216                $report->add($failure);
217            }
218
219            helpers_TimeOutHelper::reset();
220        }
221
222        $this->addOption('to_import', $toImport);
223        $this->addOption('imported', $createdResources);
224
225        if ($createdResources == $toImport) {
226            $report->setType(common_report_Report::TYPE_SUCCESS);
227            $report->setMessage(__('Imported %d resources', $toImport));
228        } elseif ($createdResources > 0) {
229            $report->setType(common_report_Report::TYPE_WARNING);
230            $report->setMessage(__('Imported %1$d/%2$d. Some records are invalid.', $createdResources, $toImport));
231        }
232
233        $uploadService->remove($file);
234
235        return $report;
236    }
237
238    /**
239     * Short description of method export
240     *
241     * @access public
242     * @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
243     * @param  core_kernel_classes_Class $source
244     * @return boolean
245     */
246    public function export(core_kernel_classes_Class $source = null)
247    {
248        return false;
249    }
250
251    /**
252     * Evaluates the raw values provided by the csv file into
253     * the actual values to be assigned to  the resource
254     *
255     * @param string $column
256     * @param core_kernel_classes_Property $property
257     * @param mixed $value
258     * @return array
259     */
260    protected function evaluateValues($column, core_kernel_classes_Property $property, $value)
261    {
262        $range = $property->getRange();
263        // assume literal if no range defined
264        $range = is_null($range) ? new core_kernel_classes_Class(OntologyRdfs::RDFS_LITERAL) : $range;
265
266        $evaluatedValue = $this->applyCallbacks($value, $this->options, $property);
267        // ensure it's an array
268        $evaluatedValue = is_array($evaluatedValue) ? $evaluatedValue : [$evaluatedValue];
269
270        if ($range->getUri() != OntologyRdfs::RDFS_LITERAL) {
271            // validate resources
272            foreach ($evaluatedValue as $key => $eVal) {
273                $resource = new core_kernel_classes_Resource($eVal);
274                if ($resource->exists()) {
275                    if (!$resource->hasType($range)) {
276                        // value outside of range
277                        unset($evaluatedValue[$key]);
278                    }
279                } else {
280                    // value not found
281                    unset($evaluatedValue[$key]);
282                }
283            }
284        }
285        return $evaluatedValue;
286    }
287
288    /**
289     * Short description of method applyCallbacks
290     *
291     * @access private
292     * @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
293     * @param  string $value
294     * @param  array $options
295     * @param  core_kernel_classes_Property $targetProperty
296     * @return string
297     */
298    private function applyCallbacks($value, $options, core_kernel_classes_Property $targetProperty)
299    {
300        if (isset($options['callbacks'])) {
301            foreach (['*', $targetProperty->getUri()] as $key) {
302                if (isset($options['callbacks'][$key]) && is_array($options['callbacks'][$key])) {
303                    foreach ($options['callbacks'][$key] as $callback) {
304                        if (is_callable($callback)) {
305                            $value = call_user_func($callback, $value);
306                        }
307                    }
308                }
309            }
310        }
311
312        return $value;
313    }
314
315    /**
316     * Short description of method attachResource
317     *
318     * @access public
319     * @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
320     * @param  core_kernel_classes_Property $targetProperty
321     * @param  core_kernel_classes_Resource $targetResource
322     * @param  string $value
323     * @return mixed
324     */
325    public function attachResource(
326        core_kernel_classes_Property $targetProperty,
327        core_kernel_classes_Resource $targetResource,
328        $value
329    ) {
330        // We have to check if the resource identified by value exists in the Ontology.
331        $resource = new core_kernel_classes_Resource($value);
332        if ($resource->exists()) {
333            // Is the range correct ?
334            $targetPropertyRanges = $targetProperty->getPropertyValuesCollection(
335                new core_kernel_classes_Property(OntologyRdfs::RDFS_RANGE)
336            );
337            $rangeCompliance = false;
338
339            // If $targetPropertyRange->count = 0, we consider that the resouce
340            // may be attached because $rangeCompliance = true.
341            foreach ($targetPropertyRanges->getIterator() as $range) {
342                // Check all classes in target property's range.
343                if ($resource->hasType(new core_kernel_classes_Class($range))) {
344                    $rangeCompliance = true;
345                    break;
346                }
347            }
348
349            if (true == $rangeCompliance) {
350                $targetResource->setPropertyValue($targetProperty, $resource->getUri());
351            }
352        }
353    }
354
355    public function onResourceImported(Closure $closure)
356    {
357        $this->resourceImported[] = $closure;
358    }
359
360    /**
361     * @param core_kernel_classes_Class $destination
362     * @param $propUri
363     * @param $csvRow
364     * @param $csvColumn
365     * @param $evaluatedData
366     * @throws ValidationException
367     * @return bool
368     */
369    protected function validate(core_kernel_classes_Class $destination, $propUri, $csvRow, $csvColumn, $evaluatedData)
370    {
371        /**  @var tao_helpers_form_Validator $validator */
372        $validators = $this->getValidator($propUri);
373        foreach ((array)$validators as $validator) {
374            $validator->setOptions(
375                array_merge(
376                    ['resourceClass' => $destination, 'property' => $propUri],
377                    $validator->getOptions()
378                )
379            );
380            $value = isset($csvRow[$csvColumn]) ? $csvRow[$csvColumn] : null;
381            if ($value === null) {
382                $value = isset($evaluatedData[$propUri]) ? $evaluatedData[$propUri] : null;
383            }
384            if (!$validator->evaluate($value)) {
385                throw new ValidationException(
386                    new core_kernel_classes_Property($propUri),
387                    $value,
388                    $validator->getMessage()
389                );
390            }
391        }
392        return true;
393    }
394
395    /**
396     * @param $createdResources
397     * @return common_report_Report
398     * @throws common_exception_Error
399     */
400    protected function getResult($createdResources)
401    {
402        $message = __('Data imported');
403        $type = common_report_Report::TYPE_SUCCESS;
404
405        if ($this->hasErrors()) {
406            $type = common_report_Report::TYPE_WARNING;
407            $message = __('Data imported. Some records are invalid.');
408        }
409
410        if (!$createdResources) {
411            $type = common_report_Report::TYPE_ERROR;
412            $message = __('Data not imported. All records are invalid.');
413        }
414
415        $report = new common_report_Report($type, $message);
416        foreach ($this->getErrorMessages() as $group) {
417            $report->add($group);
418        }
419
420        return $report;
421    }
422}