user.php 41.2 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
    public function join_institution($institution) {
615
        if ($institution != 'mahara' && !$this->in_institution($institution)) {
616
            require_once('institution.php');
617
618
            $institution = new Institution($institution);
            $institution->addUserAsMember($this);
619
            $this->reset_institutions();
620
621
622
        }
    }

623
624
625
626
    public function leave_institution($institution) {
        if ($institution != 'mahara' && $this->in_institution($institution)) {
            require_once('institution.php');
            $institution = new Institution($institution);
627
            $institution->removeMember($this->to_stdclass());
628
629
630
        }
    }

631
632
633
634
635
636
    public function in_institution($institution, $role = null) {
        $institutions = $this->get('institutions');
        return isset($institutions[$institution]) 
            && (is_null($role) || $institutions[$institution]->{$role});
    }

637
    public function is_institutional_admin($institution = null) {
638
        $a = $this->get('admininstitutions');
639
640
641
642
        if (is_null($institution)) {
            return !empty($a);
        }
        return isset($a[$institution]);
643
    }
644

645
646
647
648
649
650
651
652
    public function is_institutional_staff($institution = null) {
        $a = $this->get('staffinstitutions');
        if (is_null($institution)) {
            return !empty($a);
        }
        return isset($a[$institution]);
    }

653
654
655
656
    public function can_edit_institution($institution = null) {
        return $this->get('admin') || $this->is_institutional_admin($institution);
    }

657
658
659
660
661
662
663
664
    /**
     * 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
     */
665
666
667
668
    public function is_admin_for_user($user) {
        if ($this->get('admin')) {
            return true;
        }
669
670
671
        if (!$this->is_institutional_admin()) {
            return false;
        }
672
673

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

690
691
692
693
694
        if ($userobj->get('admin')) {
            return false;
        }

        foreach ($userobj->get('institutions') as $i) {
695
696
697
698
699
700
701
            if ($this->is_institutional_admin($i->institution)) {
                return true;
            }
        }
        return false;
    }

702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
    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;
    }

723
    public function add_institution_request($institution, $studentid = null) {
724
725
726
        if (empty($institution) || $institution == 'mahara') {
            return;
        }
727
728
        require_once('institution.php');
        $institution = new Institution($institution);
729
        $institution->addRequestFromUser($this, $studentid);
730
731
    }

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

761
762
763
764
765
766
767
768
769
770
771
    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);
    }

772
    public function can_view_artefact($a) {
773
        if ($this->get('admin')
774
            || ($this->get('id') and $this->get('id') == $a->get('owner'))
775
            || ($a->get('institution') and $this->is_institutional_admin($a->get('institution')))) {
776
777
778
779
780
781
            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
782
                WHERE ar.artefact = ? AND g.member = ? AND ar.can_view = 1 AND g.group = ?", array($a->get('id'), $this->get('id'), $a->get('group')))
783
784
785
                || record_exists('artefact_access_usr', 'usr', $this->get('id'), 'artefact', $a->get('id'));
        }
        return false;
786
787
    }

Richard Mansfield's avatar
Richard Mansfield committed
788
789
    public function can_edit_artefact($a) {
        if ($this->get('admin')
790
            || ($this->get('id') and $this->get('id') == $a->get('owner'))
791
            || ($a->get('institution') and $this->is_institutional_admin($a->get('institution')))) {
Richard Mansfield's avatar
Richard Mansfield committed
792
793
794
795
            return true;
        }
        $group = $a->get('group');
        if ($group) {
796
797
798
799
            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));
            /*
800
801
            require_once(get_config('docroot') . 'lib/group.php');
            $role = group_user_access($group, $this->get('id'));
Richard Mansfield's avatar
Richard Mansfield committed
802
803
804
            if ($role) {
                $aperms = $a->get('rolepermissions');
                return $aperms->{$role}->edit;
805
            } */
Richard Mansfield's avatar
Richard Mansfield committed
806
807
808
809
        }
        return false;
    }

