391 lines
14 KiB
PHP
391 lines
14 KiB
PHP
<?php
|
|
|
|
/**
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; under version 2
|
|
* of the License (non-upgradable).
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Copyright (c) 2008-2010 (original work) Deutsche Institut für Internationale Pädagogische Forschung (under the project TAO-TRANSFER);
|
|
* 2009-2012 (update and modification) Public Research Centre Henri Tudor (under the project TAO-SUSTAIN & TAO-DEV);
|
|
* 2013-2018 (update and modification) Open Assessment Technologies SA
|
|
*/
|
|
|
|
use oat\generis\model\OntologyRdfs;
|
|
use oat\oatbox\service\ServiceManager;
|
|
use oat\tao\helpers\data\ValidationException;
|
|
use oat\tao\model\upload\UploadService;
|
|
use oat\oatbox\filesystem\File;
|
|
|
|
/**
|
|
* Adapter for CSV format
|
|
*
|
|
* @access public
|
|
* @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
|
|
* @deprecated
|
|
* @package tao
|
|
*/
|
|
class tao_helpers_data_GenerisAdapterCsv extends tao_helpers_data_GenerisAdapter
|
|
{
|
|
|
|
/**
|
|
* Short description of attribute loadedFile
|
|
*
|
|
* @var tao_helpers_data_CsvFile
|
|
*/
|
|
private $loadedFile = null;
|
|
|
|
/**
|
|
* Contains the callback functions to be applied on created resources.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $resourceImported = [];
|
|
|
|
/**
|
|
* Instantiates a new tao_helpers_data_GenerisAdapterCSV. The $options array
|
|
* an associative array formated like this:
|
|
* array('field_delimiter' => 'a delimiter char', default is ;,
|
|
* 'field_encloser' => 'a field encloser char, default is "',
|
|
* 'multi_values_delimiter' => 'a multi values delimiter, default is empty string - do not use multi values',
|
|
* 'first_row_column_names' => 'boolean value describing if the first row
|
|
* column names').
|
|
*
|
|
* @access public
|
|
* @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
|
|
* @param array $options
|
|
* @return mixed
|
|
*/
|
|
public function __construct($options = [])
|
|
{
|
|
parent::__construct($options);
|
|
|
|
if (!isset($this->options['field_delimiter'])) {
|
|
$this->options['field_delimiter'] = ';';
|
|
}
|
|
if (!isset($this->options['field_encloser'])) {
|
|
$this->options['field_encloser'] = '"'; //double quote
|
|
}
|
|
if (!isset($this->options['multi_values_delimiter'])) {
|
|
$this->options['multi_values_delimiter'] = '';
|
|
}
|
|
if (!isset($this->options['first_row_column_names'])) {
|
|
$this->options['first_row_column_names'] = true;
|
|
}
|
|
|
|
// Bind resource callbacks.
|
|
if (isset($this->options['onResourceImported']) && is_array($this->options['onResourceImported'])) {
|
|
foreach ($this->options['onResourceImported'] as $callback) {
|
|
$this->onResourceImported($callback);
|
|
common_Logger::d("onResourceImported callback added to CSV Adapter");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* enable you to load the data in the csvFile to an associative array
|
|
* the options
|
|
*
|
|
* @access public
|
|
* @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
|
|
* @param string $csvFile
|
|
* @return tao_helpers_data_CsvFile
|
|
*/
|
|
public function load($csvFile)
|
|
{
|
|
$returnValue = null;
|
|
|
|
$csv = new tao_helpers_data_CsvFile($this->options);
|
|
$csv->load($csvFile);
|
|
$this->loadedFile = $csv;
|
|
$returnValue = $this->loadedFile;
|
|
|
|
return $returnValue;
|
|
}
|
|
|
|
/**
|
|
* Imports the currently loaded CsvFile into the destination Class.
|
|
* The map should be set in the options before executing it.
|
|
*
|
|
* @access public
|
|
* @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
|
|
* @param string $source
|
|
* @param core_kernel_classes_Class $destination
|
|
* @return common_report_Report
|
|
* @throws \BadFunctionCallException
|
|
* @throws \InvalidArgumentException
|
|
* @throws \oat\oatbox\service\ServiceNotFoundException
|
|
* @throws \common_Exception
|
|
*/
|
|
public function import($source, core_kernel_classes_Class $destination = null)
|
|
{
|
|
if (!isset($this->options['map'])) {
|
|
throw new BadFunctionCallException("import map not set");
|
|
}
|
|
if (is_null($destination)) {
|
|
throw new InvalidArgumentException("${destination} must be a valid core_kernel_classes_Class");
|
|
}
|
|
|
|
/** @var UploadService $uploadService */
|
|
$uploadService = ServiceManager::getServiceManager()->get(UploadService::SERVICE_ID);
|
|
|
|
if (!$source instanceof File) {
|
|
$file = $uploadService->getUploadedFlyFile($source);
|
|
} else {
|
|
$file = $source;
|
|
}
|
|
|
|
if (@preg_match('//u', $file->read()) === false) {
|
|
return new \common_report_Report(\common_report_Report::TYPE_ERROR, __("The imported file is not properly UTF-8 encoded."));
|
|
}
|
|
|
|
$csvData = $this->load($file);
|
|
|
|
$createdResources = 0;
|
|
$toImport = $csvData->count();
|
|
$report = new common_report_Report(common_report_Report::TYPE_ERROR, __('Data not imported. All records are invalid.'));
|
|
|
|
for ($rowIterator = 0; $rowIterator < $csvData->count(); $rowIterator++) {
|
|
helpers_TimeOutHelper::setTimeOutLimit(helpers_TimeOutHelper::SHORT);
|
|
common_Logger::d("CSV - Importing CSV row ${rowIterator}.");
|
|
|
|
$resource = null;
|
|
$csvRow = $csvData->getRow($rowIterator);
|
|
|
|
try {
|
|
// default values
|
|
$evaluatedData = $this->options['staticMap'];
|
|
|
|
// validate csv values
|
|
foreach ($this->options['map'] as $propUri => $csvColumn) {
|
|
$this->validate($destination, $propUri, $csvRow, $csvColumn, $evaluatedData);
|
|
}
|
|
|
|
// evaluate csv values
|
|
foreach ($this->options['map'] as $propUri => $csvColumn) {
|
|
if ($csvColumn != 'csv_null' && $csvColumn != 'csv_select') {
|
|
// process value
|
|
if (isset($csvRow[$csvColumn]) && !is_null($csvRow[$csvColumn])) {
|
|
$property = new core_kernel_classes_Property($propUri);
|
|
$evaluatedData[$propUri] = $this->evaluateValues($csvColumn, $property, $csvRow[$csvColumn]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// create resource
|
|
$resource = $destination->createInstanceWithProperties($evaluatedData);
|
|
|
|
// Apply 'resourceImported' callbacks.
|
|
foreach ($this->resourceImported as $callback) {
|
|
$callback($resource);
|
|
}
|
|
|
|
$report->add(new common_report_Report(common_report_Report::TYPE_SUCCESS, __('Imported resource "%s"', $resource->getLabel()), $resource));
|
|
$createdResources++;
|
|
} catch (ValidationException $valExc) {
|
|
$failure = common_report_Report::createFailure(
|
|
__('Row %s', $rowIterator + 1) . ' ' . $valExc->getProperty()->getLabel() . ': ' . $valExc->getUserMessage() . ' "' . $valExc->getValue() . '"'
|
|
);
|
|
$report->add($failure);
|
|
}
|
|
|
|
helpers_TimeOutHelper::reset();
|
|
}
|
|
|
|
$this->addOption('to_import', $toImport);
|
|
$this->addOption('imported', $createdResources);
|
|
|
|
if ($createdResources == $toImport) {
|
|
$report->setType(common_report_Report::TYPE_SUCCESS);
|
|
$report->setMessage(__('Imported %d resources', $toImport));
|
|
} elseif ($createdResources > 0) {
|
|
$report->setType(common_report_Report::TYPE_WARNING);
|
|
$report->setMessage(__('Imported %1$d/%2$d. Some records are invalid.', $createdResources, $toImport));
|
|
}
|
|
|
|
$uploadService->remove($file);
|
|
|
|
return $report;
|
|
}
|
|
|
|
/**
|
|
* Short description of method export
|
|
*
|
|
* @access public
|
|
* @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
|
|
* @param core_kernel_classes_Class $source
|
|
* @return boolean
|
|
*/
|
|
public function export(core_kernel_classes_Class $source = null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Evaluates the raw values provided by the csv file into
|
|
* the actual values to be assigned to the resource
|
|
*
|
|
* @param string $column
|
|
* @param core_kernel_classes_Property $property
|
|
* @param mixed $value
|
|
* @return array
|
|
*/
|
|
protected function evaluateValues($column, core_kernel_classes_Property $property, $value)
|
|
{
|
|
$range = $property->getRange();
|
|
// assume literal if no range defined
|
|
$range = is_null($range) ? new core_kernel_classes_Class(OntologyRdfs::RDFS_LITERAL) : $range;
|
|
|
|
$evaluatedValue = $this->applyCallbacks($value, $this->options, $property);
|
|
// ensure it's an array
|
|
$evaluatedValue = is_array($evaluatedValue) ? $evaluatedValue : [$evaluatedValue];
|
|
|
|
if ($range->getUri() != OntologyRdfs::RDFS_LITERAL) {
|
|
// validate resources
|
|
foreach ($evaluatedValue as $key => $eVal) {
|
|
$resource = new core_kernel_classes_Resource($eVal);
|
|
if ($resource->exists()) {
|
|
if (!$resource->hasType($range)) {
|
|
// value outside of range
|
|
unset($evaluatedValue[$key]);
|
|
}
|
|
} else {
|
|
// value not found
|
|
unset($evaluatedValue[$key]);
|
|
}
|
|
}
|
|
}
|
|
return $evaluatedValue;
|
|
}
|
|
|
|
/**
|
|
* Short description of method applyCallbacks
|
|
*
|
|
* @access private
|
|
* @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
|
|
* @param string $value
|
|
* @param array $options
|
|
* @param core_kernel_classes_Property $targetProperty
|
|
* @return string
|
|
*/
|
|
private function applyCallbacks($value, $options, core_kernel_classes_Property $targetProperty)
|
|
{
|
|
if (isset($options['callbacks'])) {
|
|
foreach (['*', $targetProperty->getUri()] as $key) {
|
|
if (isset($options['callbacks'][$key]) && is_array($options['callbacks'][$key])) {
|
|
foreach ($options['callbacks'][$key] as $callback) {
|
|
if (is_callable($callback)) {
|
|
$value = call_user_func($callback, $value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Short description of method attachResource
|
|
*
|
|
* @access public
|
|
* @author Jerome Bogaerts, <jerome.bogaerts@tudor.lu>
|
|
* @param core_kernel_classes_Property $targetProperty
|
|
* @param core_kernel_classes_Resource $targetResource
|
|
* @param string $value
|
|
* @return mixed
|
|
*/
|
|
public function attachResource(core_kernel_classes_Property $targetProperty, core_kernel_classes_Resource $targetResource, $value)
|
|
{
|
|
// We have to check if the resource identified by value exists in the Ontology.
|
|
$resource = new core_kernel_classes_Resource($value);
|
|
if ($resource->exists()) {
|
|
// Is the range correct ?
|
|
$targetPropertyRanges = $targetProperty->getPropertyValuesCollection(new core_kernel_classes_Property(OntologyRdfs::RDFS_RANGE));
|
|
$rangeCompliance = false;
|
|
|
|
// If $targetPropertyRange->count = 0, we consider that the resouce
|
|
// may be attached because $rangeCompliance = true.
|
|
foreach ($targetPropertyRanges->getIterator() as $range) {
|
|
// Check all classes in target property's range.
|
|
if ($resource->hasType(new core_kernel_classes_Class($range))) {
|
|
$rangeCompliance = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (true == $rangeCompliance) {
|
|
$targetResource->setPropertyValue($targetProperty, $resource->getUri());
|
|
}
|
|
}
|
|
}
|
|
|
|
public function onResourceImported(Closure $closure)
|
|
{
|
|
$this->resourceImported[] = $closure;
|
|
}
|
|
|
|
/**
|
|
* @param core_kernel_classes_Class $destination
|
|
* @param $propUri
|
|
* @param $csvRow
|
|
* @param $csvColumn
|
|
* @param $evaluatedData
|
|
* @throws ValidationException
|
|
* @return bool
|
|
*/
|
|
protected function validate(core_kernel_classes_Class $destination, $propUri, $csvRow, $csvColumn, $evaluatedData)
|
|
{
|
|
/** @var tao_helpers_form_Validator $validator */
|
|
$validators = $this->getValidator($propUri);
|
|
foreach ((array)$validators as $validator) {
|
|
$validator->setOptions(array_merge(['resourceClass' => $destination, 'property' => $propUri], $validator->getOptions()));
|
|
$value = isset($csvRow[$csvColumn]) ? $csvRow[$csvColumn] : null;
|
|
if ($value === null) {
|
|
$value = isset($evaluatedData[$propUri]) ? $evaluatedData[$propUri] : null;
|
|
}
|
|
if (!$validator->evaluate($value)) {
|
|
throw new ValidationException(new core_kernel_classes_Property($propUri), $value, $validator->getMessage());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param $createdResources
|
|
* @return common_report_Report
|
|
* @throws common_exception_Error
|
|
*/
|
|
protected function getResult($createdResources)
|
|
{
|
|
$message = __('Data imported');
|
|
$type = common_report_Report::TYPE_SUCCESS;
|
|
|
|
if ($this->hasErrors()) {
|
|
$type = common_report_Report::TYPE_WARNING;
|
|
$message = __('Data imported. Some records are invalid.');
|
|
}
|
|
|
|
if (!$createdResources) {
|
|
$type = common_report_Report::TYPE_ERROR;
|
|
$message = __('Data not imported. All records are invalid.');
|
|
}
|
|
|
|
$report = new common_report_Report($type, $message);
|
|
foreach ($this->getErrorMessages() as $group) {
|
|
$report->add($group);
|
|
}
|
|
|
|
return $report;
|
|
}
|
|
}
|