PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/src/legacy/util/UserUtil.php

https://github.com/antoniom/core
PHP | 2009 lines | 1028 code | 267 blank | 714 comment | 374 complexity | 0ead410f6ad0525ddc0a349f33f8d529 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-3.0, MIT
  1. <?php
  2. /**
  3. * Copyright Zikula Foundation 2009 - Zikula Application Framework
  4. *
  5. * This work is contributed to the Zikula Foundation under one or more
  6. * Contributor Agreements and licensed to You under the following license:
  7. *
  8. * @license GNU/LGPv3 (or at your option any later version).
  9. * @package Util
  10. *
  11. * Please see the NOTICE file distributed with this source code for further
  12. * information regarding copyright and licensing.
  13. */
  14. use Zikula\Core\Event\GenericEvent;
  15. use UsersModule\Constants as UsersConstant;
  16. use Zikula\Framework\Exception\FatalException;
  17. use Zikula\Framework\Exception\RedirectException;
  18. use Zikula\Framework\Exception\ForbiddenException;
  19. /**
  20. * UserUtil
  21. */
  22. class UserUtil
  23. {
  24. /**
  25. * Cache for groups.
  26. *
  27. * @var array
  28. */
  29. protected static $groupCache = array();
  30. /**
  31. * Clear group cache.
  32. *
  33. * @return void
  34. */
  35. public function clearGroupCache()
  36. {
  37. self::$groupCache = array();
  38. }
  39. /**
  40. * Return a hash structure mapping uid to username.
  41. *
  42. * @param string $where The where clause to use (optional, default=array()).
  43. * @param string $orderBy The order by clause to use (optional, default=array()).
  44. * @param integer $limitOffset The select-limit offset (optional, default=null).
  45. * @param integer $limitNumRows The number of rows to fetch (optional, default=null).
  46. * @param string $assocKey The associative key to apply (optional) (default='uid').
  47. *
  48. * @deprecated since 1.3.0
  49. *
  50. * @return array An array mapping uid to username.
  51. */
  52. public static function getUsers($where = array(), $orderBy = array(), $limitOffset = null, $limitNumRows = null, $assocKey = 'uid')
  53. {
  54. $em = \ServiceUtil::get('doctrine')->getManager();
  55. $users = $em->getRepository('UsersModule\Entity\User')->findBy($where, $orderBy, $limitNumRows, $limitOffset);
  56. $items = array();
  57. foreach ($users as $user) {
  58. $items[$user[$assocKey]] = $user->toArray();
  59. }
  60. return $items;
  61. }
  62. /**
  63. * Return a group object.
  64. *
  65. * @param integer $gid The groupID to retrieve.
  66. *
  67. * @todo Decouple UserUtil and Groups?
  68. *
  69. * @return array The resulting group object.
  70. */
  71. public static function getGroup($gid)
  72. {
  73. return ModUtil::apiFunc('GroupsModule', 'user', 'get', array('gid' => $gid));
  74. }
  75. /**
  76. * Return a hash structure mapping gid to groupname.
  77. *
  78. * @param string $where The where clause to use (optional) (default=array()).
  79. * @param string $orderBy The order by clause to use (optional) (default=array()).
  80. * @param integer $limitOffset The select-limit offset (optional) (default=null).
  81. * @param integer $limitNumRows The number of rows to fetch (optional) (default=null).
  82. * @param string $assocKey The associative key to apply (optional) (default='gid').
  83. *
  84. * @return array An array mapping gid to groupname.
  85. */
  86. public static function getGroups($where = array(), $orderBy = array(), $limitOffset = null, $limitNumRows = null, $assocKey='gid')
  87. {
  88. $em = \ServiceUtil::get('doctrine')->getManager();
  89. $groups = $em->getRepository('GroupsModule\Entity\Group')->findBy($where, $orderBy, $limitNumRows, $limitOffset);
  90. $items = array();
  91. foreach ($groups as $group) {
  92. $items[$group[$assocKey]] = $group->toArray();
  93. }
  94. return $items;
  95. }
  96. /**
  97. * Return a (string) list of user-ids which can then be used in a SQL 'IN (...)' clause.
  98. *
  99. * @param string $where The where clause to use (optional).
  100. * @param string $orderBy The order by clause to use (optional).
  101. * @param string $separator The field separator to use (default=",") (optional).
  102. *
  103. * @return string A string list of user ids.
  104. */
  105. public static function getUserIdList($where = '', $orderBy = '', $separator = ',')
  106. {
  107. $userdata = self::getUsers($where, $orderBy);
  108. $list = '-1';
  109. if ($userdata && count($userdata)) {
  110. $uids = array_keys($userdata);
  111. sort($uids);
  112. $list = implode((string)$separator, $uids);
  113. }
  114. return $list;
  115. }
  116. /**
  117. * Return a (string) list of group-ids which can then be used in a SQL 'IN (...)' clause.
  118. *
  119. * @param string $where The where clause to use (optional).
  120. * @param string $orderBy The order by clause to use (optional).
  121. * @param string $separator The field separator to use (default=",") (optional).
  122. *
  123. * @return string A string list of group ids.
  124. */
  125. public static function getGroupIdList($where = array(), $orderBy = array(), $separator = ',')
  126. {
  127. $groupdata = self::getGroups($where, $orderBy);
  128. $list = '';
  129. if ($groupdata && count($groupdata)) {
  130. $gids = array_keys($groupdata);
  131. sort($gids);
  132. $list = implode((string)$separator, $gids);
  133. }
  134. return $list;
  135. }
  136. /**
  137. * Return an array group-ids for the specified user.
  138. *
  139. * @param integer $uid The user ID for which we want the groups.
  140. *
  141. * @return array An array of group IDs.
  142. */
  143. public static function getGroupsForUser($uid)
  144. {
  145. if (empty($uid)) {
  146. return array();
  147. }
  148. return ModUtil::apiFunc('GroupsModule', 'user', 'getusergroups', array('uid' => $uid, 'clean' => true));
  149. }
  150. /**
  151. * Return a string list of group-ids for the specified user.
  152. *
  153. * @param integer $uid The user ID for which we want the groups.
  154. * @param string $separator The field separator to use (default=",") (optional).
  155. *
  156. * @return string A string list of group ids.
  157. */
  158. public static function getGroupListForUser($uid = null, $separator = ',')
  159. {
  160. if (!$uid) {
  161. $uid = self::getVar('uid');
  162. }
  163. if (!$uid) {
  164. return '-1';
  165. }
  166. if (!isset(self::$groupCache[$uid])) {
  167. $gidArray = self::getGroupsForUser($uid);
  168. if ($gidArray && (bool)count($gidArray)) {
  169. sort($gidArray);
  170. self::$groupCache[$uid] = implode((string)$separator, $gidArray);
  171. } else {
  172. self::$groupCache[$uid] = '-1';
  173. }
  174. }
  175. return self::$groupCache[$uid];
  176. }
  177. /**
  178. * Return a string list of user-ids for the specified group.
  179. *
  180. * @param integer $gid The group ID for which we want the users.
  181. *
  182. * @return array An array of user IDs.
  183. */
  184. public static function getUsersForGroup($gid)
  185. {
  186. if (!$gid) {
  187. return array();
  188. }
  189. $group = ModUtil::apiFunc('GroupsModule', 'user', 'get', array('gid' => $gid));
  190. $members = $group['members'];
  191. $uids = array();
  192. foreach ($members as $uid => $membership) {
  193. $uids[] = $uid;
  194. }
  195. return $uids;
  196. }
  197. /**
  198. * Get a unique string for a user, depending on this group memberships.
  199. *
  200. * String ready to be used as part of the CacheID of the output views.
  201. * Useful when there aren't another user-based access privilegies, just group permissions.
  202. *
  203. * @param integer $uid User ID to get the group memberships from. Default: current user.
  204. *
  205. * @return string Cache GIDs string to use on Zikula_View.
  206. */
  207. public static function getGidCacheString($uid = null)
  208. {
  209. $str = UserUtil::getGroupListForUser($uid, '_');
  210. return $str == '-1' ? 'guest' : 'groups_'.$str;
  211. }
  212. /**
  213. * Get a unique string for a user, based on the uid.
  214. *
  215. * String ready to be used as part of the CacheID of the output views.
  216. * Useful for user-based access privilegies.
  217. *
  218. * @param integer $uid User ID to get string from. Default: current user.
  219. *
  220. * @return string Cache UID string to use on Zikula_View.
  221. */
  222. public static function getUidCacheString($uid = null)
  223. {
  224. $uid = $uid ? (int)$uid : self::getVar('uid');
  225. return !$uid ? 'guest' : 'uid_'.$uid;
  226. }
  227. /**
  228. * Return the defined dynamic user data fields.
  229. *
  230. * @return array An array of dynamic data field definitions.
  231. */
  232. public static function getDynamicDataFields()
  233. {
  234. // decide if we have to use the (obsolete) DUDs from the Profile module
  235. $profileModule = System::getVar('profilemodule', '');
  236. if (empty($profileModule) || $profileModule != 'Profile' || !ModUtil::available($profileModule)) {
  237. return array();
  238. }
  239. return DBUtil::selectObjectArray('user_property');
  240. }
  241. /**
  242. * Return a array structure for the user group selector.
  243. *
  244. * @param mixed $defaultValue The default value of the selector (default=0) (optional).
  245. * @param string $defaultText The text of the default value (optional).
  246. * @param array $ignore An array of keys to ignore (optional).
  247. * @param mixed $includeAll Whether to include an "All" choice (optional).
  248. * @param string $allText The text to display for the "All" choice (optional).
  249. *
  250. * @return array The array structure for the user group selector.
  251. */
  252. public static function getSelectorData_Group($defaultValue = 0, $defaultText = '', $ignore = array(), $includeAll = 0, $allText = '')
  253. {
  254. $dropdown = array();
  255. if ($defaultText) {
  256. $dropdown[] = array('id' => $defaultValue, 'name' => $defaultText);
  257. }
  258. $groupdata = self::getGroups(array(), array('name' => 'ASC'));
  259. if (!$groupdata || !count($groupdata)) {
  260. return $dropdown;
  261. }
  262. if ($includeAll) {
  263. $dropdown[] = array('id' => $includeAll, 'name' => $allText);
  264. }
  265. foreach (array_keys($groupdata) as $gid) {
  266. if (!isset($ignore[$gid])) {
  267. $gname = $groupdata[$gid]['name'];
  268. $dropdown[$gname] = array('id' => $gid, 'name' => $gname);
  269. }
  270. }
  271. ksort($dropdown);
  272. return $dropdown;
  273. }
  274. /**
  275. * Return a array strcuture for the user dropdown box.
  276. *
  277. * @param miexed $defaultValue The default value of the selector (optional) (default=0).
  278. * @param string $defaultText The text of the default value (optional) (default='').
  279. * @param array $ignore An array of keys to ignore (optional) (default=array()).
  280. * @param miexed $includeAll Whether to include an "All" choice (optional) (default=0).
  281. * @param string $allText The text to display for the "All" choice (optional) (default='').
  282. * @param string $exclude An SQL IN-LIST string to exclude specified uids.
  283. *
  284. * @return array The array structure for the user group selector.
  285. */
  286. public static function getSelectorData_User($defaultValue = 0, $defaultText = '', $ignore = array(), $includeAll = 0, $allText = '', $exclude = '')
  287. {
  288. $dropdown = array();
  289. if ($defaultText) {
  290. $dropdown[] = array('id' => $defaultValue, 'name' => $defaultText);
  291. }
  292. $where = '';
  293. if ($exclude) {
  294. $where = "WHERE uid NOT IN (" . DataUtil::formatForStore($exclude) . ")";
  295. }
  296. $userdata = self::getUsers($where, 'ORDER BY uname');
  297. if (!$userdata || !count($userdata)) {
  298. return $dropdown;
  299. }
  300. if ($includeAll) {
  301. $dropdown[] = array('id' => $includeAll, 'name' => $allText);
  302. }
  303. foreach (array_keys($userdata) as $uid) {
  304. if (!isset($ignore[$uid])) {
  305. $uname = $userdata[$uid]['uname'];
  306. $dropdown[$uname] = array('id' => $uid, 'name' => $uname);
  307. }
  308. }
  309. ksort($uname);
  310. return $dropdown;
  311. }
  312. /**
  313. * Retrieve the account recovery information for a user from the various authentication modules.
  314. *
  315. * @param numeric $uid The user id of the user for which account recovery information should be retrieved; optional, defaults to the
  316. * currently logged in user (an exception occurs if the current user is not logged in).
  317. *
  318. * @return array An array of account recovery information.
  319. *
  320. * @throws FatalException If the $uid parameter is not valid.
  321. */
  322. public static function getUserAccountRecoveryInfo($uid = -1)
  323. {
  324. if (!isset($uid) || !is_numeric($uid) || ((string)((int)$uid) != $uid) || (($uid < -1) || ($uid == 0) || ($uid == 1))) {
  325. throw new FatalException('Attempt to get authentication information for an invalid user id.');
  326. }
  327. if ($uid == -1) {
  328. if (self::isLoggedIn()) {
  329. $uid = self::getVar('uid');
  330. } else {
  331. throw new FatalException('Attempt to get authentication information for an invalid user id.');
  332. }
  333. }
  334. $userAuthenticationInfo = array();
  335. $authenticationModules = ModUtil::getModulesCapableOf(UsersConstant::CAPABILITY_AUTHENTICATION);
  336. if ($authenticationModules) {
  337. $accountRecoveryArgs = array (
  338. 'uid' => $uid,
  339. );
  340. foreach ($authenticationModules as $authenticationModule) {
  341. $moduleUserAuthenticationInfo = ModUtil::apiFunc($authenticationModule['name'], 'authentication', 'getAccountRecoveryInfoForUid', $accountRecoveryArgs, 'Zikula_Api_AbstractAuthentication');
  342. if (is_array($moduleUserAuthenticationInfo)) {
  343. $userAuthenticationInfo = array_merge($userAuthenticationInfo, $moduleUserAuthenticationInfo);
  344. }
  345. }
  346. }
  347. return $userAuthenticationInfo;
  348. }
  349. /**
  350. * Login.
  351. *
  352. * @param string $loginID Login Id.
  353. * @param string $userEnteredPassword The Password.
  354. * @param boolean $rememberme Whether or not to remember login.
  355. * @param boolean $checkPassword Whether or not to check the password.
  356. *
  357. * @return boolean
  358. */
  359. public static function login($loginID, $userEnteredPassword, $rememberme = false, $checkPassword = true)
  360. {
  361. LogUtil::log(__f('Warning! Function %1$s is deprecated. Please use %2$s instead.', array(__METHOD__, 'UserUtil::loginUsing()')), E_USER_DEPRECATED);
  362. $authenticationInfo = array(
  363. 'login_id' => $loginID,
  364. 'pass' => $userEnteredPassword,
  365. );
  366. $authenticationMethod = array(
  367. 'modname' => 'Users',
  368. );
  369. if (ModUtil::getVar(UsersConstant::MODNAME, UsersConstant::MODVAR_LOGIN_METHOD, UsersConstant::DEFAULT_LOGIN_METHOD) == UsersConstant::LOGIN_METHOD_EMAIL) {
  370. $authenticationMethod['method'] = 'email';
  371. } else {
  372. $authenticationMethod['method'] = 'uname';
  373. }
  374. return self::loginUsing($authenticationMethod, $authenticationInfo, $rememberme, null, $checkPassword);
  375. }
  376. /**
  377. * Validation method previous authentication.
  378. *
  379. * @param array $authenticationMethod Auth method.
  380. * @param string $reentrantURL Reentrant URL (optional).
  381. *
  382. * @throws FatalException
  383. *
  384. * @return true
  385. */
  386. private static function preAuthenticationValidation(array $authenticationMethod, $reentrantURL = null)
  387. {
  388. if (empty($authenticationMethod) || (count($authenticationMethod) != 2)) {
  389. throw new FatalException(__f('An invalid %1$s parameter was received.', array('authenticationMethod')));
  390. }
  391. if (!isset($authenticationMethod['modname']) || !is_string($authenticationMethod['modname']) || empty($authenticationMethod['modname'])) {
  392. throw new FatalException(__f('An invalid %1$s parameter was received.', array('modname')));
  393. } elseif (!ModUtil::getInfoFromName($authenticationMethod['modname'])) {
  394. throw new FatalException(__f('The authentication module \'%1$s\' could not be found.', array($authenticationMethod['modname'])));
  395. } elseif (!ModUtil::available($authenticationMethod['modname'])) {
  396. throw new FatalException(__f('The authentication module \'%1$s\' is not available.', array($authenticationMethod['modname'])));
  397. } elseif (!ModUtil::loadApi($authenticationMethod['modname'], 'Authentication')) {
  398. throw new FatalException(__f('The authentication module \'%1$s\' could not be loaded.', array($authenticationMethod['modname'])));
  399. }
  400. if (!isset($authenticationMethod['method']) || !is_string($authenticationMethod['method']) || empty($authenticationMethod['method'])) {
  401. throw new FatalException(__f('An invalid %1$s parameter was received.', array('method')));
  402. } elseif (!ModUtil::apiFunc($authenticationMethod['modname'], 'Authentication', 'supportsAuthenticationMethod', array('method' => $authenticationMethod['method']), 'Zikula_Api_AbstractAuthentication')) {
  403. throw new FatalException(__f('The authentication method \'%1$s\' is not supported by the authentication module \'%2$s\'.', array($authenticationMethod['method'], $authenticationMethod['modname'])));
  404. }
  405. if (ModUtil::apiFunc($authenticationMethod['modname'], 'Authentication', 'isReentrant', null, 'Zikula_Api_AbstractAuthentication') && (!isset($reentrantURL) || empty($reentrantURL))) {
  406. throw new FatalException(__f('The authentication module \'%1$s\' is reentrant. A %2$s is required.', array($authenticationMethod['modname'], 'reentrantURL')));
  407. }
  408. return true;
  409. }
  410. /**
  411. * Authenticate a user's credentials against an authentication module, without any attempt to log the user in or look up a Zikula user account record.
  412. *
  413. * NOTE: Checking a password with an authentication method defined by the Users module is a special case.
  414. * The password is stored along with the account information, therefore the account information has to be
  415. * looked up by the checkPassword function in that module. Authentication modules other than the Users module should
  416. * make no attempt to look up account information,
  417. *
  418. * This function is used to check that a user is who he says he is without any attempt to log the user into the
  419. * Zikula system or look up his account information or status. It could be used, for example, to check the user's
  420. * credentials prior to registering with an authentication method like OpenID or Google Federated Login.
  421. *
  422. * This function differs from {@link authenticateUserUsing()} in that it does not make any attempt to look up a Zikula account
  423. * record for the user (nor should the authentication method specified).
  424. *
  425. * This function differs from {@link loginUsing()} in that it does not make any attempt to look up a Zikula account
  426. * record for the user (nor should the authentication method specified), and additionally it makes no attempt to log the user into
  427. * the Zikula system.
  428. *
  429. * ATTENTION: The authentication module function(s) called during this process may redirect the user to an external server
  430. * to perform authorization and/or authentication. The function calling checkPasswordUsing must already have anticipated
  431. * the reentrant nature of this process, must already have saved pertinent user state, must have supplied a
  432. * reentrant URL pointing to a function that will handle reentry into the login process silently, and must clear
  433. * any save user state immediately following the return of this function.
  434. *
  435. * @param array $authenticationMethod Authentication module and method name.
  436. * @param array $authenticationInfo Auth info array.
  437. * @param string $reentrantURL If the authentication module needs to redirect to an external authentication server (e.g., OpenID), then
  438. * this is the URL to return to in order to re-enter the log-in process. The pertinent user
  439. * state must have already been saved by the function calling checkPasswordUsing(), and the URL must
  440. * point to a Zikula_AbstractController function that is equipped to detect reentry, restore the
  441. * saved user state, and get the user back to the point where loginUsing is re-executed. This
  442. * is only optional if the authentication module identified by $authenticationMethod reports that it is not
  443. * reentrant (e.g., Users is guaranteed to not be reentrant).
  444. *
  445. * @return bool True if authentication info authenticates; otherwise false.
  446. */
  447. public static function checkPasswordUsing(array $authenticationMethod, array $authenticationInfo, $reentrantURL = null)
  448. {
  449. if (self::preAuthenticationValidation($authenticationMethod, $reentrantURL)) {
  450. // Authenticate the loginID and userEnteredPassword against the specified authentication module.
  451. // This should return the uid of the user logging in. Note that there are two routes here, both get a uid.
  452. $checkPasswordArgs = array(
  453. 'authentication_info' => $authenticationInfo,
  454. 'authentication_method' => $authenticationMethod,
  455. 'reentrant_url' => $reentrantURL,
  456. );
  457. return ModUtil::apiFunc($authenticationMethod['modname'], 'Authentication', 'checkPassword', $checkPasswordArgs, 'Zikula_Api_AbstractAuthentication');
  458. } else {
  459. return false;
  460. }
  461. }
  462. /**
  463. * Authenticate a user's credentials against an authentication module, without any attempt to log the user in.
  464. *
  465. * This function is used to check that a user is who he says he is, and that he has a valid user account with the
  466. * Zikula system. No attempt is made to log the user in to the Zikula system. It could be used, for example, to check
  467. * the user's credentials and Zikula system accoun status prior to performing a sensitive operation.
  468. *
  469. * This function differs from {@link checkPasswordUsing()} in that it attempts to look up a Zikula account
  470. * record for the user, and takes the user's account status into account when returning a value.
  471. *
  472. * This function differs from {@link loginUsing()} in that it makes no attempt to log the user into the Zikula system.
  473. *
  474. * ATTENTION: The authentication module function(s) called during this process may redirect the user to an external server
  475. * to perform authorization and/or authentication. The function calling authenticateUserUsing must already have anticipated
  476. * the reentrant nature of this process, must already have saved pertinent user state, must have supplied a
  477. * reentrant URL pointing to a function that will handle reentry into the login process silently, and must clear
  478. * any save user state immediately following the return of this function.
  479. *
  480. * @param array $authenticationMethod The name of the authentication module to use for authentication and the method name as defined by that module.
  481. * @param array $authenticationInfo The information needed by the authentication module for authentication, typically a loginID and pass.
  482. * @param string $reentrantURL If the authentication module needs to redirect to an external authentication server (e.g., OpenID), then
  483. * this is the URL to return to in order to re-enter the log-in process. The pertinent user
  484. * state must have already been saved by the function calling authenticateUserUsing(), and the URL must
  485. * point to a Zikula_AbstractController function that is equipped to detect reentry, restore the
  486. * saved user state, and get the user back to the point where loginUsing is re-executed. This
  487. * is only optional if the authentication module identified by $authenticationMethod reports that it is not
  488. * reentrant (e.g., Users is guaranteed to not be reentrant).
  489. *
  490. * @return mixed Zikula uid if the authentication info authenticates with the authentication module; otherwise false.
  491. */
  492. private static function internalAuthenticateUserUsing(array $authenticationMethod, array $authenticationInfo, $reentrantURL = null)
  493. {
  494. $authenticatedUid = false;
  495. if (self::preAuthenticationValidation($authenticationMethod, $reentrantURL)) {
  496. $authenticateUserArgs = array(
  497. 'authentication_info' => $authenticationInfo,
  498. 'authentication_method' => $authenticationMethod,
  499. 'reentrant_url' => $reentrantURL,
  500. );
  501. $authenticatedUid = ModUtil::apiFunc($authenticationMethod['modname'], 'Authentication', 'authenticateUser', $authenticateUserArgs, 'Zikula_Api_AbstractAuthentication');
  502. }
  503. return $authenticatedUid;
  504. }
  505. private static function internalUserAccountValidation($uid, $reportErrors = false, $userObj = false)
  506. {
  507. if (!$uid || !is_numeric($uid) || ((int)$uid != $uid)) {
  508. // We got something other than a uid from the authentication process.
  509. if (!LogUtil::hasErrors() && $reportErrors) {
  510. LogUtil::registerError(__('Sorry! Login failed. The information you provided was incorrect.'));
  511. }
  512. } else {
  513. if (!$userObj) {
  514. // Need to make sure the Users module stuff is loaded and available, especially if we are authenticating during
  515. // an upgrade or install.
  516. ModUtil::loadApi('Users', 'user', true);
  517. // The user's credentials have authenticated with the authentication module's method, but
  518. // now we have to check the account status itself. If the account status would not allow the
  519. // user to log in, then we return false.
  520. $userObj = self::getVars($uid);
  521. if (!$userObj) {
  522. // Might be a registration
  523. $userObj = self::getVars($uid, false, 'uid', true);
  524. }
  525. }
  526. if (!$userObj || !is_array($userObj)) {
  527. // Note that we have not actually logged into anything yet, just authenticated.
  528. throw new FatalException(__f('A %1$s (%2$s) was returned by the authenticating module, but a user account record (or registration request record) could not be found.', array('uid', $uid)));
  529. }
  530. if (!isset($userObj['activated'])) {
  531. // Provide a sane value.
  532. $userObj['activated'] = UsersConstant::ACTIVATED_INACTIVE;
  533. }
  534. if ($userObj['activated'] != UsersConstant::ACTIVATED_ACTIVE) {
  535. if ($reportErrors) {
  536. $displayVerifyPending = ModUtil::getVar(UsersConstant::MODNAME, UsersConstant::MODVAR_LOGIN_DISPLAY_VERIFY_STATUS, UsersConstant::DEFAULT_LOGIN_DISPLAY_VERIFY_STATUS);
  537. $displayApprovalPending = ModUtil::getVar(UsersConstant::MODNAME, UsersConstant::MODVAR_LOGIN_DISPLAY_APPROVAL_STATUS, UsersConstant::DEFAULT_LOGIN_DISPLAY_VERIFY_STATUS);
  538. if (($userObj['activated'] == UsersConstant::ACTIVATED_PENDING_REG) && ($displayApprovalPending || $displayVerifyPending)) {
  539. $moderationOrder = ModUtil::getVar(UsersConstant::MODNAME, UsersConstant::MODVAR_REGISTRATION_APPROVAL_SEQUENCE, UsersConstant::DEFAULT_REGISTRATION_APPROVAL_SEQUENCE);
  540. if (!$userObj['isverified']
  541. && (($moderationOrder == UsersConstant::APPROVAL_AFTER) || ($moderationOrder == UsersConstant::APPROVAL_ANY)
  542. || (!empty($userObj['approved_by'])))
  543. && $displayVerifyPending
  544. ) {
  545. $message = __('Your request to register with this site is still waiting for verification of your e-mail address. Please check your inbox for a message from us.');
  546. } elseif (empty($userObj['approved_by'])
  547. && (($moderationOrder == UsersConstant::APPROVAL_BEFORE) || ($moderationOrder == UsersConstant::APPROVAL_ANY))
  548. && $displayApprovalPending
  549. ) {
  550. $message = __('Your request to register with this site is still waiting for approval from a site administrator.');
  551. }
  552. if (isset($message) && !empty($message)) {
  553. return LogUtil::registerError($message);
  554. }
  555. // It is a pending registration but the site admin elected to not display this to the user.
  556. // No exception here because the answer is simply "no." This will fall through to return false.
  557. } elseif (($userObj['activated'] == UsersConstant::ACTIVATED_INACTIVE) && ModUtil::getVar(UsersConstant::MODNAME, UsersConstant::MODVAR_LOGIN_DISPLAY_INACTIVE_STATUS, UsersConstant::DEFAULT_LOGIN_DISPLAY_INACTIVE_STATUS)) {
  558. $message = __('Your account has been disabled. Please contact a site administrator for more information.');
  559. } elseif (($userObj['activated'] == UsersConstant::ACTIVATED_PENDING_DELETE) && ModUtil::getVar(UsersConstant::MODNAME, UsersConstant::MODVAR_LOGIN_DISPLAY_DELETE_STATUS, UsersConstant::DEFAULT_LOGIN_DISPLAY_DELETE_STATUS)) {
  560. $message = __('Your account has been disabled and is scheduled for removal. Please contact a site administrator for more information.');
  561. } else {
  562. $message = __('Sorry! Either there is no active user in our system with that information, or the information you provided does not match the information for your account.');
  563. }
  564. LogUtil::registerError($message);
  565. }
  566. $userObj = false;
  567. }
  568. }
  569. return $userObj;
  570. }
  571. /**
  572. * Authenticate a user's credentials against an authentication module, without any attempt to log the user in.
  573. *
  574. * This function is used to check that a user is who he says he is, and that he has a valid user account with the
  575. * Zikula system. No attempt is made to log the user in to the Zikula system. It could be used, for example, to check
  576. * the user's credentials and Zikula system accoun status prior to performing a sensitive operation.
  577. *
  578. * This function differs from {@link checkPasswordUsing()} in that it attempts to look up a Zikula account
  579. * record for the user, and takes the user's account status into account when returning a value.
  580. *
  581. * This function differs from {@link loginUsing()} in that it makes no attempt to log the user into the Zikula system.
  582. *
  583. * ATTENTION: The authentication module function(s) called during this process may redirect the user to an external server
  584. * to perform authorization and/or authentication. The function calling authenticateUserUsing must already have anticipated
  585. * the reentrant nature of this process, must already have saved pertinent user state, must have supplied a
  586. * reentrant URL pointing to a function that will handle reentry into the login process silently, and must clear
  587. * any save user state immediately following the return of this function.
  588. *
  589. * @param array $authenticationMethod The name of the authentication module to use for authentication and the method name as defined by that module.
  590. * @param array $authenticationInfo The information needed by the authentication module for authentication, typically a loginID and pass.
  591. * @param string $reentrantURL If the authentication module needs to redirect to an external authentication server (e.g., OpenID), then
  592. * this is the URL to return to in order to re-enter the log-in process. The pertinent user
  593. * state must have already been saved by the function calling authenticateUserUsing(), and the URL must
  594. * point to a Zikula_AbstractController function that is equipped to detect reentry, restore the
  595. * saved user state, and get the user back to the point where loginUsing is re-executed. This
  596. * is only optional if the authentication module identified by $authenticationMethod reports that it is not
  597. * reentrant (e.g., Users is guaranteed to not be reentrant).
  598. * @param boolean $reportErrors If true, then when validation of the account's ability to log in is performed, if errors are detected then
  599. * they will be reported through registering errors with Zikula's logging and error reporting system. If
  600. * false, then error reporting is supressed, and only the return value will indicate success or failure.
  601. *
  602. * @return array|bool The user account record of the user with the given credentials, if his credentials authenticate; otherwise false
  603. */
  604. public static function authenticateUserUsing(array $authenticationMethod, array $authenticationInfo, $reentrantURL = null, $reportErrors = false)
  605. {
  606. $userObj = false;
  607. $authenticatedUid = self::internalAuthenticateUserUsing($authenticationMethod, $authenticationInfo, $reentrantURL);
  608. if ($authenticatedUid) {
  609. $userObj = self::internalUserAccountValidation($authenticatedUid, $reportErrors);
  610. }
  611. return $userObj;
  612. }
  613. /**
  614. * Authenticate a user's credentials against an authentication module, logging him into the Zikula system.
  615. *
  616. * If the user is already logged in, then this function should behave as if {@link authenticateUserUsing()} was called.
  617. *
  618. * This function is used to check that a user is who he says he is, and that he has a valid user account with the
  619. * Zikula system. If so, the user is logged in to the Zikula system (if he is not already logged in). This function
  620. * should be used only to log a user into the Zikula system.
  621. *
  622. * This function differs from {@link checkPasswordUsing()} in that it attempts to look up a Zikula account
  623. * record for the user, and takes the user's account status into account when returning a value. Additionally,
  624. * the user is logged into the Zikula system if his credentials are verified with the authentication module specified.
  625. *
  626. * This function differs from {@link authenticateUserUsing()} in that it attempts to log the user into the Zikula system,
  627. * if he is not already logged in. If he is already logged in, then it should behave similarly to authenticateUserUsing().
  628. *
  629. * ATTENTION: The authentication module function(s) called during this process may redirect the user to an external server
  630. * to perform authorization and/or authentication. The function calling loginUsing must already have anticipated
  631. * the reentrant nature of this process, must already have saved pertinent user state, must have supplied a
  632. * reentrant URL pointing to a function that will handle reentry into the login process silently, and must clear
  633. * any save user state immediately following the return of this function.
  634. *
  635. * @param array $authenticationMethod Auth module name.
  636. * @param array $authenticationInfo Auth info array.
  637. * @param boolean $rememberMe Whether or not to remember login.
  638. * @param string $reentrantURL If the authentication module needs to redirect to an external authentication server (e.g., OpenID), then
  639. * this is the URL to return to in order to re-enter the log-in process. The pertinent user
  640. * state must have already been saved by the function calling loginUsing(), and the URL must
  641. * point to a Zikula_AbstractController function that is equipped to detect reentry, restore the
  642. * saved user state, and get the user back to the point where loginUsing is re-executed. This
  643. * is only optional if the authentication module identified by $authenticationMethod reports that it is not
  644. * reentrant (e.g., Users is guaranteed to not be reentrant), or if $checkPassword is false.
  645. * @param boolean $checkPassword Whether or not to check the password.
  646. * @param boolean $preauthenticatedUser Whether ot not is a preauthenticated user.
  647. *
  648. * @return array|bool The user account record of the user that has logged in successfully, otherwise false
  649. */
  650. public static function loginUsing(array $authenticationMethod, array $authenticationInfo, $rememberMe = false, $reentrantURL = null, $checkPassword = true, $preauthenticatedUser = null)
  651. {
  652. $userObj = false;
  653. if (self::preAuthenticationValidation($authenticationMethod, $authenticationInfo, $reentrantURL)) {
  654. // Authenticate the loginID and userEnteredPassword against the specified authentication module.
  655. // This should return the uid of the user logging in. Note that there are two routes here, both get a uid.
  656. // We do the authentication check first, before checking any account status information, because if the
  657. // person logging in cannot supply the proper credentials, then we should not show any detailed account status
  658. // to them. Instead they should just get the generic "no such user found or bad password" message.
  659. if ($checkPassword) {
  660. $authenticatedUid = self::internalAuthenticateUserUsing($authenticationMethod, $authenticationInfo, $reentrantURL, true);
  661. } elseif (isset($preauthenticatedUser)) {
  662. if (is_numeric($preauthenticatedUser)) {
  663. $authenticatedUid = $preauthenticatedUser;
  664. } elseif (is_array($preauthenticatedUser)) {
  665. $authenticatedUid = $preauthenticatedUser['uid'];
  666. $userObj = $preauthenticatedUser;
  667. } else {
  668. throw new FatalException();
  669. }
  670. } else {
  671. $authArgs = array(
  672. 'authentication_info' => $authenticationInfo,
  673. 'authentication_method' => $authenticationMethod,
  674. );
  675. $authenticatedUid = ModUtil::apiFunc($authenticationMethod['modname'], 'Authentication', 'getUidForAuththenticationInfo', $authArgs, 'Zikula_Api_AbstractAuthentication');
  676. }
  677. $session = ServiceUtil::get('request')->getSession();
  678. $userObj = self::internalUserAccountValidation($authenticatedUid, true, isset($userObj) ? $userObj : null);
  679. if ($userObj && is_array($userObj)) {
  680. // BEGIN ACTUAL LOGIN
  681. // Made it through all the checks. We can actually log in now.
  682. // Give any interested module one last chance to prevent the login from happening.
  683. $eventArgs = array(
  684. 'authentication_method' => $authenticationMethod,
  685. 'uid' => $userObj['uid'],
  686. );
  687. $event = new GenericEvent($userObj, $eventArgs);
  688. $event = EventUtil::dispatch('user.login.veto', $event);
  689. if ($event->isPropagationStopped()) {
  690. // The login attempt has been vetoed by one or more modules.
  691. $eventData = $event->getData();
  692. if (isset($eventData['retry']) && $eventData['retry']) {
  693. $sessionVarName = 'Users_Controller_User_login';
  694. $sessionNamespace = 'Zikula_Users';
  695. $redirectURL = ModUtil::url('Users', 'user', 'login', array('csrftoken' => SecurityUtil::generateCsrfToken()));
  696. } elseif (isset($eventData['redirect_func'])) {
  697. if (isset($eventData['redirect_func']['session'])) {
  698. $sessionVarName = $eventData['redirect_func']['session']['var'];
  699. $sessionNamespace = isset($eventData['redirect_func']['session']['namespace']) ? $eventData['redirect_func']['session']['namespace'] : '';
  700. }
  701. $redirectURL = ModUtil::url($eventData['redirect_func']['modname'], $eventData['redirect_func']['type'], $eventData['redirect_func']['func'], $eventData['redirect_func']['args']);
  702. }
  703. if (isset($redirectURL)) {
  704. if (isset($sessionVarName)) {
  705. SessionUtil::requireSession();
  706. $sessionVars = $session->get('users/Users_User_Controller_login', array());
  707. $sessionVars = array(
  708. 'returnpage' => isset($sessionVars['returnpage']) ? $sessionVars['returnpage'] : '',
  709. 'authentication_info' => $authenticationInfo,
  710. 'authentication_method' => $authenticationMethod,
  711. 'rememberme' => $rememberMe,
  712. 'user_obj' => $userObj,
  713. );
  714. $session->set("$sessionNamespace/$sessionVarName", $sessionVars);
  715. }
  716. $userObj = false;
  717. throw new RedirectException($redirectURL);
  718. } else {
  719. throw new ForbiddenException();
  720. }
  721. } else {
  722. // The login has not been vetoed
  723. // This is what really does the Zikula login
  724. self::setUserByUid($userObj['uid'], $rememberMe, $authenticationMethod);
  725. }
  726. }
  727. }
  728. return $userObj;
  729. }
  730. /**
  731. * Sets the currently logged in active user to the user account for the given Users module uname.
  732. *
  733. * No events are fired from this function. To receive events, use {@link loginUsing()}.
  734. *
  735. * @param string $uname The user name of the user who should be logged into the system; required.
  736. * @param boolean $rememberMe If the user's login should be maintained on the computer from which the user is logging in, set this to true;
  737. * optional, defaults to false.
  738. *
  739. * @return void
  740. */
  741. public static function setUserByUname($uname, $rememberMe = false)
  742. {
  743. if (!isset($uname) || !is_string($uname) || empty($uname)) {
  744. throw new FatalException(__('Attempt to set the current user with an invalid uname.'));
  745. }
  746. $uid = self::getIdFromName($uname);
  747. $authenticationMethod = array(
  748. 'modname' => 'Users',
  749. 'method' => 'uname',
  750. );
  751. self::setUserByUid($uid, $rememberMe, $authenticationMethod);
  752. }
  753. /**
  754. * Sets the currently logged in active user to the user account for the given uid.
  755. *
  756. * No events are fired from this function. To receive events, use {@link loginUsing()}.
  757. *
  758. * @param numeric $uid The user id of the user who should be logged into the system; required.
  759. * @param boolean $rememberMe If the user's login should be maintained on the computer from which the user is logging in, set this to true;
  760. * optional, defaults to false.
  761. * @param array $authenticationMethod An array containing the authentication method used to log the user in; optional,
  762. * defaults to the 'Users' module 'uname' method.
  763. *
  764. * @return void
  765. */
  766. public static function setUserByUid($uid, $rememberMe = false, array $authenticationMethod = null)
  767. {
  768. if (!isset($uid) || empty($uid) || ((string)((int)$uid) != $uid)) {
  769. throw new FatalException(__('Attempt to set the current user with an invalid uid.'));
  770. }
  771. $userObj = self::getVars($uid);
  772. if (!isset($userObj) || !is_array($userObj) || empty($userObj)) {
  773. throw new FatalException(__('Attempt to set the current user with an unknown uid.'));
  774. }
  775. if (!isset($authenticationMethod)) {
  776. $authenticationMethod = array(
  777. 'modname' => 'Users',
  778. 'method' => 'uname',
  779. );
  780. } elseif (empty($authenticationMethod) || !isset($authenticationMethod['modname']) || empty($authenticationMethod['modname'])
  781. || !isset($authenticationMethod['method']) || empty($authenticationMethod['method'])
  782. ) {
  783. throw new FatalException(__('Attempt to set the current user with an invalid authentication method.'));
  784. }
  785. // Storing Last Login date -- store it in UTC! Do not use date() function!
  786. $nowUTC = new DateTime(null, new DateTimeZone('UTC'));
  787. if (!self::setVar('lastlogin', $nowUTC->format('Y-m-d H:i:s'), $userObj['uid'])) {
  788. // show messages but continue
  789. LogUtil::registerError(__('Error! Could not save the log-in date.'));
  790. }
  791. if (!System::isInstalling()) {
  792. SessionUtil::requireSession();
  793. }
  794. $session = ServiceUtil::get('request')->getSession();
  795. // Set session variables -- this is what really does the Zikula login
  796. $session->set('uid', $userObj['uid']);
  797. $session->set('users/authentication_method', $authenticationMethod);
  798. if (!empty($rememberMe)) {
  799. $session->set('rememberme', 1);
  800. }
  801. // now that we've logged in the permissions previously calculated (if any) are invalid
  802. $GLOBALS['authinfogathered'][$userObj['uid']] = 0;
  803. }
  804. /**
  805. * Log the user out.
  806. *
  807. * @return bool true if the user successfully logged out, false otherwise
  808. */
  809. public static function logout()
  810. {
  811. if (self::isLoggedIn()) {
  812. $userObj = self::getVars(self::getVar('uid'));
  813. $session = ServiceUtil::get('request')->getSession();
  814. $authenticationMethod = $session->get('users/authentication_method', array('modname' => '', 'method' => ''));
  815. $session->invalidate();
  816. }
  817. return true;
  818. }
  819. /**
  820. * Is the user logged in?
  821. *
  822. * @return bool true if the user is logged in, false if they are not
  823. */
  824. public static function isLoggedIn()
  825. {
  826. return (bool)ServiceUtil::getManager()->get('request')->getSession()->get('uid');
  827. }
  828. /**
  829. * Counts how many times a user name has been used by user accounts in the system.
  830. *
  831. * @param string $uname The e-mail address in question (required).
  832. * @param int $excludeUid The uid to exclude from the check, used when checking modifications.
  833. *
  834. * @return integer|boolean The count, or false on error.
  835. */
  836. public static function getUnameUsageCount($uname, $excludeUid = 0)
  837. {
  838. if (!is_numeric($excludeUid) || ((int)$excludeUid != $excludeUid)) {
  839. return false;
  840. }
  841. $uname = DataUtil::formatForStore(mb_strtolower($uname));
  842. // get doctrine manager
  843. $em = \ServiceUtil::get('doctrine')->getManager();
  844. // count of uname appearances in users table
  845. $dql = "SELECT count(u.uid) FROM UsersModule\Entity\User u WHERE u.uname = '{$uname}'";
  846. if ($excludeUid > 1) {
  847. $dql .= " AND u.uid <> {$excludeUid}";
  848. }
  849. $query = $em->createQuery($dql);
  850. $ucount = $query->getSingleScalarResult();
  851. return (int)$ucount;
  852. }
  853. /**
  854. * Counts how many times an e-mail address has been used by user accounts in the system.
  855. *
  856. * @param string $emailAddress The e-mail address in question (required).
  857. * @param int $excludeUid The uid to exclude from the check, used when checking modifications.
  858. *
  859. * @return integer|boolean the count, or false on error.
  860. */
  861. public static function getEmailUsageCount($emailAddress, $excludeUid = 0)
  862. {
  863. if (!is_numeric($excludeUid) || ((int)$excludeUid != $excludeUid)) {
  864. return false;
  865. }
  866. $emailAddress = DataUtil::formatForStore(mb_strtolower($emailAddress));
  867. // get doctrine manager
  868. $em = \ServiceUtil::get('doctrine')->getManager();
  869. // count of email appearances in users table
  870. $dql = "SELECT COUNT(u.uid) FROM UsersModule\Entity\User u WHERE u.email = '{$emailAddress}'";
  871. if ($excludeUid > 1) {
  872. $dql .= " AND u.uid <> {$excludeUid}";
  873. }
  874. $query = $em->createQuery($dql);
  875. $ucount = (int)$query->getSingleScalarResult();
  876. // count of email appearances in users verification table
  877. $dql = "SELECT COUNT(v.id) FROM UsersModule\Entity\UserVerification v WHERE v.newemail = '{$emailAddress}' AND v.changetype = " . UsersConstant::VERIFYCHGTYPE_EMAIL;
  878. if ($excludeUid > 1) {
  879. $dql .= " AND v.uid <> {$excludeUid}";
  880. }
  881. $query = $em->createQuery($dql);
  882. $vcount = (int)$query->getSingleScalarResult();
  883. return ($ucount + $vcount);
  884. }
  885. /**
  886. * When getting a registration record, this function calculates several fields needed for registration state.
  887. *
  888. * @param array &$userObj The user object array created by UserUtil::getVars(). NOTE: this parameter is passed by
  889. * reference, and therefore will be updated by the actions of this function.
  890. *
  891. * @return array The updated $userObj.
  892. */
  893. public static function postProcessGetRegistration(&$userObj)
  894. {
  895. if ($userObj['activated'] == UsersConstant::ACTIVATED_PENDING_REG) {
  896. // Get isverified from the attributes.
  897. if (isset($userObj['__ATTRIBUTES__']['_Users_isVerified'])) {
  898. $userObj['isverified'] = $userObj['__ATTRIBUTES__']['_Users_isVerified'];
  899. //unset($userObj['__ATTRIBUTES__']['_Users_isVerified']);
  900. } else {
  901. $userObj['isverified'] = false;
  902. }
  903. // Get verificationsent from the users_verifychg table
  904. $em = \ServiceUtil::get('doctrine')->getManager();
  905. $dql = "SELECT v FROM UsersModule\Entity\UserVerification v WHERE v.uid = {$userObj['uid']} AND v.changetype = " . UsersConstant::VERIFYCHGTYPE_REGEMAIL;
  906. $query = $em->createQuery($dql);
  907. $verifyChgList = $query->getResult(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);
  908. if ($verifyChgList && is_array($verifyChgList) && !empty($verifyChgList) && is_array($verifyChgList[0]) && !empty($verifyChgList[0])) {
  909. $userObj['verificationsent'] = $verifyChgList[0]['created_dt'];
  910. } else {
  911. $userObj['verificationsent'] = false;
  912. }
  913. // Calculate isapproved from approved_by
  914. $userObj['isapproved'] = isset($userObj['approved_by']) && !empty($userObj['approved_by']);
  915. }
  916. return $userObj;
  917. }
  918. /**
  919. * Get all user variables, maps new style attributes to old style user data.
  920. *
  921. * @param integer $id The user id of the user (required).
  922. * @param boolean $force True to force loading from database and ignore the cache.
  923. * @param string $idfield Field to use as id (possible values: uid, uname or email).
  924. * @param bool $getRegistration Indicates whether a "regular" user record or a pending registration
  925. * is to be returned. False (default) for a user record and true
  926. * for a registration. If false and the user record is a pending
  927. * registration, then the record is not returned and false is returned
  928. * instead; likewise, if true and the user record is not a registration,
  929. * then false is returned; (Defaults to false).
  930. *
  931. * @return array|bool An associative array with all variables for a user (or pending registration);
  932. * false on error.
  933. */
  934. public static function getVars($id, $force = false, $idfield = '', $getRegistration = false)
  935. {
  936. if (empty($id)) {
  937. return false;
  938. }
  939. // assign a value for the parameter idfield if it is necessary and prevent from possible typing mistakes
  940. if ($idfield == '' || ($idfield != 'uid' && $idfield != 'uname' && $idfield != 'email')) {
  941. $idfield = 'uid';
  942. if (!is_numeric($id)) {
  943. $idfield = 'uname';
  944. if (strpos($id, '@')) {
  945. $idfield = 'email';
  946. }
  947. }
  948. }
  949. static $cache = array(), $unames = array(), $emails = array();
  950. // caching
  951. $user = null;
  952. if ($force == false) {
  953. if ($idfield == 'uname' && isset($unames[$id])) {
  954. if ($unames[$id] !== false) {
  955. $user = $cache[$unames[$id]];
  956. } else {
  957. return false;
  958. }
  959. }
  960. if ($idfield == 'email' && isset($emails[$id])) {
  961. if ($emails[$id] !== false) {
  962. $user = $cache[$emails[$id]];
  963. } else {
  964. return false;
  965. }
  966. }
  967. if (isset($cache[$id])) {
  968. $user = $cache[$id];
  969. }
  970. }
  971. if (!isset($user) || $force) {
  972. $em = \ServiceUtil::get('doctrine')->getManager();
  973. $user = $em->getRepository('UsersModule\Entity\User')->findOneBy(array($idfield => $id));
  974. if ($user) {
  975. $user = $user->toArray();
  976. $attributes = array();
  977. foreach ($user['attributes'] as $attribute) {
  978. $attributes[$attribute['name']] = $attribute['value'];
  979. }
  980. $user['__ATTRIBUTES__'] = $attributes;
  981. unset($user['attributes']);
  982. }
  983. // If $idfield is email, make sure that we are getting a unique record.
  984. if ($user && ($idfield == 'email')) {
  985. $emailCount = self::getEmailUsageCount($id);
  986. if (($emailCount > 1) || ($emailCount === false)) {
  987. $user = false;
  988. }
  989. }
  990. // update cache
  991. if (!$user) {
  992. switch ($idfield) {
  993. case 'uid':
  994. $cache[$id] = false;
  995. break;
  996. case 'uname':
  997. $unames[$id] = false;
  998. break;
  999. case 'email':
  1000. $emails[$id] = false;
  1001. break;
  1002. }
  1003. return false;
  1004. } else {
  1005. // This check should come at the very end, here, so that if $force is true the vars get
  1006. // reloaded into cache no matter what $getRegistration is set to. If not, and this is
  1007. // called from setVar(), and setVar() changed the 'activated' value, then we'd have trouble.
  1008. if (($getRegistration && ($user['activated'] != UsersConstant::ACTIVATED_PENDING_REG))
  1009. || (!$getRegistration && ($user['activated'] == UsersConstant::ACTIVATED_PENDING_REG))) {
  1010. return false;
  1011. }
  1012. $user = self::postProcessGetRegistration($user);
  1013. $cache[$user['uid']] = $user;
  1014. $unames[$user['uname']] = $user['uid'];
  1015. $emails[$user['email']] = $user['uid'];
  1016. }
  1017. } elseif (($getRegistration && ($user['activated'] != UsersConstant::ACTIVATED_PENDING_REG))
  1018. || (!$getRegistration && ($user['activated'] == UsersConstant::ACTIVATED_PENDING_REG))) {
  1019. return false;
  1020. }
  1021. return $user;
  1022. }
  1023. /**
  1024. * Get a user variable.
  1025. *
  1026. * @param string $name The name of the variable.
  1027. * @param integer $uid The user to get the variable for.
  1028. * @param mixed $default The default value to return if the specified variable doesn't exist.
  1029. * @param bool $getRegistration Indicates whether the variable should be retrieved from a "regular"
  1030. * user record or from a pending registration. False (default) for a
  1031. * user record and true for a registration. If false and the uid refers
  1032. * to a pending registration, then the variable is not returned and
  1033. * null is returned instead; likewise, if true and the user record is
  1034. * not a registration, then null is returned. (Defaults to false).
  1035. *
  1036. * @return mixed The value of the user variable if successful, null otherwise.
  1037. */
  1038. public static function getVar($name, $uid = -1, $default = false, $getRegistration = false)
  1039. {
  1040. if (empty($name)) {
  1041. return null;
  1042. }
  1043. // bug fix #1311 [landseer]
  1044. if (isset($uid) && !is_numeric($uid)) {
  1045. return null;
  1046. }
  1047. if ($uid == -1) {
  1048. $uid = ServiceUtil::get('request')->getSession()->get('uid');
  1049. }
  1050. if (empty($uid)) {
  1051. return null;
  1052. }
  1053. // get this user's variables
  1054. $vars = self::getVars($uid, false, '', $getRegistration);
  1055. if ($vars === false) {
  1056. return null;
  1057. }
  1058. // Return the variable
  1059. if (isset($vars[$name])) {
  1060. return $vars[$name];
  1061. }
  1062. // or an attribute
  1063. if (isset($vars['__ATTRIBUTES__'][$name])) {
  1064. return $vars['__ATTRIBUTES__'][$name];
  1065. }
  1066. return $default;
  1067. }
  1068. /**
  1069. * Maps the old DUD names to new attribute names.
  1070. *
  1071. * @param string $name The name of the field.
  1072. *
  1073. * @return string|bool The attribute name corresponding to the DUD name, or false if the parameter is not a DUD name.
  1074. */
  1075. private static function convertOldDynamicUserDataAlias($name)
  1076. {
  1077. $attributeName = false;
  1078. if (isset($name) && !empty($name)) {
  1079. // Only need to build the array once
  1080. static $mappingArray;
  1081. if (!isset($mappingArray)) {
  1082. // this array maps old DUDs to new attributes
  1083. $mappingArray = array(
  1084. '_UREALNAME' => 'realname',
  1085. '_UFAKEMAIL' => 'publicemail',
  1086. '_YOURHOMEPAGE' => 'url',
  1087. '_TIMEZONEOFFSET' => 'tzoffset',
  1088. '_YOURAVATAR' => 'avatar',
  1089. '_YLOCATION' => 'city',
  1090. '_YICQ' => 'icq',
  1091. '_YAIM' => 'aim',
  1092. '_YYIM' => 'yim',
  1093. '_YMSNM' => 'msnm',
  1094. '_YOCCUPATION' => 'occupation',
  1095. '_SIGNATURE' => 'signature',
  1096. '_EXTRAINFO' => 'extrainfo',
  1097. '_YINTERESTS' => 'interests',
  1098. 'name' => 'realname',
  1099. 'femail' => 'publicemail',
  1100. 'timezone_offset' => 'tzoffset',
  1101. 'user_avatar' => 'avatar',
  1102. 'user_icq' => 'icq',
  1103. 'user_aim' => 'aim',
  1104. 'user_yim' => 'yim',
  1105. 'user_msnm' => 'msnm',
  1106. 'user_from' => 'city',
  1107. 'user_occ' => 'occupation',
  1108. 'user_intrest' => 'interests',
  1109. 'user_sig' => 'signature',
  1110. 'bio' => 'extrainfo',
  1111. );
  1112. }
  1113. $attributeName = isset($mappingArray[$name]) ? $mappingArray[$name] : false;
  1114. }
  1115. return $attributeName;
  1116. }
  1117. /**
  1118. * Set a user variable.
  1119. *
  1120. * This can be
  1121. * - a field in the users table
  1122. * - or an attribute and in this case either a new style attribute or an old style user information.
  1123. *
  1124. * Examples:
  1125. * self::setVar('pass', 'mysecretpassword'); // store a password (should be hashed of course)
  1126. * self::setVar('avatar', 'mypicture.gif'); // stores an users avatar, new style
  1127. * (internally both the new and the old style write the same attribute)
  1128. *
  1129. * If the user variable does not exist it will be created automatically. This means with
  1130. * self::setVar('somename', 'somevalue');
  1131. * you can easily create brand new users variables onthefly.
  1132. *
  1133. * This function does not allow you to set uid or uname.
  1134. *
  1135. * @param string $name The name of the variable.
  1136. * @param mixed $value The value of the variable.
  1137. * @param integer $uid The user to set the variable for.
  1138. *
  1139. * @return bool true if the set was successful, false otherwise
  1140. */
  1141. public static function setVar($name, $value, $uid = -1)
  1142. {
  1143. if (empty($name)) {
  1144. return false;
  1145. }
  1146. if (!isset($value)) {
  1147. return false;
  1148. }
  1149. if ($uid == -1) {
  1150. $uid =ServiceUtil::get('request')->getSession()->get('uid');
  1151. }
  1152. if (empty($uid)) {
  1153. return false;
  1154. }
  1155. $isRegistration = self::isRegistration($uid);
  1156. $origUserObj = self::getVars($uid, false, 'uid', $isRegistration);
  1157. if (!$origUserObj) {
  1158. // No such user record!
  1159. return false;
  1160. }
  1161. $varIsSet = false;
  1162. // Cannot setVar the user's uid or uname
  1163. if (($name != 'uid') && ($name != 'uname')) {
  1164. // get user given a uid
  1165. $em = \ServiceUtil::get('doctrine')->getManager();
  1166. $user = $em->getRepository('UsersModule\Entity\User')->findOneBy(array('uid' => $uid));
  1167. // check if var to set belongs to table or it's an attribute
  1168. if (self::fieldAlias($name)) {
  1169. // this value comes from the users table
  1170. $oldValue = isset($origUserObj[$name]) ? $origUserObj[$name] : null;
  1171. $user[$name] = $value;
  1172. $em->flush();
  1173. $varIsSet = true;
  1174. } else {
  1175. // Not a table field alias, not 'uid', and not 'uname'. Treat it as an attribute.
  1176. $dudAttributeName = self::convertOldDynamicUserDataAlias($name);
  1177. if ($dudAttributeName) {
  1178. LogUtil::log(__f('Warning! User variable [%1$s] is deprecated. Please use [%2$s] instead.', array($name, $mappingarray[$name])), E_USER_DEPRECATED);
  1179. // $name is a former DUD /old style user information now stored as an attribute
  1180. $attributeName = $dudAttributeName;
  1181. } else {
  1182. // $name not in the users table and also not found in the mapping array and also not one of the
  1183. // forbidden names, let's make an attribute out of it
  1184. $attributeName = $name;
  1185. }
  1186. $oldValue = isset($origUserObj['__ATTRIBUTES__'][$attributeName]) ? $origUserObj['__ATTRIBUTES__'][$attributeName] : null;
  1187. $user->setAttribute($attributeName, $value);
  1188. $varIsSet = true;
  1189. }
  1190. // force loading of attributes from db
  1191. $updatedUserObj = self::getVars($uid, true, '', $isRegistration);
  1192. if (!$updatedUserObj) {
  1193. // Should never get here!
  1194. return false;
  1195. }
  1196. // Do not fire update event/hook unless the update happened, it was not a registration record, it was not
  1197. // the password being updated, and the system is not currently being installed.
  1198. if ($varIsSet && ($name != 'pass') && !System::isInstalling()) {
  1199. // Fire the event
  1200. $eventName = $isRegistration ? 'user.registration.update' : 'user.account.update';
  1201. $eventArgs = array(
  1202. 'action' => 'setVar',
  1203. 'field' => isset($attributeName) ? null : $name,
  1204. 'attribute' => isset($attributeName) ? $attributeName : null,
  1205. );
  1206. $eventData = array(
  1207. 'old_value' => $oldValue,
  1208. 'new_value' => $value,
  1209. );
  1210. $updateEvent = new GenericEvent($eventName, $updatedUserObj, $eventArgs, $eventData);
  1211. EventUtil::dispatch($eventName, $updateEvent);
  1212. }
  1213. }
  1214. return $varIsSet;
  1215. }
  1216. /**
  1217. * Get an array of hash algorithms valid for hashing user passwords.
  1218. *
  1219. * Either as an array of
  1220. * algorithm names index by internal integer code, or as an array of internal integer algorithm
  1221. * codes indexed by algorithm name.
  1222. *
  1223. * @param bool $reverse If false, then return an array of codes indexed by name (e.g. given $name, then $code = $methods[$name]);
  1224. * if true, return an array of names indexed by code (e.g. given $code, then $name = $methods[$code]);
  1225. * optional, default = false.
  1226. *
  1227. * @return array Depending on the value of $reverse, an array of codes indexed by name or an
  1228. * array of names indexed by code.
  1229. */
  1230. public static function getPasswordHashMethods($reverse = false)
  1231. {
  1232. // NOTICE: Be extremely cautious about removing entries from this array! If a hash method is no longer
  1233. // to be used, then it probably should be removed from the available options at display time. If an entry is
  1234. // removed from this array but a user's password has been hashed with that method, then that user will no
  1235. // longer be able to log in!! Only remove an entry if you are absolutely positive no user record has a
  1236. // password hashed with that method!
  1237. // NOTICE: DO NOT change the numbers assigned to each hash method. The number is the identifier for the
  1238. // method stored in the database. If a number is changed to a different method, then any user whose password
  1239. // was hashed with the method previously identified by that number will no longer be able to log in!
  1240. $reverse = is_bool($reverse) ? $reverse : false;
  1241. if ($reverse) {
  1242. // Ensure this is in sync with the array below!
  1243. return array(
  1244. 1 => 'md5',
  1245. 5 => 'sha1',
  1246. 8 => 'sha256'
  1247. );
  1248. } else {
  1249. // Ensure this is in sync with the array above!
  1250. return array(
  1251. 'md5' => 1,
  1252. 'sha1' => 5,
  1253. 'sha256' => 8
  1254. );
  1255. }
  1256. }
  1257. /**
  1258. * For a given password hash algorithm name, return its internal integer code.
  1259. *
  1260. * @param string $hashAlgorithmName The name of a hash algorithm suitable for hashing user passwords.
  1261. *
  1262. * @return integer|bool The internal integer code corresponding to the given algorithm name; false if the name is not valid.
  1263. */
  1264. public static function getPasswordHashMethodCode($hashAlgorithmName)
  1265. {
  1266. static $hashMethodCodesByName;
  1267. if (!isset($hashMethodCodesByName)) {
  1268. $hashMethodCodesByName = self::getPasswordHashMethods(false);
  1269. }
  1270. if (!isset($hashAlgorithmName) || !is_string($hashAlgorithmName) || empty($hashAlgorithmName) || !isset($hashMethodCodesByName[$hashAlgorithmName])
  1271. || empty($hashMethodCodesByName[$hashAlgorithmName]) || !is_numeric($hashMethodCodesByName[$hashAlgorithmName])) {
  1272. return LogUtil::registerArgsError();
  1273. }
  1274. return $hashMethodCodesByName[$hashAlgorithmName];
  1275. }
  1276. /**
  1277. * For a given internal password hash algorithm code, return its name suitable for use with the hash() function.
  1278. *
  1279. * @param int $hashAlgorithmCode The internal code representing a hashing algorithm suitable for hashing user passwords.
  1280. *
  1281. * @return string|bool The hashing algorithm name corresponding to that code, suitable for use with hash(); false if the code is invalid.
  1282. */
  1283. public static function getPasswordHashMethodName($hashAlgorithmCode)
  1284. {
  1285. static $hashMethodNamesByCode;
  1286. if (!isset($hashMethodNamesByCode)) {
  1287. $hashMethodNamesByCode = self::getPasswordHashMethods(true);
  1288. }
  1289. if (!isset($hashAlgorithmCode) || !is_numeric($hashAlgorithmCode) || !isset($hashMethodNamesByCode[$hashAlgorithmCode])
  1290. || !is_string($hashMethodNamesByCode[$hashAlgorithmCode]) || empty($hashMethodNamesByCode[$hashAlgorithmCode])) {
  1291. return LogUtil::registerArgsError();
  1292. }
  1293. return $hashMethodNamesByCode[$hashAlgorithmCode];
  1294. }
  1295. /**
  1296. * Determines if a given unhashed password meets the minimum criteria for use as a user password.
  1297. *
  1298. * The given password must be set, a string, not the empty string, and must have a length greater than
  1299. * the minimum length defined by the Users module variable 'minpass' (or 5 if that variable is not set or
  1300. * is misconfigured).
  1301. *
  1302. * @param string $unhashedPassword The proposed password.
  1303. *
  1304. * @return bool True if the proposed password meets the minimum criteria; otherwise false;
  1305. */
  1306. public static function validatePassword($unhashedPassword)
  1307. {
  1308. $minLength = ModUtil::getVar(UsersConstant::MODNAME, UsersConstant::MODVAR_PASSWORD_MINIMUM_LENGTH, UsersConstant::DEFAULT_PASSWORD_MINIMUM_LENGTH);
  1309. return isset($unhashedPassword)
  1310. && is_string($unhashedPassword)
  1311. && (strlen($unhashedPassword) >= $minLength);
  1312. }
  1313. /**
  1314. * Given a string return it's hash and the internal integer hashing algorithm code used to hash that string.
  1315. *
  1316. * Note that this can be used for more than just user login passwords. If a user-readale password-like code is needed,
  1317. * then this method may be suitable.
  1318. *
  1319. * @param string $unhashedPassword An unhashed password, as might be entered by a user or generated by the system, that meets
  1320. * all of the constraints of a valid password for a user account.
  1321. * @param int $hashMethodCode An internal code identifying one of the valid user password hashing methods; optional, leave this
  1322. * unset (null) when creating a new password for a user to get the currently configured system
  1323. * hashing method, otherwise to hash a password for comparison, specify the method used to hash
  1324. * the original password.
  1325. *
  1326. * @return array|bool An array containing two elements: 'hash' containing the hashed password, and 'hashMethodCode' containing the
  1327. * internal integer hashing algorithm code used to hash the password; false if the password does not meet the
  1328. * constraints of a valid password, or if the hashing method (stored in the Users module 'hash_method' var) is
  1329. * not valid.
  1330. */
  1331. public static function getHashedPassword($unhashedPassword, $hashMethodCode = null)
  1332. {
  1333. if (isset($hashMethodCode)) {
  1334. if (!is_numeric($hashMethodCode) || ((int)$hashMethodCode != $hashMethodCode)) {
  1335. return LogUtil::registerArgsError();
  1336. }
  1337. $hashAlgorithmName = self::getPasswordHashMethodName($hashMethodCode);
  1338. if (!$hashAlgorithmName) {
  1339. return LogUtil::registerArgsError();
  1340. }
  1341. } else {
  1342. $hashAlgorithmName = ModUtil::getVar('Users', 'hash_method', '');
  1343. $hashMethodCode = self::getPasswordHashMethodCode($hashAlgorithmName);
  1344. if (!$hashMethodCode) {
  1345. return LogUtil::registerArgsError();
  1346. }
  1347. }
  1348. return SecurityUtil::getSaltedHash($unhashedPassword, $hashAlgorithmName, self::getPasswordHashMethods(false), 5, UsersConstant::SALT_DELIM);
  1349. // FIXME this return is not reached
  1350. return array(
  1351. 'hashMethodCode' => $hashMethodCode,
  1352. 'hash' => hash($hashAlgorithmName, $unhashedPassword),
  1353. );
  1354. }
  1355. /**
  1356. * Create a system-generated password or password-like code, meeting the configured constraints for a password.
  1357. *
  1358. * @return string The generated (unhashed) password-like string.
  1359. */
  1360. public static function generatePassword()
  1361. {
  1362. $minLength = ModUtil::getVar('Users', 'minpass', 5);
  1363. if (!is_numeric($minLength) || ((int)$minLength != $minLength) || ($minLength < 5)) {
  1364. $minLength = 5;
  1365. }
  1366. $minLength = min($minLength, 25);
  1367. $maxLength = min($minLength + 3, 25);
  1368. return RandomUtil::getStringForPassword($minLength, $maxLength);
  1369. }
  1370. /**
  1371. * Change the specified user's password to the one provided, defaulting to the current user if a uid is not specified.
  1372. *
  1373. * @param string $unhashedPassword The new password for the current user.
  1374. * @param int $uid The user ID of the user for whom the password should be set; optional; defaults to current user.
  1375. *
  1376. * @return bool True if the password was successfully saved; otherwise false if the password is empty,
  1377. * invalid (too short), or if the password was not successfully saved.
  1378. */
  1379. public static function setPassword($unhashedPassword, $uid = -1)
  1380. {
  1381. $passwordChanged = false;
  1382. // If uid is not -1 (specifies someone other than the current user) then make sure the current user
  1383. // is allowed to do that.
  1384. if (self::validatePassword($unhashedPassword)) {
  1385. $hashedPassword = self::getHashedPassword($unhashedPassword);
  1386. if ($hashedPassword) {
  1387. // TODO - Important! This needs to be an atomic change to the database. If pass is changed without hash_method, then the user will not be able to log in!
  1388. $passwordChanged = self::setVar('pass', $hashedPassword, $uid);
  1389. }
  1390. // TODO - Should we force the change of passreminder here too? If the password is changing, certainly the existing reminder is no longer valid.
  1391. }
  1392. return $passwordChanged;
  1393. }
  1394. /**
  1395. * Compare a password-like code to a hashed value, to determine if they match.
  1396. *
  1397. * Note that this is not limited only to use for user login passwords, but can be used where ever a human-readable
  1398. * password-like code is needed.
  1399. *
  1400. * @param string $unhashedPassword The password-like code entered by the user.
  1401. * @param string $hashedPassword The hashed password-like code that the entered password-like code is to be compared to.
  1402. *
  1403. * @return bool True if the $unhashedPassword matches the $hashedPassword with the given hashing method; false if they do not
  1404. * match, or if there was an error (such as an empty password or invalid code).
  1405. */
  1406. public static function passwordsMatch($unhashedPassword, $hashedPassword)
  1407. {
  1408. $passwordsMatch = false;
  1409. if (!isset($unhashedPassword) || !is_string($unhashedPassword) || empty($unhashedPassword)) {
  1410. return LogUtil::registerArgsError();
  1411. }
  1412. if (!isset($hashedPassword) || !is_string($hashedPassword) || empty($hashedPassword) || (strpos($hashedPassword, UsersConstant::SALT_DELIM) === false)) {
  1413. return LogUtil::registerArgsError();
  1414. }
  1415. $passwordsMatch = SecurityUtil::checkSaltedHash($unhashedPassword, $hashedPassword, self::getPasswordHashMethods(true), UsersConstant::SALT_DELIM);
  1416. return $passwordsMatch;
  1417. }
  1418. /**
  1419. * Delete the contents of a user variable.
  1420. *
  1421. * This can either be
  1422. * - a variable stored in the users table or
  1423. * - an attribute to the users table, either a new style sttribute or the old style user information
  1424. *
  1425. * Examples:
  1426. * UserUtil::delVar('ublock'); // clears the recent users table entry for 'ublock'
  1427. * UserUtil::delVar('_YOURAVATAR', 123), // removes a users avatar, old style (uid = 123)
  1428. * UserUtil::delVar('avatar', 123); // removes a users avatar, new style (uid=123)
  1429. * (internally both the new style and the old style clear the same attribute)
  1430. *
  1431. * It does not allow the deletion of uid, email, uname, pass (password), as these are mandatory
  1432. * fields in the users table.
  1433. *
  1434. * @param string $name The name of the variable.
  1435. * @param integer $uid The user to delete the variable for.
  1436. *
  1437. * @return boolen true on success, false on failure
  1438. */
  1439. public static function delVar($name, $uid = -1)
  1440. {
  1441. // Prevent deletion of core fields (duh)
  1442. if (empty($name) || ($name == 'uid') || ($name == 'email') || ($name == 'pass') || ($name == 'uname')
  1443. || ($name == 'activated')) {
  1444. return false;
  1445. }
  1446. if ($uid == -1) {
  1447. $uid = ServiceUtil::get('request')->getSession()->get('uid');
  1448. }
  1449. if (empty($uid)) {
  1450. return false;
  1451. }
  1452. // Special delete value for approved_by
  1453. if ($name == 'approved_by') {
  1454. return (bool)self::setVar($name, -1, $uid);
  1455. }
  1456. $isRegistration = self::isRegistration($uid);
  1457. $origUserObj = self::getVars($uid, false, 'uid', $isRegistration);
  1458. if (!$origUserObj) {
  1459. // No such user record!
  1460. return false;
  1461. }
  1462. $varIsDeleted = false;
  1463. // Cannot delVar the user's uid or uname
  1464. if (($name != 'uid') && ($name != 'uname')) {
  1465. // get user given a uid
  1466. $em = \ServiceUtil::get('doctrine')->getManager();
  1467. $user = $em->getRepository('UsersModule\Entity\User')->findOneBy(array('uid' => $uid));
  1468. if (self::fieldAlias($name)) {
  1469. // this value comes from the users table
  1470. $oldValue = isset($origUserObj[$name]) ? $origUserObj[$name] : null;
  1471. $user[$name] = '';
  1472. $em->flush();
  1473. $varIsDeleted = true;
  1474. } else {
  1475. // Not a table field alias, not 'uid', and not 'uname'. Treat it as an attribute.
  1476. $dudAttributeName = self::convertOldDynamicUserDataAlias($name);
  1477. if ($dudAttributeName) {
  1478. LogUtil::log(__f('Warning! User variable [%1$s] is deprecated. Please use [%2$s] instead.', array($name, $mappingarray[$name])), E_USER_DEPRECATED);
  1479. // $name is a former DUD /old style user information now stored as an attribute
  1480. $attributeName = $dudAttributeName;
  1481. } else {
  1482. // $name not in the users table and also not found in the mapping array and also not one of the
  1483. // forbidden names, let's make an attribute out of it
  1484. $attributeName = $name;
  1485. }
  1486. $oldValue = isset($origUserObj['__ATTRIBUTES__'][$attributeName]) ? $origUserObj['__ATTRIBUTES__'][$attributeName] : null;
  1487. $user->delAttribute($attributeName);
  1488. $varIsDeleted = true;
  1489. }
  1490. // force loading of attributes from db
  1491. $updatedUserObj = self::getVars($uid, true, '', $isRegistration);
  1492. if (!$updatedUserObj) {
  1493. // Should never get here!
  1494. return false;
  1495. }
  1496. // Do not fire update event/hook unless the update happened, it was not a registration record, it was not
  1497. // the password being updated, and the system is not currently being installed.
  1498. if ($varIsDeleted && ($name != 'pass') && !System::isInstalling()) {
  1499. // Fire the event
  1500. $eventArgs = array(
  1501. 'action' => 'delVar',
  1502. 'field' => isset($attributeName) ? null : $name,
  1503. 'attribute' => isset($attributeName) ? $attributeName : null,
  1504. );
  1505. $eventData = array(
  1506. 'old_value' => $oldValue,
  1507. );
  1508. if ($isRegistration) {
  1509. $updateEvent = new GenericEvent($updatedUserObj, $eventArgs, $eventData);
  1510. EventUtil::dispatch('user.registration.update', $updateEvent);
  1511. } else {
  1512. $updateEvent = new GenericEvent($updatedUserObj, $eventArgs, $eventData);
  1513. EventUtil::dispatch('user.account.update', $updateEvent);
  1514. }
  1515. }
  1516. }
  1517. return $varIsDeleted;
  1518. }
  1519. /**
  1520. * Get the user's theme.
  1521. *
  1522. * This function will return the current theme for the user.
  1523. * Order of theme priority:
  1524. * - page-specific
  1525. * - category
  1526. * - user
  1527. * - system
  1528. *
  1529. * @param boolean $force True to ignore the cache.
  1530. *
  1531. * @return string the name of the user's theme
  1532. * @throws RuntimeException If this function was unable to calculate theme name.
  1533. */
  1534. public static function getTheme($force = false)
  1535. {
  1536. static $theme;
  1537. if (isset($theme) && !$force) {
  1538. return $theme;
  1539. }
  1540. // Page-specific theme
  1541. $request = ServiceUtil::get('request');
  1542. $pagetheme = $request->get('theme', null);
  1543. $type = $request->attributes->get('_controller', null);
  1544. if (!empty($pagetheme)) {
  1545. $themeinfo = ThemeUtil::getInfo(ThemeUtil::getIDFromName($pagetheme));
  1546. if ($themeinfo['state'] == ThemeUtil::STATE_ACTIVE && ($themeinfo['user'] || $themeinfo['system'] || ($themeinfo['admin'] && ($type == 'admin'))) && is_dir(ZIKULA_ROOT.'/themes/' . DataUtil::formatForOS($themeinfo['directory']))) {
  1547. return self::_getThemeFilterEvent($themeinfo['name'], 'page-specific');
  1548. }
  1549. }
  1550. // check for an admin theme
  1551. if (($type == 'admin' || $type == 'adminplugin') && SecurityUtil::checkPermission('::', '::', ACCESS_EDIT)) {
  1552. $admintheme = ModUtil::getVar('Admin', 'admintheme');
  1553. if (!empty($admintheme)) {
  1554. $themeinfo = ThemeUtil::getInfo(ThemeUtil::getIDFromName($admintheme));
  1555. if ($themeinfo && $themeinfo['state'] == ThemeUtil::STATE_ACTIVE && is_dir(ZIKULA_ROOT.'/themes/' . DataUtil::formatForOS($themeinfo['directory']))) {
  1556. return self::_getThemeFilterEvent($themeinfo['name'], 'admin-theme');
  1557. }
  1558. }
  1559. }
  1560. // set a new theme for the user
  1561. $session = $request->getSession();
  1562. $newtheme = $request->get('newtheme');
  1563. if (!empty($newtheme) && System::getVar('theme_change')) {
  1564. $themeinfo = ThemeUtil::getInfo(ThemeUtil::getIDFromName($newtheme));
  1565. if ($themeinfo && $themeinfo['state'] == ThemeUtil::STATE_ACTIVE && is_dir(ZIKULA_ROOT.'/themes/' . DataUtil::formatForOS($themeinfo['directory']))) {
  1566. if (self::isLoggedIn()) {
  1567. self::setVar('theme', $newtheme);
  1568. } else {
  1569. $session->set('theme', $newtheme);
  1570. }
  1571. return self::_getThemeFilterEvent($themeinfo['name'], 'new-theme');
  1572. }
  1573. }
  1574. // User theme
  1575. if (System::getVar('theme_change') || SecurityUtil::checkPermission('::', '::', ACCESS_ADMIN)) {
  1576. if ((self::isLoggedIn())) {
  1577. $usertheme = self::getVar('theme');
  1578. } else {
  1579. $usertheme = $session->get('theme');
  1580. }
  1581. $themeinfo = ThemeUtil::getInfo(ThemeUtil::getIDFromName($usertheme));
  1582. if ($themeinfo && $themeinfo['state'] == ThemeUtil::STATE_ACTIVE && is_dir(ZIKULA_ROOT.'/themes/' . DataUtil::formatForOS($themeinfo['directory']))) {
  1583. return self::_getThemeFilterEvent($themeinfo['name'], 'user-theme');
  1584. }
  1585. }
  1586. // default site theme
  1587. $defaulttheme = System::getVar('Default_Theme');
  1588. $themeinfo = ThemeUtil::getInfo(ThemeUtil::getIDFromName($defaulttheme));
  1589. if ($themeinfo && $themeinfo['state'] == ThemeUtil::STATE_ACTIVE && is_dir(ZIKULA_ROOT.'/themes/' . DataUtil::formatForOS($themeinfo['directory']))) {
  1590. return self::_getThemeFilterEvent($themeinfo['name'], 'default-theme');
  1591. }
  1592. if (!System::isInstalling()) {
  1593. throw new RuntimeException(__('UserUtil::getTheme() is unable to calculate theme name.'));
  1594. }
  1595. }
  1596. /**
  1597. * Filter results for a given getTheme() type.
  1598. *
  1599. * @param string $themeName Theme name.
  1600. * @param string $type Event type.
  1601. *
  1602. * @return string Theme name
  1603. */
  1604. private static function _getThemeFilterEvent($themeName, $type)
  1605. {
  1606. $event = new GenericEvent(null, array('type' => $type), $themeName);
  1607. return EventUtil::dispatch('user.gettheme', $event)->getData();
  1608. }
  1609. /**
  1610. * Get a list of user information.
  1611. *
  1612. * @param string $sortbyfield Sort by field.
  1613. * @param string $sortorder Sort by order.
  1614. * @param integer $limit Select limit.
  1615. * @param integer $offset Select offset.
  1616. * @param string $activated Activated value.
  1617. * @param string $field Field for filter.
  1618. * @param string $expression Like expression.
  1619. * @param string $where Where clause.
  1620. *
  1621. * @return array Array of users.
  1622. */
  1623. public static function getAll($sortbyfield = 'uname', $sortorder = 'ASC', $limit = null, $offset = null, $activated = '', $field = '', $expression = '', $where = '')
  1624. {
  1625. $user = new \UsersModule\Entity\User;
  1626. if (empty($where)) {
  1627. $whereFragments = array();
  1628. if (!empty($field) && isset($user[$field]) && !empty($expression)) {
  1629. $whereFragments[] = 'u.' . $field . ' LIKE \'' . DataUtil::formatForStore($expression) . '\'';
  1630. }
  1631. if (!empty($activated) && is_numeric($activated) && isset($user['activated'])) {
  1632. $whereFragments[] = 'u.activated <> "' . DataUtil::formatForStore($activated) . '"';
  1633. }
  1634. if (!empty($whereFragments)) {
  1635. $where = 'WHERE ' . implode(' AND ', $whereFragments);
  1636. }
  1637. }
  1638. if (!empty($sortbyfield)) {
  1639. $sortFragments = array();
  1640. $sortFragments[] = 'u.'. $sortbyfield . ' ' . DataUtil::formatForStore($sortorder);
  1641. if ($sortbyfield != 'uname') {
  1642. $sortFragments[] = 'u.uname ASC';
  1643. }
  1644. if (!empty($sortFragments)) {
  1645. $orderby = 'ORDER BY ' . implode(', ', $sortFragments);
  1646. }
  1647. }
  1648. $em = \ServiceUtil::get('doctrine')->getManager();
  1649. $dql = "SELECT u FROM UsersModule\Entity\User u $where $orderby $limit_clause";
  1650. $query = $em->createQuery($dql);
  1651. if (isset($limit) && is_numeric($limit) && $limit > 0) {
  1652. $query->setMaxResults($limit);
  1653. if (isset($offset) && is_numeric($offset) && $offset > 0) {
  1654. $query->setFirstResult($offset);
  1655. }
  1656. }
  1657. $users = $query->getResult(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);
  1658. $usersObj = array();
  1659. foreach ($users as $user) {
  1660. $usersObj[$user['uid']] = $user;
  1661. }
  1662. return $usersObj;
  1663. }
  1664. /**
  1665. * Get the uid of a user from the username.
  1666. *
  1667. * @param string $uname The username.
  1668. * @param bool $forRegistration Get the id for a pending registration (default = false).
  1669. *
  1670. * @return integer|boolean The uid if found, false if not.
  1671. */
  1672. public static function getIdFromName($uname, $forRegistration = false)
  1673. {
  1674. $result = self::getVars($uname, false, 'uname', $forRegistration);
  1675. return ($result && isset($result['uid']) ? $result['uid'] : false);
  1676. }
  1677. /**
  1678. * Get the uid of a user from the email (case for unique emails).
  1679. *
  1680. * @param string $email The user email.
  1681. * @param bool $forRegistration Get the id for a pending registration (default = false).
  1682. *
  1683. * @return integer|boolean The uid if found, false if not.
  1684. */
  1685. public static function getIdFromEmail($email, $forRegistration = false)
  1686. {
  1687. $result = self::getVars($email, false, 'email', $forRegistration);
  1688. return ($result && isset($result['uid']) ? $result['uid'] : false);
  1689. }
  1690. /**
  1691. * Checks the alias and returns if we save the data in the Profile module's user_data table or the users table.
  1692. *
  1693. * This should be removed if we ever go fully dynamic
  1694. *
  1695. * @param string $label The alias of the field to check.
  1696. *
  1697. * @return true if found, false if not, void upon error
  1698. */
  1699. public static function fieldAlias($label)
  1700. {
  1701. $isFieldAlias = false;
  1702. // no change in uid or uname allowed, empty label is not an alias
  1703. if (($label != 'uid') && ($label != 'uname') && !empty($label)) {
  1704. $userObj = new \UsersModule\Entity\User;
  1705. $isFieldAlias = isset($userObj[$label]) ? true : false;
  1706. }
  1707. return $isFieldAlias;
  1708. }
  1709. /**
  1710. * Determine if the current session is that of an anonymous user.
  1711. *
  1712. * @return boolean
  1713. */
  1714. public static function isGuestUser()
  1715. {
  1716. return !ServiceUtil::get('request')->getSession()->get('uid', 0);
  1717. }
  1718. /**
  1719. * Determine if the record represented by the $uid is a registration or not.
  1720. *
  1721. * @param numeric $uid The uid of the record in question.
  1722. *
  1723. * @throws InvalidArgumentException If the uid is not valid.
  1724. *
  1725. * @return boolean True if it is a registration record, otherwise false;
  1726. */
  1727. public static function isRegistration($uid)
  1728. {
  1729. if (!isset($uid) || !is_numeric($uid) || (!is_int($uid) && ((string)((int)$uid) != $uid))) {
  1730. throw new InvalidArgumentException(__('An invalid uid was provided.'));
  1731. }
  1732. $isRegistration = false;
  1733. $user = self::getVars($uid);
  1734. if (!$user) {
  1735. $user = self::getVars($uid, false, 'uid', true);
  1736. if ($user) {
  1737. $isRegistration = true;
  1738. }
  1739. }
  1740. return $isRegistration;
  1741. }
  1742. }