user.php 45.7 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
        $username = strtolower($username);
187
        if ($remoteuser) {
188
189
190
191
192
193
194
            // 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 = '';
195
            if ($parentid = get_field('auth_instance_config', 'value', 'field', 'parent', 'instance', $instanceid)) {
196
197
198
                $parentwhere = '
                            OR
                            (
199
200
201
202
203
204
205
206
                                LOWER(username) = (
                                    SELECT
                                        username
                                    FROM
                                        {usr} us
                                    JOIN
                                        {auth_remote_user} aru ON (us.id = aru.localusr)
                                    WHERE
207
                                        LOWER(aru.remoteusername) = ' . db_quote($username) . '
208
209
210
211
                                        AND us.authinstance = ' . db_quote($parentid) . '
                                )
                                AND
                                u.authinstance = ' . db_quote($parentid) . '
212
213
                            )
                    ';
214
            }
215

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

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

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

262
    /**
263
     * Populates this object with the user record identified by a mobile 'token'
264
     *
265
     * @throws AuthUnknownUserException If the user cannot be found. 
266
     */
267
    public function find_by_mobileuploadtoken($token, $username) {
268
269
270
271
272
273

        if (!is_string($token)) {
            throw new InvalidArgumentException('Input parameters must be strings to create a User object from token');
        }

        $sql = 'SELECT
274
                        u.*,
275
276
277
278
279
280
281
282
283
                        ' . db_format_tsfield('u.expiry', 'expiry') . ',
                        ' . db_format_tsfield('u.lastlogin', 'lastlogin') . ',
                        ' . db_format_tsfield('u.lastlastlogin', 'lastlastlogin') . ',
                        ' . db_format_tsfield('u.lastaccess', 'lastaccess') . ',
                        ' . db_format_tsfield('u.suspendedctime', 'suspendedctime') . ',
                        ' . db_format_tsfield('u.ctime', 'ctime') . '
                FROM
                    {usr} u
                    LEFT JOIN {usr_account_preference} p ON u.id = p.usr
284
                            WHERE p.field=\'mobileuploadtoken\' AND p.value = ? AND u.username = ?
285
286
		';

287
        $user = get_record_sql($sql, array($token, $username));
288
289
290
291
292
293
294
295
296

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

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

297
298
299
300
301
302
303
304
305
306
307
    /**
     * Refreshes a users mobile 'token' and returns it
     *
     */
    public function refresh_mobileuploadtoken() {
	$new_token = md5( uniqid() );
        $this->set_account_preference('mobileuploadtoken', $new_token);
        $this->set('lastaccess', time());
	$this->commit();
	return $new_token;
    }
308

Richard Mansfield's avatar
Richard Mansfield committed
309
310
311
312
313
314
315
316
    /**
     * 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
317
318
319
320
321
322
323
324
325
326
327
328
329
    /**
     * 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});
            }
        }
330
    }
Donal McMullan's avatar
Donal McMullan committed
331

332
333
334
335
336
    /**
     * 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
337
     * @throws InvalidArgumentException
338
339
     */
    public function get($key) {
Donal McMullan's avatar
Donal McMullan committed
340
        if (!array_key_exists($key, $this->defaults)) {
341
            throw new InvalidArgumentException($key);
342
        }
Donal McMullan's avatar
Donal McMullan committed
343
344
        if (array_key_exists($key, $this->attributes) && null !== $this->attributes[$key]) {
            return $this->attributes[$key];
345
346
347
348
        }
        return $this->defaults[$key];
    }

Donal McMullan's avatar
Donal McMullan committed
349
350
351
352
353
354
355
356
357
358
359
    /**
     * 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);
    }

360
361
362
    /**
     * Sets the property keyed by $key
     */
Donal McMullan's avatar
Donal McMullan committed
363
364
365
    protected function set($key, $value) {

        if (!array_key_exists($key, $this->defaults)) {
366
            throw new InvalidArgumentException($key);
367
        }
Donal McMullan's avatar
Donal McMullan committed
368
369
370
371

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

        // For now, these fields are saved to the DB elsewhere
372
        if ($key != 'activityprefs' && $key != 'accountprefs' && $key != 'views') {
Donal McMullan's avatar
Donal McMullan committed
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
            $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
396
        $record = $this->to_stdclass();
Donal McMullan's avatar
Donal McMullan committed
397
        if (is_numeric($this->id) && 0 < $this->id) {
Donal McMullan's avatar
Donal McMullan committed
398
399
400
401
402
403
            try {
                update_record('usr', $record, array('id' => $this->id));
            } catch (Exception $e) {
                throw $e;
                //var_dump($e);
            }
Donal McMullan's avatar
Donal McMullan committed
404
        } else {
Donal McMullan's avatar
Donal McMullan committed
405
406
            try {
                $this->set('id', insert_record('usr', $record, 'id', true));
Donal McMullan's avatar
Donal McMullan committed
407
408
            } catch (SQLException $e) {
                throw $e;
Donal McMullan's avatar
Donal McMullan committed
409
            }
Donal McMullan's avatar
Donal McMullan committed
410
411
        }
        $this->changed = false;
412
413
414
415
416
417
    }

    /** 
     * This function returns a method for a particular
     * activity type, or null if it's not set.
     * 
418
     * @param int $key the activity type id
419
420
421
     */
    public function get_activity_preference($key) {
        $activityprefs = $this->get('activityprefs');
422
        return isset($activityprefs[$key]) ? $activityprefs[$key] : null;
423
    }
Donal McMullan's avatar
Donal McMullan committed
424

425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
    /** @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
443

444
445
446
447
448
449
    /** @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);
450
451
    }

452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470

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

471
472
473
474
475
476
477
478
    /**
     * 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() {
479
        return $this->get_view_by_type('profile');
480
481
482
483
484
485
486
    }

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

490
        db_begin();
491
492
493
494
495
496
497
498
        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),
499
            'description' => get_string('profiledescription'),
500
501
            'type'  => 'profile',
        ), $systemprofileviewid, $this->get('id'));
502
503
504
505
506
507
508

        // Add about me block
        $aboutme = new BlockInstance(0, array(
            'blocktype'  => 'profileinfo',
            'title'      => get_string('aboutme', 'blocktype.internal/profileinfo'),
            'view'       => $view->get('id'),
            'column'     => 1,
509
            'order'      => 1,
510
511
512
513
514
515
        ));
        $configdata = array('artefactids' => array());
        if ($intro = get_field('artefact', 'id', 'owner', $this->get('id'), 'artefacttype', 'introduction')) {
            $configdata['artefactids'][] = $intro;
        }
        else {
516
            $configdata['introtext'] = get_string('thisistheprofilepagefor', 'mahara', display_name($this, null, true));
517
518
519
520
521
        }
        if ($this->get('profileicon')) {
            $configdata['profileicon'] = $this->get('profileicon');
        }
        $aboutme->set('configdata', $configdata);
522
        $view->addblockinstance($aboutme);
523

524
525
526
527
528
529
530
        $view->set_access(array(
            array(
                'type'      => 'loggedin',
                'startdate' => null,
                'stopdate'  => null,
            ),
        ));
531
        db_commit();
532
533
534
535

        return $view;
    }

536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
    /**
     * 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),
561
            'description' => get_string('dashboarddescription'),
562
563
564
565
566
567
568
            'type'  => 'dashboard',
        ), $systemdashboardviewid, $this->get('id'));

        db_commit();

        return $view;
    }
569

570
571
572
573
    protected function install_view($viewtype) {
        $function = 'install_' . $viewtype . '_view';
        return $this->$function();
    }
574

575
576
577
578
579
580
    // 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
581
            '"owner" = ? AND type IN (' . join(',', array_map('db_quote', $types)) . ')',
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
            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);
    }
599

600
601
602
603
604
605
606
607
    /**
     * 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
608

609
    public function to_stdclass() {
Donal McMullan's avatar
Donal McMullan committed
610
611
612
        $this->stdclass = new StdClass;
        reset($this->defaults);
        foreach (array_keys($this->defaults) as $k) {
Richard Mansfield's avatar
Richard Mansfield committed
613
            if ($k == 'expiry' || $k == 'lastlogin' || $k == 'lastlastlogin' || $k == 'lastaccess' || $k == 'suspendedctime' || $k == 'ctime') {
Donal McMullan's avatar
Donal McMullan committed
614
615
616
617
                $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));
            }
618
619
620
        }
        return $this->stdclass;
    }
621

Martyn Smith's avatar
Martyn Smith committed
622
623
624
625
626
627
628
629
    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
630
631
        $this->set("quotaused", $newquota);
        return $this;
Martyn Smith's avatar
Martyn Smith committed
632
633
634
635
636
637
638
639
640
641
    }

    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
642
643
        $this->set("quotaused", $newquota);
        return $this;
Martyn Smith's avatar
Martyn Smith committed
644
645
646
647
648
649
650
651
652
    }

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

        return true;
    }
653

654
655
656
657
658
659
660
661
    public function quota_init() {
        if (!$this->get('quota')) {
            if ($defaultquota = get_config_plugin('artefact', 'file', 'defaultquota')) {
                $this->set('quota', $defaultquota);
            }
        }
    }

662
663
664
665
666
667
    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);
    }

668
    public function join_institution($institution) {
669
        if ($institution != 'mahara' && !$this->in_institution($institution)) {
670
            require_once('institution.php');
671
672
            $institution = new Institution($institution);
            $institution->addUserAsMember($this);
673
            $this->reset_institutions();
674
675
676
        }
    }

677
678
679
680
    public function leave_institution($institution) {
        if ($institution != 'mahara' && $this->in_institution($institution)) {
            require_once('institution.php');
            $institution = new Institution($institution);
681
            $institution->removeMember($this->to_stdclass());
682
683
684
        }
    }

685
686
687
688
689
690
    public function in_institution($institution, $role = null) {
        $institutions = $this->get('institutions');
        return isset($institutions[$institution]) 
            && (is_null($role) || $institutions[$institution]->{$role});
    }

691
    public function is_institutional_admin($institution = null) {
692
        $a = $this->get('admininstitutions');
693
694
695
696
        if (is_null($institution)) {
            return !empty($a);
        }
        return isset($a[$institution]);
697
    }
698

699
700
701
702
703
704
705
706
    public function is_institutional_staff($institution = null) {
        $a = $this->get('staffinstitutions');
        if (is_null($institution)) {
            return !empty($a);
        }
        return isset($a[$institution]);
    }

707
708
709
710
    public function can_edit_institution($institution = null) {
        return $this->get('admin') || $this->is_institutional_admin($institution);
    }

711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
    public function institution_allows_public_views($institution = null) {
        $user_institutions = $this->get('institutions');
        if (empty($user_institutions)) {
            // user belongs to no institutions
            return true;
        }
        else if (is_null($institution) || !isset($user_institutions[$institution->institution])) {
            foreach ($user_institutions as $institution) {
                if ($institution->allowinstitutionpublicviews == 1) {
                    return true;
                }
            }
            return false;
        }
        return $user_institutions[$institution->institution]->allowinstitutionpublicviews == 1;
    }

728
729
730
731
732
733
734
735
    /**
     * 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
     */
736
737
738
739
    public function is_admin_for_user($user) {
        if ($this->get('admin')) {
            return true;
        }
740
741
742
        if (!$this->is_institutional_admin()) {
            return false;
        }
743
744

        // Check privileges for institutional admins now
745
        if ($user instanceof User) {
746
            $userobj = $user;
747
748
        }
        else if (is_numeric($user)) {
749
750
            $userobj = new User;
            $userobj->find_by_id($user);
751
752
753
        }
        else if (is_object($user)) {
            // Should be a row from the usr table
754
755
            $userobj = new User;
            $userobj->find_by_id($user->id);
756
        }
757
758
759
760
        else {
            throw new SystemException("Invalid argument pass to is_admin_for_user method");
        }

761
762
763
764
765
        if ($userobj->get('admin')) {
            return false;
        }

        foreach ($userobj->get('institutions') as $i) {
766
767
768
769
770
771
772
            if ($this->is_institutional_admin($i->institution)) {
                return true;
            }
        }
        return false;
    }

773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
    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;
    }

794
    public function add_institution_request($institution, $studentid = null) {
795
796
797
        if (empty($institution) || $institution == 'mahara') {
            return;
        }
798
799
        require_once('institution.php');
        $institution = new Institution($institution);
800
        $institution->addRequestFromUser($this, $studentid);
801
802
    }

803
    public function reset_institutions() {
804
805
        $institutions             = load_user_institutions($this->id);
        $admininstitutions = array();
806
        $staffinstitutions = array();
807
        $this->theme = get_config('theme');
808
809
810
811
        foreach ($institutions as $i) {
            if ($i->admin) {
                $admininstitutions[$i->institution] = $i->institution;
            }
812
813
814
            if ($i->staff) {
                $staffinstitutions[$i->institution] = $i->institution;
            }
815
            if (!empty($i->theme) && $this->theme == get_config('theme') && $i->theme != $this->theme) {
816
                $this->theme = $i->theme;
817
            }
818
        }
819
820
821
822
823
824
825
826
        if ($this->authinstance) {
            $authobj = AuthFactory::create($this->authinstance);
            if (isset($institutions[$authobj->institution])) {
                if ($t = $institutions[$authobj->institution]->theme) {
                    $this->theme = $t;
                }
            }
        }
827
828
        $this->institutions       = $institutions;
        $this->admininstitutions  = $admininstitutions;
829
        $this->staffinstitutions  = $staffinstitutions;
830
831
    }

832
833
834
835
836
837
838
839
840
841
842
    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);
    }

