user.php 42 KB
Newer Older
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
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.
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.
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/>.
19
20
21
 *
 * @package    mahara
 * @subpackage core
22
 * @author     Catalyst IT Ltd
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
24
 * @copyright  (C) 2006-2009 Catalyst IT Ltd http://catalyst.net.nz
25
26
27
28
 *
 */

defined('INTERNAL') || die();
Donal McMullan's avatar
Donal McMullan committed
29
30
$put = array();

31
32

/**
Donal McMullan's avatar
Donal McMullan committed
33
 * The user class represents any user in the system.
34
35
36
 *
 */
class User {
Donal McMullan's avatar
Donal McMullan committed
37

38
39
40
41
42
    /**
     * Defaults for user information.
     *
     * @var array
     */
Donal McMullan's avatar
Donal McMullan committed
43
44
45
46
47
    protected $defaults;
    protected $stdclass;
    protected $authenticated = false;
    protected $changed       = false;
    protected $attributes    = array();
48
49
50
51
52

    /**
     * Sets defaults for the user object (only because PHP5 does not appear
     * to support private static const arrays), and resumes a session
     */
Donal McMullan's avatar
Donal McMullan committed
53
    public function __construct() {
54
        $this->defaults = array(
55
56
57
58
            'logout_time'      => 0,
            'id'               => 0,
            'username'         => '',
            'password'         => '',
Donal McMullan's avatar
Donal McMullan committed
59
            'salt'             => '',
Donal McMullan's avatar
Donal McMullan committed
60
            'passwordchange'   => 0,
Donal McMullan's avatar
Donal McMullan committed
61
            'active'           => 1,
Donal McMullan's avatar
Donal McMullan committed
62
            'deleted'          => 0,
63
            'expiry'           => null,
64
            'expirymailsent'   => 0,
65
            'lastlogin'        => null,
66
            'lastlastlogin'    => null,
67
            'lastaccess'       => null, /* Is not necessarily updated every request, see accesstimeupdatefrequency config variable */
68
            'inactivemailsent' => 0,
Donal McMullan's avatar
Donal McMullan committed
69
70
            'staff'            => 0,
            'admin'            => 0,
71
72
            'firstname'        => '',
            'lastname'         => '',
Donal McMullan's avatar
Donal McMullan committed
73
            'studentid'        => '',
74
75
            'preferredname'    => '',
            'email'            => '',
Donal McMullan's avatar
Donal McMullan committed
76
77
78
79
            'profileicon'      => null,
            'suspendedctime'   => null,
            'suspendedreason'  => null,
            'suspendedcusr'    => null,
80
            'quota'            => null,
Donal McMullan's avatar
Donal McMullan committed
81
82
83
            'quotaused'        => 0,
            'authinstance'     => 1,
            'sessionid'        => '', /* The real session ID that PHP knows about */
84
85
            'accountprefs'     => array(),
            'activityprefs'    => array(),
86
            'institutions'     => array(),
87
            'grouproles'       => array(),
88
            'theme'            => null,
89
            'admininstitutions' => array(),
90
            'staffinstitutions' => array(),
Richard Mansfield's avatar
Richard Mansfield committed
91
            'parentuser'       => null,
92
            'loginanyway'       => false,
93
            'sesskey'          => '',
Richard Mansfield's avatar
Richard Mansfield committed
94
            'ctime'            => null,
95
            'views'            => array(),
96
            'showhomeinfo'     => 1,
97
        );
Donal McMullan's avatar
Donal McMullan committed
98
99
100
101
102
103
104
        $this->attributes = array();

    }

