institution.php 41.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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
        'expiry' => null,
        'expirymailsent' => 0,
        'suspended' => 0,
        'priority' => 1,
        'defaultquota' => null,
        'showonlineusers' => 2,
        'allowinstitutionpublicviews' => 1,
        'logo' => null,
        'style' => null,
        'licensedefault' => null,
        'licensemandatory' => 0,
        'dropdownmenu' => 0,
        'skins' => true
    );

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

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

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

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

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

        // 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];
103
        }
104

105
106
107
        return null;
    }

108

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

        // 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':
            case 'suspended':
            case 'licensemandatory':
            case 'expirymailsent':
                $value = $value ? 1 : 0;
                break;

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

        if (array_key_exists($name, self::$dbfields)) {
            if ($this->fields[$name] !== $value) {
                $this->fields[$name] = $value;
                $this->dirtyfields[$name] = true;
147
            }
148
        }
149
150
151
152
153
        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;
154
            }
155
156
            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.");
157
            }
158
159
160

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

168
    public function findByName($name) {
169

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
        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;
    }

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

        $this->name = $name;
196

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

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

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

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

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

230
231
232
233
234
235
236
237
238
239
240
241
242
            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;
243
        }
244
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
    }

    /**
     * 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;
273
274
275
    }

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

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

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

302
303
304
305
306
307
308
309
310
311
        $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');
            }
312
        }
313

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

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

363
        db_commit();
364
365
    }

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
    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();
405
406
407
408

        foreach ($users as $user) {
            remove_user_sessions($user->id);
        }
409
410
    }

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

446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
    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);
            }
        }
    }

468
469
470
471
472
473
474
475
476
477
    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);
478
        require_once('activity.php');
479
480
481
482
483
        activity_occurred('maharamessage', $message);
        handle_event('updateuser', $userid);
        db_commit();
    }

484
485
486
487
488
489
490
491
492
493
494
495
496
497
    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();
    }

498
499
    public function inviteUser($user) {
        $userid = is_object($user) ? $user->id : $user;
500
        db_begin();
501
502
503
504
505
506
        insert_record('usr_institution_request', (object) array(
            'usr' => $userid,
            'institution' => $this->name,
            'confirmedinstitution' => 1,
            'ctime' => db_format_timestamp(time())
        ));
507
        require_once('activity.php');
508
509
510
511
512
        activity_occurred('institutionmessage', (object) array(
            'messagetype' => 'invite',
            'users' => array($userid),
            'institution' => (object)array('name' => $this->name, 'displayname' => $this->displayname),
        ));
513
        handle_event('updateuser', $userid);
514
        db_commit();
515
516
    }

517
518
519
520
521
522
523
524
525
526
527
528
529
530
    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();
    }

531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
    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
        );
    }

552
553
554
    public function removeMembers($userids) {
        // Remove self last.
        global $USER;
555
556
557
558
559

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

560
        $users = get_records_select_array('usr', 'id IN (' . join(',', array_map('intval', $userids)) . ')');
561
        $removeself = false;
562
        db_begin();
563
564
565
566
567
568
569
570
571
572
        foreach ($users as $user) {
            if ($user->id == $USER->id) {
                $removeself = true;
                continue;
            }
            $this->removeMember($user);
        }
        if ($removeself) {
            $USER->leave_institution($this->name);
        }
573
        db_commit();
574
575
    }

576
    public function removeMember($user) {
577
578
579
580
581
        if (is_numeric($user)) {
            $user = get_record('usr', 'id', $user);
        }
        db_begin();
        // If the user is being authed by the institution they are
582
583
584
585
        // 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',
586
            "institution IN ('mahara', ?) AND active = 1",
587
588
589
            array($this->name),
            "institution = 'mahara' DESC, authname = 'internal' DESC"
        );
590
591
592
        $oldauth = $user->authinstance;
        if (isset($authinstances[$oldauth]) && $authinstances[$oldauth]->institution == $this->name) {
            foreach ($authinstances as $ai) {
593
                if ($ai->authname == 'internal' && $ai->institution == 'mahara') {
594
595
596
                    $user->authinstance = $ai->id;
                    break;
                }
597
598
599
600
                else if ($ai->institution == 'mahara') {
                    $user->authinstance = $ai->id;
                    break;
                }
601
602
603
604
605
606
607
608
            }
            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);
            }
609
610
611
612
613
614
615
616
            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')));
            }
617
618
            update_record('usr', $user);
        }
619
620
621
622
623
624
625
626
627
628
629
630

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

631
632
633
634
635
636
637
638
639
640
641
642
643
644
        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),
            )
        );

645
646
647
        // 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);

648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
        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),
671
                get_string('noinstitutionsetpassemailmessagetext', 'mahara', $fullname, $this->displayname, $sitename, $user->username, get_config('wwwroot'), $pwrequest->key, get_config('wwwroot'), $sitename, get_config('wwwroot'), $pwrequest->key),
672
                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)));
673
674
675
676
677
678
679
680
            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'));
        }
681
    }
682
683
684
685
686
687
688
689
690
691
692
693
694

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

    /**
Aaron Wells's avatar
Aaron Wells committed
697
     * Returns true if the institution already has its full quota of users
698
699
700
701
702
703
704
     * assigned to it.
     *
     * @return bool
     */
    public function isFull() {
        return ($this->maxuseraccounts != '') && ($this->countMembers() >= $this->maxuseraccounts);
    }
