Newer
Older
<?php
/*********************************************************************
class.import.php
Utilities for importing objects and data (usually via CSV)
Peter Rotich <peter@osticket.com>
Jared Hancock <jared@osticket.com>
Copyright (c) 2006-2015 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class ImportError extends Exception {}
class ImportDataError extends ImportError {}
class CsvImporter {
var $stream;
function __construct($stream) {
// File upload
if (is_array($stream) && !$stream['error']) {
// Properly detect Macintosh style line endings
ini_set('auto_detect_line_endings', true);
$this->stream = fopen($stream['tmp_name'], 'r');
}
// Open file
elseif (is_resource($stream)) {
$this->stream = $stream;
}
// Text from standard-in
elseif (is_string($stream)) {
$this->stream = fopen('php://temp', 'w+');
fwrite($this->stream, $stream);
rewind($this->stream);
}
else {
throw new ImportError(__('Unable to parse submitted csv: ').print_r($stream, true));
}
}
function __destruct() {
fclose($this->stream);
}
function importCsv($all_fields=array(), $defaults=array()) {
$named_fields = array();
$has_header = true;
foreach ($all_fields as $f)
if ($f->get('name'))
$named_fields[$f->get('name')] = $f;
// Read the first row and see if it is a header or not
if (!($data = fgetcsv($this->stream, 1000, ",")))
throw new ImportError(__('Whoops. Perhaps you meant to send some CSV records'));
$headers = array();
foreach ($data as $h) {
$h = trim($h);
$found = false;
foreach ($all_fields as $f) {
if (in_array(mb_strtolower($h), array(
mb_strtolower($f->get('name')), mb_strtolower($f->get('label'))))) {
$found = true;
if (!$f->get('name'))
throw new ImportError(sprintf(__(
'%s: Field must have `variable` set to be imported'), $h));
$headers[$f->get('name')] = $f->get('label');
break;
}
}
if (!$found) {
$has_header = false;
if (count($data) == count($named_fields)) {
// Number of fields in the user form matches the number
// of fields in the data. Assume things line up
$headers = array();
foreach ($named_fields as $f)
$headers[$f->get('name')] = $f->get('label');
break;
}
else {
throw new ImportError(sprintf(
__('%s: Unable to map header to the object field'), $h));
}
}
}
if (!$has_header)
fseek($this->stream, 0);
$objects = $fields = array();
foreach ($headers as $h => $label) {
if (!isset($named_fields[$h]))
continue;
$f = $named_fields[$h];
$name = $f->get('name');
$fields[$name] = $f;
}
// Add default fields (org_id, etc).
foreach ($defaults as $key => $val) {
// Don't apply defaults which are also being imported
if (isset($header[$key]))
unset($defaults[$key]);
}
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// Avoid reading the entire CSV before yielding the first record.
// Use an iterator. This will also allow row-level errors to be
// continuable such that the rows with errors can be handled and the
// iterator can continue with the next row.
return new CsvImportIterator($this->stream, $headers, $fields, $defaults);
}
}
class CsvImportIterator
implements Iterator {
var $stream;
var $start = 0;
var $headers;
var $fields;
var $defaults;
var $current = true;
var $row = 0;
function __construct($stream, $headers, $fields, $defaults) {
$this->stream = $stream;
$this->start = ftell($stream);
$this->headers = $headers;
$this->fields = $fields;
$this->defaults = $defaults;
}
// Iterator interface -------------------------------------
function rewind() {
@fseek($this->stream, $this->start);
if (ftell($this->stream) != $this->start)
throw new RuntimeException('Stream cannot be rewound');
$this->row = 0;
$this->next();
}
function valid() {
return $this->current;
}
function current() {
return $this->current;
}
function key() {
return $this->row;
}
function next() {
do {
if (($csv = fgetcsv($this->stream, 4096, ",")) === false) {
// Read error
$this->current = false;
break;
}
if (count($csv) == 1 && $csv[0] == null)
// Skip empty rows
continue;
elseif (count($csv) != count($this->headers))
throw new ImportDataError(sprintf(__('Bad data. Expected: %s'),
implode(', ', $this->headers)));
// Validate according to field configuration
$i = 0;
$this->current = $this->defaults;
foreach ($this->headers as $h => $label) {
$f = $this->fields[$h];
$f->_errors = array();
$T = $f->parse($csv[$i]);
if ($f->validateEntry($T) && $f->errors())
throw new ImportDataError(sprintf(__(
/* 1 will be a field label, and 2 will be error messages */
'%1$s: Invalid data: %2$s'),
$label, implode(', ', $f->errors())));
// Convert to database format
$this->current[$h] = $f->to_database($T);
$i++;
// Use the do-loop only for the empty line skipping
while (false);
$this->row++;