843
    public function can_view_artefact($a) {
844
        if ($this->get('admin')
845
            || ($this->get('id') and $this->get('id') == $a->get('owner'))
846
            || ($a->get('institution') and $this->is_institutional_admin($a->get('institution')))) {
847
848
849
850
851
852
            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
853
                WHERE ar.artefact = ? AND g.member = ? AND ar.can_view = 1 AND g.group = ?", array($a->get('id'), $this->get('id'), $a->get('group')))
854
855
856
                || record_exists('artefact_access_usr', 'usr', $this->get('id'), 'artefact', $a->get('id'));
        }
        return false;
857
858
    }

Richard Mansfield's avatar
Richard Mansfield committed
859
860
    public function can_edit_artefact($a) {
        if ($this->get('admin')
861
            || ($this->get('id') and $this->get('id') == $a->get('owner'))
862
            || ($a->get('institution') and $this->is_institutional_admin($a->get('institution')))) {
Richard Mansfield's avatar
Richard Mansfield committed
863
864
865
866
            return true;
        }
        $group = $a->get('group');
        if ($group) {
867
868
869
870
            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));
            /*
871
872
            require_once(get_config('docroot') . 'lib/group.php');
            $role = group_user_access($group, $this->get('id'));
Richard Mansfield's avatar
Richard Mansfield committed
873
874
875
            if ($role) {
                $aperms = $a->get('rolepermissions');
                return $aperms->{$role}->edit;
876
            } */
