uploadcsv.php 15.4 KB
Newer Older
1
2
<?php
/**
Francois Marier's avatar
Francois Marier committed
3
 * Mahara: Electronic portfolio, weblog, resume builder and social networking
4
 * Copyright (C) 2006-2008 Catalyst IT Ltd (http://www.catalyst.net.nz)
5
 *
Francois Marier's avatar
Francois Marier committed
6
7
8
9
 * 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.
10
 *
Francois Marier's avatar
Francois Marier committed
11
12
13
14
 * 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.
15
 *
Francois Marier's avatar
Francois Marier committed
16
17
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19
20
 *
 * @package    mahara
 * @subpackage admin
21
 * @author     Catalyst IT Ltd
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
23
 * @copyright  (C) 2006-2008 Catalyst IT Ltd http://catalyst.net.nz
24
25
26
27
 *
 */

define('INTERNAL', 1);
28
define('INSTITUTIONALADMIN', 1);
29
define('MENUITEM', 'configusers/uploadcsv');
30
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
Martyn Smith's avatar
Martyn Smith committed
31
define('TITLE', get_string('uploadcsv', 'admin'));
32
require_once('pieforms/pieform.php');
33
require_once('institution.php');
Nigel McNie's avatar
Nigel McNie committed
34
safe_require('artefact', 'internal');
35

36
37
38
// Turn on autodetecting of line endings, so mac newlines (\r) will work
ini_set('auto_detect_line_endings', 1);

Nigel McNie's avatar
Nigel McNie committed
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
$FORMAT = array();
$ALLOWEDKEYS = array(
    'username',
    'password',
    'email',
    'firstname',
    'lastname',
    'preferredname',
    'studentid',
    'introduction',
    'officialwebsite',
    'personalwebsite',
    'blogaddress',
    'address',
    'town',
    'city',
    'country',
    'homenumber',
    'businessnumber',
    'mobilenumber',
    'faxnumber',
    'icqnumber',
    'msnnumber',
    'aimscreenname',
    'yahoochat',
    'skypeusername',
    'jabberusername',
    'occupation',
    'industry',
68
    'authinstance'
Nigel McNie's avatar
Nigel McNie committed
69
70
);

71
72
73
74
75
76
if ($USER->get('admin')) {
    $authinstances = auth_get_auth_instances();
} else {
    $admininstitutions = $USER->get('admininstitutions');
    $authinstances = auth_get_auth_instances_for_institutions($admininstitutions);
    if (empty($authinstances)) {
77
        $SESSION->add_info_msg(get_string('configureauthplugin', 'admin'));
78
79
80
        redirect(get_config('wwwroot').'admin/users/institutions.php?i='.key($admininstitutions).'&amp;edit=1');
    }
}
81

82
if (count($authinstances) > 1) {
83
    $options = array();
84
85

    foreach ($authinstances as $authinstance) {
Richard Mansfield's avatar
Richard Mansfield committed
86
        if ($USER->can_edit_institution($authinstance->name)) {
87
88
            $options[$authinstance->id .'_'. $authinstance->name] = $authinstance->displayname. ': '.$authinstance->instancename;
        }
89
    }
90
91
92
    $default = key($options);

    $authinstanceelement = array(
93
94
        'type' => 'select',
        'title' => get_string('institution'),
95
96
        'description' => get_string('uploadcsvinstitution', 'admin'),
        'options' => $options,
97
        'defaultvalue' => $default
98
99
100
    );
}
else {
101
102
103
104
    foreach ($authinstances as $authinstance) {
        $string = $authinstance->id .'_'. $authinstance->name;
    }

105
    $authinstanceelement = array(
106
        'type' => 'hidden',
107
        'value' => $string
108
109
110
111
112
113
    );
}

