eZ Publish  [trunk]
ezuser.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZUser class.
00004  *
00005  * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved.
00006  * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
00007  * @version //autogentag//
00008  * @package kernel
00009  */
00010 
00011 /*!
00012   \class eZUser ezuser.php
00013   \brief eZUser handles eZ Publish user accounts
00014   \ingroup eZDatatype
00015 
00016 */
00017 
00018 class eZUser extends eZPersistentObject
00019 {
00020     /// MD5 of password
00021     const PASSWORD_HASH_MD5_PASSWORD = 1;
00022     /// MD5 of user and password
00023     const PASSWORD_HASH_MD5_USER = 2;
00024     /// MD5 of site, user and password
00025     const PASSWORD_HASH_MD5_SITE = 3;
00026     /// Legacy support for mysql hashed passwords
00027     const PASSWORD_HASH_MYSQL = 4;
00028     /// Passwords in plaintext, should not be used for real sites
00029     const PASSWORD_HASH_PLAINTEXT = 5;
00030     // Crypted passwords
00031     const PASSWORD_HASH_CRYPT = 6;
00032 
00033     /// Authenticate by matching the login field
00034     const AUTHENTICATE_LOGIN = 1;
00035     /// Authenticate by matching the email field
00036     const AUTHENTICATE_EMAIL = 2;
00037 
00038     const AUTHENTICATE_ALL = 3; //EZ_USER_AUTHENTICATE_LOGIN | EZ_USER_AUTHENTICATE_EMAIL;
00039 
00040     /**
00041      * Flag used to prevent session regeneration
00042      *
00043      * @var int
00044      * @see eZUser::setCurrentlyLoggedInUser()
00045      */
00046     const NO_SESSION_REGENERATE = 1;
00047 
00048     protected static $anonymousId = null;
00049 
00050     function eZUser( $row = array() )
00051     {
00052         $this->eZPersistentObject( $row );
00053         $this->OriginalPassword = false;
00054         $this->OriginalPasswordConfirm = false;
00055     }
00056 
00057     static function definition()
00058     {
00059         static $definition = array( 'fields' => array( 'contentobject_id' => array( 'name' => 'ContentObjectID',
00060                                                                       'datatype' => 'integer',
00061                                                                       'default' => 0,
00062                                                                       'required' => true,
00063                                                                       'foreign_class' => 'eZContentObject',
00064                                                                       'foreign_attribute' => 'id',
00065                                                                       'multiplicity' => '0..1' ),
00066                                          'login' => array( 'name' => 'Login',
00067                                                            'datatype' => 'string',
00068                                                            'default' => '',
00069                                                            'required' => true ),
00070                                          'email' => array( 'name' => 'Email',
00071                                                            'datatype' => 'string',
00072                                                            'default' => '',
00073                                                            'required' => true ),
00074                                          'password_hash' => array( 'name' => 'PasswordHash',
00075                                                                    'datatype' => 'string',
00076                                                                    'default' => '',
00077                                                                    'required' => true ),
00078                                          'password_hash_type' => array( 'name' => 'PasswordHashType',
00079                                                                         'datatype' => 'integer',
00080                                                                         'default' => 1,
00081                                                                         'required' => true ) ),
00082                       'keys' => array( 'contentobject_id' ),
00083                       'sort' => array( 'contentobject_id' => 'asc' ),
00084                       'function_attributes' => array( 'contentobject' => 'contentObject',
00085                                                       'account_key' => 'accountKey',
00086                                                       'groups' => 'groups',
00087                                                       'has_stored_login' => 'hasStoredLogin',
00088                                                       'original_password' => 'originalPassword',
00089                                                       'original_password_confirm' => 'originalPasswordConfirm',
00090                                                       'roles' => 'roles',
00091                                                       'role_id_list' => 'roleIDList',
00092                                                       'limited_assignment_value_list' => 'limitValueList',
00093                                                       'is_logged_in' => 'isLoggedIn',
00094                                                       'is_enabled' => 'isEnabled',
00095                                                       'is_locked' => 'isLocked',
00096                                                       'last_visit' => 'lastVisit',
00097                                                       'login_count' => 'loginCount',
00098                                                       'has_manage_locations' => 'hasManageLocations' ),
00099                       'relations' => array( 'contentobject_id' => array( 'class' => 'ezcontentobject',
00100                                                                          'field' => 'id' ) ),
00101                       'class_name' => 'eZUser',
00102                       'name' => 'ezuser' );
00103         return $definition;
00104     }
00105 
00106     /*!
00107      \return a textual identifier for the hash type $id
00108     */
00109     static function passwordHashTypeName( $id )
00110     {
00111         switch ( $id )
00112         {
00113             case self::PASSWORD_HASH_MD5_PASSWORD:
00114             {
00115                 return 'md5_password';
00116             } break;
00117             case self::PASSWORD_HASH_MD5_USER:
00118             {
00119                 return 'md5_user';
00120             } break;
00121             case self::PASSWORD_HASH_MD5_SITE:
00122             {
00123                 return 'md5_site';
00124             } break;
00125             case self::PASSWORD_HASH_MYSQL:
00126             {
00127                 return 'mysql';
00128             } break;
00129             case self::PASSWORD_HASH_PLAINTEXT:
00130             {
00131                 return 'plaintext';
00132             } break;
00133             case self::PASSWORD_HASH_CRYPT:
00134             {
00135                 return 'crypt';
00136             } break;
00137         }
00138     }
00139 
00140     /*!
00141      \return the hash type for the textual identifier $identifier
00142     */
00143     static function passwordHashTypeID( $identifier )
00144     {
00145         switch ( $identifier )
00146         {
00147             case 'md5_password':
00148             {
00149                 return self::PASSWORD_HASH_MD5_PASSWORD;
00150             } break;
00151             default:
00152             case 'md5_user':
00153             {
00154                 return self::PASSWORD_HASH_MD5_USER;
00155             } break;
00156             case 'md5_site':
00157             {
00158                 return self::PASSWORD_HASH_MD5_SITE;
00159             } break;
00160             case 'mysql':
00161             {
00162                 return self::PASSWORD_HASH_MYSQL;
00163             } break;
00164             case 'plaintext':
00165             {
00166                 return self::PASSWORD_HASH_PLAINTEXT;
00167             } break;
00168             case 'crypt':
00169             {
00170                 return self::PASSWORD_HASH_CRYPT;
00171             } break;
00172         }
00173     }
00174 
00175     /*!
00176      Check if current user has "content/manage_locations" access
00177     */
00178     function hasManageLocations()
00179     {
00180         $accessResult = $this->hasAccessTo( 'content', 'manage_locations' );
00181         if ( $accessResult['accessWord'] != 'no' )
00182         {
00183             return true;
00184         }
00185 
00186         return false;
00187     }
00188 
00189     static function create( $contentObjectID )
00190     {
00191         $row = array(
00192             'contentobject_id' => $contentObjectID,
00193             'login' => null,
00194             'email' => null,
00195             'password_hash' => null,
00196             'password_hash_type' => null
00197             );
00198         return new eZUser( $row );
00199     }
00200 
00201     function store( $fieldFilters = null )
00202     {
00203         $this->Email = trim( $this->Email );
00204         $userID = $this->attribute( 'contentobject_id' );
00205         // Clear memory cache
00206         unset( $GLOBALS['eZUserObject_' . $userID] );
00207         $GLOBALS['eZUserObject_' . $userID] = $this;
00208         self::purgeUserCacheByUserId( $userID );
00209         eZPersistentObject::store( $fieldFilters );
00210     }
00211 
00212     function originalPassword()
00213     {
00214         return $this->OriginalPassword;
00215     }
00216 
00217     function setOriginalPassword( $password )
00218     {
00219         $this->OriginalPassword = $password;
00220     }
00221 
00222     function originalPasswordConfirm()
00223     {
00224         return $this->OriginalPasswordConfirm;
00225     }
00226 
00227     function setOriginalPasswordConfirm( $password )
00228     {
00229         $this->OriginalPasswordConfirm = $password;
00230     }
00231 
00232     function hasStoredLogin()
00233     {
00234         $db = eZDB::instance();
00235         $contentObjectID = $this->attribute( 'contentobject_id' );
00236         $sql = "SELECT * FROM ezuser WHERE contentobject_id='$contentObjectID' AND LENGTH( login ) > 0";
00237         $rows = $db->arrayQuery( $sql );
00238         return !empty( $rows );
00239     }
00240 
00241     /*!
00242      Fills in the \a $id, \a $login, \a $email and \a $password for the user
00243      and creates the proper password hash.
00244     */
00245     function setInformation( $id, $login, $email, $password, $passwordConfirm = false )
00246     {
00247         $this->setAttribute( "contentobject_id", $id );
00248         $this->setAttribute( "email", $email );
00249         $this->setAttribute( "login", $login );
00250         if ( eZUser::validatePassword( $password ) and
00251              $password == $passwordConfirm ) // Cannot change login or password_hash without login and password
00252         {
00253             $this->setAttribute( "password_hash", eZUser::createHash( $login, $password, eZUser::site(),
00254                                                                       eZUser::hashType() ) );
00255             $this->setAttribute( "password_hash_type", eZUser::hashType() );
00256         }
00257         else
00258         {
00259             $this->setOriginalPassword( $password );
00260             $this->setOriginalPasswordConfirm( $passwordConfirm );
00261         }
00262     }
00263 
00264     static function fetch( $id, $asObject = true )
00265     {
00266         if ( !$id )
00267             return null;
00268         return eZPersistentObject::fetchObject( eZUser::definition(),
00269                                                 null,
00270                                                 array( 'contentobject_id' => $id ),
00271                                                 $asObject );
00272     }
00273 
00274     static function fetchByName( $login, $asObject = true )
00275     {
00276         return eZPersistentObject::fetchObject( eZUser::definition(),
00277                                                 null,
00278                                                 array( 'LOWER( login )' => strtolower( $login ) ),
00279                                                 $asObject );
00280     }
00281 
00282     static function fetchByEmail( $email, $asObject = true )
00283     {
00284         return eZPersistentObject::fetchObject( eZUser::definition(),
00285                                                   null,
00286                                                   array( 'LOWER( email )' => strtolower( $email ) ),
00287                                                   $asObject );
00288     }
00289 
00290     /**
00291      * Return an array of unactivated eZUser object
00292      *
00293      * @param array|false|null An associative array of sorting conditions,
00294      *        if set to false ignores settings in $def, if set to null uses
00295      *        settingss in $def.
00296      * @param int $limit
00297      * @param int $offset
00298      * @return array( eZUser )
00299      */
00300     static public function fetchUnactivated( $sort = false, $limit = 10, $offset = 0 )
00301     {
00302         $accountDef = eZUserAccountKey::definition();
00303         $settingsDef = eZUserSetting::definition();
00304 
00305         return eZPersistentObject::fetchObjectList(
00306             eZUser::definition(), null, null, $sort,
00307             array(
00308                 'limit' => $limit,
00309                 'offset' => $offset
00310             ),
00311             true, false, null,
00312             array( $accountDef['name'], $settingsDef['name'] ),
00313             " WHERE contentobject_id = {$accountDef['name']}.user_id"
00314                 . " AND {$settingsDef['name']}.user_id = contentobject_id"
00315                 . " AND is_enabled = 0"
00316         );
00317     }
00318 
00319     /*!
00320      \static
00321      \return a list of the logged in users.
00322      \param $asObject If false it will return a list with only the names of the users as elements and user ID as key,
00323                       otherwise each entry is a eZUser object.
00324      \sa fetchLoggedInCount
00325     */
00326     static function fetchLoggedInList( $asObject = false, $offset = false, $limit = false, $sortBy = false )
00327     {
00328         $db = eZDB::instance();
00329         $time = time() - eZINI::instance()->variable( 'Session', 'ActivityTimeout' );
00330 
00331         $parameters = array();
00332         if ( $offset )
00333             $parameters['offset'] =(int) $offset;
00334         if ( $limit )
00335             $parameters['limit'] =(int) $limit;
00336         $sortText = '';
00337         if ( $asObject )
00338         {
00339             $selectArray = array( "distinct ezuser.*" );
00340         }
00341         else
00342         {
00343             $selectArray = array( "ezuser.contentobject_id as user_id", "ezcontentobject.name" );
00344         }
00345         if ( $sortBy !== false )
00346         {
00347             $sortElements = array();
00348             if ( !is_array( $sortBy ) )
00349             {
00350                 $sortBy = array( array( $sortBy, true ) );
00351             }
00352             else if ( !is_array( $sortBy[0] ) )
00353                 $sortBy = array( $sortBy );
00354             $sortColumns = array();
00355             foreach ( $sortBy as $sortElements )
00356             {
00357                 $sortColumn = $sortElements[0];
00358                 $sortOrder = $sortElements[1];
00359                 $orderText = $sortOrder ? 'asc' : 'desc';
00360                 switch ( $sortColumn )
00361                 {
00362                     case 'user_id':
00363                     {
00364                         $sortColumn = "ezuser.contentobject_id $orderText";
00365                     } break;
00366 
00367                     case 'login':
00368                     {
00369                         $sortColumn = "ezuser.login $orderText";
00370                     } break;
00371 
00372                     case 'activity':
00373                     {
00374                         $selectArray[] = "ezuservisit.current_visit_timestamp AS activity";
00375                         $sortColumn = "activity $orderText";
00376                     } break;
00377 
00378                     case 'email':
00379                     {
00380                         $sortColumn = "ezuser.email $orderText";
00381                     } break;
00382 
00383                     default:
00384                     {
00385                         eZDebug::writeError( "Unkown sort column '$sortColumn'", __METHOD__ );
00386                         $sortColumn = false;
00387                     } break;
00388                 }
00389                 if ( $sortColumn )
00390                     $sortColumns[] = $sortColumn;
00391             }
00392             if ( !empty( $sortColumns ) )
00393                 $sortText = "ORDER BY " . implode( ', ', $sortColumns );
00394         }
00395         if ( $asObject )
00396         {
00397             $selectText = implode( ', ',  $selectArray );
00398             $sql = "SELECT $selectText
00399 FROM ezuservisit, ezuser
00400 WHERE ezuservisit.user_id != '" . self::anonymousId() . "' AND
00401       ezuservisit.current_visit_timestamp > '$time' AND
00402       ezuser.contentobject_id = ezuservisit.user_id
00403 $sortText";
00404             $rows = $db->arrayQuery( $sql, $parameters );
00405             $list = array();
00406             foreach ( $rows as $row )
00407             {
00408                 $list[] = new eZUser( $row );
00409             }
00410         }
00411         else
00412         {
00413             $selectText = implode( ', ',  $selectArray );
00414             $sql = "SELECT $selectText
00415 FROM ezuservisit, ezuser, ezcontentobject
00416 WHERE ezuservisit.user_id != '" . self::anonymousId() . "' AND
00417       ezuservisit.current_visit_timestamp > '$time' AND
00418       ezuser.contentobject_id = ezuservisit.user_id AND
00419       ezcontentobject.id = ezuser.contentobject_id
00420 $sortText";
00421             $rows = $db->arrayQuery( $sql, $parameters );
00422             $list = array();
00423             foreach ( $rows as $row )
00424             {
00425                 $list[$row['user_id']] = $row['name'];
00426             }
00427         }
00428         return $list;
00429     }
00430 
00431     /*!
00432      \return the number of logged in users in the system.
00433      \note The count will be cached for the current page if caching is allowed.
00434      \sa fetchAnonymousCount
00435     */
00436     static function fetchLoggedInCount()
00437     {
00438         if ( isset( $GLOBALS['eZSiteBasics']['no-cache-adviced'] ) and
00439              !$GLOBALS['eZSiteBasics']['no-cache-adviced'] and
00440              isset( $GLOBALS['eZUserLoggedInCount'] ) )
00441             return $GLOBALS['eZUserLoggedInCount'];
00442         $db = eZDB::instance();
00443         $time = time() - eZINI::instance()->variable( 'Session', 'ActivityTimeout' );
00444 
00445         $sql = "SELECT count( DISTINCT user_id ) as count
00446 FROM ezuservisit
00447 WHERE user_id != '" . self::anonymousId() . "' AND
00448       user_id > 0 AND
00449       current_visit_timestamp > '$time'";
00450         $rows = $db->arrayQuery( $sql );
00451         $count = isset( $rows[0] ) ? $rows[0]['count'] : 0;
00452         $GLOBALS['eZUserLoggedInCount'] = $count;
00453         return $count;
00454     }
00455 
00456     /**
00457      * Return the number of anonymous users in the system.
00458      *
00459      * @deprecated As of 4.4 since default session handler does not support this.
00460      * @return int
00461      */
00462     static function fetchAnonymousCount()
00463     {
00464         if ( isset( $GLOBALS['eZSiteBasics']['no-cache-adviced'] ) and
00465              !$GLOBALS['eZSiteBasics']['no-cache-adviced'] and
00466              isset( $GLOBALS['eZUserAnonymousCount'] ) )
00467             return $GLOBALS['eZUserAnonymousCount'];
00468         $db = eZDB::instance();
00469         $time = time();
00470         $ini = eZINI::instance();
00471         $activityTimeout = $ini->variable( 'Session', 'ActivityTimeout' );
00472         $sessionTimeout = $ini->variable( 'Session', 'SessionTimeout' );
00473         $time = $time + $sessionTimeout - $activityTimeout;
00474 
00475         $sql = "SELECT count( session_key ) as count
00476 FROM ezsession
00477 WHERE user_id = '" . self::anonymousId() . "' AND
00478       expiration_time > '$time'";
00479         $rows = $db->arrayQuery( $sql );
00480         $count = isset( $rows[0] ) ? $rows[0]['count'] : 0;
00481         $GLOBALS['eZUserAnonymousCount'] = $count;
00482         return $count;
00483     }
00484 
00485     /*!
00486      \static
00487      \return true if the user with ID $userID is currently logged into the system.
00488      \note The information will be cached for the current page if caching is allowed.
00489      \sa fetchLoggedInList
00490     */
00491     static function isUserLoggedIn( $userID )
00492     {
00493         $userID = (int)$userID;
00494         if ( isset( $GLOBALS['eZSiteBasics']['no-cache-adviced'] ) and
00495              !$GLOBALS['eZSiteBasics']['no-cache-adviced'] and
00496              isset( $GLOBALS['eZUserLoggedInMap'][$userID] ) )
00497             return $GLOBALS['eZUserLoggedInMap'][$userID];
00498         $db = eZDB::instance();
00499         $time = time() - eZINI::instance()->variable( 'Session', 'ActivityTimeout' );
00500 
00501         $sql = "SELECT DISTINCT user_id
00502 FROM ezuservisit
00503 WHERE user_id = '" . $userID . "' AND
00504       current_visit_timestamp > '$time'";
00505         $rows = $db->arrayQuery( $sql, array( 'limit' => 2 ) );
00506         $isLoggedIn = isset( $rows[0] );
00507         $GLOBALS['eZUserLoggedInMap'][$userID] = $isLoggedIn;
00508         return $isLoggedIn;
00509     }
00510 
00511     /*!
00512      \static
00513      Removes any cached session information, this is:
00514      - logged in user count
00515      - anonymous user count
00516      - logged in user map
00517     */
00518     static function clearSessionCache()
00519     {
00520         unset( $GLOBALS['eZUserLoggedInCount'] );
00521         unset( $GLOBALS['eZUserAnonymousCount'] );
00522         unset( $GLOBALS['eZUserLoggedInMap'] );
00523     }
00524 
00525     /**
00526      * Remove session data for user \a $userID.
00527      * @todo should use eZSession api (needs to be created) so
00528      *       callbacks (like preference / basket..) is cleared as well.
00529      *
00530      * @params int $userID
00531      */
00532     static function removeSessionData( $userID )
00533     {
00534         eZUser::clearSessionCache();
00535         eZSession::getHandlerInstance()->deleteByUserIDs( array( $userID ) );
00536     }
00537 
00538     /*!
00539      Removes the user from the ezuser table.
00540      \note Will also remove any notifications and session related to the user.
00541     */
00542     static function removeUser( $userID )
00543     {
00544         $user = eZUser::fetch( $userID );
00545         if ( !$user )
00546         {
00547             eZDebug::writeError( "unable to find user with ID $userID", __METHOD__ );
00548             return false;
00549         }
00550 
00551         eZUser::removeSessionData( $userID );
00552 
00553         eZSubtreeNotificationRule::removeByUserID( $userID );
00554         eZCollaborationNotificationRule::removeByUserID( $userID );
00555         eZUserSetting::removeByUserID( $userID );
00556         eZUserAccountKey::removeByUserID( $userID );
00557         eZForgotPassword::removeByUserID( $userID );
00558         eZWishList::removeByUserID( $userID );
00559 
00560         // only remove general digest setting if there are no other users with the same e-mail
00561         $email = $user->attribute( 'email' );
00562         $usersWithEmailCount = eZPersistentObject::count( eZUser::definition(), array( 'email' => $email ) );
00563         if ( $usersWithEmailCount == 1 )
00564         {
00565             eZGeneralDigestUserSettings::removeByAddress( $email );
00566         }
00567 
00568         eZPersistentObject::removeObject( eZUser::definition(),
00569                                           array( 'contentobject_id' => $userID ) );
00570         return true;
00571     }
00572 
00573     /*!
00574      \return a list of valid and enabled users, the data returned is an array
00575              with ezcontentobject database data.
00576     */
00577     static function fetchContentList()
00578     {
00579         $contentObjectStatus = eZContentObject::STATUS_PUBLISHED;
00580         $query = "SELECT ezcontentobject.*
00581                   FROM ezuser, ezcontentobject, ezuser_setting
00582                   WHERE ezcontentobject.status = '$contentObjectStatus' AND
00583                         ezuser_setting.is_enabled = 1 AND
00584                         ezcontentobject.id = ezuser.contentobject_id AND
00585                         ezuser_setting.user_id = ezuser.contentobject_id";
00586         $db = eZDB::instance();
00587         $rows = $db->arrayQuery( $query );
00588         return $rows;
00589     }
00590 
00591     /*!
00592      \static
00593      \return the default hash type which is specified in UserSettings/HashType in site.ini
00594     */
00595     static function hashType()
00596     {
00597         $ini = eZINI::instance();
00598         $type = strtolower( $ini->variable( 'UserSettings', 'HashType' ) );
00599         if ( $type == 'md5_site' )
00600             return self::PASSWORD_HASH_MD5_SITE;
00601         else if ( $type == 'md5_user' )
00602             return self::PASSWORD_HASH_MD5_USER;
00603         else if ( $type == 'plaintext' )
00604             return self::PASSWORD_HASH_PLAINTEXT;
00605         else if ( $type == 'crypt' )
00606             return self::PASSWORD_HASH_CRYPT;
00607         else
00608             return self::PASSWORD_HASH_MD5_PASSWORD;
00609     }
00610 
00611     /*!
00612      \static
00613      \return the site name used in password hashing.
00614     */
00615     static function site()
00616     {
00617         $ini = eZINI::instance();
00618         return $ini->variable( 'UserSettings', 'SiteName' );
00619     }
00620 
00621     /*!
00622      Fetches a builtin user and returns it, this helps avoid special cases where
00623      user is not logged in.
00624     */
00625     static function fetchBuiltin( $id )
00626     {
00627         if ( !in_array( $id, $GLOBALS['eZUserBuiltins'] ) )
00628             $id = self::anonymousId();
00629         if ( empty( $GLOBALS["eZUserBuilitinInstance-$id"] ) )
00630         {
00631             $GLOBALS["eZUserBuilitinInstance-$id"] = eZUser::fetch( self::anonymousId() );
00632         }
00633         return $GLOBALS["eZUserBuilitinInstance-$id"];
00634     }
00635 
00636     /*!
00637      \return the user id.
00638     */
00639     function id()
00640     {
00641         return $this->ContentObjectID;
00642     }
00643 
00644     /*!
00645      \return a bitfield which decides the authenticate methods.
00646     */
00647     static function authenticationMatch()
00648     {
00649         $ini = eZINI::instance();
00650         $matchArray = $ini->variableArray( 'UserSettings', 'AuthenticateMatch' );
00651         $match = 0;
00652         foreach ( $matchArray as $matchItem )
00653         {
00654             switch ( $matchItem )
00655             {
00656                 case "login":
00657                 {
00658                     $match = ( $match | self::AUTHENTICATE_LOGIN );
00659                 } break;
00660                 case "email":
00661                 {
00662                     $match = ( $match | self::AUTHENTICATE_EMAIL );
00663                 } break;
00664             }
00665         }
00666         return $match;
00667     }
00668 
00669     /*!
00670      \return \c true if there can only be one instance of an email address on the site.
00671     */
00672     static function requireUniqueEmail()
00673     {
00674         $ini = eZINI::instance();
00675         return $ini->variable( 'UserSettings', 'RequireUniqueEmail' ) == 'true';
00676     }
00677 
00678     /**
00679      * Logs in the user if applied username and password is valid.
00680      *
00681      * @param string $login
00682      * @param string $password
00683      * @param bool $authenticationMatch
00684      * @return mixed eZUser on success, bool false on failure
00685      */
00686     public static function loginUser( $login, $password, $authenticationMatch = false )
00687     {
00688         $user = self::_loginUser( $login, $password, $authenticationMatch );
00689 
00690         if ( is_object( $user ) )
00691         {
00692             self::loginSucceeded( $user );
00693             return $user;
00694         }
00695         else
00696         {
00697             self::loginFailed( $user, $login );
00698             return false;
00699         }
00700     }
00701 
00702     /**
00703      * Does some house keeping work once a log in has succeeded.
00704      *
00705      * @param eZUser $user
00706      */
00707     protected static function loginSucceeded( $user )
00708     {
00709         $userID = $user->attribute( 'contentobject_id' );
00710 
00711         // if audit is enabled logins should be logged
00712         eZAudit::writeAudit( 'user-login', array( 'User id' => $userID, 'User login' => $user->attribute( 'login' ) ) );
00713 
00714         eZUser::updateLastVisit( $userID, true );
00715         eZUser::setCurrentlyLoggedInUser( $user, $userID );
00716 
00717         // Reset number of failed login attempts
00718         eZUser::setFailedLoginAttempts( $userID, 0 );
00719     }
00720 
00721     /**
00722      * Does some house keeping work when a log in has failed.
00723      *
00724      * @param mixed $userID
00725      * @param string $login
00726      */
00727      protected static function loginFailed( $userID = false, $login )
00728     {
00729         $loginEscaped = eZDB::instance()->escapeString( $login );
00730 
00731         // Failed login attempts should be logged
00732         eZAudit::writeAudit( 'user-failed-login', array( 'User login' => $loginEscaped,
00733                                                          'Comment' => 'Failed login attempt: eZUser::loginUser()' ) );
00734 
00735         // Increase number of failed login attempts.
00736         if ( $userID )
00737             eZUser::setFailedLoginAttempts( $userID );
00738     }
00739 
00740     /**
00741      * Logs in an user if applied login and password is valid.
00742      *
00743      * This method does not do any house keeping work anymore (writing audits, etc).
00744      * When you call this method make sure to call loginSucceeded() or loginFailed()
00745      * depending on the success of the login.
00746      *
00747      * @param string $login
00748      * @param string $password
00749      * @param bool $authenticationMatch
00750      * @return mixed eZUser object on log in success, int userID if the username
00751      *         exists but log in failed, or false if the username doesn't exists.
00752      */
00753     protected static function _loginUser( $login, $password, $authenticationMatch = false )
00754     {
00755         $http = eZHTTPTool::instance();
00756         $db = eZDB::instance();
00757 
00758         if ( $authenticationMatch === false )
00759             $authenticationMatch = eZUser::authenticationMatch();
00760 
00761         $loginEscaped = $db->escapeString( $login );
00762         $passwordEscaped = $db->escapeString( $password );
00763 
00764         $loginArray = array();
00765         if ( $authenticationMatch & self::AUTHENTICATE_LOGIN )
00766             $loginArray[] = "login='$loginEscaped'";
00767         if ( $authenticationMatch & self::AUTHENTICATE_EMAIL )
00768         {
00769             if ( eZMail::validate( $login ) )
00770             {
00771                 $loginArray[] = "email='$loginEscaped'";
00772             }
00773         }
00774         if ( empty( $loginArray ) )
00775             $loginArray[] = "login='$loginEscaped'";
00776         $loginText = implode( ' OR ', $loginArray );
00777 
00778         $contentObjectStatus = eZContentObject::STATUS_PUBLISHED;
00779 
00780         $ini = eZINI::instance();
00781         $databaseName = $db->databaseName();
00782         // if mysql
00783         if ( $databaseName === 'mysql' )
00784         {
00785             $query = "SELECT contentobject_id, password_hash, password_hash_type, email, login
00786                       FROM ezuser, ezcontentobject
00787                       WHERE ( $loginText ) AND
00788                         ezcontentobject.status='$contentObjectStatus' AND
00789                         ezcontentobject.id=contentobject_id AND
00790                         ( ( password_hash_type!=4 ) OR
00791                           ( password_hash_type=4 AND
00792                               ( $loginText ) AND
00793                           password_hash=PASSWORD('$passwordEscaped') ) )";
00794         }
00795         else
00796         {
00797             $query = "SELECT contentobject_id, password_hash,
00798                              password_hash_type, email, login
00799                       FROM   ezuser, ezcontentobject
00800                       WHERE  ( $loginText )
00801                       AND    ezcontentobject.status='$contentObjectStatus'
00802                       AND    ezcontentobject.id=contentobject_id";
00803         }
00804 
00805         $users = $db->arrayQuery( $query );
00806         $exists = false;
00807         if ( $users !== false && isset( $users[0] ) )
00808         {
00809             $ini = eZINI::instance();
00810             foreach ( $users as $userRow )
00811             {
00812                 $userID = $userRow['contentobject_id'];
00813                 $hashType = $userRow['password_hash_type'];
00814                 $hash = $userRow['password_hash'];
00815                 $exists = eZUser::authenticateHash( $userRow['login'], $password, eZUser::site(),
00816                                                     $hashType,
00817                                                     $hash );
00818 
00819                 // If hash type is MySql
00820                 if ( $hashType == self::PASSWORD_HASH_MYSQL and $databaseName === 'mysql' )
00821                 {
00822                     $queryMysqlUser = "SELECT contentobject_id, password_hash, password_hash_type, email, login
00823                               FROM ezuser, ezcontentobject
00824                               WHERE ezcontentobject.status='$contentObjectStatus' AND
00825                                     password_hash_type=4 AND ( $loginText ) AND password_hash=PASSWORD('$passwordEscaped') ";
00826                     $mysqlUsers = $db->arrayQuery( $queryMysqlUser );
00827                     if ( isset( $mysqlUsers[0] ) )
00828                         $exists = true;
00829 
00830                 }
00831 
00832                 eZDebugSetting::writeDebug( 'kernel-user', eZUser::createHash( $userRow['login'], $password, eZUser::site(),
00833                                                                                $hashType, $hash ), "check hash" );
00834                 eZDebugSetting::writeDebug( 'kernel-user', $hash, "stored hash" );
00835                  // If current user has been disabled after a few failed login attempts.
00836                 $canLogin = eZUser::isEnabledAfterFailedLogin( $userID );
00837 
00838                 if ( $exists )
00839                 {
00840                     // We should store userID for warning message.
00841                     $GLOBALS['eZFailedLoginAttemptUserID'] = $userID;
00842 
00843                     $userSetting = eZUserSetting::fetch( $userID );
00844                     $isEnabled = $userSetting->attribute( "is_enabled" );
00845                     if ( $hashType != eZUser::hashType() and
00846                          strtolower( $ini->variable( 'UserSettings', 'UpdateHash' ) ) == 'true' )
00847                     {
00848                         $hashType = eZUser::hashType();
00849                         $hash = eZUser::createHash( $userRow['login'], $password, eZUser::site(),
00850                                                     $hashType );
00851                         $db->query( "UPDATE ezuser SET password_hash='$hash', password_hash_type='$hashType' WHERE contentobject_id='$userID'" );
00852                     }
00853                     break;
00854                 }
00855             }
00856         }
00857 
00858         if ( $exists and $isEnabled and $canLogin )
00859         {
00860             return new eZUser( $userRow );
00861         }
00862         else
00863         {
00864             return isset( $userID ) ? $userID : false;
00865         }
00866     }
00867 
00868     /*!
00869      \static
00870      Checks if IP address of current user is in \a $ipList.
00871     */
00872     static function isUserIPInList( $ipList )
00873     {
00874         $ipAddress = eZSys::clientIP();
00875         if ( $ipAddress )
00876         {
00877             $result = false;
00878             foreach( $ipList as $itemToMatch )
00879             {
00880                 if ( preg_match("/^(([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+))(\/([0-9]+)$|$)/", $itemToMatch, $matches ) )
00881                 {
00882                     if ( $matches[6] )
00883                     {
00884                         if ( eZDebug::isIPInNet( $ipAddress, $matches[1], $matches[7] ) )
00885                         {
00886                             $result = true;
00887                             break;
00888                         }
00889                     }
00890                     else
00891                     {
00892                         if ( $matches[1] == $ipAddress )
00893                         {
00894                             $result = true;
00895                             break;
00896                         }
00897                     }
00898                 }
00899             }
00900         }
00901         else
00902         {
00903             $result = (
00904                 in_array( 'commandline', $ipList ) &&
00905                 ( php_sapi_name() == 'cli' )
00906             );
00907         }
00908         return $result;
00909     }
00910 
00911     /*!
00912      \static
00913       Returns true if current user is trusted user.
00914     */
00915     static function isTrusted()
00916     {
00917         $ini = eZINI::instance();
00918 
00919         // Check if current user is trusted user.
00920         $trustedIPs = $ini->hasVariable( 'UserSettings', 'TrustedIPList' ) ? $ini->variable( 'UserSettings', 'TrustedIPList' ) : array();
00921 
00922         // Check if IP address of current user is in $trustedIPs array.
00923         $trustedUser = eZUser::isUserIPInList( $trustedIPs );
00924         if ( $trustedUser )
00925             return true;
00926 
00927         return false;
00928     }
00929 
00930     /*!
00931      \static
00932      Returns max number of failed login attempts.
00933     */
00934     static function maxNumberOfFailedLogin()
00935     {
00936         $ini = eZINI::instance();
00937 
00938         $maxNumberOfFailedLogin = $ini->hasVariable( 'UserSettings', 'MaxNumberOfFailedLogin' ) ? $ini->variable( 'UserSettings', 'MaxNumberOfFailedLogin' ) : '0';
00939         return $maxNumberOfFailedLogin;
00940     }
00941 
00942     /*
00943      \static
00944      Returns true if the user can login
00945      If user has number of failed login attempts more than eZUser::maxNumberOfFailedLogin()
00946      and user is not trusted
00947      the user will not be allowed to login.
00948     */
00949     static function isEnabledAfterFailedLogin( $userID, $ignoreTrusted = false )
00950     {
00951         if ( !is_numeric( $userID ) )
00952             return true;
00953 
00954         $userObject = eZUser::fetch( $userID );
00955         if ( !$userObject )
00956             return true;
00957 
00958         $trustedUser = eZUser::isTrusted();
00959         // If user is trusted we should stop processing
00960         if ( $trustedUser and !$ignoreTrusted )
00961             return true;
00962 
00963         $maxNumberOfFailedLogin = eZUser::maxNumberOfFailedLogin();
00964 
00965         if ( $maxNumberOfFailedLogin == '0' )
00966             return true;
00967 
00968         $failedLoginAttempts = $userObject->failedLoginAttempts();
00969         if ( $failedLoginAttempts > $maxNumberOfFailedLogin )
00970             return false;
00971 
00972         return true;
00973     }
00974 
00975     /**
00976      * Makes sure the $user is set as the currently logged in user by
00977      * updating the session and setting the necessary global variables.
00978      *
00979      * All login handlers should use this function to ensure that the process
00980      * is executed properly.
00981      *
00982      * @access private
00983      *
00984      * @param eZUser $user User
00985      * @param int $userID User ID
00986      * @param int $flags Optional flag that can be set to:
00987      *        eZUser::NO_SESSION_REGENERATE to avoid session to be regenerated
00988      */
00989     static function setCurrentlyLoggedInUser( $user, $userID, $flags = 0 )
00990     {
00991         $GLOBALS["eZUserGlobalInstance_$userID"] = $user;
00992         // Set/overwrite the global user, this will be accessed from
00993         // instance() when there is no ID passed to the function.
00994         $GLOBALS["eZUserGlobalInstance_"] = $user;
00995         eZSession::setUserID( $userID );
00996 
00997         if ( !( $flags & self::NO_SESSION_REGENERATE) )
00998             eZSession::regenerate();
00999 
01000         eZSession::set( 'eZUserLoggedInID', $userID );
01001         self::cleanup();
01002     }
01003 
01004     /*!
01005      \virtual
01006      Used by login handler to clean up session variables
01007     */
01008     function sessionCleanup()
01009     {
01010     }
01011 
01012     /**
01013      * Cleanup user related session values, for use by login / logout code
01014      *
01015      * @internal
01016      */
01017     static function cleanup()
01018     {
01019         $http = eZHTTPTool::instance();
01020 
01021         $http->removeSessionVariable( 'CanInstantiateClassList' );
01022         $http->removeSessionVariable( 'ClassesCachedForUser' );
01023 
01024         // Note: This must be done more generic with an internal
01025         //       callback system.
01026         eZPreferences::sessionCleanup();
01027     }
01028 
01029     /*!
01030      \return logs in the current user object
01031     */
01032     function loginCurrent()
01033     {
01034         self::setCurrentlyLoggedInUser( $this, $this->ContentObjectID );
01035     }
01036 
01037     /*!
01038      \static
01039      Logs out the current user
01040     */
01041     static function logoutCurrent()
01042     {
01043         $http = eZHTTPTool::instance();
01044         $id = false;
01045         $GLOBALS["eZUserGlobalInstance_$id"] = false;
01046         $contentObjectID = $http->sessionVariable( 'eZUserLoggedInID' );
01047 
01048         // reset session data
01049         $newUserID = self::anonymousId();
01050         eZSession::setUserID( $newUserID );
01051         $http->setSessionVariable( 'eZUserLoggedInID', $newUserID );
01052 
01053         // Clear current basket if necessary
01054         $db = eZDB::instance();
01055         $db->begin();
01056         eZBasket::cleanupCurrentBasket();
01057         $db->commit();
01058 
01059         if ( $contentObjectID )
01060             self::cleanup();
01061 
01062         // give user new session id
01063         eZSession::regenerate();
01064 
01065         // set the property used to prevent SSO from running again
01066         self::$userHasLoggedOut = true;
01067     }
01068 
01069     /**
01070      * Returns a shared instance of the eZUser class pr $id value.
01071      * If user can not be fetched, then anonymous user is returned and
01072      * a warning trown, if anonymous user can not be fetched, then NoUser
01073      * is returned and another warning is thrown.
01074      *
01075      * @param int|false $id On false: Gets current user id from session
01076      *        or from {@link eZUser::anonymousId()} if not set.
01077      * @return eZUser
01078      */
01079     static function instance( $id = false )
01080     {
01081         if ( !empty( $GLOBALS["eZUserGlobalInstance_$id"] ) )
01082         {
01083             return $GLOBALS["eZUserGlobalInstance_$id"];
01084         }
01085 
01086         $userId = $id;
01087         $currentUser = null;
01088         $http = eZHTTPTool::instance();
01089         $anonymousUserID = self::anonymousId();
01090         $sessionHasStarted = eZSession::hasStarted();
01091         // If not specified get the current user
01092         if ( $userId === false )
01093         {
01094             if ( $sessionHasStarted )
01095             {
01096                 $userId = $http->sessionVariable( 'eZUserLoggedInID' );
01097                 if ( !is_numeric( $userId ) )
01098                 {
01099                     $userId = $anonymousUserID;
01100                     eZSession::setUserID( $userId );
01101                     $http->setSessionVariable( 'eZUserLoggedInID', $userId );
01102                 }
01103             }
01104             else
01105             {
01106                 $userId = $anonymousUserID;
01107                 eZSession::setUserID( $userId );
01108             }
01109         }
01110 
01111         // Check user cache (this effectivly fetches user from cache)
01112         // user not found if !isset( isset( $userCache['info'][$userId] ) )
01113         $userCache = self::getUserCacheByUserId( $userId );
01114         if ( isset( $userCache['info'][$userId] ) )
01115         {
01116             $userArray = $userCache['info'][$userId];
01117             if ( is_numeric( $userArray['contentobject_id'] ) )
01118             {
01119                 $currentUser = new eZUser( $userArray );
01120                 $currentUser->setUserCache( $userCache );
01121             }
01122         }
01123 
01124         $ini = eZINI::instance();
01125         // Check if:
01126         // - the user has not logged out,
01127         // - the user is not logged in,
01128         // - and if a automatic single sign on plugin is enabled.
01129         if ( !self::$userHasLoggedOut and is_object( $currentUser ) and !$currentUser->isLoggedIn() )
01130         {
01131             $ssoHandlerArray = $ini->variable( 'UserSettings', 'SingleSignOnHandlerArray' );
01132             if ( !empty( $ssoHandlerArray ) )
01133             {
01134                 $ssoUser = false;
01135                 foreach ( $ssoHandlerArray as $ssoHandler )
01136                 {
01137                     $className = 'eZ' . $ssoHandler . 'SSOHandler';
01138                     if( class_exists( $className ) )
01139                     {
01140                         $impl = new $className();
01141                         $ssoUser = $impl->handleSSOLogin();
01142                         // If a user was found via SSO, then use it
01143                         if ( $ssoUser !== false )
01144                         {
01145                             $currentUser = $ssoUser;
01146                             $userId = $currentUser->attribute( 'contentobject_id' );
01147 
01148                             $userInfo = array();
01149                             $userInfo[$userId] = array( 
01150                                 'contentobject_id' => $userId,
01151                                 'login' => $currentUser->attribute( 'login' ),
01152                                 'email' => $currentUser->attribute( 'email' ),
01153                                 'password_hash' => $currentUser->attribute( 'password_hash' ),
01154                                 'password_hash_type' => $currentUser->attribute( 'password_hash_type' )
01155                             );
01156                             eZSession::setUserID( $userId );
01157                             $http->setSessionVariable( 'eZUserLoggedInID', $userId );
01158 
01159                             eZUser::updateLastVisit( $userId );
01160                             eZUser::setCurrentlyLoggedInUser( $currentUser, $userId );
01161                             eZHTTPTool::redirect( eZSys::wwwDir() . eZSys::indexFile( false ) . eZSys::requestURI(), array(), 302 );
01162                             eZExecution::cleanExit();
01163                         }
01164                     }
01165                     else
01166                     {
01167                         eZDebug::writeError( "Undefined ssoHandler class: $className", __METHOD__ );
01168                     }
01169                 }                
01170             }
01171         }
01172 
01173         if ( $userId <> $anonymousUserID )
01174         {
01175             $sessionInactivityTimeout = $ini->variable( 'Session', 'ActivityTimeout' );
01176             if ( !isset( $GLOBALS['eZSessionIdleTime'] ) )
01177             {
01178                 eZUser::updateLastVisit( $userId );
01179             }
01180             else
01181             {
01182                 $sessionIdle = $GLOBALS['eZSessionIdleTime'];
01183                 if ( $sessionIdle > $sessionInactivityTimeout )
01184                 {
01185                     eZUser::updateLastVisit( $userId );
01186                 }
01187             }
01188         }
01189 
01190         if ( !$currentUser )
01191         {
01192             $currentUser = eZUser::fetch( self::anonymousId() );
01193             eZDebug::writeWarning( 'User not found, returning anonymous' );
01194         }
01195 
01196         if ( !$currentUser )
01197         {
01198             $currentUser = new eZUser( array( 'id' => -1, 'login' => 'NoUser' ) );
01199             eZDebug::writeWarning( 'Anonymous user not found, returning NoUser' );
01200         }
01201 
01202         $GLOBALS["eZUserGlobalInstance_$id"] = $currentUser;
01203         return $currentUser;
01204     }
01205 
01206     /**
01207      * Get User cache from cache file
01208      *
01209      * @since 4.4
01210      * @return array( 'info' => array, 'groups' => array, 'roles' => array, 'role_limitations' => array, 'access_array' => array)
01211      */
01212     public function getUserCache()
01213     {
01214         if ( $this->UserCache === null )
01215         {
01216             $this->setUserCache( self::getUserCacheByUserId( $this->ContentObjectID ) );
01217         }
01218         return $this->UserCache;
01219     }
01220 
01221     /**
01222      * Delete User cache from locale var and cache file for current user.
01223      *
01224      * @since 4.4
01225      */
01226     public function purgeUserCache()
01227     {
01228         $this->UserCache = null;
01229         self::purgeUserCacheByUserId( $this->ContentObjectID );
01230     }
01231 
01232     /**
01233      * Set User cache from cache file
01234      * Needs to be in excact same format as {@link eZUser::getUserCache()}!
01235      *
01236      * @since 4.4
01237      * @param array $userCache
01238      */
01239     public function setUserCache( array $userCache )
01240     {
01241         $this->UserCache = $userCache;
01242     }
01243 
01244     /**
01245      * Delete User cache from cache file for Anonymous user(usefull for sessionless users)
01246      *
01247      * @since 4.4
01248      * @see eZUser::purgeUserCacheByUserId()
01249      */
01250     static public function purgeUserCacheByAnonymousId()
01251     {
01252         self::purgeUserCacheByUserId( self::anonymousId() );
01253     }
01254 
01255     /**
01256      * Delete User cache pr user
01257      *
01258      * @since 4.4
01259      * @param int $userId
01260      */
01261     static public function purgeUserCacheByUserId( $userId )
01262     {
01263         $cacheFilePath = eZUser::getCacheDir( $userId ). "/user-data-{$userId}.cache.php" ;
01264         eZClusterFileHandler::instance()->fileDelete( $cacheFilePath );
01265     }
01266 
01267     /**
01268      * Get User cache from cache file for Anonymous user(usefull for sessionless users)
01269      *
01270      * @since 4.4
01271      * @see eZUser::getUserCacheByUserId()
01272      * @return array
01273      */
01274     static public function getUserCacheByAnonymousId()
01275     {
01276         return self::getUserCacheByUserId( self::anonymousId() );
01277     }
01278 
01279     /**
01280      * Get User cache from cache file (usefull for sessionless users)
01281      *
01282      * @since 4.4
01283      * @see eZUser::getUserCache()
01284      * @param int $userId
01285      * @return array
01286      */
01287     static protected function getUserCacheByUserId( $userId )
01288     {
01289         $cacheFilePath = eZUser::getCacheDir( $userId ). "/user-data-{$userId}.cache.php" ;
01290         $cacheFile = eZClusterFileHandler::instance( $cacheFilePath );
01291         return $cacheFile->processCache( array( 'eZUser', 'retrieveUserCacheFromFile' ),
01292                                          array( 'eZUser', 'generateUserCacheForFile' ),
01293                                          null,
01294                                          self::userInfoExpiry(),
01295                                          $userId );
01296     }
01297 
01298     /**
01299      * Callback which fetches user cache from local file.
01300      *
01301      * @internal
01302      * @since 4.4
01303      * @see eZUser::getUserCacheByUserId()
01304      */
01305     static function retrieveUserCacheFromFile( $filePath, $mtime, $userId )
01306     {
01307         return include( $filePath );
01308     }
01309 
01310     /**
01311      * Callback which generates user cache for user
01312      *
01313      * @internal
01314      * @since 4.4
01315      * @see eZUser::getUserCacheByUserId()
01316      */
01317     static function generateUserCacheForFile( $filePath, $userId )
01318     {
01319         $data = array( 'info' => array(),
01320                        'groups' => array(),
01321                        'roles' => array(),
01322                        'role_limitations' => array(),
01323                        'access_array' => array(),
01324                        'discount_rules' => array() );
01325         $user = eZUser::fetch( $userId );
01326         if ( $user instanceOf eZUser )
01327         {
01328             // user info (session: eZUserInfoCache)
01329             $data['info'][$userId] = array( 'contentobject_id'   => $user->attribute( 'contentobject_id' ),
01330                                             'login'              => $user->attribute( 'login' ),
01331                                             'email'              => $user->attribute( 'email' ),
01332                                             'password_hash'      => $user->attribute( 'password_hash' ),
01333                                             'password_hash_type' => $user->attribute( 'password_hash_type' ) );
01334 
01335             // user groups list (session: eZUserGroupsCache)
01336             $groups = $user->generateGroupIdList();
01337             $data['groups'] = $groups;
01338 
01339             // role list (session: eZRoleIDList)
01340             $groups[] = $userId;
01341             $data['roles'] = eZRole::fetchIDListByUser( $groups );
01342 
01343             // role limitation list (session: eZRoleLimitationValueList)
01344             $limitList = $user->limitList( false );
01345             foreach ( $limitList as $limit )
01346             {
01347                 $data['role_limitations'][] = $limit['limit_value'];
01348             }
01349 
01350             // access array (session: AccessArray)
01351             $data['access_array'] = $user->generateAccessArray();
01352 
01353             // discount rules (session: eZUserDiscountRules<userId>)
01354             $data['discount_rules'] = eZUserDiscountRule::generateIDListByUserID( $userId );
01355         }
01356         return array( 'content'  => $data,
01357                       'scope'    => 'user-info-cache',
01358                       'datatype' => 'php',
01359                       'store'    => true );
01360     }
01361 
01362     /*!
01363        Updates the user's last visit timestamp
01364        Optionally updates user login count by setting $updateLoginCount to true
01365     */
01366     static function updateLastVisit( $userID, $updateLoginCount = false )
01367     {
01368         if ( isset( $GLOBALS['eZUserUpdatedLastVisit'] ) )
01369             return;
01370         $db = eZDB::instance();
01371         $userID = (int) $userID;
01372         $userVisitArray = $db->arrayQuery( "SELECT 1 FROM ezuservisit WHERE user_id=$userID" );
01373         $time = time();
01374 
01375         if ( isset( $userVisitArray[0] ) )
01376         {
01377             $loginCountSQL = $updateLoginCount ? ', login_count=login_count+1' : '';
01378             $db->query( "UPDATE ezuservisit SET last_visit_timestamp=current_visit_timestamp, current_visit_timestamp=$time$loginCountSQL WHERE user_id=$userID" );
01379         }
01380         else
01381         {
01382             $intialLoginCount = $updateLoginCount ? 1 : 0;
01383             $db->query( "INSERT INTO ezuservisit ( current_visit_timestamp, last_visit_timestamp, user_id, login_count ) VALUES ( $time, $time, $userID, $intialLoginCount )" );
01384         }
01385         $GLOBALS['eZUserUpdatedLastVisit'] = true;
01386     }
01387 
01388     /*!
01389       Returns the last visit timestamp to the current user.
01390     */
01391     function lastVisit()
01392     {
01393         $db = eZDB::instance();
01394 
01395         $userVisitArray = $db->arrayQuery( "SELECT last_visit_timestamp FROM ezuservisit WHERE user_id=$this->ContentObjectID" );
01396         if ( isset( $userVisitArray[0] ) )
01397         {
01398             return $userVisitArray[0]['last_visit_timestamp'];
01399         }
01400         else
01401         {
01402             return time();
01403         }
01404     }
01405 
01406     /**
01407      * Returns the login count for the current user.
01408      *
01409      * @since Version 4.1
01410      * @return int Login count for current user.
01411      */
01412     function loginCount()
01413     {
01414         $db = eZDB::instance();
01415         $userVisitArray = $db->arrayQuery( "SELECT login_count FROM ezuservisit WHERE user_id=$this->ContentObjectID" );
01416         if ( isset( $userVisitArray[0] ) )
01417         {
01418             return $userVisitArray[0]['login_count'];
01419         }
01420         else
01421         {
01422             return 0;
01423         }
01424     }
01425 
01426     /*!
01427        If \a $value is false will increase the user's number of failed login attempts
01428        otherwise failed_login_attempts will be updated by $value.
01429        \a $setByForce if true checking for trusting or max number of failed login attempts will be ignored.
01430     */
01431     static function setFailedLoginAttempts( $userID, $value = false, $setByForce = false )
01432     {
01433         $trustedUser = eZUser::isTrusted();
01434         // If user is trusted we should stop processing
01435         if ( $trustedUser and !$setByForce )
01436             return true;
01437 
01438         $maxNumberOfFailedLogin = eZUser::maxNumberOfFailedLogin();
01439 
01440         if ( $maxNumberOfFailedLogin == '0' and !$setByForce )
01441             return true;
01442 
01443         $userID = (int) $userID;
01444         $userObject = eZUser::fetch( $userID );
01445         if ( !$userObject )
01446             return true;
01447 
01448         $isEnabled = $userObject->isEnabled();
01449         // If current user is disabled we should not continue
01450         if ( !$isEnabled and !$setByForce )
01451             return true;
01452 
01453         $db = eZDB::instance();
01454         $db->begin();
01455 
01456         $userVisitArray = $db->arrayQuery( "SELECT 1 FROM ezuservisit WHERE user_id=$userID" );
01457 
01458         if ( isset( $userVisitArray[0] ) )
01459         {
01460             if ( $value === false )
01461             {
01462                 $failedLoginAttempts = $userObject->failedLoginAttempts();
01463                 $failedLoginAttempts += 1;
01464             }
01465             else
01466                 $failedLoginAttempts = (int) $value;
01467 
01468             $db->query( "UPDATE ezuservisit SET failed_login_attempts=$failedLoginAttempts WHERE user_id=$userID" );
01469         }
01470         else
01471         {
01472             if ( $value === false )
01473             {
01474                 $failedLoginAttempts = 1;
01475             }
01476             else
01477                 $failedLoginAttempts = (int) $value;
01478 
01479             $db->query( "INSERT INTO ezuservisit ( failed_login_attempts, user_id ) VALUES ( $failedLoginAttempts, $userID )" );
01480         }
01481         $db->commit();
01482 
01483         eZContentCacheManager::clearContentCacheIfNeeded( $userID );
01484         eZContentCacheManager::generateObjectViewCache( $userID );
01485     }
01486 
01487     /*!
01488       Returns the current user's number of failed login attempts.
01489     */
01490     function failedLoginAttempts()
01491     {
01492         return eZUser::failedLoginAttemptsByUserID( $this->attribute( 'contentobject_id' ) );
01493     }
01494 
01495     /*!
01496       Returns the current user's number of failed login attempts.
01497     */
01498     static function failedLoginAttemptsByUserID( $userID )
01499     {
01500         $db = eZDB::instance();
01501         $contentObjectID = (int) $userID;
01502 
01503         $userVisitArray = $db->arrayQuery( "SELECT failed_login_attempts FROM ezuservisit WHERE user_id=$contentObjectID" );
01504 
01505         $failedLoginAttempts = isset( $userVisitArray[0] ) ? $userVisitArray[0]['failed_login_attempts'] : 0;
01506         return $failedLoginAttempts;
01507     }
01508 
01509     /*!
01510      \return \c true if the user is locked (is enabled after failed login) and can be logged on the site.
01511     */
01512     function isLocked()
01513     {
01514         $userID = $this->attribute( 'contentobject_id' );
01515         $isNotLocked = eZUser::isEnabledAfterFailedLogin( $userID, true );
01516         return !$isNotLocked;
01517     }
01518 
01519     /*!
01520      \return \c true if the user is enabled and can be used on the site.
01521     */
01522     function isEnabled()
01523     {
01524         if ( $this == eZUser::currentUser() )
01525         {
01526             return true;
01527         }
01528 
01529         $setting = eZUserSetting::fetch( $this->attribute( 'contentobject_id' ) );
01530         if ( $setting and !$setting->attribute( 'is_enabled' ) )
01531         {
01532             return false;
01533         }
01534         return true;
01535     }
01536 
01537     /*!
01538      \return \c true if the user is the anonymous user.
01539     */
01540     function isAnonymous()
01541     {
01542         if ( $this->attribute( 'contentobject_id' ) != self::anonymousId() )
01543         {
01544             return false;
01545         }
01546         return true;
01547     }
01548 
01549     /*!
01550      \static
01551      Returns the currently logged in user.
01552     */
01553     static function currentUser()
01554     {
01555         $user = eZUser::instance();
01556         return $user;
01557     }
01558 
01559     /*!
01560      \static
01561      Returns the ID of the currently logged in user.
01562     */
01563     static function currentUserID()
01564     {
01565         $user = eZUser::instance();
01566         if ( !$user )
01567             return 0;
01568         return $user->attribute( 'contentobject_id' );
01569     }
01570 
01571     /*!
01572      \static
01573      Creates a hash out of \a $user, \a $password and \a $site according to the type \a $type.
01574      \return true if the generated hash is equal to the supplied hash \a $hash.
01575     */
01576     static function authenticateHash( $user, $password, $site, $type, $hash )
01577     {
01578         return eZUser::createHash( $user, $password, $site, $type, $hash ) === (string) $hash;
01579     }
01580 
01581     /*!
01582      \static
01583      \return an array with characters which are allowed in password.
01584     */
01585     static function passwordCharacterTable()
01586     {
01587         if ( !empty( $GLOBALS['eZUserPasswordCharacterTable'] ) )
01588         {
01589             return $GLOBALS['eZUserPasswordCharacterTable'];
01590         }
01591         $table = array_merge( range( 'a', 'z' ), range( 'A', 'Z' ), range( 0, 9 ) );
01592 
01593         $ini = eZINI::instance();
01594         if ( $ini->variable( 'UserSettings', 'UseSpecialCharacters' ) == 'true' )
01595         {
01596             $specialCharacters = '!#%&{[]}+?;:*';
01597             $table = array_merge( $table, preg_split( '//', $specialCharacters, -1, PREG_SPLIT_NO_EMPTY ) );
01598         }
01599         // Remove some characters that are too similar visually
01600         $table = array_diff( $table, array( 'I', 'l', 'o', 'O', '0' ) );
01601         $tableTmp = $table;
01602         $table = array();
01603         foreach ( $tableTmp as $item )
01604         {
01605             $table[] = $item;
01606         }
01607 
01608         return $GLOBALS['eZUserPasswordCharacterTable'] = $table;
01609     }
01610 
01611     /*!
01612      Checks if the supplied content object is a user object ( contains ezuser datatype )
01613 
01614      \param ContentObject
01615 
01616      \return true or false
01617     */
01618     static function isUserObject( $contentObject )
01619     {
01620         if ( !$contentObject )
01621         {
01622             return false;
01623         }
01624 
01625         eZDataType::loadAndRegisterType( 'ezuser' );
01626 
01627         $contentClass = $contentObject->attribute( 'content_class' );
01628         $classAttributeList = $contentClass->fetchAttributes();
01629         foreach( $classAttributeList as $classAttribute )
01630         {
01631             if ( $classAttribute->attribute( 'data_type_string' ) == eZUserType::DATA_TYPE_STRING )
01632                 return true;
01633         }
01634 
01635         return false;
01636     }
01637 
01638     /*!
01639      \static
01640      Creates a password with number of characters equal to \a $passwordLength and returns it.
01641      If you want pass a value in \a $seed it will be used as basis for the password, if not
01642      it will use the current time value as seed.
01643      \note If \a $passwordLength exceeds 16 it will need to generate new seed for the remaining
01644            characters.
01645     */
01646     static function createPassword( $passwordLength, $seed = false )
01647     {
01648         $chars = 0;
01649         $password = '';
01650         if ( $passwordLength < 1 )
01651             $passwordLength = 1;
01652         $decimal = 0;
01653         while ( $chars < $passwordLength )
01654         {
01655             if ( $seed == false )
01656                 $seed = time() . ":" . mt_rand();
01657             $text = md5( $seed );
01658             $characterTable = eZUser::passwordCharacterTable();
01659             $tableCount = count( $characterTable );
01660             for ( $i = 0; ( $chars < $passwordLength ) and $i < 32; ++$chars, $i += 2 )
01661             {
01662                 $decimal += hexdec( substr( $text, $i, 2 ) );
01663                 $index = ( $decimal % $tableCount );
01664                 $character = $characterTable[$index];
01665                 $password .= $character;
01666             }
01667             $seed = false;
01668         }
01669         return $password;
01670     }
01671 
01672     /*!
01673      \static
01674      Will create a hash of the given string. This is used to store the passwords in the database.
01675     */
01676     static function createHash( $user, $password, $site, $type, $hash = false )
01677     {
01678         $str = '';
01679         if( $type == self::PASSWORD_HASH_MD5_USER )
01680         {
01681             $str = md5( "$user\n$password" );
01682         }
01683         else if ( $type == self::PASSWORD_HASH_MD5_SITE )
01684         {
01685             $str = md5( "$user\n$password\n$site" );
01686         }
01687         else if ( $type == self::PASSWORD_HASH_MYSQL )
01688         {
01689             $db = eZDB::instance();
01690             $hash = $db->escapeString( $password );
01691 
01692             $str = $db->arrayQuery( "SELECT PASSWORD( '$hash' )" );
01693             $hashes = array_values( $str[0] );
01694             $str = $hashes[0];
01695         }
01696         else if ( $type == self::PASSWORD_HASH_PLAINTEXT )
01697         {
01698             $str = $password;
01699         }
01700         else if ( $type == self::PASSWORD_HASH_CRYPT )
01701         {
01702             if ( $hash )
01703             {
01704                 $str = crypt( $password, $hash );
01705             }
01706             else
01707             {
01708                 $str = crypt( $password );
01709             }
01710         }
01711         else // self::PASSWORD_HASH_MD5_PASSWORD
01712         {
01713             $str = md5( $password );
01714         }
01715         eZDebugSetting::writeDebug( 'kernel-user', $str, "ezuser($type)" );
01716         return $str;
01717     }
01718 
01719     /*!
01720      Check if user has got access to the specified module and function
01721 
01722      \param module name
01723      \param funtion name
01724 
01725      \return Array containg result.
01726              Array elements : 'accessWord', yes - access allowed
01727                                             no - access denied
01728                                             limited - access array describing access included
01729                               'policies', array containing the policy limitations
01730                               'accessList', array describing missing access rights
01731     */
01732     function hasAccessTo( $module, $function = false )
01733     {
01734         $accessArray = $this->accessArray();
01735 
01736         $functionArray = array();
01737         if ( isset( $accessArray['*']['*'] ) )
01738         {
01739             $functionArray = $accessArray['*']['*'];
01740         }
01741         if ( isset( $accessArray[$module] ) )
01742         {
01743             if ( isset( $accessArray[$module]['*'] ) )
01744             {
01745                 $functionArray = array_merge_recursive( $functionArray, $accessArray[$module]['*'] );
01746             }
01747             if ( $function and isset( $accessArray[$module][$function] ) and $function != '*' )
01748             {
01749                 $functionArray = array_merge_recursive( $functionArray, $accessArray[$module][$function] );
01750             }
01751         }
01752 
01753         if ( !$functionArray )
01754         {
01755             return array( 'accessWord' => 'no',
01756                           'accessList' => array( 'FunctionRequired' => array ( 'Module' => $module,
01757                                                                                'Function' => $function,
01758                                                                                'ClassID' => '',
01759                                                                                'MainNodeID' => '' ),
01760                                                  'PolicyList' => array() )
01761                           );
01762         }
01763 
01764         if ( isset( $functionArray['*'] ) &&
01765                   ( $functionArray['*'] == '*' || in_array( '*',  $functionArray['*'] ) ) )
01766         {
01767             return array( 'accessWord' => 'yes' );
01768         }
01769 
01770         return array( 'accessWord' => 'limited', 'policies' => $functionArray );
01771     }
01772 
01773     /**
01774      * Returns either cached or newly generated accessArray for the user depending on
01775      * site.ini\[RoleSettings]\EnableCaching setting
01776      *
01777      * @access private
01778      * @return array
01779      */
01780     function accessArray()
01781     {
01782         if ( !isset( $this->AccessArray ) )
01783         {
01784             if ( eZINI::instance()->variable( 'RoleSettings', 'EnableCaching' ) === 'true' )
01785             {
01786                 $userCache = $this->getUserCache();
01787                 $this->AccessArray = $userCache['access_array'];
01788             }
01789             else
01790             {
01791                 // if role caching is disabled then generate access array on-the-fly.
01792                 $this->AccessArray = $this->generateAccessArray();
01793             }
01794         }
01795         return $this->AccessArray;
01796     }
01797 
01798     /**
01799      * Generates the accessArray for the user (for $this).
01800      * This function is uncached, and used as basis for user cache callback.
01801      *
01802      * @internal
01803      * @return array
01804      */
01805     function generateAccessArray()
01806     {
01807         $idList = $this->generateGroupIdList();
01808         $idList[] = $this->attribute( 'contentobject_id' );
01809 
01810         return eZRole::accessArrayByUserID( $idList );
01811     }
01812 
01813     /*!
01814      \private
01815      \static
01816      Callback which figures out global expiry and returns it.
01817      */
01818     static protected function userInfoExpiry()
01819     {
01820         /* Figure out when the last update was done */
01821         eZExpiryHandler::registerShutdownFunction();
01822         $handler = eZExpiryHandler::instance();
01823         if ( $handler->hasTimestamp( 'user-info-cache' ) )
01824         {
01825             $expiredTimestamp = $handler->timestamp( 'user-info-cache' );
01826         }
01827         else
01828         {
01829             $expiredTimestamp = time();
01830             $handler->setTimestamp( 'user-info-cache', $expiredTimestamp );
01831         }
01832 
01833         return $expiredTimestamp;
01834     }
01835 
01836     /*
01837      Returns list of sections which are allowed to assign to the given content object by the user.
01838     */
01839     function canAssignToObjectSectionList( $contentObject )
01840     {
01841         $access = $this->hasAccessTo( 'section', 'assign' );
01842 
01843         if ( $access['accessWord'] == 'yes' )
01844         {
01845             return array( '*' );
01846         }
01847         else if ( $access['accessWord'] == 'limited' )
01848         {
01849             $userID = $this->attribute( 'contentobject_id' );
01850             $classID = $contentObject->attribute( 'contentclass_id' );
01851             $ownerID = $contentObject->attribute( 'owner_id' );
01852             $sectionID = $contentObject->attribute( 'section_id' );
01853 
01854             $allowedSectionIDList = array();
01855             foreach ( $access['policies'] as $policy )
01856             {
01857                 if ( ( isset( $policy['Class'] ) and !in_array( $classID, $policy['Class'] ) ) or
01858                      ( isset( $policy['Owner']  ) and in_array( 1, $policy['Owner'] ) and $userID != $ownerID ) or
01859                      ( isset( $policy['Section'] ) and !in_array( $sectionID, $policy['Section'] ) ) )
01860                 {
01861                     continue;
01862                 }
01863                 if ( isset( $policy['NewSection'] ) && !empty( $policy['NewSection'] ) )
01864                 {
01865                     $allowedSectionIDList = array_merge( $allowedSectionIDList, $policy['NewSection'] );
01866                 }
01867                 else
01868                 {
01869                     return array( '*' );
01870                 }
01871             }
01872             $allowedSectionIDList = array_unique( $allowedSectionIDList );
01873             return $allowedSectionIDList;
01874         }
01875         return array();
01876     }
01877 
01878     /*
01879      Checks whether user can assign the section to the given content object or not.
01880     */
01881     function canAssignSectionToObject( $checkSectionID, $contentObject )
01882     {
01883         $access = $this->hasAccessTo( 'section', 'assign' );
01884 
01885         if ( $access['accessWord'] == 'yes' )
01886         {
01887             return true;
01888         }
01889         else if ( $access['accessWord'] == 'limited' )
01890         {
01891             $hasSubtreeLimitation = false;
01892             $subtreeLimitationResult = false;
01893 
01894             // Trying first to discover if a subtree limitation exists.
01895             // Only the main node of the object is considered!
01896             foreach ( $access['policies'] as $policy )
01897             {
01898                 if ( isset( $policy['User_Subtree'] ) )
01899                 {
01900                     $hasSubtreeLimitation = true;
01901                     $nodePath = $contentObject->mainNode()->PathString;
01902 
01903                     foreach ( $policy['User_Subtree'] as $path )
01904                     {
01905                         if ( strpos( $nodePath, $path ) !== false )
01906                         {
01907                             $subtreeLimitationResult = true;
01908                         }
01909                     }
01910                     continue;
01911                 }
01912             }
01913 
01914             // If a subtree limitation exists and none of the path corresponds then the user have not enough rights.
01915             if ( $hasSubtreeLimitation && !$subtreeLimitationResult )
01916             {
01917                 return false;
01918             }
01919 
01920             unset( $hasSubtreeLimitation, $subtreeLimitationResult, $nodePath );
01921 
01922             $userID = $this->attribute( 'contentobject_id' );
01923             $classID = $contentObject->attribute( 'contentclass_id' );
01924             $ownerID = $contentObject->attribute( 'owner_id' );
01925             $sectionID = $contentObject->attribute( 'section_id' );
01926 
01927             foreach ( $access['policies'] as $policy )
01928             {
01929                 if ( ( isset( $policy['Class'] ) and !in_array( $classID, $policy['Class'] ) ) or
01930                      ( isset( $policy['Owner']  ) and in_array( 1, $policy['Owner'] ) and $userID != $ownerID ) or
01931                      ( isset( $policy['Section'] ) and !in_array( $sectionID, $policy['Section'] ) ) )
01932                 {
01933                     continue;
01934                 }
01935                 if ( isset( $policy['NewSection'] ) )
01936                 {
01937                     if ( is_array( $policy['NewSection'] ) and in_array( $checkSectionID, $policy['NewSection'] ) )
01938                     {
01939                         return true;
01940                     }
01941                 }
01942                 else
01943                 {
01944                     return true;
01945                 }
01946             }
01947         }
01948         return false;
01949     }
01950 
01951     /*
01952      Checks whether user has privileges to assign the section or not at all.
01953     */
01954     function canAssignSection( $checkSectionID )
01955     {
01956         $access = $this->hasAccessTo( 'section', 'assign' );
01957 
01958         if ( $access['accessWord'] == 'yes' )
01959         {
01960             return true;
01961         }
01962         else if ( $access['accessWord'] == 'limited' )
01963         {
01964             foreach ( $access['policies'] as $policy )
01965             {
01966                 if ( isset( $policy['NewSection'] ) )
01967                 {
01968                     if ( in_array( $checkSectionID, $policy['NewSection'] ) )
01969                     {
01970                         return true;
01971                     }
01972                 }
01973                 else
01974                 {
01975                     return true;
01976                 }
01977             }
01978         }
01979         return false;
01980     }
01981 
01982     /*
01983      Returns list of sections allowed to assign for the user.
01984     */
01985     function canAssignSectionList()
01986     {
01987         $access = $this->hasAccessTo( 'section', 'assign' );
01988 
01989         if ( $access['accessWord'] == 'yes' )
01990         {
01991             return array( '*' );
01992         }
01993         else if ( $access['accessWord'] == 'limited' )
01994         {
01995             $allowedSectionIDList = array();
01996             foreach ( $access['policies'] as $policy )
01997             {
01998                 if ( isset( $policy['NewSection'] ) )
01999                 {
02000                     if ( is_array( $policy['NewSection'] ) && !empty( $policy['NewSection'] ) )
02001                     {
02002                         $allowedSectionIDList = array_merge( $allowedSectionIDList, $policy['NewSection'] );
02003                     }
02004                 }
02005                 else
02006                 {
02007                     return array( '*' );
02008                 }
02009             }
02010             $allowedSectionIDList = array_unique( $allowedSectionIDList );
02011             return $allowedSectionIDList;
02012         }
02013         return array();
02014     }
02015 
02016     /*
02017      Returns list of classes allowed to assign to the given section for the user.
02018     */
02019     function canAssignSectionToClassList( $checkSectionID )
02020     {
02021         $access = $this->hasAccessTo( 'section', 'assign' );
02022 
02023         if ( $access['accessWord'] == 'yes' )
02024         {
02025             return array( '*' );
02026         }
02027         else if ( $access['accessWord'] == 'limited' )
02028         {
02029             $allowedClassList = array();
02030             foreach ( $access['policies'] as $policy )
02031             {
02032                 if ( !isset( $policy['NewSection'] ) or in_array( $checkSectionID, $policy['NewSection'] ) )
02033                 {
02034                     if ( isset( $policy['Class'] ) )
02035                     {
02036                         $allowedClassList = array_merge( $allowedClassList, $policy['Class'] );
02037                     }
02038                     else
02039                     {
02040                         return array( '*' );
02041                     }
02042                 }
02043             }
02044 
02045             if ( !empty( $allowedClassList ) )
02046             {
02047                 // Now we are trying to fetch classes by collected ids list to return
02048                 // class list consisting of existing classes's identifiers only.
02049                 $allowedClassList = array_unique( $allowedClassList );
02050                 $classList = eZContentClass::fetchList( eZContentClass::VERSION_STATUS_DEFINED, false, false, null, null, $allowedClassList );
02051                 if ( is_array( $classList ) && !empty( $classList ) )
02052                 {
02053                     $classIdentifierList = array();
02054                     foreach( $classList as $class )
02055                     {
02056                         $classIdentifierList[] = $class['identifier'];
02057                     }
02058                     return $classIdentifierList;
02059                 }
02060             }
02061         }
02062         return array();
02063     }
02064 
02065     /*
02066      Evaluates if $this user has access to the view $viewName based on its policy functions and
02067      checks if the assigned to the view functions expression is valid and handles errors if it is not.
02068      Returns true if access is allowed, false if access is denied.
02069     */
02070     function hasAccessToView( $module, $viewName, &$params )
02071     {
02072         $validView = false;
02073         $accessAllowed = false;
02074         if ( $module->singleFunction() )
02075         {
02076             $info = $module->attribute( 'info' );
02077             if ( isset( $info['function'] ) )
02078             {
02079                 $validView = true;
02080                 if ( isset( $info['function']['functions'] ) && !empty( $info['function']['functions'] ) )
02081                 {
02082                     $functions = $info['function']['functions'];
02083                 }
02084             }
02085         }
02086         else
02087         {
02088             $views = $module->attribute( 'views' );
02089             if ( isset( $views[$viewName] ) )
02090             {
02091                 $view = $views[$viewName];
02092                 $validView = true;
02093                 if ( isset( $view['functions'] ) && !empty( $view['functions'] ) )
02094                 {
02095                     $functions = $view['functions'];
02096                 }
02097             }
02098         }
02099 
02100         if ( $validView )
02101         {
02102             if ( isset( $functions ) )
02103             {
02104                 if ( is_array( $functions ) )
02105                 {
02106                     $funcExpression = false;
02107                     $accessAllowed = true;
02108                     foreach ( $functions as $function )
02109                     {
02110                         if ( empty( $function ) )
02111                         {
02112                             $funcExpression = false;
02113                             $accessAllowed = false;
02114                             break;
02115                         }
02116                         else if ( is_string( $function ) )
02117                         {
02118                             if ( $funcExpression )
02119                             {
02120                                 $funcExpression .= ' and ';
02121                             }
02122                             $funcExpression .= '( ' . $function . ' )';
02123                         }
02124                     }
02125                 }
02126                 else if ( is_string( $functions ) )
02127                 {
02128                     $funcExpression = $functions;
02129                 }
02130                 else
02131                 {
02132                     $funcExpression = false;
02133                     $accessAllowed = true;
02134                 }
02135 
02136                 if ( $funcExpression )
02137                 {
02138                     // Validate and evaluate functions expression.
02139                     // Lets construct functions's expression ready for evaluating first.
02140                     $pS = '/(?<=\b)';
02141                     $pE = '(?=\b)/';
02142 
02143                     $moduleName = $module->attribute( 'name' );
02144                     $availableFunctions = $module->attribute( 'available_functions' );
02145                     if ( is_array( $availableFunctions ) and
02146                          !empty( $availableFunctions ) )
02147                     {
02148                         $pattern = $pS . '(' . implode( '|', array_keys( $availableFunctions ) ) . ')' . $pE;
02149                         $matches = array();
02150                         if ( preg_match_all( $pattern, ' ' . $funcExpression . ' ', $matches ) > 0 )
02151                         {
02152                             $patterns = array();
02153                             $replacements = array();
02154                             $matches = array_unique( $matches[1] );
02155                             foreach ( $matches as $match )
02156                             {
02157                                 if ( !isset( $replacements[$match] ) )
02158                                 {
02159                                     $accessResult = $this->hasAccessTo( $moduleName, $match );
02160                                     if ( $accessResult['accessWord'] == 'no' )
02161                                     {
02162                                         $replacements[$match] = 'false';
02163                                         $params['accessList'] = $accessResult['accessList'];
02164                                     }
02165                                     else
02166                                     {
02167                                         $replacements[$match] = 'true';
02168                                         if ( $accessResult['accessWord'] == 'limited' )
02169                                         {
02170                                             $params['Limitation'] = $accessResult['policies'];
02171                                             $GLOBALS['ezpolicylimitation_list'][$this->ContentObjectID][$moduleName][$match] = $params['Limitation'];
02172                                         }
02173                                     }
02174                                     $patterns[$match] = $pS . $match . $pE;
02175                                 }
02176                             }
02177                             $funcExpression = preg_replace( $patterns, $replacements, ' ' . $funcExpression . ' ' );
02178                         }
02179                     }
02180                     $funcExpressionForEval = $funcExpression;
02181 
02182                     // continue to validate expression
02183                     $words = array();
02184                     $words[] = $pS . 'and' . $pE;
02185                     $words[] = $pS . 'or' . $pE;
02186                     $words[] = $pS . 'true' . $pE;
02187                     $words[] = $pS . 'false' . $pE;
02188                     $pS = '/(?<=[^&|])';
02189                     $pE = '(?=[^&|])/';
02190                     $words[] = $pS . '\|\|' . $pE;
02191                     $words[] = $pS . '&&' . $pE;
02192                     $words[] = '/[\(\)]/';
02193 
02194                     $replacement = '';
02195                     $funcExpression = preg_replace( $words, $replacement, ' ' . $funcExpression . ' ' );
02196 
02197                     $funcExpression = trim( $funcExpression );
02198 
02199                     if ( empty( $funcExpression ) )
02200                     {
02201                         // if expression is valid then evaluate value of the $functionsToEvaluate string
02202                         ob_start();
02203                         $ret = eval( "\$accessAllowed = ( bool ) ( $funcExpressionForEval );" );
02204                         $buffer = ob_get_contents();
02205                         ob_end_clean();
02206 
02207                         // if we get any error while evaluating then set result to false
02208                         if ( !empty( $buffer ) or $ret === false )
02209                         {
02210                             eZDebug::writeError( "There was an error while evaluating the policy functions value of the '$moduleName/$viewName' view. " .
02211                                                  "Please check the '$moduleName/module.php' file." );
02212                             $accessAllowed = false;
02213                         }
02214                     }
02215                     else
02216                     {
02217                         eZDebug::writeError( "There is a mistake in the functions array data of the '$moduleName/$viewName' view. " .
02218                                              "Please check the '$moduleName/module.php' file." );
02219                         $accessAllowed = false;
02220                     }
02221                 }
02222             }
02223             else
02224             {
02225                 $moduleName = $module->attribute( 'name' );
02226                 $accessResult = $this->hasAccessTo( $moduleName );
02227                 if ( $accessResult['accessWord'] == 'no' )
02228                 {
02229                     $params['accessList'] = $accessResult['accessList'];
02230                     $accessAllowed = false;
02231                 }
02232                 else
02233                 {
02234                     $accessAllowed = true;
02235                     if ( $accessResult['accessWord'] == 'limited' )
02236                     {
02237                         $params['Limitation'] = $accessResult['policies'];
02238                         $GLOBALS['ezpolicylimitation_list'][$this->ContentObjectID][$moduleName]['*'] = $params['Limitation'];
02239                     }
02240                 }
02241             }
02242         }
02243 
02244         return $accessAllowed;
02245     }
02246 
02247     /*!
02248      \return an array of roles which the user is assigned to
02249     */
02250     function roles()
02251     {
02252         $groups = $this->attribute( 'groups' );
02253         $groups[] = $this->attribute( 'contentobject_id' );
02254         return eZRole::fetchByUser( $groups );
02255     }
02256 
02257     /*!
02258      \return an array of role ids which the user is assigned to
02259     */
02260     function roleIDList()
02261     {
02262         if ( eZINI::instance()->variable( 'RoleSettings', 'EnableCaching' ) === 'true' )
02263         {
02264             $userCache = $this->getUserCache();
02265             return $userCache['roles'];
02266         }
02267 
02268         $groups = $this->attribute( 'groups' );
02269         $groups[] = $this->attribute( 'contentobject_id' );
02270         return eZRole::fetchIDListByUser( $groups );
02271     }
02272 
02273     /*!
02274      \return an array of limited assignments
02275     */
02276     function limitList( $useGroupsCache = true )
02277     {
02278         if ( $useGroupsCache )
02279             $groups = $this->groups();
02280         else
02281             $groups = $this->generateGroupIdList();
02282         $groups[] = $this->attribute( 'contentobject_id' );
02283         $groups = implode( ', ', $groups );
02284 
02285         $db = eZDB::instance();
02286 
02287         $limitationsArray = $db->arrayQuery( "SELECT DISTINCT limit_identifier, limit_value
02288                                               FROM ezuser_role
02289                                               WHERE contentobject_id IN ( $groups )" );
02290 
02291         return $limitationsArray;
02292     }
02293 
02294     /*!
02295      \return an array of values of limited assignments
02296     */
02297     function limitValueList()
02298     {
02299         if ( eZINI::instance()->variable( 'RoleSettings', 'EnableCaching' ) === 'true' )
02300         {
02301             $userCache = $this->getUserCache();
02302             return $userCache['role_limitations'];
02303         }
02304 
02305         $limitValueList = array();
02306         $limitList      = $this->limitList();
02307         foreach ( $limitList as $limit )
02308         {
02309             $limitValueList[] = $limit['limit_value'];
02310         }
02311 
02312         return $limitValueList;
02313     }
02314 
02315     function contentObject()
02316     {
02317         if ( isset( $this->ContentObjectID ) and $this->ContentObjectID )
02318         {
02319             return eZContentObject::fetch( $this->ContentObjectID );
02320         }
02321         return null;
02322     }
02323 
02324     /**
02325      * Returns the eZUserAccountKey associated with this user
02326      *
02327      * @return eZUserAccountKey
02328      */
02329     public function accountKey()
02330     {
02331         return eZUserAccountKey::fetchByUserID( $this->ContentObjectID );
02332     }
02333 
02334     /*!
02335      Returns true if it's a real user which is logged in. False if the user
02336      is the default user or the fallback buildtin user.
02337     */
02338     function isLoggedIn()
02339     {
02340         if ( $this->ContentObjectID == self::anonymousId() or
02341              $this->ContentObjectID == -1 )
02342         {
02343             return false;
02344         }
02345         return true;
02346     }
02347 
02348     /*!
02349      \return an array of id's with all the groups the user belongs to.
02350     */
02351     function groups( $asObject = false )
02352     {
02353         if ( $asObject == true )
02354         {
02355             if ( !isset( $this->GroupsAsObjects ) )
02356             {
02357                 $db = eZDB::instance();
02358                 $contentobjectID = $this->attribute( 'contentobject_id' );
02359                 $userGroups = $db->arrayQuery( "SELECT d.*, c.path_string
02360                                                 FROM ezcontentobject_tree  b,
02361                                                      ezcontentobject_tree  c,
02362                                                      ezcontentobject d
02363                                                 WHERE b.contentobject_id='$contentobjectID' AND
02364                                                       b.parent_node_id = c.node_id AND
02365                                                       d.id = c.contentobject_id
02366                                                 ORDER BY c.contentobject_id  ");
02367                 $userGroupArray = array();
02368                 $pathArray = array();
02369                 foreach ( $userGroups as $group )
02370                 {
02371                     $pathItems = explode( '/', $group["path_string"] );
02372                     array_pop( $pathItems );
02373                     array_pop( $pathItems );
02374                     foreach ( $pathItems as $pathItem )
02375                     {
02376                         if ( $pathItem != '' && $pathItem > 1 )
02377                             $pathArray[] = $pathItem;
02378                     }
02379                     $userGroupArray[] = new eZContentObject( $group );
02380                 }
02381                 $pathArray = array_unique( $pathArray );
02382 
02383                 if ( !empty( $pathArray ) )
02384                 {
02385                     $extraGroups = $db->arrayQuery( "SELECT d.*
02386                                                 FROM ezcontentobject_tree  c,
02387                                                      ezcontentobject d
02388                                                 WHERE c.node_id in ( " . implode( ', ', $pathArray ) . " ) AND
02389                                                       d.id = c.contentobject_id
02390                                                 ORDER BY c.contentobject_id  ");
02391                     foreach ( $extraGroups as $group )
02392                     {
02393                         $userGroupArray[] = new eZContentObject( $group );
02394                     }
02395                 }
02396 
02397                 $this->GroupsAsObjects = $userGroupArray;
02398             }
02399             return $this->GroupsAsObjects;
02400         }
02401         else
02402         {
02403             if ( !isset( $this->Groups ) )
02404             {
02405                 if ( eZINI::instance()->variable( 'RoleSettings', 'EnableCaching' ) === 'true' )
02406                 {
02407                     $userCache = $this->getUserCache();
02408                     $this->Groups = $userCache['groups'];
02409                 }
02410                 else
02411                 {
02412                     $this->Groups = $this->generateGroupIdList();
02413                 }
02414             }
02415             return $this->Groups;
02416         }
02417     }
02418 
02419     /**
02420      * Generate list of group id's
02421      *
02422      * @since 4.4
02423      * @return array
02424      */
02425     protected function generateGroupIdList()
02426     {
02427         $db         = eZDB::instance();
02428         $userID     = $this->ContentObjectID;
02429         $userGroups = $db->arrayQuery( "SELECT  c.contentobject_id as id,c.path_string
02430                                         FROM ezcontentobject_tree  b,
02431                                              ezcontentobject_tree  c
02432                                         WHERE b.contentobject_id='$userID' AND
02433                                               b.parent_node_id = c.node_id
02434                                         ORDER BY c.contentobject_id  ");
02435         $pathArray      = array();
02436         $userGroupArray = array();
02437         foreach ( $userGroups as $group )
02438         {
02439             $pathItems = explode( '/', $group["path_string"] );
02440             array_pop( $pathItems );
02441             array_pop( $pathItems );
02442             foreach ( $pathItems as $pathItem )
02443             {
02444                 if ( $pathItem != '' && $pathItem > 1 )
02445                     $pathArray[] = $pathItem;
02446             }
02447             $userGroupArray[] = $group['id'];
02448         }
02449 
02450         if ( !empty( $pathArray ) )
02451         {
02452             $pathArray = array_unique ($pathArray);
02453             $extraGroups = $db->arrayQuery( "SELECT c.contentobject_id as id
02454                                             FROM ezcontentobject_tree  c,
02455                                                  ezcontentobject d
02456                                             WHERE c.node_id in ( " . implode( ', ', $pathArray ) . " ) AND
02457                                                   d.id = c.contentobject_id
02458                                             ORDER BY c.contentobject_id  ");
02459             foreach ( $extraGroups as $group )
02460             {
02461                 $userGroupArray[] = $group['id'];
02462             }
02463         }
02464         return $userGroupArray;
02465     }
02466 
02467     /*!
02468      Checks if user is logged in, if not and the site requires user login for access
02469      a module redirect is returned.
02470 
02471      \return null, user login not required.
02472     */
02473     function checkUser( &$siteBasics, $uri )
02474     {
02475         $http = eZHTTPTool::instance();
02476         $check = array( "module" => "user",
02477                         "function" => "login" );
02478         if ( eZSession::issetkey( 'eZUserLoggedInID', false ) &&
02479              $http->sessionVariable( "eZUserLoggedInID" ) != '' &&
02480              $http->sessionVariable( "eZUserLoggedInID" ) != self::anonymousId() )
02481         {
02482             $currentUser = eZUser::currentUser();
02483             if ( !$currentUser->isEnabled() )
02484             {
02485                 eZUser::logoutCurrent();
02486                 $currentUser = eZUser::currentUser();
02487             }
02488             else
02489             {
02490                 return null;
02491             }
02492         }
02493 
02494         $ini        = eZINI::instance();
02495         $moduleName = $uri->element();
02496         $viewName   = $uri->element( 1 );
02497         $anonymousAccessList = $ini->variable( "SiteAccessSettings", "AnonymousAccessList" );
02498         foreach ( $anonymousAccessList as $anonymousAccess )
02499         {
02500             $elements = explode( '/', $anonymousAccess );
02501             if ( !isset( $elements[1] ) )
02502             {
02503                 if ( $moduleName == $elements[0] )
02504                 {
02505                     return null;
02506                 }
02507             }
02508             else
02509             {
02510                 if ( $moduleName == $elements[0] and
02511                      $viewName == $elements[1] )
02512                 {
02513                     return null;
02514                 }
02515             }
02516         }
02517 
02518         return $check;
02519     }
02520 
02521     /*!
02522      Funtion performed before user login info is collected.
02523      It's optional to implement this function in new login handler.
02524 
02525      \return @see eZUserLoginHandler::checkUser()
02526     */
02527     function preCollectUserInfo()
02528     {
02529         return array( 'module' => 'user', 'function' => 'login' );
02530     }
02531 
02532     /*!
02533      Function performed after user login info has been collected.
02534      Store login data as array:
02535      array( 'login' => <username>,
02536             'password' = <password> )
02537      to session variable EZ_LOGIN_HANDLER_USER_INFO for automatic processing of login data.
02538 
02539      \return @see eZUserLoginHandler::checkUser()
02540      */
02541     function postCollectUserInfo()
02542     {
02543         return true;
02544     }
02545 
02546     /*!
02547      Check if login handler require special login URI
02548 
02549      \return Special login uri. If false, use system standard login uri.
02550     */
02551     function loginURI()
02552     {
02553         return false;
02554     }
02555 
02556     /*!
02557      Check if login handler require forced login at user check.
02558 
02559      \return true if force login on user check, false if not.
02560     */
02561     function forceLogin()
02562     {
02563         return false;
02564     }
02565 
02566     /**
02567      * Creates the cache path if it doesn't exist, and returns the cache
02568      * directory. The $userId parameter is used to create multi-level directory names
02569      *
02570      * @params int $userId
02571      * @return string Cache directory for the user
02572      */
02573     static function getCacheDir( $userId = 0 )
02574     {
02575         $dir = eZSys::cacheDirectory() . '/user-info' . eZDir::createMultilevelPath( $userId, 5 );
02576 
02577         if ( !is_dir( $dir ) )
02578         {
02579             eZDir::mkdir( $dir, false, true );
02580         }
02581         return $dir;
02582     }
02583 
02584     /**
02585      * Expire user access / info cache globally
02586      */
02587     static function cleanupCache()
02588     {
02589         eZExpiryHandler::registerShutdownFunction();
02590         $handler = eZExpiryHandler::instance();
02591         $handler->setTimestamp( 'user-info-cache', time() );
02592         $handler->store();
02593     }
02594 
02595     /**
02596      * Returns the filename for a cache file with user information
02597      *
02598      * @deprecated In 4.4.0
02599      * @params int $userId
02600      * @return string|false Filename of the cachefile, or false when the user should not be cached
02601      */
02602     static function getCacheFilename( $userId )
02603     {
02604         $ini = eZINI::instance();
02605         $cacheUserPolicies = $ini->variable( 'RoleSettings', 'UserPolicyCache' );
02606         if ( $cacheUserPolicies === 'enabled' )
02607         {
02608             return eZUser::getCacheDir( $userId ). '/user-'. $userId . '.cache.php';
02609         }
02610         else if ( $cacheUserPolicies !== 'disabled' )
02611         {
02612             $cachableIDs = explode( ',', $cacheUserPolicies );
02613             if ( in_array( $userId, $cachableIDs ) )
02614             {
02615                 return eZUser::getCacheDir( $userId ). '/user-'. $userId . '.cache.php';
02616             }
02617         }
02618         return false;
02619     }
02620 
02621     static function fetchUserClassList( $asObject = false, $fields = false )
02622     {
02623         // Get names of user classes
02624         if ( !$asObject and
02625              is_array( $fields ) and
02626              !empty( $fields ) )
02627         {
02628             $fieldsFilter = '';
02629             $i = 0;
02630             foreach ( $fields as $fieldName )
02631             {
02632                 if ( $i > 0 )
02633                     $fieldsFilter .= ', ';
02634                 $fieldsFilter .= 'ezcontentclass.' . $fieldName;
02635                 $i++;
02636             }
02637         }
02638         else
02639         {
02640             $fieldsFilter = 'ezcontentclass.*';
02641         }
02642         $db = eZDB::instance();
02643         $userClasses = $db->arrayQuery( "SELECT $fieldsFilter
02644                                          FROM ezcontentclass, ezcontentclass_attribute
02645                                          WHERE ezcontentclass.id = ezcontentclass_attribute.contentclass_id AND
02646                                                ezcontentclass.version = " . eZContentClass::VERSION_STATUS_DEFINED ." AND
02647                                                ezcontentclass_attribute.version = 0 AND
02648                                                ezcontentclass_attribute.data_type_string = 'ezuser'" );
02649 
02650         return eZPersistentObject::handleRows( $userClasses, "eZContentClass", $asObject );
02651     }
02652 
02653     static function fetchUserClassNames()
02654     {
02655         $userClassNames = array();
02656         $userClasses = eZUser::fetchUserClassList( false, array( 'identifier' ) );
02657         foreach ( $userClasses as $class )
02658         {
02659             $userClassNames[] = $class[ 'identifier' ];
02660         }
02661         return $userClassNames;
02662     }
02663 
02664     static function fetchUserGroupClassNames()
02665     {
02666         // Get names of user classes
02667         $userClassNames = array();
02668         $userClasses = eZUser::fetchUserClassList( false, array( 'identifier' ) );
02669         foreach ( $userClasses as $class )
02670         {
02671             $userClassNames[] = $class[ 'identifier' ];
02672         }
02673 
02674         // Get names of all allowed content-classes for the Users subtree
02675         $contentIni = eZINI::instance( "content.ini" );
02676         $userGroupClassNames = array();
02677         if ( $contentIni->hasVariable( 'ClassGroupIDs', 'Users' ) and
02678              is_numeric( $usersClassGroupID = $contentIni->variable( 'ClassGroupIDs', 'Users' ) ) and
02679              count( $usersClassList = eZContentClassClassGroup::fetchClassList( eZContentClass::VERSION_STATUS_DEFINED, $usersClassGroupID ) ) > 0 )
02680         {
02681             foreach ( $usersClassList as $userClass )
02682             {
02683                 $userGroupClassNames[] = $userClass->attribute( 'identifier' );
02684             }
02685         }
02686 
02687         // Get names of user-group classes
02688         $groupClassNames = array_diff( $userGroupClassNames, $userClassNames );
02689         return $groupClassNames;
02690     }
02691 
02692     /*!
02693      Checks the password for validity
02694      \static
02695      \return true when password is valid by length and not empty, false if not
02696     */
02697     static function validatePassword( $password )
02698     {
02699         $ini = eZINI::instance();
02700         $minPasswordLength = $ini->variable( 'UserSettings', 'MinPasswordLength' );
02701         if ( $password !== false and
02702              $password !== null and
02703              strlen( $password ) >= (int) $minPasswordLength )
02704         {
02705             return true;
02706         }
02707 
02708         return false;
02709     }
02710 
02711     /**
02712      * Validates user login name using site.ini[UserSettings]UserNameValidationRegex[]
02713      *
02714      * @static
02715      * @since Version 4.1
02716      * @param string $loginName that we want to validate.
02717      * @param string $errorText by reference for details if validation fails.
02718      * @return bool Indicates if validation failed (false) or not (true).
02719      */
02720     static function validateLoginName( $loginName, &$errorText )
02721     {
02722         $ini = eZINI::instance();
02723         $regexList = $ini->variable( 'UserSettings', 'UserNameValidationRegex' );
02724         $errorTextList = $ini->variable( 'UserSettings', 'UserNameValidationErrorText' );
02725         foreach ( $regexList as $key => $regex )
02726         {
02727             if( preg_match( $regex, $loginName) )
02728             {
02729                 if ( isset( $errorTextList[$key] ) )
02730                     $errorText = $errorTextList[$key];
02731                 else
02732                     $errorText = $ini->variable( 'UserSettings', 'DefaultUserNameValidationErrorText' );
02733                 return false;
02734             }
02735         }
02736         return true;
02737     }
02738 
02739     /**
02740      * Gets the id of the anonymous user.
02741      *
02742      * @static
02743      * @return int User id of anonymous user.
02744      */
02745     public static function anonymousId()
02746     {
02747         if ( self::$anonymousId === null )
02748         {
02749             $ini = eZINI::instance();
02750             self::$anonymousId = (int)$ini->variable( 'UserSettings', 'AnonymousUserID' );
02751             $GLOBALS['eZUserBuiltins'] = array( self::$anonymousId );
02752         }
02753         return self::$anonymousId;
02754     }
02755 
02756     /*!
02757       Returns the IDs of content classes that contain user accounts
02758     */
02759     public static function contentClassIDs()
02760     {
02761         $userContentClassIDs = array();
02762 
02763         $ini = eZINI::instance( 'content.ini' );
02764         $userDatatypes = $ini->variable( "DataTypeSettings", "UserDataTypes" );
02765 
02766         $userContentClassIDs = array();
02767         foreach ( $userDatatypes as $datatypeIdentifier )
02768         {
02769             $userContentClassIDs = array_merge( $userContentClassIDs, eZContentClass::fetchIDListContainingDatatype( $datatypeIdentifier ) );
02770         }
02771 
02772         return $userContentClassIDs;
02773     }
02774 
02775     public function canLoginToSiteAccess( $access )
02776     {
02777         $siteAccessResult = $this->hasAccessTo( 'user', 'login' );
02778         $hasAccessToSite = false;
02779 
02780         if ( $siteAccessResult[ 'accessWord' ] == 'limited' )
02781         {
02782             $siteNameCRC = eZSys::ezcrc32( $access[ 'name' ] );
02783             $policyChecked = false;
02784             foreach ( $siteAccessResult['policies'] as $policy )
02785             {
02786                 if ( isset( $policy['SiteAccess'] ) )
02787                 {
02788                     $policyChecked = true;
02789                     if ( in_array( $siteNameCRC, $policy['SiteAccess'] ) )
02790                     {
02791                         $hasAccessToSite = true;
02792                         break;
02793                     }
02794                 }
02795             }
02796 
02797             if ( !$policyChecked )
02798             {
02799                 $hasAccessToSite = true;
02800             }
02801         }
02802         else if ( $siteAccessResult[ 'accessWord' ] == 'yes' )
02803         {
02804             $hasAccessToSite = true;
02805         }
02806 
02807         return $hasAccessToSite;
02808     }
02809 
02810     /// \privatesection
02811     public $Login;
02812     public $Email;
02813     public $PasswordHash;
02814     public $PasswordHashType;
02815     public $Groups;
02816     public $OriginalPassword;
02817     public $OriginalPasswordConfirm;
02818 
02819     /**
02820      * Holds user cache like user info and access array
02821      *
02822      * @since 4.4
02823      * @see eZUser::getUserCache()
02824      * @var array|null
02825      */
02826     protected $UserCache = null;
02827 
02828     /**
02829      * Used to keep track that a logout was performed, and therefore prevent
02830      * auto-login from happening if an SSO is used
02831      * @var bool
02832      * @since 4.3
02833      */
02834     protected static $userHasLoggedOut = false;
02835 }
02836 
02837 ?>