Richard Mansfield's avatar
Richard Mansfield committed
877
878
879
880
        }
        return false;
    }

881
882
883
884
885
886
887
888
889
890
891
892
893
894
    public function can_publish_artefact($a) {
        if (($this->get('id') and $this->get('id') == $a->get('owner'))
            || ($a->get('institution') and $this->is_institutional_admin($a->get('institution')))) {
            return true;
        }
        $group = $a->get('group');
        if ($group) {
            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_publish = 1 AND g.group = ?", array($a->get('id'), $this->get('id'), $group));
        }
        return false;
    }

895
896
    public function can_edit_view($v) {
        $owner = $v->get('owner');
897
        if ($owner > 0 && $owner == $this->get('id')) {
898
899
            return true;
        }
900
        $institution = $v->get('institution');
901
        if ($institution && $this->can_edit_institution($institution)) {
902
903
            return true;
        }
904
905
906
        $group = $v->get('group');
        if ($group) {
            $editroles = $v->get('editingroles');
907
            $this->reset_grouproles();
908
909
910
            if ($v->get('type') == 'grouphomepage' && $this->grouproles[$group] != 'admin') {
                return false;
            }
Richard Mansfield's avatar
Richard Mansfield committed
911
            return isset($this->grouproles[$group]) && in_array($this->grouproles[$group], $editroles);
912
913
914
915
        }
        return false;
    }