810
811
812
813
814
    public function can_edit_view($v) {
        $owner = $v->get('owner');
        if ($owner == $this->get('id')) {
            return true;
        }
815
        $institution = $v->get('institution');
816
        if ($institution && $this->can_edit_institution($institution)) {
817
818
            return true;
        }
819
820
821
        $group = $v->get('group');
        if ($group) {
            $editroles = $v->get('editingroles');
822
            $this->reset_grouproles();
Richard Mansfield's avatar
Richard Mansfield committed
823
            return isset($this->grouproles[$group]) && in_array($this->grouproles[$group], $editroles);
824
825
826
827
        }
        return false;
    }

828
829
    public function can_delete_self() {
        if (!$this->get('admin')) {
830
831
832
833
834
835
836
837
            // 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;
838
839
840
841
842
        }
        // The last admin user should not be deleted.
        return count_records('usr', 'admin', 1, 'deleted', 0) > 1;
    }

843
844
845
846
847
    /**
     * Makes a literal copy of a list of views for this user.
     *
     * @param array $templateids A list of viewids to copy.
     */
848
    public function copy_views($templateids, $checkviewaccess=true) {
849
850
851
852
853
854
855
856
857
858
        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();
859
        foreach (get_records_select_array('view', 'id IN (' . implode(', ', db_array_to_ph($templateids)) . ')', $templateids, '', 'id, title, description, type') as $result) {
860
861
862
863
864
865
866
867
868
            $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,
869
                'type' => $views[$tid]->type == 'profile' && $checkviewaccess ? 'portfolio' : $views[$tid]->type,
870
            ), $tid, $this->get('id'), $checkviewaccess);
871
872
873
874
875
        }
        db_commit();
    }


876
877
878
}


Donal McMullan's avatar
Donal McMullan committed
879
880
class LiveUser extends User {

881
882
    protected $SESSION;

Donal McMullan's avatar
Donal McMullan committed
883
884
885
    public function __construct() {

        parent::__construct();
886
        $this->SESSION = Session::singleton();
Donal McMullan's avatar
Donal McMullan committed
887
888
889
890
891
892
893
894
895

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

896
    /**
897
     * Take a username and password and try to authenticate the
898
899
900
901
902
903
     * user
     *
     * @param  string $username
     * @param  string $password
     * @return bool
     */
904
    public function login($username, $password) {
905
906
        $sql = 'SELECT
                    *, 
907
908
909
910
                    ' . db_format_tsfield('expiry') . ',
                    ' . db_format_tsfield('lastlogin') . ',
                    ' . db_format_tsfield('lastlastlogin') . ',
                    ' . db_format_tsfield('lastaccess') . ',
Richard Mansfield's avatar
Richard Mansfield committed
911
912
                    ' . db_format_tsfield('suspendedctime') . ',
                    ' . db_format_tsfield('ctime') . '
913
914
915
916
917
                FROM
                    {usr}
                WHERE
                    LOWER(username) = ?';
        $user = get_record_sql($sql, array(strtolower($username)));
918

919
920
        if ($user == false) {
            throw new AuthUnknownUserException("\"$username\" is not known");
921
922
        }

923
924
925
        $siteclosedforupgrade = get_config('siteclosed');
        if ($siteclosedforupgrade && get_config('disablelogin')) {
            global $SESSION;
926
            $SESSION->add_error_msg(get_string('siteclosedlogindisabled', 'mahara', get_config('wwwroot') . 'admin/upgrade.php'), false);
927
928
929
930
931
932
933
934
            return false;
        }
        if (!$user->admin && ($siteclosedforupgrade || get_config('siteclosedbyadmin'))) {
            global $SESSION;
            $SESSION->add_error_msg(get_string('siteclosed'));
            return false;
        }

935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
        // 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);
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
        
        // 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;
                }
967

968
                return true;
969
            }
970
971
        }
        catch (AuthInstanceException $e) {
972
            return false;
973
        }
974
        
975
976
977
        // 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
978
979
            global $SESSION;
            $SESSION->add_info_msg(clean_html($auth->authloginmsg), false);
980
981
        }

982
983
984
        return false;
    }

