<?php
class User {
	var $db;

	function __construct() {
		include 'db.inc.php';
		$this->db = $db;
	}

	function login($id, $password) {
		$user = $this->db->selectRow(
			'users',
			NULL,
			array(
				'login_id' => $id,
				'password' => $this->_hash($password),
				'is_active' => '1'
			)
		);
		if (! $user)
			return FALSE;

		if (empty($user['user_type_id']))
			return FALSE;

		if (! empty($user['setting'])) {
			$user['setting'] = unserialize($user['setting']);
		}

		$this->db->update(
			'users',
			array('last_login' => '^CURRENT_TIMESTAMP'),
			array('id' => $user['id'])
		);

		return $user;
	}

	function _hash($password) {
		return hash_hmac('sha256', BULLET_USER_SECRET_KEY.$password, FALSE);
	}

	function save($user) {
		$_SESSION['user'] = $user;
		if (! empty($user['setting'])) {
			$_SESSION['anonymous'] = @$user['setting']['anonymous'];
			$_SESSION['trip'] = @$user['setting']['trip'];
		}
	}

	function tryUpdateSetting() {
		$user = @$_SESSION['user'];
		if (! $user)
			return FALSE;

		$anonymous = @$_SESSION['anonymous'];
		$trip = @$_SESSION['trip'];
		if (@$user['setting']['anonymous'] !== $anonymous || @$user['setting']['trip'] !== $trip) {
			$user['setting'] = array(
				'anonymous' => $anonymous,
				'trip'      => $trip
			);
			return $this->db->update(
				'users',
				array('setting' => serialize($user['setting'])),
				array('id' => $user['id'])
			);
		}

		return FALSE;
	}

	function read($id) {
		return $this->db->selectRow(
			'users',
			NULL,
			array('login_id' => $id)
		);
	}

	function readDetail($id) {
		return $this->db->selectRow(
			'^'.$this->db->getTableName('users').' u',
			array(
				'u.id',
				'u.login_id',
				'u.display_name',
				'u.registered',
				'u.user_type_id',
				'^(select created from '.$this->db->getTableName('posts').' p where u.id=p.user_id and p.anonymous!=1 order by p.id desc limit 1) as recent_post',
				'^(select count(*) from '.$this->db->getTableName('posts').' p where u.id=p.user_id and p.anonymous!=1) as post_count'
			),
			array('u.login_id' => $id)
		);
	}

	function readAdmin($id) {
		$user = $this->db->selectRow(
			'^'.$this->db->getTableName('users').' u',
			array(
				'^u.*',
				'^(select created from '.$this->db->getTableName('posts').' p where u.id=p.user_id and p.anonymous!=1 order by p.id desc limit 1) as recent_post',
				'^(select count(*) from '.$this->db->getTableName('posts').' p where u.id=p.user_id and p.anonymous!=1) as post_count'
			),
			array('id' => $id)
		);
		if (! $user)
			return FALSE;

		$user['logs'] = $this->db->select(
			'action_logs',
			NULL,
			'user_id=?',
			$user['id'],
			array('limit' => 100, 'order' => 'log_id desc')
		);

		$user['posts'] = $this->db->select(
			'posts',
			NULL,
			'user_id=?',
			$user['id'],
			array('limit' => 100, 'order' => 'id desc')
		);

		return $user;
	}

	function load() {
		return empty($_SESSION['user']) ? FALSE : $_SESSION['user'];
	}

	function clear() {
		unset($_SESSION['user']);
	}

	function _getRule($key_values) {
		$template = array(
			'id' => array('rule' => array('empty', 'alphanum')),
			'password' => array('rule' => array('empty')),
			'display_name' => array('rule' => array('empty'))
		);
		$ret = array();
		foreach ($key_values as $k=>$v) {
			if (array_key_exists($k, $template)) {
				$ret[$k] = array_merge($template[$k], array('value' => $v));
			}
		}
		return $ret;
	}

	function _execValidation($checks) {
		foreach ($checks as $field => $v) {
			foreach ($v['rule'] as $rule) {
				if (is_string($rule)) {
					$validator = $rule.'Validation';
					if (! $this->$validator($v['value']))
						throw new UserRegistException($field, $rule);
				} else {
					$validator = $rule['name'].'Validation';
					$params = $rule['params'];
					if (! $this->$validator($v['value'], $params))
						throw new UserRegistException($field, $rule);
				}
			}
		}
	}

	function registUser($id, $password, $display_name, $user_type_id=2, $is_active=1) {
		$display_name = trim(mb_convert_kana($display_name, 'rnsKV'));
		$id = trim($id);

		$checks = $this->_getRule(
			array('id' => $id, 'password' => $password, 'display_name' => $display_name)
		);
		$this->_execValidation($checks);
		if (! $this->hasUserType($user_type_id))
			throw new UserRegistException('user_type_id', 'invalid');
		if ($is_active !== 0 && $is_active !== 1)
			throw new UserRegistException('is_active', 'invalid');

		$ret = $this->db->selectRow('users', NULL, array('login_id' => $id));
		if ($ret) {
			throw new UserRegistException('id', 'duplicate');
		}

		return $this->db->insert(
			'users',
			array(
				'user_type_id' => $user_type_id,
				'login_id' => $id,
				'password' => $this->_hash($password),
				'display_name' => $display_name,
				'registered' => '^CURRENT_TIMESTAMP',
				'is_active' => $is_active
			)
		);
	}