$form = array(
    'name' => 'uploadcsv',
    'elements' => array(
114
        'authinstance' => $authinstanceelement,
115
116
117
118
119
120
121
122
        'file' => array(
            'type' => 'file',
            'title' => get_string('csvfile', 'admin'),
            'description' => get_string('csvfiledescription', 'admin'),
            'rules' => array(
                'required' => true
            )
        ),
123
124
125
126
127
128
129
130
131
132
133
134
        'forcepasswordchange' => array(
            'type'         => 'checkbox',
            'title'        => get_string('forceuserstochangepassword', 'admin'),
            'description'  => get_string('forceuserstochangepassworddescription', 'admin'),
            'defaultvalue' => true,
        ),
        'emailusers' => array(
            'type' => 'checkbox',
            'title' => get_string('emailusersaboutnewaccount', 'admin'),
            'description' => get_string('emailusersaboutnewaccountdescription', 'admin'),
            'defaultvalue' => true,
        ),
135
136
        'submit' => array(
            'type' => 'submit',
Nigel McNie's avatar
Nigel McNie committed
137
            'value' => get_string('uploadcsv', 'admin')
138
139
140
141
142
143
144
145
146
        )
    )
);

/**
 * The CSV file is parsed here so validation errors can be returned to the
 * user. The data from a successful parsing is stored in the <var>$CVSDATA</var>
 * array so it can be accessed by the submit function
 *
147
148
 * @param Pieform  $form   The form to validate
 * @param array    $values The values submitted
149
 */
150
function uploadcsv_validate(Pieform $form, $values) {
151
    global $CSVDATA, $ALLOWEDKEYS, $FORMAT, $USER;
152
153
154
155
156
157
158

    // Don't even start attempting to parse if there are previous errors
    if ($form->has_errors()) {
        return;
    }

    if ($values['file']['size'] == 0) {
159
        $form->set_error('file', $form->i18n('rule', 'required', 'required', array()));
160
161
162
163
164
165
        return;
    }

    require_once('pear/File.php');
    require_once('pear/File/CSV.php');

166
167
168
169
    // Don't be tempted to use 'explode' here. There may be > 1 underscore.
    $break = strpos($values['authinstance'], '_');
    $authinstance = substr($values['authinstance'], 0, $break);
    $institution  = substr($values['authinstance'], $break+1);
Richard Mansfield's avatar
Richard Mansfield committed
170
    if (!$USER->can_edit_institution($institution)) {
171
172
173
        $form->set_error('authinstance', get_string('notadminforinstitution', 'admin'));
        return;
    }
174

175
176
    $usernames = array();
    $emails = array();
177
178
    $conf = File_CSV::discoverFormat($values['file']['tmp_name']);
    $i = 0;
179
    while ($line = File_CSV::readQuoted($values['file']['tmp_name'], $conf)) {
180
        $i++;
Nigel McNie's avatar
Nigel McNie committed
181
182
183
184
        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));
185
186
187
            return;
        }

Nigel McNie's avatar
Nigel McNie committed
188
189
        // Get the format of the file
        if ($i == 1) {
190
191
            foreach ($line as &$potentialkey) {
                $potentialkey = trim($potentialkey);
Nigel McNie's avatar
Nigel McNie committed
192
193
194
195
196
                if (!in_array($potentialkey, $ALLOWEDKEYS)) {
                    $form->set_error('file', get_string('uploadcsverrorinvalidfieldname', 'admin', $potentialkey));
                    return;
                }
            }
197

Nigel McNie's avatar
Nigel McNie committed
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
            // 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;
                }
            }

            // The format line is valid
            $FORMAT = $line;
            log_info('FORMAT:');
            log_info($FORMAT);
221
        }
