institution.php 44.7 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' => true,
        'tags' => false
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) {
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
317
        $userinst = new StdClass;
        $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
        $defaultexpiry = $this->defaultmembershipperiod;
        if (!empty($defaultexpiry)) {
            $userinst->expiry = db_format_timestamp($now + $defaultexpiry);
332
        }
333
334
        $message = (object) array(
            'users' => array($user->id),
335
336
            'subject' => get_string_from_language($lang, 'institutionmemberconfirmsubject'),
            'message' => get_string_from_language($lang, 'institutionmemberconfirmmessage', 'mahara', $this->displayname),
337
        );
338
        db_begin();
339
340
        if (!get_config('usersallowedmultipleinstitutions')) {
            delete_records('usr_institution', 'usr', $user->id);
341
            delete_records('usr_institution_request', 'usr', $user->id);
342
        }
343
344
        insert_record('usr_institution', $userinst);
        delete_records('usr_institution_request', 'usr', $userinst->usr, 'institution', $this->name);
345
346
347
348
349
        execute_sql("
            DELETE FROM {usr_tag}
            WHERE usr = ? AND tag " . db_ilike() . " 'lastinstitution:%'",
            array($user->id)
        );
350
        // Copy institution views and collection to the user's portfolio
351
        $checkviewaccess = empty($user->newuser) && !$USER->get('admin');
352
353
        $userobj = new User();
        $userobj->find_by_id($user->id);
354
        $userobj->copy_institution_views_collections_to_new_member($this->name);
355
        require_once('activity.php');
356
        activity_occurred('maharamessage', $message);
357
        handle_event('updateuser', $userinst->usr);
358
359
360

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

365
        db_commit();
366
367
    }

368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
    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();
407
408
409
410

        foreach ($users as $user) {
            remove_user_sessions($user->id);
        }
411
412
    }

413
    public function addRequestFromUser($user, $studentid = null) {
414
415
416
417
418
419
        $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,
420
                'studentid'    => empty($studentid) ? $user->studentid : $studentid,
421
422
                'ctime'        => db_format_timestamp(time())
            );
423
424
425
426
            $message = (object) array(
                'messagetype' => 'request',
                'username' => $user->username,
                'fullname' => $user->firstname . ' ' . $user->lastname,
427
                'institution' => (object)array('name' => $this->name, 'displayname' => $this->displayname, 'language' => $this->lang),
428
429
            );
            db_begin();
430
431
432
            if (!get_config('usersallowedmultipleinstitutions')) {
                delete_records('usr_institution_request', 'usr', $user->id);
            }
433
            insert_record('usr_institution_request', $request);
434
            require_once('activity.php');
435
436
            activity_occurred('institutionmessage', $message);
            handle_event('updateuser', $user->id);
437
438
439
440
441
            // 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();
            }
442
            db_commit();
443
444
445
446
447
        } else if ($request->confirmedinstitution) {
            $this->addUserAsMember($user);
        }
    }

448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
    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);
            }
        }
    }
469
470
471
472
473
474
475
    /**
     * 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.
476
     * @param string $reason The reson why the user refused the privacy statement.
477
     * @param array $whathasbeenrefused The content (privacy statement or terms or both) that the user has refused.
478
     */
479
    public function send_admin_institution_refused_privacy_message($studentid, $reason, $whathasbeenrefused) {
480
481
482
483
484
485
486
487
488
489
        $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();
        }
490
491
492
493
494
        $thereasonis = '';
        if ($reason != '') {
            $thereasonis = get_string('thereasonis', 'mahara');
            $reason = '"' . urldecode($reason) . '"';
        }
495
        $contentrefused = count($whathasbeenrefused) > 1 ? 'privacyandtheterms' : $whathasbeenrefused[0];
496
497
498
499
500
501
502
503
504
505
        // 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),
506
                    'subject' => $studentname . ' ' . get_string('hasrefused', 'admin', get_string($contentrefused, 'admin')),
507
                    'message' => get_string_from_language($lang, 'institutionmemberrefusedprivacy', 'mahara',
508
                        $user->firstname, $studentname, $student->username, get_string($contentrefused, 'admin'),
509
                        $thereasonis, $reason, $student->email, get_config('sitename')),
510
511
512
513
514
                );
                activity_occurred('maharamessage', $message);
            }
        }
    }
515

516
517
518
519
520
521
522
523
524
525
    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);
526
        require_once('activity.php');
527
528
529
530
531
        activity_occurred('maharamessage', $message);
        handle_event('updateuser', $userid);
        db_commit();
    }

532
533
534
535
536
537
538
539
540
541
542
543
544
545
    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();
    }

546
547
    public function inviteUser($user) {
        $userid = is_object($user) ? $user->id : $user;
548
        db_begin();
549
550
551
552
553
554
        insert_record('usr_institution_request', (object) array(
            'usr' => $userid,
            'institution' => $this->name,
            'confirmedinstitution' => 1,
            'ctime' => db_format_timestamp(time())
        ));
555
        require_once('activity.php');
556
557
558
        activity_occurred('institutionmessage', (object) array(
            'messagetype' => 'invite',
            'users' => array($userid),
559
            'institution' => (object)array('name' => $this->name, 'displayname' => $this->displayname, 'language' => $this->lang),
560
        ));
561
        handle_event('updateuser', $userid);
562
        db_commit();
563
564
    }