	function updateProfile($id, $key_values) {
		$white_list = array(
			'display_name' => '',
			'password' => '',
			'login_id' => 'operation',
			'is_active' => 'operation',
			'user_type_id' => 'admin',
		);

		$update_key_values = array();
		foreach ($key_values as $k => $v) {
			if (array_key_exists($k, $white_list)) {
				if (empty($white_list[$k])) {
					$update_key_values[$k] = $v;
				} else if (can_action($white_list[$k])) {
					$update_key_values[$k] = $v;
				}
			}
		}

		if (empty($update_key_values))
			return FALSE;

		if (isset($udpate_key_values['display_name'])) {
			$update_key_values['display_name'] = trim(mb_convert_kana($update_key_values['display_name'], 'rnsKV'));
		}

		$checks = $this->_getRule($update_key_values);
		$this->_execValidation($checks);

		if (isset($update_key_values['user_type_id'])) {
			if (! $this->hasUserType($update_key_values['user_type_id']))
				throw new UserRegistException('user_type_id', 'invalid');
		}

		if (isset($update_key_values['is_active'])) {
			$update_key_values['is_active'] = (int)$update_key_values['is_active'];
			if ($update_key_values['is_active'] !== 0 && $update_key_values['is_active'] !== 1)
				throw new UserRegistException('is_active', 'invalid');
		}

		if (isset($update_key_values['password']))
			$update_key_values['password'] = $this->_hash($update_key_values['password']);

		if (isset($update_key_values['is_active'])) {
			$target_user = $this->db->selectRow('users', NULL, array('id' => $id));
			if ($target_user['user_type_id'] != @$_SESSION['user']['user_type_id']) {
				if ($target_user['user_type_id'] == '1' && @$_SESSION['user']['user_type_id']  != '1') {
					throw new UserRegistException('is_active', 'invalid');
				}
			}
		}

		return $this->db->update(
			'users',
			$update_key_values,
			array('id' => $id)
		);
	}

	function searchAdmin($user_type_id, $word, $limit=NULL, $order='recent_post desc') {
		$p = '%'.str_replace('%', '\\%', $word).'%';
		$fields = array(
			'^u.*',
			'^(select created from '.$this->db->getTableName('posts').' p where u.id=p.user_id and p.anonymous!=1 order by p.id desc limit 1) as recent_post',
			'^(select count(*) from '.$this->db->getTableName('posts').' p where u.id=p.user_id and p.anonymous!=1) as post_count'
		);

		$users = $this->db->select(
			'^'.$this->db->getTableName('users').' u',
			$fields,
			'u.user_type_id=? and (u.display_name like ? or u.login_id like ?)',
			array($user_type_id, $p, $p),
			array('limit' => $this->db->safeLimit($limit), 'key_field' => 'id', 'order' => $order)
		);

		return $users;
	}

	function countSearchAdmin($user_type_id, $word) {
		$p = '%'.str_replace('%', '\\%', $word).'%';
		return $this->db->count(
			'users',
			'user_type_id=? and (display_name like ? or login_id like ?)',
			array($user_type_id, $p, $p)
		);
	}

	function search($word, $limit=NULL, $order='id asc', $fields=NULL) {
		$p = '%'.str_replace('%', '\\%', $word).'%';
		if (! $fields) {
			$fields = array(
				'u.id',
				'u.login_id',
				'u.display_name',
				'u.registered',
				'u.user_type_id',
				'^(select created from '.$this->db->getTableName('posts').' p where u.id=p.user_id and p.anonymous!=1 order by p.id desc limit 1) as recent_post',
				'^(select count(*) from '.$this->db->getTableName('posts').' p where u.id=p.user_id and p.anonymous!=1) as post_count'
			);
		}
		$users = $this->db->select(
			'^'.$this->db->getTableName('users').' u',
			$fields,
			'u.display_name like ? or u.login_id like ?',
			array($p, $p),
			array('limit' => $this->db->safeLimit($limit), 'key_field' => 'id', 'order' => $order)
		);

		return $users;
	}

	function countSearch($word) {
		$p = '%'.str_replace('%', '\\%', $word).'%';
		return $this->db->count(
			'users',
			'display_name like ? or login_id like ?',
			array($p, $p)
		);
	}