    /**
     * 
     */
Donal McMullan's avatar
Donal McMullan committed
105
    public function find_by_id($id) {
106

Donal McMullan's avatar
Donal McMullan committed
107
108
109
        if (!is_numeric($id) || $id < 0) {
            throw new InvalidArgumentException('parameter must be a positive integer to create a User object');
        }
Donal McMullan's avatar
Donal McMullan committed
110

111
112
113
114
        $sql = 'SELECT
                    *, 
                    ' . db_format_tsfield('expiry') . ', 
                    ' . db_format_tsfield('lastlogin') . ', 
115
116
                    ' . db_format_tsfield('lastlastlogin') . ',
                    ' . db_format_tsfield('lastaccess') . ',
Richard Mansfield's avatar
Richard Mansfield committed
117
118
                    ' . db_format_tsfield('suspendedctime') . ',
                    ' . db_format_tsfield('ctime') . '
119
                FROM
120
                    {usr}
121
122
123
                WHERE
                    id = ?';

124
        $user = get_record_sql($sql, array($id));
125

Donal McMullan's avatar
Donal McMullan committed
126
127
128
129
        if (false == $user) {
            throw new AuthUnknownUserException("User with id \"$id\" is not known");
        }

Donal McMullan's avatar
Donal McMullan committed
130
        $this->populate($user);
131
        $this->reset_institutions();
132
        $this->reset_grouproles();
Donal McMullan's avatar
Donal McMullan committed
133
134
        return $this;
    }
135

136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
    /**
     * Populates this object with the user record identified by the given 
     * username
     *
     * @throws AuthUnknownUserException If the user cannot be found. Note that 
     *                                  deleted users _can_ be found
     */
    public function find_by_username($username) {

        if (!is_string($username)) {
            throw new InvalidArgumentException('username parameter must be a string to create a User object');
        }

        $sql = 'SELECT
                    *,
                    ' . db_format_tsfield('expiry') . ',
                    ' . db_format_tsfield('lastlogin') . ',
153
                    ' . db_format_tsfield('lastlastlogin') . ',
154
                    ' . db_format_tsfield('lastaccess') . ',
Richard Mansfield's avatar
Richard Mansfield committed
155
156
                    ' . db_format_tsfield('suspendedctime') . ',
                    ' . db_format_tsfield('ctime') . '
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
                FROM
                    {usr}
                WHERE
                    username = ?';

        $user = get_record_sql($sql, $username);

        if (false == $user) {
            throw new AuthUnknownUserException("User with username \"$username\" is not known");
        }

        $this->populate($user);
        $this->reset_institutions();
        return $this;
    }

173
    /**
174
175
176
177
178
     * Finds details for a user given a username and their authentication 
     * instance.
     *
     * If the authentication instance is a child or a parent, its relation is 
     * checked too, because the user can enter the system by either method.
179
     */
180
    public function find_by_instanceid_username($instanceid, $username, $remoteuser=false) {
181
182
183
184
185

        if (!is_numeric($instanceid) || $instanceid < 0) {
            throw new InvalidArgumentException('parameter must be a positive integer to create a User object');
        }

186
        if ($remoteuser) {
187
188
189
190
191
192
193
            // See if the user has either the child or the parent authinstance. 
            // Most of the time, it's the parent auth instance that is 
            // stored with the user, but if they were created by (for 
            // example) SSO with no parent, then it will be the child that 
            // is stored. Nevertheless, a parent could be added later, and 
            // that should not matter in finding the user
            $parentwhere = '';
194
            if ($parentid = get_field('auth_instance_config', 'value', 'field', 'parent', 'instance', $instanceid)) {
195
196
197
                $parentwhere = '
                            OR
                            (
198
199
200
201
202
203
204
205
206
207
208
209
210
                                LOWER(username) = (
                                    SELECT
                                        username
                                    FROM
                                        {usr} us
                                    JOIN
                                        {auth_remote_user} aru ON (us.id = aru.localusr)
                                    WHERE
                                        aru.remoteusername = ' . db_quote($username) . '
                                        AND us.authinstance = ' . db_quote($parentid) . '
                                )
                                AND
                                u.authinstance = ' . db_quote($parentid) . '
211
212
                            )
                    ';
213
            }
214

215
216
            $sql = 'SELECT
                        u.*, 
217
218
219
220
                        ' . db_format_tsfield('u.expiry', 'expiry') . ',
                        ' . db_format_tsfield('u.lastlogin', 'lastlogin') . ',
                        ' . db_format_tsfield('u.lastlastlogin', 'lastlastlogin') . ',
                        ' . db_format_tsfield('u.lastaccess', 'lastaccess') . ',
221
                        ' . db_format_tsfield('u.suspendedctime', 'suspendedctime') . ',
Richard Mansfield's avatar
Richard Mansfield committed
222
                        ' . db_format_tsfield('u.ctime', 'ctime') . '
223
                    FROM {usr} u
224
                    LEFT JOIN {auth_remote_user} r ON u.id = r.localusr
225
                    WHERE
226
227
                        (
                            (
228
                                r.remoteusername = ?
229
230
231
232
                                AND r.authinstance = ?
                            )'
                            . $parentwhere
                            . '
233
                        )';
234
235
236
            $user = get_record_sql($sql, array($username, $instanceid));
        }
        else {
237
238
            $sql = 'SELECT
                        *, 
239
240
241
242
                        ' . db_format_tsfield('expiry') . ',
                        ' . db_format_tsfield('lastlogin') . ',
                        ' . db_format_tsfield('lastlastlogin') . ',
                        ' . db_format_tsfield('lastaccess') . ',
Richard Mansfield's avatar
Richard Mansfield committed
243
244
                        ' . db_format_tsfield('suspendedctime') . ',
                        ' . db_format_tsfield('ctime') . '
245
246
247
248
                    FROM
                        {usr}
                    WHERE
                        LOWER(username) = ? AND
249
250
                        authinstance = ?';
            $user = get_record_sql($sql, array($username, $instanceid));
251
252
        }

253
254
255
256
257
258
259
260
        if (false == $user) {
            throw new AuthUnknownUserException("User with username \"$username\" is not known at auth instance \"$instanceid\"");
        }

        $this->populate($user);
        return $this;
    }

Richard Mansfield's avatar
Richard Mansfield committed
261
262
263
264
265
266
267
268
    /**
     * Set stuff that needs to be initialised once before a user record is created.
     */
    public function create() {
        $this->set('ctime', time());
    }


Donal McMullan's avatar
Donal McMullan committed
269
270
271
272
273
274
275
276
277
278
279
280
281
    /**
     * Take a row object from the usr table and populate this object with the
     * values
     *
     * @param  object $data  The row data
     */
    protected function populate($data) {
        reset($this->defaults);
        while(list($key, ) = each($this->defaults)) {
            if (property_exists($data, $key)) {
                $this->set($key, $data->{$key});
            }
        }
282
    }
Donal McMullan's avatar
Donal McMullan committed
283

284
285
286
287
288
    /**
     * Gets the user property keyed by $key.
     *
     * @param string $key The key to get the value of
     * @return mixed
Nigel McNie's avatar
Nigel McNie committed
289
     * @throws InvalidArgumentException
290
291
     */
    public function get($key) {
Donal McMullan's avatar
Donal McMullan committed
292
        if (!array_key_exists($key, $this->defaults)) {
293
            throw new InvalidArgumentException($key);
294
        }
Donal McMullan's avatar
Donal McMullan committed
295
296
        if (array_key_exists($key, $this->attributes) && null !== $this->attributes[$key]) {
            return $this->attributes[$key];
297
298
299
300
        }
        return $this->defaults[$key];
    }

Donal McMullan's avatar
Donal McMullan committed
301
302
303
304
305
306
307
308
309
310
311
    /**
     * Gets the user property keyed by $key.
     *
     * @param string $key The key to get the value of
     * @return mixed
     * @throws InvalidArgumentException
     */
    public function __get($key) {
        return $this->get($key);
    }

312
313
314
    /**
     * Sets the property keyed by $key
     */
Donal McMullan's avatar
Donal McMullan committed
315
316
317
    protected function set($key, $value) {

        if (!array_key_exists($key, $this->defaults)) {
318
            throw new InvalidArgumentException($key);
319
        }
Donal McMullan's avatar
Donal McMullan committed
320
321
322
323

        $this->attributes[$key] = $value;

        // For now, these fields are saved to the DB elsewhere
324
        if ($key != 'activityprefs' && $key != 'accountprefs' && $key != 'views') {
Donal McMullan's avatar
Donal McMullan committed
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
            $this->changed = true;
        }
        return $this;
    }

