432 lines
13 KiB
PHP
432 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 1999-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (LGPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
|
|
*
|
|
* @author Jan Schneider <jan@horde.org>
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @category Horde
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL
|
|
* @package Data
|
|
*/
|
|
|
|
/**
|
|
* Abstract class that Data drivers extend.
|
|
*
|
|
* @author Jan Schneider <jan@horde.org>
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @category Horde
|
|
* @copyright 1999-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL
|
|
* @package Data
|
|
*/
|
|
abstract class Horde_Data_Base
|
|
{
|
|
/**
|
|
* Storage object.
|
|
*
|
|
* @var Horde_Data_Storage
|
|
*/
|
|
public $storage;
|
|
|
|
/**
|
|
* Browser object.
|
|
*
|
|
* @var Horde_Browser
|
|
*/
|
|
protected $_browser;
|
|
|
|
/**
|
|
* HTTP client object.
|
|
*
|
|
* @var Horde_Http_Client
|
|
*/
|
|
protected $_http;
|
|
|
|
/**
|
|
* Cleanup callback function.
|
|
*
|
|
* @var callback
|
|
*/
|
|
protected $_cleanupCallback;
|
|
|
|
/**
|
|
* MIME content type.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_contentType = 'text/plain';
|
|
|
|
/**
|
|
* File extension.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_extension = '';
|
|
|
|
/**
|
|
* Variables object.
|
|
*
|
|
* @var Horde_Variables
|
|
*/
|
|
protected $_vars;
|
|
|
|
/**
|
|
* A list of warnings raised during the last operation.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_warnings = array();
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param Horde_Data_Storage A storage object.
|
|
* @param array $params Optional parameters:
|
|
* - browser: (Horde_Browser) A browser object.
|
|
* - http: (Horde_Http_Client) A HTTP client object.
|
|
* - cleanup: (callback) A callback to call at cleanup time.
|
|
* - vars: (Horde_Variables) Form data.
|
|
*/
|
|
public function __construct(Horde_Data_Storage $storage,
|
|
array $params = array())
|
|
{
|
|
$this->storage = $storage;
|
|
if (isset($params['browser'])) {
|
|
$this->_browser = $params['browser'];
|
|
}
|
|
if (isset($params['http'])) {
|
|
$this->_http = $params['http'];
|
|
}
|
|
if (isset($params['cleanup']) && is_callable($params['cleanup'])) {
|
|
$this->_cleanupCallback = $params['cleanup'];
|
|
}
|
|
$this->_vars = isset($params['vars'])
|
|
? $params['vars']
|
|
: Horde_Variables::getDefaultVariables();
|
|
}
|
|
|
|
/**
|
|
* Stub to import passed data.
|
|
*/
|
|
public function importData($text)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Stub to return exported data.
|
|
*/
|
|
abstract public function exportData($data, $method = 'REQUEST');
|
|
|
|
/**
|
|
* Imports a file.
|
|
*/
|
|
public function importFile($filename, $header = false)
|
|
{
|
|
$data = file_get_contents($filename);
|
|
return $this->importData($data, $header);
|
|
}
|
|
|
|
/**
|
|
* Imports a URL.
|
|
*
|
|
* @since 2.1.0
|
|
*/
|
|
public function importUrl($url, $header = false)
|
|
{
|
|
if (!isset($this->_http)) {
|
|
throw new LogicException('Missing http parameter.');
|
|
}
|
|
|
|
try {
|
|
$response = $this->_http->get($url);
|
|
if ($response->code != 200) {
|
|
throw new Horde_Data_Exception(sprintf(
|
|
Horde_Data_Translation::t("URL %s not found"),
|
|
$url
|
|
));
|
|
}
|
|
$data = $response->getBody();
|
|
} catch (Horde_Http_Exception $e) {
|
|
throw new Horde_Data_Exception($e);
|
|
}
|
|
|
|
return $this->importData($data, $header);
|
|
}
|
|
|
|
/**
|
|
* Stub to export data to a file.
|
|
*/
|
|
abstract public function exportFile($filename, $data);
|
|
|
|
/**
|
|
* Tries to determine the expected newline character based on the
|
|
* platform information passed by the browser's agent header.
|
|
*
|
|
* @return string The guessed expected newline characters, either \n, \r
|
|
* or \r\n.
|
|
*/
|
|
public function getNewline()
|
|
{
|
|
if (!isset($this->_browser)) {
|
|
throw new LogicException('Missing browser parameter.');
|
|
}
|
|
|
|
switch ($this->_browser->getPlatform()) {
|
|
case 'win':
|
|
return "\r\n";
|
|
|
|
case 'mac':
|
|
return "\r";
|
|
|
|
case 'unix':
|
|
default:
|
|
return "\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the full filename including the basename and extension.
|
|
*
|
|
* @param string $basename Basename for the file.
|
|
*
|
|
* @return string The file name.
|
|
*/
|
|
public function getFilename($basename)
|
|
{
|
|
return $basename . '.' . $this->_extension;
|
|
}
|
|
|
|
/**
|
|
* Returns the content type.
|
|
*
|
|
* @return string The content type.
|
|
*/
|
|
public function getContentType()
|
|
{
|
|
return $this->_contentType;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of warnings that have been raised during the last
|
|
* operation.
|
|
*
|
|
* @return array A (possibly empty) list of warnings.
|
|
*/
|
|
public function warnings()
|
|
{
|
|
return $this->_warnings;
|
|
}
|
|
|
|
/**
|
|
* Maps a date/time string to an associative array.
|
|
*
|
|
* @param string $date The date.
|
|
* @param string $type One of 'date', 'time' or 'datetime'.
|
|
* @param array $params Two-dimensional array with additional information
|
|
* about the formatting. Possible keys are:
|
|
* - delimiter: The character that seperates the different date/time
|
|
* parts.
|
|
* - format: If 'ampm' and $date contains a time we assume that it is in
|
|
* AM/PM format.
|
|
* - order: If $type is 'datetime' the order of the day and time parts:
|
|
* -1 (timestamp), 0 (day/time), 1 (time/day).
|
|
* @param integer $key The key to use for $params.
|
|
*
|
|
* @return string The date or time in ISO format.
|
|
*/
|
|
protected function _mapDate($date, $type, $params, $key)
|
|
{
|
|
switch ($type) {
|
|
case 'date':
|
|
case 'monthday':
|
|
case 'monthdayyear':
|
|
$dates = explode($params['delimiter'][$key], $date);
|
|
if (count($dates) != 3) {
|
|
return $date;
|
|
}
|
|
$index = array_flip(explode('/', $params['format'][$key]));
|
|
return $dates[$index['year']] . '-' . $dates[$index['month']] . '-' . $dates[$index['mday']];
|
|
|
|
case 'time':
|
|
$dates = explode($params['delimiter'][$key], $date);
|
|
if (count($dates) < 2 || count($dates) > 3) {
|
|
return $date;
|
|
}
|
|
if ($params['format'][$key] == 'ampm') {
|
|
if (strpos(Horde_String::lower($dates[count($dates)-1]), 'pm') !== false) {
|
|
if ($dates[0] !== '12') {
|
|
$dates[0] += 12;
|
|
}
|
|
} elseif ($dates[0] == '12') {
|
|
$dates[0] = '0';
|
|
}
|
|
$dates[count($dates) - 1] = sprintf('%02d', $dates[count($dates)-1]);
|
|
}
|
|
return $dates[0] . ':' . $dates[1] . (count($dates) == 3 ? (':' . $dates[2]) : ':00');
|
|
|
|
case 'datetime':
|
|
switch ($params['order'][$key]) {
|
|
case -1:
|
|
return (string)(int)$date == $date
|
|
? date('Y-m-d H:i:s', $date)
|
|
: $date;
|
|
case 0:
|
|
list($day, $time) = explode(' ', $date, 2);
|
|
break;
|
|
case 1:
|
|
list($time, $day) = explode(' ', $date, 2);
|
|
break;
|
|
}
|
|
$date = $this->_mapDate($day, 'date', array(
|
|
'delimiter' => $params['day_delimiter'],
|
|
'format' => $params['day_format']
|
|
), $key);
|
|
$time = $this->_mapDate($time, 'time', array(
|
|
'delimiter' => $params['time_delimiter'],
|
|
'format' => $params['time_format']
|
|
), $key);
|
|
|
|
return $date . ' ' . $time;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes all necessary actions for the given import step, parameters and
|
|
* form values and returns the next necessary step.
|
|
*
|
|
* @param integer $action The current step. One of the IMPORT_* constants.
|
|
* @param array $param An associative array containing needed
|
|
* parameters for the current step.
|
|
*
|
|
* @return mixed Either the next step as an integer constant or imported
|
|
* data set after the final step.
|
|
* @throws Horde_Data_Exception
|
|
*/
|
|
public function nextStep($action, array $param = array())
|
|
{
|
|
/* First step. */
|
|
if (is_null($action)) {
|
|
return Horde_Data::IMPORT_FILE;
|
|
}
|
|
|
|
switch ($action) {
|
|
case Horde_Data::IMPORT_FILE:
|
|
if (!isset($this->_browser)) {
|
|
throw new LogicException('Missing browser parameter.');
|
|
}
|
|
/* Sanitize uploaded file. */
|
|
try {
|
|
$this->_browser->wasFileUploaded('import_file', $param['file_types'][$this->_vars->import_format]);
|
|
} catch (Horde_Exception $e) {
|
|
throw new Horde_Data_Exception($e);
|
|
}
|
|
if ($_FILES['import_file']['size'] <= 0) {
|
|
throw new Horde_Data_Exception(Horde_Data_Translation::t("The file contained no data."));
|
|
}
|
|
$this->storage->set('format', $this->_vars->import_format);
|
|
break;
|
|
|
|
case Horde_Data::IMPORT_MAPPED:
|
|
if (!$this->_vars->dataKeys || !$this->_vars->appKeys) {
|
|
throw new Horde_Data_Exception(Horde_Data_Translation::t("You didn\'t map any fields from the imported file to the corresponding fields."));
|
|
}
|
|
$dataKeys = explode("\t", $this->_vars->dataKeys);
|
|
$appKeys = explode("\t", $this->_vars->appKeys);
|
|
$dates = $map = array();
|
|
|
|
if (!$import_data = $this->storage->get('data')) {
|
|
$import_data = array();
|
|
}
|
|
|
|
foreach ($appKeys as $key => $app) {
|
|
$map[$dataKeys[$key]] = $app;
|
|
if (isset($param['time_fields']) &&
|
|
isset($param['time_fields'][$app])) {
|
|
$dates[$dataKeys[$key]]['type'] = $param['time_fields'][$app];
|
|
$dates[$dataKeys[$key]]['values'] = array();
|
|
$i = 0;
|
|
/* Build an example array of up to 10 date/time fields. */
|
|
while ($i < count($import_data) &&
|
|
count($dates[$dataKeys[$key]]['values']) < 10) {
|
|
if (!empty($import_data[$i][$dataKeys[$key]])) {
|
|
$dates[$dataKeys[$key]]['values'][] = $import_data[$i][$dataKeys[$key]];
|
|
}
|
|
++$i;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->storage->set('map', $map);
|
|
if (count($dates) > 0) {
|
|
foreach ($dates as $key => $data) {
|
|
if (count($data['values'])) {
|
|
$this->storage->set('dates', $dates);
|
|
return Horde_Data::IMPORT_DATETIME;
|
|
}
|
|
}
|
|
}
|
|
return $this->nextStep(Horde_Data::IMPORT_DATA, $param);
|
|
|
|
case Horde_Data::IMPORT_DATETIME:
|
|
case Horde_Data::IMPORT_DATA:
|
|
if ($action == Horde_Data::IMPORT_DATETIME) {
|
|
$params = array(
|
|
'delimiter' => $this->_vars->delimiter,
|
|
'format' => $this->_vars->format,
|
|
'order' => $this->_vars->order,
|
|
'day_delimiter' => $this->_vars->day_delimiter,
|
|
'day_format' => $this->_vars->day_format,
|
|
'time_delimiter' => $this->_vars->time_delimiter,
|
|
'time_format' => $this->_vars->time_format
|
|
);
|
|
}
|
|
|
|
if (!$this->storage->exists('data')) {
|
|
throw new Horde_Data_Exception(Horde_Data_Translation::t("The uploaded data was lost since the previous step."));
|
|
}
|
|
|
|
/* Build the result data set as an associative array. */
|
|
$data = array();
|
|
$data_map = $this->storage->get('map');
|
|
|
|
foreach ($this->storage->get('data') as $row) {
|
|
$data_row = array();
|
|
foreach ($row as $key => $val) {
|
|
if (isset($data_map[$key])) {
|
|
$mapped_key = $data_map[$key];
|
|
if ($action == Horde_Data::IMPORT_DATETIME &&
|
|
!empty($val) &&
|
|
isset($param['time_fields']) &&
|
|
isset($param['time_fields'][$mapped_key])) {
|
|
$val = $this->_mapDate($val, $param['time_fields'][$mapped_key], $params, $key);
|
|
}
|
|
$data_row[$mapped_key] = $val;
|
|
}
|
|
}
|
|
$data[] = $data_row;
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes any uploaded and moved files.
|
|
*
|
|
* @return mixed If callback called, the return value of this call.
|
|
* This should be the value of the first import step.
|
|
*/
|
|
public function cleanup()
|
|
{
|
|
$this->storage->clear();
|
|
|
|
if ($this->_cleanupCallback) {
|
|
return call_user_func($this->_cleanupCallback);
|
|
}
|
|
}
|
|
|
|
}
|