705

706
707
708
    /**
     * Returns the current institution admin member records
     *
709
     * @return array  A data structure containing site admins
710
711
712
713
714
     */
    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))) {
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
            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);
733
734
735
736
737
738
739
740
741
742
743
744
745
        }
        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))) {
746
            return array_map('extract_institution_user_id', $results);
747
748
749
750
        }
        return array();
    }

751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
    /**
     * 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) {
774
775
        if ($filter) {
            $where = '
776
            AND ii.name IN (' . join(',', array_map('db_quote', $filter)) . ')';
777
778
779
780
        }
        else {
            $where = '';
        }
781

782
        $querydata = explode(' ', preg_replace('/\s\s+/', ' ', strtolower(trim($query))));
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
        $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
        );

800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
        $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
823
824
                    ) a ON (a.name = ii.name)
                    WHERE (' . $namesql . ')' . $where . '
825
                    ORDER BY
826
                        ii.name = \'mahara\', ii.displayname', $queryvalues, $offset, $limit);
827

828
        if ($showdefault && $institutions && array_key_exists('mahara', $institutions)) {
829
830
831
            $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
832
                WHERE u.deleted = 0 AND i.usr IS NULL AND u.id != 0
833
834
835
836
837
838
839
            ', 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;
840
841
842
        }
        return $institutions;
    }
843
}
Richard Mansfield's avatar
Richard Mansfield committed
844

845
846
847
848
849
850
851
852
853
854
855
856
/**
 * 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
857
858
    global $USER;

859
    if (($assumesiteadmin || $USER->get('admin')) || ($includesitestaff && $USER->get('staff'))) {
860
        if ($includedefault) {
861
            $institutions = get_records_array('institution', '', '', 'displayname');
862
863
        }
        else {
864
            $institutions = get_records_select_array('institution', "name != 'mahara'", null, 'displayname');
865
        }
866
867
868
869
870
871
872
873
874
875
876
    }
    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()) {
877
878
879
880
881
        $institutions = get_records_select_array(
            'institution',
            'name IN (' . join(',', array_map('db_quote',$USER->get('admininstitutions'))) . ')',
            null, 'displayname'
        );
882
883
884
885
886
887
888
    }
    else if ($includeinstitutionstaff) {
        $institutions = get_records_select_array(
            'institution',
            'name IN (' . join(',', array_map('db_quote',$USER->get('staffinstitutions'))) . ')',
            null, 'displayname'
        );
889
890
    }
    else {
Richard Mansfield's avatar
Richard Mansfield committed
891
892
893
        return null;
    }

894
895
896
897
    if (empty($institutions)) {
        return null;
    }

898
    $options = array();
899
900
901
    if ($allselector) {
        $options['all'] = get_string('Allinstitutions', 'mahara');
    }
902
903
    foreach ($institutions as $i) {
        $options[$i->name] = $i->displayname;
Richard Mansfield's avatar
Richard Mansfield committed
904
    }
905
906
907
908
909
910
911
912
    $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
913
914
915
916

    return $institutionelement;
}

917
918
/* 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
919
920
   This function creates the form for the page. */
