<?php

/**
 *
 * @author p6
 *
 * @property integer id
 * @property string username
 * @property string password
 * @property string email
 * @property boolean superuser
 * @property string status
 * @property DateTime create_time
 * @property DateTime update_time
 * @property string first_name
 * @property string last_name
 * @property string matriculation_number
 * @property DateTime password_timeout
 * @property DateTime password_last_update
 * @property boolean can_change_password
 * @property boolean refresh_password_at_first_login
 * @property string apikey
 * @property UserSettingsManager settings
 */
class User extends ActiveRecord
{

  const STATUS_ACTIVE = 'active';

  const STATUS_INACTIVE = 'inactive';
  
  // public $status = 'active';
  // public $superuser = 0;
  public $password_repeat;

  public $role;
  // public $can_change_password = 1;
  // public $password_timeout = 0;
  // public $password_last_update;
  public static function model($className = __CLASS__)
  {
    return parent::model ( $className );
  }

  public function tableName()
  {
    return 'users';
  }

  public function rules()
  {
    return array (
        array (
            'username, password, email, superuser, status',
            'required' 
        ),
        array (
            'superuser',
            'numerical',
            'integerOnly' => true 
        ),
        array (
            'username, password, email, status',
            'length',
            'max' => 256 
        ),
        array (
            'first_name, last_name, matriculation_number',
            'length',
            'max' => 255 
        ),
        
        array (
            'username',
            'unique' 
        ),
        array (
            'apikey',
            'unique' 
        ),
        array (
            'password',
            'required',
            'on' => 'update' 
        ),
        
        array (
            'password_repeat',
            'required',
            'on' => 'insert' 
        ),
        array (
            'password',
            'compare',
            'compareAttribute' => 'password_repeat',
            'on' => 'insert' 
        ),
        
        array (
            'password_repeat',
            'required',
            'on' => 'update' 
        ),
        array (
            'password',
            'compare',
            'compareAttribute' => 'password_repeat',
            'on' => 'update' 
        ),
        
        array (
            'can_change_password',
            'numerical',
            'integerOnly' => true 
        ),
        array (
            'password_timeout',
            'numerical',
            'integerOnly' => true 
        ),
        
        array (
            'refresh_password_at_first_login',
            'numerical',
            'integerOnly' => true 
        ),
        array (
            'refresh_password_at_first_login',
            'default',
            'value' => 1,
            'setOnEmpty' => 1 
        ),
        
        // The following rule is used by search().
        // Please remove those attributes that should not be searched.
        array (
            'id, username, password, email, superuser, status, create_time, update_time, first_name, last_name, matriculation_number, password_last_update, password_timeout, can_change_password, refresh_password_at_first_login',
            'safe',
            'on' => 'search' 
        ) 
    );
  }

  /**
   *
   * @param string $password          
   * @param bool $addError
   *          default TRUE
   * @return boolean true on success
   */
  public function validatePasswordStrength($password, $addError = true)
  {
    $strength = paramdeep ( 'users', 'password', 'strength' );
    $result = validate_password ( $password, $strength ['minLength'], $strength ['min1Upper'], $strength ['min1Lower'], $strength ['min1Digit'], $strength ['min1Symbol'] );
    if ($result !== true)
    {
      if ($addError)
        $this->addError ( 'password', $result );
      return false;
    }
    else
      return true;
  }

  public function relations()
  {
    return array (
        'services' => array (
            self::HAS_MANY,
            'Service',
            'in_charge_to' 
        ),
        'workPeriods' => array (
            self::HAS_MANY,
            'WorkPeriod',
            'id_user' 
        ),
        'servicesCount' => array (
            self::STAT,
            'Service',
            'in_charge_to' 
        ),
        'auths' => array (
            self::MANY_MANY,
            'AuthItem',
            'authassignment(userid,itemname)' 
        ),
        'notificationSettings' => array (
            self::HAS_MANY,
            'NotificationSetting',
            'target_id',
            'condition' => "notificationSettings.type='" . NotificationSetting::TARGET_TYPE_USER . "'" 
        ),
        'notifications' => array (
            self::HAS_MANY,
            'Notification',
            'target_id',
            'condition' => "notificationSettings.type='" . NotificationSetting::TARGET_TYPE_USER . "'" 
        ) 
    );
  }

  public function getLastActionTime()
  {
    $lastAction = query ( "SELECT last_action_at laa FROM work_periods WHERE id_user=:id ORDER BY last_action_at DESC LIMIT 1", array (
        ':id' => $this->id 
    ) )->laa;
    if (! empty ( $lastAction ))
      return new DateTimeEx ( $lastAction );
    return null;
  }

