PageRenderTime 225ms CodeModel.GetById 33ms app.highlight 144ms RepoModel.GetById 1ms app.codeStats 2ms

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

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