function institution_selector_for_page($institution, $page) {
921
922
923
924
    // Special case: $institution == 1 <-> any institution
    if ($institution == 1) {
        $institution = '';
    }
925
926
    $institutionelement = get_institution_selector(false);

927
    if (empty($institutionelement)) {
Richard Mansfield's avatar
Richard Mansfield committed
928
        return array('institution' => false, 'institutionselector' => null, 'institutionselectorjs' => '');
929
930
    }

931
932
933
934
935
936
937
    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
938

939
940
    $institutionselector = pieform(array(
        'name' => 'institutionselect',
941
        'class' => 'form-inline',
942
        'checkdirtychange' => false,
943
944
945
946
        'elements' => array(
            'institution' => $institutionelement,
        )
    ));
947
948

    $page = json_encode($page);
949
    $js = <<< EOF
950
951
952
953
954
955
956
957
958
959
960
961
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;
            }
962
        }
963
        window.location.href = urlstr;
964
    }
965
966
967

    if ($('#institutionselect_institution').length) {
        $('#institutionselect_institution').on('change', reloadUsers);
968
969
970
    }
});
EOF;
Aaron Wells's avatar
Aaron Wells committed
971

Richard Mansfield's avatar
Richard Mansfield committed
972
973
974
975
976
    return array(
        'institution'           => $institution,
        'institutionselector'   => $institutionselector,
        'institutionselectorjs' => $js
    );
977
}
978
979

function build_institutions_html($filter, $showdefault, $query, $limit, $offset, &$count=null) {
980
    global $USER, $CFG;
981
982

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

985
986
987
988

    $smarty = smarty_core();
    $smarty->assign('institutions', $institutions);
    $smarty->assign('siteadmin', $USER->get('admin'));
989
    $smarty->assign('webserviceconnections', (bool) count(webservice_connection_definitions()));
990
991
992
993
994
    $data['tablerows'] = $smarty->fetch('admin/users/institutionsresults.tpl');

    $pagination = build_pagination(array(
                'id' => 'adminstitutionslist_pagination',
                'datatable' => 'adminstitutionslist',
995
                'url' => get_config('wwwroot') . 'admin/users/institutions.php' . (($query != '') ? '?query=' . urlencode($query) : ''),
996
997
998
999
                'jsonscript' => 'admin/users/institutions.json.php',
                'count' => $count,
                'limit' => $limit,
                'offset' => $offset,
1000
                'setlimit' => true,
Eugene Venter's avatar
Eugene Venter committed
1001
                'jumplinks' => 4,
1002
1003
1004
1005
1006
1007
1008
1009
1010
                'resultcounttextsingular' => get_string('institution', 'admin'),
                'resultcounttextplural' => get_string('institutions', 'admin'),
            ));

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

    return $data;
}
1011
1012

function institution_display_name($name) {
1013
    return hsc(get_field('institution', 'displayname', 'name', $name));
1014
}
1015

1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
/**
 * 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;
}

1051
1052
1053
1054
1055
1056
1057
/**
 * Callback function to extract user ID from an object.
 * @param object $input
 */
function extract_institution_user_id($input) {
    return $input->id;
}
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
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
1112

/**
 * 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);
    }
}
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135

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