Nigel McNie's avatar
Nigel McNie committed
222
223
224
225
226
227
228
229
230
        else {
            // 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.

            $formatkeylookup = array_flip($FORMAT);
            $username = $line[$formatkeylookup['username']];
            $password = $line[$formatkeylookup['password']];
            $email    = $line[$formatkeylookup['email']];
231

232
233
234
            $authobj = AuthFactory::create($authinstance);

            if (method_exists($authobj, 'is_username_valid') && !$authobj->is_username_valid($username)) {
Nigel McNie's avatar
Nigel McNie committed
235
236
237
                $form->set_error('file', get_string('uploadcsverrorinvalidusername', 'admin', $i));
                return;
            }
238
            if (record_exists_select('usr', 'LOWER(username) = ?', strtolower($username)) || isset($usernames[strtolower($username)])) {
Nigel McNie's avatar
Nigel McNie committed
239
                $form->set_error('file', get_string('uploadcsverroruseralreadyexists', 'admin', $i, $username));
240
241
                return;
            }
242
            if (record_exists('usr', 'email', $email) || isset($emails[$email])) {
243
244
                $form->set_error('file', get_string('uploadcsverroremailaddresstaken', 'admin', $i, $email));
            }
245

Nigel McNie's avatar
Nigel McNie committed
246
247
248
            // 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.
249
            if (method_exists($authobj, 'is_password_valid') && !$authobj->is_password_valid($password)) {
Nigel McNie's avatar
Nigel McNie committed
250
251
252
253
                $form->set_error('file', get_string('uploadcsverrorinvalidpassword', 'admin', $i));
                return;
            }

254
255
256
            $usernames[strtolower($username)] = 1;
            $emails[$email] = 1;

Nigel McNie's avatar
Nigel McNie committed
257
258
            // All OK!
            $CSVDATA[] = $line;
259
260
        }

Nigel McNie's avatar
Nigel McNie committed
261
262
263
264
265
266
    }

    if ($i == 1) {
        // There was only the title row :(
        $form->set_error('file', get_string('uploadcsverrornorecords', 'admin'));
        return;
267
    }
268
269
270
271
272

    if ($CSVDATA === null) {
        // Oops! Couldn't get CSV data for some reason
        $form->set_error('file', get_string('uploadcsverrorunspecifiedproblem', 'admin'));
    }
273
274
275
276
277
278
}

/**
 * Add the users to the system. Make sure that they have to change their
 * password on next login also.
 */
