institution.php 46.5 KB
Newer Older
1
2
3
4
5
<?php
/**
 *
 * @package    mahara
 * @subpackage auth-internal
6
 * @author     Catalyst IT Ltd
7
8
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
 * @copyright  For copyright information on Mahara, please see the README file distributed with this software.
9
10
11
 *
 */

Donal McMullan's avatar
Donal McMullan committed
12
13
// TODO : lib

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

16
17
require_once(get_config('libroot') . 'license.php');

18
19
20
21
22
23
24
class Institution {

    const   UNINITIALIZED  = 0;
    const   INITIALIZED    = 1;
    const   PERSISTENT     = 2;

    protected $initialized = self::UNINITIALIZED;
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

    /**
     * The institution properties stored in the institution table, and their default values. The
     * actual instance values will be in this->fields;
     *
     * Note that there's a dual system for institution properties. All required values and several
     * older ones are stored in the institution table itself. Optional and/or newer values are
     * stored in the institution_config table and go in $this->configs
     *
     * TODO: If we have problems with future developers adding columns and forgetting to add them
     * here, perhaps replace this with a system that determines the DB columns of the institution
     * table dynamically, by the same method as insert_record().
     *
     * @var unknown_type
     */
    static $dbfields = array(
41
        'id' => null,
42
43
        'name' => '',
        'displayname' => '',
44
        'registerallowed' => 0,
45
46
        'registerconfirm' => 1,
        'theme' => null,
47
        'defaultmembershipperiod' => null,
48
        'maxuseraccounts' => null,
49
50
51
52
53
54
55
56
57
58
59
60
        'expiry' => null,
        'expirymailsent' => 0,
        'suspended' => 0,
        'priority' => 1,
        'defaultquota' => null,
        'showonlineusers' => 2,
        'allowinstitutionpublicviews' => 1,
        'logo' => null,
        'style' => null,
        'licensedefault' => null,
        'licensemandatory' => 0,
        'dropdownmenu' => 0,
61
62
        'skins' => 1,
        'tags' => 0
63
64
65
66
67
68
69
70
71
72
73
74
75
    );

    // This institution's config settings
    protected $configs = array();

    // Configs that have been updated and need to be saved on commit
    protected $dirtyconfigs = array();

    // This institution's properties
    protected $fields = array();

    // Fields that have been updated and need to be saved on commit
    protected $dirtyfields = array();
76

77
    public function __construct($name = null) {
78
79
        $this->fields = self::$dbfields;

80
81
82
83
        if (is_null($name)) {
            return $this;
        }

84
        if (!$this->findByName($name)) {
85
            throw new ParamOutOfRangeException('No such institution: ' . $name);
86
87
88
        }
    }

89
    public function __get($name) {
90
91
92
93
94
95
96
97
98
99
100
101
102
103

        // If it's an institution DB field, use the setting from $this->fields or null if that's empty for whatever reason
        if (array_key_exists($name, self::$dbfields)) {
            if (array_key_exists($name, $this->fields)) {
                return $this->fields[$name];
            }
            else {
                return null;
            }
        }

        // If there's a config setting for it, use that
        if (array_key_exists($name, $this->configs)) {
            return $this->configs[$name];
104
        }
105

106
107
108
        return null;
    }

109

110
    public function __set($name, $value) {
111
        if (!is_string($name)) {
112
113
            throw new ParamOutOfRangeException();
        }
114
115
116
117
118
119
120
121
122
123
124
125
126
127

        // Validate the DB fields
        switch ($name) {
            // char 255
            case 'name':
            case 'displayname':
                if (!is_string($value) || empty($value) || strlen($value) > 255) {
                    throw new ParamOutOfRangeException("'{$name}' should be a string between 1 and 255 characters in length");
                }
                break;

            // int 1 (i.e. true/false)
            case 'registerallowed':
            case 'skins':
128
            case 'tags':
129
130
131
132
133
134
            case 'suspended':
            case 'licensemandatory':
            case 'expirymailsent':
                $value = $value ? 1 : 0;
                break;

135
            case 'id':
136
137
138
139
            case 'maxuseraccounts':
            case 'showonlineusers':
                $value = (int) $value;
                break;
140
141
142
            case 'defaultmembershipperiod':
                $value = is_null($value) ? null : (int) $value;
                break;
143
        }
144
145
146
147
148

        if (array_key_exists($name, self::$dbfields)) {
            if ($this->fields[$name] !== $value) {
                $this->fields[$name] = $value;
                $this->dirtyfields[$name] = true;
149
            }
150
        }
151
152
153
154
155
        else {
            // Anything else goes in institution_config.
            // Since it's a DB field, the value must be a number, string, or NULL.
            if (is_bool($value)) {
                $value = $value ? 1 : 0;
156
            }
157
158
            if ($value !== NULL && !is_float($value) && !is_int($value) && !is_string($value)) {
                throw new ParameterException("Attempting to set institution config field \"{$name}\" to a non-scalar value.");
159
            }
160
161
162

            // A NULL here means you should drop the config from the DB
            $existingvalue = array_key_exists($name, $this->configs) ? $this->configs[$name] : NULL;
163
            if ($value !== $existingvalue) {
164
165
                $this->configs[$name] = $value;
                $this->dirtyconfigs[$name] = true;
166
            }
167
168
169
        }
    }

170
    public function findByName($name) {
171

172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
        if (!is_string($name) || strlen($name) < 1 || strlen($name) > 255) {
            throw new ParamOutOfRangeException("'name' must be a string.");
        }

        $result = get_record('institution', 'name', $name);

        if (false == $result) {
            return false;
        }

        $this->initialized = self::PERSISTENT;
        $this->populate($result);

        return $this;
    }

188
    public function initialise($name, $displayname) {
189
190
191
192
193
        if (!is_string($name)) {
            return false;
        }
        $name = strtolower($name);
        if (empty($name)) {
194
195
196
197
            return false;
        }

        $this->name = $name;
198

199
200
201
202
203
204
        if (empty($displayname) || !is_string($displayname)) {
            return false;
        }

        $this->displayname = $displayname;
        $this->initialized = max(self::INITIALIZED, $this->initialized);
205
        $this->dirtyfields = self::$dbfields;
206
207
208
        return true;
    }

209
    public function verifyReady() {
210
        if (empty($this->fields['name']) || !is_string($this->fields['name'])) {
211
212
            return false;
        }
213
        if (empty($this->fields['displayname']) || !is_string($this->fields['displayname'])) {
214
215
216
217
218
219
            return false;
        }
        $this->initialized = max(self::INITIALIZED, $this->initialized);
        return true;
    }

220
    public function commit() {
221
        if (!$this->verifyReady()) {
222
            throw new SystemException('Commit failed');
223
224
        }

225
226
227
228
229
230
        $result = true;
        if (count($this->dirtyfields)) {
            $record = new stdClass();
            foreach (array_keys($this->dirtyfields) as $fieldname) {
                $record->{$fieldname} = $this->{$fieldname};
            }
231

232
233
234
235
236
237
238
239
240
241
242
243
244
            if ($this->initialized == self::INITIALIZED) {
                $result = insert_record('institution', $record);
            }
            else if ($this->initialized == self::PERSISTENT) {
                $result = update_record('institution', $record, array('name' => $this->name));
            }
        }
        if ($result) {
            return $this->_commit_configs();
        }
        else {
            // Shouldn't happen but who noes?
            return false;
245
        }
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
    }