916
917
918
919
920
921
922
    /**
     * 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) {
923
924
        $owner = $c->get('owner');
        if ($owner == $this->get('id')) {
925
926
927
928
929
            return true;
        }
        return false;
    }

930
931
    public function can_delete_self() {
        if (!$this->get('admin')) {
932
933
934
935
936
937
938
939
            // 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;
940
941
942
943
944
        }
        // The last admin user should not be deleted.
        return count_records('usr', 'admin', 1, 'deleted', 0) > 1;
    }

945
946
947
948
949
    /**
     * Makes a literal copy of a list of views for this user.
     *
     * @param array $templateids A list of viewids to copy.
     */
950
    public function copy_views($templateids, $checkviewaccess=true) {
951
952
953
954
955
956
957
958
959
960
        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();
961
        foreach (get_records_select_array('view', 'id IN (' . implode(', ', db_array_to_ph($templateids)) . ')', $templateids, '', 'id, title, description, type') as $result) {
962
963
964
965
966
967
968
969
970
            $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,
971
                'type' => $views[$tid]->type == 'profile' && $checkviewaccess ? 'portfolio' : $views[$tid]->type,
972
            ), $tid, $this->get('id'), $checkviewaccess);
973
974
975
976
977
        }
        db_commit();
    }


978
979
980
}


