PageRenderTime 39ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

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

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