    /**
     * Commit the config values for this institution. Called as part of commit();
     */
    protected function _commit_configs() {
        $result = true;
        foreach (array_keys($this->dirtyconfigs) as $confkey) {
            $newvalue = $this->configs[$confkey];

            if ($newvalue === NULL) {
                delete_records('institution_config', 'institution', $this->name, 'field', $confkey);
            }
            else {

                $todb = new stdClass();
                $todb->institution = $this->name;
                $todb->field = $confkey;
                $todb->value = $this->configs[$confkey];

                if (!record_exists('institution_config', 'institution', $this->name, 'field', $confkey)) {
                    $result = $result && insert_record('institution_config', $todb);
                }
                else {
                    $result = $result && update_record('institution_config', $todb, array('institution', 'field'));
                }
            }
        }
        return $result;
275
276
277
    }

    protected function populate($result) {
278
279
280
        foreach (array_keys(self::$dbfields) as $fieldname) {
            $this->{$fieldname} = $result->{$fieldname};
        }
281
282
283
284
285
286
287
        try {
            $this->configs = get_records_menu('institution_config', 'institution', $result->name, 'field', 'field, value');
        }
        catch (SQLException $e) {
            $this->configs = false;
        }

288
289
290
        if (!$this->configs) {
            $this->configs = array();
        }
291
292
293
        $this->verifyReady();
    }

294
    public function addUserAsMember($user, $staff=null, $admin=null) {
295
        global $USER;
296
        if ($this->isFull()) {
297
298
            $this->send_admin_institution_is_full_message();
            die_info(get_string('institutionmaxusersexceeded', 'admin'));
299
        }
300
301
302
        if (is_numeric($user)) {
            $user = get_record('usr', 'id', $user);
        }
303

304
305
306
307
308
309
310
311
312
313
        $lang = get_account_preference($user->id, 'lang');
        if ($lang == 'default') {
            // The user does not have a preset lang preference so we will use the institution if it has one.
            $institution_lang = !empty($this->configs['lang']) ? $this->configs['lang'] : 'default';
            if ($institution_lang != 'default') {
                $lang = $institution_lang;
            }
            else {
                $lang = get_config('lang');
            }
314
        }
315

316
        $userinst = new stdClass();
317
        $userinst->institution = $this->name;
318
        $studentid = get_field('usr_institution_request', 'studentid', 'usr', $user->id,
319
320
321
322
323
                               'institution', $this->name);
        if (!empty($studentid)) {
            $userinst->studentid = $studentid;
        }
        else if (!empty($user->studentid)) {
324
            $userinst->studentid = $user->studentid;
325
        }
326
        $userinst->usr = $user->id;
327
328
        $now = time();
        $userinst->ctime = db_format_timestamp($now);
329
330
331
332
333
334
        if ($staff) {
            $userinst->staff = true;
        }
        if ($admin) {
            $userinst->admin = true;
        }
335
336
337
        $defaultexpiry = $this->defaultmembershipperiod;
        if (!empty($defaultexpiry)) {
            $userinst->expiry = db_format_timestamp($now + $defaultexpiry);
338
        }
339
340
        $message = (object) array(
            'users' => array($user->id),
341
342
            'subject' => get_string_from_language($lang, 'institutionmemberconfirmsubject'),
            'message' => get_string_from_language($lang, 'institutionmemberconfirmmessage', 'mahara', $this->displayname),
343
        );
344
        db_begin();
345
346
        if (!get_config('usersallowedmultipleinstitutions')) {
            delete_records('usr_institution', 'usr', $user->id);
347
            delete_records('usr_institution_request', 'usr', $user->id);
348
        }
349
350
        insert_record('usr_institution', $userinst);
        delete_records('usr_institution_request', 'usr', $userinst->usr, 'institution', $this->name);
351
        execute_sql("
352
353
354
            DELETE FROM {tag}
            WHERE resourcetype = ? AND resourceid = ? AND tag " . db_ilike() . " 'lastinstitution:%'",
            array('usr', $user->id)
355
        );
356
        // Copy institution views and collection to the user's portfolio
357
        $checkviewaccess = empty($user->newuser) && !$USER->get('admin');
358
359
        $userobj = new User();
        $userobj->find_by_id($user->id);
360
        $userobj->copy_institution_views_collections_to_new_member($this->name);
361
        require_once('activity.php');
362
        activity_occurred('maharamessage', $message);
363
        handle_event('updateuser', $userinst->usr);
364
365
366

        // Give institution members access to user's profile page
        require_once('view.php');
367
368
369
        if ($profileview = $userobj->get_profile_view()) {
            $profileview->add_owner_institution_access(array($this->name));
        }
370

371
        db_commit();
372
373
    }

374
375
376
377
378
379
380
381
382
383
    public function addUserAsStaff($user) {
        // Only to be used to add a member to an institution and bump ther permissions to staff
        $this->addUserAsMember($user, true);
    }