    /**
     * Sets the property keyed by $key
     */
    public function __set($key, $value) {
        if ($key == 'quotaused') {
            throw new InvalidArgumentException('quotaused should be set via the quota_* methods');
        }

        $this->set($key, $value);
    }

    /**
     * Commit the USR record to the database
     */
    public function commit() {
        if ($this->changed == false) {
            return;
        }
Donal McMullan's avatar
Donal McMullan committed
348
        $record = $this->to_stdclass();
Donal McMullan's avatar
Donal McMullan committed
349
        if (is_numeric($this->id) && 0 < $this->id) {
Donal McMullan's avatar
Donal McMullan committed
350
351
352
353
354
355
            try {
                update_record('usr', $record, array('id' => $this->id));
            } catch (Exception $e) {
                throw $e;
                //var_dump($e);
            }
Donal McMullan's avatar
Donal McMullan committed
356
        } else {
Donal McMullan's avatar
Donal McMullan committed
357
358
            try {
                $this->set('id', insert_record('usr', $record, 'id', true));
Donal McMullan's avatar
Donal McMullan committed
359
360
            } catch (SQLException $e) {
                throw $e;
Donal McMullan's avatar
Donal McMullan committed
361
            }
Donal McMullan's avatar
Donal McMullan committed
362
363
        }
        $this->changed = false;
364
365
366
367
368
369
    }

    /** 
     * This function returns a method for a particular
     * activity type, or null if it's not set.
     * 
370
     * @param int $key the activity type id
371
372
373
     */
    public function get_activity_preference($key) {
        $activityprefs = $this->get('activityprefs');
374
        return isset($activityprefs[$key]) ? $activityprefs[$key] : null;
375
    }
Donal McMullan's avatar
Donal McMullan committed
376

377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
    /** @todo document this method */
    public function set_activity_preference($activity, $method) {
        set_activity_preference($this->get('id'), $activity, $method);
        $activityprefs = $this->get('activityprefs');
        $activityprefs[$activity] = $method;
        $this->set('activityprefs', $activityprefs);
    }

    /** 
     * This function returns a value for a particular
     * account preference, or null if it's not set.
     * 
     * @param string $key the field name
     */
    public function get_account_preference($key) {
        $accountprefs = $this->get('accountprefs');
        return isset($accountprefs[$key]) ? $accountprefs[$key] : null;
    }
Donal McMullan's avatar
Donal McMullan committed
395

396
397
398
399
400
401
    /** @todo document this method */
    public function set_account_preference($field, $value) {
        set_account_preference($this->get('id'), $field, $value);
        $accountprefs = $this->get('accountprefs');
        $accountprefs[$field] = $value;
        $this->set('accountprefs', $accountprefs);
402
403
    }

404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422

    public function get_view_by_type($viewtype) {
        $views = $this->get('views');
        if (isset($views[$viewtype])) {
            $viewid = $views[$viewtype];
        }
        else {
            $viewid = get_field('view', 'id', 'type', $viewtype, 'owner', $this->get('id'));
        }
        if (!$viewid) {
            global $USER;
            if (!$USER->get('id')) {
                return null;
            }
            return $this->install_view($viewtype);
        }
        return new View($viewid);
    }

423
424
425
426
427
428
429
430
    /**
     * Return the profile view object for this user.
     *
     * If the user does not yet have a profile view, one is created for them.
     *
     * @return View
     */
    public function get_profile_view() {
431
        return $this->get_view_by_type('profile');
432
433
434
435
436
437
438
    }