279
function uploadcsv_submit(Pieform $form, $values) {
Nigel McNie's avatar
Nigel McNie committed
280
    global $SESSION, $CSVDATA, $FORMAT;
281

Nigel McNie's avatar
Nigel McNie committed
282
    $formatkeylookup = array_flip($FORMAT);
283
284
285
286

    // Don't be tempted to use 'explode' here. There may be > 1 underscore.
    $break = strpos($values['authinstance'], '_');
    $authinstance = substr($values['authinstance'], 0, $break);
287
288
    $institution = substr($values['authinstance'], $break+1);
    $institution = new Institution($institution);
289

290
291
292
293
294
295
296
297
298
299
300
301
302
303
    $maxusers = $institution->maxuseraccounts; 
    if (!empty($maxusers)) {
        $members = count_records_sql('
            SELECT COUNT(*) FROM {usr} u INNER JOIN {usr_institution} i ON u.id = i.usr
            WHERE i.institution = ? AND u.deleted = 0', array($institution->name));
        if ($members + count($CSVDATA) > $maxusers) {
            $SESSION->add_error_msg(get_string('uploadcsvfailedusersexceedmaxallowed', 'admin'));
            redirect('/admin/users/uploadcsv.php');
        }
    }

    log_info('Inserting users from the CSV file');
    db_begin();

304
    $addedusers = array();
305
    foreach ($CSVDATA as $record) {
Nigel McNie's avatar
Nigel McNie committed
306
        log_debug('adding user ' . $record[$formatkeylookup['username']]);
307
        $user = new StdClass;
308
309
        $user->authinstance = $authinstance;
        $user->username     = $record[$formatkeylookup['username']];
310
311
        $user->firstname    = $record[$formatkeylookup['firstname']];
        $user->lastname     = $record[$formatkeylookup['lastname']];
312
313
314
        $user->password     = $record[$formatkeylookup['password']];
        $user->email        = $record[$formatkeylookup['email']];

Nigel McNie's avatar
Nigel McNie committed
315
316
317
318
319
320
        if (isset($formatkeylookup['studentid'])) {
            $user->studentid = $record[$formatkeylookup['studentid']];
        }
        if (isset($formatkeylookup['preferredname'])) {
            $user->preferredname = $record[$formatkeylookup['preferredname']];
        }
321
        $user->passwordchange = (int)$values['forcepasswordchange'];
322
        $id = insert_record('usr', $user, 'id', true);
323
        $user->id = $id;
324
325
        if ($institution->name != 'mahara') {
            $institution->addUserAsMember($user);
326
        }
327
328
329
330
331
332
333
334
335
        if (get_field('auth_instance', 'authname', 'id', $authinstance) != 'internal') {
            // Assume the admin knows what they're doing when they choose the external auth instance.
            delete_records('auth_remote_user', 'authinstance', $authinstance, 'remoteusername', $user->username);
            insert_record('auth_remote_user', (object) array(
                'authinstance'   => $authinstance,
                'remoteusername' => $user->username,
                'localusr'       => $id,
            ));
        }
336

Nigel McNie's avatar
Nigel McNie committed
337
338
339
340
341
        foreach ($FORMAT as $field) {
            if ($field == 'username' || $field == 'password') {
                continue;
            }
            set_profile_field($id, $field, $record[$formatkeylookup[$field]]);
342
        }
Nigel McNie's avatar
Nigel McNie committed
343
344

        handle_event('createuser', $user);
345
346
347
        if ($values['emailusers']) {
            $addedusers[] = $user;
        }
348
    }
349
    db_commit();
350
351
352
353
354
355
356

    // Only send e-mail to users after we're sure they have been inserted 
    // successfully
    $straccountcreatedtext = ($values['forcepasswordchange']) ? 'accountcreatedchangepasswordtext' : 'accountcreatedtext';
    $straccountcreatedhtml = ($values['forcepasswordchange']) ? 'accountcreatedchangepasswordhtml' : 'accountcreatedhtml';
    if ($values['emailusers'] && $addedusers) {
        foreach ($addedusers as $user) {
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
            $failedusers = array();
            try {
                email_user($user, null, get_string('accountcreated'),
                    get_string($straccountcreatedtext, 'mahara', $user->firstname, get_config('sitename'), $user->username, $user->password, get_config('sitename')),
                    get_string($straccountcreatedhtml, 'mahara', $user->firstname, get_config('sitename'), $user->username, $user->password, get_config('sitename'))
                );
            }
            catch (EmailException $e) {
                log_info($e->getMessage());
                $failedusers[] = $user;
            }
        }

        if ($failedusers) {
            $message = get_string('uploadcsvsomeuserscouldnotbeemailed', 'admin') . "\n<ul>\n";
            foreach ($failedusers as $user) {
                $message .= '<li>' . full_name($user) . ' &lt;' . hsc($user->email) . "&gt;</li>\n";
            }
            $message .= "</ul>\n";
            $SESSION->add_info_msg($message, false);
377
378
379
        }
    }

380
381
    log_info('Inserted ' . count($CSVDATA) . ' records');

382
    $SESSION->add_ok_msg(get_string('uploadcsvusersaddedsuccessfully', 'admin'));
383
    redirect('/admin/users/uploadcsv.php');
384
385
}

Nigel McNie's avatar
Nigel McNie committed
386
387
388
389
390
391
392
393
394
395
396
// Get a list of all profile fields, to inform the user on what fields they can
// put in their file.
$fields = "<ul>\n";
foreach (array_keys(ArtefactTypeProfile::get_all_fields()) as $type) {
    if ($type == 'firstname' || $type == 'lastname' || $type == 'email') {
        continue;
    }
    $fields .= '<li>' . hsc($type) . "</li>\n";
}
$fields .= "</ul>\n";

397
398
399
400
401
402
403
404
405
406
407
408
409
410
if ($USER->get('admin')) {
    $uploadcsvpagedescription = get_string('uploadcsvpagedescription2', 'admin',
        get_config('wwwroot') . 'admin/extensions/pluginconfig.php?plugintype=artefact&pluginname=internal&type=profile',
        get_config('wwwroot') . 'admin/users/institutions.php',
        $fields
    );
}
else {
    $uploadcsvpagedescription = get_string('uploadcsvpagedescription2institutionaladmin', 'admin',
        get_config('wwwroot') . 'admin/users/institutions.php',
        $fields
    );
}

411
$smarty = smarty();
412
$smarty->assign('uploadcsvpagedescription', $uploadcsvpagedescription);
413
$smarty->assign('uploadcsvform', pieform($form));
414
$smarty->display('admin/users/uploadcsv.tpl');
415
416

?>