  public function myAttributeLabels()
  {
    return array (
        'id' => 'ID',
        'username' => Yii::t ( 'app', 'Username' ),
        'password' => Yii::t ( 'app', 'Password' ),
        'email' => Yii::t ( 'app', 'Email' ),
        'superuser' => Yii::t ( 'app', 'Administrator' ),
        'status' => Yii::t ( 'app', 'Status' ),
        'create_time' => Yii::t ( 'app', 'Create Time' ),
        'update_time' => Yii::t ( 'app', 'Update Time' ),
        'first_name' => Yii::t ( 'app', 'First Name' ),
        'last_name' => Yii::t ( 'app', 'Last Name' ),
        'matriculation_number' => Yii::t ( 'app', 'Matriculation Number' ),
        'password_repeat' => Yii::t ( 'app', 'Repeat password' ),
        'role' => Yii::t ( 'app', 'Role' ),
        'password_timeout' => Yii::t ( 'app', 'Password expiration' ),
        'password_last_update' => Yii::t ( 'app', 'Last password change' ),
        'can_change_password' => Yii::t ( 'app', 'User can change password' ),
        'refresh_password_at_first_login' => Yii::t ( 'app', 'Change password at first login' ),
        'apikey' => Yii::t ( 'app', 'Api key' ) 
    );
  }

  public function search()
  {
    $criteria = new CDbCriteria ();
    
    $criteria->compare ( 'id', $this->id );
    $criteria->compare ( 'username', $this->username, true );
    $criteria->compare ( 'email', $this->email, true );
    $criteria->compare ( 'superuser', $this->superuser );
    $criteria->compare ( 'status', $this->status, true );
    $criteria->compare ( 'create_time', $this->create_time, true );
    $criteria->compare ( 'update_time', $this->update_time, true );
    $criteria->compare ( 'first_name', $this->first_name, true );
    $criteria->compare ( 'last_name', $this->last_name, true );
    $criteria->compare ( 'password_timeout', $this->password_timeout, true );
    $criteria->compare ( 'password_last_update', $this->password_last_update, true );
    $criteria->compare ( 'can_change_password', $this->can_change_password, true );
    $criteria->compare ( 'matriculation_number', $this->matriculation_number, true );
    
    return new CActiveDataProvider ( $this, array (
        'criteria' => $criteria 
    ) );
  }

  public function getPasswordChangeTimeout()
  {
    if ($this->password_last_update == null)
      return null;
    $now = new DateTime ();
    $lastpwdchange = new DateTime ( $this->password_last_update );
    return $now->diff ( $lastpwdchange );
  }

  public function canChangePassword($who = NULL)
  {
    $can = false;
    
    if ($who == NULL || $this->id == $who)
    {
      $can = ($this->can_change_password == 1);
    }
    else
    {
      $model = User::model ()->findByPk ( $who );
      if ($model != null)
      {
        $isAdm = in_array ( $this->username, Yii::app ()->authManager->admins );
        $hasRol = $this->hasRole ( 'user_update' );
        
        $can = ($isAdm || $hasRol);
      }
    }
    
    return $can;
  }

  public function getFullName()
  {
    if (empty ( $this->first_name ) && empty ( $this->last_name ))
      return empty ( $this->username ) ? $this->id : $this->username;
    return $this->first_name . " " . $this->last_name;
  }

  public function validatePassword($password)
  {
    return crypt ( $password, $this->password ) === $this->password;
  }

  public function hashPassword($password)
  {
    return crypt ( $password, $this->generateSalt () );
  }

  public function resetPassword($newPassword)
  {
    if (isEmpty ( $newPassword ))
      return false;
    
    $this->password = $this->hashPassword ( $newPassword );
    $this->password_last_update = date ( "Y-m-d H:i:s" );
    $this->refresh_password_at_first_login = 1;
    
    return $this->update ( array (
        'password',
        'password_last_update',
        'refresh_password_at_first_login' 
    ) );
  }

  public function hasRole($role)
  {
    return array_key_exists ( $role, Yii::app ()->user->model->getAllAuths () );
  }

  /**
   * Generates a salt that can be used to generate a password hash.
   *
   * The {@link http://php.net/manual/en/function.crypt.php PHP `crypt()` built-in function}
   * requires, for the Blowfish hash algorithm, a salt string in a specific format:
   * - "$2a$"
   * - a two digit cost parameter
   * - "$"
   * - 22 characters from the alphabet "./0-9A-Za-z".
   *
   * @param
   *          int cost parameter for Blowfish hash algorithm
   * @return string the salt
   */
  private function generateSalt($cost = 13)
  {
    if (! is_numeric ( $cost ) || $cost < 4 || $cost > 31)
    {
      throw new CException ( Yii::t ( 'app', 'Cost parameter must be between 4 and 31.' ) );
    }
    // Get some pseudo-random data from mt_rand().
    $rand = '';
    for($i = 0; $i < 8; ++ $i)
      $rand .= pack ( 'S', mt_rand ( 0, 0xffff ) );
      // Add the microtime for a little more entropy.
    $rand .= microtime ();
    // Mix the bits cryptographically.
    $rand = sha1 ( $rand, true );
    // Form the prefix that specifies hash algorithm type and cost parameter.
    $salt = '$2a$' . str_pad ( ( int ) $cost, 2, '0', STR_PAD_RIGHT ) . '$';
    // Append the random salt string in the required base64 format.
    $salt .= strtr ( substr ( base64_encode ( $rand ), 0, 22 ), array (
        '+' => '.' 
    ) );
    return $salt;
  }

