diff --git a/include/class.import.php b/include/class.import.php new file mode 100644 index 0000000000000000000000000000000000000000..3e67c394a27106dd60b54bd647048e8089edc0bb --- /dev/null +++ b/include/class.import.php @@ -0,0 +1,141 @@ +<?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 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 importCsv($forms=array(), $defaults=array()) { + $named_fields = $all_fields = array(); + foreach ($forms as $F) + foreach ($F->getFields() as $field) + $all_fields[] = $field; + + $has_header = true; + foreach ($all_fields as $f) + if ($f->get('name')) + $named_fields[] = $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 a user field'), $h)); + } + } + } + + if (!$has_header) + fseek($this->stream, 0); + + $objects = $fields = $keys = array(); + foreach ($forms as $F) { + foreach ($headers as $h => $label) { + if (!($f = $F->getField($h))) + continue; + + $name = $keys[] = $f->get('name'); + $fields[$name] = $f->getImpl(); + } + } + + // 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]); + $keys[] = $key; + } + + while (($data = fgetcsv($this->stream, 4096, ",")) !== false) { + if (count($data) == 1 && $data[0] == null) + // Skip empty rows + continue; + elseif (count($data) != count($headers)) + throw new ImportError(sprintf(__('Bad data. Expected: %s'), implode(', ', $headers))); + // Validate according to field configuration + $i = 0; + foreach ($headers as $h => $label) { + $f = $fields[$h]; + $T = $f->parse($data[$i]); + if ($f->validateEntry($T) && $f->errors()) + throw new ImportError(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 + $data[$h] = $data[$i++] = $f->to_database($T); + } + // Add default fields + foreach ($defaults as $key => $val) + $data[$key] = $data[$i++] = $val; + + $objects[] = $data; + } + + return $objects; + } +} diff --git a/include/class.user.php b/include/class.user.php index dc599225c6ae05573c7e91f0cccbf7aeeeb6d123..c1c7255f6c250a81c9fd05d881b4d04b767777dc 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -456,138 +456,31 @@ implements TemplateVariable { } static function importCsv($stream, $defaults=array()) { - //Read the header (if any) - $headers = array('name' => __('Full Name'), 'email' => __('Email Address')); - $uform = UserForm::getUserForm(); - $all_fields = $uform->getFields(); - $named_fields = array(); - $has_header = true; - foreach ($all_fields as $f) - if ($f->get('name')) - $named_fields[] = $f; - - if (!($data = fgetcsv($stream, 1000, ","))) - return __('Whoops. Perhaps you meant to send some CSV records'); - - if (Validator::is_email($data[1])) { - $has_header = false; // We don't have an header! - } - else { - $headers = array(); - foreach ($data as $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')) - return 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 { - return sprintf(__('%s: Unable to map header to a user field'), $h); - } - } - } - } - - // 'name' and 'email' MUST be in the headers - if (!isset($headers['name']) || !isset($headers['email'])) - return __('CSV file must include `name` and `email` columns'); - - if (!$has_header) - fseek($stream, 0); - - $users = $fields = $keys = array(); - foreach ($headers as $h => $label) { - if (!($f = $uform->getField($h))) - continue; - - $name = $keys[] = $f->get('name'); - $fields[$name] = $f->getImpl(); - } - - // 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]); - $keys[] = $key; - } - - while (($data = fgetcsv($stream, 1000, ",")) !== false) { - if (count($data) == 1 && $data[0] == null) - // Skip empty rows - continue; - elseif (count($data) != count($headers)) - return sprintf(__('Bad data. Expected: %s'), implode(', ', $headers)); - // Validate according to field configuration - $i = 0; - foreach ($headers as $h => $label) { - $f = $fields[$h]; - $T = $f->parse($data[$i]); - if ($f->validateEntry($T) && $f->errors()) - return 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 - $data[$i] = $f->to_database($T); - $i++; - } - // Add default fields - foreach ($defaults as $key => $val) - $data[] = $val; - - $users[] = $data; - } - - db_autocommit(false); - $error = false; - foreach ($users as $u) { - $vars = array_combine($keys, $u); - if (!static::fromVars($vars, true, true)) { - $error = sprintf(__('Unable to import user: %s'), - print_r($vars, true)); - break; + require_once INCLUDE_DIR . 'class.import.php'; + + $importer = new CsvImporter($stream); + $imported = 0; + try { + db_autocommit(false); + $records = $importer->importCsv(array(UserForm::getUserForm()), $defaults); + foreach ($records as $data) { + if (!isset($data['email']) || !isset($data['name'])) + throw new ImportError('Both `name` and `email` fields are required'); + if (!($user = static::fromVars($data, true, true))) + throw new ImportError(sprintf(__('Unable to import user: %s'), + print_r($vars, true))); + $imported++; } + db_autocommit(true); } - if ($error) + catch (Exception $ex) { db_rollback(); - - db_autocommit(true); - - return $error ?: count($users); - } - - function importFromPost($stuff, $extra=array()) { - if (is_array($stuff) && !$stuff['error']) { - // Properly detect Macintosh style line endings - ini_set('auto_detect_line_endings', true); - $stream = fopen($stuff['tmp_name'], 'r'); - } - elseif ($stuff) { - $stream = fopen('php://temp', 'w+'); - fwrite($stream, $stuff); - rewind($stream); - } - else { - return __('Unable to parse submitted users'); + return $ex->getMessage(); } + return $imported; + } + function importFromPost($stream, $extra=array()) { return User::importCsv($stream, $extra); }