user.php 50.2 KB
Newer Older
Penny Leach's avatar
Penny Leach committed
1
2
<?php
/**
Francois Marier's avatar
Francois Marier committed
3
 * Mahara: Electronic portfolio, weblog, resume builder and social networking
4
5
 * Copyright (C) 2006-2009 Catalyst IT Ltd and others; see:
 *                         http://wiki.mahara.org/Contributors
Penny Leach's avatar
Penny Leach committed
6
 *
Francois Marier's avatar
Francois Marier committed
7
8
9
10
 * 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.
Penny Leach's avatar
Penny Leach committed
11
 *
Francois Marier's avatar
Francois Marier committed
12
13
14
15
 * 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.
Penny Leach's avatar
Penny Leach committed
16
 *
Francois Marier's avatar
Francois Marier committed
17
18
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
Penny Leach's avatar
Penny Leach committed
19
20
21
 *
 * @package    mahara
 * @subpackage core
22
 * @author     Catalyst IT Ltd
Penny Leach's avatar
Penny Leach committed
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
24
 * @copyright  (C) 2006-2009 Catalyst IT Ltd http://catalyst.net.nz
Penny Leach's avatar
Penny Leach committed
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 *
 */

defined('INTERNAL') || die();

/** 
 * loads up activity preferences for a given user
 *
 * @param int $userid to load preferences for 
 * @todo caching
 */
function load_activity_preferences($userid) {
    $prefs = array();
    if (empty($userid)) {
        throw new InvalidArgumentException("couldn't load activity preferences, no user id specified");
    }
Richard Mansfield's avatar
Richard Mansfield committed
41
    if ($prefs = get_records_assoc('usr_activity_preference', 'usr', $userid, '', 'activity,method')) {
Penny Leach's avatar
Penny Leach committed
42
        foreach ($prefs as $p) {
43
            $prefs[$p->activity] = $p->method;
Penny Leach's avatar
Penny Leach committed
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
        }
    }
    return $prefs;
}

/** 
 * loads up account preferences for a given user
 * if you want them for the current user
 * use $SESSION->accountprefs
 *
 * @param int $userid to load preferences for 
 * @todo caching
 * @todo defaults? 
 */
function load_account_preferences($userid) {
    $prefs = array();
    $expectedprefs = expected_account_preferences();
    if (empty($userid)) {
        throw new InvalidArgumentException("couldn't load account preferences, no user id specified");
    }
64
    if ($prefs = get_records_array('usr_account_preference', 'usr', $userid)) {
Penny Leach's avatar
Penny Leach committed
65
        foreach ($prefs as $p) {
66
            $prefs[$p->field] = $p->value;
Penny Leach's avatar
Penny Leach committed
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
        }
    }
    foreach ($expectedprefs as $field => $default) {
        if (!isset($prefs[$field])) {
            $prefs[$field] = $default;
        }
    }
    return $prefs;
}


/** 
 * sets a user preference in the database
 * if you want to set it in the session as well
 * use SESSION->set_account_preference 
 *
 * @param int $userid user id to set preference for
 * @param string $field preference field to set
 * @param string $value preference value to set.
 */
function set_account_preference($userid, $field, $value) {
88
89
90
91
92
93
94
95
96
97
    if ($field == 'lang') {
        $oldlang = get_field('usr_account_preference', 'value', 'usr', $userid, 'field', 'lang');
        if (empty($oldlang) || $oldlang == 'default') {
            $oldlang = get_config('lang');
        }
        $newlang = (empty($value) || $value == 'default') ? get_config('lang') : $value;
        if ($newlang != $oldlang) {
            change_language($userid, $oldlang, $newlang);
        }
    }
Penny Leach's avatar
Penny Leach committed
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
    if (record_exists('usr_account_preference', 'usr', $userid, 'field', $field)) {
        set_field('usr_account_preference', 'value', $value, 'usr', $userid, 'field', $field);
    }
    else {
        try {
            $pref = new StdClass;
            $pref->usr = $userid;
            $pref->field = $field;
            $pref->value = $value;
            insert_record('usr_account_preference', $pref);
        }
        catch (Exception $e) {
            throw new InvalidArgumentException("Failed to insert account preference "
                ." $value for $field for user $userid");
        }
    }
}

116
117
118

/** 
 * Change language-specific stuff in the db for a user.  Currently
119
120
 * changes the name of the 'assessmentfiles' folder in the user's
 * files area and the views and artefacts tagged for the profile
Clare Lenihan's avatar
Clare Lenihan committed
121
 * sideblock
122
123
124
125
126
127
 *
 * @param int $userid user id to set preference for
 * @param string $oldlang old language
 * @param string $newlang new language
 */
