PageRenderTime 24ms CodeModel.GetById 2ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 0ms

/htdocs/lib/institution.php

https://gitlab.com/mahara-contrib/janrain-auth
PHP | 609 lines | 486 code | 55 blank | 68 comment | 105 complexity | ec7a1488db5688a6c7dd5c2c98e8bc8b MD5 | raw file
  1<?php
  2/**
  3 * Mahara: Electronic portfolio, weblog, resume builder and social networking
  4 * Copyright (C) 2006-2009 Catalyst IT Ltd and others; see:
  5 *                         http://wiki.mahara.org/Contributors
  6 *
  7 * This program is free software: you can redistribute it and/or modify
  8 * it under the terms of the GNU General Public License as published by
  9 * the Free Software Foundation, either version 3 of the License, or
 10 * (at your option) any later version.
 11 *
 12 * This program is distributed in the hope that it will be useful,
 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15 * GNU General Public License for more details.
 16 *
 17 * You should have received a copy of the GNU General Public License
 18 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 19 *
 20 * @package    mahara
 21 * @subpackage auth-internal
 22 * @author     Catalyst IT Ltd
 23 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
 24 * @copyright  (C) 2006-2009 Catalyst IT Ltd http://catalyst.net.nz
 25 *
 26 */
 27
 28// TODO : lib
 29
 30defined('INTERNAL') || die();
 31
 32class Institution {
 33
 34    const   UNINITIALIZED  = 0;
 35    const   INITIALIZED    = 1;
 36    const   PERSISTENT     = 2;
 37
 38    protected $initialized = self::UNINITIALIZED;
 39    protected $members = array(
 40        'name' => '',
 41        'displayname' => '',
 42        'registerallowed' => 1,
 43        'theme' => 'default',
 44        'defaultmembershipperiod' => 0,
 45        'maxuseraccounts' => null
 46        ); 
 47
 48    function __construct($name = null) {
 49        if (is_null($name)) {
 50            return $this;
 51        }
 52
 53        if (!$this->findByName($name)) {
 54            throw new ParamOutOfRangeException('No such institution');
 55        }
 56    }
 57
 58    function __get($name) {
 59        if (array_key_exists($name, $this->members)) {
 60            return $this->members[$name];
 61        }
 62        return null;
 63    }
 64
 65    public function __set($name, $value) {
 66        if (!is_string($name) | !array_key_exists($name, $this->members)) {
 67            throw new ParamOutOfRangeException();
 68        }
 69        if ($name == 'name') {
 70            if (!is_string($value) || empty($value) || strlen($value) > 255) {
 71                throw new ParamOutOfRangeException("'name' should be a string between 1 and 255 characters in length");
 72            }
 73        } elseif ($name == 'displayname') {
 74            if (!is_string($value) || empty($value) || strlen($value) > 255) {
 75                throw new ParamOutOfRangeException("'displayname' ($value) should be a string between 1 and 255 characters in length");
 76            }
 77        } elseif ($name == 'registerallowed') {
 78            if (!is_numeric($value) || $value < 0 || $value > 1) {
 79                throw new ParamOutOfRangeException("'registerallowed' should be zero or one");
 80            }
 81        } elseif ($name == 'theme') {
 82            if (!empty($value) && is_string($value) && strlen($value) > 255) {
 83                throw new ParamOutOfRangeException("'theme' ($value) should be less than 255 characters in length");
 84            }
 85        } elseif ($name == 'defaultmembershipperiod') {
 86            if (!empty($value) && (!is_numeric($value) || $value < 0 || $value > 9999999999)) {
 87                throw new ParamOutOfRangeException("'defaultmembershipperiod' should be a number between 1 and 9,999,999,999");
 88            }
 89        } elseif ($name == 'maxuseraccounts') {
 90            if (!empty($value) && (!is_numeric($value) || $value < 0 || $value > 9999999999)) {
 91                throw new ParamOutOfRangeException("'maxuseraccounts' should be a number between 1 and 9,999,999,999");
 92            }
 93        }
 94        $this->members[$name] = $value;
 95    }
 96
 97    function findByName($name) {
 98
 99        if (!is_string($name) || strlen($name) < 1 || strlen($name) > 255) {
100            throw new ParamOutOfRangeException("'name' must be a string.");
101        }
102
103        $result = get_record('institution', 'name', $name);
104
105        if (false == $result) {
106            return false;
107        }
108
109        $this->initialized = self::PERSISTENT;
110        $this->populate($result);
111
112        return $this;
113    }
114
115    function initialise($name, $displayname) {
116        if (empty($name) || !is_string($name)) {
117            return false;
118        }
119
120        $this->name = $name;
121        if (empty($displayname) || !is_string($displayname)) {
122            return false;
123        }
124
125        $this->displayname = $displayname;
126        $this->initialized = max(self::INITIALIZED, $this->initialized);
127        return true;
128    }
129
130    function verifyReady() {
131        if (empty($this->members['name']) || !is_string($this->members['name'])) {
132            return false;
133        }
134        if (empty($this->members['displayname']) || !is_string($this->members['displayname'])) {
135            return false;
136        }
137        $this->initialized = max(self::INITIALIZED, $this->initialized);
138        return true;
139    }
140
141    function commit() {
142        if (!$this->verifyReady()) {
143            throw new SystemException('Commit failed');
144        }
145
146        $record = new stdClass();
147        $record->name                         = $this->name;
148        $record->displayname                  = $this->displayname;
149        $record->theme                        = $this->theme;
150        $record->defaultmembershipperiod      = $this->defaultmembershipperiod;
151        $record->maxuseraccounts              = $this->maxuseraccounts;
152
153        if ($this->initialized == self::INITIALIZED) {
154            return insert_record('institution', $record);
155        } elseif ($this->initialized == self::PERSISTENT) {
156            return update_record('institution', $record, array('name' => $this->name));
157        }
158        // Shouldn't happen but who noes?
159        return false;
160    }
161
162    protected function populate($result) {
163        $this->name                         = $result->name;
164        $this->displayname                  = $result->displayname;
165        $this->registerallowed              = $result->registerallowed;
166        $this->theme                        = $result->theme;
167        $this->defaultmembershipperiod      = $result->defaultmembershipperiod;
168        $this->maxuseraccounts              = $result->maxuseraccounts;
169        $this->verifyReady();
170    }
171
172    public function addUserAsMember($user) {
173        global $USER;
174        if ($this->isFull()) {
175            throw new SystemException('Trying to add a user to an institution that already has a full quota of members');
176        }
177        if (is_numeric($user)) {
178            $user = get_record('usr', 'id', $user);
179        }
180        if ($user instanceof User) {
181            $lang = $user->get_account_preference('lang');
182            if (empty($lang) || $lang == 'default') {
183                $lang = get_config('lang');
184            }
185        }
186        else { // stdclass object
187            $lang = get_user_language($user->id);
188        }
189        $userinst = new StdClass;
190        $userinst->institution = $this->name;
191        $studentid = get_field('usr_institution_request', 'studentid', 'usr', $user->id, 
192                               'institution', $this->name);
193        if (!empty($studentid)) {
194            $userinst->studentid = $studentid;
195        }
196        else if (!empty($user->studentid)) {
197            $userinst->studentid = $user->studentid;
198        }
199        $userinst->usr = $user->id;
200        $now = time();
201        $userinst->ctime = db_format_timestamp($now);
202        $defaultexpiry = $this->defaultmembershipperiod;
203        if (!empty($defaultexpiry)) {
204            $userinst->expiry = db_format_timestamp($now + $defaultexpiry);
205        }
206        $message = (object) array(
207            'users' => array($user->id),
208            'subject' => get_string_from_language($lang, 'institutionmemberconfirmsubject'),
209            'message' => get_string_from_language($lang, 'institutionmemberconfirmmessage', 'mahara', $this->displayname),
210        );
211        db_begin();
212        if (!get_config('usersallowedmultipleinstitutions')) {
213            delete_records('usr_institution', 'usr', $user->id);
214            delete_records('usr_institution_request', 'usr', $user->id);
215        }
216        insert_record('usr_institution', $userinst);
217        delete_records('usr_institution_request', 'usr', $userinst->usr, 'institution', $this->name);
218        // Copy institution views to the user's portfolio
219        $checkviewaccess = empty($user->newuser) && !$USER->get('admin');
220        $userobj = new User();
221        $userobj->find_by_id($user->id);
222        $userobj->copy_views(get_column('view', 'id', 'institution', $this->name, 'copynewuser', 1), $checkviewaccess);
223        require_once('activity.php');
224        activity_occurred('maharamessage', $message);
225        handle_event('updateuser', $userinst->usr);
226        db_commit();
227    }
228
229    public function addRequestFromUser($user, $studentid = null) {
230        $request = get_record('usr_institution_request', 'usr', $user->id, 'institution', $this->name);
231        if (!$request) {
232            $request = (object) array(
233                'usr'          => $user->id,
234                'institution'  => $this->name,
235                'confirmedusr' => 1,
236                'studentid'    => empty($studentid) ? $user->studentid : $studentid,
237                'ctime'        => db_format_timestamp(time())
238            );
239            $message = (object) array(
240                'messagetype' => 'request',
241                'username' => $user->username,
242                'fullname' => $user->firstname . ' ' . $user->lastname,
243                'institution' => (object)array('name' => $this->name, 'displayname' => $this->displayname),
244            );
245            db_begin();
246            if (!get_config('usersallowedmultipleinstitutions')) {
247                delete_records('usr_institution_request', 'usr', $user->id);
248            }
249            insert_record('usr_institution_request', $request);
250            require_once('activity.php');
251            activity_occurred('institutionmessage', $message);
252            handle_event('updateuser', $user->id);
253            db_commit();
254        } else if ($request->confirmedinstitution) {
255            $this->addUserAsMember($user);
256        }
257    }
258
259    public function declineRequestFromUser($userid) {
260        $lang = get_user_language($userid);
261        $message = (object) array(
262            'users' => array($userid),
263            'subject' => get_string_from_language($lang, 'institutionmemberrejectsubject'),
264            'message' => get_string_from_language($lang, 'institutionmemberrejectmessage', 'mahara', $this->displayname),
265        );
266        db_begin();
267        delete_records('usr_institution_request', 'usr', $userid, 'institution', $this->name,
268                       'confirmedusr', 1);
269        require_once('activity.php');
270        activity_occurred('maharamessage', $message);
271        handle_event('updateuser', $userid);
272        db_commit();
273    }
274
275    public function inviteUser($user) {
276        $userid = is_object($user) ? $user->id : $user;
277        db_begin();
278        insert_record('usr_institution_request', (object) array(
279            'usr' => $userid,
280            'institution' => $this->name,
281            'confirmedinstitution' => 1,
282            'ctime' => db_format_timestamp(time())
283        ));
284        require_once('activity.php');
285        activity_occurred('institutionmessage', (object) array(
286            'messagetype' => 'invite',
287            'users' => array($userid),
288            'institution' => (object)array('name' => $this->name, 'displayname' => $this->displayname),
289        ));
290        handle_event('updateuser', $userid);
291        db_commit();
292    }
293
294    public function removeMembers($userids) {
295        // Remove self last.
296        global $USER;
297        $users = get_records_select_array('usr', 'id IN (' . join(',', array_map('intval', $userids)) . ')');
298        $removeself = false;
299        foreach ($users as $user) {
300            if ($user->id == $USER->id) {
301                $removeself = true;
302                continue;
303            }
304            $this->removeMember($user);
305        }
306        if ($removeself) {
307            $USER->leave_institution($this->name);
308        }
309    }
310
311    public function removeMember($user) {
312        if (is_numeric($user)) {
313            $user = get_record('usr', 'id', $user);
314        }
315        db_begin();
316        // If the user is being authed by the institution they are
317        // being removed from, change them to internal auth, or if
318        // we can't find that, some other no institution auth.
319        $authinstances = get_records_select_assoc(
320            'auth_instance',
321            "institution IN ('mahara', ?)",
322            array($this->name),
323            "institution = 'mahara' DESC, authname = 'internal' DESC"
324        );
325        $oldauth = $user->authinstance;
326        if (isset($authinstances[$oldauth]) && $authinstances[$oldauth]->institution == $this->name) {
327            foreach ($authinstances as $ai) {
328                if ($ai->authname == 'internal' && $ai->institution == 'mahara') {
329                    $user->authinstance = $ai->id;
330                    break;
331                }
332                else if ($ai->institution == 'mahara') {
333                    $user->authinstance = $ai->id;
334                    break;
335                }
336            }
337            delete_records('auth_remote_user', 'authinstance', $oldauth, 'localusr', $user->id);
338            // If the old authinstance was external, the user may need
339            // to set a password
340            if ($user->password == '') {
341                log_debug('resetting pw for '.$user->id);
342                $this->removeMemberSetPassword($user);
343            }
344            update_record('usr', $user);
345        }
346        delete_records('usr_institution', 'usr', $user->id, 'institution', $this->name);
347        handle_event('updateuser', $user->id);
348        db_commit();
349    }
350
351    /**
352     * Reset user's password, and send them a password change email
353     */
354    private function removeMemberSetPassword(&$user) {
355        global $SESSION, $USER;
356        if ($user->id == $USER->id) {
357            $user->passwordchange = 1;
358            return;
359        }
360        try {
361            $pwrequest = new StdClass;
362            $pwrequest->usr = $user->id;
363            $pwrequest->expiry = db_format_timestamp(time() + 86400);
364            $pwrequest->key = get_random_key();
365            $sitename = get_config('sitename');
366            $fullname = display_name($user, null, true);
367            email_user($user, null,
368                get_string('noinstitutionsetpassemailsubject', 'mahara', $sitename, $this->displayname),
369                get_string('noinstitutionsetpassemailmessagetext', 'mahara', $fullname, $this->displayname, $sitename, $user->username, get_config('wwwroot'), $pwrequest->key, get_config('wwwroot'), $sitename, get_config('wwwroot'), $pwrequest->key),
370                get_string('noinstitutionsetpassemailmessagehtml', 'mahara', $fullname, $this->displayname, $sitename, $user->username, get_config('wwwroot'), $pwrequest->key, get_config('wwwroot'), $pwrequest->key, get_config('wwwroot'), $sitename, get_config('wwwroot'), $pwrequest->key, get_config('wwwroot'), $pwrequest->key));
371            insert_record('usr_password_request', $pwrequest);
372        }
373        catch (SQLException $e) {
374            $SESSION->add_error_msg(get_string('forgotpassemailsendunsuccessful'));
375        }
376        catch (EmailException $e) {
377            $SESSION->add_error_msg(get_string('forgotpassemailsendunsuccessful'));
378        }
379    }
380
381    public function countMembers() {
382        return count_records_sql('
383            SELECT COUNT(*) FROM {usr} u INNER JOIN {usr_institution} i ON u.id = i.usr
384            WHERE i.institution = ? AND u.deleted = 0', array($this->name));
385    }
386
387    public function countInvites() {
388        return count_records_sql('
389            SELECT COUNT(*) FROM {usr} u INNER JOIN {usr_institution_request} r ON u.id = r.usr
390            WHERE r.institution = ? AND u.deleted = 0 AND r.confirmedinstitution = 1',
391            array($this->name));
392    }
393
394    /**
395     * Returns true if the institution already has its full quota of users 
396     * assigned to it.
397     *
398     * @return bool
399     */
400    public function isFull() {
401        return ($this->maxuseraccounts != '') && ($this->countMembers() >= $this->maxuseraccounts);
402    }
403
404    /**
405     * Returns the list of institutions, implements institution searching
406     *
407     * @param array   Limit the output to only institutions in this array (used for institution admins).
408     * @param bool    Whether default institution should be listed in results.
409     * @param string  Searching query string.
410     * @param int     Limit of results (used for pagination).
411     * @param int     Offset of results (used for pagination).
412     * @param int     Returns the total number of results.
413     * @return array  A data structure containing results looking like ...
414     *   $institutions = array(
415     *                       name => array(
416     *                           displayname     => string
417     *                           maxuseraccounts => integer
418     *                           members         => integer
419     *                           staff           => integer
420     *                           admins          => integer
421     *                           name            => string
422     *                       ),
423     *                       name => array(...),
424     *                   );
425     */
426    public static function count_members($filter, $showdefault, $query='', $limit=null, $offset=null, &$count=null) {
427        if ($filter) {
428            $where = '
429            AND ii.name IN (' . join(',', array_map('db_quote', $filter)) . ')';
430        }
431        else {
432            $where = '';
433        }
434
435        $querydata = split(' ', preg_replace('/\s\s+/', ' ', strtolower(trim($query))));
436        $namesql = '(
437                ii.name ' . db_ilike() . ' \'%\' || ? || \'%\'
438            )
439            OR (
440                ii.displayname ' . db_ilike() . ' \'%\' || ? || \'%\'
441            )';
442        $namesql = join(' OR ', array_fill(0, count($querydata), $namesql));
443        $queryvalues = array();
444        foreach ($querydata as $w) {
445            $queryvalues = array_pad($queryvalues, count($queryvalues) + 2, $w);
446        }
447
448        $count = count_records_sql('SELECT COUNT(ii.name)
449            FROM {institution} ii
450            WHERE' . $namesql, $queryvalues
451        );
452
453        $institutions = get_records_sql_assoc('
454            SELECT
455                ii.name,
456                ii.displayname,
457                ii.maxuseraccounts,
458                ii.suspended,
459                COALESCE(a.members, 0) AS members,
460                COALESCE(a.staff, 0) AS staff,
461                COALESCE(a.admins, 0) AS admins
462            FROM
463                {institution} ii
464                LEFT JOIN
465                    (SELECT
466                        i.name, i.displayname, i.maxuseraccounts,
467                        COUNT(ui.usr) AS members, SUM(ui.staff) AS staff, SUM(ui.admin) AS admins
468                    FROM
469                        {institution} i
470                        LEFT OUTER JOIN {usr_institution} ui ON (ui.institution = i.name)
471                        LEFT OUTER JOIN {usr} u ON (u.id = ui.usr)
472                    WHERE
473                        (u.deleted = 0 OR u.id IS NULL)
474                    GROUP BY
475                        i.name, i.displayname, i.maxuseraccounts
476                    ) a ON (a.name = ii.name)
477                    WHERE (' . $namesql . ')' . $where . '
478                    ORDER BY
479                        ii.name = \'mahara\', ii.displayname', $queryvalues, $offset, $limit);
480
481        if ($showdefault && $institutions && array_key_exists('mahara', $institutions)) {
482            $defaultinstmembers = count_records_sql('
483                SELECT COUNT(u.id) FROM {usr} u LEFT OUTER JOIN {usr_institution} i ON u.id = i.usr
484                WHERE u.deleted = 0 AND i.usr IS NULL AND u.id != 0
485            ');
486            $institutions['mahara']->members = $defaultinstmembers;
487            $institutions['mahara']->staff   = '';
488            $institutions['mahara']->admins  = '';
489        }
490        return $institutions;
491    }
492}
493
494function get_institution_selector($includedefault = true) {
495    global $USER;
496
497    if ($USER->get('admin')) {
498        if ($includedefault) {
499            $institutions = get_records_array('institution', '', '', 'displayname');
500        }
501        else {
502            $institutions = get_records_select_array('institution', "name != 'mahara'", null, 'displayname');
503        }
504    } else if ($USER->is_institutional_admin()) {
505        $institutions = get_records_select_array(
506            'institution',
507            'name IN (' . join(',', array_map('db_quote',$USER->get('admininstitutions'))) . ')',
508            null, 'displayname'
509        );
510    } else {
511        return null;
512    }
513
514    if (empty($institutions)) {
515        return null;
516    }
517
518    $options = array();
519    foreach ($institutions as $i) {
520        $options[$i->name] = $i->displayname;
521    }
522    $institution = key($options);
523    $institutionelement = array(
524        'type' => 'select',
525        'title' => get_string('institution'),
526        'defaultvalue' => $institution,
527        'options' => $options,
528        'rules' => array('regex' => '/^[a-zA-Z0-9]+$/')
529    );
530
531    return $institutionelement;
532}
533
534/* The institution selector does exactly the same thing in both
535   institutionadmins.php and institutionstaff.php (in /admin/users/).
536   This function creates the form for the page. */
537function institution_selector_for_page($institution, $page) {
538    require_once('pieforms/pieform.php');
539    $institutionelement = get_institution_selector(false);
540
541    if (empty($institutionelement)) {
542        return array('institution' => false, 'institutionselector' => null, 'institutionselectorjs' => '');
543    }
544
545    global $USER;
546    if (empty($institution) || !$USER->can_edit_institution($institution)) {
547        $institution = empty($institutionelement['value']) ? $institutionelement['defaultvalue'] : $institutionelement['value'];
548    }
549    else {
550        $institutionelement['defaultvalue'] = $institution;
551    }
552    
553    $institutionselector = pieform(array(
554        'name' => 'institutionselect',
555        'elements' => array(
556            'institution' => $institutionelement,
557        )
558    ));
559    
560    $js = <<< EOF
561function reloadUsers() {
562    var inst = '';
563    if ($('institutionselect_institution')) {
564        inst = '?institution='+$('institutionselect_institution').value;
565    }
566    window.location.href = '{$page}'+inst;
567}
568addLoadEvent(function() {
569    if ($('institutionselect_institution')) {
570        connect($('institutionselect_institution'), 'onchange', reloadUsers);
571    }
572});
573EOF;
574    
575    return array(
576        'institution'           => $institution,
577        'institutionselector'   => $institutionselector,
578        'institutionselectorjs' => $js
579    );
580}
581
582function build_institutions_html($filter, $showdefault, $query, $limit, $offset, &$count=null) {
583    global $USER;
584
585    $institutions = Institution::count_members($filter, $showdefault, $query, $limit, $offset, $count);
586
587    $smarty = smarty_core();
588    $smarty->assign('institutions', $institutions);
589    $smarty->assign('siteadmin', $USER->get('admin'));
590    $data['tablerows'] = $smarty->fetch('admin/users/institutionsresults.tpl');
591
592    $pagination = build_pagination(array(
593                'id' => 'adminstitutionslist_pagination',
594                'datatable' => 'adminstitutionslist',
595                'url' => get_config('wwwroot') . 'admin/users/institutions.php' . (!empty($query) ? '?query=' . urlencode($query) : ''),
596                'jsonscript' => 'admin/users/institutions.json.php',
597                'count' => $count,
598                'limit' => $limit,
599                'offset' => $offset,
600                'resultcounttextsingular' => get_string('institution', 'admin'),
601                'resultcounttextplural' => get_string('institutions', 'admin'),
602            ));
603
604    $data['pagination'] = $pagination['html'];
605    $data['pagination_js'] = $pagination['javascript'];
606
607    return $data;
608}
609?>