985
986
987
988
989
990
991
992
993
994
995
    /**
     * 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());
        }
996

997
998
        // Unset session variables related to authentication
        $this->SESSION->set('authinstance', null);
999
1000
        if (get_config('installed') && !defined('INSTALLER') && $this->get('sessionid')
            && function_exists('table_exists') && table_exists('usr_session')) {
1001
1002
            delete_records('usr_session', 'session', $this->get('sessionid'));
        }
1003

1004
1005
1006
1007
1008
1009
1010
1011
1012
        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;
    }

    /**
1013
1014
     * Updates information in a users' session once we know their session is 
     * continuing
1015
1016
     */
    public function renew() {
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
        $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();
        }
1032
1033
    }

1034
1035
1036
1037
    /**
     * When a user creates a security context by whatever method, we do some 
     * standard stuff
     *
1038
1039
1040
     * @param  object $user          Record from the usr table
     * @param  integer $authinstance The ID of the authinstance that the user 
     *                               signed in with
1041
1042
     * @return void
     */
1043
    protected function authenticate($user, $authinstance) {
1044
        $this->authenticated  = true;
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054

        // 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);
        }

1055
1056
        $this->populate($user);
        session_regenerate_id(true);
1057
        $this->lastlastlogin      = $this->lastlogin;
Donal McMullan's avatar
Donal McMullan committed
1058
        $this->lastlogin          = time();
1059
        $this->lastaccess         = time();
1060
1061
1062
        $this->sessionid          = session_id();
        $this->logout_time        = time() + get_config('session_timeout');
        $this->sesskey            = get_random_key();
Donal McMullan's avatar
Donal McMullan committed
1063
1064
1065

        // We need a user->id before we load_c*_preferences
        if (empty($user->id)) $this->commit();
1066
1067
        $this->activityprefs      = load_activity_preferences($user->id);
        $this->accountprefs       = load_account_preferences($user->id);
1068
        $this->reset_institutions();
1069
        $this->reset_grouproles();
1070
        $this->load_views();
1071
        $this->store_sessionid();
1072

1073
        $this->commit();
1074
1075
1076
1077

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

1080
1081
1082
1083
    /**
     * When a user creates a security context by whatever method, we do some 
     * standard stuff
     *
1084
1085
1086
     * @param  int  $user       User ID
     * @param  int  $instanceid Auth Instance ID
     * @return bool             True if user with given ID exists
1087
     */
1088
    public function reanimate($id, $instanceid) {
1089
        if ($user = get_record('usr','id',$id)) {
1090
            $this->authenticate($user, $instanceid);
1091
1092
1093
1094
1095
            return true;
        }
        return false;
    }

Donal McMullan's avatar
Donal McMullan committed
1096
1097
1098
1099
1100
1101
1102
1103
1104
    /**
     * 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)) {
1105
            throw new InvalidArgumentException("Invalid key: $key");
Donal McMullan's avatar
Donal McMullan committed
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
        }
        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
1129

1130
1131
1132
1133
    protected function reloadLiveUser($id=null) {
        if (is_null($id)) {
            $id = $this->get('id');
        }
Richard Mansfield's avatar
Richard Mansfield committed
1134
1135
1136
        $this->find_by_id($id);
        $this->activityprefs = load_activity_preferences($id);
        $this->accountprefs = load_account_preferences($id);
1137
        $this->load_views();
Richard Mansfield's avatar
Richard Mansfield committed
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
    }

    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);
1169
        $this->set('loginanyway', false);
Richard Mansfield's avatar
Richard Mansfield committed
1170
1171
1172
1173

        return $id;
    }

1174
1175
1176
1177
1178
1179
    public function leave_institution($institution) {
        parent::leave_institution($institution);
        $this->find_by_id($this->get('id'));
        $this->reset_institutions();
    }

1180
1181
1182
1183
    public function update_theme() {
        $this->reset_institutions();
        $this->commit();
    }
1184
1185
1186
1187
1188
1189
1190
1191

    public function reset_institutions() {
        global $THEME;
        parent::reset_institutions();
        if (isset($THEME->basename) && $this->theme != $THEME->basename && !defined('INSTALLER')) {
            $THEME = new Theme($this->theme);
        }
    }
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202

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

Donal McMullan's avatar
Donal McMullan committed
1203
}
Donal McMullan's avatar
Donal McMullan committed
1204
?>