	function searchID($word, $limit=NULL) {
		$p = str_replace('%', '\\%', $word).'%';
		return $this->db->select(
			'^'.$this->db->getTableName('posts').' u',
			array(
				'u.display_name',
				'^(select created from '.$this->db->getTableName('posts').' p where u.display_name=p.display_name and p.anonymous=1 order by p.id desc limit 1) as recent_post',
				'^(select count(*) from '.$this->db->getTableName('posts').' p where u.display_name=p.display_name and p.anonymous=1) as post_count'
			),
			'u.display_name like ? and u.anonymous=1',
			array($p),
			array('limit' => $this->db->safeLimit($limit), 'distinct' => TRUE)
		);

		return $users;
	}

	function countSearchID($word) {
		$p = str_replace('%', '\\%', $word).'%';
		return $this->db->pureSelectOne(
			'posts',
			'^count(distinct display_name)',
			'display_name like ? and anonymous=1',
			array($p)
		);
	}

	function searchTrip($word, $limit=NULL) {
		$p = str_replace('%', '\\%', $word).'%';
		return $this->db->select(
			'^'.$this->db->getTableName('posts').' u',
			array(
				'u.trip',
				'^(select created from '.$this->db->getTableName('posts').' p where u.trip=p.trip and p.anonymous=1 order by p.id desc limit 1) as recent_post',
				'^(select count(*) from '.$this->db->getTableName('posts').' p where u.trip=p.trip and p.anonymous=1) as post_count'
			),
			'u.trip like ? and u.anonymous=1',
			array($p),
			array('limit' => $this->db->safeLimit($limit), 'distinct' => TRUE)
		);

		return $users;
	}

	function countSearchTrip($word) {
		$p = str_replace('%', '\\%', $word).'%';
		return $this->db->pureSelectOne(
			'posts',
			'^count(distinct trip)',
			'trip like ? and anonymous=1',
			array($p)
		);
	}

	function hasUserType($user_type_id) {
		$ret = $this->db->selectRow('user_types', NULL, array('id' => $user_type_id));
		return $ret ? TRUE : FALSE;
	}

	function hasUser($id) {
		$ret = $this->db->selectRow('users', NULL, array('login_id' => $id));
		if ($ret)
			return TRUE;

		return FALSE;
	}

	function updateAuth($auth_name, $user_type_id, $auth) {
		$exists = $this->db->selectRow(
			'user_auth',
			NULL,
			array('auth_name' => $auth_name, 'user_type_id' => $user_type_id)
		);
		if ($exists) {
			return $this->db->update(
				'user_auth',
				array(
					'auth' => $auth,
					'modified' => '^CURRENT_TIMESTAMP'
				),
				array(
					'auth_name' => $auth_name,
					'user_type_id' => $user_type_id
				)
			);
		}

		$mst_user_type = $this->db->selectRow('user_types', NULL, array('id' => $user_type_id));
		$mst_ast = $this->db->selectRow('auths', NULL, array('name' => $auth_name));
		if ($mst_user_type === FALSE || $mst_ast === FALSE)
			return FALSE;

		$ret = $this->db->insert(
			'user_auth',
			array(
				'auth_name' => $auth_name,
				'user_type_id' => $user_type_id,
				'auth' => $auth,
				'modified' => '^CURRENT_TIMESTAMP'
			)
		);
		if ($ret === FALSE)
			return FALSE;

		return TRUE;
	}

	function saveAuthCache() {
		$types = $this->db->select('user_types');
		foreach ($types as $type) {
			$auth = $this->db->selectList(
				'user_auth',
				NULL,
				'user_type_id=?',
				array($type['id']),
				array('key_field' => 'auth_name', 'data_field' => 'auth')
			);
			file_put_contents(DATA.'auth'.$type['id'], serialize($auth));
		}
	}

	function getAuths() {
		return $this->db->select('auths', NULL, array('key_field' => 'name'));
	}

	function getUserTypes() {
		return $this->db->select('user_types', NULL, array('key_field' => 'id'));
	}

	function getAllAuth() {
		return $this->db->select('user_auth',NULL);
	}

	function emptyValidation($value) {
		if (empty($value))
			return FALSE;
		return TRUE;
	}

	function alphanumValidation($value) {
		return preg_match('/^[a-zA-Z0-9]*$/', $value);
	}
}
class UserRegistException extends Exception {
	var $field;
	var $reason;
	function __construct($field, $reason) {
		$this->field = $field;
		$this->reason = $reason;
		parent::__construct($this->_getMessage());
	}
	function _getMessage() {
		$field_jp_map = array(
			'id' => 'ID',
			'password' => 'パスワード',
			'is_active' => '有効無効',
			'auth' => '権限',
			'user_type_id' => 'ユーザ種別',
			'display_name' => '表示名',
			'system' => 'システム'
		);
		$reason_jp_map = array(
			'empty' => 'を入力してください',
			'invalid' => 'が不正です',
			'alphanum' => 'は英数字で入力してください',
			'duplicate' => 'は既に利用されています',
			'nothing' => 'がありません',
			'error' => 'にエラーがあります'
		);
		return $field_jp_map[$this->field].$reason_jp_map[$this->reason];
	}
}
?>