function change_language($userid, $oldlang, $newlang) {
128
129
130
131
    if (get_field('artefact_installed', 'active', 'name', 'file')) {
        safe_require('artefact', 'file');
        ArtefactTypeFolder::change_language($userid, $oldlang, $newlang);
    }
Clare Lenihan's avatar
Clare Lenihan committed
132
133
    set_field_select('artefact_tag', 'tag', get_string_from_language($newlang, 'profile'), 'WHERE tag = ? AND artefact IN (SELECT id FROM {artefact} WHERE owner = ?)', array(get_string_from_language($oldlang, 'profile'), $userid));
    set_field_select('view_tag', 'tag', get_string_from_language($newlang, 'profile'), 'WHERE tag = ? AND view IN (SELECT id FROM {view} WHERE owner = ?)', array(get_string_from_language($oldlang, 'profile'), $userid));
134
135
}

Penny Leach's avatar
Penny Leach committed
136
137
138
139
140
141
/** 
 * sets an activity preference in the database
 * if you want to set it in the session as well
 * use $SESSION->set_activity_preference 
 *
 * @param int $userid user id to set preference for
142
 * @param int $activity activity type to set
Penny Leach's avatar
Penny Leach committed
143
144
145
 * @param string $method notification method to set.
 */
function set_activity_preference($userid, $activity, $method) {
146
147
148
    if (empty($method)) {
        return delete_records('usr_activity_preference', 'activity', $activity, 'usr', $userid);
    }
Penny Leach's avatar
Penny Leach committed
149
150
151
152
153
154
155
156
157
158
159
160
161
    if (record_exists('usr_activity_preference', 'usr', $userid, 'activity', $activity)) {
        set_field('usr_activity_preference', 'method', $method, 'usr', $userid, 'activity', $activity);
    }
    else {
        try {
            $pref = new StdClass;
            $pref->usr = $userid;
            $pref->activity = $activity;
            $pref->method = $method;
            insert_record('usr_activity_preference', $pref);
        }
        catch (Exception $e) {
            throw new InvalidArgumentException("Failed to insert activity preference "
162
                ." $method for $activity for user $userid");
Penny Leach's avatar
Penny Leach committed
163
164
165
166
        }
    }
}

167
168
169
170
171
172
173
174
175
/**
 * gets an account preference for the user, 
 * or the default if not set for that user,
 * as specified in {@link expected_account_preferences}
 *
 * @param int $userid id of user
 * @param string $field preference to get
 */
function get_account_preference($userid, $field) {
176
    if ($pref = get_record('usr_account_preference', 'usr', $userid, 'field', $field)) {
177
178
179
180
181
182
        return $pref->value;
    }
    $expected = expected_account_preferences();
    return $expected[$field];
}

183
184
185
186
187
188
189
190
191
192

function get_user_language($userid) {
    $langpref = get_account_preference($userid, 'lang');
    if (empty($langpref) || $langpref == 'default') {
        return get_config('lang');
    }
    return $langpref;
}


193
194
195
196
197
/**
 * default account settings
 * 
 * @returns array of fields => values
 */
Penny Leach's avatar
Penny Leach committed
198
199
200
201
function expected_account_preferences() {
    return array('friendscontrol' => 'auth',
                 'wysiwyg'        =>  1,
                 'messages'       => 'allow',
202
                 'lang'           => 'default',
203
                 'addremovecolumns' => 0,
204
                 'tagssideblockmaxtags' => get_config('tagssideblockmaxtags'),
Penny Leach's avatar
Penny Leach committed
205
206
207
                 );
}

208
209
function set_profile_field($userid, $field, $value) {
    safe_require('artefact', 'internal');
210
211
212
213
214

    // this is a special case that replaces the primary email address with the
    // specified one
    if ($field == 'email') {
        try {
215
            $email = artefact_instance_from_type('email', $userid);
216
217
218
219
220
221
222
223
224
225
226
227
228
229
        }
        catch (ArtefactNotFoundException $e) {
            $email = new ArtefactTypeEmail();
            $email->set('owner', $userid);
        }
        $email->set('title', $value);
        $email->commit();
    }
    else {
        $classname = generate_artefact_class_name($field);
        $profile = new $classname(0, array('owner' => $userid));
        $profile->set('title', $value);
        $profile->commit();
    }
230
231
232
233
234
235
236
237
238
239
240
241
}

/**
 * Return the value of a profile field for a given user
 *
 * @param integer user id to find the profile field for
 * @param field what profile field you want the value for
 * @returns string the value of the profile field (null if it doesn't exist)
 *
 * @todo, this needs to be better (fix email behaviour)
 */
function get_profile_field($userid, $field) {
242
243
244
245
246
247
248
249
250
251
    if ($field == 'email') {
        $value = get_field_sql("
            SELECT a.title
            FROM {usr} u
            JOIN {artefact} a ON (a.title = u.email AND a.owner = u.id)
            WHERE a.artefacttype = 'email' AND u.id = ?", array($userid));
    }
    else {
        $value = get_field('artefact', 'title', 'owner', $userid, 'artefacttype', $field);
    }
252
253
254
255

    if ($value) {
        return $value;
    }
256

257
    return null;
258
259
}

260
261
262
263
264
265
266
267
/** 
 * Always use this function for all emails to users
 * 
 * @param object $userto user object to send email to. must contain firstname,lastname,preferredname,email
 * @param object $userfrom user object to send email from. If null, email will come from mahara
 * @param string $subject email subject
 * @param string $messagetext text version of email
 * @param string $messagehtml html version of email (will send both html and text)
268
 * @param array  $customheaders email headers
269
270
 * @throws EmailException
 */ 
271
function email_user($userto, $userfrom, $subject, $messagetext, $messagehtml='', $customheaders=null) {
272
273
274
    global $IDPJUMPURL;
    static $mnetjumps = array();

275
276
277
278
279
    if (!get_config('sendemail')) {
        // You can entirely disable Mahara from sending any e-mail via the 
        // 'sendemail' configuration variable
        return true;
    }
280
281
282
283
284

    if (empty($userto)) {
        throw new InvalidArgumentException("empty user given to email_user");
    }
    
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
    // If the user is a remote xmlrpc user, trawl through the email text for URLs
    // to our wwwroot and modify the url to direct the user's browser to login at
    // their home site before hitting the link on this site
    if (!empty($userto->mnethostwwwroot) && !empty($userto->mnethostapp)) {
        require_once(get_config('docroot') . 'auth/xmlrpc/lib.php');

        // Form the request url to hit the idp's jump.php
        if (isset($mnetjumps[$userto->mnethostwwwroot])) {
            $IDPJUMPURL = $mnetjumps[$userto->mnethostwwwroot];
        } else {
            $mnetjumps[$userto->mnethostwwwroot] = $IDPJUMPURL = PluginAuthXmlrpc::get_jump_url_prefix($userto->mnethostwwwroot, $userto->mnethostapp);
        }

        $wwwroot = get_config('wwwroot');
        $messagetext = preg_replace_callback('%(' . $wwwroot . '([\w_:\?=#&@/;.~-]*))%',
            'localurl_to_jumpurl',
            $messagetext);
        $messagehtml = preg_replace_callback('%href=["\'`](' . $wwwroot . '([\w_:\?=#&@/;.~-]*))["\'`]%',
            'localurl_to_jumpurl',
            $messagehtml);
    }


308
309
310
311
    require_once('phpmailer/class.phpmailer.php');

    $mail = new phpmailer();

312
313
    // Leaving this commented out - there's no reason for people to know this
    //$mail->Version = 'Mahara ' . get_config('release');
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
    $mail->PluginDir = get_config('libroot')  . 'phpmailer/';
    
    $mail->CharSet = 'UTF-8';

    $smtphosts = get_config('smtphosts');
    if ($smtphosts == 'qmail') {
        // use Qmail system
        $mail->IsQmail();
    } 
    else if (empty($smtphosts)) {
        // use PHP mail() = sendmail
        $mail->IsMail();
    }
    else {
        $mail->IsSMTP();
        // use SMTP directly
        $mail->Host = get_config('smtphosts');
        if (get_config('smtpuser')) {
            // Use SMTP authentication
            $mail->SMTPAuth = true;
            $mail->Username = get_config('smtpuser');
            $mail->Password = get_config('smtppass');
        }
    }

339
    if (empty($userfrom) || $userfrom->email == get_config('noreplyaddress')) {
340
341
        $mail->Sender = get_config('noreplyaddress');
        $mail->From = $mail->Sender;
342
        $mail->FromName = (isset($userfrom->id)) ? display_name($userfrom, $userto) : get_config('sitename');
343
        $customheaders[] = 'Precedence: Bulk'; // Try to avoid pesky out of office responses
Richard Mansfield's avatar
Richard Mansfield committed
344
        $messagetext .= "\n\n" . get_string('pleasedonotreplytothismessage') . "\n";
345
        if ($messagehtml) {
Richard Mansfield's avatar
Richard Mansfield committed
346
            $messagehtml .= "\n\n<p>" . get_string('pleasedonotreplytothismessage') . "</p>\n";
347
        }
348
349
350
351
352
353
    }
    else {
        $mail->Sender = $userfrom->email;
        $mail->From = $mail->Sender;
        $mail->FromName = display_name($userfrom, $userto);
    }
354
    $replytoset = false;
355
356
357
    if (!empty($customheaders) && is_array($customheaders)) {
        foreach ($customheaders as $customheader) {
            $mail->AddCustomHeader($customheader);
358
359
360
            if (0 === stripos($customheader, 'reply-to')) {
                $replytoset = true;
            }
361
362
        }
    }
363
364
365
366

    if (!$replytoset) {
        $mail->AddReplyTo($mail->From, $mail->FromName);
    }
367

368
    $mail->Subject = substr(stripslashes($subject), 0, 900);
369

370
371
372
373
374
375
376
    if ($to = get_config('sendallemailto')) {
        // Admins can configure the system to send all email to a given address 
        // instead of whoever would receive it, useful for debugging.
        $mail->addAddress($to);
        $notice = get_string('debugemail', 'mahara', display_name($userto, $userto), $userto->email);
        $messagetext =  $notice . "\n\n" . $messagetext;
        if ($messagehtml) {
377
            $messagehtml = '<p>' . hsc($notice) . '</p>' . $messagehtml;
378
379
380
381
382
383
        }
    }
    else {
        $usertoname = display_name($userto, $userto);
        $mail->AddAddress($userto->email, $usertoname );
    }
384
385
386
387
388
389
390

    $mail->WordWrap = 79;   

    if ($messagehtml) { 
        $mail->IsHTML(true);
        $mail->Encoding = 'quoted-printable';
        $mail->Body    =  $messagehtml;
391
        $mail->AltBody =  $messagetext;
392
393
394
    } 
    else {
        $mail->IsHTML(false);
395
        $mail->Body =  $messagetext;
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
    }

    if ($mail->Send()) {
        return true;
    } 
    throw new EmailException("Couldn't send email to $usertoname with subject $subject. "
                        . "Error from phpmailer was: " . $mail->ErrorInfo );
}

/**
 * converts a user object to a string representation of the user suitable for
 * the current user (or specified user) to see
 *
 * Both parameters should be objects containing id, preferredname, firstname,
 * lastname, admin
 *
412
413
 * @param object $user the user that you're trying to format to a string
 * @param object $userto the user that is looking at the string representation (if left
414
 * blank, will default to the currently logged in user).
415
 * @param boolean $nameonly do not append the user's username even if $userto can see it.
416
417
418
 *
 * @returns string name to display
 */
419
function display_name($user, $userto=null, $nameonly=false) {
420
    global $USER;
Nigel McNie's avatar
Nigel McNie committed
421
422
423
    static $resultcache = array();
    static $usercache   = array();

424
425
426
    if (empty($userto)) {
        $userto = new StdClass;
        $userto->id            = $USER->get('id');
427
        $userto->username      = $USER->get('username');
428
429
430
        $userto->preferredname = $USER->get('preferredname');
        $userto->firstname     = $USER->get('firstname');
        $userto->lastname      = $USER->get('lastname');
431
        $userto->admin         = $USER->get('admin') || $USER->is_institutional_admin();
432
        $userto->staff         = $USER->get('staff') || $USER->is_institutional_staff();
433
    }
Nigel McNie's avatar
Nigel McNie committed
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
    else if (is_numeric($userto)) {
        if (isset($usercache[$userto])) {
            $userto = $usercache[$userto];
        }
        else if ($userto == $USER->get('id')) {
            $userto = new StdClass;
            $userto->id            = $USER->get('id');
            $userto->username      = $USER->get('username');
            $userto->preferredname = $USER->get('preferredname');
            $userto->firstname     = $USER->get('firstname');
            $userto->lastname      = $USER->get('lastname');
            $userto->admin         = $USER->get('admin') || $USER->is_institutional_admin();
            $userto->staff         = $USER->get('staff') || $USER->is_institutional_staff();
            $usercache[$userto->id] = $userto;
        }
        else {
            $userto = $usercache[$userto] = get_record('usr', 'id', $userto);
        }
    }

454
455
456
457
    if (is_array($user)) {
        $user = (object)$user;
    }
    else if (is_numeric($user)) {
Nigel McNie's avatar
Nigel McNie committed
458
459
460
461
462
463
464
465
466
467
468
469
        if (isset($usercache[$user])) {
            $user = $usercache[$user];
        }
        else if ($user == $USER->get('id')) {
            $user = new StdClass;
            $user->id            = $USER->get('id');
            $user->username      = $USER->get('username');
            $user->preferredname = $USER->get('preferredname');
            $user->firstname     = $USER->get('firstname');
            $user->lastname      = $USER->get('lastname');
            $user->admin         = $USER->get('admin') || $USER->is_institutional_admin();
            $user->staff         = $USER->get('staff') || $USER->is_institutional_staff();
470
            $user->deleted       = 0;
Nigel McNie's avatar
Nigel McNie committed
471
472
473
474
475
            $usercache[$user->id] = $user;
        }
        else {
            $user = $usercache[$user] = get_record('usr', 'id', $user);
        }
476
477
478
479
480
    }
    if (!is_object($user)) {
        throw new InvalidArgumentException("Invalid user passed to display_name");
    }

Martyn Smith's avatar
Martyn Smith committed
481
482
483
484
    if ($user instanceof User) {
        $userObj = $user;
        $user = new StdClass;
        $user->id            = $userObj->get('id');
485
        $user->username      = $userObj->get('username');
Martyn Smith's avatar
Martyn Smith committed
486
487
488
489
        $user->preferredname = $userObj->get('preferredname');
        $user->firstname     = $userObj->get('firstname');
        $user->lastname      = $userObj->get('lastname');
        $user->admin         = $userObj->get('admin');
490
        $user->staff         = $userObj->get('staff');
491
        $user->deleted       = $userObj->get('deleted');
Martyn Smith's avatar
Martyn Smith committed
492
493
    }

494
495
496
    $user->id   = (isset($user->id)) ? $user->id : null;
    $userto->id = (isset($userto->id)) ? $userto->id : null;

Nigel McNie's avatar
Nigel McNie committed
497
498
499
500
    if (isset($resultcache[$user->id][$userto->id][$nameonly])) {
        return $resultcache[$user->id][$userto->id][$nameonly];
    }

501
    // if they don't have a preferred name set, just return here
502
    $firstlast = (isset($user->deleted) && $user->deleted) ? get_string('deleteduser') : ($user->firstname . ' ' . $user->lastname);
503
    if (empty($user->preferredname)) {
504
        if ((!empty($userto->admin) || !empty($userto->staff)) && !$nameonly) {
505
            return ($resultcache[$user->id][$userto->id][$nameonly] = $firstlast . ' (' . $user->username . ')');
506
        }
507
        return ($resultcache[$user->id][$userto->id][$nameonly] = $firstlast);
508
    }
509
510
    else if ($user->id == $userto->id) {
        // If viewing our own name, show it how we like it
511
        return ($resultcache[$user->id][$userto->id][$nameonly] = $user->preferredname);
512
    }
513

514
    if ((!empty($userto->admin) || !empty($userto->staff)) && !$nameonly) {
Nigel McNie's avatar
Nigel McNie committed
515
        return ($resultcache[$user->id][$userto->id][$nameonly]
516
            = $user->preferredname . ' (' . $firstlast . ' - ' . $user->username . ')');
517
518
    }

519
    $sql = "SELECT g1.member
520
521
            FROM {group_member} g1 
            JOIN {group_member} g2
522
523
                ON g1.group = g2.group
            JOIN {group} g ON (g.id = g1.group AND g.deleted = 0)
524
525
            WHERE g1.member = ? AND g2.member = ? AND g2.role = 'tutor'";
    if (record_exists_sql($sql, array($user->id, $userto->id))) {
Nigel McNie's avatar
Nigel McNie committed
526
        return ($resultcache[$user->id][$userto->id][$nameonly]
527
            = $user->preferredname . ($nameonly ? '' : ' (' . $firstlast . ')'));
528
    }
529
    return ($resultcache[$user->id][$userto->id][$nameonly] = $user->preferredname);
530
531
}

Penny Leach's avatar
Penny Leach committed
532
533
534
/**
 * function to format a users name when there is no user to look at them
 * ie when display_name is not going to work..
535
 */
Penny Leach's avatar
Penny Leach committed
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
function display_default_name($user) {
    if (is_array($user)) {
        $user = (object)$user;
    }
    else if (is_numeric($user)) {
        $user = get_record('usr', 'id', $user);
    }
    if (!is_object($user)) {
        throw new InvalidArgumentException("Invalid user passed to display_name");
    }

    if ($user instanceof User) {
        $userObj = $user;
        $user = new StdClass;
        $user->id            = $userObj->get('id');
        $user->preferredname = $userObj->get('preferredname');
        $user->firstname     = $userObj->get('firstname');
        $user->lastname      = $userObj->get('lastname');
        $user->admin         = $userObj->get('admin');
    }

    // if they don't have a preferred name set, just return here
    if (empty($user->preferredname)) {
        return $user->firstname . ' ' . $user->lastname;
    }
    else {
562
        return $user->preferredname;
Penny Leach's avatar
Penny Leach committed
563
564
565
566
567
    }
}



568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
/**
 * Converts a user object to a full name representation, honouring the language
 * setting.
 *
 * Currently a stub, will need to be improved and completed as demand arises.
 *
 * @param object $user The user object to make a full name out of. If empty,
 *                     the global $USER object is used*/
function full_name($user=null) {
    global $USER;

    if ($user === null) {
        $user = new StdClass;
        $user->firstname = $USER->get('firstname');
        $user->lastname  = $USER->get('lastname');
    }

    return $user->firstname . ' ' . $user->lastname;
}
587

588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607

/**
 * helper function to default to currently
 * logged in user if there isn't an id specified
 * @throws InvalidArgumentException if there is no user and no $USER
 */
function optional_userid($userid) {

    if (!empty($userid)) {
        return $userid;
    }

    if (!is_logged_in()) {
        throw new InvalidArgumentException("optional_userid no userid and no logged in user");
    }
    
    global $USER;
    return $USER->get('id');
}

608
609
610
611
612
613
614
615
616
617
618
619
620


/**
 * helper function to default to currently
 * logged in user if there isn't an id specified
 * @throws InvalidArgumentException if there is no user and no $USER
 */
function optional_userobj($user) {

    if (!empty($user) && is_object($user)) {
        return $user;
    }

621
    if (!empty($user) && is_numeric($user)) {
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
        if ($user = get_record('usr', 'id', $user)) {
            return $user;
        }
        throw new InvalidArgumentException("optional_userobj given id $id no db match found");
    }

    if (!is_logged_in()) {
        throw new InvalidArgumentException("optional_userobj no userid and no logged in user");
    }
    
    global $USER;
    return $USER->to_stdclass();
}




639
640
641
/**
 * helper function for testing logins
 */
642
643
function is_logged_in() {
    global $USER;
Martyn Smith's avatar
Martyn Smith committed
644
645
646
647
648
    if (empty($USER)) {
        return false;
    }

    return $USER->is_logged_in();
649
650
}

651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
/**
 * is there a friend relationship between these two users?
 *
 * @param int $userid1 
 * @param int $userid2
 */

function is_friend($userid1, $userid2) {
    return record_exists_select('usr_friend', '(usr1 = ? AND usr2 = ?) OR (usr2 = ? AND usr1 = ?)', 
                                array($userid1, $userid2, $userid1, $userid2));
}

/**
 * has there been a request between these two users?
 *
 * @param int $userid1
 * @param int $userid2
 */
function get_friend_request($userid1, $userid2) {
    return get_record_select('usr_friend_request', '(owner = ? AND requester = ?) OR (requester = ? AND owner = ?)',
                             array($userid1, $userid2, $userid1, $userid2));
        
} 

675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
/**
 * Returns an object containing information about a user, including account
 * and activity preferences
 *
 * @param int $userid The ID of the user to retrieve information about
 * @return object     The user object. Note this is not in the same form as
 *                    the $USER object used to denote the current user -
 *                    the object returned by this method is a simple object.
 */
function get_user($userid) {
    if (!$user = get_record('usr', 'id', $userid, null, null, null, null,
        '*, ' . db_format_tsfield('expiry') . ', ' . db_format_tsfield('lastlogin'))) {
        throw new InvalidArgumentException('Unknown user ' . $userid);
    }

    $user->activityprefs = load_activity_preferences($userid);
    $user->accountprefs  = load_account_preferences($userid);
    return $user;
}

695

Nigel McNie's avatar
Nigel McNie committed
696
697
698
699
700
701
702
703
704
705
706
707
/**
 * Suspends a user
 *
 * @param int $suspendeduserid  The ID of the user to suspend
 * @param string $reason        The reason why the user is being suspended
 * @param int $suspendinguserid The ID of the user who is performing the suspension
 */
function suspend_user($suspendeduserid, $reason, $suspendinguserid=null) {
    if ($suspendinguserid === null) {
        global $USER;
        $suspendinguserid = $USER->get('id');
    }
708

Nigel McNie's avatar
Nigel McNie committed
709
710
711
712
713
714
    $suspendrec = new StdClass;
    $suspendrec->id              = $suspendeduserid;
    $suspendrec->suspendedcusr   = $suspendinguserid;
    $suspendrec->suspendedreason = $reason;
    $suspendrec->suspendedctime  = db_format_timestamp(time());
    update_record('usr', $suspendrec, 'id');
715

716
    $lang = get_user_language($suspendeduserid);
717
718
    $message = new StdClass;
    $message->users = array($suspendeduserid);
719
    $message->subject = get_string_from_language($lang, 'youraccounthasbeensuspended');
720
721
722
723
724
725
726
727
    if ($reason == '') {
        $message->message = get_string_from_language($lang, 'youraccounthasbeensuspendedtext2', 'mahara',
            get_config('sitename'), display_name($suspendinguserid, $suspendeduserid));
    }
    else {
        $message->message = get_string_from_language($lang, 'youraccounthasbeensuspendedreasontext', 'mahara',
            get_config('sitename'), display_name($suspendinguserid, $suspendeduserid), $reason);
    }
728
    require_once('activity.php');
729
730
    activity_occurred('maharamessage', $message);

Nigel McNie's avatar
Nigel McNie committed
731
732
    handle_event('suspenduser', $suspendeduserid);
}
733

734
735
736
737
738
739
740
741
742
743
744
745
746
/**
 * Unsuspends a user
 *
 * @param int $userid The ID of the user to unsuspend
 */
function unsuspend_user($userid) {
    $suspendedrec = new StdClass;
    $suspendedrec->id = $userid;
    $suspendedrec->suspendedcusr = null;
    $suspendedrec->suspendedreason = null;
    $suspendedrec->suspendedctime  = null;
    update_record('usr', $suspendedrec);

747
    $lang = get_user_language($userid);
748
749
    $message = new StdClass;
    $message->users = array($userid);
750
    $message->subject = get_string_from_language($lang, 'youraccounthasbeenunsuspended');
751
    $message->message = get_string_from_language($lang, 'youraccounthasbeenunsuspendedtext2', 'mahara', get_config('sitename'));
752
    require_once('activity.php');
753
754
755
756
757
758
759
760
    activity_occurred('maharamessage', $message);

    handle_event('unsuspenduser', $userid);
}

/**
 * Deletes a user
 *
761
762
763
764
 * This function ensures that a user is deleted according to how Mahara wants a 
 * deleted user to be. You can call it multiple times on the same user without 
 * harm.
 *
765
766
767
 * @param int $userid The ID of the user to delete
 */
function delete_user($userid) {
768
769
    db_begin();

770
771
772
    // We want to append 'deleted.timestamp' to some unique fields in the usr 
    // table, so they can be reused by new accounts
    $fieldstomunge = array('username', 'email');
773
    $datasuffix = '.deleted.' . time();
774

775
776
    $user = get_record('usr', 'id', $userid, null, null, null, null, implode(', ', $fieldstomunge));

777
778
779
    $deleterec = new StdClass;
    $deleterec->id = $userid;
    $deleterec->deleted = 1;
780
781
782
783
784
    foreach ($fieldstomunge as $field) {
        if (!preg_match('/\.deleted\.\d+$/', $user->$field)) {
            $deleterec->$field = $user->$field . $datasuffix;
        }
    }
785
786
787

    // Set authinstance to default internal, otherwise the old authinstance can be blocked from deletion
    // by deleted users.
788
    $authinst = get_field('auth_instance', 'id', 'institution', 'mahara', 'authname', 'internal');
789
    if ($authinst) {
790
        $deleterec->authinstance = $authinst;
791
792
    }

793
794
    update_record('usr', $deleterec);

795
    // Remove user from any groups they're in, invited to or want to be in
796
797
798
799
800
801
802
    $groupids = get_column('group_member', '"group"', 'member', $userid);
    if ($groupids) {
        require_once(get_config('libroot') . 'group.php');
        foreach ($groupids as $groupid) {
            group_remove_user($groupid, $userid, true);
        }
    }
803
804
    delete_records('group_member_request', 'member', $userid);
    delete_records('group_member_invite', 'member', $userid);
805

806
807
808
809
810
811
812
813
    // Remove any friend relationships the user is in
    execute_sql('DELETE FROM {usr_friend}
        WHERE usr1 = ?
        OR usr2 = ?', array($userid, $userid));
    execute_sql('DELETE FROM {usr_friend_request}
        WHERE owner = ?
        OR requester = ?', array($userid, $userid));

814
    delete_records('artefact_access_usr', 'usr', $userid);
815
    delete_records('auth_remote_user', 'localusr', $userid);
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
    delete_records('import_queue', 'usr', $userid);
    delete_records('usr_account_preference', 'usr', $userid);
    delete_records('usr_activity_preference', 'usr', $userid);
    delete_records('usr_infectedupload', 'usr', $userid);
    delete_records('usr_institution', 'usr', $userid);
    delete_records('usr_institution_request', 'usr', $userid);
    delete_records('usr_password_request', 'usr', $userid);
    delete_records('usr_watchlist_view', 'usr', $userid);
    delete_records('view_access_usr', 'usr', $userid);

    // Remove the user's views & artefacts
    $viewids = get_column('view', 'id', 'owner', $userid);
    if ($viewids) {
        require_once(get_config('libroot') . 'view.php');
        foreach ($viewids as $viewid) {
            $view = new View($viewid);
            $view->delete();
        }
    }
    $artefactids = get_column('artefact', 'id', 'owner', $userid);
    if ($artefactids) {
        foreach ($artefactids as $artefactid) {
            try {
                $a = artefact_instance_from_id($artefactid);
                $a->delete();
            }
            catch (ArtefactNotFoundException $e) {
                // Awesome, it's already gone.
            }
        }
    }
847

848
849
    db_commit();

850
851
852
853
854
855
    handle_event('deleteuser', $userid);
}

/**
 * Undeletes a user
 *
856
857
858
859
860
861
 * NOTE: changing their email addresses to remove the .deleted.timestamp part 
 * has not been implemented yet! This function is not actually used anywhere in 
 * Mahara, so hasn't really been tested because of this. It's a simple enough 
 * job for the first person who gets there - see how delete_user works to see 
 * what you must undo.
 *
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
 * @param int $userid The ID of the user to undelete
 */
function undelete_user($userid) {
    $deleterec = new StdClass;
    $deleterec->id = $userid;
    $deleterec->deleted = 0;
    update_record('usr', $deleterec);

    handle_event('undeleteuser', $userid);
}

/**
 * Expires a user
 *
 * Nothing amazing needs to happen here, but this function is here for
 * consistency.
 *
 * This function is called when a user account is detected to be expired.
 * It is assumed that the account actually is expired.
 *
 * @param int $userid The ID of user to expire
 */
function expire_user($userid) {
    handle_event('expireuser', $userid);
}

/**
 * Unexpires a user
 *
 * @param int $userid The ID of user to unexpire
 */
function unexpire_user($userid) {
    handle_event('unexpireuser', $userid);
}

/**
 * Marks a user as inactive
 *
 * Nothing amazing needs to happen here, but this function is here for
 * consistency.
 *
 * This function is called when a user account is detected to be inactive.
 * It is assumed that the account actually is inactive.
 *
 * @param int $userid The ID of user to mark inactive
 */
function deactivate_user($userid) {
    handle_event('deactivateuser', $userid);
}

/**
 * Activates a user
 *
 * @param int $userid The ID of user to reactivate
 */
function activate_user($userid) {
    handle_event('activateuser', $userid);
}

921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
/**
 * Sends a message from one user to another
 *
 * @param object $to User to send the message to
 * @param string $message The message to send
 * @param object $from Who to send the message from. If not set, defaults to 
 * the currently logged in user
 * @throws AccessDeniedException if the message is not allowed to be sent (as 
 * configured by the 'to' user's settings)
 */
function send_user_message($to, $message, $from=null) {
    // FIXME: permission checking!
    if ($from === null) {
        global $USER;
        $from = $USER;
    }

    $messagepref = get_account_preference($to->id, 'messages');
939
    if ($messagepref == 'allow' || ($messagepref == 'friends' && is_friend($from->id, $to->id)) || $from->get('admin')) {
940
        require_once('activity.php');
941
942
943
944
945
946
947
948
949
950
951
952
        activity_occurred('usermessage', 
            array(
                'userto'   => $to->id, 
                'userfrom' => $from->id, 
                'message'  => $message,
            )
        );
    }
    else {
        throw new AccessDeniedException('Cannot send messages between ' . display_name($from) . ' and ' . display_name($to));
    }
}
953
954
955
/**
 * can a user send a message to another?
 *
956
957
 * @param int/object from the user to send the message
 * @param int/object to the user to receive the message
958
959
960
 * @return boolean whether userfrom is allowed to send messages to userto
 */
function can_send_message($from, $to) {
961
962
963
    if (empty($from)) {
        return false; // not logged in
    }
964
965
966
967
968
969
970
	if (!is_object($from)) {
	    $from = get_record('usr', 'id', $from);
	}
	if (is_object($to)) {
	    $to = $to->id;
	}
    if ($from->id == $to) {
971
972
        return false;
    }
973
    $messagepref = get_account_preference($to, 'messages');
974
    return (is_friend($from->id, $to) && $messagepref == 'friends') || $messagepref == 'allow' || $from->admin;
975
}
976
977

function load_user_institutions($userid) {
978
    if (!is_numeric($userid) || $userid < 0) {
979
980
        throw new InvalidArgumentException("couldn't load institutions, no user id specified");
    }
981
    if ($institutions = get_records_sql_assoc('
982
        SELECT u.institution,'.db_format_tsfield('ctime').','.db_format_tsfield('u.expiry', 'membership_expiry').',u.studentid,u.staff,u.admin,i.theme,i.registerallowed
983
984
        FROM {usr_institution} u INNER JOIN {institution} i ON u.institution = i.name
        WHERE u.usr = ?', array($userid))) {
985
986
987
988
989
        return $institutions;
    }
    return array();
}

990
991
992
993
994
995
996
997

/**
 * Return a username which isn't taken and which is similar to a desired username
 * 
 * @param string $desired
 */
function get_new_username($desired) {
    $maxlen = 30;
998
    $desired = strtolower(substr($desired, 0, $maxlen));
999
1000
    $taken = get_column_sql('
        SELECT username FROM {usr}
For faster browsing, not all history is shown. View entire blame