Donal McMullan's avatar
Donal McMullan committed
981
982
class LiveUser extends User {

983
984
    protected $SESSION;

Donal McMullan's avatar
Donal McMullan committed
985
986
987
    public function __construct() {

        parent::__construct();
988
        $this->SESSION = Session::singleton();
Donal McMullan's avatar
Donal McMullan committed
989
990
991
992
993
994
995
996
997

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

998
    /**
999
     * Take a username and password and try to authenticate the
1000
1001
1002
1003
1004
1005
     * user
     *
     * @param  string $username
     * @param  string $password
     * @return bool
     */
1006
    public function login($username, $password) {
1007
1008
        $sql = 'SELECT
                    *, 
1009
1010
1011
1012
                    ' . db_format_tsfield('expiry') . ',
                    ' . db_format_tsfield('lastlogin') . ',
                    ' . db_format_tsfield('lastlastlogin') . ',
                    ' . db_format_tsfield('lastaccess') . ',
Richard Mansfield's avatar
Richard Mansfield committed
1013
1014
                    ' . db_format_tsfield('suspendedctime') . ',
                    ' . db_format_tsfield('ctime') . '
1015
1016
1017
1018
1019
                FROM
                    {usr}
                WHERE
                    LOWER(username) = ?';
        $user = get_record_sql($sql, array(strtolower($username)));
1020

1021
1022
        if ($user == false) {
            throw new AuthUnknownUserException("\"$username\" is not known");
1023
1024
        }

1025
1026
1027
        $siteclosedforupgrade = get_config('siteclosed');
        if ($siteclosedforupgrade && get_config('disablelogin')) {
            global $SESSION;
1028
            $SESSION->add_error_msg(get_string('siteclosedlogindisabled', 'mahara', get_config('wwwroot') . 'admin/upgrade.php'), false);
1029
1030
1031
1032
1033
1034
1035
1036
            return false;
        }
        if (!$user->admin && ($siteclosedforupgrade || get_config('siteclosedbyadmin'))) {
            global $SESSION;
            $SESSION->add_error_msg(get_string('siteclosed'));
            return false;
        }

1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
        // 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);
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
        
        // 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;
                }
1069

1070
                return true;
1071
            }
1072
1073
        }
        catch (AuthInstanceException $e) {
1074
            return false;
1075
        }
