264 lines
9.3 KiB
PHP
264 lines
9.3 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
|
|
*/
|
|
|
|
/**
|
|
* Horde_Data implementation for tab-separated data (TSV).
|
|
*
|
|
* @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
|
|
*/
|
|
class Horde_Data_Tsv extends Horde_Data_Base
|
|
{
|
|
/**
|
|
* File extension.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_extension = 'tsv';
|
|
|
|
/**
|
|
* MIME content type.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_contentType = 'text/tab-separated-values';
|
|
|
|
/**
|
|
* Convert data file contents to list of data records.
|
|
*
|
|
* @param string $contents Data file contents.
|
|
* @param boolean $header True if a header row is present.
|
|
* @param string $delimiter Field delimiter.
|
|
*
|
|
* @return array List of data records.
|
|
*/
|
|
public function importData($contents, $header = false, $delimiter = "\t")
|
|
{
|
|
if ($this->storage->get('format') == 'pine') {
|
|
$contents = preg_replace('/\n +/', '', $contents);
|
|
}
|
|
|
|
$contents = explode("\n", $contents);
|
|
$data = array();
|
|
if ($header) {
|
|
$head = explode($delimiter, array_shift($contents));
|
|
}
|
|
|
|
foreach ($contents as $line) {
|
|
if (trim($line) == '') {
|
|
continue;
|
|
}
|
|
$line = explode($delimiter, $line);
|
|
if (!isset($head)) {
|
|
$data[] = $line;
|
|
} else {
|
|
$newline = array();
|
|
for ($i = 0; $i < count($head); $i++) {
|
|
$newline[$head[$i]] = empty($line[$i]) ? '' : $line[$i];
|
|
}
|
|
$data[] = $newline;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Builds a TSV file from a given data structure and returns it as a
|
|
* string.
|
|
*
|
|
* @param array $data A two-dimensional array containing the data set.
|
|
* @param boolean $header If true, the rows of $data are associative
|
|
* arrays with field names as their keys.
|
|
*
|
|
* @return string The TSV data.
|
|
*/
|
|
public function exportData($data, $header = false)
|
|
{
|
|
if (!is_array($data) || count($data) == 0) {
|
|
return '';
|
|
}
|
|
$export = '';
|
|
$head = array_keys(current($data));
|
|
if ($header) {
|
|
$export = implode("\t", $head) . "\n";
|
|
}
|
|
foreach ($data as $row) {
|
|
foreach ($head as $key) {
|
|
$cell = $row[$key];
|
|
if (!empty($cell) || $cell === 0) {
|
|
$export .= $cell;
|
|
}
|
|
$export .= "\t";
|
|
}
|
|
$export = substr($export, 0, -1) . "\n";
|
|
}
|
|
|
|
return $export;
|
|
}
|
|
|
|
/**
|
|
* Builds a TSV file from a given data structure and triggers its download.
|
|
* It DOES NOT exit the current script but only outputs the correct headers
|
|
* and data.
|
|
*
|
|
* @param string $filename The name of the file to be downloaded.
|
|
* @param array $data A two-dimensional array containing the data
|
|
* set.
|
|
* @param boolean $header If true, the rows of $data are associative
|
|
* arrays with field names as their keys.
|
|
*/
|
|
public function exportFile($filename, $data, $header = false)
|
|
{
|
|
if (!isset($this->_browser)) {
|
|
throw new LogicException('Missing browser parameter.');
|
|
}
|
|
|
|
$export = $this->exportData($data, $header);
|
|
$this->_browser->downloadHeaders($filename, 'text/tab-separated-values', false, strlen($export));
|
|
echo $export;
|
|
}
|
|
|
|
/**
|
|
* 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())
|
|
{
|
|
switch ($action) {
|
|
case Horde_Data::IMPORT_FILE:
|
|
parent::nextStep($action, $param);
|
|
|
|
$format = $this->storage->get('format');
|
|
if (in_array($format, array('mulberry', 'pine'))) {
|
|
$filedata = $this->importFile($_FILES['import_file']['tmp_name']);
|
|
|
|
switch ($format) {
|
|
case 'mulberry':
|
|
$appKeys = array('alias', 'name', 'email', 'company', 'workAddress', 'workPhone', 'homePhone', 'fax', 'notes');
|
|
$dataKeys = array(0, 1, 2, 3, 4, 5, 6, 7, 9);
|
|
break;
|
|
|
|
case 'pine':
|
|
$appKeys = array('alias', 'name', 'email', 'notes');
|
|
$dataKeys = array(0, 1, 2, 4);
|
|
break;
|
|
}
|
|
|
|
foreach ($appKeys as $key => $app) {
|
|
$map[$dataKeys[$key]] = $app;
|
|
}
|
|
|
|
$data = array();
|
|
foreach ($filedata as $row) {
|
|
$hash = array();
|
|
|
|
switch ($format) {
|
|
case 'mulberry':
|
|
if (preg_match("/^Grp:/", $row[0]) || empty($row[1])) {
|
|
continue;
|
|
}
|
|
$row[1] = preg_replace('/^([^,"]+),\s*(.*)$/', '$2 $1', $row[1]);
|
|
foreach ($dataKeys as $key) {
|
|
if (array_key_exists($key, $row)) {
|
|
$hash[$key] = stripslashes(preg_replace('/\\\\r/', "\n", $row[$key]));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'pine':
|
|
if (count($row) < 3 || preg_match("/^#DELETED/", $row[0]) || preg_match("/[()]/", $row[2])) {
|
|
continue;
|
|
}
|
|
$row[1] = preg_replace('/^([^,"]+),\s*(.*)$/', '$2 $1', $row[1]);
|
|
/* Address can be a full RFC822 address */
|
|
$addr_ob = new Horde_Mail_Rfc822_Address($row[2]);
|
|
if (!$addr_ob->valid) {
|
|
continue;
|
|
}
|
|
$row[2] = $addr_ob->bare_address;
|
|
if (empty($row[1]) && !is_null($addr_ob->personal)) {
|
|
$row[1] = $addr_ob->personal;
|
|
}
|
|
foreach ($dataKeys as $key) {
|
|
if (array_key_exists($key, $row)) {
|
|
$hash[$key] = $row[$key];
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
$data[] = $hash;
|
|
}
|
|
|
|
$this->storage->set('data', $data);
|
|
$this->storage->set('map', $map);
|
|
|
|
return $this->nextStep(Horde_Data::IMPORT_DATA, $param);
|
|
}
|
|
|
|
/* Store uploaded file data so that we can read it again in the
|
|
* next step after the user gives some format details. */
|
|
try {
|
|
$this->_browser->wasFileUploaded('import_file', Horde_Data_Translation::t("TSV file"));
|
|
} catch (Horde_Browser_Exception $e) {
|
|
throw new Horde_Data_Exception($e);
|
|
}
|
|
|
|
$file_name = $_FILES['import_file']['tmp_name'];
|
|
if (($file_data = file_get_contents($file_name)) === false) {
|
|
throw new Horde_Data_Exception(Horde_Data_Translation::t("The uploaded file could not be saved."));
|
|
}
|
|
$this->storage->set('file_data', $file_data);
|
|
|
|
/* Read the file's first two lines to show them to the user. */
|
|
$first_lines = '';
|
|
if ($fp = @fopen($file_name, 'r')) {
|
|
$line_no = 1;
|
|
while (($line_no < 3) && ($line = fgets($fp))) {
|
|
$newline = Horde_String::length($line) > 100 ? "\n" : '';
|
|
$first_lines .= substr($line, 0, 100) . $newline;
|
|
++$line_no;
|
|
}
|
|
}
|
|
$this->storage->set('first_lines', $first_lines);
|
|
return Horde_Data::IMPORT_TSV;
|
|
|
|
case Horde_Data::IMPORT_TSV:
|
|
$file_name = Horde_Util::getTempFile('import');
|
|
file_put_contents($file_name, $this->storage->get('file_data'));
|
|
|
|
$this->storage->set('header', $this->_vars->header);
|
|
$this->storage->set('data', $this->importFile($file_name, $this->storage->get('header')));
|
|
$this->storage->set('map');
|
|
return Horde_Data::IMPORT_MAPPED;
|
|
}
|
|
|
|
return parent::nextStep($action, $param);
|
|
}
|
|
|
|
}
|