PageRenderTime 57ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/kernel/classes/datatypes/ezuser/ezuser.php

https://github.com/granitegreg/ezpublish
PHP | 2814 lines | 2017 code | 245 blank | 552 comment | 279 complexity | 6b6800fc5d1a48bc2a093bb996a441f7 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. //
  3. // Definition of eZUser class
  4. //
  5. // Created on: <10-Jun-2002 17:03:15 bf>
  6. //
  7. // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  8. // SOFTWARE NAME: eZ Publish
  9. // SOFTWARE RELEASE: 4.1.x
  10. // COPYRIGHT NOTICE: Copyright (C) 1999-2011 eZ Systems AS
  11. // SOFTWARE LICENSE: GNU General Public License v2.0
  12. // NOTICE: >
  13. // This program is free software; you can redistribute it and/or
  14. // modify it under the terms of version 2.0 of the GNU General
  15. // Public License as published by the Free Software Foundation.
  16. //
  17. // This program is distributed in the hope that it will be useful,
  18. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. // GNU General Public License for more details.
  21. //
  22. // You should have received a copy of version 2.0 of the GNU General
  23. // Public License along with this program; if not, write to the Free
  24. // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  25. // MA 02110-1301, USA.
  26. //
  27. //
  28. // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  29. //
  30. /*!
  31. \class eZUser ezuser.php
  32. \brief eZUser handles eZ Publish user accounts
  33. \ingroup eZDatatype
  34. */
  35. class eZUser extends eZPersistentObject
  36. {
  37. /// MD5 of password
  38. const PASSWORD_HASH_MD5_PASSWORD = 1;
  39. /// MD5 of user and password
  40. const PASSWORD_HASH_MD5_USER = 2;
  41. /// MD5 of site, user and password
  42. const PASSWORD_HASH_MD5_SITE = 3;
  43. /// Legacy support for mysql hashed passwords
  44. const PASSWORD_HASH_MYSQL = 4;
  45. /// Passwords in plaintext, should not be used for real sites
  46. const PASSWORD_HASH_PLAINTEXT = 5;
  47. // Crypted passwords
  48. const PASSWORD_HASH_CRYPT = 6;
  49. /// Authenticate by matching the login field
  50. const AUTHENTICATE_LOGIN = 1;
  51. /// Authenticate by matching the email field
  52. const AUTHENTICATE_EMAIL = 2;
  53. const AUTHENTICATE_ALL = 3; //EZ_USER_AUTHENTICATE_LOGIN | EZ_USER_AUTHENTICATE_EMAIL;
  54. protected static $anonymousId = null;
  55. function eZUser( $row = array() )
  56. {
  57. $this->eZPersistentObject( $row );
  58. $this->OriginalPassword = false;
  59. $this->OriginalPasswordConfirm = false;
  60. }
  61. static function definition()
  62. {
  63. static $definition = array( 'fields' => array( 'contentobject_id' => array( 'name' => 'ContentObjectID',
  64. 'datatype' => 'integer',
  65. 'default' => 0,
  66. 'required' => true,
  67. 'foreign_class' => 'eZContentObject',
  68. 'foreign_attribute' => 'id',
  69. 'multiplicity' => '0..1' ),
  70. 'login' => array( 'name' => 'Login',
  71. 'datatype' => 'string',
  72. 'default' => '',
  73. 'required' => true ),
  74. 'email' => array( 'name' => 'Email',
  75. 'datatype' => 'string',
  76. 'default' => '',
  77. 'required' => true ),
  78. 'password_hash' => array( 'name' => 'PasswordHash',
  79. 'datatype' => 'string',
  80. 'default' => '',
  81. 'required' => true ),
  82. 'password_hash_type' => array( 'name' => 'PasswordHashType',
  83. 'datatype' => 'integer',
  84. 'default' => 1,
  85. 'required' => true ) ),
  86. 'keys' => array( 'contentobject_id' ),
  87. 'sort' => array( 'contentobject_id' => 'asc' ),
  88. 'function_attributes' => array( 'contentobject' => 'contentObject',
  89. 'groups' => 'groups',
  90. 'has_stored_login' => 'hasStoredLogin',
  91. 'original_password' => 'originalPassword',
  92. 'original_password_confirm' => 'originalPasswordConfirm',
  93. 'roles' => 'roles',
  94. 'role_id_list' => 'roleIDList',
  95. 'limited_assignment_value_list' => 'limitValueList',
  96. 'is_logged_in' => 'isLoggedIn',
  97. 'is_enabled' => 'isEnabled',
  98. 'is_locked' => 'isLocked',
  99. 'last_visit' => 'lastVisit',
  100. 'login_count' => 'loginCount',
  101. 'has_manage_locations' => 'hasManageLocations' ),
  102. 'relations' => array( 'contentobject_id' => array( 'class' => 'ezcontentobject',
  103. 'field' => 'id' ) ),
  104. 'class_name' => 'eZUser',
  105. 'name' => 'ezuser' );
  106. return $definition;
  107. }
  108. /*!
  109. \return a textual identifier for the hash type $id
  110. */
  111. static function passwordHashTypeName( $id )
  112. {
  113. switch ( $id )
  114. {
  115. case self::PASSWORD_HASH_MD5_PASSWORD:
  116. {
  117. return 'md5_password';
  118. } break;
  119. case self::PASSWORD_HASH_MD5_USER:
  120. {
  121. return 'md5_user';
  122. } break;
  123. case self::PASSWORD_HASH_MD5_SITE:
  124. {
  125. return 'md5_site';
  126. } break;
  127. case self::PASSWORD_HASH_MYSQL:
  128. {
  129. return 'mysql';
  130. } break;
  131. case self::PASSWORD_HASH_PLAINTEXT:
  132. {
  133. return 'plaintext';
  134. } break;
  135. case self::PASSWORD_HASH_CRYPT:
  136. {
  137. return 'crypt';
  138. } break;
  139. }
  140. }
  141. /*!
  142. \return the hash type for the textual identifier $identifier
  143. */
  144. static function passwordHashTypeID( $identifier )
  145. {
  146. switch ( $identifier )
  147. {
  148. case 'md5_password':
  149. {
  150. return self::PASSWORD_HASH_MD5_PASSWORD;
  151. } break;
  152. default:
  153. case 'md5_user':
  154. {
  155. return self::PASSWORD_HASH_MD5_USER;
  156. } break;
  157. case 'md5_site':
  158. {
  159. return self::PASSWORD_HASH_MD5_SITE;
  160. } break;
  161. case 'mysql':
  162. {
  163. return self::PASSWORD_HASH_MYSQL;
  164. } break;
  165. case 'plaintext':
  166. {
  167. return self::PASSWORD_HASH_PLAINTEXT;
  168. } break;
  169. case 'crypt':
  170. {
  171. return self::PASSWORD_HASH_CRYPT;
  172. } break;
  173. }
  174. }
  175. /*!
  176. Check if current user has "content/manage_locations" access
  177. */
  178. function hasManageLocations()
  179. {
  180. $accessResult = $this->hasAccessTo( 'content', 'manage_locations' );
  181. if ( $accessResult['accessWord'] != 'no' )
  182. {
  183. return true;
  184. }
  185. return false;
  186. }
  187. static function create( $contentObjectID )
  188. {
  189. $row = array(
  190. 'contentobject_id' => $contentObjectID,
  191. 'login' => null,
  192. 'email' => null,
  193. 'password_hash' => null,
  194. 'password_hash_type' => null
  195. );
  196. return new eZUser( $row );
  197. }
  198. function store( $fieldFilters = null )
  199. {
  200. $this->Email = trim( $this->Email );
  201. $userID = $this->attribute( 'contentobject_id' );
  202. // Clear memory cache
  203. unset( $GLOBALS['eZUserObject_' . $userID] );
  204. $GLOBALS['eZUserObject_' . $userID] = $this;
  205. self::purgeUserCacheByUserId( $userID );
  206. eZPersistentObject::store( $fieldFilters );
  207. }
  208. function originalPassword()
  209. {
  210. return $this->OriginalPassword;
  211. }
  212. function setOriginalPassword( $password )
  213. {
  214. $this->OriginalPassword = $password;
  215. }
  216. function originalPasswordConfirm()
  217. {
  218. return $this->OriginalPasswordConfirm;
  219. }
  220. function setOriginalPasswordConfirm( $password )
  221. {
  222. $this->OriginalPasswordConfirm = $password;
  223. }
  224. function hasStoredLogin()
  225. {
  226. $db = eZDB::instance();
  227. $contentObjectID = $this->attribute( 'contentobject_id' );
  228. $sql = "SELECT * FROM ezuser WHERE contentobject_id='$contentObjectID' AND LENGTH( login ) > 0";
  229. $rows = $db->arrayQuery( $sql );
  230. return !empty( $rows );
  231. }
  232. /*!
  233. Fills in the \a $id, \a $login, \a $email and \a $password for the user
  234. and creates the proper password hash.
  235. */
  236. function setInformation( $id, $login, $email, $password, $passwordConfirm = false )
  237. {
  238. $this->setAttribute( "contentobject_id", $id );
  239. $this->setAttribute( "email", $email );
  240. $this->setAttribute( "login", $login );
  241. if ( eZUser::validatePassword( $password ) and
  242. $password == $passwordConfirm ) // Cannot change login or password_hash without login and password
  243. {
  244. $this->setAttribute( "password_hash", eZUser::createHash( $login, $password, eZUser::site(),
  245. eZUser::hashType() ) );
  246. $this->setAttribute( "password_hash_type", eZUser::hashType() );
  247. }
  248. else
  249. {
  250. $this->setOriginalPassword( $password );
  251. $this->setOriginalPasswordConfirm( $passwordConfirm );
  252. }
  253. }
  254. static function fetch( $id, $asObject = true )
  255. {
  256. if ( !$id )
  257. return null;
  258. return eZPersistentObject::fetchObject( eZUser::definition(),
  259. null,
  260. array( 'contentobject_id' => $id ),
  261. $asObject );
  262. }
  263. static function fetchByName( $login, $asObject = true )
  264. {
  265. return eZPersistentObject::fetchObject( eZUser::definition(),
  266. null,
  267. array( 'LOWER( login )' => strtolower( $login ) ),
  268. $asObject );
  269. }
  270. static function fetchByEmail( $email, $asObject = true )
  271. {
  272. return eZPersistentObject::fetchObject( eZUser::definition(),
  273. null,
  274. array( 'LOWER( email )' => strtolower( $email ) ),
  275. $asObject );
  276. }
  277. /*!
  278. \static
  279. \return a list of the logged in users.
  280. \param $asObject If false it will return a list with only the names of the users as elements and user ID as key,
  281. otherwise each entry is a eZUser object.
  282. \sa fetchLoggedInCount
  283. */
  284. static function fetchLoggedInList( $asObject = false, $offset = false, $limit = false, $sortBy = false )
  285. {
  286. $db = eZDB::instance();
  287. $time = time() - eZINI::instance()->variable( 'Session', 'ActivityTimeout' );
  288. $parameters = array();
  289. if ( $offset )
  290. $parameters['offset'] =(int) $offset;
  291. if ( $limit )
  292. $parameters['limit'] =(int) $limit;
  293. $sortText = '';
  294. if ( $asObject )
  295. {
  296. $selectArray = array( "distinct ezuser.*" );
  297. }
  298. else
  299. {
  300. $selectArray = array( "ezuser.contentobject_id as user_id", "ezcontentobject.name" );
  301. }
  302. if ( $sortBy !== false )
  303. {
  304. $sortElements = array();
  305. if ( !is_array( $sortBy ) )
  306. {
  307. $sortBy = array( array( $sortBy, true ) );
  308. }
  309. else if ( !is_array( $sortBy[0] ) )
  310. $sortBy = array( $sortBy );
  311. $sortColumns = array();
  312. foreach ( $sortBy as $sortElements )
  313. {
  314. $sortColumn = $sortElements[0];
  315. $sortOrder = $sortElements[1];
  316. $orderText = $sortOrder ? 'asc' : 'desc';
  317. switch ( $sortColumn )
  318. {
  319. case 'user_id':
  320. {
  321. $sortColumn = "ezuser.contentobject_id $orderText";
  322. } break;
  323. case 'login':
  324. {
  325. $sortColumn = "ezuser.login $orderText";
  326. } break;
  327. case 'activity':
  328. {
  329. $selectArray[] = "ezuservisit.current_visit_timestamp AS activity";
  330. $sortColumn = "activity $orderText";
  331. } break;
  332. case 'email':
  333. {
  334. $sortColumn = "ezuser.email $orderText";
  335. } break;
  336. default:
  337. {
  338. eZDebug::writeError( "Unkown sort column '$sortColumn'", __METHOD__ );
  339. $sortColumn = false;
  340. } break;
  341. }
  342. if ( $sortColumn )
  343. $sortColumns[] = $sortColumn;
  344. }
  345. if ( !empty( $sortColumns ) )
  346. $sortText = "ORDER BY " . implode( ', ', $sortColumns );
  347. }
  348. if ( $asObject )
  349. {
  350. $selectText = implode( ', ', $selectArray );
  351. $sql = "SELECT $selectText
  352. FROM ezuservisit, ezuser
  353. WHERE ezuservisit.user_id != '" . self::anonymousId() . "' AND
  354. ezuservisit.current_visit_timestamp > '$time' AND
  355. ezuser.contentobject_id = ezuservisit.user_id
  356. $sortText";
  357. $rows = $db->arrayQuery( $sql, $parameters );
  358. $list = array();
  359. foreach ( $rows as $row )
  360. {
  361. $list[] = new eZUser( $row );
  362. }
  363. }
  364. else
  365. {
  366. $selectText = implode( ', ', $selectArray );
  367. $sql = "SELECT $selectText
  368. FROM ezuservisit, ezuser, ezcontentobject
  369. WHERE ezuservisit.user_id != '" . self::anonymousId() . "' AND
  370. ezuservisit.current_visit_timestamp > '$time' AND
  371. ezuser.contentobject_id = ezuservisit.user_id AND
  372. ezcontentobject.id = ezuser.contentobject_id
  373. $sortText";
  374. $rows = $db->arrayQuery( $sql, $parameters );
  375. $list = array();
  376. foreach ( $rows as $row )
  377. {
  378. $list[$row['user_id']] = $row['name'];
  379. }
  380. }
  381. return $list;
  382. }
  383. /*!
  384. \return the number of logged in users in the system.
  385. \note The count will be cached for the current page if caching is allowed.
  386. \sa fetchAnonymousCount
  387. */
  388. static function fetchLoggedInCount()
  389. {
  390. if ( isset( $GLOBALS['eZSiteBasics']['no-cache-adviced'] ) and
  391. !$GLOBALS['eZSiteBasics']['no-cache-adviced'] and
  392. isset( $GLOBALS['eZUserLoggedInCount'] ) )
  393. return $GLOBALS['eZUserLoggedInCount'];
  394. $db = eZDB::instance();
  395. $time = time() - eZINI::instance()->variable( 'Session', 'ActivityTimeout' );
  396. $sql = "SELECT count( DISTINCT user_id ) as count
  397. FROM ezuservisit
  398. WHERE user_id != '" . self::anonymousId() . "' AND
  399. user_id > 0 AND
  400. current_visit_timestamp > '$time'";
  401. $rows = $db->arrayQuery( $sql );
  402. $count = isset( $rows[0] ) ? $rows[0]['count'] : 0;
  403. $GLOBALS['eZUserLoggedInCount'] = $count;
  404. return $count;
  405. }
  406. /**
  407. * Return the number of anonymous users in the system.
  408. *
  409. * @deprecated As of 4.4 since default session handler does not support this.
  410. * @return int
  411. */
  412. static function fetchAnonymousCount()
  413. {
  414. if ( isset( $GLOBALS['eZSiteBasics']['no-cache-adviced'] ) and
  415. !$GLOBALS['eZSiteBasics']['no-cache-adviced'] and
  416. isset( $GLOBALS['eZUserAnonymousCount'] ) )
  417. return $GLOBALS['eZUserAnonymousCount'];
  418. $db = eZDB::instance();
  419. $time = time();
  420. $ini = eZINI::instance();
  421. $activityTimeout = $ini->variable( 'Session', 'ActivityTimeout' );
  422. $sessionTimeout = $ini->variable( 'Session', 'SessionTimeout' );
  423. $time = $time + $sessionTimeout - $activityTimeout;
  424. $sql = "SELECT count( session_key ) as count
  425. FROM ezsession
  426. WHERE user_id = '" . self::anonymousId() . "' AND
  427. expiration_time > '$time'";
  428. $rows = $db->arrayQuery( $sql );
  429. $count = isset( $rows[0] ) ? $rows[0]['count'] : 0;
  430. $GLOBALS['eZUserAnonymousCount'] = $count;
  431. return $count;
  432. }
  433. /*!
  434. \static
  435. \return true if the user with ID $userID is currently logged into the system.
  436. \note The information will be cached for the current page if caching is allowed.
  437. \sa fetchLoggedInList
  438. */
  439. static function isUserLoggedIn( $userID )
  440. {
  441. $userID = (int)$userID;
  442. if ( isset( $GLOBALS['eZSiteBasics']['no-cache-adviced'] ) and
  443. !$GLOBALS['eZSiteBasics']['no-cache-adviced'] and
  444. isset( $GLOBALS['eZUserLoggedInMap'][$userID] ) )
  445. return $GLOBALS['eZUserLoggedInMap'][$userID];
  446. $db = eZDB::instance();
  447. $time = time() - eZINI::instance()->variable( 'Session', 'ActivityTimeout' );
  448. $sql = "SELECT DISTINCT user_id
  449. FROM ezuservisit
  450. WHERE user_id = '" . $userID . "' AND
  451. current_visit_timestamp > '$time'";
  452. $rows = $db->arrayQuery( $sql, array( 'limit' => 2 ) );
  453. $isLoggedIn = isset( $rows[0] );
  454. $GLOBALS['eZUserLoggedInMap'][$userID] = $isLoggedIn;
  455. return $isLoggedIn;
  456. }
  457. /*!
  458. \static
  459. Removes any cached session information, this is:
  460. - logged in user count
  461. - anonymous user count
  462. - logged in user map
  463. */
  464. static function clearSessionCache()
  465. {
  466. unset( $GLOBALS['eZUserLoggedInCount'] );
  467. unset( $GLOBALS['eZUserAnonymousCount'] );
  468. unset( $GLOBALS['eZUserLoggedInMap'] );
  469. }
  470. /**
  471. * Remove session data for user \a $userID.
  472. * @todo should use eZSession api (needs to be created) so
  473. * callbacks (like preference / basket..) is cleared as well.
  474. *
  475. * @params int $userID
  476. */
  477. static function removeSessionData( $userID )
  478. {
  479. eZUser::clearSessionCache();
  480. eZSession::getHandlerInstance()->deleteByUserIDs( array( $userID ) );
  481. }
  482. /*!
  483. Removes the user from the ezuser table.
  484. \note Will also remove any notifications and session related to the user.
  485. */
  486. static function removeUser( $userID )
  487. {
  488. $user = eZUser::fetch( $userID );
  489. if ( !$user )
  490. {
  491. eZDebug::writeError( "unable to find user with ID $userID", __METHOD__ );
  492. return false;
  493. }
  494. eZUser::removeSessionData( $userID );
  495. eZSubtreeNotificationRule::removeByUserID( $userID );
  496. eZCollaborationNotificationRule::removeByUserID( $userID );
  497. eZUserSetting::removeByUserID( $userID );
  498. eZUserAccountKey::removeByUserID( $userID );
  499. eZForgotPassword::removeByUserID( $userID );
  500. eZWishList::removeByUserID( $userID );
  501. // only remove general digest setting if there are no other users with the same e-mail
  502. $email = $user->attribute( 'email' );
  503. $usersWithEmailCount = eZPersistentObject::count( eZUser::definition(), array( 'email' => $email ) );
  504. if ( $usersWithEmailCount == 1 )
  505. {
  506. eZGeneralDigestUserSettings::removeByAddress( $email );
  507. }
  508. eZPersistentObject::removeObject( eZUser::definition(),
  509. array( 'contentobject_id' => $userID ) );
  510. return true;
  511. }
  512. /*!
  513. \return a list of valid and enabled users, the data returned is an array
  514. with ezcontentobject database data.
  515. */
  516. static function fetchContentList()
  517. {
  518. $contentObjectStatus = eZContentObject::STATUS_PUBLISHED;
  519. $query = "SELECT ezcontentobject.*
  520. FROM ezuser, ezcontentobject, ezuser_setting
  521. WHERE ezcontentobject.status = '$contentObjectStatus' AND
  522. ezuser_setting.is_enabled = 1 AND
  523. ezcontentobject.id = ezuser.contentobject_id AND
  524. ezuser_setting.user_id = ezuser.contentobject_id";
  525. $db = eZDB::instance();
  526. $rows = $db->arrayQuery( $query );
  527. return $rows;
  528. }
  529. /*!
  530. \static
  531. \return the default hash type which is specified in UserSettings/HashType in site.ini
  532. */
  533. static function hashType()
  534. {
  535. $ini = eZINI::instance();
  536. $type = strtolower( $ini->variable( 'UserSettings', 'HashType' ) );
  537. if ( $type == 'md5_site' )
  538. return self::PASSWORD_HASH_MD5_SITE;
  539. else if ( $type == 'md5_user' )
  540. return self::PASSWORD_HASH_MD5_USER;
  541. else if ( $type == 'plaintext' )
  542. return self::PASSWORD_HASH_PLAINTEXT;
  543. else if ( $type == 'crypt' )
  544. return self::PASSWORD_HASH_CRYPT;
  545. else
  546. return self::PASSWORD_HASH_MD5_PASSWORD;
  547. }
  548. /*!
  549. \static
  550. \return the site name used in password hashing.
  551. */
  552. static function site()
  553. {
  554. $ini = eZINI::instance();
  555. return $ini->variable( 'UserSettings', 'SiteName' );
  556. }
  557. /*!
  558. Fetches a builtin user and returns it, this helps avoid special cases where
  559. user is not logged in.
  560. */
  561. static function fetchBuiltin( $id )
  562. {
  563. if ( !in_array( $id, $GLOBALS['eZUserBuiltins'] ) )
  564. $id = self::anonymousId();
  565. if ( empty( $GLOBALS["eZUserBuilitinInstance-$id"] ) )
  566. {
  567. $GLOBALS["eZUserBuilitinInstance-$id"] = eZUser::fetch( self::anonymousId() );
  568. }
  569. return $GLOBALS["eZUserBuilitinInstance-$id"];
  570. }
  571. /*!
  572. \return the user id.
  573. */
  574. function id()
  575. {
  576. return $this->ContentObjectID;
  577. }
  578. /*!
  579. \return a bitfield which decides the authenticate methods.
  580. */
  581. static function authenticationMatch()
  582. {
  583. $ini = eZINI::instance();
  584. $matchArray = $ini->variableArray( 'UserSettings', 'AuthenticateMatch' );
  585. $match = 0;
  586. foreach ( $matchArray as $matchItem )
  587. {
  588. switch ( $matchItem )
  589. {
  590. case "login":
  591. {
  592. $match = ( $match | self::AUTHENTICATE_LOGIN );
  593. } break;
  594. case "email":
  595. {
  596. $match = ( $match | self::AUTHENTICATE_EMAIL );
  597. } break;
  598. }
  599. }
  600. return $match;
  601. }
  602. /*!
  603. \return \c true if there can only be one instance of an email address on the site.
  604. */
  605. static function requireUniqueEmail()
  606. {
  607. $ini = eZINI::instance();
  608. return $ini->variable( 'UserSettings', 'RequireUniqueEmail' ) == 'true';
  609. }
  610. /**
  611. * Logs in the user if applied username and password is valid.
  612. *
  613. * @param string $login
  614. * @param string $password
  615. * @param bool $authenticationMatch
  616. * @return mixed eZUser on success, bool false on failure
  617. */
  618. public static function loginUser( $login, $password, $authenticationMatch = false )
  619. {
  620. $user = self::_loginUser( $login, $password, $authenticationMatch );
  621. if ( is_object( $user ) )
  622. {
  623. self::loginSucceeded( $user );
  624. return $user;
  625. }
  626. else
  627. {
  628. self::loginFailed( $user, $login );
  629. return false;
  630. }
  631. }
  632. /**
  633. * Does some house keeping work once a log in has succeeded.
  634. *
  635. * @param eZUser $user
  636. */
  637. protected static function loginSucceeded( $user )
  638. {
  639. $userID = $user->attribute( 'contentobject_id' );
  640. // if audit is enabled logins should be logged
  641. eZAudit::writeAudit( 'user-login', array( 'User id' => $userID, 'User login' => $user->attribute( 'login' ) ) );
  642. eZUser::updateLastVisit( $userID, true );
  643. eZUser::setCurrentlyLoggedInUser( $user, $userID );
  644. // Reset number of failed login attempts
  645. eZUser::setFailedLoginAttempts( $userID, 0 );
  646. }
  647. /**
  648. * Does some house keeping work when a log in has failed.
  649. *
  650. * @param mixed $userID
  651. * @param string $login
  652. */
  653. protected static function loginFailed( $userID = false, $login )
  654. {
  655. $loginEscaped = eZDB::instance()->escapeString( $login );
  656. // Failed login attempts should be logged
  657. eZAudit::writeAudit( 'user-failed-login', array( 'User login' => $loginEscaped,
  658. 'Comment' => 'Failed login attempt: eZUser::loginUser()' ) );
  659. // Increase number of failed login attempts.
  660. if ( $userID )
  661. eZUser::setFailedLoginAttempts( $userID );
  662. }
  663. /**
  664. * Logs in an user if applied login and password is valid.
  665. *
  666. * This method does not do any house keeping work anymore (writing audits, etc).
  667. * When you call this method make sure to call loginSucceeded() or loginFailed()
  668. * depending on the success of the login.
  669. *
  670. * @param string $login
  671. * @param string $password
  672. * @param bool $authenticationMatch
  673. * @return mixed eZUser object on log in success, int userID if the username
  674. * exists but log in failed, or false if the username doesn't exists.
  675. */
  676. protected static function _loginUser( $login, $password, $authenticationMatch = false )
  677. {
  678. $http = eZHTTPTool::instance();
  679. $db = eZDB::instance();
  680. if ( $authenticationMatch === false )
  681. $authenticationMatch = eZUser::authenticationMatch();
  682. $loginEscaped = $db->escapeString( $login );
  683. $passwordEscaped = $db->escapeString( $password );
  684. $loginArray = array();
  685. if ( $authenticationMatch & self::AUTHENTICATE_LOGIN )
  686. $loginArray[] = "login='$loginEscaped'";
  687. if ( $authenticationMatch & self::AUTHENTICATE_EMAIL )
  688. {
  689. if ( eZMail::validate( $login ) )
  690. {
  691. $loginArray[] = "email='$loginEscaped'";
  692. }
  693. }
  694. if ( empty( $loginArray ) )
  695. $loginArray[] = "login='$loginEscaped'";
  696. $loginText = implode( ' OR ', $loginArray );
  697. $contentObjectStatus = eZContentObject::STATUS_PUBLISHED;
  698. $ini = eZINI::instance();
  699. $databaseName = $db->databaseName();
  700. // if mysql
  701. if ( $databaseName === 'mysql' )
  702. {
  703. $query = "SELECT contentobject_id, password_hash, password_hash_type, email, login
  704. FROM ezuser, ezcontentobject
  705. WHERE ( $loginText ) AND
  706. ezcontentobject.status='$contentObjectStatus' AND
  707. ezcontentobject.id=contentobject_id AND
  708. ( ( password_hash_type!=4 ) OR
  709. ( password_hash_type=4 AND
  710. ( $loginText ) AND
  711. password_hash=PASSWORD('$passwordEscaped') ) )";
  712. }
  713. else
  714. {
  715. $query = "SELECT contentobject_id, password_hash,
  716. password_hash_type, email, login
  717. FROM ezuser, ezcontentobject
  718. WHERE ( $loginText )
  719. AND ezcontentobject.status='$contentObjectStatus'
  720. AND ezcontentobject.id=contentobject_id";
  721. }
  722. $users = $db->arrayQuery( $query );
  723. $exists = false;
  724. if ( $users !== false && isset( $users[0] ) )
  725. {
  726. $ini = eZINI::instance();
  727. foreach ( $users as $userRow )
  728. {
  729. $userID = $userRow['contentobject_id'];
  730. $hashType = $userRow['password_hash_type'];
  731. $hash = $userRow['password_hash'];
  732. $exists = eZUser::authenticateHash( $userRow['login'], $password, eZUser::site(),
  733. $hashType,
  734. $hash );
  735. // If hash type is MySql
  736. if ( $hashType == self::PASSWORD_HASH_MYSQL and $databaseName === 'mysql' )
  737. {
  738. $queryMysqlUser = "SELECT contentobject_id, password_hash, password_hash_type, email, login
  739. FROM ezuser, ezcontentobject
  740. WHERE ezcontentobject.status='$contentObjectStatus' AND
  741. password_hash_type=4 AND ( $loginText ) AND password_hash=PASSWORD('$passwordEscaped') ";
  742. $mysqlUsers = $db->arrayQuery( $queryMysqlUser );
  743. if ( isset( $mysqlUsers[0] ) )
  744. $exists = true;
  745. }
  746. eZDebugSetting::writeDebug( 'kernel-user', eZUser::createHash( $userRow['login'], $password, eZUser::site(),
  747. $hashType, $hash ), "check hash" );
  748. eZDebugSetting::writeDebug( 'kernel-user', $hash, "stored hash" );
  749. // If current user has been disabled after a few failed login attempts.
  750. $canLogin = eZUser::isEnabledAfterFailedLogin( $userID );
  751. if ( $exists )
  752. {
  753. // We should store userID for warning message.
  754. $GLOBALS['eZFailedLoginAttemptUserID'] = $userID;
  755. $userSetting = eZUserSetting::fetch( $userID );
  756. $isEnabled = $userSetting->attribute( "is_enabled" );
  757. if ( $hashType != eZUser::hashType() and
  758. strtolower( $ini->variable( 'UserSettings', 'UpdateHash' ) ) == 'true' )
  759. {
  760. $hashType = eZUser::hashType();
  761. $hash = eZUser::createHash( $userRow['login'], $password, eZUser::site(),
  762. $hashType );
  763. $db->query( "UPDATE ezuser SET password_hash='$hash', password_hash_type='$hashType' WHERE contentobject_id='$userID'" );
  764. }
  765. break;
  766. }
  767. }
  768. }
  769. if ( $exists and $isEnabled and $canLogin )
  770. {
  771. return new eZUser( $userRow );
  772. }
  773. else
  774. {
  775. return isset( $userID ) ? $userID : false;
  776. }
  777. }
  778. /*!
  779. \static
  780. Checks if IP address of current user is in \a $ipList.
  781. */
  782. static function isUserIPInList( $ipList )
  783. {
  784. $ipAddress = eZSys::serverVariable( 'REMOTE_ADDR', true );
  785. if ( $ipAddress )
  786. {
  787. $result = false;
  788. foreach( $ipList as $itemToMatch )
  789. {
  790. if ( preg_match("/^(([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+))(\/([0-9]+)$|$)/", $itemToMatch, $matches ) )
  791. {
  792. if ( $matches[6] )
  793. {
  794. if ( eZDebug::isIPInNet( $ipAddress, $matches[1], $matches[7] ) )
  795. {
  796. $result = true;
  797. break;
  798. }
  799. }
  800. else
  801. {
  802. if ( $matches[1] == $ipAddress )
  803. {
  804. $result = true;
  805. break;
  806. }
  807. }
  808. }
  809. }
  810. }
  811. else
  812. {
  813. $result = (
  814. in_array( 'commandline', $ipList ) &&
  815. ( php_sapi_name() == 'cli' )
  816. );
  817. }
  818. return $result;
  819. }
  820. /*!
  821. \static
  822. Returns true if current user is trusted user.
  823. */
  824. static function isTrusted()
  825. {
  826. $ini = eZINI::instance();
  827. // Check if current user is trusted user.
  828. $trustedIPs = $ini->hasVariable( 'UserSettings', 'TrustedIPList' ) ? $ini->variable( 'UserSettings', 'TrustedIPList' ) : array();
  829. // Check if IP address of current user is in $trustedIPs array.
  830. $trustedUser = eZUser::isUserIPInList( $trustedIPs );
  831. if ( $trustedUser )
  832. return true;
  833. return false;
  834. }
  835. /*!
  836. \static
  837. Returns max number of failed login attempts.
  838. */
  839. static function maxNumberOfFailedLogin()
  840. {
  841. $ini = eZINI::instance();
  842. $maxNumberOfFailedLogin = $ini->hasVariable( 'UserSettings', 'MaxNumberOfFailedLogin' ) ? $ini->variable( 'UserSettings', 'MaxNumberOfFailedLogin' ) : '0';
  843. return $maxNumberOfFailedLogin;
  844. }
  845. /*
  846. \static
  847. Returns true if the user can login
  848. If user has number of failed login attempts more than eZUser::maxNumberOfFailedLogin()
  849. and user is not trusted
  850. the user will not be allowed to login.
  851. */
  852. static function isEnabledAfterFailedLogin( $userID, $ignoreTrusted = false )
  853. {
  854. if ( !is_numeric( $userID ) )
  855. return true;
  856. $userObject = eZUser::fetch( $userID );
  857. if ( !$userObject )
  858. return true;
  859. $trustedUser = eZUser::isTrusted();
  860. // If user is trusted we should stop processing
  861. if ( $trustedUser and !$ignoreTrusted )
  862. return true;
  863. $maxNumberOfFailedLogin = eZUser::maxNumberOfFailedLogin();
  864. if ( $maxNumberOfFailedLogin == '0' )
  865. return true;
  866. $failedLoginAttempts = $userObject->failedLoginAttempts();
  867. if ( $failedLoginAttempts > $maxNumberOfFailedLogin )
  868. return false;
  869. return true;
  870. }
  871. /*!
  872. \protected
  873. Makes sure the user \a $user is set as the currently logged in user by
  874. updating the session and setting the necessary global variables.
  875. All login handlers should use this function to ensure that the process
  876. is executed properly.
  877. */
  878. static function setCurrentlyLoggedInUser( $user, $userID )
  879. {
  880. $GLOBALS["eZUserGlobalInstance_$userID"] = $user;
  881. // Set/overwrite the global user, this will be accessed from
  882. // instance() when there is no ID passed to the function.
  883. $GLOBALS["eZUserGlobalInstance_"] = $user;
  884. eZSession::setUserID( $userID );
  885. eZSession::set( 'eZUserLoggedInID', $userID );
  886. self::cleanup();
  887. eZSession::regenerate();
  888. }
  889. /*!
  890. \virtual
  891. Used by login handler to clean up session variables
  892. */
  893. function sessionCleanup()
  894. {
  895. }
  896. /**
  897. * Cleanup user related session values, for use by login / logout code
  898. *
  899. * @internal
  900. */
  901. static function cleanup()
  902. {
  903. $http = eZHTTPTool::instance();
  904. $http->removeSessionVariable( 'CanInstantiateClassList' );
  905. $http->removeSessionVariable( 'ClassesCachedForUser' );
  906. // Note: This must be done more generic with an internal
  907. // callback system.
  908. eZPreferences::sessionCleanup();
  909. }
  910. /*!
  911. \return logs in the current user object
  912. */
  913. function loginCurrent()
  914. {
  915. self::setCurrentlyLoggedInUser( $this, $this->ContentObjectID );
  916. }
  917. /*!
  918. \static
  919. Logs out the current user
  920. */
  921. static function logoutCurrent()
  922. {
  923. $http = eZHTTPTool::instance();
  924. $id = false;
  925. $GLOBALS["eZUserGlobalInstance_$id"] = false;
  926. $contentObjectID = $http->sessionVariable( 'eZUserLoggedInID' );
  927. // reset session data
  928. $newUserID = self::anonymousId();
  929. eZSession::setUserID( $newUserID );
  930. $http->setSessionVariable( 'eZUserLoggedInID', $newUserID );
  931. // Clear current basket if necessary
  932. $db = eZDB::instance();
  933. $db->begin();
  934. eZBasket::cleanupCurrentBasket();
  935. $db->commit();
  936. if ( $contentObjectID )
  937. self::cleanup();
  938. // give user new session id
  939. eZSession::regenerate();
  940. // set the property used to prevent SSO from running again
  941. self::$userHasLoggedOut = true;
  942. }
  943. /**
  944. * Returns a shared instance of the eZUser class pr $id value.
  945. * If user can not be fetched, then anonymous user is returned and
  946. * a warning trown, if anonymous user can not be fetched, then NoUser
  947. * is returned and another warning is thrown.
  948. *
  949. * @param int|false $id On false: Gets current user id from session
  950. * or from {@link eZUser::anonymousId()} if not set.
  951. * @return eZUser
  952. */
  953. static function instance( $id = false )
  954. {
  955. if ( !empty( $GLOBALS["eZUserGlobalInstance_$id"] ) )
  956. {
  957. return $GLOBALS["eZUserGlobalInstance_$id"];
  958. }
  959. $userId = $id;
  960. $currentUser = null;
  961. $http = eZHTTPTool::instance();
  962. $anonymousUserID = self::anonymousId();
  963. $sessionHasStarted = eZSession::hasStarted();
  964. // If not specified get the current user
  965. if ( $userId === false )
  966. {
  967. if ( $sessionHasStarted )
  968. {
  969. $userId = $http->sessionVariable( 'eZUserLoggedInID' );
  970. if ( !is_numeric( $userId ) )
  971. {
  972. $userId = $anonymousUserID;
  973. eZSession::setUserID( $userId );
  974. $http->setSessionVariable( 'eZUserLoggedInID', $userId );
  975. }
  976. }
  977. else
  978. {
  979. $userId = $anonymousUserID;
  980. eZSession::setUserID( $userId );
  981. }
  982. }
  983. // Check user cache (this effectivly fetches user from cache)
  984. // user not found if !isset( isset( $userCache['info'][$userId] ) )
  985. $userCache = self::getUserCacheByUserId( $userId );
  986. if ( isset( $userCache['info'][$userId] ) )
  987. {
  988. $userArray = $userCache['info'][$userId];
  989. if ( is_numeric( $userArray['contentobject_id'] ) )
  990. {
  991. $currentUser = new eZUser( $userArray );
  992. $currentUser->setUserCache( $userCache );
  993. }
  994. }
  995. $ini = eZINI::instance();
  996. // Check if:
  997. // - the user has not logged out,
  998. // - the user is not logged in,
  999. // - and if a automatic single sign on plugin is enabled.
  1000. if ( !self::$userHasLoggedOut and is_object( $currentUser ) and !$currentUser->isLoggedIn() )
  1001. {
  1002. $ssoHandlerArray = $ini->variable( 'UserSettings', 'SingleSignOnHandlerArray' );
  1003. if ( !empty( $ssoHandlerArray ) )
  1004. {
  1005. $ssoUser = false;
  1006. foreach ( $ssoHandlerArray as $ssoHandler )
  1007. {
  1008. // Load handler
  1009. $handlerFile = 'kernel/classes/ssohandlers/ez' . strtolower( $ssoHandler ) . 'ssohandler.php';
  1010. if ( file_exists( $handlerFile ) )
  1011. {
  1012. include_once( $handlerFile );
  1013. $className = 'eZ' . $ssoHandler . 'SSOHandler';
  1014. $impl = new $className();
  1015. $ssoUser = $impl->handleSSOLogin();
  1016. }
  1017. else // check in extensions
  1018. {
  1019. $extensionDirectories = $ini->variable( 'UserSettings', 'ExtensionDirectory' );
  1020. $directoryList = eZExtension::expandedPathList( $extensionDirectories, 'sso_handler' );
  1021. foreach( $directoryList as $directory )
  1022. {
  1023. $handlerFile = $directory . '/ez' . strtolower( $ssoHandler ) . 'ssohandler.php';
  1024. if ( file_exists( $handlerFile ) )
  1025. {
  1026. include_once( $handlerFile );
  1027. $className = 'eZ' . $ssoHandler . 'SSOHandler';
  1028. $impl = new $className();
  1029. $ssoUser = $impl->handleSSOLogin();
  1030. }
  1031. }
  1032. }
  1033. }
  1034. // If a user was found via SSO, then use it
  1035. if ( $ssoUser !== false )
  1036. {
  1037. $currentUser = $ssoUser;
  1038. $userId = $currentUser->attribute( 'contentobject_id' );
  1039. $userInfo = array();
  1040. $userInfo[$userId] = array( 'contentobject_id' => $userId,
  1041. 'login' => $currentUser->attribute( 'login' ),
  1042. 'email' => $currentUser->attribute( 'email' ),
  1043. 'password_hash' => $currentUser->attribute( 'password_hash' ),
  1044. 'password_hash_type' => $currentUser->attribute( 'password_hash_type' )
  1045. );
  1046. eZSession::setUserID( $userId );
  1047. $http->setSessionVariable( 'eZUserLoggedInID', $userId );
  1048. eZUser::updateLastVisit( $userId );
  1049. eZUser::setCurrentlyLoggedInUser( $currentUser, $userId );
  1050. eZHTTPTool::redirect( eZSys::wwwDir() . eZSys::indexFile( false ) . eZSys::requestURI(), array(), 302 );
  1051. eZExecution::cleanExit();
  1052. }
  1053. }
  1054. }
  1055. if ( $userId <> $anonymousUserID )
  1056. {
  1057. $sessionInactivityTimeout = $ini->variable( 'Session', 'ActivityTimeout' );
  1058. if ( !isset( $GLOBALS['eZSessionIdleTime'] ) )
  1059. {
  1060. eZUser::updateLastVisit( $userId );
  1061. }
  1062. else
  1063. {
  1064. $sessionIdle = $GLOBALS['eZSessionIdleTime'];
  1065. if ( $sessionIdle > $sessionInactivityTimeout )
  1066. {
  1067. eZUser::updateLastVisit( $userId );
  1068. }
  1069. }
  1070. }
  1071. if ( !$currentUser )
  1072. {
  1073. $currentUser = eZUser::fetch( self::anonymousId() );
  1074. eZDebug::writeWarning( 'User not found, returning anonymous' );
  1075. }
  1076. if ( !$currentUser )
  1077. {
  1078. $currentUser = new eZUser( array( 'id' => -1, 'login' => 'NoUser' ) );
  1079. eZDebug::writeWarning( 'Anonymous user not found, returning NoUser' );
  1080. }
  1081. $GLOBALS["eZUserGlobalInstance_$id"] = $currentUser;
  1082. return $currentUser;
  1083. }
  1084. /**
  1085. * Get User cache from cache file
  1086. *
  1087. * @since 4.4
  1088. * @return array( 'info' => array, 'groups' => array, 'roles' => array, 'role_limitations' => array, 'access_array' => array)
  1089. */
  1090. public function getUserCache()
  1091. {
  1092. if ( $this->UserCache === null )
  1093. {
  1094. $this->setUserCache( self::getUserCacheByUserId( $this->ContentObjectID ) );
  1095. }
  1096. return $this->UserCache;
  1097. }
  1098. /**
  1099. * Delete User cache from locale var and cache file for current user.
  1100. *
  1101. * @since 4.4
  1102. */
  1103. public function purgeUserCache()
  1104. {
  1105. $this->UserCache = null;
  1106. self::purgeUserCacheByUserId( $this->ContentObjectID );
  1107. }
  1108. /**
  1109. * Set User cache from cache file
  1110. * Needs to be in excact same format as {@link eZUser::getUserCache()}!
  1111. *
  1112. * @since 4.4
  1113. * @param array $userCache
  1114. */
  1115. public function setUserCache( array $userCache )
  1116. {
  1117. $this->UserCache = $userCache;
  1118. }
  1119. /**
  1120. * Delete User cache from cache file for Anonymous user(usefull for sessionless users)
  1121. *
  1122. * @since 4.4
  1123. * @see eZUser::purgeUserCacheByUserId()
  1124. */
  1125. static public function purgeUserCacheByAnonymousId()
  1126. {
  1127. self::purgeUserCacheByUserId( self::anonymousId() );
  1128. }
  1129. /**
  1130. * Delete User cache pr user
  1131. *
  1132. * @since 4.4
  1133. * @param int $userId
  1134. */
  1135. static public function purgeUserCacheByUserId( $userId )
  1136. {
  1137. $cacheFilePath = eZUser::getCacheDir( $userId ). "/user-data-{$userId}.cache.php" ;
  1138. eZClusterFileHandler::instance()->fileDelete( $cacheFilePath );
  1139. }
  1140. /**
  1141. * Get User cache from cache file for Anonymous user(usefull for sessionless users)
  1142. *
  1143. * @since 4.4
  1144. * @see eZUser::getUserCacheByUserId()
  1145. * @return array
  1146. */
  1147. static public function getUserCacheByAnonymousId()
  1148. {
  1149. return self::getUserCacheByUserId( self::anonymousId() );
  1150. }
  1151. /**
  1152. * Get User cache from cache file (usefull for sessionless users)
  1153. *
  1154. * @since 4.4
  1155. * @see eZUser::getUserCache()
  1156. * @param int $userId
  1157. * @return array
  1158. */
  1159. static protected function getUserCacheByUserId( $userId )
  1160. {
  1161. $cacheFilePath = eZUser::getCacheDir( $userId ). "/user-data-{$userId}.cache.php" ;
  1162. $cacheFile = eZClusterFileHandler::instance( $cacheFilePath );
  1163. return $cacheFile->processCache( array( 'eZUser', 'retrieveUserCacheFromFile' ),
  1164. array( 'eZUser', 'generateUserCacheForFile' ),
  1165. null,
  1166. self::userInfoExpiry(),
  1167. $userId );
  1168. }
  1169. /**
  1170. * Callback which fetches user cache from local file.
  1171. *
  1172. * @internal
  1173. * @since 4.4
  1174. * @see eZUser::getUserCacheByUserId()
  1175. */
  1176. static function retrieveUserCacheFromFile( $filePath, $mtime, $userId )
  1177. {
  1178. return include( $filePath );
  1179. }
  1180. /**
  1181. * Callback which generates user cache for user
  1182. *
  1183. * @internal
  1184. * @since 4.4
  1185. * @see eZUser::getUserCacheByUserId()
  1186. */
  1187. static function generateUserCacheForFile( $filePath, $userId )
  1188. {
  1189. $data = array( 'info' => array(),
  1190. 'groups' => array(),
  1191. 'roles' => array(),
  1192. 'role_limitations' => array(),
  1193. 'access_array' => array(),
  1194. 'discount_rules' => array() );
  1195. $user = eZUser::fetch( $userId );
  1196. if ( $user instanceOf eZUser )
  1197. {
  1198. // user info (session: eZUserInfoCache)
  1199. $data['info'][$userId] = array( 'contentobject_id' => $user->attribute( 'contentobject_id' ),
  1200. 'login' => $user->attribute( 'login' ),
  1201. 'email' => $user->attribute( 'email' ),
  1202. 'password_hash' => $user->attribute( 'password_hash' ),
  1203. 'password_hash_type' => $user->attribute( 'password_hash_type' ) );
  1204. // user groups list (session: eZUserGroupsCache)
  1205. $groups = $user->generateGroupIdList();
  1206. $data['groups'] = $groups;
  1207. // role list (session: eZRoleIDList)
  1208. $groups[] = $userId;
  1209. $data['roles'] = eZRole::fetchIDListByUser( $groups );
  1210. // role limitation list (session: eZRoleLimitationValueList)
  1211. $limitList = $user->limitList( false );
  1212. foreach ( $limitList as $limit )
  1213. {
  1214. $data['role_limitations'][] = $limit['limit_value'];
  1215. }
  1216. // access array (session: AccessArray)
  1217. $data['access_array'] = $user->generateAccessArray();
  1218. // discount rules (session: eZUserDiscountRules<userId>)
  1219. $data['discount_rules'] = eZUserDiscountRule::generateIDListByUserID( $userId );
  1220. }
  1221. return array( 'content' => $data,
  1222. 'scope' => 'user-info-cache',
  1223. 'datatype' => 'php',
  1224. 'store' => true );
  1225. }
  1226. /*!
  1227. Updates the user's last visit timestamp
  1228. Optionally updates user login count by setting $updateLoginCount to true
  1229. */
  1230. static function updateLastVisit( $userID, $updateLoginCount = false )
  1231. {
  1232. if ( isset( $GLOBALS['eZUserUpdatedLastVisit'] ) )
  1233. return;
  1234. $db = eZDB::instance();
  1235. $userID = (int) $userID;
  1236. $userVisitArray = $db->arrayQuery( "SELECT 1 FROM ezuservisit WHERE user_id=$userID" );
  1237. $time = time();
  1238. if ( isset( $userVisitArray[0] ) )
  1239. {
  1240. $loginCountSQL = $updateLoginCount ? ', login_count=login_count+1' : '';
  1241. $db->query( "UPDATE ezuservisit SET last_visit_timestamp=current_visit_timestamp, current_visit_timestamp=$time$loginCountSQL WHERE user_id=$userID" );
  1242. }
  1243. else
  1244. {
  1245. $intialLoginCount = $updateLoginCount ? 1 : 0;
  1246. $db->query( "INSERT INTO ezuservisit ( current_visit_timestamp, last_visit_timestamp, user_id, login_count ) VALUES ( $time, $time, $userID, $intialLoginCount )" );
  1247. }
  1248. $GLOBALS['eZUserUpdatedLastVisit'] = true;
  1249. }
  1250. /*!
  1251. Returns the last visit timestamp to the current user.
  1252. */
  1253. function lastVisit()
  1254. {
  1255. $db = eZDB::instance();
  1256. $userVisitArray = $db->arrayQuery( "SELECT last_visit_timestamp FROM ezuservisit WHERE user_id=$this->ContentObjectID" );
  1257. if ( isset( $userVisitArray[0] ) )
  1258. {
  1259. return $userVisitArray[0]['last_visit_timestamp'];
  1260. }
  1261. else
  1262. {
  1263. return 0;
  1264. }
  1265. }
  1266. /**
  1267. * Returns the login count for the current user.
  1268. *
  1269. * @since Version 4.1
  1270. * @return int Login count for current user.
  1271. */
  1272. function loginCount()
  1273. {
  1274. $db = eZDB::instance();
  1275. $userVisitArray = $db->arrayQuery( "SELECT login_count FROM ezuservisit WHERE user_id=$this->ContentObjectID" );
  1276. if ( isset( $userVisitArray[0] ) )
  1277. {
  1278. return $userVisitArray[0]['login_count'];
  1279. }
  1280. else
  1281. {
  1282. return 0;
  1283. }
  1284. }
  1285. /*!
  1286. If \a $value is false will increase the user's number of failed login attempts
  1287. otherwise failed_login_attempts will be updated by $value.
  1288. \a $setByForce if true checking for trusting or max number of failed login attempts will be ignored.
  1289. */
  1290. static function setFailedLoginAttempts( $userID, $value = false, $setByForce = false )
  1291. {
  1292. $trustedUser = eZUser::isTrusted();
  1293. // If user is trusted we should stop processing
  1294. if ( $trustedUser and !$setByForce )
  1295. return true;
  1296. $maxNumberOfFailedLogin = eZUser::maxNumberOfFailedLogin();
  1297. if ( $maxNumberOfFailedLogin == '0' and !$setByForce )
  1298. return true;
  1299. $userID = (int) $userID;
  1300. $userObject = eZUser::fetch( $userID );
  1301. if ( !$userObject )
  1302. return true;
  1303. $isEnabled = $userObject->isEnabled();
  1304. // If current user is disabled we should not continue
  1305. if ( !$isEnabled and !$setByForce )
  1306. return true;
  1307. $db = eZDB::instance();
  1308. $db->begin();
  1309. $userVisitArray = $db->arrayQuery( "SELECT 1 FROM ezuservisit WHERE user_id=$userID" );
  1310. if ( isset( $userVisitArray[0] ) )
  1311. {
  1312. if ( $value === false )
  1313. {
  1314. $failedLoginAttempts = $userObject->failedLoginAttempts();
  1315. $failedLoginAttempts += 1;
  1316. }
  1317. else
  1318. $failedLoginAttempts = (int) $value;
  1319. $db->query( "UPDATE ezuservisit SET failed_login_attempts=$failedLoginAttempts WHERE user_id=$userID" );
  1320. }
  1321. else
  1322. {
  1323. if ( $value === false )
  1324. {
  1325. $failedLoginAttempts = 1;
  1326. }
  1327. else
  1328. $failedLoginAttempts = (int) $value;
  1329. $db->query( "INSERT INTO ezuservisit ( failed_login_attempts, user_id ) VALUES ( $failedLoginAttempts, $userID )" );
  1330. }
  1331. $db->commit();
  1332. eZContentCacheManager::clearContentCacheIfNeeded( $userID );
  1333. eZContentCacheManager::generateObjectViewCache( $userID );
  1334. }
  1335. /*!
  1336. Returns the current user's number of failed login attempts.
  1337. */
  1338. function failedLoginAttempts()
  1339. {
  1340. return eZUser::failedLoginAttemptsByUserID( $this->attribute( 'contentobject_id' ) );
  1341. }
  1342. /*!
  1343. Returns the current user's number of failed login attempts.
  1344. */
  1345. static function failedLoginAttemptsByUserID( $userID )
  1346. {
  1347. $db = eZDB::instance();
  1348. $contentObjectID = (int) $userID;
  1349. $userVisitArray = $db->arrayQuery( "SELECT failed_login_attempts FROM ezuservisit WHERE user_id=$contentObjectID" );
  1350. $failedLoginAttempts = isset( $userVisitArray[0] ) ? $userVisitArray[0]['failed_login_attempts'] : 0;
  1351. return $failedLoginAttempts;
  1352. }
  1353. /*!
  1354. \return \c true if the user is locked (is enabled after failed login) and can be logged on the site.
  1355. */
  1356. function isLocked()
  1357. {
  1358. $userID = $this->attribute( 'contentobject_id' );
  1359. $isNotLocked = eZUser::isEnabledAfterFailedLogin( $userID, true );
  1360. return !$isNotLocked;
  1361. }
  1362. /*!
  1363. \return \c true if the user is enabled and can be used on the site.
  1364. */
  1365. function isEnabled()
  1366. {
  1367. if ( $this == eZUser::currentUser() )
  1368. {
  1369. return true;
  1370. }
  1371. $setting = eZUserSetting::fetch( $this->attribute( 'contentobject_id' ) );
  1372. if ( $setting and !$setting->attribute( 'is_enabled' ) )
  1373. {
  1374. return false;
  1375. }
  1376. return true;
  1377. }
  1378. /*!
  1379. \return \c true if the user is the anonymous user.
  1380. */
  1381. function isAnonymous()
  1382. {
  1383. if ( $this->attribute( 'contentobject_id' ) != self::anonymousId() )
  1384. {
  1385. return false;
  1386. }
  1387. return true;
  1388. }
  1389. /*!
  1390. \static
  1391. Returns the currently logged in user.
  1392. */
  1393. static function currentUser()
  1394. {
  1395. $user = eZUser::instance();
  1396. return $user;
  1397. }
  1398. /*!
  1399. \static
  1400. Returns the ID of the currently logged in user.
  1401. */
  1402. static function currentUserID()
  1403. {
  1404. $user = eZUser::instance();
  1405. if ( !$user )
  1406. return 0;
  1407. return $user->attribute( 'contentobject_id' );
  1408. }
  1409. /*!
  1410. \static
  1411. Creates a hash out of \a $user, \a $password and \a $site according to the type \a $type.
  1412. \return true if the generated hash is equal to the supplied hash \a $hash.
  1413. */
  1414. static function authenticateHash( $user, $password, $site, $type, $hash )
  1415. {
  1416. return eZUser::createHash( $user, $password, $site, $type, $hash ) === (string) $hash;
  1417. }
  1418. /*!
  1419. \static
  1420. \return an array with characters which are allowed in password.
  1421. */
  1422. static function passwordCharacterTable()
  1423. {
  1424. if ( !empty( $GLOBALS['eZUserPasswordCharacterTable'] ) )
  1425. {
  1426. return $GLOBALS['eZUserPasswordCharacterTable'];
  1427. }
  1428. $table = array_merge( range( 'a', 'z' ), range( 'A', 'Z' ), range( 0, 9 ) );
  1429. $ini = eZINI::instance();
  1430. if ( $ini->variable( 'UserSettings', 'UseSpecialCharacters' ) == 'true' )
  1431. {
  1432. $specialCharacters = '!#%&{[]}+?;:*';
  1433. $table = array_merge( $table, preg_split( '//', $specialCharacters, -1, PREG_SPLIT_NO_EMPTY ) );
  1434. }
  1435. // Remove some characters that are too similar visually
  1436. $table = array_diff( $table, array( 'I', 'l', 'o', 'O', '0' ) );
  1437. $tableTmp = $table;
  1438. $table = array();
  1439. foreach ( $tableTmp as $item )
  1440. {
  1441. $table[] = $item;
  1442. }
  1443. return $GLOBALS['eZUserPasswordCharacterTable'] = $table;
  1444. }
  1445. /*!
  1446. Checks if the supplied content object is a user object ( contains ezuser datatype )
  1447. \param ContentObject
  1448. \return true or false
  1449. */
  1450. static function isUserObject( $contentObject )
  1451. {
  1452. if ( !$contentObject )
  1453. {
  1454. return false;
  1455. }
  1456. eZDataType::loadAndRegisterType( 'ezuser' );
  1457. $contentClass = $contentObject->attribute( 'content_class' );
  1458. $classAttributeList = $contentClass->fetchAttributes();
  1459. foreach( $classAttributeList as $classAttribute )
  1460. {
  1461. if ( $classAttribute->attribute( 'data_type_string' ) == eZUserType::DATA_TYPE_STRING )
  1462. return true;
  1463. }
  1464. return false;
  1465. }
  1466. /*!
  1467. \static
  1468. Creates a password with number of characters equal to \a $passwordLength and returns it.
  1469. If you want pass a value in \a $seed it will be used as basis for the password, if not
  1470. it will use the current time value as seed.
  1471. \note If \a $passwordLength exceeds 16 it will need to generate new seed for the remaining
  1472. characters.
  1473. */
  1474. static function createPassword( $passwordLength, $seed = false )
  1475. {
  1476. $chars = 0;
  1477. $password = '';
  1478. if ( $passwordLength < 1 )
  1479. $passwordLength = 1;
  1480. $decimal = 0;
  1481. while ( $chars < $passwordLength )
  1482. {
  1483. if ( $seed == false )
  1484. $seed = time() . ":" . mt_rand();
  1485. $text = md5( $seed );
  1486. $characterTable = eZUser::passwordCharacterTable();
  1487. $tableCount = count( $characterTable );
  1488. for ( $i = 0; ( $chars < $passwordLength ) and $i < 32; ++$chars, $i += 2 )
  1489. {
  1490. $decimal += hexdec( substr( $text, $i, 2 ) );
  1491. $index = ( $decimal % $tableCount );
  1492. $character = $characterTable[$index];
  1493. $password .= $character;
  1494. }
  1495. $seed = false;
  1496. }
  1497. return $password;
  1498. }
  1499. /*!
  1500. \static
  1501. Will create a hash of the given string. This is used to store the passwords in the database.
  1502. */
  1503. static function createHash( $user, $password, $site, $type, $hash = false )
  1504. {
  1505. $str = '';
  1506. if( $type == self::PASSWORD_HASH_MD5_USER )
  1507. {
  1508. $str = md5( "$user\n$password" );
  1509. }
  1510. else if ( $type == self::PASSWORD_HASH_MD5_SITE )
  1511. {
  1512. $str = md5( "$user\n$password\n$site" );
  1513. }
  1514. else if ( $type == self::PASSWORD_HASH_MYSQL )
  1515. {
  1516. $db = eZDB::instance();
  1517. $hash = $db->escapeString( $password );
  1518. $str = $db->arrayQuery( "SELECT PASSWORD( '$hash' )" );
  1519. $hashes = array_values( $str[0] );
  1520. $str = $hashes[0];
  1521. }
  1522. else if ( $type == self::PASSWORD_HASH_PLAINTEXT )
  1523. {
  1524. $str = $password;
  1525. }
  1526. else if ( $type == self::PASSWORD_HASH_CRYPT )
  1527. {
  1528. if ( $hash )
  1529. {
  1530. $str = crypt( $password, $hash );
  1531. }
  1532. else
  1533. {
  1534. $str = crypt( $password );
  1535. }
  1536. }
  1537. else // self::PASSWORD_HASH_MD5_PASSWORD
  1538. {
  1539. $str = md5( $password );
  1540. }
  1541. eZDebugSetting::writeDebug( 'kernel-user', $str, "ezuser($type)" );
  1542. return $str;
  1543. }
  1544. /*!
  1545. Check if user has got access to the specified module and function
  1546. \param module name
  1547. \param funtion name
  1548. \return Array containg result.
  1549. Array elements : 'accessWord', yes - access allowed
  1550. no - access denied
  1551. limited - access array describing access included
  1552. 'policies', array containing the policy limitations
  1553. 'accessList', array describing missing access rights
  1554. */
  1555. function hasAccessTo( $module, $function = false )
  1556. {
  1557. $accessArray = $this->accessArray();
  1558. $functionArray = array();
  1559. if ( isset( $accessArray['*']['*'] ) )
  1560. {
  1561. $functionArray = $accessArray['*']['*'];
  1562. }
  1563. if ( isset( $accessArray[$module] ) )
  1564. {
  1565. if ( isset( $accessArray[$module]['*'] ) )
  1566. {
  1567. $functionArray = array_merge_recursive( $functionArray, $accessArray[$module]['*'] );
  1568. }
  1569. if ( $function and isset( $accessArray[$module][$function] ) and $function != '*' )
  1570. {
  1571. $functionArray = array_merge_recursive( $functionArray, $accessArray[$module][$function] );
  1572. }
  1573. }
  1574. if ( !$functionArray )
  1575. {
  1576. return array( 'accessWord' => 'no',
  1577. 'accessList' => array( 'FunctionRequired' => array ( 'Module' => $module,
  1578. 'Function' => $function,
  1579. 'ClassID' => '',
  1580. 'MainNodeID' => '' ),
  1581. 'PolicyList' => array() )
  1582. );
  1583. }
  1584. if ( isset( $functionArray['*'] ) &&
  1585. ( $functionArray['*'] == '*' || in_array( '*', $functionArray['*'] ) ) )
  1586. {
  1587. return array( 'accessWord' => 'yes' );
  1588. }
  1589. return array( 'accessWord' => 'limited', 'policies' => $functionArray );
  1590. }
  1591. /**
  1592. * Returns either cached or newly generated accessArray for the user depending on
  1593. * site.ini\[RoleSettings]\EnableCaching setting
  1594. *
  1595. * @access private
  1596. * @return array
  1597. */
  1598. function accessArray()
  1599. {
  1600. if ( !isset( $this->AccessArray ) )
  1601. {
  1602. if ( eZINI::instance()->variable( 'RoleSettings', 'EnableCaching' ) === 'true' )
  1603. {
  1604. $userCache = $this->getUserCache();
  1605. $this->AccessArray = $userCache['access_array'];
  1606. }
  1607. else
  1608. {
  1609. // if role caching is disabled then generate access array on-the-fly.
  1610. $this->AccessArray = $this->generateAccessArray();
  1611. }
  1612. }
  1613. return $this->AccessArray;
  1614. }
  1615. /**
  1616. * Generates the accessArray for the user (for $this).
  1617. * This function is uncached, and used as basis for user cache callback.
  1618. *
  1619. * @internal
  1620. * @return array
  1621. */
  1622. function generateAccessArray()
  1623. {
  1624. $idList = $this->generateGroupIdList();
  1625. $idList[] = $this->attribute( 'contentobject_id' );
  1626. return eZRole::accessArrayByUserID( $idList );
  1627. }
  1628. /*!
  1629. \private
  1630. \static
  1631. Callback which figures out global expiry and returns it.
  1632. */
  1633. static protected function userInfoExpiry()
  1634. {
  1635. /* Figure out when the last update was done */
  1636. eZExpiryHandler::registerShutdownFunction();
  1637. $handler = eZExpiryHandler::instance();
  1638. if ( $handler->hasTimestamp( 'user-info-cache' ) )
  1639. {
  1640. $expiredTimestamp = $handler->timestamp( 'user-info-cache' );
  1641. }
  1642. else
  1643. {
  1644. $expiredTimestamp = time();
  1645. $handler->setTimestamp( 'user-info-cache', $expiredTimestamp );
  1646. }
  1647. return $expiredTimestamp;
  1648. }
  1649. /*
  1650. Returns list of sections which are allowed to assign to the given content object by the user.
  1651. */
  1652. function canAssignToObjectSectionList( $contentObject )
  1653. {
  1654. $access = $this->hasAccessTo( 'section', 'assign' );
  1655. if ( $access['accessWord'] == 'yes' )
  1656. {
  1657. return array( '*' );
  1658. }
  1659. else if ( $access['accessWord'] == 'limited' )
  1660. {
  1661. $userID = $this->attribute( 'contentobject_id' );
  1662. $classID = $contentObject->attribute( 'contentclass_id' );
  1663. $ownerID = $contentObject->attribute( 'owner_id' );
  1664. $sectionID = $contentObject->attribute( 'section_id' );
  1665. $allowedSectionIDList = array();
  1666. foreach ( $access['policies'] as $policy )
  1667. {
  1668. if ( ( isset( $policy['Class'] ) and !in_array( $classID, $policy['Class'] ) ) or
  1669. ( isset( $policy['Owner'] ) and in_array( 1, $policy['Owner'] ) and $userID != $ownerID ) or
  1670. ( isset( $policy['Section'] ) and !in_array( $sectionID, $policy['Section'] ) ) )
  1671. {
  1672. continue;
  1673. }
  1674. if ( isset( $policy['NewSection'] ) && !empty( $policy['NewSection'] ) )
  1675. {
  1676. $allowedSectionIDList = array_merge( $allowedSectionIDList, $policy['NewSection'] );
  1677. }
  1678. else
  1679. {
  1680. return array( '*' );
  1681. }
  1682. }
  1683. $allowedSectionIDList = array_unique( $allowedSectionIDList );
  1684. return $allowedSectionIDList;
  1685. }
  1686. return array();
  1687. }
  1688. /*
  1689. Checks whether user can assign the section to the given content object or not.
  1690. */
  1691. function canAssignSectionToObject( $checkSectionID, $contentObject )
  1692. {
  1693. $access = $this->hasAccessTo( 'section', 'assign' );
  1694. if ( $access['accessWord'] == 'yes' )
  1695. {
  1696. return true;
  1697. }
  1698. else if ( $access['accessWord'] == 'limited' )
  1699. {
  1700. $hasSubtreeLimitation = false;
  1701. $subtreeLimitationResult = false;
  1702. // Trying first to discover if a subtree limitation exists.
  1703. // Only the main node of the object is considered!
  1704. foreach ( $access['policies'] as $policy )
  1705. {
  1706. if ( isset( $policy['User_Subtree'] ) )
  1707. {
  1708. $hasSubtreeLimitation = true;
  1709. $nodePath = $contentObject->mainNode()->PathString;
  1710. foreach ( $policy['User_Subtree'] as $path )
  1711. {
  1712. if ( strpos( $nodePath, $path ) !== false )
  1713. {
  1714. $subtreeLimitationResult = true;
  1715. }
  1716. }
  1717. continue;
  1718. }
  1719. }
  1720. // If a subtree limitation exists and none of the path corresponds then the user have not enough rights.
  1721. if ( $hasSubtreeLimitation && !$subtreeLimitationResult )
  1722. {
  1723. return false;
  1724. }
  1725. unset( $hasSubtreeLimitation, $subtreeLimitationResult, $nodePath );
  1726. $userID = $this->attribute( 'contentobject_id' );
  1727. $classID = $contentObject->attribute( 'contentclass_id' );
  1728. $ownerID = $contentObject->attribute( 'owner_id' );
  1729. $sectionID = $contentObject->attribute( 'section_id' );
  1730. foreach ( $access['policies'] as $policy )
  1731. {
  1732. if ( ( isset( $policy['Class'] ) and !in_array( $classID, $policy['Class'] ) ) or
  1733. ( isset( $policy['Owner'] ) and in_array( 1, $policy['Owner'] ) and $userID != $ownerID ) or
  1734. ( isset( $policy['Section'] ) and !in_array( $sectionID, $policy['Section'] ) ) )
  1735. {
  1736. continue;
  1737. }
  1738. if ( isset( $policy['NewSection'] ) )
  1739. {
  1740. if ( is_array( $policy['NewSection'] ) and in_array( $checkSectionID, $policy['NewSection'] ) )
  1741. {
  1742. return true;
  1743. }
  1744. }
  1745. else
  1746. {
  1747. return true;
  1748. }
  1749. }
  1750. }
  1751. return false;
  1752. }
  1753. /*
  1754. Checks whether user has privileges to assign the section or not at all.
  1755. */
  1756. function canAssignSection( $checkSectionID )
  1757. {
  1758. $access = $this->hasAccessTo( 'section', 'assign' );
  1759. if ( $access['accessWord'] == 'yes' )
  1760. {
  1761. return true;
  1762. }
  1763. else if ( $access['accessWord'] == 'limited' )
  1764. {
  1765. foreach ( $access['policies'] as $policy )
  1766. {
  1767. if ( isset( $policy['NewSection'] ) )
  1768. {
  1769. if ( in_array( $checkSectionID, $policy['NewSection'] ) )
  1770. {
  1771. return true;
  1772. }
  1773. }
  1774. else
  1775. {
  1776. return true;
  1777. }
  1778. }
  1779. }
  1780. return false;
  1781. }
  1782. /*
  1783. Returns list of sections allowed to assign for the user.
  1784. */
  1785. function canAssignSectionList()
  1786. {
  1787. $access = $this->hasAccessTo( 'section', 'assign' );
  1788. if ( $access['accessWord'] == 'yes' )
  1789. {
  1790. return array( '*' );
  1791. }
  1792. else if ( $access['accessWord'] == 'limited' )
  1793. {
  1794. $allowedSectionIDList = array();
  1795. foreach ( $access['policies'] as $policy )
  1796. {
  1797. if ( isset( $policy['NewSection'] ) )
  1798. {
  1799. if ( is_array( $policy['NewSection'] ) && !empty( $policy['NewSection'] ) )
  1800. {
  1801. $allowedSectionIDList = array_merge( $allowedSectionIDList, $policy['NewSection'] );
  1802. }
  1803. }
  1804. else
  1805. {
  1806. return array( '*' );
  1807. }
  1808. }
  1809. $allowedSectionIDList = array_unique( $allowedSectionIDList );
  1810. return $allowedSectionIDList;
  1811. }
  1812. return array();
  1813. }
  1814. /*
  1815. Returns list of classes allowed to assign to the given section for the user.
  1816. */
  1817. function canAssignSectionToClassList( $checkSectionID )
  1818. {
  1819. $access = $this->hasAccessTo( 'section', 'assign' );
  1820. if ( $access['accessWord'] == 'yes' )
  1821. {
  1822. return array( '*' );
  1823. }
  1824. else if ( $access['accessWord'] == 'limited' )
  1825. {
  1826. $allowedClassList = array();
  1827. foreach ( $access['policies'] as $policy )
  1828. {
  1829. if ( !isset( $policy['NewSection'] ) or in_array( $checkSectionID, $policy['NewSection'] ) )
  1830. {
  1831. if ( isset( $policy['Class'] ) )
  1832. {
  1833. $allowedClassList = array_merge( $allowedClassList, $policy['Class'] );
  1834. }
  1835. else
  1836. {
  1837. return array( '*' );
  1838. }
  1839. }
  1840. }
  1841. if ( !empty( $allowedClassList ) )
  1842. {
  1843. // Now we are trying to fetch classes by collected ids list to return
  1844. // class list consisting of existing classes's identifiers only.
  1845. $allowedClassList = array_unique( $allowedClassList );
  1846. $classList = eZContentClass::fetchList( eZContentClass::VERSION_STATUS_DEFINED, false, false, null, null, $allowedClassList );
  1847. if ( is_array( $classList ) && !empty( $classList ) )
  1848. {
  1849. $classIdentifierList = array();
  1850. foreach( $classList as $class )
  1851. {
  1852. $classIdentifierList[] = $class['identifier'];
  1853. }
  1854. return $classIdentifierList;
  1855. }
  1856. }
  1857. }
  1858. return array();
  1859. }
  1860. /*
  1861. Evaluates if $this user has access to the view $viewName based on its policy functions and
  1862. checks if the assigned to the view functions expression is valid and handles errors if it is not.
  1863. Returns true if access is allowed, false if access is denied.
  1864. */
  1865. function hasAccessToView( $module, $viewName, &$params )
  1866. {
  1867. $validView = false;
  1868. $accessAllowed = false;
  1869. if ( $module->singleFunction() )
  1870. {
  1871. $info = $module->attribute( 'info' );
  1872. if ( isset( $info['function'] ) )
  1873. {
  1874. $validView = true;
  1875. if ( isset( $info['function']['functions'] ) && !empty( $info['function']['functions'] ) )
  1876. {
  1877. $functions = $info['function']['functions'];
  1878. }
  1879. }
  1880. }
  1881. else
  1882. {
  1883. $views = $module->attribute( 'views' );
  1884. if ( isset( $views[$viewName] ) )
  1885. {
  1886. $view = $views[$viewName];
  1887. $validView = true;
  1888. if ( isset( $view['functions'] ) && !empty( $view['functions'] ) )
  1889. {
  1890. $functions = $view['functions'];
  1891. }
  1892. }
  1893. }
  1894. if ( $validView )
  1895. {
  1896. if ( isset( $functions ) )
  1897. {
  1898. if ( is_array( $functions ) )
  1899. {
  1900. $funcExpression = false;
  1901. $accessAllowed = true;
  1902. foreach ( $functions as $function )
  1903. {
  1904. if ( empty( $function ) )
  1905. {
  1906. $funcExpression = false;
  1907. $accessAllowed = false;
  1908. break;
  1909. }
  1910. else if ( is_string( $function ) )
  1911. {
  1912. if ( $funcExpression )
  1913. {
  1914. $funcExpression .= ' and ';
  1915. }
  1916. $funcExpression .= '( ' . $function . ' )';
  1917. }
  1918. }
  1919. }
  1920. else if ( is_string( $functions ) )
  1921. {
  1922. $funcExpression = $functions;
  1923. }
  1924. else
  1925. {
  1926. $funcExpression = false;
  1927. $accessAllowed = true;
  1928. }
  1929. if ( $funcExpression )
  1930. {
  1931. // Validate and evaluate functions expression.
  1932. // Lets construct functions's expression ready for evaluating first.
  1933. $pS = '/(?<=\b)';
  1934. $pE = '(?=\b)/';
  1935. $moduleName = $module->attribute( 'name' );
  1936. $availableFunctions = $module->attribute( 'available_functions' );
  1937. if ( is_array( $availableFunctions ) and
  1938. !empty( $availableFunctions ) )
  1939. {
  1940. $pattern = $pS . '(' . implode( '|', array_keys( $availableFunctions ) ) . ')' . $pE;
  1941. $matches = array();
  1942. if ( preg_match_all( $pattern, ' ' . $funcExpression . ' ', $matches ) > 0 )
  1943. {
  1944. $patterns = array();
  1945. $replacements = array();
  1946. $matches = array_unique( $matches[1] );
  1947. foreach ( $matches as $match )
  1948. {
  1949. if ( !isset( $replacements[$match] ) )
  1950. {
  1951. $accessResult = $this->hasAccessTo( $moduleName, $match );
  1952. if ( $accessResult['accessWord'] == 'no' )
  1953. {
  1954. $replacements[$match] = 'false';
  1955. $params['accessList'] = $accessResult['accessList'];
  1956. }
  1957. else
  1958. {
  1959. $replacements[$match] = 'true';
  1960. if ( $accessResult['accessWord'] == 'limited' )
  1961. {
  1962. $params['Limitation'] = $accessResult['policies'];
  1963. $GLOBALS['ezpolicylimitation_list'][$this->ContentObjectID][$moduleName][$match] = $params['Limitation'];
  1964. }
  1965. }
  1966. $patterns[$match] = $pS . $match . $pE;
  1967. }
  1968. }
  1969. $funcExpression = preg_replace( $patterns, $replacements, ' ' . $funcExpression . ' ' );
  1970. }
  1971. }
  1972. $funcExpressionForEval = $funcExpression;
  1973. // continue to validate expression
  1974. $words = array();
  1975. $words[] = $pS . 'and' . $pE;
  1976. $words[] = $pS . 'or' . $pE;
  1977. $words[] = $pS . 'true' . $pE;
  1978. $words[] = $pS . 'false' . $pE;
  1979. $pS = '/(?<=[^&|])';
  1980. $pE = '(?=[^&|])/';
  1981. $words[] = $pS . '\|\|' . $pE;
  1982. $words[] = $pS . '&&' . $pE;
  1983. $words[] = '/[\(\)]/';
  1984. $replacement = '';
  1985. $funcExpression = preg_replace( $words, $replacement, ' ' . $funcExpression . ' ' );
  1986. $funcExpression = trim( $funcExpression );
  1987. if ( empty( $funcExpression ) )
  1988. {
  1989. // if expression is valid then evaluate value of the $functionsToEvaluate string
  1990. ob_start();
  1991. $ret = eval( "\$accessAllowed = ( bool ) ( $funcExpressionForEval );" );
  1992. $buffer = ob_get_contents();
  1993. ob_end_clean();
  1994. // if we get any error while evaluating then set result to false
  1995. if ( !empty( $buffer ) or $ret === false )
  1996. {
  1997. eZDebug::writeError( "There was an error while evaluating the policy functions value of the '$moduleName/$viewName' view. " .
  1998. "Please check the '$moduleName/module.php' file." );
  1999. $accessAllowed = false;
  2000. }
  2001. }
  2002. else
  2003. {
  2004. eZDebug::writeError( "There is a mistake in the functions array data of the '$moduleName/$viewName' view. " .
  2005. "Please check the '$moduleName/module.php' file." );
  2006. $accessAllowed = false;
  2007. }
  2008. }
  2009. }
  2010. else
  2011. {
  2012. $moduleName = $module->attribute( 'name' );
  2013. $accessResult = $this->hasAccessTo( $moduleName );
  2014. if ( $accessResult['accessWord'] == 'no' )
  2015. {
  2016. $params['accessList'] = $accessResult['accessList'];
  2017. $accessAllowed = false;
  2018. }
  2019. else
  2020. {
  2021. $accessAllowed = true;
  2022. if ( $accessResult['accessWord'] == 'limited' )
  2023. {
  2024. $params['Limitation'] = $accessResult['policies'];
  2025. $GLOBALS['ezpolicylimitation_list'][$this->ContentObjectID][$moduleName]['*'] = $params['Limitation'];
  2026. }
  2027. }
  2028. }
  2029. }
  2030. return $accessAllowed;
  2031. }
  2032. /*!
  2033. \return an array of roles which the user is assigned to
  2034. */
  2035. function roles()
  2036. {
  2037. $groups = $this->attribute( 'groups' );
  2038. $groups[] = $this->attribute( 'contentobject_id' );
  2039. return eZRole::fetchByUser( $groups );
  2040. }
  2041. /*!
  2042. \return an array of role ids which the user is assigned to
  2043. */
  2044. function roleIDList()
  2045. {
  2046. if ( eZINI::instance()->variable( 'RoleSettings', 'EnableCaching' ) === 'true' )
  2047. {
  2048. $userCache = $this->getUserCache();
  2049. return $userCache['roles'];
  2050. }
  2051. $groups = $this->attribute( 'groups' );
  2052. $groups[] = $this->attribute( 'contentobject_id' );
  2053. return eZRole::fetchIDListByUser( $groups );
  2054. }
  2055. /*!
  2056. \return an array of limited assignments
  2057. */
  2058. function limitList( $useGroupsCache = true )
  2059. {
  2060. if ( $useGroupsCache )
  2061. $groups = $this->groups();
  2062. else
  2063. $groups = $this->generateGroupIdList();
  2064. $groups[] = $this->attribute( 'contentobject_id' );
  2065. $groups = implode( ', ', $groups );
  2066. $db = eZDB::instance();
  2067. $limitationsArray = $db->arrayQuery( "SELECT DISTINCT limit_identifier, limit_value
  2068. FROM ezuser_role
  2069. WHERE contentobject_id IN ( $groups )" );
  2070. return $limitationsArray;
  2071. }
  2072. /*!
  2073. \return an array of values of limited assignments
  2074. */
  2075. function limitValueList()
  2076. {
  2077. if ( eZINI::instance()->variable( 'RoleSettings', 'EnableCaching' ) === 'true' )
  2078. {
  2079. $userCache = $this->getUserCache();
  2080. return $userCache['role_limitations'];
  2081. }
  2082. $limitValueList = array();
  2083. $limitList = $this->limitList();
  2084. foreach ( $limitList as $limit )
  2085. {
  2086. $limitValueList[] = $limit['limit_value'];
  2087. }
  2088. return $limitValueList;
  2089. }
  2090. function contentObject()
  2091. {
  2092. if ( isset( $this->ContentObjectID ) and $this->ContentObjectID )
  2093. {
  2094. return eZContentObject::fetch( $this->ContentObjectID );
  2095. }
  2096. return null;
  2097. }
  2098. /*!
  2099. Returns true if it's a real user which is logged in. False if the user
  2100. is the default user or the fallback buildtin user.
  2101. */
  2102. function isLoggedIn()
  2103. {
  2104. if ( $this->ContentObjectID == self::anonymousId() or
  2105. $this->ContentObjectID == -1 )
  2106. {
  2107. return false;
  2108. }
  2109. return true;
  2110. }
  2111. /*!
  2112. \return an array of id's with all the groups the user belongs to.
  2113. */
  2114. function groups( $asObject = false )
  2115. {
  2116. if ( $asObject == true )
  2117. {
  2118. if ( !isset( $this->GroupsAsObjects ) )
  2119. {
  2120. $db = eZDB::instance();
  2121. $contentobjectID = $this->attribute( 'contentobject_id' );
  2122. $userGroups = $db->arrayQuery( "SELECT d.*, c.path_string
  2123. FROM ezcontentobject_tree b,
  2124. ezcontentobject_tree c,
  2125. ezcontentobject d
  2126. WHERE b.contentobject_id='$contentobjectID' AND
  2127. b.parent_node_id = c.node_id AND
  2128. d.id = c.contentobject_id
  2129. ORDER BY c.contentobject_id ");
  2130. $userGroupArray = array();
  2131. $pathArray = array();
  2132. foreach ( $userGroups as $group )
  2133. {
  2134. $pathItems = explode( '/', $group["path_string"] );
  2135. array_pop( $pathItems );
  2136. array_pop( $pathItems );
  2137. foreach ( $pathItems as $pathItem )
  2138. {
  2139. if ( $pathItem != '' && $pathItem > 1 )
  2140. $pathArray[] = $pathItem;
  2141. }
  2142. $userGroupArray[] = new eZContentObject( $group );
  2143. }
  2144. $pathArray = array_unique( $pathArray );
  2145. if ( !empty( $pathArray ) )
  2146. {
  2147. $extraGroups = $db->arrayQuery( "SELECT d.*
  2148. FROM ezcontentobject_tree c,
  2149. ezcontentobject d
  2150. WHERE c.node_id in ( " . implode( ', ', $pathArray ) . " ) AND
  2151. d.id = c.contentobject_id
  2152. ORDER BY c.contentobject_id ");
  2153. foreach ( $extraGroups as $group )
  2154. {
  2155. $userGroupArray[] = new eZContentObject( $group );
  2156. }
  2157. }
  2158. $this->GroupsAsObjects = $userGroupArray;
  2159. }
  2160. return $this->GroupsAsObjects;
  2161. }
  2162. else
  2163. {
  2164. if ( !isset( $this->Groups ) )
  2165. {
  2166. if ( eZINI::instance()->variable( 'RoleSettings', 'EnableCaching' ) === 'true' )
  2167. {
  2168. $userCache = $this->getUserCache();
  2169. $this->Groups = $userCache['groups'];
  2170. }
  2171. else
  2172. {
  2173. $this->Groups = $this->generateGroupIdList();
  2174. }
  2175. }
  2176. return $this->Groups;
  2177. }
  2178. }
  2179. /**
  2180. * Generate list of group id's
  2181. *
  2182. * @since 4.4
  2183. * @return array
  2184. */
  2185. protected function generateGroupIdList()
  2186. {
  2187. $db = eZDB::instance();
  2188. $userID = $this->ContentObjectID;
  2189. $userGroups = $db->arrayQuery( "SELECT c.contentobject_id as id,c.path_string
  2190. FROM ezcontentobject_tree b,
  2191. ezcontentobject_tree c
  2192. WHERE b.contentobject_id='$userID' AND
  2193. b.parent_node_id = c.node_id
  2194. ORDER BY c.contentobject_id ");
  2195. $pathArray = array();
  2196. $userGroupArray = array();
  2197. foreach ( $userGroups as $group )
  2198. {
  2199. $pathItems = explode( '/', $group["path_string"] );
  2200. array_pop( $pathItems );
  2201. array_pop( $pathItems );
  2202. foreach ( $pathItems as $pathItem )
  2203. {
  2204. if ( $pathItem != '' && $pathItem > 1 )
  2205. $pathArray[] = $pathItem;
  2206. }
  2207. $userGroupArray[] = $group['id'];
  2208. }
  2209. if ( !empty( $pathArray ) )
  2210. {
  2211. $pathArray = array_unique ($pathArray);
  2212. $extraGroups = $db->arrayQuery( "SELECT c.contentobject_id as id
  2213. FROM ezcontentobject_tree c,
  2214. ezcontentobject d
  2215. WHERE c.node_id in ( " . implode( ', ', $pathArray ) . " ) AND
  2216. d.id = c.contentobject_id
  2217. ORDER BY c.contentobject_id ");
  2218. foreach ( $extraGroups as $group )
  2219. {
  2220. $userGroupArray[] = $group['id'];
  2221. }
  2222. }
  2223. return $userGroupArray;
  2224. }
  2225. /*!
  2226. Checks if user is logged in, if not and the site requires user login for access
  2227. a module redirect is returned.
  2228. \return null, user login not required.
  2229. */
  2230. function checkUser( &$siteBasics, $uri )
  2231. {
  2232. $http = eZHTTPTool::instance();
  2233. $check = array( "module" => "user",
  2234. "function" => "login" );
  2235. if ( eZSession::issetkey( 'eZUserLoggedInID', false ) &&
  2236. $http->sessionVariable( "eZUserLoggedInID" ) != '' &&
  2237. $http->sessionVariable( "eZUserLoggedInID" ) != self::anonymousId() )
  2238. {
  2239. $currentUser = eZUser::currentUser();
  2240. if ( !$currentUser->isEnabled() )
  2241. {
  2242. eZUser::logoutCurrent();
  2243. $currentUser = eZUser::currentUser();
  2244. }
  2245. else
  2246. {
  2247. return null;
  2248. }
  2249. }
  2250. $ini = eZINI::instance();
  2251. $moduleName = $uri->element();
  2252. $viewName = $uri->element( 1 );
  2253. $anonymousAccessList = $ini->variable( "SiteAccessSettings", "AnonymousAccessList" );
  2254. foreach ( $anonymousAccessList as $anonymousAccess )
  2255. {
  2256. $elements = explode( '/', $anonymousAccess );
  2257. if ( !isset( $elements[1] ) )
  2258. {
  2259. if ( $moduleName == $elements[0] )
  2260. {
  2261. return null;
  2262. }
  2263. }
  2264. else
  2265. {
  2266. if ( $moduleName == $elements[0] and
  2267. $viewName == $elements[1] )
  2268. {
  2269. return null;
  2270. }
  2271. }
  2272. }
  2273. return $check;
  2274. }
  2275. /*!
  2276. Funtion performed before user login info is collected.
  2277. It's optional to implement this function in new login handler.
  2278. \return @see eZUserLoginHandler::checkUser()
  2279. */
  2280. function preCollectUserInfo()
  2281. {
  2282. return array( 'module' => 'user', 'function' => 'login' );
  2283. }
  2284. /*!
  2285. Function performed after user login info has been collected.
  2286. Store login data as array:
  2287. array( 'login' => <username>,
  2288. 'password' = <password> )
  2289. to session variable EZ_LOGIN_HANDLER_USER_INFO for automatic processing of login data.
  2290. \return @see eZUserLoginHandler::checkUser()
  2291. */
  2292. function postCollectUserInfo()
  2293. {
  2294. return true;
  2295. }
  2296. /*!
  2297. Check if login handler require special login URI
  2298. \return Special login uri. If false, use system standard login uri.
  2299. */
  2300. function loginURI()
  2301. {
  2302. return false;
  2303. }
  2304. /*!
  2305. Check if login handler require forced login at user check.
  2306. \return true if force login on user check, false if not.
  2307. */
  2308. function forceLogin()
  2309. {
  2310. return false;
  2311. }
  2312. /**
  2313. * Creates the cache path if it doesn't exist, and returns the cache
  2314. * directory. The $userId parameter is used to create multi-level directory names
  2315. *
  2316. * @params int $userId
  2317. * @return string Cache directory for the user
  2318. */
  2319. static function getCacheDir( $userId = 0 )
  2320. {
  2321. $dir = eZSys::cacheDirectory() . '/user-info' . eZDir::createMultilevelPath( $userId, 5 );
  2322. if ( !is_dir( $dir ) )
  2323. {
  2324. eZDir::mkdir( $dir, false, true );
  2325. }
  2326. return $dir;
  2327. }
  2328. /**
  2329. * Expire user access / info cache globally
  2330. */
  2331. static function cleanupCache()
  2332. {
  2333. eZExpiryHandler::registerShutdownFunction();
  2334. $handler = eZExpiryHandler::instance();
  2335. $handler->setTimestamp( 'user-info-cache', time() );
  2336. $handler->store();
  2337. }
  2338. /**
  2339. * Returns the filename for a cache file with user information
  2340. *
  2341. * @deprecated In 4.4.0
  2342. * @params int $userId
  2343. * @return string|false Filename of the cachefile, or false when the user should not be cached
  2344. */
  2345. static function getCacheFilename( $userId )
  2346. {
  2347. $ini = eZINI::instance();
  2348. $cacheUserPolicies = $ini->variable( 'RoleSettings', 'UserPolicyCache' );
  2349. if ( $cacheUserPolicies === 'enabled' )
  2350. {
  2351. return eZUser::getCacheDir( $userId ). '/user-'. $userId . '.cache.php';
  2352. }
  2353. else if ( $cacheUserPolicies !== 'disabled' )
  2354. {
  2355. $cachableIDs = explode( ',', $cacheUserPolicies );
  2356. if ( in_array( $userId, $cachableIDs ) )
  2357. {
  2358. return eZUser::getCacheDir( $userId ). '/user-'. $userId . '.cache.php';
  2359. }
  2360. }
  2361. return false;
  2362. }
  2363. static function fetchUserClassList( $asObject = false, $fields = false )
  2364. {
  2365. // Get names of user classes
  2366. if ( !$asObject and
  2367. is_array( $fields ) and
  2368. !empty( $fields ) )
  2369. {
  2370. $fieldsFilter = '';
  2371. $i = 0;
  2372. foreach ( $fields as $fieldName )
  2373. {
  2374. if ( $i > 0 )
  2375. $fieldsFilter .= ', ';
  2376. $fieldsFilter .= 'ezcontentclass.' . $fieldName;
  2377. $i++;
  2378. }
  2379. }
  2380. else
  2381. {
  2382. $fieldsFilter = 'ezcontentclass.*';
  2383. }
  2384. $db = eZDB::instance();
  2385. $userClasses = $db->arrayQuery( "SELECT $fieldsFilter
  2386. FROM ezcontentclass, ezcontentclass_attribute
  2387. WHERE ezcontentclass.id = ezcontentclass_attribute.contentclass_id AND
  2388. ezcontentclass.version = " . eZContentClass::VERSION_STATUS_DEFINED ." AND
  2389. ezcontentclass_attribute.version = 0 AND
  2390. ezcontentclass_attribute.data_type_string = 'ezuser'" );
  2391. return eZPersistentObject::handleRows( $userClasses, "eZContentClass", $asObject );
  2392. }
  2393. static function fetchUserClassNames()
  2394. {
  2395. $userClassNames = array();
  2396. $userClasses = eZUser::fetchUserClassList( false, array( 'identifier' ) );
  2397. foreach ( $userClasses as $class )
  2398. {
  2399. $userClassNames[] = $class[ 'identifier' ];
  2400. }
  2401. return $userClassNames;
  2402. }
  2403. static function fetchUserGroupClassNames()
  2404. {
  2405. // Get names of user classes
  2406. $userClassNames = array();
  2407. $userClasses = eZUser::fetchUserClassList( false, array( 'identifier' ) );
  2408. foreach ( $userClasses as $class )
  2409. {
  2410. $userClassNames[] = $class[ 'identifier' ];
  2411. }
  2412. // Get names of all allowed content-classes for the Users subtree
  2413. $contentIni = eZINI::instance( "content.ini" );
  2414. $userGroupClassNames = array();
  2415. if ( $contentIni->hasVariable( 'ClassGroupIDs', 'Users' ) and
  2416. is_numeric( $usersClassGroupID = $contentIni->variable( 'ClassGroupIDs', 'Users' ) ) and
  2417. count( $usersClassList = eZContentClassClassGroup::fetchClassList( eZContentClass::VERSION_STATUS_DEFINED, $usersClassGroupID ) ) > 0 )
  2418. {
  2419. foreach ( $usersClassList as $userClass )
  2420. {
  2421. $userGroupClassNames[] = $userClass->attribute( 'identifier' );
  2422. }
  2423. }
  2424. // Get names of user-group classes
  2425. $groupClassNames = array_diff( $userGroupClassNames, $userClassNames );
  2426. return $groupClassNames;
  2427. }
  2428. /*!
  2429. Checks the password for validity
  2430. \static
  2431. \return true when password is valid by length and not empty, false if not
  2432. */
  2433. static function validatePassword( $password )
  2434. {
  2435. $ini = eZINI::instance();
  2436. $minPasswordLength = $ini->variable( 'UserSettings', 'MinPasswordLength' );
  2437. if ( $password !== false and
  2438. $password !== null and
  2439. strlen( $password ) >= (int) $minPasswordLength )
  2440. {
  2441. return true;
  2442. }
  2443. return false;
  2444. }
  2445. /**
  2446. * Validates user login name using site.ini[UserSettings]UserNameValidationRegex[]
  2447. *
  2448. * @static
  2449. * @since Version 4.1
  2450. * @param string $loginName that we want to validate.
  2451. * @param string $errorText by reference for details if validation fails.
  2452. * @return bool Indicates if validation failed (false) or not (true).
  2453. */
  2454. static function validateLoginName( $loginName, &$errorText )
  2455. {
  2456. $ini = eZINI::instance();
  2457. $regexList = $ini->variable( 'UserSettings', 'UserNameValidationRegex' );
  2458. $errorTextList = $ini->variable( 'UserSettings', 'UserNameValidationErrorText' );
  2459. foreach ( $regexList as $key => $regex )
  2460. {
  2461. if( preg_match( $regex, $loginName) )
  2462. {
  2463. if ( isset( $errorTextList[$key] ) )
  2464. $errorText = $errorTextList[$key];
  2465. else
  2466. $errorText = $ini->variable( 'UserSettings', 'DefaultUserNameValidationErrorText' );
  2467. return false;
  2468. }
  2469. }
  2470. return true;
  2471. }
  2472. /**
  2473. * Gets the id of the anonymous user.
  2474. *
  2475. * @static
  2476. * @return int User id of anonymous user.
  2477. */
  2478. public static function anonymousId()
  2479. {
  2480. if ( self::$anonymousId === null )
  2481. {
  2482. $ini = eZINI::instance();
  2483. self::$anonymousId = (int)$ini->variable( 'UserSettings', 'AnonymousUserID' );
  2484. $GLOBALS['eZUserBuiltins'] = array( self::$anonymousId );
  2485. }
  2486. return self::$anonymousId;
  2487. }
  2488. /*!
  2489. Returns the IDs of content classes that contain user accounts
  2490. */
  2491. public static function contentClassIDs()
  2492. {
  2493. $userContentClassIDs = array();
  2494. $ini = eZINI::instance( 'content.ini' );
  2495. $userDatatypes = $ini->variable( "DataTypeSettings", "UserDataTypes" );
  2496. $userContentClassIDs = array();
  2497. foreach ( $userDatatypes as $datatypeIdentifier )
  2498. {
  2499. $userContentClassIDs = array_merge( $userContentClassIDs, eZContentClass::fetchIDListContainingDatatype( $datatypeIdentifier ) );
  2500. }
  2501. return $userContentClassIDs;
  2502. }
  2503. public function canLoginToSiteAccess( $access )
  2504. {
  2505. $siteAccessResult = $this->hasAccessTo( 'user', 'login' );
  2506. $hasAccessToSite = false;
  2507. if ( $siteAccessResult[ 'accessWord' ] == 'limited' )
  2508. {
  2509. $siteNameCRC = eZSys::ezcrc32( $access[ 'name' ] );
  2510. $policyChecked = false;
  2511. foreach ( $siteAccessResult['policies'] as $policy )
  2512. {
  2513. if ( isset( $policy['SiteAccess'] ) )
  2514. {
  2515. $policyChecked = true;
  2516. if ( in_array( $siteNameCRC, $policy['SiteAccess'] ) )
  2517. {
  2518. $hasAccessToSite = true;
  2519. break;
  2520. }
  2521. }
  2522. }
  2523. if ( !$policyChecked )
  2524. {
  2525. $hasAccessToSite = true;
  2526. }
  2527. }
  2528. else if ( $siteAccessResult[ 'accessWord' ] == 'yes' )
  2529. {
  2530. $hasAccessToSite = true;
  2531. }
  2532. return $hasAccessToSite;
  2533. }
  2534. /// \privatesection
  2535. public $Login;
  2536. public $Email;
  2537. public $PasswordHash;
  2538. public $PasswordHashType;
  2539. public $Groups;
  2540. public $OriginalPassword;
  2541. public $OriginalPasswordConfirm;
  2542. /**
  2543. * Holds user cache like user info and access array
  2544. *
  2545. * @since 4.4
  2546. * @see eZUser::getUserCache()
  2547. * @var array|null
  2548. */
  2549. protected $UserCache = null;
  2550. /**
  2551. * Used to keep track that a logout was performed, and therefore prevent
  2552. * auto-login from happening if an SSO is used
  2553. * @var bool
  2554. * @since 4.3
  2555. */
  2556. protected static $userHasLoggedOut = false;
  2557. }
  2558. ?>