1076
        
1077
1078
1079
        // 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
1080
1081
            global $SESSION;
            $SESSION->add_info_msg(clean_html($auth->authloginmsg), false);
1082
1083
        }

1084
1085
1086
        return false;
    }

1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
    /**
     * 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());
        }
1098

1099
1100
        // Unset session variables related to authentication
        $this->SESSION->set('authinstance', null);
1101
1102
        if (get_config('installed') && !defined('INSTALLER') && $this->get('sessionid')
            && function_exists('table_exists') && table_exists('usr_session')) {
1103
1104
            delete_records('usr_session', 'session', $this->get('sessionid'));
        }
1105

1106
1107
1108
1109
1110
1111
1112
1113
1114
        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;
    }

    /**
1115
1116
     * Updates information in a users' session once we know their session is 
     * continuing
1117
1118
     */
    public function renew() {
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
        $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();
        }
1134
1135
    }

1136
1137
1138
1139
    /**
     * When a user creates a security context by whatever method, we do some 
     * standard stuff
     *
1140
1141
1142
     * @param  object $user          Record from the usr table
     * @param  integer $authinstance The ID of the authinstance that the user 
     *                               signed in with
1143
1144
     * @return void
     */
1145
    protected function authenticate($user, $authinstance) {
1146
        $this->authenticated  = true;
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156

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

1157
1158
        $this->populate($user);
        session_regenerate_id(true);
1159
        $this->lastlastlogin      = $this->lastlogin;
Donal McMullan's avatar
Donal McMullan committed
1160
        $this->lastlogin          = time();
1161
        $this->lastaccess         = time();
1162
1163
1164
        $this->sessionid          = session_id();
        $this->logout_time        = time() + get_config('session_timeout');
        $this->sesskey            = get_random_key();
Donal McMullan's avatar
Donal McMullan committed
1165
1166
1167

        // We need a user->id before we load_c*_preferences
        if (empty($user->id)) $this->commit();
1168
1169
        $this->activityprefs      = load_activity_preferences($user->id);
        $this->accountprefs       = load_account_preferences($user->id);
1170

1171
1172
1173
1174
1175
1176
1177
        // If user has chosen a language while logged out, save it as their lang pref.
        $sessionlang = $this->SESSION->get('lang');
        if (!empty($sessionlang) && $sessionlang != 'default'
            && (empty($this->accountprefs['lang']) || $sessionlang != $this->accountprefs['lang'])) {
            $this->set_account_preference('lang', $sessionlang);
        }

1178
1179
1180
1181
1182
        // Set language for the current request
        if (!empty($this->accountprefs['lang'])) {
            current_language($this->accountprefs['lang']);
        }

1183
        $this->reset_institutions();
1184
        $this->reset_grouproles();
1185
        $this->load_views();
1186
        $this->store_sessionid();
1187

1188
        $this->commit();
1189
1190
1191
1192

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