    /**
     * Installs a user's profile view.
     *
     * @return View
     */
439
    protected function install_profile_view() {
440
441
        static $systemprofileviewid = null;

442
        db_begin();
443
444
445
446
447
448
449
450
        if (is_null($systemprofileviewid)) {
            $systemprofileviewid = get_field('view', 'id', 'owner', 0, 'type', 'profile');
        }

        require_once(get_config('libroot') . 'view.php');
        list($view) = View::create_from_template(array(
            'owner' => $this->get('id'),
            'title' => get_field('view', 'title', 'id', $systemprofileviewid),
451
            'description' => get_string('profiledescription'),
452
453
            'type'  => 'profile',
        ), $systemprofileviewid, $this->get('id'));
454
455
456
457
458
459
460

        // Add about me block
        $aboutme = new BlockInstance(0, array(
            'blocktype'  => 'profileinfo',
            'title'      => get_string('aboutme', 'blocktype.internal/profileinfo'),
            'view'       => $view->get('id'),
            'column'     => 1,
461
            'order'      => 1,
462
463
464
465
466
467
        ));
        $configdata = array('artefactids' => array());
        if ($intro = get_field('artefact', 'id', 'owner', $this->get('id'), 'artefacttype', 'introduction')) {
            $configdata['artefactids'][] = $intro;
        }
        else {
468
            $configdata['introtext'] = get_string('thisistheprofilepagefor', 'mahara', display_name($this, null, true));
469
470
471
472
473
        }
        if ($this->get('profileicon')) {
            $configdata['profileicon'] = $this->get('profileicon');
        }
        $aboutme->set('configdata', $configdata);
474
        $view->addblockinstance($aboutme);
475

476
477
478
479
480
481
482
        $view->set_access(array(
            array(
                'type'      => 'loggedin',
                'startdate' => null,
                'stopdate'  => null,
            ),
        ));
483
        db_commit();
484
485
486
487

        return $view;
    }

488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
    /**
     * Return the dashboard view object for this user.
     *
     * If the user does not yet have a dashboard view, one is created for them.
     *
     * @return View
     */

    /**
     * Installs a user's dashboard view.
     *
     * @return View
     */
    protected function install_dashboard_view() {
        static $systemdashboardviewid = null;

        db_begin();
        if (is_null($systemdashboardviewid)) {
            $systemdashboardviewid = get_field('view', 'id', 'owner', 0, 'type', 'dashboard');
        }

        require_once(get_config('libroot') . 'view.php');
        list($view) = View::create_from_template(array(
            'owner' => $this->get('id'),
            'title' => get_field('view', 'title', 'id', $systemdashboardviewid),
513
            'description' => get_string('dashboarddescription'),
514
515
516
517
518
519
520
            'type'  => 'dashboard',
        ), $systemdashboardviewid, $this->get('id'));

        db_commit();

        return $view;
    }
521

522
523
524
525
    protected function install_view($viewtype) {
        $function = 'install_' . $viewtype . '_view';
        return $this->$function();
    }
526

527
528
529
530
531
532
    // Store the ids of the user's special views (profile, dashboard).  Users can have only
    // one each of these, so there really should be columns in the user table to store them.
    protected function load_views() {
        $types = array('profile', 'dashboard');
        $views = get_records_select_assoc(
            'view',
Francois Marier's avatar
Francois Marier committed
533
            '"owner" = ? AND type IN (' . join(',', array_map('db_quote', $types)) . ')',
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
            array($this->id),
            '',
            'type,id'
        );

        $specialviews = array();
        foreach ($types as $type) {
            if (!empty($views[$type])) {
                $specialviews[$type] = $views[$type]->id;
            }
            else {
                $view = $this->install_view($type);
                $specialviews[$type] = $view->get('id');
            }
        }
        $this->set('views', $specialviews);
    }
551

552
553
554
555
556
557
558
559
    /**
     * Determines if the user is currently logged in
     *
     * @return boolean
     */
    public function is_logged_in() {
        return ($this->get('logout_time') > 0 ? true : false);
    }
Donal McMullan's avatar
Donal McMullan committed
560

561
    public function to_stdclass() {
Donal McMullan's avatar
Donal McMullan committed
562
563
564
        $this->stdclass = new StdClass;
        reset($this->defaults);
        foreach (array_keys($this->defaults) as $k) {
Richard Mansfield's avatar
Richard Mansfield committed
565
            if ($k == 'expiry' || $k == 'lastlogin' || $k == 'lastlastlogin' || $k == 'lastaccess' || $k == 'suspendedctime' || $k == 'ctime') {
Donal McMullan's avatar
Donal McMullan committed
566
567
568
569
                $this->stdclass->{$k} = db_format_timestamp($this->get($k));
            } else {
                $this->stdclass->{$k} = $this->get($k);//(is_null($this->get($k))? 'NULL' : $this->get($k));
            }
570
571
572
        }
        return $this->stdclass;
    }
573

Martyn Smith's avatar
Martyn Smith committed
574
575
576
577
578
579
580
581
    public function quota_add($bytes) {
        if (!is_numeric($bytes) || $bytes < 0) {
            throw new InvalidArgumentException('parameter must be a positive integer to add to the quota');
        }
        if (!$this->quota_allowed($bytes)) {
            throw new QuotaExceededException('Adding ' . $bytes . ' bytes would exceed the user\'s quota');
        }
        $newquota = $this->get('quotaused') + $bytes;
Donal McMullan's avatar
Donal McMullan committed
582
583
        $this->set("quotaused", $newquota);
        return $this;
Martyn Smith's avatar
Martyn Smith committed
584
585
586
587
588
589
590
591
592
593
    }

    public function quota_remove($bytes) {
        if (!is_numeric($bytes) || $bytes < 0) {
            throw new InvalidArgumentException('parameter must be a positive integer to remove from the quota');
        }
        $newquota = $this->get('quotaused') - $bytes;
        if ($newquota < 0) {
            $newquota = 0;
        }
Donal McMullan's avatar
Donal McMullan committed
594
595
        $this->set("quotaused", $newquota);
        return $this;
Martyn Smith's avatar
Martyn Smith committed
596
597
598
599
600
601
602
603
604
    }

