Commit c10d562d authored by Elliot Pahl's avatar Elliot Pahl Committed by Elliot Pahl
Browse files

Add lib/csvfile.php and update admin/user/uploadcsv.php to use it.

Add non-functional lib/csvfile.php.
(cherry picked from commit 34b4960d5659fdff1d0043c09aa193f10f3e5b50)

CsvFile will now parse a csv file and return an object with errors, headers, and data.
(cherry picked from commit ac62b4a53265ded746e32dfb2f41943bf62c849a)

Use mandatory fields correctly in csvfile.php.
(cherry picked from commit e378fc7de6f127cd0c9bc3ab48912466f3d42351)

Remove checking for an error that will never be reached.
(cherry picked from commit 36f2a90218145f349763aada2c469fd5e6eade71)

Replace references to headers with format.
(cherry picked from commit 002a602cc016986df03752bca551832bbb3ffda9)

Throw exception for no CSV header row if there are no other errors.
(cherry picked from commit e09686075502c50506ebd0dc737021914d1ec03c)

Update uploadcsv.php to use csvfile.php
(cherry picked from commit 4070d03a0c7ff811361038781bd3194d5b98d4b0)

Set an error on the upload users form and return if a duplicate email address is found.
(cherry picked from commit f6dba540e90582b139d0748802f40e3cc1865c49)

Also check profile for duplicate email addresses.
(cherry picked from commit 4f5d8eb661c8abd8d9553e3c193d7f15397a91ee)

Add checks to see if the filename exists.
(cherry picked from commit ba5bdb0e9c9dbc77a422d48c3a0609258a2b0c15)

