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