    public function addUserAsAdmin($user) {
        // Only to be used to add a member to an institution and bump ther permissions to admin
        $this->addUserAsMember($user, null, true);
    }

384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
    public function add_members($userids) {
        global $USER;

        if (empty($userids)) {
            return;
        }

        if (!$USER->can_edit_institution($this->name)) {
            throw new AccessDeniedException("Institution::add_members: access denied");
        }

        $values = array_map('intval', $userids);
        array_unshift($values, $this->name);
        $users = get_records_sql_array('
            SELECT u.*, r.confirmedusr
            FROM {usr} u LEFT JOIN {usr_institution_request} r ON u.id = r.usr AND r.institution = ?
            WHERE u.id IN (' . join(',', array_fill(0, count($values) - 1, '?')) . ') AND u.deleted = 0',
            $values
        );

        if (empty($users)) {
            return;
        }

        db_begin();
        foreach ($users as $user) {
            // If the user hasn't requested membership, allow them to be added to
            // the institution anyway so long as the logged-in user is a site admin
            // or institutional admin for the user (in some other institution).
            if (!$user->confirmedusr) {
                $userobj = new User;
                $userobj->from_stdclass($user);
                if (!$USER->is_admin_for_user($userobj)) {
                    continue;
                }
            }
            $this->addUserAsMember($user);
        }
        db_commit();
423
424
425
426

        foreach ($users as $user) {
            remove_user_sessions($user->id);
        }
427
428
    }

429
    public function addRequestFromUser($user, $studentid = null) {
430
431
432
433
434
435
        $request = get_record('usr_institution_request', 'usr', $user->id, 'institution', $this->name);
        if (!$request) {
            $request = (object) array(
                'usr'          => $user->id,
                'institution'  => $this->name,
                'confirmedusr' => 1,
436
                'studentid'    => empty($studentid) ? $user->studentid : $studentid,
437
438
                'ctime'        => db_format_timestamp(time())
            );
439
440
441
442
            $message = (object) array(
                'messagetype' => 'request',
                'username' => $user->username,
                'fullname' => $user->firstname . ' ' . $user->lastname,
443
                'institution' => (object)array('name' => $this->name, 'displayname' => $this->displayname, 'language' => $this->lang),
444
445
            );
            db_begin();
446
447
448
            if (!get_config('usersallowedmultipleinstitutions')) {
                delete_records('usr_institution_request', 'usr', $user->id);
            }
449
            insert_record('usr_institution_request', $request);
450
            require_once('activity.php');
451
452
            activity_occurred('institutionmessage', $message);
            handle_event('updateuser', $user->id);
453
454
455
456
457
            // If the total number of accounts has been reached, send an email to the institution
            // and site administrators notifying them of the fact.
            if ($this->isFull()) {
                $this->send_admin_institution_is_full_message();
            }
458
            db_commit();
459
460
461
462
463
        } else if ($request->confirmedinstitution) {
            $this->addUserAsMember($user);
        }
    }

464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
    public function send_admin_institution_is_full_message(){
        // get the site admin and institution admin user records.
        $admins = $this->institution_and_site_admins();
        // check if there are admins - otherwise there are no site admins?!?!?
        if (count($admins) > 0) {
            require_once('activity.php');
            // send an email/message to each amdininistrator based on their specific language.
            foreach ($admins as $index => $id) {
                $lang = get_user_language($id);
                $user = new User();
                $user->find_by_id($id);
                $message = (object) array(
                    'users'   => array($id),
                    'subject' => get_string_from_language($lang, 'institutionmembershipfullsubject'),
                    'message' => get_string_from_language($lang, 'institutionmembershipfullmessagetext', 'mahara',
                            $user->firstname, $this->displayname, get_config('sitename'), get_config('sitename')),
                );
                activity_occurred('maharamessage', $message);
            }
        }
    }
485
486
487
488
489
490
491
    /**
     * Send a message to the site admin or to the institution admin when a user refuses the privacy statement.
     *
     * If the user is part of an institution and the institution has admin(s), send the message just to the inst. admin(s).
     * Else send the messege to the site admin(s).
     *
     * @param integer $studentid The id of the user who has refused the privacy statement.
492
     * @param string $reason The reson why the user refused the privacy statement.
493
     * @param array $whathasbeenrefused The content (privacy statement or terms or both) that the user has refused.
494
     */
495
    public function send_admin_institution_refused_privacy_message($studentid, $reason, $whathasbeenrefused) {
496
497
498
499
500
501
502
503
504
505
        $student = new User();
        $student->find_by_id($studentid);
        $studentname = display_name($student, null, true);

        // Get the institution admin user records.
        $admins = $this->admins();
        // If the user is not part of an institution OR his institution has no admin, send the message to the site admin.
        if (empty($admins)) {
            $admins = $this->institution_and_site_admins();
        }
506
507
508
509
510
        $thereasonis = '';
        if ($reason != '') {
            $thereasonis = get_string('thereasonis', 'mahara');
            $reason = '"' . urldecode($reason) . '"';
        }
511
        $contentrefused = count($whathasbeenrefused) > 1 ? 'privacyandtheterms' : $whathasbeenrefused[0];
512
513
514
515
516
517
518
519
520
521
        // check if there are admins - otherwise there are no site admins?!?!?
        if (count($admins) > 0) {
            require_once('activity.php');
            // send an email/message to each amdininistrator based on their specific language.
            foreach ($admins as $index => $id) {
                $lang = get_user_language($id);
                $user = new User();
                $user->find_by_id($id);
                $message = (object) array(
                    'users'   => array($id),
522
                    'subject' => $studentname . ' ' . get_string('hasrefused', 'admin', get_string($contentrefused, 'admin')),
523
                    'message' => get_string_from_language($lang, 'institutionmemberrefusedprivacy', 'mahara',
524
                        $user->firstname, $studentname, $student->username, get_string($contentrefused, 'admin'),
525
                        $thereasonis, $reason, $student->email, get_config('sitename')),
526
527
528
529
530
                );
                activity_occurred('maharamessage', $message);
            }
        }
    }
531

532
533
534
535
536
537
538
539
540
541
    public function declineRequestFromUser($userid) {
        $lang = get_user_language($userid);
        $message = (object) array(
            'users' => array($userid),
            'subject' => get_string_from_language($lang, 'institutionmemberrejectsubject'),
            'message' => get_string_from_language($lang, 'institutionmemberrejectmessage', 'mahara', $this->displayname),
        );
        db_begin();
        delete_records('usr_institution_request', 'usr', $userid, 'institution', $this->name,
                       'confirmedusr', 1);
542
        require_once('activity.php');
543
544
545
546
547
        activity_occurred('maharamessage', $message);
        handle_event('updateuser', $userid);
        db_commit();
    }

548
549
550
551
552
553
554
555
556
557
558
559
560
561
    public function decline_requests($userids) {
        global $USER;

        if (!$USER->can_edit_institution($this->name)) {
            throw new AccessDeniedException("Institution::decline_requests: access denied");
        }

        db_begin();
        foreach ($userids as $id) {
            $this->declineRequestFromUser($id);
        }
        db_commit();
    }

562
563
    public function inviteUser($user) {
        $userid = is_object($user) ? $user->id : $user;
564
        db_begin();
565
566
567
568
569
570
        insert_record('usr_institution_request', (object) array(
            'usr' => $userid,
            'institution' => $this->name,
            'confirmedinstitution' => 1,
            'ctime' => db_format_timestamp(time())
        ));
571
        require_once('activity.php');
572
573
574
        activity_occurred('institutionmessage', (object) array(
            'messagetype' => 'invite',
            'users' => array($userid),
575
            'institution' => (object)array('name' => $this->name, 'displayname' => $this->displayname, 'language' => $this->lang),
576
        ));
577
        handle_event('updateuser', $userid);
578
        db_commit();
579
580
    }

581
582
583
584
585
586
587
588
589
590
591
592
593
594
    public function invite_users($userids) {
        global $USER;

        if (!$USER->can_edit_institution($this->name)) {
            throw new AccessDeniedException("Institution::invite_users: access denied");
        }

        db_begin();
        foreach ($userids as $id) {
            $this->inviteUser($id);
        }
        db_commit();
    }

595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
    public function uninvite_users($userids) {
        global $USER;

        if (!$USER->can_edit_institution($this->name)) {
            throw new AccessDeniedException("Institution::uninvite_users: access denied");
        }

        if (!is_array($userids) || empty($userids)) {
            return;
        }

        $ph = array_map('intval', $userids);
        $ph[] = $this->name;

        delete_records_select(
            'usr_institution_request',
            'usr IN (' . join(',', array_fill(0, count($userids), '?')) . ') AND institution = ? AND confirmedinstitution = 1',
            $ph
        );
    }

616
617
618
    public function removeMembers($userids) {
        // Remove self last.
        global $USER;
619
620
621
622
623

        if (!$USER->can_edit_institution($this->name)) {
            throw new AccessDeniedException("Institution::removeMembers: access denied");
        }

624
        $users = get_records_select_array('usr', 'id IN (' . join(',', array_map('intval', $userids)) . ')');
625
        $removeself = false;
626
        db_begin();
627
628
629
630
631
632
633
634
635
636
        foreach ($users as $user) {
            if ($user->id == $USER->id) {
                $removeself = true;
                continue;
            }
            $this->removeMember($user);
        }
        if ($removeself) {
            $USER->leave_institution($this->name);
        }
637
        db_commit();
638
639
    }

640
    public function removeMember($user) {
641
642
        global $USER;

643
644
645
646
647
        if (is_numeric($user)) {
            $user = get_record('usr', 'id', $user);
        }
        db_begin();
        // If the user is being authed by the institution they are
648
649
650
651
        // being removed from, change them to internal auth, or if
        // we can't find that, some other no institution auth.
        $authinstances = get_records_select_assoc(
            'auth_instance',
652
            "institution IN ('mahara', ?) AND active = 1",
653
654
655
            array($this->name),
            "institution = 'mahara' DESC, authname = 'internal' DESC"
        );
656
657
658
        $oldauth = $user->authinstance;
        if (isset($authinstances[$oldauth]) && $authinstances[$oldauth]->institution == $this->name) {
            foreach ($authinstances as $ai) {
659
                if ($ai->authname == 'internal' && $ai->institution == 'mahara') {
660
661
662
                    $user->authinstance = $ai->id;
                    break;
                }
663
664
665
666
                else if ($ai->institution == 'mahara') {
                    $user->authinstance = $ai->id;
                    break;
                }
667
668
669
670
671
672
673
674
            }
            delete_records('auth_remote_user', 'authinstance', $oldauth, 'localusr', $user->id);
            // If the old authinstance was external, the user may need
            // to set a password
            if ($user->password == '') {
                log_debug('resetting pw for '.$user->id);
                $this->removeMemberSetPassword($user);
            }
675
676
677
678
679
680
681
682
            else if ($authinstances[$oldauth]->authname != 'internal') {
                $sitename = get_config('sitename');
                $fullname = display_name($user, null, true);
                email_user($user, null,
                    get_string('noinstitutionoldpassemailsubject', 'mahara', $sitename, $this->displayname),
                    get_string('noinstitutionoldpassemailmessagetext', 'mahara', $fullname, $this->displayname, $sitename, $user->username, get_config('wwwroot'), get_config('wwwroot'), $sitename, get_config('wwwroot')),
                    get_string('noinstitutionoldpassemailmessagehtml', 'mahara', hsc($fullname), hsc($this->displayname), hsc($sitename), hsc($user->username), get_config('wwwroot'), get_config('wwwroot'), get_config('wwwroot'), hsc($sitename), get_config('wwwroot'), get_config('wwwroot')));
            }
683
684
            update_record('usr', $user);
        }
685
686
687
688
689
690
691
692
693
694
695
696

        // If this user has a favourites list which is updated by this institution, remove it
        // from this institution's control.
        // Don't delete it in case the user wants to keep it, but move it out of the way, so
        // another institution can create a new faves list with the same name.
        execute_sql("
            UPDATE {favorite}
            SET institution = NULL, shortname = substring(shortname from 1 for 100) || '.' || ?
            WHERE owner = ? AND institution = ?",
            array(substr($this->name, 0, 100) . '.' . get_random_key(), $user->id, $this->name)
        );

697
        execute_sql("
698
699
700
            DELETE FROM {tag}
            WHERE resourcetype = ? AND resourceid = ? AND tag " . db_ilike() . " 'lastinstitution:%'",
            array('usr', $user->id)
701
702
703
        );

        insert_record(
704
            'tag',
705
            (object) array(
706
707
708
709
                'resourcetype' => 'usr',
                'resourceid' => $user->id,
                'ownertype' => 'institution',
                'ownerid' => $this->name,
710
                'tag' => 'lastinstitution:' . strtolower($this->name),
711
712
                'ctime' => db_format_timestamp(time()),
                'editedby' => $USER->get('id'),
713
714
715
            )
        );

716
717
718
719
720
721
722
723
724
725
726
727
728
729
        // Need to change any user's "institution tag" tags for this institution
        // into normal user tags
        $typecast = is_postgres() ? '::varchar' : '';
        if ($userinstitutiontags = get_records_sql_array("
            SELECT t.id, t.tag, (SELECT t2.tag FROM {tag} t2 WHERE t2.id" . $typecast . " = SUBSTRING(t.tag, 7)) AS realtag
            FROM {tag} t
            WHERE ownertype = ? AND ownerid = ?
            AND tag LIKE 'tagid_%'", array('user', $user->id))) {

            foreach ($userinstitutiontags as $newtag) {
                execute_sql("UPDATE {tag} SET tag = ? WHERE id = ?", array($newtag->realtag, $newtag->id));
            }
        }

730
731
732
        // If the user's license default is set to "institution default", remove the pref
        delete_records('usr_account_preference', 'usr', $user->id, 'field', 'licensedefault', 'value', LICENSE_INSTITUTION_DEFAULT);

733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
        delete_records('usr_institution', 'usr', $user->id, 'institution', $this->name);
        handle_event('updateuser', $user->id);
        db_commit();
    }

    /**
     * Reset user's password, and send them a password change email
     */
    private function removeMemberSetPassword(&$user) {
        global $SESSION, $USER;
        if ($user->id == $USER->id) {
            $user->passwordchange = 1;
            return;
        }
        try {
748
            $pwrequest = new stdClass();
749
750
751
752
753
754
755
            $pwrequest->usr = $user->id;
            $pwrequest->expiry = db_format_timestamp(time() + 86400);
            $pwrequest->key = get_random_key();
            $sitename = get_config('sitename');
            $fullname = display_name($user, null, true);
            email_user($user, null,
                get_string('noinstitutionsetpassemailsubject', 'mahara', $sitename, $this->displayname),
756
                get_string('noinstitutionsetpassemailmessagetext', 'mahara', $fullname, $this->displayname, $sitename, $user->username, get_config('wwwroot'), $pwrequest->key, get_config('wwwroot'), $sitename, get_config('wwwroot'), $pwrequest->key),
757
                get_string('noinstitutionsetpassemailmessagehtml', 'mahara', hsc($fullname), hsc($this->displayname), hsc($sitename), hsc($user->username), get_config('wwwroot'), hsc($pwrequest->key), get_config('wwwroot'), hsc($pwrequest->key), get_config('wwwroot'), hsc($sitename), get_config('wwwroot'), hsc($pwrequest->key), get_config('wwwroot'), hsc($pwrequest->key)));
758
759
760
761
762
763
764
765
            insert_record('usr_password_request', $pwrequest);
        }
        catch (SQLException $e) {
            $SESSION->add_error_msg(get_string('forgotpassemailsendunsuccessful'));
        }
        catch (EmailException $e) {
            $SESSION->add_error_msg(get_string('forgotpassemailsendunsuccessful'));
        }
766
    }
767
768
769
770
771
772
773
774
775
776
777
778
779

    public function countMembers() {
        return count_records_sql('
            SELECT COUNT(*) FROM {usr} u INNER JOIN {usr_institution} i ON u.id = i.usr
            WHERE i.institution = ? AND u.deleted = 0', array($this->name));
    }

    public function countInvites() {
        return count_records_sql('
            SELECT COUNT(*) FROM {usr} u INNER JOIN {usr_institution_request} r ON u.id = r.usr
            WHERE r.institution = ? AND u.deleted = 0 AND r.confirmedinstitution = 1',
            array($this->name));
    }
780
781

    /**
Aaron Wells's avatar
Aaron Wells committed
782
     * Returns true if the institution already has its full quota of users
783
784
785
786
787
788
789
     * assigned to it.
     *
     * @return bool
     */
    public function isFull() {
        return ($this->maxuseraccounts != '') && ($this->countMembers() >= $this->maxuseraccounts);
    }
790

791
792
793
    /**
     * Returns the current institution admin member records
     *
794
     * @return array  A data structure containing site admins
795
796
797
798
799
     */
    public function admins() {
        if ($results = get_records_sql_array('
            SELECT u.id FROM {usr} u INNER JOIN {usr_institution} i ON u.id = i.usr
            WHERE i.institution = ? AND u.deleted = 0 AND i.admin = 1', array($this->name))) {
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
            return array_map('extract_institution_user_id', $results);
        }
        return array();
    }

    /**
     * Returns the current institution and site admin records
     *
     * @return array  A data structure containing site and institution admins
     */
    public function institution_and_site_admins() {
        if ($results = get_records_sql_array('
            SELECT u.id FROM {usr} u INNER JOIN {usr_institution} i ON u.id = i.usr
            WHERE i.institution = ? AND u.deleted = 0 AND i.admin = 1
            UNION
            SELECT u.id FROM {usr} u
            WHERE u.deleted = 0 AND u.admin = 1', array($this->name))) {
            return array_map('extract_institution_user_id', $results);
818
819
820
821
822
823
824
825
826
827
828
829
830
        }
        return array();
    }

    /**
     * Returns the current institution staff member records
     *
     * @return array  A data structure containing staff
     */
    public function staff() {
        if ($results = get_records_sql_array('
            SELECT u.id FROM {usr} u INNER JOIN {usr_institution} i ON u.id = i.usr
            WHERE i.institution = ? AND u.deleted = 0 AND i.staff = 1', array($this->name))) {
831
            return array_map('extract_institution_user_id', $results);
832
833
834
835
        }
        return array();
    }

836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
    /**
     * Returns the list of institutions, implements institution searching
     *
     * @param array   Limit the output to only institutions in this array (used for institution admins).
     * @param bool    Whether default institution should be listed in results.
     * @param string  Searching query string.
     * @param int     Limit of results (used for pagination).
     * @param int     Offset of results (used for pagination).
     * @param int     Returns the total number of results.
     * @return array  A data structure containing results looking like ...
     *   $institutions = array(
     *                       name => array(
     *                           displayname     => string
     *                           maxuseraccounts => integer
     *                           members         => integer
     *                           staff           => integer
     *                           admins          => integer
     *                           name            => string
     *                       ),
     *                       name => array(...),
     *                   );
     */
    public static function count_members($filter, $showdefault, $query='', $limit=null, $offset=null, &$count=null) {
859
860
        if ($filter) {
            $where = '
861
            AND ii.name IN (' . join(',', array_map('db_quote', $filter)) . ')';
862
863
864
865
        }
        else {
            $where = '';
        }
866

867
        $querydata = explode(' ', preg_replace('/\s\s+/', ' ', strtolower(trim($query))));
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
        $namesql = '(
                ii.name ' . db_ilike() . ' \'%\' || ? || \'%\'
            )
            OR (
                ii.displayname ' . db_ilike() . ' \'%\' || ? || \'%\'
            )';
        $namesql = join(' OR ', array_fill(0, count($querydata), $namesql));
        $queryvalues = array();
        foreach ($querydata as $w) {
            $queryvalues = array_pad($queryvalues, count($queryvalues) + 2, $w);
        }

        $count = count_records_sql('SELECT COUNT(ii.name)
            FROM {institution} ii
            WHERE' . $namesql, $queryvalues
        );

885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
        $institutions = get_records_sql_assoc('
            SELECT
                ii.name,
                ii.displayname,
                ii.maxuseraccounts,
                ii.suspended,
                COALESCE(a.members, 0) AS members,
                COALESCE(a.staff, 0) AS staff,
                COALESCE(a.admins, 0) AS admins
            FROM
                {institution} ii
                LEFT JOIN
                    (SELECT
                        i.name, i.displayname, i.maxuseraccounts,
                        COUNT(ui.usr) AS members, SUM(ui.staff) AS staff, SUM(ui.admin) AS admins
                    FROM
                        {institution} i
                        LEFT OUTER JOIN {usr_institution} ui ON (ui.institution = i.name)
                        LEFT OUTER JOIN {usr} u ON (u.id = ui.usr)
                    WHERE
                        (u.deleted = 0 OR u.id IS NULL)
                    GROUP BY
                        i.name, i.displayname, i.maxuseraccounts
908
909
                    ) a ON (a.name = ii.name)
                    WHERE (' . $namesql . ')' . $where . '
910
                    ORDER BY
911
                        ii.name = \'mahara\', ii.displayname', $queryvalues, $offset, $limit);
912

913
        if ($showdefault && $institutions && array_key_exists('mahara', $institutions)) {
914
915
916
            $defaultinstarray = get_records_sql_assoc('
                SELECT COUNT(u.id) AS members, COALESCE(SUM(u.staff), 0) AS staff, COALESCE(SUM(u.admin), 0) AS admins
                FROM {usr} u LEFT OUTER JOIN {usr_institution} i ON u.id = i.usr
917
                WHERE u.deleted = 0 AND i.usr IS NULL AND u.id != 0
918
919
920
921
922
923
924
            ', array());
            $defaultinst = current($defaultinstarray);
            $institutions['mahara']->members = $defaultinst->members;
            $institutions['mahara']->staff   = $defaultinst->staff;
            $institutions['mahara']->admins  = $defaultinst->admins;
            $institutions['mahara']->site = true;
            $institutions['mahara']->maxuseraccounts = 0;
925
926
927
        }
        return $institutions;
    }
928
929
930
931
932
933
934
935
936
937
938
939
940

    /*
    * returns: true if the institution requires admin approval before deleting a user account
    * or it doesn't have the value set for it in the configuration, but the site requires approval by default
    */
    public function requires_user_deletion_approval() {
        /* If site default is set to 'yes', it will be the value for the institutions
         * if it's set to 'no', then we take the value from the institution settings
         */
        return (get_config('defaultreviewselfdeletion') ||
               (isset($this->configs['reviewselfdeletion']) && $this->configs['reviewselfdeletion'])
            );
    }
941
}
Richard Mansfield's avatar
Richard Mansfield committed
942

943
944
945
946
947
948
949
950
/**
 * Returns an institution dropdown selector
 *
 * @param bool $includedefault           To include the 'mahara' institution in list
 * @param bool $assumesiteadmin          To call this function like you had site admin privileges
 * @param bool $includesitestaff         To allow site staff to see dropdown like the site admin would
 * @param bool $includeinstitutionstaff  To allow institution staff to see dropdown like institution admin would
 * @param bool $allselector              To add an 'all' option to the dropdown where it makes sense, eg in institution statistics page
951
 * @param bool $withactiveinstitutiontags To only fetch institutions which are configured to define their own tags
952
953
954
 *
 * @return null or array suitable for pieform element
 */
955
956
function get_institution_selector($includedefault = true, $assumesiteadmin=false, $includesitestaff=false, $includeinstitutionstaff=false,
    $allselector=false, $withactiveinstitutiontags=false) {
Richard Mansfield's avatar
Richard Mansfield committed
957
958
    global $USER;

959
    if (($assumesiteadmin || $USER->get('admin')) || ($includesitestaff && $USER->get('staff'))) {
960
        if ($includedefault) {
961
            $institutions = get_records_array('institution', '', '', 'displayname');
962
963
        }
        else {
964
            $institutions = get_records_select_array('institution', "name != 'mahara'", null, 'displayname');
965
        }
966
967
968
969
970
971
972
973
974
975
976
    }
    else if ($USER->is_institutional_admin() && ($USER->is_institutional_staff() && $includeinstitutionstaff)) {
        // if a user is both an admin for some institution and is a staff member for others
        $institutions = get_records_select_array(
            'institution',
            'name IN (' . join(',', array_map('db_quote',$USER->get('admininstitutions'))) .
                      ',' . join(',', array_map('db_quote',$USER->get('staffinstitutions'))) . ')',
            null, 'displayname'
        );
    }
    else if ($USER->is_institutional_admin()) {
977
978
979
980
981
        $institutions = get_records_select_array(
            'institution',
            'name IN (' . join(',', array_map('db_quote',$USER->get('admininstitutions'))) . ')',
            null, 'displayname'
        );
982
983
984
985
986
987
988
    }
    else if ($includeinstitutionstaff) {
        $institutions = get_records_select_array(
            'institution',
            'name IN (' . join(',', array_map('db_quote',$USER->get('staffinstitutions'))) . ')',
            null, 'displayname'
        );
989
990
    }
    else {
Richard Mansfield's avatar
Richard Mansfield committed
991
992
993
        return null;
    }

994
995
996
997
    if (empty($institutions)) {
        return null;
    }

998
    $options = array();
999
1000
1001
    if ($allselector) {
        $options['all'] = get_string('Allinstitutions', 'mahara');
    }
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
    if ($withactiveinstitutiontags) {
        foreach ($institutions as $i) {
            if ($i->tags) {
                $options[$i->name] = $i->displayname;
            }
        }
    }
    else {
        foreach ($institutions as $i) {
            $options[$i->name] = $i->displayname;
        }
Richard Mansfield's avatar
Richard Mansfield committed
1013
    }
1014
1015
1016
1017
1018
1019
1020
1021
    $institution = key($options);
    $institutionelement = array(
        'type' => 'select',
        'title' => get_string('institution'),
        'defaultvalue' => $institution,
        'options' => $options,
        'rules' => array('regex' => '/^[a-zA-Z0-9]+$/')
    );
Richard Mansfield's avatar
Richard Mansfield committed
1022
1023
1024
1025

    return $institutionelement;
}

1026
1027
/* The institution selector does exactly the same thing in both
   institutionadmins.php and institutionstaff.php (in /admin/users/).
Richard Mansfield's avatar
Richard Mansfield committed
1028
1029
   This function creates the form for the page. */
function institution_selector_for_page($institution, $page) {
1030
1031
1032
1033
    // Special case: $institution == 1 <-> any institution
    if ($institution == 1) {
        $institution = '';
    }
1034
1035
    $institutionelement = get_institution_selector(false);

1036
    if (empty($institutionelement)) {
Richard Mansfield's avatar
Richard Mansfield committed
1037
        return array('institution' => false, 'institutionselector' => null, 'institutionselectorjs' => '');
1038
1039
    }

1040
1041
1042
1043
1044
1045
1046
    global $USER;
    if (empty($institution) || !$USER->can_edit_institution($institution)) {
        $institution = empty($institutionelement['value']) ? $institutionelement['defaultvalue'] : $institutionelement['value'];
    }
    else {
        $institutionelement['defaultvalue'] = $institution;
    }
Aaron Wells's avatar
Aaron Wells committed
1047

1048
1049
    $institutionselector = pieform(array(
        'name' => 'institutionselect',
1050
        'class' => 'form-inline',
1051
        'checkdirtychange' => false,
1052
1053
1054
1055
        'elements' => array(
            'institution' => $institutionelement,
        )
    ));
1056
1057

    $page = json_encode($page);
1058
    $js = <<< EOF
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
jQuery(function($) {
    function reloadUsers() {
        var urlstr = $page;
        var inst = '';
        if ($('#institutionselect_institution').length) {
            inst = 'institution=' + $('#institutionselect_institution').val();
            if (urlstr.indexOf('?') > 0) {
                urlstr = urlstr + '&' + inst;
            }
            else {
                urlstr = urlstr + '?' + inst;
            }
1071
        }
1072
        window.location.href = urlstr;
1073
    }
1074
1075
1076

    if ($('#institutionselect_institution').length) {
        $('#institutionselect_institution').on('change', reloadUsers);
1077
1078
1079
    }
});
EOF;
Aaron Wells's avatar
Aaron Wells committed
1080

Richard Mansfield's avatar
Richard Mansfield committed
1081
1082
1083
1084
1085
    return array(
        'institution'           => $institution,
        'institutionselector'   => $institutionselector,
        'institutionselectorjs' => $js
    );
1086
}
1087
1088

function build_institutions_html($filter, $showdefault, $query, $limit, $offset, &$count=null) {
1089
    global $USER, $CFG;
1090
1091

    $institutions = Institution::count_members($filter, $showdefault, $query, $limit, $offset, $count);
1092
1093
    require_once($CFG->docroot . '/webservice/lib.php');

1094
1095
1096
1097

    $smarty = smarty_core();
    $smarty->assign('institutions', $institutions);
    $smarty->assign('siteadmin', $USER->get('admin'));
1098
    $smarty->assign('webserviceconnections', (bool) count(webservice_connection_definitions()));
1099
1100
1101
1102
1103
    $data['tablerows'] = $smarty->fetch('admin/users/institutionsresults.tpl');

    $pagination = build_pagination(array(
                'id' => 'adminstitutionslist_pagination',
                'datatable' => 'adminstitutionslist',
1104
                'url' => get_config('wwwroot') . 'admin/users/institutions.php' . (($query != '') ? '?query=' . urlencode($query) : ''),
1105
1106
1107
1108
                'jsonscript' => 'admin/users/institutions.json.php',
                'count' => $count,
                'limit' => $limit,
                'offset' => $offset,
1109
                'setlimit' => true,
Eugene Venter's avatar
Eugene Venter committed
1110
                'jumplinks' => 4,
1111
1112
1113
1114
1115
1116
1117
1118
1119
                'resultcounttextsingular' => get_string('institution', 'admin'),
                'resultcounttextplural' => get_string('institutions', 'admin'),
            ));

    $data['pagination'] = $pagination['html'];
    $data['pagination_js'] = $pagination['javascript'];

    return $data;
}
1120
1121

function institution_display_name($name) {
1122
    return hsc(get_field('institution', 'displayname', 'name', $name));
1123
}
1124

1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
/**
 * Generate a valid name for the institution.name column, based on the specified display name
 *
 * @param string $displayname
 * @return string
 */
function institution_generate_name($displayname) {
    // iconv can crash on strings that are too long, so truncate before converting
    $basename = mb_substr($displayname, 0, 255);
    $basename = iconv('UTF-8', 'ASCII//TRANSLIT', $displayname);
    $basename = strtolower($basename);
    $basename = preg_replace('/[^a-z]/', '', $basename);
    if (strlen($basename) < 2) {
        $basename = 'inst' . $basename;
    }
    else {
        $basename = substr($basename, 0, 255);
    }

    // Make sure the name is unique. If it is not, add a suffix and see if
    // that makes it unique
    $finalname = $basename;
    $suffix = 'a';
    while (record_exists('institution', 'name', $finalname)) {
        // Add the suffix but make sure the name length doesn't go over 255
        $finalname = substr($basename, 0, 255 - strlen($suffix)) . $suffix;

        // Will iterate a-z, aa-az, ba-bz, etc.
        // See: http://php.net/manual/en/language.operators.increment.php
        $suffix++;
    }

    return $finalname;
}

1160
1161
1162
1163
1164
1165
1166
/**
 * Callback function to extract user ID from an object.
 * @param object $input
 */
function extract_institution_user_id($input) {
    return $input->id;
}
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221

/**
 * Get institution settings elements from artefact plugins.
 *
 * @param Institution $institution
 * @return array
 */
function plugin_institution_prefs_form_elements(Institution $institution = null) {
    $elements = array();
    $installed = plugin_all_installed();
    foreach ($installed as $i) {
        if (!safe_require_plugin($i->plugintype, $i->name)) {
            continue;
        }
        $elements = array_merge($elements, call_static_method(generate_class_name($i->plugintype, $i->name),
                'get_institutionprefs_elements', $institution));
    }
    return $elements;
}

/**
 * Validate plugin institution form values.
 *
 * @param Pieform $form
 * @param array $values
 */
function plugin_institution_prefs_validate(Pieform $form, $values) {
    $elements = array();
    $installed = plugin_all_installed();
    foreach ($installed as $i) {
        if (!safe_require_plugin($i->plugintype, $i->name)) {
            continue;
        }
        call_static_method(generate_class_name($i->plugintype, $i->name), 'institutionprefs_validate', $form, $values);
    }
}

/**
 * Submit plugin institution form values.
 *
 * @param Pieform $form
 * @param array $values
 * @param Institution $institution
 * @return bool is page need to be refreshed
 */
function plugin_institution_prefs_submit(Pieform $form, $values, Institution $institution) {
    $elements = array();
    $installed = plugin_all_installed();
    foreach ($installed as $i) {
        if (!safe_require_plugin($i->plugintype, $i->name)) {
            continue;
        }
        call_static_method(generate_class_name($i->plugintype, $i->name), 'institutionprefs_submit', $form, $values, $institution);
    }
}
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244

/**
 * Get current institution by theme.
 * If the user account theme is set use that otherwise use
 * the first institution the user belongs to.
 *
 * @return $string Name of institution
 */
function get_institution_by_current_theme() {
    global $USER;
    $usrtheme = $USER->get_account_preference('theme');
    if ($usrtheme) {
        $list = (explode('/', $usrtheme));
        if (count($list) > 1 && !empty($list[1])) {
            return $list[1];
        }
    }
    $institutions = $USER->institutions;
    if (!empty($institutions)) {
        return key(array_slice($institutions, 0, 1));
    }
    return 'mahara';
}