    public function quota_allowed($bytes) {
        if ($this->get('quotaused') + $bytes > $this->get('quota')) {
            return false;
        }

        return true;
    }
605

606
607
608
609
610
611
612
613
    public function quota_init() {
        if (!$this->get('quota')) {
            if ($defaultquota = get_config_plugin('artefact', 'file', 'defaultquota')) {
                $this->set('quota', $defaultquota);
            }
        }
    }

614
615
616
617
618
619
    public function quota_refresh() {
        $quotadata = get_record_sql('SELECT quota, quotaused FROM {usr} WHERE id = ?', array($this->get('id')));
        $this->set('quota', $quotadata->quota);
        $this->set("quotaused", $quotadata->quotaused);
    }

620
    public function join_institution($institution) {
621
        if ($institution != 'mahara' && !$this->in_institution($institution)) {
622
            require_once('institution.php');
623
624
            $institution = new Institution($institution);
            $institution->addUserAsMember($this);
625
            $this->reset_institutions();
626
627
628
        }
    }

629
630
631
632
    public function leave_institution($institution) {
        if ($institution != 'mahara' && $this->in_institution($institution)) {
            require_once('institution.php');
            $institution = new Institution($institution);
633
            $institution->removeMember($this->to_stdclass());
634
635
636
        }
    }

637
638
639
640
641
642
    public function in_institution($institution, $role = null) {
        $institutions = $this->get('institutions');
        return isset($institutions[$institution]) 
            && (is_null($role) || $institutions[$institution]->{$role});
    }

643
    public function is_institutional_admin($institution = null) {
644
        $a = $this->get('admininstitutions');
645
646
647
648
        if (is_null($institution)) {
            return !empty($a);
        }
        return isset($a[$institution]);
649
    }
650

651
652
653
654
655
656
657
658
    public function is_institutional_staff($institution = null) {
        $a = $this->get('staffinstitutions');
        if (is_null($institution)) {
            return !empty($a);
        }
        return isset($a[$institution]);
    }

659
660
661
662
    public function can_edit_institution($institution = null) {
        return $this->get('admin') || $this->is_institutional_admin($institution);
    }

663
664
665
666
667
668
669
670
    /**
     * Returns whether this user is allowed to perform administration type
     * actions on another user.
     *
     * @param mixed $user The user to check we can perform actions on. Can
     *                    either be a User object, a row from the usr table or
     *                    an ID
     */
671
672
673
674
    public function is_admin_for_user($user) {
        if ($this->get('admin')) {
            return true;
        }
675
676
677
        if (!$this->is_institutional_admin()) {
            return false;
        }
678
679

        // Check privileges for institutional admins now
680
        if ($user instanceof User) {
681
            $userobj = $user;
682
683
        }
        else if (is_numeric($user)) {
684
685
            $userobj = new User;
            $userobj->find_by_id($user);
686
687
688
        }
        else if (is_object($user)) {
            // Should be a row from the usr table
689
690
            $userobj = new User;
            $userobj->find_by_id($user->id);
691
        }
692
693
694
695
        else {
            throw new SystemException("Invalid argument pass to is_admin_for_user method");
        }

696
697
698
699
700
        if ($userobj->get('admin')) {
            return false;
        }

        foreach ($userobj->get('institutions') as $i) {
701
702
703
704
705
706
707
            if ($this->is_institutional_admin($i->institution)) {
                return true;
            }
        }
        return false;
    }

708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
    public function is_staff_for_user($user) {
        if ($this->get('admin') || $this->get('staff')) {
            return true;
        }
        if (!$this->is_institutional_admin() && !$this->is_institutional_staff()) {
            return false;
        }
        if ($user instanceof User) {
            $userinstitutions = $user->get('institutions');
        } else {
            $userinstitutions = load_user_institutions($user->id);
        }
        foreach ($userinstitutions as $i) {
            if ($this->is_institutional_admin($i->institution)
                || $this->is_institutional_staff($i->institution)) {
                return true;
            }
        }
        return false;
    }

729
    public function add_institution_request($institution, $studentid = null) {
730
731
732
        if (empty($institution) || $institution == 'mahara') {
            return;
        }
733
734
        require_once('institution.php');
        $institution = new Institution($institution);
735
        $institution->addRequestFromUser($this, $studentid);
736
737
    }

738
    public function reset_institutions() {
739
740
        $institutions             = load_user_institutions($this->id);
        $admininstitutions = array();
741
        $staffinstitutions = array();
742
        $this->theme = get_config('theme');
743
744
745
746
        foreach ($institutions as $i) {
            if ($i->admin) {
                $admininstitutions[$i->institution] = $i->institution;
            }
747
748
749
            if ($i->staff) {
                $staffinstitutions[$i->institution] = $i->institution;
            }
750
751
            if (!empty($i->theme) && $i->theme != get_config('theme')) {
                $this->theme = $i->theme;
752
            }
753
        }
754
755
756
757
758
759
760
761
        if ($this->authinstance) {
            $authobj = AuthFactory::create($this->authinstance);
            if (isset($institutions[$authobj->institution])) {
                if ($t = $institutions[$authobj->institution]->theme) {
                    $this->theme = $t;
                }
            }
        }
762
763
        $this->institutions       = $institutions;
        $this->admininstitutions  = $admininstitutions;
764
        $this->staffinstitutions  = $staffinstitutions;
765
766
    }

767
768
769
770
771
772
773
774
775
776
777
    public function reset_grouproles() {
        $memberships = get_records_array('group_member', 'member', $this->get('id'));
        $roles = array();
        if ($memberships) {
            foreach ($memberships as $m) {
                $roles[$m->group] = $m->role;
            }
        }
        $this->set('grouproles', $roles);
    }

778
    public function can_view_artefact($a) {
779
        if ($this->get('admin')
780
            || ($this->get('id') and $this->get('id') == $a->get('owner'))
781
            || ($a->get('institution') and $this->is_institutional_admin($a->get('institution')))) {
782
783
784
785
786
787
            return true;
        }
        if ($a->get('group')) {
            // Only group artefacts can have artefact_access_role & artefact_access_usr records
            return (bool) count_records_sql("SELECT COUNT(*) FROM {artefact_access_role} ar
                INNER JOIN {group_member} g ON ar.role = g.role
788
                WHERE ar.artefact = ? AND g.member = ? AND ar.can_view = 1 AND g.group = ?", array($a->get('id'), $this->get('id'), $a->get('group')))
789
790
791
                || record_exists('artefact_access_usr', 'usr', $this->get('id'), 'artefact', $a->get('id'));
        }
        return false;
792
793
    }

Richard Mansfield's avatar
Richard Mansfield committed
794
795
    public function can_edit_artefact($a) {
        if ($this->get('admin')
796
            || ($this->get('id') and $this->get('id') == $a->get('owner'))
797
            || ($a->get('institution') and $this->is_institutional_admin($a->get('institution')))) {
Richard Mansfield's avatar
Richard Mansfield committed
798
799
800
801
            return true;
        }
        $group = $a->get('group');
        if ($group) {
802
803
804
805
            return count_records_sql("SELECT COUNT(*) FROM {artefact_access_role} ar
                INNER JOIN {group_member} g ON ar.role = g.role
                WHERE ar.artefact = ? AND g.member = ? AND ar.can_edit = 1 AND g.group = ?", array($a->get('id'), $this->get('id'), $group));
            /*
806
807
            require_once(get_config('docroot') . 'lib/group.php');
            $role = group_user_access($group, $this->get('id'));
Richard Mansfield's avatar
Richard Mansfield committed
808
809
810
            if ($role) {
                $aperms = $a->get('rolepermissions');
                return $aperms->{$role}->edit;
811
            } */
Richard Mansfield's avatar
Richard Mansfield committed
812
813
814
815
        }
        return false;
    }

816
817
818
819
820
    public function can_edit_view($v) {
        $owner = $v->get('owner');
        if ($owner == $this->get('id')) {
            return true;
        }
821
        $institution = $v->get('institution');
822
        if ($institution && $this->can_edit_institution($institution)) {
823
824
            return true;
        }
825
826
827
        $group = $v->get('group');
        if ($group) {
            $editroles = $v->get('editingroles');
828
            $this->reset_grouproles();
829
830
831
            if ($v->get('type') == 'grouphomepage' && $this->grouproles[$group] != 'admin') {
                return false;
            }
Richard Mansfield's avatar
Richard Mansfield committed
832
            return isset($this->grouproles[$group]) && in_array($this->grouproles[$group], $editroles);
833
834
835
836
        }
        return false;
    }

837
838
839
840
841
842
843
    /**
     * Function to check current user can edit collection
     *
     * This is fairly straightforward at the moment but it might require more
     * if groups are allowed collections and other amendments in the future
     */
    public function can_edit_collection($c) {
844
845
        $owner = $c->get('owner');
        if ($owner == $this->get('id')) {
846
847
848
849
850
            return true;
        }
        return false;
    }

851
852
    public function can_delete_self() {
        if (!$this->get('admin')) {
853
854
855
856
857
858
859
860
            // Users who belong to an institution that doesn't allow
            // registration cannot delete themselves.
            foreach ($this->get('institutions') as $i) {
                if (!$i->registerallowed) {
                    return false;
                }
            }
            return true;
861
862
863
864
865
        }
        // The last admin user should not be deleted.
        return count_records('usr', 'admin', 1, 'deleted', 0) > 1;
    }

866
867
868
869
870
    /**
     * Makes a literal copy of a list of views for this user.
     *
     * @param array $templateids A list of viewids to copy.
     */
871
    public function copy_views($templateids, $checkviewaccess=true) {
872
873
874
875
876
877
878
879
880
881
        if (!$templateids) {
            // Nothing to do
            return;
        }
        if (!is_array($templateids)) {
            throw new SystemException('User->copy_views: templateids must be a list of templates to copy for the user');
        }
        require_once(get_config('libroot') . 'view.php');

        $views = array();
882
        foreach (get_records_select_array('view', 'id IN (' . implode(', ', db_array_to_ph($templateids)) . ')', $templateids, '', 'id, title, description, type') as $result) {
883
884
885
886
887
888
889
890
891
            $views[$result->id] = $result;
        }

        db_begin();
        foreach ($templateids as $tid) {
            View::create_from_template(array(
                'owner' => $this->get('id'),
                'title' => $views[$tid]->title,
                'description' => $views[$tid]->description,
892
                'type' => $views[$tid]->type == 'profile' && $checkviewaccess ? 'portfolio' : $views[$tid]->type,
893
            ), $tid, $this->get('id'), $checkviewaccess);
894
895
896
897
898
        }
        db_commit();
    }


899
900
901
}


Donal McMullan's avatar
Donal McMullan committed
902
903
class LiveUser extends User {

904
905
    protected $SESSION;

Donal McMullan's avatar
Donal McMullan committed
906
907
908
    public function __construct() {

        parent::__construct();
909
        $this->SESSION = Session::singleton();
Donal McMullan's avatar
Donal McMullan committed
910
911
912
913
914
915
916
917
918

        if ($this->SESSION->is_live()) {
            $this->authenticated  = true;
            while(list($key,) = each($this->defaults)) {
                $this->get($key);
            }
        }
    }

919
    /**
920
     * Take a username and password and try to authenticate the
921
922
923
924
925
926
     * user
     *
     * @param  string $username
     * @param  string $password
     * @return bool
     */
927
    public function login($username, $password) {
928
929
        $sql = 'SELECT
                    *, 
930
931
932
933
                    ' . db_format_tsfield('expiry') . ',
                    ' . db_format_tsfield('lastlogin') . ',
                    ' . db_format_tsfield('lastlastlogin') . ',
                    ' . db_format_tsfield('lastaccess') . ',
Richard Mansfield's avatar
Richard Mansfield committed
934
935
                    ' . db_format_tsfield('suspendedctime') . ',
                    ' . db_format_tsfield('ctime') . '
936
937
938
939
940
                FROM
                    {usr}
                WHERE
                    LOWER(username) = ?';
        $user = get_record_sql($sql, array(strtolower($username)));
941

942
943
        if ($user == false) {
            throw new AuthUnknownUserException("\"$username\" is not known");
944
945
        }

946
947
948
        $siteclosedforupgrade = get_config('siteclosed');
        if ($siteclosedforupgrade && get_config('disablelogin')) {
            global $SESSION;
949
            $SESSION->add_error_msg(get_string('siteclosedlogindisabled', 'mahara', get_config('wwwroot') . 'admin/upgrade.php'), false);
950
951
952
953
954
955
956
957
            return false;
        }
        if (!$user->admin && ($siteclosedforupgrade || get_config('siteclosedbyadmin'))) {
            global $SESSION;
            $SESSION->add_error_msg(get_string('siteclosed'));
            return false;
        }

958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
        // Authentication instances that have parents do so because they cannot 
        // use Mahara's normal login mechanism - for example, XMLRPC. If the 
        // user is using one of these authentication instances, we look and try 
        // to use the parent.
        //
        // There's no code here that prevents the authinstance being tried if 
        // it has no parent, mainly because that's an extra database lookup for 
        // the general case, and the authentication will probably just fail 
        // anyway. (XMLRPC, for example, leaves implementation of 
        // authenticate_user_account to the parent Auth class, which says 'not 
        // authorised' by default).
        $instanceid = $user->authinstance;
        if ($parentid = get_field('auth_instance_config', 'value', 'field', 'parent', 'instance', $instanceid)) {
            $instanceid = $parentid;
        }
        $auth = AuthFactory::create($instanceid);
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
        
        // catch the AuthInstanceException that allows authentication plugins to
        // fail but pass onto the next possible plugin
        try {
            if ($auth->authenticate_user_account($user, $password)) {
                $this->authenticate($user, $auth->instanceid);
                // Check for a suspended institution
                $authinstance = get_record_sql('
                    SELECT i.suspended, i.displayname
                    FROM {institution} i JOIN {auth_instance} a ON a.institution = i.name
                    WHERE a.id = ?', array($instanceid));
                if ($authinstance->suspended) {
                    $sitename = get_config('sitename');
                    throw new AccessTotallyDeniedException(get_string('accesstotallydenied_institutionsuspended', 'mahara', $authinstance->displayname, $sitename));
                    return false;
                }
990

991
                return true;
992
            }
993
994
        }
        catch (AuthInstanceException $e) {
995
            return false;
996
        }
997
        
998
999
1000
        // Display a message to users who are only allowed to login via their
        // external application.
        if ($auth->authloginmsg != '') {
Elliot Pahl's avatar
Elliot Pahl committed
1001
1002
            global $SESSION;
            $SESSION->add_info_msg(clean_html($auth->authloginmsg), false);
1003
1004
        }

1005
1006
1007
        return false;
    }

1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
    /**
     * Logs the current user out
     */
    public function logout () {
        if ($this->changed == true) {
            log_debug('Destroying user with un-committed changes');
        }
        $this->set('logout_time', 0);
        if ($this->authenticated === true) {
            $this->SESSION->set('messages', array());
        }
1019

1020
1021
        // Unset session variables related to authentication
        $this->SESSION->set('authinstance', null);
1022
1023
        if (get_config('installed') && !defined('INSTALLER') && $this->get('sessionid')
            && function_exists('table_exists') && table_exists('usr_session')) {
1024
1025
            delete_records('usr_session', 'session', $this->get('sessionid'));
        }
1026

1027
1028
1029
1030
1031
1032
1033
1034
1035
        reset($this->defaults);
        foreach (array_keys($this->defaults) as $key) {
            $this->set($key, $this->defaults[$key]);
        }
        // We don't want to commit the USER object after logout:
        $this->changed = false;
    }

    /**
1036
1037
     * Updates information in a users' session once we know their session is 
     * continuing
1038
1039
     */
    public function renew() {
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
        $time = time();
        $this->set('logout_time', $time + get_config('session_timeout'));
        $oldlastaccess = $this->get('lastaccess');
        // If there is an access time update frequency, we use a cookie to 
        // prevent updating before this time has expired.
        // If it is set to zero, we always update the accesstime.
        $accesstimeupdatefrequency = get_config('accesstimeupdatefrequency');
        if ($accesstimeupdatefrequency == 0) {
            $this->set('lastaccess', $time);
            $this->commit();
        }
        else if ($oldlastaccess + $accesstimeupdatefrequency < $time) {
            $this->set('lastaccess', $time);
            $this->commit();
        }
1055
1056
    }

1057
1058
1059
1060
    /**
     * When a user creates a security context by whatever method, we do some 
     * standard stuff
     *
1061
1062
1063
     * @param  object $user          Record from the usr table
     * @param  integer $authinstance The ID of the authinstance that the user 
     *                               signed in with
1064
1065
     * @return void
     */
1066
    protected function authenticate($user, $authinstance) {
1067
        $this->authenticated  = true;
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077

        // If the user has reauthenticated and they were an MNET user, we 
        // don't set these variables, because we wish to remember that they 
        // originally SSO-ed in from their other authinstance. See the 
        // session timeout code in auth_setup() for more info.
        if ($this->SESSION->get('mnetuser') != $user->id) {
            $this->SESSION->set('mnetuser', null);
            $this->SESSION->set('authinstance', $authinstance);
        }

1078
1079
        $this->populate($user);
        session_regenerate_id(true);
1080
        $this->lastlastlogin      = $this->lastlogin;
Donal McMullan's avatar
Donal McMullan committed
1081
        $this->lastlogin          = time();
1082
        $this->lastaccess         = time();
1083
1084
1085
        $this->sessionid          = session_id();
        $this->logout_time        = time() + get_config('session_timeout');
        $this->sesskey            = get_random_key();
Donal McMullan's avatar
Donal McMullan committed
1086
1087
1088

        // We need a user->id before we load_c*_preferences
        if (empty($user->id)) $this->commit();
1089
1090
        $this->activityprefs      = load_activity_preferences($user->id);
        $this->accountprefs       = load_account_preferences($user->id);
1091
        $this->reset_institutions();
1092
        $this->reset_grouproles();
1093
        $this->load_views();
1094
        $this->store_sessionid();
1095

1096
        $this->commit();
1097
1098
1099
1100

        // finally, after all is done, call the (maybe non existant) hook on their auth plugin
        $authobj = AuthFactory::create($authinstance);
        $authobj->login();
1101
1102
    }

1103
1104
1105
1106
    /**
     * When a user creates a security context by whatever method, we do some 
     * standard stuff
     *
1107
1108
1109
     * @param  int  $user       User ID
     * @param  int  $instanceid Auth Instance ID
     * @return bool             True if user with given ID exists
1110
     */
1111
    public function reanimate($id, $instanceid) {
1112
        if ($user = get_record('usr','id',$id)) {
1113
            $this->authenticate($user, $instanceid);
1114
1115
1116
1117
1118
            return true;
        }
        return false;
    }

Donal McMullan's avatar
Donal McMullan committed
1119
1120
1121
1122
1123
1124
1125
1126
1127
    /**
     * Gets the user property keyed by $key.
     *
     * @param string $key The key to get the value of
     * @return mixed
     * @throws InvalidArgumentException
     */
    public function get($key) {
        if (!array_key_exists($key, $this->defaults)) {
1128
            throw new InvalidArgumentException("Invalid key: $key");
Donal McMullan's avatar
Donal McMullan committed
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
        }
        if (null !== ($value = $this->SESSION->get("user/$key"))) {
            return $value;
        }
        return $this->defaults[$key];
    }

    /**
     * Sets the property keyed by $key
     */
    protected function set($key, $value) {

        if (!array_key_exists($key, $this->defaults)) {
            throw new InvalidArgumentException($key);
        }

        // For now, these fields are saved to the DB elsewhere
        if ($key != 'activityprefs' && $key !=  'accountprefs') {
            $this->changed = true;
        }
        $this->SESSION->set("user/$key", $value);
        return $this;
    }
Richard Mansfield's avatar
Richard Mansfield committed
1152

1153
1154
1155
1156
    protected function reloadLiveUser($id=null) {
        if (is_null($id)) {
            $id = $this->get('id');
        }
Richard Mansfield's avatar
Richard Mansfield committed
1157
1158
1159
        $this->find_by_id($id);
        $this->activityprefs = load_activity_preferences($id);
        $this->accountprefs = load_account_preferences($id);
1160
        $this->load_views();
Richard Mansfield's avatar
Richard Mansfield committed
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
    }

    public function change_identity_to($userid) {
        $user = new User;
        $user->find_by_id($userid);
        if (!$this->is_admin_for_user($user)) {
            throw new AccessDeniedException(get_string('loginasdenied', 'admin'));
        }
        $olduser = $this->get('parentuser');
        if (!is_null($olduser)) {
            throw new UserException(get_string('loginastwice', 'admin'));
        }

        $olduser = new StdClass;
        $olduser->id = $this->get('id');
        $olduser->name = $this->firstname . ' ' . $this->lastname;

        $this->reloadLiveUser($userid);

        $this->set('parentuser', $olduser);
    }

    public function restore_identity() {
        $id = $this->get('id');
        $olduser = $this->get('parentuser');
        if (empty($olduser) || empty($olduser->id)) {
            throw new UserException(get_string('loginasrestorenodata', 'admin'));
        }

        $this->reloadLiveUser($olduser->id);
        $this->set('parentuser', null);
1192
        $this->set('loginanyway', false);
Richard Mansfield's avatar
Richard Mansfield committed
1193
1194
1195
1196

        return $id;
    }

1197
1198
1199
1200
1201
1202
    public function leave_institution($institution) {
        parent::leave_institution($institution);
        $this->find_by_id($this->get('id'));
        $this->reset_institutions();
    }

1203
1204
1205
1206
    public function update_theme() {
        $this->reset_institutions();
        $this->commit();
    }
1207
1208
1209
1210
1211
1212
1213
1214

    public function reset_institutions() {
        global $THEME;
        parent::reset_institutions();
        if (isset($THEME->basename) && $this->theme != $THEME->basename && !defined('INSTALLER')) {
            $THEME = new Theme($this->theme);
        }
    }
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225

    private function store_sessionid() {
        $sessionid = $this->get('sessionid');
        delete_records('usr_session', 'session', $sessionid);
        insert_record('usr_session', (object) array(
            'usr'