565
566
567
568
569
570
571
572
573
574
575
576
577
578
    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();
    }

579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
    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
        );
    }

600
601
602
    public function removeMembers($userids) {
        // Remove self last.
        global $USER;
603
604
605
606
607

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

608
        $users = get_records_select_array('usr', 'id IN (' . join(',', array_map('intval', $userids)) . ')');
609
        $removeself = false;
610
        db_begin();
611
612
613
614
615
616
617
618
619
620
        foreach ($users as $user) {
            if ($user->id == $USER->id) {
                $removeself = true;
                continue;
            }
            $this->removeMember($user);
        }
        if ($removeself) {
            $USER->leave_institution($this->name);
        }
621
        db_commit();
622
623
    }

624
    public function removeMember($user) {
625
626
627
628
629
        if (is_numeric($user)) {
            $user = get_record('usr', 'id', $user);
        }
        db_begin();
        // If the user is being authed by the institution they are
630
631
632
633
        // 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',
634
            "institution IN ('mahara', ?) AND active = 1",
635
636
637
            array($this->name),
            "institution = 'mahara' DESC, authname = 'internal' DESC"
        );
638
639
640
        $oldauth = $user->authinstance;
        if (isset($authinstances[$oldauth]) && $authinstances[$oldauth]->institution == $this->name) {
            foreach ($authinstances as $ai) {
641
                if ($ai->authname == 'internal' && $ai->institution == 'mahara') {
642
643
644
                    $user->authinstance = $ai->id;
                    break;
                }
645
646
647
648
                else if ($ai->institution == 'mahara') {
                    $user->authinstance = $ai->id;
                    break;
                }
649
650
651
652
653
654
655
656
            }
            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);
            }
657
658
659
660
661
662
663
664
            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')));
            }
665
666
            update_record('usr', $user);
        }
667
668
669
670
671
672
673
674
675
676
677
678

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

679
680
681
682
683
684
685
686
687
688
689
690
691
692
        execute_sql("
            DELETE FROM {usr_tag}
            WHERE usr = ? AND tag " . db_ilike() . " 'lastinstitution:%'",
            array($user->id)
        );

        insert_record(
            'usr_tag',
            (object) array(
                'usr' => $user->id,
                'tag' => 'lastinstitution:' . strtolower($this->name),
            )
        );

693
694
695
        // 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);

696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
        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 {
            $pwrequest = new StdClass;
            $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),
719
                get_string('noinstitutionsetpassemailmessagetext', 'mahara', $fullname, $this->displayname, $sitename, $user->username, get_config('wwwroot'), $pwrequest->key, get_config('wwwroot'), $sitename, get_config('wwwroot'), $pwrequest->key),
720
                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)));
721
722
723
724
725
726
727
728
            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'));
        }
729
    }
730
731
732
733
734
735
736
737
738
739
740
741
742

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

    /**
Aaron Wells's avatar
Aaron Wells committed
745
     * Returns true if the institution already has its full quota of users
746
747
748
749
750
751
752
     * assigned to it.
     *
     * @return bool
     */
    public function isFull() {
        return ($this->maxuseraccounts != '') && ($this->countMembers() >= $this->maxuseraccounts);
    }
753

754
755
756
    /**
     * Returns the current institution admin member records
     *
757
     * @return array  A data structure containing site admins
758
759
760
761
762
     */
    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))) {
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
            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);
781
782
783
784
785
786
787
788
789
790
791
792
793
        }
        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))) {
794
            return array_map('extract_institution_user_id', $results);
795
796
797
798
        }
        return array();
    }

799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
    /**
     * 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) {
822
823
        if ($filter) {
            $where = '
824
            AND ii.name IN (' . join(',', array_map('db_quote', $filter)) . ')';
825
826
827
828
        }
        else {
            $where = '';
        }
829

830
        $querydata = explode(' ', preg_replace('/\s\s+/', ' ', strtolower(trim($query))));
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
        $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
        );

848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
        $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
871
872
                    ) a ON (a.name = ii.name)
                    WHERE (' . $namesql . ')' . $where . '
873
                    ORDER BY
874
                        ii.name = \'mahara\', ii.displayname', $queryvalues, $offset, $limit);
875

876
        if ($showdefault && $institutions && array_key_exists('mahara', $institutions)) {
877
878
879
            $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
880
                WHERE u.deleted = 0 AND i.usr IS NULL AND u.id != 0
881
882
883
884
885
886
887
            ', 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;
888
889
890
        }
        return $institutions;
    }
891
892
893
894
895
896
897
898
899
900
901
902
903

    /*
    * 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'])
            );
    }
904
}
Richard Mansfield's avatar
Richard Mansfield committed
905

906
907
908
909
910
911
912
913
914
915
916
917
/**
 * 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
 *
 * @return null or array suitable for pieform element
 */