  public function getAllAuths()
  {
    return $this->getAllAuths_rec ( $this->auths );
  }

  private function getAllAuths_rec($authArray, $parentArray = array())
  {
    foreach ( $authArray as $a )
    {
      if (! array_key_exists ( $a->name, $parentArray ))
      {
        $parentArray [$a->name] = $a->description;
        $parentArray = $this->getAllAuths_rec ( $a->childs, $parentArray );
      }
    }
    
    return $parentArray;
  }

  public function getRoles()
  {
    return Yii::app ()->authManager->getRoles ( $this->id );
  }

  public function getRolesArray()
  {
    return array_convert ( $this->getRoles (), function ($item)
    {
      return $item->name;
    } );
  }

  public function getAuthItemsArray($type = null)
  {
    return array_convert ( Yii::app ()->authManager->getAuthItems ( $type, $this->id ), function ($item)
    {
      return $item->name;
    } );
  }

  public function getFullAuthArray()
  {
    $roles = array ();
    $tasks = array ();
    $operations = array ();
    
    foreach ( $this->getRoles () as $role )
    {
      $roles [] = $role->name;
      foreach ( $role->getChildren () as $task )
      {
        $tasks [] = $task->name;
        foreach ( $task->getChildren () as $op )
        {
          $operations [] = $op->name;
        }
      }
    }
    
    foreach ( $this->getAuthItemsArray ( 2 ) as $rl )
    {
      $roles [] = $rl;
    }
    foreach ( $this->getAuthItemsArray ( 1 ) as $tk )
    {
      $tasks [] = $tk;
    }
    foreach ( $this->getAuthItemsArray ( 0 ) as $op )
    {
      $operations [] = $op;
    }
    
    return array (
        0 => array_unique ( $operations ),
        1 => array_unique ( $tasks ),
        2 => array_unique ( $roles ) 
    );
  }

  public function getIsMe()
  {
    return user ()->model->id == $this->id;
  }

  public function getFullAuthLabelsArray()
  {
    $result = array ();
    foreach ( $this->getFullAuthArray () as $k => $v )
    {
      $result [$k] = array_convert ( $v, function ($x)
      {
        return AuthItem::getLabelByName ( $x );
      } );
    }
    return $result;
  }

  public function deleteByListCode($list_code)
  {
    User::model ()->deleteAllByAttributes ( array (
        'list_code' => $list_code 
    ) );
  }

  /**
   *
   * @return UserSettingsManager
   */
  public function getSettings()
  {
    return new UserSettingsManager ( $this->id );
  }

  /**
   *
   * @param string $username          
   * @return User
   */
  public static function findByUsername($username)
  {
    return self::model ()->find ( 'username=:un', array (
        ':un' => $username 
    ) );
  }

  /**
   *
   * @param int $id          
   * @return User
   */
  public static function findById($id)
  {
    return self::model ()->findByPk ( $id );
  }

  /**
   *
   * @return string[] array where key=id, value=username
   */
  public static function getUsernameArray()
  {
    $usernames = array ();
    foreach ( User::model ()->findAll () as $user )
    {
      $usernames [$user->id] = $user->username;
    }
    return $usernames;
  }

  /**
   *
   * @return string[] array where key=id, value=getFullName()
   */
  public static function getUserFullNameArray()
  {
    $usernames = array ();
    foreach ( User::model ()->findAll () as $user )
    {
      /** @var User $user */
      $usernames [$user->id] = $user->getFullName ();
    }
    return $usernames;
  }

  /**
   *
   * @return User[] array where key=id, value=User
   */
  public static function getUsers()
  {
    $users = array ();
    foreach ( User::model ()->findAll () as $user )
    {
      /** @var User $user */
      $users [$user->id] = $user;
    }
    return $users;
  }

  /**
   *
   * @param string $token          
   * @return User
   */
  public static function findByImpersonationToken($token)
  {
    return self::model ()->find ( "md5(concat(username,md5(password),date(now()),' ',hour(now())))=:tkn", array (
        ':tkn' => $token 
    ) );
  }

  /**
   *
   * @return string
   */
  public function getImpersonationToken()
  {
    $now = DateTimeEx::now ();
    $now->minute = 0;
    $now->second = 0;
    $tokenPrepare = "$this->username" . md5 ( "$this->password" ) . $now->toDateString () . ' ' . $now->hour;
    $token = md5 ( $tokenPrepare );
    return $token;
  }

  /**
   *
   * @param boolean $absolute
   *          default false
   * @return string
   */
  public function getImpersonationUrl($absolute = false)
  {
    $urler = $absolute ? absoluteUrl : url;
    return $urler ( 'session/login', array (
        'impersonate' => $this->getImpersonationToken () 
    ) );
  }
}

