<?php

  /**
   * Framework level role implementation
   *
   * @package angie.frameworks.authentication
   * @subpackage models
   */
  abstract class FwRole extends BaseRole implements IRoutingContext {
    
    /**
     * Return proper type name in user's language
     *
     * @param boolean $lowercase
     * @param Language $language
     * @return string
     */
    function getVerboseType($lowercase = false, $language = null) {
      return $lowercase ? lang('role', null, true, $language) : lang('Role', null, true, $language);
    } // getVerboseType
    
    /**
     * Return all users who use this role
     * 
     * @return DBResult
     */
    function getUsers(IUser $user) {
      return Users::findByRole($this);
    } // getUsers
    
    /**
     * Return number of users who use this role
     *
     * @return integer
     */
    function countUsers() {
      return Users::countByRole($this);
    } // countUsers
    
    /**
     * Returns true if this role has administration permissions
     *
     * @return boolean
     */
    function isAdministrator() {
      return $this->getPermissionValue('has_admin_access');
    } // isAdministrator
    
    /**
     * Returns true if users with this role can see private objects
     * 
     * @return boolean
     */
    function canSeePrivate() {
      return $this->isAdministrator() || (boolean) $this->getPermissionValue('can_see_private_objects');
    } // canSeePrivate
    
    /**
     * Bulk set object attributes
     *
     * @param array $attributes
     */
    function setAttributes($attributes) {
      if(isset($attributes['permissions'])) {
        $this->setPermissions($attributes['permissions']);
        unset($attributes['permissions']);
      } // if
      
      parent::setAttributes($attributes);
    } // setAttributes
    
    /**
     * Return array or property => value pairs that describes this object
     *
     * $user is an instance of user who requested description - it's used to get
     * only the data this user can see
     *
     * @param IUser $user
     * @param boolean $detailed
     * @param boolean $for_interface
     * @return array
     */
    function describe(IUser $user, $detailed = false, $for_interface = false) {
      $result = parent::describe($user, $detailed, $for_interface);
      
      $result['role_permissions'] = array();
      
      foreach(Roles::getPermissionNames() as $permission) {
        $result['role_permissions'][$permission] = (boolean) $this->getPermissionValue($permission, false, false);
      } // foreach
      
      $result['is_default'] = $this->getIsDefault();
      $result['is_administrator'] = $this->isAdministrator();
      
      $result['urls']['set_as_default'] = $this->getSetAsDefaultUrl();
      
      return $result;
    } // describe

    /**
     * Return array or property => value pairs that describes this object
     *
     * @param IUser $user
     * @param boolean $detailed
     * @return array
     */
    function describeForApi(IUser $user, $detailed = false) {
      $result = parent::describeForApi($user, $detailed);

      $result['role_permissions'] = array();

      foreach(Roles::getPermissionNames() as $permission) {
        $result['role_permissions'][$permission] = (boolean) $this->getPermissionValue($permission, false, false);
      } // foreach

      $result['is_default'] = $this->getIsDefault();
      $result['is_administrator'] = $this->isAdministrator();

      $result['urls']['set_as_default'] = $this->getSetAsDefaultUrl();

      return $result;
    } // describeForApi
    
    /**
     * Prepare list of options that $user can use
     *
     * @param IUser $user
     * @param NamedList $options
     * @param string $interface
     * @return NamedList
     */
    protected function prepareOptionsFor(IUser $user, NamedList $options, $interface = AngieApplication::INTERFACE_DEFAULT) {
      if($this->canEdit($user)) {
        $options->add('edit', array(
        	'text' => lang('Edit'),
          'url' => $this->getEditUrl(),
        	'icon' => AngieApplication::getImageUrl('icons/12x12/edit.png', ENVIRONMENT_FRAMEWORK), 
          'onclick' => new FlyoutFormCallback(array(
            'success_event' => 'role_updated', 
          )), 
        ));
      } // if
      
      if($this->canDelete($user)) {
        $options->add('delete', array(
          'text' => lang('Delete'),
          'url' => $this->getDeleteUrl(),  
          'icon' => AngieApplication::getImageUrl('/icons/12x12/delete.png', ENVIRONMENT_FRAMEWORK),
          'onclick' => new AsyncLinkCallback(array(
            'confirmation' => lang('Are you sure that you want to delete this system role?'), 
            'success_event' => 'role_deleted', 
          )),
        ));
      } // if
      
      EventsManager::trigger('on_system_role_options', array(&$this, &$user, &$options));
    } // prepareOptionsFor
    
    // ---------------------------------------------------
    //  Permission related
    // ---------------------------------------------------
    
    /**
     * Return permission value
     * 
     * Set $cascade to false to make execution faster, and skip depends_on 
     * permission checks
     *
     * @param string $permission_name
     * @param mixed $default
     * @param boolean $cascade
     * @return mixed
     */
    function getPermissionValue($permission_name, $default = false, $cascade = true) {
      $permission = Roles::getPermission($permission_name);
      if($permission) {
        if($cascade && isset($permission['depends_on']) && !$this->getPermissionValue($permission['depends_on'])) {
          return false; // Parent permission not found
        } // if
        
        return isset($this->permissions[$permission_name]) ? (boolean) $this->permissions[$permission_name] : $default;
      } else {
        throw new InvalidParamError('permission_name', $permission_name, "Permission '$permission_name' is not defined");
      } // if
    } // getPermissionValue
    
    /**
     * Set permission value
     *
     * @param string $permission_name
     * @param boolean $value
     */
    function setPermissionValue($permission_name, $value) {
      if($permission_name == 'has_admin_access' && !$value && Roles::isLastAdministratorRole($this)) {
        throw new InvalidParamError('value', $value, "Can't remove administration permissions from last administration role");
      } // if
      
      $this->permissions[$permission_name] = (boolean) $value;
    } // setPermissionValue
    
    /**
     * Cached permissions array
     *
     * @var mixed
     */
    protected $permissions = false;
    
    /**
     * Return permissions
     *
     * @return array
     */
    function getPermissions() {
      return $this->permissions;
    } // getPermissions
    
    /**
     * Set permissions values
     *
     * @param mixed
     * @return mixed
     */
    function setPermissions($value) {
      $this->permissions = array();
      
      if(is_array($value)) {
        foreach($value as $k => $v) {
          $this->setPermissionValue($k, $v);
        } // foreach
      } // if
      
      return $this->permissions;
    } // setPermissions
    
    // ---------------------------------------------------
    //  Interface implementations
    // ---------------------------------------------------
    
    /**
     * Return routing context name
     *
     * @return string
     */
    function getRoutingContext() {
      return 'admin_role';
    } // getRoutingContext
    
    /**
     * Return routing context parameters
     *
     * @return mixed
     */
    function getRoutingContextParams() {
      return array('role_id' => $this->getId());
    } // getRoutingContextParams
    
    // ---------------------------------------------------
    //  Permissions
    // ---------------------------------------------------
    
    /**
     * Returns true if $user can update this role
     * 
     * @param IUser $user
     * @return boolean
     */
    function canEdit(IUser $user) {
      return $user->isAdministrator();
    } // canEdit
    
    /**
     * Returns true if $user can delete this role
     *
     * @param IUser $user
     * @return boolean
     */
    function canDelete(IUser $user) {
      if($user instanceof User) {
        if($user->getRoleId() == $this->getId()) {
          return false; // Can't delete role that you belong to
        } // if
        
        return $user->isAdministrator() && $this->countUsers() < 1;
      } else {
        return false;
      } // if
    } // canDelete
    
    // ---------------------------------------------------
    //  URL-s
    // ---------------------------------------------------
    
    /**
     * Returns set as default URL
     *
     * @return string
     */
    function getSetAsDefaultUrl() {
      return Router::assemble('admin_role_set_as_default', array('role_id' => $this->getId()));
    } // getSetAsDefaultUrl
    
    /**
     * Return set permission value URL
     *
     * @param string $permission_name
     * @param boolean $permission_value
     * @return string
     */
    function getSetPermissionValueUrl($permission_name, $permission_value) {
      return Router::assemble('admin_role_set_permission_value', array(
        'role_id' => $this->getId(), 
        'permission_name' => $permission_name, 
        'permission_value' => (boolean) $permission_value, 
      ));
    } // getSetPermissionValueUrl
    
    // ---------------------------------------------------
    //  System
    // ---------------------------------------------------
    
    /**
     * When row is loaded, unserialize row permissions
     *
     * @param array $row
     * @param boolean $cache_row
     */
    function loadFromRow($row, $cache_row = false) {
      parent::loadFromRow($row, $cache_row);
      
      $raw = $this->getFieldValue('permissions');
      $this->permissions = empty($raw) ? array() : unserialize($raw);
      
      return true;
    } // loadFromRow
    
    /**
     * Save role to database
     */
    function save() {
      $this->setFieldValue('permissions', serialize($this->permissions));
      parent::save();
    } // save
    
    /**
     * Validate before save
     *
     * @param ValidationErrors $errors
     */
    function validate(ValidationErrors &$errors) {
      if($this->validatePresenceOf('name', 3)) {
        if(!$this->validateUniquenessOf('name')) {
          $errors->addError(lang('Role name needs to be unique'), 'name');
        } // if
      } else {
        $errors->addError(lang('Role name is required and it needs to be at least 3 characters long'), 'name');
      } // if
    } // validate
    
  }