Add a TODO for documenting csvfile.php.
(cherry picked from commit a3ebd09be81f8908aa2cdd35a92798ea937cffc6)
parent 6f9b2def
......@@ -151,8 +151,7 @@ function uploadcsv_validate(Pieform $form, $values) {
return;
}
require_once('pear/File.php');
require_once('pear/File/CSV.php');
require_once('csvfile.php');
// Don't be tempted to use 'explode' here. There may be > 1 underscore.
$break = strpos($values['authinstance'], '_');
......@@ -165,107 +164,76 @@ function uploadcsv_validate(Pieform $form, $values) {
$usernames = array();
$emails = array();
$conf = File_CSV::discoverFormat($values['file']['tmp_name']);
$i = 0;
while ($line = File_CSV::readQuoted($values['file']['tmp_name'], $conf)) {
$i++;
if (!is_array($line)) {
// Note: the CSV parser returns true on some errors and false on
// others! Yes that's retarded. No I didn't write it :(
$form->set_error('file', get_string('uploadcsverrorincorrectnumberoffields', 'admin', $i));
return;
}
// Get the format of the file
if ($i == 1) {
foreach ($line as &$potentialkey) {
$potentialkey = trim($potentialkey);
if (!in_array($potentialkey, $ALLOWEDKEYS)) {
$form->set_error('file', get_string('uploadcsverrorinvalidfieldname', 'admin', $potentialkey));
return;
}
}
$csvusers = new CsvFile($values['file']['tmp_name']);
$csvusers->set('allowedkeys', $ALLOWEDKEYS);
// Now we know all of the field names are valid, we need to make
// sure that the required fields are included
$mandatoryfields = array(
'username',
'password'
);
$mandatoryfields = array_merge($mandatoryfields, array_keys(ArtefactTypeProfile::get_mandatory_fields()));
if ($lockedprofilefields = get_column('institution_locked_profile_field', 'profilefield', 'name', $institution)) {
$mandatoryfields = array_merge($mandatoryfields, $lockedprofilefields);
}
// Add in the locked profile fields for this institution
foreach ($mandatoryfields as $field) {
if (!in_array($field, $line)) {
$form->set_error('file', get_string('uploadcsverrorrequiredfieldnotspecified', 'admin', $field));
return;
}
}
// Now we know all of the field names are valid, we need to make
// sure that the required fields are included
$mandatoryfields = array(
'username',
'password'
);
$mandatoryfields = array_merge($mandatoryfields, array_keys(ArtefactTypeProfile::get_mandatory_fields()));
if ($lockedprofilefields = get_column('institution_locked_profile_field', 'profilefield', 'name', $institution)) {
$mandatoryfields = array_merge($mandatoryfields, $lockedprofilefields);
}
// The format line is valid
$FORMAT = $line;
log_info('FORMAT:');
log_info($FORMAT);
}
else {
// Trim non-breaking spaces -- they get left in place by File_CSV
foreach ($line as &$field) {
$field = preg_replace('/^(\s|\xc2\xa0)*(.*?)(\s|\xc2\xa0)*$/', '$2', $field);
}
$csvusers->set('mandatoryfields', $mandatoryfields);
$csvdata = $csvusers->get_data();
// We have a line with the correct number of fields, but should validate these fields
// Note: This validation should really be methods on each profile class, that way
// it can be used in the profile screen as well.
if (!empty($csvdata->errors['file'])) {
$form->set_error('file', $csvdata->errors['file']);
return;
}
$formatkeylookup = array_flip($FORMAT);
$username = $line[$formatkeylookup['username']];
$password = $line[$formatkeylookup['password']];
$email = $line[$formatkeylookup['email']];
foreach ($csvdata->data as $key => $line) {
// If headers exists, increment i = key + 1 for actual line number
$i = ($csvusers->get('headerExists')) ? ($key + 1) : $key;
$authobj = AuthFactory::create($authinstance);
// Trim non-breaking spaces -- they get left in place by File_CSV
foreach ($line as &$field) {
$field = preg_replace('/^(\s|\xc2\xa0)*(.*?)(\s|\xc2\xa0)*$/', '$2', $field);
}
if (method_exists($authobj, 'is_username_valid') && !$authobj->is_username_valid($username)) {
$form->set_error('file', get_string('uploadcsverrorinvalidusername', 'admin', $i));
return;
}
if (record_exists_select('usr', 'LOWER(username) = ?', strtolower($username)) || isset($usernames[strtolower($username)])) {
$form->set_error('file', get_string('uploadcsverroruseralreadyexists', 'admin', $i, $username));
return;
}
if (record_exists('usr', 'email', $email) || isset($emails[$email])) {
$form->set_error('file', get_string('uploadcsverroremailaddresstaken', 'admin', $i, $email));
}
// We have a line with the correct number of fields, but should validate these fields
// Note: This validation should really be methods on each profile class, that way
// it can be used in the profile screen as well.
// Note: only checks for valid form are done here, none of the checks
// like whether the password is too easy. The user is going to have to
// change their password on first login anyway.
if (method_exists($authobj, 'is_password_valid') && !$authobj->is_password_valid($password)) {
$form->set_error('file', get_string('uploadcsverrorinvalidpassword', 'admin', $i));
return;
}
$formatkeylookup = array_flip($csvdata->format);
$username = $line[$formatkeylookup['username']];
$password = $line[$formatkeylookup['password']];
$email = $line[$formatkeylookup['email']];
$usernames[strtolower($username)] = 1;
$emails[$email] = 1;
$authobj = AuthFactory::create($authinstance);
// All OK!
$CSVDATA[] = $line;
if (method_exists($authobj, 'is_username_valid') && !$authobj->is_username_valid($username)) {
$form->set_error('file', get_string('uploadcsverrorinvalidusername', 'admin', $i));
return;
}
if (record_exists_select('usr', 'LOWER(username) = ?', strtolower($username)) || isset($usernames[strtolower($username)])) {
$form->set_error('file', get_string('uploadcsverroruseralreadyexists', 'admin', $i, $username));
return;
}
if (record_exists('usr', 'email', $email) || record_exists('artefact_internal_profile_email', 'email', $email) || isset($emails[$email])) {
$form->set_error('file', get_string('uploadcsverroremailaddresstaken', 'admin', $i, $email));
return;
}
}
// Note: only checks for valid form are done here, none of the checks
// like whether the password is too easy. The user is going to have to
// change their password on first login anyway.
if (method_exists($authobj, 'is_password_valid') && !$authobj->is_password_valid($password)) {
$form->set_error('file', get_string('uploadcsverrorinvalidpassword', 'admin', $i));
return;
}
if ($i == 1) {
// There was only the title row :(
$form->set_error('file', get_string('uploadcsverrornorecords', 'admin'));
return;
$usernames[strtolower($username)] = 1;
$emails[$email] = 1;
}
if ($CSVDATA === null) {
// Oops! Couldn't get CSV data for some reason
$form->set_error('file', get_string('uploadcsverrorunspecifiedproblem', 'admin'));
}
$FORMAT = $csvdata->format;
$CSVDATA = $csvdata->data;
}
/**
......
......@@ -247,7 +247,7 @@ $string['proxyauthcredentialsdescription'] = 'Enter the credentials required for
$string['proxyauthcredntialsset'] = 'Proxy authentication credentials set';
// Upload CSV
// Upload CSV and CSV errors
$string['csvfile'] = 'CSV File';
$string['emailusersaboutnewaccount'] = 'E-mail users about their account?';
$string['emailusersaboutnewaccountdescription'] = 'Whether an e-mail should be sent to users informing them of their new account details';
......@@ -256,6 +256,8 @@ $string['forceuserstochangepassworddescription'] = 'Whether users should be forc
$string['uploadcsvinstitution'] = 'The institution and authentication method for the new users';
$string['configureauthplugin'] = 'You must configure an authentication plugin before you can add users';
$string['csvfiledescription'] = 'The file containing users to add';
$string['csverroremptyfile'] = 'The csv file is empty.';
$string['invalidfilename'] = 'The file "%s" does not exist';
$string['uploadcsverrorinvalidfieldname'] = 'The field name "%s" is invalid';
$string['uploadcsverrorrequiredfieldnotspecified'] = 'A required field "%s" has not been specified in the format line';
$string['uploadcsverrornorecords'] = 'The file appears to contain no records (although the header is fine)';
......
<?php
/**
* Mahara: Electronic portfolio, weblog, resume builder and social networking
* Copyright (C) 2006-2007 Catalyst IT Ltd (http://www.catalyst.net.nz)
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*
* @package mahara
* @subpackage core
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
* @copyright (C) 2006-2008 Catalyst IT Ltd http://catalyst.net.nz
*
*/
defined('INTERNAL') || die();
raise_memory_limit("512M");
require_once('pear/File.php');
require_once('pear/File/CSV.php');
/**
* TODO: Document how this class should be used.
*/
class CsvFile {
protected $allowedkeys = array();
protected $conf;
protected $data;
protected $errors = array();
protected $filename;
protected $format = array();
protected $headerExists = true;
protected $mandatoryfields;
public function __construct($filename = '') {
if (!empty($filename) && file_exists($filename)) {
$this->filename = $filename;
$this->conf = File_CSV::discoverFormat($filename);
}
else {
$this->errors['file'] = get_string('invalidfilename', 'admin', $filename);
}
}
public function get($field) {
if (!property_exists($this, $field)) {
throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
}
return $this->{$field};
}
public function set($field, $value) {
if (property_exists($this, $field)) {
if ($this->{$field} != $value) {
// only set it to dirty if it's changed
$this->dirty = true;
}
$this->{$field} = $value;
if ($field == 'parent') {
$this->parentdirty = true;
}
$this->mtime = time();
return true;
}
throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
}
public function get_data() {
$csvfile = new StdClass;
if (!empty($this->errors)) {
$csvfile->errors = $this->errors;
return $csvfile;
}
$this->parse_data();
$csvfile->errors = $this->errors;
if (empty($this->format) && empty($this->errors)) {
throw new SystemException('CSV File has no headers');
}
else {
$csvfile->format = $this->format;
}
$csvfile->data = $this->data;
return $csvfile;
}
public function add_error($key, $value) {
$this->errors[$key] = $value;
}
private function parse_data() {
// Note: readQuoted will fill the array with empty values when there is
// no data in a line for that field
$i = 0;
while ($line = File_CSV::readQuoted($this->filename, $this->conf)) {
$i++;
// Get the format of the file
if ($this->headerExists && $i == 1) {
foreach ($line as &$potentialkey) {
$potentialkey = trim($potentialkey);
if (!in_array($potentialkey, $this->allowedkeys)) {
$this->add_error('file', get_string('uploadcsverrorinvalidfieldname', 'admin', $potentialkey));
return;
}
}
// Now we know all of the field names are valid, we need to make
// sure that the required fields are included
foreach ($this->mandatoryfields as $field) {
if (!in_array($field, $line)) {
$this->add_error('file', get_string('uploadcsverrorrequiredfieldnotspecified', 'admin', $field));
return;
}
}
// The format line is valid
$this->format = $line;
log_info('FORMAT:');
log_info($this->format);
}
else {
// Trim non-breaking spaces -- they get left in place by File_CSV
foreach ($line as &$field) {
$field = preg_replace('/^(\s|\xc2\xa0)*(.*?)(\s|\xc2\xa0)*$/', '$2', $field);
}
// All OK!
$this->data[] = $line;
}
}
if ($this->headerExists && $i == 1) {
// There was only the title row :(
$this->add_error('file', get_string('uploadcsverrornorecords', 'admin'));
return;
}
if ($this->data === null) {
// Oops! Couldn't get CSV data for some reason
$this->add_error('file', get_string('uploadcsverrorunspecifiedproblem', 'admin'));
}
}
}
?>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment