user.php 49.8 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
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 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
    $taken = get_column_sql('
        SELECT username FROM {usr}
        WHERE username ' . db_ilike() . " '" . substr($desired, 0, $maxlen - 6) . "%'");
    if (!$taken) {
        return $desired;
    }
    $taken = array_flip($taken);
    $i = '';
    $newname = substr($desired, 0, $maxlen - 1) . $i;
    while (isset($taken[$newname])) {
        $i++;
        $newname = substr($desired, 0, $maxlen - strlen($i)) . $i;
    }
    return $newname;
}

1015 1016
/**
 * used by user/myfriends.php and user/find.php to get the data (including pieforms etc) for display
1017 1018
 * @param $userlist the ids separated by commas
 * @return array containing the users in the order from $userlist
1019
 */
1020
function get_users_data($userlist, $getviews=true) {
1021 1022 1023 1024 1025 1026 1027
	global $USER;
    $sql = 'SELECT u.id, 0 AS pending,
                COALESCE((SELECT ap.value FROM {usr_account_preference} ap WHERE ap.usr = u.id AND ap.field = \'messages\'), \'allow\') AS messages,
                COALESCE((SELECT ap.value FROM {usr_account_preference} ap WHERE ap.usr = u.id AND ap.field = \'friendscontrol\'), \'auth\') AS friendscontrol,
                (SELECT 1 FROM {usr_friend} WHERE ((usr1 = ? AND usr2 = u.id) OR (usr2 = ? AND usr1 = u.id))) AS friend,
                (SELECT 1 FROM {usr_friend_request} fr WHERE fr.requester = ? AND fr.owner = u.id) AS requestedfriendship,
                (SELECT title FROM {artefact} WHERE artefacttype = \'introduction\' AND owner = u.id) AS introduction,
1028
                NULL AS message
1029
                FROM {usr} u
1030
                WHERE u.id IN (' . $userlist . ')
1031 1032 1033 1034 1035 1036 1037
            UNION
            SELECT u.id, 1 AS pending,
                COALESCE((SELECT ap.value FROM {usr_account_preference} ap WHERE ap.usr = u.id AND ap.field = \'messages\'), \'allow\') AS messages,
                NULL AS friendscontrol,
                NULL AS friend,
                NULL AS requestedfriendship,
                (SELECT title FROM {artefact} WHERE artefacttype = \'introduction\' AND owner = u.id) AS introduction,
1038
                message
1039 1040 1041
                FROM {usr} u
                JOIN {usr_friend_request} fr ON fr.requester = u.id
                WHERE fr.owner = ?
1042
                AND u.id IN (' . $userlist . ')';
1043 1044 1045 1046 1047
    $userid = $USER->get('id');
    $data = get_records_sql_assoc($sql, array($userid, $userid, $userid, $userid));

    foreach ($data as &$record) {
        if (isset($record->introduction)) {
1048
            $record->introduction = str_shorten_html($record->introduction, 100, true);
1049 1050 1051
        }

        $record->messages = ($record->messages == 'allow' || $record->friend && $record->messages == 'friends' || $USER->get('admin')) ? 1 : 0;
1052
        $record->institutions = get_institution_string_for_user($record->id);
1053 1054
    }

1055
    if (!$data || !$getviews || !$views = get_views(array_keys($data), null, null)) {
1056 1057 1058
        $views = array();
    }

1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
    if ($getviews) {
        $viewcount = array_map('count', $views);
        // since php is so special and inconsistent, we can't use array_map for this because it breaks the top level indexes.
        $cleanviews = array();
        foreach ($views as $userindex => $viewarray) {
            $cleanviews[$userindex] = array_slice($viewarray, 0, 5);

            // Don't reveal any more about the view than necessary
            foreach ($cleanviews as $userviews) {
                foreach ($userviews as &$view) {
                    foreach (array_keys(get_object_vars($view)) as $key) {
                        if ($key != 'id' && $key != 'title') {
                            unset($view->$key);
                        }
1073 1074 1075 1076 1077 1078 1079
                    }
                }
            }
        }
    }

    foreach ($data as $friend) {
1080
        if ($getviews && isset($cleanviews[$friend->id])) {
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102
            $friend->views = $cleanviews[$friend->id];
        }
        if ($friend->pending) {
            $friend->accept = pieform(array(
                'name' => 'acceptfriend' . $friend->id,
                'successcallback' => 'acceptfriend_submit',
                'renderer' => 'div',
                'autofocus' => 'false',
                'elements' => array(
                    'submit' => array(
                        'type' => 'submit',
                        'value' => get_string('approverequest', 'group')
                    ),
                    'id' => array(
                        'type' => 'hidden',
                        'value' => $friend->id
                    )
                )
            ));
        }
        if (!$friend->friend && !$friend->pending && !$friend->requestedfriendship && $friend->friendscontrol == 'auto') {
            $friend->makefriend = pieform(array(
1103 1104
                'name' => 'addfriend' . $friend->id,
                'successcallback' => 'addfriend_submit',
1105 1106 1107 1108 1109
                'renderer' => 'div',
                'autofocus' => 'false',
                'elements' => array(
                    'submit' => array(
                        'type' => 'submit',
1110
                        'value' => get_string('addtofriendslist', 'group'),
1111 1112 1113
                    ),
                    'id' => array(
                        'type' => 'hidden',
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133
                        'value' => $friend->id,
                    ),
                    // These two fields pass on any query that was running on a 
                    // user search screen. This is so when the form is 
                    // submitted, the correct user search is run again and so 
                    // this pieform will definitely be created and ready to be 
                    // submitted.
                    //
                    // A bit of a hack caused by having one form for each user. 
                    // It would be nice at some point to put the entire 'find 
                    // friends' page into one form and toggle on the submit 
                    // button to work out which friend to add.
                    'query' => array(
                        'type' => 'hidden',
                        'value' => param_variable('query', ''),
                    ),
                    'offset' => array(
                        'type' => 'hidden',
                        'value' => param_integer('offset', 0),
                    ),
1134 1135 1136 1137
                )
            ));
        }
    }
1138 1139 1140 1141 1142 1143 1144 1145
    $order = explode(',', $userlist);
    $ordereddata = array();
    foreach ($order as $id) {
        if (isset($data[$id])) {
            $ordereddata[] = $data[$id];
        }
    }
    return $ordereddata;
1146 1147
}

1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167
function build_userlist_html(&$data, $page) {
    if ($data['data']) {
        $userlist = join(',', array_map(create_function('$u','return $u[\'id\'];'), $data['data']));
        $userdata = get_users_data($userlist, $page == 'myfriends');
    }
    $smarty = smarty_core();
    $smarty->assign('data', isset($userdata) ? $userdata : null);
    $smarty->assign('page', $page);
    $smarty->assign('query', $data['query']);
    $data['tablerows'] = $smarty->fetch('user/userresults.tpl');
    $pagination = build_pagination(array(
        'id' => 'friendslist_pagination',
        'url' => get_config('wwwroot') . 'user/' . $page . '.php?query=' . $data['query'],
        'jsonscript' => 'json/friendsearch.php',
        'datatable' => 'friendslist',
        'count' => $data['count'],
        'limit' => $data['limit'],
        'offset' => $data['offset'],
        'resultcounttextsingular' => get_string('user', 'group'),
        'resultcounttextplural' => get_string('users', 'group'),
1168
        'extradata' => array('page' => $page),
1169 1170 1171 1172 1173
    ));
    $data['pagination'] = $pagination['html'];
    $data['pagination_js'] = $pagination['javascript'];
}

1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193
function get_institution_string_for_user($userid) {
    static $institutions = null;
    if (is_null($institutions)) {
        $institutions = get_records_assoc('institution', '', '', '', 'name, displayname');
    }

    $user = new User;
    $user->find_by_id($userid);

    $userinstitutions = array();
    foreach ($user->get('institutions') as $institution) {
        $userinstitutions[] = $institutions[$institution->institution]->displayname;
    }

    if ($userinstitutions) {
        return get_string('memberofinstitutions', 'mahara', join(', ', $userinstitutions));
    }
    return '';
}

1194
function friends_control_sideblock($returnto='myfriends') {
1195 1196 1197 1198 1199 1200