<?php
/*
 * This file is part of INQMAN
 *
 * Copyright(c) 2008 BULLHEAD,INC. ALL RIGHTS RESERVED.
 *
 * http://www.bullhead.co.jp/
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation;
 * either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * 認証＆承認（Auth、ACL）のロジッククラス
 * 
 *
 */
class Inqman_AclModel extends Inqman_AbstractDao
{
    protected $_name = 'm_acl_role';
    
    /**
     * アカウント認証を行う
     * 
     * @access public
     * @param string $userId アカウント名
     * @param string $password パスワード
     * @return mixed 認証に成功した場合はユーザ情報（標準クラスオブジェクト）を返す。<br/>失敗した場合はFALSEを返す。
     */
    public function authenticate($user_id, $password)
    {
        $db = $this->getAdapter();
        
        $auth = Zend_Auth::getInstance();
        
        $adapter = new Zend_Auth_Adapter_Dbtable($db, 'm_user', 'user_id', 'password', '? AND deleted <> 1');
        
        $adapter->setIdentity($user_id);
        $adapter->setCredential($password);
        
        $result = $auth->authenticate($adapter);
            
        if ($result->isValid()) {
            
            //認証結果オブジェクトを取得
            $user_info = $adapter->getResultRowObject();
            
            //認証結果のIDからアクセス権限情報を取得する
            $user_info->roles = $this->getUserRoles($user_info->id);
                        
            $storage = $auth->getStorage();
            $storage->write($user_info);
            
            return $user_info;
        }
        
        return false;
    }
    
    /**
     * アクセス制御をチェックする
     * 
     * 引数で渡されたアクセス権限（Role）名に対してアクセス可否のチェックを行う
     * Zend_ACL を用いる
     *
     * @param array $roles 権限名
     * @return boolean アクセスを許可する場合 TRUE を返す
     */
    public function checkAcl($resource_key, $privilege_key, $service_id, $roles)
    {
        $this->_logger->debug("resource_key: {$resource_key}, privilege_key: {$privilege_key}, service_id: {$service_id}.");
        
        $sv_mode = new Inqman_ServiceModel();
        $service = $sv_mode->getOneService($service_id);
        $service_code = $service['code'];
        
        $resource_key = "{$service_code}_{$resource_key}";
        
        $acl_model = new Inqman_AclModel();
        $acl = $acl_model->prepareLocalAcl($service_id);
        
        $access_flg = false;
        //権限ごとにアクセス可否をチェックする
        foreach ($roles as $role) {
            if(!$acl->hasRole($role)) continue;
            //アクセスの許可された権限が（１つでも）あればアクセス可とする
            if($acl->isAllowed($role, strtoupper($resource_key), strtoupper($privilege_key))) {
                $access_flg = true;
                //var_dump($role);var_dump($privilegeId);var_dump($access_flg);
            }
        }
        return $access_flg;
    }
    
    /**
     * アクセス制御情報（ACLオブジェクト）を構築する
     * 
     * @return Zend_Acl
     */
    public function prepareLocalAcl($service_id)
    {
        $acl = new Zend_Acl();
        
        $db = $this->getAdapter();
        
        //引数のサービスIDからサービスコードを取得
        $service_model = new Inqman_ServiceModel();
        $service = $service_model->getOneService($service_id);
        $service_code = $service['code'];
        
        //DBに永続化されたACLが無いか確認
        $dump_text = $db->fetchOne($db->select()->from('t_acl_serial', 'dump_text')->where('service_id=?', $service_id));
        if (!empty($dump_text)) {
            //あったらunserializeして返す
            return unserialize($dump_text);
        }

        //setup resource
        $acl = $this->_prepareLocalAclResources($acl, $service_code);
        
        //権限情報を取得
        $authorities = $this->getServiceAuthorities($service_id);
        
        foreach ($authorities as $authority) {
            $role_code    = $authority['role_code'];
            $authority_key = strtoupper("{$service_code}_{$role_code}");
            
            //親権限情報を取得
            $authority_id = $authority['id'];
            $parent_authority_keys = $this->getParentAuthorityKeys($authority_id);

            $acl->addRole(new Zend_Acl_Role($authority_key), $parent_authority_keys);

            $role_id      = $authority['role_id'];
            $acl_structs = $this->getAclStructures($role_id);

            foreach ($acl_structs as $acl_struct) {
                $resource_key  = strtoupper("{$service_code}_{$acl_struct['resource_code']}");                
                $privilege_key = strtoupper($acl_struct['privilege_code']);
                
                $this->_logger->debug("authority_key: {$authority_key}, resource_key: {$resource_key}, privilege_key: {$privilege_key}");
                $acl->allow($authority_key, $resource_key, $privilege_key);
            }
        }
        
        $dump_text = serialize($acl);
        $db->insert('t_acl_serial', array('service_id'=>$service_id, 'dump_text'=>$dump_text));
        
        return $acl;
    }
    
    /**
     * Zend_ACL_Resourceを設定する
     * 
     */
    protected function _prepareLocalAclResources($acl, $service_code)
    {
        $db = $this->getAdapter();
        
        //ACLリソースマスタからACLベースリソース情報を取得
        $select = $db->select()
                     ->from('m_acl_resource', array('resource_code'=>'code'))
                     ->order('id asc')
        ;
        $this->_logger->info($select->__toString());
        $resource_codes = (array) $db->fetchCol($select);
        
        //サービスコードと合わせてローカルリソースとして登録
        foreach ($resource_codes as $resource_code) {
            $resource_key = strtoupper("{$service_code}_{$resource_code}");  
            $acl->add(new Zend_Acl_Resource($resource_key));
        }
        
        return $acl;
    }

    /**
     * 親権限情報を取得する
     */
    public function getParentAuthorities($authority_id)
    {
        $db = $this->getAdapter();
        
        $select = $db->select()
                     ->from(array('ap' => 'l_authority_parent'), array())
                     ->joinInner(array('aa'=> 't_acl_authority'), 'ap.parent_id=aa.id')
                     ->joinInner(array('r'=>'m_acl_role'), 'aa.role_id=r.id', array('role_code'=>'r.code'))
                     ->joinInner(array('s'=>'m_service'), 'aa.service_id=s.id', array('service_code'=>'s.code'))
                     ->where('ap.authority_id=?', $authority_id)
        ;
        $this->_logger->info($select->__toString());
        $result = (array) $db->fetchAll($select);
        
        return $result;
    }
    
    /**
     * 親権限のキーを配列で取得する
     */
    public function getParentAuthorityKeys($authority_id)
    {
        $parent_authorities = $this->getParentAuthorities($authority_id);
        
        $parent_authority_keys = array();
        foreach ($parent_authorities as $parent_authority) {
            $role_code    = $parent_authority['role_code'];
            $service_code = $parent_authority['service_code'];
            $parent_authority_key = strtoupper("{$service_code}_{$role_code}");
            
            $this->_logger->debug("authority_id: {$authority_id}, parent_authority_key: {$parent_authority_key}");
            $parent_authority_keys[] = $parent_authority_key;
        }
       
        return $parent_authority_keys;
    }
    
    /**
     * ユーザの役割情報を取得する
     * 
     */
    public function getUserRoles($user_id)
    {
        $db = $this->getAdapter();
        
        $select = $db->select()
                     ->from(array('au'=>'t_acl_user'))
                     ->joinInner(array('aa' => 't_acl_authority'), 'au.authority_id=aa.id', array('role_name'=>'name', 'role_id', 'service_id'))
                     ->joinInner(array('rl' => 'm_acl_role'), 'aa.role_id=rl.id', array('role_code'=>'rl.code'))
                     ->joinInner(array('sv' => 'm_service'), 'aa.service_id=sv.id', array('service_code'=>'sv.code'))
                     ->where('aa.enable=1')
                     ->where('au.user_id=?', $user_id)
        ;
        $this->_logger->info('Inqman_AclModel->getUserAcl : ' . $select->__toString());
        
        $rows = $db->fetchAll($select);
        
        $roles = array();
        foreach ($rows as $row) {
            $role_key = strtoupper("{$row['service_code']}_{$row['role_code']}");
            $roles[$role_key] = $row;
        }
        
        return $roles;
    }
    
    /**
     * サービスに対する役割情報を取得する
     */
    public function getServiceAuthorities($service_id)
    {
        $db = $this->getAdapter();
        
        //権限情報を取得
        $select = $db->select()
                     ->from(array('aa' => 't_acl_authority'))
                     ->joinInner(array('sv' => 'm_service'), 'aa.service_id=sv.id', array('service_code'=>'sv.code'))
                     ->joinInner(array('rl'=> 'm_acl_role'), 'aa.role_id=rl.id', array('role_code'=>'rl.code'))
                     ->where('aa.enable=1')
                     ->where('aa.service_id=?', $service_id)
                     ->order('aa.id asc')
        ;
        $this->_logger->info($select->__toString());
        $rows = $db->fetchAll($select);
        
        $authorities = array();
        foreach ($rows as $row) {
            $authority_key = strtoupper("{$row['service_code']}_{$row['role_code']}");
            $authorities[$authority_key] = $row;
        }
        
        return $authorities;
    }
    
    /**
     * サービスに対する役割情報を取得する
     * 
     */
    public function getServiceAuthorityOptions($service_id)
    {
        $authorities = (array) $this->getServiceAuthorities($service_id);
        
        $authority_options = array();
        foreach ($authorities as $authority) {
            $authority_options[$authority['id']] = $authority['name'];
        }
        
        return $authority_options;
    }
    
    /**
     * ACLの構造情報を取得する
     * 
     */
    public function getAclStructures($role_id)
    {
        $db = $this->getAdapter();
        
        $select = $db->select()
                     ->from(array('as'=>'m_acl_struct'), array('as.role_id', 'as.resource_id', 'as.privilege_id'))
                     ->joinInner(array('rs'=> 'm_acl_resource'), 'as.resource_id=rs.id', array('resource_code'=>'rs.code'))
                     ->joinInner(array('p' => 'm_acl_privilege'), 'as.privilege_id=p.id', array('privilege_code'=>'p.code'))
                     ->where('role_id=?', $role_id)
        ;
        $this->_logger->info($select->__toString());
        $result = (array) $db->fetchAll($select);
        
        return $result;
    }
    
    /**
     * 検索する
     * 
     * @access public
     * @param mixed $order
     * @param integer $page ページ番号
     * @param integer $rp 1ページあたりの表示件数
     * @param mixed $criteria 検索条件
     */
    public function findRole($order=null, $page=null, $rp=null, $criteria=null)
    {
        $db = $this->getAdapter();
        
        $select = $db->select()
                     ->from('t_acl_authority')
                     ->order('id asc')
        ;
        $this->_logger->info('Inqman_AclModel->findRole : ' . $select->__toString());
        
        return $db->fetchAll($select);
    }

    /**
     * 役割情報をKEY=ID,VALUE=NAMEの配列で取得する
     * 
     * @return array
     */
    public function getRoleOptions()
    {
        $roles = $this->findRole();
        
        $role_options = array();
        foreach ($roles as $role) {
            $role_options[$role['id']] = $role['name'];
        }
        
        return $role_options;
    }
    
    /**
     * ユーザの役割情報を保存する
     * 
     */
    public function saveUserRoles($user_id, $roles)
    {
        $db = $this->getAdapter();
        $db->beginTransaction();
        
        //現在の設定を取得
        $old_roles = $this->getUserRoles($user_id);
        
        $roles = array_map('intval', $roles);
        $user_id = intval($user_id);
        
        foreach ($old_roles as $key => $value) {
            $authority_id = $value['authority_id'];
            $idx = array_search($authority_id, $roles);
            if ($idx === false) {
                $where = array($db->quoteInto('authority_id=?', $authority_id),
                               $db->quoteInto('user_id=?', $user_id),
                );
                $db->delete('t_acl_user', $where);
            } else {
                unset($roles[$idx]);
            }
        }
        
        foreach ($roles as $role) {
            $values = array('authority_id' => $role, 'user_id' => $user_id);
            $db->insert('t_acl_user', $values);
        }
        
        $db->commit();
    }

    /**
     * 指定した権限に属するユーザ情報を取得する
     */
    public function getUsersByAuthority($authority_id)
    {
        $db = $this->getAdapter();
        
        $select = $db->select()
                     ->from(array('au'=>'t_acl_user'))
                     ->joinInner(array('u'=>'m_user'), 'au.user_id=u.id')
                     ->where('au.authority_id=?', $authority_id)
                     ->where('u.deleted<>1')
                     ->order('au.user_id')
        ;
        $this->_logger->info('Inqman_AclModel : ' . $select->__toString());
        
        return $db->fetchAll($select);
    }
}