function get_institution_selector($includedefault = true, $assumesiteadmin=false, $includesitestaff=false, $includeinstitutionstaff=false, $allselector=false) {
Richard Mansfield's avatar
Richard Mansfield committed
918
919
    global $USER;

920
    if (($assumesiteadmin || $USER->get('admin')) || ($includesitestaff && $USER->get('staff'))) {
921
        if ($includedefault) {
922
            $institutions = get_records_array('institution', '', '', 'displayname');
923
924
        }
        else {
925
            $institutions = get_records_select_array('institution', "name != 'mahara'", null, 'displayname');
926
        }
927
928
929
930
931
932
933
934
935
936
937
    }
    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()) {
938
939
940
941
942
        $institutions = get_records_select_array(
            'institution',
            'name IN (' . join(',', array_map('db_quote',$USER->get('admininstitutions'))) . ')',
            null, 'displayname'
        );
943
944
945
946
947
948
949
    }
    else if ($includeinstitutionstaff) {
        $institutions = get_records_select_array(
            'institution',
            'name IN (' . join(',', array_map('db_quote',$USER->get('staffinstitutions'))) . ')',
            null, 'displayname'
        );
950
951
    }
    else {
Richard Mansfield's avatar
Richard Mansfield committed
952
953
954
        return null;
    }

955
956
957
958
    if (empty($institutions)) {
        return null;
    }

959
    $options = array();
960
961
962
    if ($allselector) {
        $options['all'] = get_string('Allinstitutions', 'mahara');
    }
963
964
    foreach ($institutions as $i) {
        $options[$i->name] = $i->displayname;
Richard Mansfield's avatar
Richard Mansfield committed
965
    }
966
967
968
969
970
971
972
973
    $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
974
975
976
977

    return $institutionelement;
}

978
979
/* 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
980
981
   This function creates the form for the page. */
function institution_selector_for_page($institution, $page) {
982
983
984
985
    // Special case: $institution == 1 <-> any institution
    if ($institution == 1) {
        $institution = '';
    }
986
987
    $institutionelement = get_institution_selector(false);

988
    if (empty($institutionelement)) {
Richard Mansfield's avatar
Richard Mansfield committed
989
        return array('institution' => false, 'institutionselector' => null, 'institutionselectorjs' => '');
990
991
    }

992
993
994
995
996
997
998
    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
999

1000
1001
    $institutionselector = pieform(array(
        'name' => 'institutionselect',
1002
        'class' => 'form-inline',
1003
        'checkdirtychange' => false,
1004
1005
1006
1007
        'elements' => array(
            'institution' => $institutionelement,
        )
    ));
1008
1009

    $page = json_encode($page);
1010
    $js = <<< EOF
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
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;
            }
1023
        }
1024
        window.location.href = urlstr;
1025
    }
1026
1027
1028

    if ($('#institutionselect_institution').length) {
        $('#institutionselect_institution').on('change', reloadUsers);
1029
1030
1031
    }
});
EOF;
Aaron Wells's avatar
Aaron Wells committed
1032

Richard Mansfield's avatar
Richard Mansfield committed
1033
1034
1035
1036
1037
    return array(
        'institution'           => $institution,
        'institutionselector'   => $institutionselector,
        'institutionselectorjs' => $js
    );
1038
}
1039
1040

function build_institutions_html($filter, $showdefault, $query, $limit, $offset, &$count=null) {
1041
    global $USER, $CFG;
1042
1043

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

1046
1047
1048
1049

    $smarty = smarty_core();
    $smarty->assign('institutions', $institutions);
    $smarty->assign('siteadmin', $USER->get('admin'));
1050
    $smarty->assign('webserviceconnections', (bool) count(webservice_connection_definitions()));
1051
1052
1053
1054
1055
    $data['tablerows'] = $smarty->fetch('admin/users/institutionsresults.tpl');

    $pagination = build_pagination(array(
                'id' => 'adminstitutionslist_pagination',
                'datatable' => 'adminstitutionslist',
1056
                'url' => get_config('wwwroot') . 'admin/users/institutions.php' . (($query != '') ? '?query=' . urlencode($query) : ''),
1057
1058
1059
1060
                'jsonscript' => 'admin/users/institutions.json.php',
                'count' => $count,
                'limit' => $limit,
                'offset' => $offset,
1061
                'setlimit' => true,
Eugene Venter's avatar
Eugene Venter committed
1062
                'jumplinks' => 4,
1063
1064
1065
1066
1067
1068
1069
1070
1071
                'resultcounttextsingular' => get_string('institution', 'admin'),
                'resultcounttextplural' => get_string('institutions', 'admin'),
            ));

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

    return $data;
}
1072
1073

function institution_display_name($name) {
1074
    return hsc(get_field('institution', 'displayname', 'name', $name));
1075
}
1076

1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
/**
 * 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;
}

1112
1113
1114
1115
1116
1117
1118
/**
 * Callback function to extract user ID from an object.
 * @param object $input
 */
function extract_institution_user_id($input) {
    return $input->id;
}
1119
1120
1121
1122
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
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173

/**
 * 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);
    }
}
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196

/**
 * 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';
}