<?php

#.begin
#.mod[volmgr]
class xVolume extends xItemObj {
	/**
	 * Update changes in volume info.
	 * @return bool
	 */
	public function update() {
		return $this->doUpdate(TB_PREFIX.'servers');
	}
	public function __construct($volume = false, $flush = false) {
		if (is_int($volume))
			$this->select($volume, $flush);
	}
	/**
	 * Select a volume and load it into this object.
	 * @param uint $volume
	 * @return bool
	 */
	public function select($volume, $flush = false) {
		$this->update();
		$this->m_changed = array();
		if ($this->m_data = xVolMgr::getArr($volume, $flush))
			return true;
		return false;
	}
	/**
	 * Get volume object by volume info array.
	 * @param array $volume
	 * @return xVolume
	 */
	public static function load($volume) {
		$tmp = new xVolume();
		$tmp->m_data = $volume;
		return $tmp;
	}
	
	/**
	 * Get volume object by volume info array.
	 * @param array $volume
	 * @return xVolume
	 */
	public function getUnlinkSize() {
		$res = xGbl::$mysql->exec('SELECT SUM(`size`) FROM `'.TB_VFILES.'` v WHERE NOT EXISTS (SELECT * FROM `'.TB_FILES.'` WHERE `vfid` = v.`id`) AND (`serv0` = '.$this->m_data['id'].' OR `serv1` = '.$this->m_data['id'].' OR `serv2` = '.$this->m_data['id'].' OR `serv3` = '.$this->m_data['id'].');');
		list($size) = $res->fetch_array();
		$res->free();
		return $size == '' ? 0 : $size;
	}
}
class xVolMgr {
	public static function add($host, $size, $type = 0, $description = '', $mr0 = 0) {
		$db = xGbl::$mysql;
		$db->exec('INSERT INTO `'.TB_PREFIX.'servers` SET `host`="'.$db->escape($host).'", `type`="'.$type.'", `info`="'.$db->escape($description).'", `size`="'.$size.'", `rsize`="'.$size.'", `mr0`="'.$mr0.'";');
		return $db->getInsertID();
	}
	public static function flushCache($newDat = false) {
		if ($newDat)
			@xGbl::$mmc->delete(VOLS_MMC_NAME);
		else
			@xGbl::$mmc->set(VOLS_MMC_NAME, $newDat, 1, 3600);
	}
	public static function getAllArr($flush = false) {
		static $vols;
		if (!$flush) {
			if ($vols) return $vols;
			if ($vols = @xGbl::$mmc->get(VOLS_MMC_NAME)) return $vols;
		}
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_PREFIX.'servers`;');
		$vols = array();
		while ($r = $res->fetch_assoc())
			$vols[$r['id']] = $r;
		$res->free();
		self::flushCache($vols);
		return $vols;
	}
	/**
	 * Get list of volume info object.
	 * @param bool $flush  flush cache if true.
	 * @return array(xVolume) return array list of volume info if succeed, or false.
	 */
	public static function getAll($flush = false) {
		static $vols;
		if (!$flush && $vols) return $vols;
		$vols = array();
		$all = self::getAllArr($flush);
		foreach ($all as $id => $vol)
			$vols[$id] = xVolume::load($vol);
		return $vols;
	}
	/**
	 * Get volume info array.
	 * @param uint32 $id
	 * @param bool $flush flush cache if true.
	 * @return mixed return info array if succeed, or false.
	 */
	public static function getArr($id, $flush = false) {
		$all = self::getAllArr($flush);
		if (isset($all[$id]))
			return $all[$id];
		return false;
	}
	/**
	 * Get list of volume info array.
	 * @param array(uint32) $ids
	 * @param bool $flush  flush cache if true.
	 * @return array return array list of volume info if succeed, or false.
	 */
	public static function listByIdsArr($ids, $flush = false) {
		$vols = array();
		$all = self::getAllArr($flush);
		foreach ($ids as $id)
			if (isset($all[$id]))
				$vols[$id] = $all[$id];
		return $vols;
	}
	/**
	 * Get list of volume info object.
	 * @param array(uint32) $ids
	 * @param bool $flush  flush cache if true.
	 * @return array(xVolume) return array list of volume info object if succeed, or false.
	 */
	public static function listByIds($ids, $flush = false) {
		$vols = array();
		$all = self::getAll($flush);
		foreach ($ids as $id)
			if (isset($all[$id]))
				$vols[$id] = $all[$id];
		return $vols;
	}
	/**
	 * Use space of volume.
	 * @param uint32 $id
	 * @param uint64 $spaceSize
	 * @return bool return true if succeeded, or false.
	 */
	public static function useSpace($id, $spaceSize) {
		xGbl::$mysql->exec('UPDATE `'.TB_PREFIX.'servers` SET `rsize` = `rsize` - '.$spaceSize.' WHERE `id`='.$id.' AND `rsize`>='.$spaceSize.' LIMIT 1;');
		return xGbl::$mysql->getAffectRows()? true: false;
	}
}
#.mod[/volmgr]

#.mod[ipmgr]
class xIPMgr{
	public static $remoteIP;
	public static $remoteIPAddr;
	protected function __construct() {}
	public static function init() {
		if (!@xGbl::$mmc->get('IP_MGR_INITED'))
			self::rebuild();
		if(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'])
			self::$remoteIP = $_SERVER['REMOTE_ADDR'];
		elseif(getenv('REMOTE_ADDR'))
			self::$remoteIP = getenv('REMOTE_ADDR');
		self::$remoteIP = ip2long(self::$remoteIP);
	}
	public static function record($user = 0, $date = false) {
		xGbl::$mysql->exec('INSERT INTO `'.TB_IPLOGS.'` SET `ip`='.self::$remoteIPAddr.', `user`='.$user.', `date`='.TIMENOW.';');
	}
	public static function flush() {
		xGbl::$mysql->exec('DELETE `'.TB_PREFIX.'iplimits`, `'.TB_PREFIX.'ip_temp` from `'.TB_PREFIX.'iplimits` LEFT JOIN `'.TB_PREFIX.'ip_temp` ON `'.TB_PREFIX.'iplimits`.`ip`=`'.TB_PREFIX.'ip_temp`.`ip` WHERE `'.TB_PREFIX.'iplimits`.`expire` <> 0 AND `'.TB_PREFIX.'ip_temp`.`expire`<'.TIMENOW);
	}
	public static function rebuild() {
		self::flush();
		xGbl::$mysql->exec('INSERT INTO `'.TB_PREFIX.'ip_temp`(`ip`,`type`,`expire`) SELECT `ip`,`type`,IF(`expire`=0, 0, `expire` + `date`) FROM `'.TB_PREFIX.'iplimits`;');
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_PREFIX.'ip_temp`;');
		$t = TIMENOW;
		while ($r = $res->fetch_assoc())
			xGbl::$mmc->set($r['ip'].'_IP_BAN', $r['type'] + 0, 0, $r['expire']+0);
		xGbl::$mmc->set('IP_MGR_INITED', 1, 0, 0);
		$res->free();
	}
	public static function isBanned($ip) {
		if (@xGbl::$mmc->get($ip.'_IP_BAN') === 1)
			return true;
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_PREFIX.'ip_temp` WHERE `ip`='.xIPMgr::$remoteIP.' AND `type`=0 AND (`expire` = 0 OR `expire`>='.TIMENOW.') LIMIT 1');
		if ($res->num_rows) {
			$res->free();
			return true;
		}
		return false;
	}
	public static function isAllowed($ip) {
		if (@xGbl::$mmc->get($ip.'_IP_BAN') === 0)
			return false;
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_PREFIX.'ip_temp` WHERE `ip`='.xIPMgr::$remoteIP.' AND `type`=1 AND (`expire` = 0 OR `expire`>='.TIMENOW.') LIMIT 1');
		if ($res->num_rows) {
			$res->free();
			return true;
		}
		return false;
	}
	public static function banIP($ip, $expire = 0) {
		return true;
	}
}
#.mod[/ipmgr]

#.mod[ui]
class xUI {
	public static $uiName;
	public static function loadPage($pageName) {
		global $_SLEX, $_UI, $_APP;
		$filename = __SKINS__.self::$uiName.'/'.$pageName;
		if (file_exists($filename))
			include $filename;
	}
	public static function setupPage($pageName, $path = __SKINS__) {
		global $_SLEX, $_UI, $_APP;
		$filename = $path.'/'.$pageName;
		if (file_exists($filename))
			include $filename;
	}
}
#.mod[/ui]

#.mod[usermgr]
class xUser extends xItemObj {
	const ITEM_CACHE_FLUSH_LOCK = 'users_info_flush_lock';
	const ERR_BASED = 0x0B00;
	const ERR_BAD_EMAIL = 0x0B01;
	const ERR_BAD_USERNAME = 0x0B02;
	const ERR_BAD_PASSWORD = 0x0B03;
	const ERR_BANNED = 0x0B04;
	public static $s_expire;
	protected static $s_lck;
	public static $su;
	public static $cur;
	/**
	 * Initialize the xUser
	 */
	public static function init() {
		global $_SLEX;
		$settings = $_SLEX['usermgr'];
		xGroup::$s_begin_class = $settings['begin'];
		xGroup::$s_group_expire = $settings['groupexpire'];
		self::$s_expire = $settings['expire'];
		self::$cur = new xUser(0);
	}
	/**
	 * Auto set-up login info when user logined.
	 */
	public static function autologin() {
		if (isset($_SESSION['user']) && $_SESSION['user']) {
			self::$cur = new xUser($_SESSION['user'] + 0);
			if (self::$cur->isLogined())
				return self::$cur;
			else
				self::$cur = new xUser();
		}
		return false;
	}
	/**
	 * Initialize the super user.
	 */
	public static function suInit() {
		if (!self::$su)
			self::$su = new xUser(1);
	}
	/**
	 * Add a user.
	 * @param string $userName
	 * @param string $password
	 * @param string $email
	 */
	public static function creat($userName, $password, $email) {
		$su = xStrUtils::getInst();
		if (!$su->isEMail($email))
			throw new xError($email, self::ERR_BAD_EMAIL);
		if (!$su->isUsername($userName))
			throw new xError($userName, self::ERR_BAD_USERNAME);
		self::newPassHash($password, $salt);
		xGbl::$mysql->exec('INSERT INTO `'.TB_USERS.'`(`username`, `passhash`, `salt`, `regdate`, `regip`, `email`, `class`, `msg_max`, `disk_max`) SELECT "'.$userName.'", "'.$password.'", "'.$salt.'", '.TIMENOW.', '.xIPMgr::$remoteIP.', "'.$email.'", '.xGroup::$s_begin_class.', `msg_max`, `disk_max` FROM `'.TB_GROUPS.'` WHERE `id`='.xGroup::$s_begin_class.' LIMIT 1;');
		if (xGbl::$mysql->getErrno() == 1062)
			throw new xError($userName, xError::ERR_DUP);
		if ($id = xGbl::$mysql->getInsertID())
			return new xUser($id + 0, true);
		throw new xError($userName, xError::ERR_UNKNOWN);
	}
	/**
	 * Load a user by userinfo in array.
	 * @param array $userData userinfo in array
	 * @param bool $flush Reflush memcache if true
	 */
	public static function load($userData, $flush = true) {
		if ($userData) {
			$obj = new xUser();
			$obj->m_data = $userData;
			if ($flush)
				self::cacheUser($userData);
		} else $obj = null;
		return $obj;
	}
	public function occupySpace($fileSize, $fileNum = 1) {
		if ($this->isGuest())
			return false;
		xGbl::$mysql->exec('UPDATE `'.TB_USERS.'` SET `files` = `files` + '.$fileNum.', `disk_usage` = `disk_usage` + '.$fileSize.' WHERE `id`='.$this->id.' AND `disk_max` > `disk_usage` AND `disk_max` - `disk_usage` >= '.$fileSize.' LIMIT 1;');
		if (!xGbl::$mysql->getAffectRows())
			return false;
		$this->select($this->id + 0, true);
		return true;
	}
	public function freeSpace($fileSize, $fileNum = 1) {
		if ($this->isGuest())
			return false;
		xGbl::$mysql->exec('UPDATE `'.TB_USERS.'` SET `files` = `files` - '.$fileNum.', `disk_usage` = `disk_usage` - '.$fileSize.' WHERE `id`='.$this->id.' AND `disk_usage` >= '.$fileSize.' LIMIT 1;');
		if (!xGbl::$mysql->getAffectRows())
			return false;
		$this->select($this->id + 0, true);
		return true;
	}
	public static function lockFlush() {
		if (self::$s_lck)
			return self::$s_lck;
		if (self::$s_lck = @xGbl::$mmc->get(self::ITEM_CACHE_FLUSH_LOCK))
			return self::$s_lck;
		if (@xGbl::$mmc->add(self::ITEM_CACHE_FLUSH_LOCK, TIMENOW, self::$s_expire))
			return TIMENOW;
		if (self::$s_lck = @xGbl::$mmc->get(self::ITEM_CACHE_FLUSH_LOCK))
			return self::$s_lck;
		return false;
	}
	public static function unlockFlush() {
		self::$s_lck = 0;
		return @xGbl::$mmc->delete(self::ITEM_CACHE_FLUSH_LOCK);
	}
	/**
	 * Select a folder and load it into this object.
	 * @param uint $folder
	 * @return bool
	 */
	public function select($user, $flush = false) {
		$this->update();
		$this->m_changed = array();
		if ($user) {
			if (($lck = self::$s_lck) || ($lck = self::lockFlush())) {
				$uKey = USER_MMC_PREFIX.$user.'_'.$lck;
				if ((!$flush) && ($r = @xGbl::$mmc->get(array(self::ITEM_CACHE_FLUSH_LOCK, $uKey))))
					if (isset($r[self::ITEM_CACHE_FLUSH_LOCK], $r[$uKey]) && $r[self::ITEM_CACHE_FLUSH_LOCK] == $lck) {
						$this->m_data = $r[$uKey];
						return true;
					}
			}
			if (is_int($user))
				$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_USERS.'` WHERE `id`='.$user.' LIMIT 1;');
			elseif (is_string($user))
				$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_USERS.'` WHERE LCASE(`username`)="'.xGbl::$mysql->escape(strtolower($user)).'" LIMIT 1;');
			else
				return false;
			$r = $res->fetch_assoc();
			$res->free();
			if ($r) {
				self::cacheUser($r);
				$this->m_data = $r;
				return true;
			} else
				$this->select(0);
		} else {
			$this->m_data = array('id' => 0,
			'username' => 'Guest',
			'email' => 'guest@localhost',
			'passhash' => '',
			'salt' => '',
			'class' => 0,
			'skin' => 1,
			'lang' => 'zh-cn',
			'disk_usage' => 0,
			'disk_max' => 1,
			'disk_ext' => 0,
			'files' => 0,
			'folders' => 0,
			'msgs' => 0,
			'msg_max' => 0);
		}
		return false;
	}
	public function isGuest() {
		return $this->id? false: true;
	}
	/**
	 * This method cache user info into memory.
	 * @param array $info
	 */
	public static function cacheUser(&$info) {
		if ($info['id'])
			if (($lck = self::$s_lck) || ($lck = self::lockFlush())) {
				xGbl::$mmc->set(USER_MMC_PREFIX.$info['id'].'_'.$lck, $info, self::$s_expire, 1);
				xGbl::$mmc->set(USER_MMC_PREFIX.$info['username'].'_'.$lck, $info, self::$s_expire, 1);
			}
	}
	/**
	 * Construct a user object by user's ID or name
	 * @param mixed<int/string> $user uid or username
	 * @param bool $flush Reflush memcache if true
	 */
	public function __construct($user = 0, $flush = false) {
		$this->m_changed = array();
		$this->select($user, $flush);
	}
	/**
	 * Log in this user by password
	 * @param string $password
	 * @return bool return true if succeed.
	 */
	public function login($password) {
		if ($this->isGuest())
			throw new xError($this->username, self::ERR_BAD_USERNAME);
		if ($this->is(USR_R0_LOGIN))
			throw new xError($password, self::ERR_BANNED);
		if (self::verifyPassword($password)) {
			$_SESSION['user'] = $this->id += 0;
			$this->lastdate = TIMENOW;
			$this->lastip = xIPMgr::$remoteIP;
			xUser::$cur = $this;
			xAutoSession::update();
			return true;
		}
		throw new xError($password, self::ERR_BAD_PASSWORD);
	}
	/**
	 * Log out this user
	 * @return bool return true if succeed.
	 */
	public function logout() {
		if ($this->isGuest())
			return false;
		if ($_SESSION['user'] == $this->id) {
			$_SESSION['user'] = 0;
			xUser::$cur = new xUser();
			xAutoSession::clean();
			return true;
		}
		return false;
	}
	/**
	 * Tell whether user logined
	 * @return bool return true if succeed.
	 */
	public function isLogined() {
		if (!xUser::$cur->isGuest() && xUser::$cur->id == $this->id)
			return true;
		return false;
	}
	/**
	 * Forbid a user to do anything.
	 */
	public function ban() {
		return false;
	}
	/**
	 * Update changes in user info.
	 */
	public function update() {
		if (!$this->isGuest()) {
			$this->doUpdate(TB_USERS, false, true);
			self::cacheUser($this->m_data);
			return true;
		}
		return false;
	}
	/**
	 * Get group info of user.
	 */
	public function getGroup() {
		return new xGroup($this->class);
	}
	/**
	 * Test the password
	 * @param string $password
	 */
	public function verifyPassword($password) {
		if (xUser::makePassHash($password, $this->salt) == $this->passhash)
			return true;
		return false;
	}
	/**
	 * Edit the password.
	 * @param string $oldPass
	 * @param string $newPass
	 */
	public function editPassword($oldPass, $newPass) {
		if ($this->isGuest())
			return false;
		if ($this->verifyPassword($oldPass)) {
			$this->setPassword($newPass);
			return true;
		}
		return false;
	}
	/**
	 * Set the password.
	 * @param string $newPass
	 */
	public function setPassword($newPass) {
		if ($this->isGuest())
			return false;
		xUser::newPassHash($newPass, $salt);
		$this->passhash = $newPass;
		$this->salt = $salt;
		return true;
	}
	/**
	 * Make hash of password
	 * @param string $password
	 * @param string $salt
	 */
	public static function makePassHash($password, $salt) {
		return md5(md5($salt.$password).$salt);
	}
	/**
	 * Make hash pair of password
	 * @param string $password
	 * @param string $salt
	 */
	public static function newPassHash(&$password, &$salt) {
		$salt = xStrUtils::getInst()->randomString(16);
		$password = self::makePassHash($password, $salt);
	}
	public static function getCount($flush = false) {
		if ($flush || !($cnt = @xGbl::$mmc->get('users_count'))) {
			$res = xGbl::$mysql->exec('SELECT COUNT(*) FROM `'.TB_USERS.'`;');
			$cnt = $res->fetch_array();
			$cnt = $cnt[0] + 0;
			$res->free();
			@xGbl::$mmc->set('users_count', $cnt, 0, 3600);
		}
		return $cnt;
	}
	public static function listItems($fields = '*', $conds = false, $page = 0, $perpage = 10, $order = '`id` ASC') {
		if ($conds)
			$conds = 'WHERE '.$conds;
		if ($page) 
			$page = 'LIMIT '.(($page - 1) * $perpage).', '.$perpage;
		$order = 'ORDER BY '.$order;
		return xGbl::$mysql->exec('SELECT '.$fields.' FROM `'.TB_USERS.'` '.$conds.' '.$order.' '.$page);
	}
	/**
	 * load users by array<int> of IDs.
	 * @param Array<int> $users
	 * @param bool $flush get user data from user center if true.
	 * @return Array<xUser>
	 */
	public static function listById($users, $flush = false) {
		$rtn = array();
		if (!$users)
			return $rtn;
		$users = array_unique($users);
		if (!$flush) {
			$usrKeys = array();
			foreach ($users as $id)
				$usrKeys[$id] = USER_MMC_PREFIX.$id;
			$restUsrs = array();
			if ($mUsrs = @xGbl::$mmc->get($usrKeys)) {
				$restUsrs = array_diff($usrKeys, array_keys($mUsrs));
				$usrKeys = array_diff($usrKeys, $restUsrs);
				foreach ($usrKeys as $uid => $key)
					$rtn[$uid] = xUser::load($mUsrs[$key]);
			}
			if ($restUsrs)
				$users = $restUsrs;
		}
		if ($users) {
			$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_USERS.'` WHERE `id` IN ('.join(',', $users).')');
			while ($u = $res->fetch_assoc())
				$rtn[$u['id']] = xUser::load($u);
			$res->free();
		}
		return $rtn;
	}
	/**
	 * List users by username.
	 * @param Array<string> $users
	 * @return Array<xUser>
	 */
	public static function listByName($users) {
		$rtn = array();
		if (!$users)
			return $rtn;
		$users = array_unique($users);
		if (!$flush) {
			$usrKeys = array();
			foreach ($users as $id)
				$usrKeys[$id] = USER_MMC_PREFIX.$id;
			$restUsrs = array();
			if ($mUsrs = @xGbl::$mmc->get($usrKeys)) {
				$restUsrs = array_diff($usrKeys, array_keys($mUsrs));
				$usrKeys = array_diff($usrKeys, $restUsrs);
				foreach ($usrKeys as $uid => $key)
					$rtn[$uid] = xUser::load($mUsrs[$key]);
			}
			if ($restUsrs)
				$users = $restUsrs;
		}
		if ($users) {
			foreach ($users as $key => $username)
				$users[$key] = '"'.xGbl::$mysql->escape($username).'"';
			$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_USERS.'` WHERE `username` IN ('.join(',', $users).')');
			while ($u = $res->fetch_assoc())
				$rtn[$u['id']] = xUser::load($u);
			$res->free();
		}
		return $rtn;
	}
	public function credit($cid, $intval) {
		$cid = new xCredit($cid);
		$intval = $cid->toValidDulta($intval);
		$this->add($cid, $intval, (($cid->isUnsigned())?'':'`credit'.$cid->id.'` >= '.$intval));
	}
}
class xGroup extends xItemObj {
	public static $s_begin_class;
	public static $s_group_expire;
	const ERR_BASED = 0x0B10;
	const ERR_BAD_GROUP = 0x0B11;
	public static function load($userData) {
		if ($userData) {
			$obj = new xGroup();
			$obj->m_data = $userData;
		} else $obj = null;
		return $obj;
	}
	public function __construct($id = false, $flush = false) {
		if ($id)
			$this->select($id, $flush);
	}
	public function select($id, $flush = false) {
		$this->update();
		$this->m_changed = array();
		$gs = self::getAll($flush);
		if (isset($gs[$id])) {
			$this->m_data = $gs[$id];
			return true;
		}
		throw new xError($id, self::ERR_BAD_GROUP);
	}
	/**
	 * Update changes in user info.
	 */
	public function update() {
		if ($this->isValid() && $this->m_changed) {
			if (isset($this->m_changed['disk_max'])) {
				if (is_array($this->m_changed['disk_max']))
					xGbl::$mysql->exec('UPDATE `'.TB_USERS.'` SET `disk_max` = `disk_max` + '.$this->m_changed['disk_max'][1].' WHERE `class`='.$this->id);
				else {
					$g = new xGroup($this->id);
					xGbl::$mysql->exec('UPDATE `'.TB_USERS.'` SET `disk_max` = `disk_max` - '.$g->disk_max.' + '.$this->disk_max.' WHERE `class`='.$this->id);
				}
			}
			$this->doUpdate(TB_GROUPS, false, false);
			$r = $this->getAll(1);
			$this->m_data = $r[$this->id];
			return true;
		}
		return false;
	}
	/**
	 * Add a group of users.
	 * @param string $name
	 * @param uint64 $disk_max
	 * @param uint32 $msg_max
	 * @param uint32 $mr0
	 */
	public static function creat($name, $disk_max, $msg_max, $mr0) {
		xGbl::$mysql->exec('INSERT INTO `'.TB_GROUPS.'` SET `name`="'.xGbl::$mysql->escape($name).'", `disk_max` = '.$disk_max.', `msg_max` = '.$msg_max.', `mr0` = '.$mr0.';');
		if ($id = xGbl::$mysql->getInsertID())
			return new xGroup($id, true);
		throw new xError($name, xError::ERR_UNKNOWN);
	}
	/**
	 * get info array of groups.
	 * @param bool $flush reflush the memcache if true.
	 */
	public static function getAll($flush = false) {
		static $groups;
		if (!$flush) {
			if ($groups) return $groups;
			if ($groups = @xGbl::$mmc->get('user_group_all')) return $groups;
		}
		$s = xGbl::$mysql->exec('SELECT * FROM `'.TB_GROUPS.'`;');
		$groups = array();
		while ($r = $s->fetch_assoc()) {
			$ls = explode('|', $r['vols']);
			if (!$ls)
				$ls = array($r['vols']);
				foreach ($ls as $idx => $v) {
					if (strchr($v, '-')) {
						$v = explode('-', $v);
						$v[0] += 0; $v[1] += 0;
					} else
						$v += 0;
					$ls[$idx] = $v;
				}
			$r['volist'] = $ls;
			$groups[$r['id']] = $r;
		}
		$s->free();
		xGbl::$mmc->set('user_group_all', $groups, self::$s_group_expire, 1);
		return $groups;
	}
	public function provideVol($size) {
		$ds = xVolMgr::getAllArr();
		$max = array_keys($ds);
		$max = $max[0];
		foreach ($this->volist as $v) {
			if (is_array($v)) {
				for ($i = $v[0]; $i <= $v[1]; ++$i)
					if ($ds[$i]['rsize'] >= $size && $ds[$i]['rsize'] > $ds[$max]['rsize'])
						$max = $i;
			} elseif ($ds[$i]['rsize'] >= $size && $ds[$v]['rsize'] > $ds[$max]['rsize'])
				$max = $v;
		}
		if ($ds[$max]['rsize'] >= $size)
			return new xVolume($max);
		throw new xError($size, xError::ERR_EXCEED);
	}
}
#.mod[/usermgr]

#.mod[file]
abstract class xFileOSObject extends xItemObj {
	abstract public function getFullPath($slash = false);
	abstract public function getFullPaths($slash = false);
	public function getParentIds($addSelf = false) {
		if (!$this->m_data)
			return array();
		$r = explode('/', $this->path);
		array_shift($r);
		array_pop($r);
		if ($addSelf)
			$r[] = $this->id;
		return $r;
	}
	public function getParentNames($addSelf = false) {
		if (!$this->m_data)
			return array();
		$r = explode('/', $this->paths);
		array_shift($r);
		array_pop($r);
		if ($addSelf)
			$r[] = $this->id;
		return $r;
	}
	/**
	 * Apply sql operation to parents.
	 * @param string $sqlA
	 * @param string $sqlB
	 * @param bool $withSelf Don't use in xFile.
	 */
	public function applyToParents($sqlA, $sqlB, $withSelf) {
		$parents = $this->getParentIds($withSelf);
		if ($parents)
			return xGbl::$mysql->exec($sqlA.' `'.TB_FOLDERS.'` '.$sqlB.' WHERE `id` IN ('.join(',', $parents).');');
		return true;
	}
	/**
	 * Test if a user is the owner of this file/folder.
	 * @param xUser $user
	 */
	public function isOwner($user) {
		if ($this->isValid() && $this->owner == $user->id)
			return true;
		return false;
	}
}
class xFile extends xFileOSObject {
	protected $m_data;
	protected $m_changed;
	/**
	 * Add a file.
	 * @param string $title
	 * @param xUser $owner
	 * @param xVFile $vfile
	 * @param xFolder $parent
	 * @param uint32 $mr0
	 */
	public static function create($title, $owner, $vfile, $parent = false, $mr0 = 0) {
		if (!$owner || !$vfile || $owner->isGuest() || !$vfile->isValid() || !$parent->isValid())
			throw new xError(false, xError::ERR_BAD_ARGS);
		if ($vfile->mr0 & FILEM_PREADD_R0)
			$mr0 |= FILEM_PREADD_R0;
		$vfileSize = $vfile->size;
		$vfile = $vfile->id;
		if ($parent->id) {
			$updirs = explode('/', $parent->path);
			unset($updirs[count($updirs) - 1], $updirs[0]);
			$updirs[] = $parent->id;
		} else 
			$updirs = 0;
		$depth = $parent->depth + 1;
		if ($parent->id)
			$sql2 = 'CONCAT(d.`path`, d.`id`, "/"), CONCAT(d.`paths`, d.`name`, "/"), '.$vfile.', '.$vfileSize.' FROM `'.TB_FOLDERS.'` d JOIN `'.TB_USERS.'` u ON u.`id`='.$owner->id.' JOIN `'.TB_VFILES.'` v ON v.`id`='.$vfile.' WHERE d.`id`='.$parent->id.' AND d.`owner`='.$owner->id.';';
		else
			$sql2 = 'CONCAT(u.`id`, "/"), CONCAT(u.`username`, "/"), '.$vfile.', '.$vfileSize.' FROM `'.TB_USERS.'` u JOIN `'.TB_VFILES.'` v ON v.`id`='.$vfile.' WHERE u.`id`='.$owner->id.';';
		$key = '';
		do {
			$key = xItemObj::$stru->randomString(8);
			$nameExtDot = strrpos($title, '.');
			$nameExt = ($nameExtDot!==false) ? strtolower(substr($title, $nameExtDot+1)) : '';
			$sql1 = 'INSERT INTO `'.TB_FILES.'`(`key`, `name`, `name_ext`, `owner`, `parent`, `added`, `lastedited`, `depth`, `mr0`, `path`, `paths`, `vfid`, `size`) SELECT "'.$key.'", "'.mb_substr($title, 0, 128, 'utf8').'", "'.substr($title, strrpos($title, '.')+1).'", '.$owner->id.', '.$parent->id.', '.TIMENOW.', '.TIMENOW.', '.$depth.', '.$mr0.', ';
			xGbl::$mysql->exec($sql1.$sql2);
		} while (xGbl::$mysql->getErrno() == 1062 && strpos(xGbl::$mysql->getError(), 'PRIMARY'));
		if (xGbl::$mysql->getErrno() == 1062 && !strpos(xGbl::$mysql->getError(), 'PRIMARY'))
			throw new xError($title, xError::ERR_DUP);
		$ret = new xFile($key);
		if ($parent->id) {
			xGbl::$mysql->exec('UPDATE `'.TB_FOLDERS.'` SET `files` = `files` + 1 WHERE `owner`='.$owner->id.' AND `id`='.$parent->id.' LIMIT 1;');
			$ret->applyToParents('UPDATE', 'SET `size` = `size` + '.$vfileSize, false);
		}
		return $ret;
	}
	public static function toStrSize($size) {
		static $sizeU = array('Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB');
		$i = 0;
		for ($i = 0; $size >= 1048576.0; ++$i)
			$size /= 1024.0;
		if ($i)
			return number_format($size / 1024.0, 2).' '.$sizeU[$i + 1];
		if ($size >= 1024.0)
			return number_format($size / 1024.0, 2).' '.$sizeU[1];
		return $size.' '.$sizeU[0];
	}
	
	public static function verifyExt($filename, $ext) {
		if($ext == '') return true;
		else {
			$nameExtDot = strrpos($filename, '.');
			$nameExt = ($nameExtDot!==false) ? strtolower(substr($filename, $nameExtDot+1)) : '';
			$exts = explode(',',$ext);
			return in_array($nameExt, $exts);
		}
	}
	/**
	 * Update changes in file info.
	 */
	public function update() {
		return $this->doUpdate(TB_FILES, '`key`="'.$this->key.'"');
	}
	/**
	 * Apply sql operation to parents.
	 * @param string $sqlA
	 * @param string $sqlB
	 * @param bool $withSelf Don't use in xFile.
	 */
	public function applyToParents($sqlA, $sqlB = '', $withSelf = false) {
		return parent::applyToParents($sqlA, $sqlB, false);
	}
	public function __construct($file = false) {
		if ($file)
			$this->select($file);
	}
	/**
	 * Select a file and load it into this object.
	 * @param uint $file
	 * @return bool
	 */
	public function select($file) {
		if (is_int($file))
			$conds = '`id`='.$file;
		elseif (is_string($file))
			$conds = '`key`="'.xGbl::$mysql->escape($file).'"';
		else
			throw new xError($file, xError::ERR_BAD_ARGS);
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_FILES.'` WHERE '.$conds.' LIMIT 1;');
		$this->m_data = $res->fetch_assoc();
		$res->free();
		if ($this->m_data)
			return true;
		return false;
	}
	public static function load($file) {
		$tmp = new xFile();
		$tmp->m_data = $file;
		return $tmp;
	}
	/**
	 * Get a file by paths
	 * @param String $path
	 * @param xUser $owner
	 * @return xFile
	 */
	public static function locate($path, $owner) {
		$tree = explode('/', $path);
		$objname = $tree[count($tree) - 1];
		$tree[count($tree) - 1] = '';
		$ppath = strtolower(join('/', $tree));
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_FILES.'` WHERE `owner`='.$owner->id.' AND `depth`='.(count($tree) - 1).' AND `name`="'.xGbl::$mysql->escape($objname).'";');
		while ($r = $res->fetch_assoc()) {
			if (strtolower($r['paths']) == $ppath) {
				$res->free();
				return xFile::load($r);
			}
		}
		return null;
	}
	
	/**
	 * Move a file to a new place.
	 * @param xFolder $to
	 */
	public function move($to, $newName = false) {
		if (!$to->isValid())
			return false;
		if (!$newName)
			$newName = $this->name;
		if ($to->id == $this->parent && $newName == $this->name)
			return false;
		$oldUpDirs = $this->getParentIds();
		$newUpDirs = $to->getParentIds(true);
		$affectOldDirs = array_diff($oldUpDirs, $newUpDirs);
		$affectNewDirs = array_diff($newUpDirs, $oldUpDirs);
		unset($oldUpDirs, $newUpDirs);
		$nameExtDot = strrpos($newName, '.');
		$nameExt = ($nameExtDot!==false) ? strtolower(substr($newName, $nameExtDot+1)) : '';
		xGbl::$mysql->exec('UPDATE `'.TB_FILES.'` SET `name`="'.xGbl::$mysql->escape($newName).'", `parent`='.$to->id.', `paths`="'.xGbl::$mysql->escape($to->getFullPaths(true)).'", `path`="'.xGbl::$mysql->escape($to->getFullPath(true)).'", `depth`='.($to->depth + 1).' WHERE `id`='.$this->id.' LIMIT 1;');
		if (!($ret = xGbl::$mysql->getAffectRows()))
			throw new xError($newName, xError::ERR_DUP);
		if ($to->id != $this->parent) {
			if ($affectOldDirs) xGbl::$mysql->exec('UPDATE `'.TB_FOLDERS.'` SET `size`=`size`-'.$this->size.', `files` = `files` - IF(`id`='.$this->parent.', 1, 0) WHERE `id` IN ('.join(',', $affectOldDirs).');');
			if ($affectNewDirs) xGbl::$mysql->exec('UPDATE `'.TB_FOLDERS.'` SET `size`=`size`+'.$this->size.', `files` = `files` + IF(`id`='.$to->id.', 1, 0) WHERE `id` IN ('.join(',', $affectNewDirs).');');
		}
		return true;
	}
	/**
	 * Remove a file
	 * @param xUser $owner
	 */
	public function delete() {
		if (!$this->isValid())
			return false;
		$owner = new xUser($this->owner + 0);
		xGbl::$mysql->exec('DELETE FROM `'.TB_FILES.'` WHERE `id`='.$this->id.' LIMIT 1');
		if (!xGbl::$mysql->getAffectRows())
			return false;
		if ($this->size)
			$this->applyToParents('UPDATE', 'SET `files`=IF(`id`='.$this->parent.', `files` - 1, `files`), `size`=`size` - '.$this->size, false);
		elseif ($this->parent)
			xGbl::$mysql->exec('UPDATE `'.TB_FOLDERS.'` SET `files`=`files` - 1, `size`=`size` - '.$this->size.' WHERE `id`='.$this->parent.' LIMIT 1;');
		$owner->add('disk_usage', '-'.$this->size);
		$owner->add('files', -1);
		$owner->update();
		return $this;
	}
	public function getFullPath($slash = false) {
		return $this->path.$this->id.($slash?'/':'');
	}
	public function getFullPaths($slash = false) {
		return $this->paths.$this->name.($slash?'/':'');
	}
}
class xVFile extends xItemObj {
	protected $m_data;
	protected $m_changed;
	public static function create($size, $srv, $md5 = '', $sha1 = '', $mr0 = 0) {
		$mr0 |= FILEM_PREADD_R0;
		if (xVolMgr::useSpace($srv, $size)) {
			xGbl::$mysql->exec('INSERT INTO `'.TB_VFILES.'` SET `size`='.$size.', `serv0`='.$srv.', `md5`="'.$md5.'", `mr0`='.$mr0.',  `sha1`="'.$sha1.'";');
			return new xVFile(xGbl::$mysql->getInsertID());
		}
		return false;
	}
	/**
	 * Update changes in file info.
	 */
	public function update() {
		return $this->doUpdate(TB_VFILES);
	}
	public function uploadDone() {
		if ($this->mr0 & FILEM_PREADD_R0) {
			xGbl::$mysql->exec('UPDATE `'.TB_VFILES.'` SET `mr0`=`mr0`&~'.FILEM_PREADD_R0.' WHERE `id`='.$this->mr0.' LIMIT 1;');
			if (xGbl::$mysql->getAffectRows())
				xGbl::$mysql->exec('UPDATE `'.TB_FILES.'` SET `mr0`=`mr0`&~'.FILEM_PREADD_R0.' WHERE `vfid`='.$this->mr0.';');
			return true;
		}
		return false;
	}
	public function __construct($file = false) {
		if ($file)
			$this->select($file);
	}
	/**
	 * Select a file and load it into this object.
	 * @param uint $file
	 * @return bool
	 */
	public function select($file) {
		$this->update();
		$this->m_changed = array();
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_VFILES.'` WHERE `id`='.($file+0).' LIMIT 1;');
		$this->m_data = $res->fetch_assoc();
		$res->free();
		if ($this->m_data)
			return true;
		return false;
	}
	public static function load($file) {
		$tmp = new xVFile();
		$tmp->m_data = $file;
		return $tmp;
	}
	
	public static function getUnlink() {
		return xGbl::$mysql->exec('SELECT * FROM `'.TB_VFILES.'` v WHERE NOT EXISTS (SELECT * FROM `'.TB_FILES.'` WHERE `vfid` = v.`id`);');
	}
	
	public static function getUnlinkCount() {
		$res = xGbl::$mysql->exec('SELECT COUNT(0) FROM `'.TB_VFILES.'` v WHERE NOT EXISTS (SELECT * FROM `'.TB_FILES.'` WHERE `vfid` = v.`id`);');
		list($count) = $res->fetch_array();
		$res->free();
		return $count;
	}
}
class xFolder extends xFileOSObject {
	const ERR_BASED = 0x0A00;
	const ERR_MOVE_TO_CHILD = 0x0A01;
	protected $m_data;
	protected $m_changed;
	/**
	 * Create a folder.
	 * @param string $title
	 * @param xUser $owner
	 * @param xFolder $parent
	 * @param uint32 $mr0
	 */
	public static function create($title, $owner, $parent, $mr0 = 0) {
		if ($owner->isGuest())
			return false;
		if (!$parent->isValid() || $parent->owner != $owner->id)
			return false;
		$depth = $parent->depth + 1;
		$owner->add('folders', 1);
		if (!$owner->update())
			return false;
		if ($parent->id)
			$sql2 = 'd.`id`, CONCAT(d.`path`, d.`id`, "/"), CONCAT(d.`paths`, d.`name`, "/") FROM `'.TB_USERS.'` u JOIN `'.TB_FOLDERS.'` d ON d.`id`='.$parent->id.' AND d.`owner`='.$owner->id.' WHERE u.`id`='.$owner->id.' LIMIT 1;';
		else
			$sql2 = '0, CONCAT("'.$owner->id.'", "/"), CONCAT(u.`username`, "/") FROM `'.TB_USERS.'` u WHERE u.`id`='.$owner->id.' LIMIT 1;';
		$key = '';
		do {
			$key = xItemObj::$stru->randomString(8);
			$sql1 = 'INSERT INTO `'.TB_FOLDERS.'`(`key`, `name`, `depth`, `mr0`, `owner`, `added`, `lastedited`, `parent`, `path`, `paths`) SELECT "'.$key.'", "'.mb_substr($title, 0, 128, 'utf8').'", '.$depth.', '.$mr0.', u.`id`, '.TIMENOW.', '.TIMENOW.', ';
			xGbl::$mysql->exec($sql1.$sql2);
		} while (xGbl::$mysql->getErrno() == 1062 && strpos(xGbl::$mysql->getError(), 'PRIMARY'));
		if (xGbl::$mysql->getErrno() == 1062 && !strpos(xGbl::$mysql->getError(), 'PRIMARY'))
			throw new xError($title, xError::ERR_DUP);
		if ($parent->id) {
			$parent->add('folders', 1);
			$parent->update();
		}
		return new xFolder($key);
	}
	/**
	 * Update changes in folder info.
	 */
	public function update() {
		return $this->doUpdate(TB_FOLDERS, '`key`="'.$this->key.'"');
	}
	/**
	 * 
	 * Enter description here ...
	 * @param uint32 $folder
	 * @param xUser $owner
	 */
	public function __construct($folder = false, $owner = false) {
		$this->select($folder, $owner);
	}
	/**
	 * Select a folder and load it into this object.
	 * @param uint32 $folder
	 * @param xUser $owner
	 * @return bool
	 */
	public function select($folder, $owner = false) {
		$this->update();
		if ($folder) {
			if (is_int($folder))
				$conds = '`id`='.$folder;
			elseif (is_string($folder))
				$conds = '`key`="'.xGbl::$mysql->escape($folder).'"';
			else
				throw new xError($folder, xError::ERR_BAD_ARGS);
			$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_FOLDERS.'` WHERE '.$conds.' LIMIT 1;');
			$this->m_data = $res->fetch_assoc();
			$res->free();
		} elseif ($owner && !$owner->isGuest()) {
			$this->m_data = array(
			'id' =>0,
			'key' =>'',
			'name' => '',
			'parent' =>0,
			'depth' =>0,
			'owner' => $owner->id,
			'paths' => $owner->username.'/',
			'path' => $owner->id.'/',
			'size' => $owner->disk_usage,
			'mr0' => 0 );
		} else
			return false;
		if ($this->m_data)
			return true;
		return false;
	}
	/**
	 * Tell if this object loaded a root folder.
	 * @return bool
	 */
	public function isRoot() {
		if ($this->isValid() && !$this->id)
			return true;
		return false;
	}
	public function getFullPath($slash = false) {
		if ($this->isRoot())
			return $this->path;
		return $this->path.$this->id.($slash?'/':'');
	}
	public function getFullPaths($slash = false) {
		if ($this->isRoot())
			return $this->paths;
		return $this->paths.$this->name.($slash?'/':'');
	}
	public static function load($folder) {
		$tmp = new xFolder();
		$tmp->m_data = $folder;
		return $tmp;
	}
	/**
	 * Get a folder by paths
	 * @param String $path
	 * @param xUser $owner
	 * @return xFolder
	 */
	public static function locate($path, $owner) {
		$tree = explode('/', $path);
		unset($tree[count($tree) - 1]);
		$objname = $tree[count($tree) - 1];
		$tree[count($tree) - 1] = '';
		$ppath = strtolower(join('/', $tree));
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_FOLDERS.'` WHERE `owner`='.$owner->id.' AND `depth`='.(count($tree) - 1).' AND `name`="'.xGbl::$mysql->escape($objname).'";');
		while ($r = $res->fetch_assoc()) {
			if (strtolower($r['paths']) == $ppath) {
				$res->free();
				return self::load($r);
			}
		}
		return null;
	}
	public function applyToSubFolders($sqlA, $sqlB = '', $withSelf = false) {
		return xGbl::$mysql->exec($sqlA.' `'.TB_FOLDERS.'` '.$sqlB.' WHERE `owner`='.$this->owner.' AND (`depth` > '.$this->depth.' AND SUBSTR(`path`, 1, '.strlen($this->getFullPath(true)).') = "'.$this->getFullPath(true).'") '.($withSelf?'OR `id`='.$this->id:''));
	}
	public function applyToSubFiles($sqlA, $sqlB = '') {
		return xGbl::$mysql->exec($sqlA.' `'.TB_FILES.'` '.$sqlB.' WHERE `owner`='.$this->owner.' AND `depth` > '.$this->depth.' AND SUBSTR(`path`, 1, '.strlen($this->getFullPath(true)).') = "'.$this->getFullPath(true).'"');
	}
	/**
	 * Move a folder to a new place.
	 * @param xFolder $to
	 * @param xUser $owner
	 * @param xFolder $parent
	 */
	public function move($to, $newName = false) {
		if (!$this->isValid() || $this->isRoot() || !$to->isValid())
			throw new xError(false, xError::ERR_BAD_ARGS);
		$newUpDirs = $to->getParentIds(true);
		if (in_array($this->id, $newUpDirs)) # if move folder into its child folder.
			throw new xError($to->id, self::ERR_MOVE_TO_CHILD);
		if (!$newName)
			$newName = $this->name;
		if ($to->id != $this->parent && $this->size) {
			$oldUpDirs = $this->getParentIds();
			$affectOldDirs = array_diff($oldUpDirs, $newUpDirs);
			$affectNewDirs = array_diff($newUpDirs, $oldUpDirs);
		}
		unset($oldUpDirs, $newUpDirs);
		xGbl::$mysql->exec('UPDATE `'.TB_FOLDERS.'` SET `name`="'.xGbl::$mysql->escape($newName).'", `parent`='.$to->id.', `paths`="'.xGbl::$mysql->escape($to->getFullPaths(true)).'", `path`="'.xGbl::$mysql->escape($to->getFullPath(true)).'", `depth`='.($to->depth + 1).' WHERE `id`='.$this->id.' LIMIT 1;');
		if (1 > ($ret = xGbl::$mysql->getAffectRows()))
			throw new xError($newName, xError::ERR_DUP);
		$fileAddOpr = $to->id != $this->parent?'`path`=CONCAT("'.xGbl::$mysql->escape($to->getFullPath(true).$this->id.'/').'", SUBSTR(`path`, '.(strlen($this->getFullPath(true)) + 1).'))':'';
		$thisAddOpr = '`depth` = `depth` + '.($to->depth + 1 - $this->depth).', `paths`=CONCAT("'.xGbl::$mysql->escape($to->getFullPaths(true).$this->name.'/').'", SUBSTR(`paths`, '.(mb_strlen($this->getFullPaths(true), 'utf8') + 1).'))';
		$this->applyToSubFolders('UPDATE', 'SET '.$thisAddOpr.', '.$fileAddOpr);
		$ret += xGbl::$mysql->getAffectRows();
		$this->applyToSubFiles('UPDATE', 'SET '.$thisAddOpr.', '.$fileAddOpr);
		$ret += xGbl::$mysql->getAffectRows();
		if ($to->id != $this->parent && $this->size) {
			if ($affectOldDirs) xGbl::$mysql->exec('UPDATE `'.TB_FOLDERS.'` SET `size`=`size`-'.$this->size.', `folders` = `folders` - IF(`id`='.$this->parent.', 1, 0) WHERE `id` IN ('.join(',', $affectOldDirs).');');
			if ($affectNewDirs) xGbl::$mysql->exec('UPDATE `'.TB_FOLDERS.'` SET `size`=`size`+'.$this->size.', `folders` = `folders` + IF(`id`='.$to->id.', 1, 0) WHERE `id` IN ('.join(',', $affectNewDirs).');');
		}
		return $ret;
	}
	/**
	 * Get the list of files.
	 * @param string $order
	 * @param string $orderBy
	 * @param uint32 $max
	 * @param uint32 $page
	 * @param uint32 $offset
	 */
		public function listFiles($order = 'ASC', $orderBy = 'id', $max = 10, $page = 0, $offset = 0, $filter = '*') {
		$rtn = array();
		if (!$this->isValid())
			return false;
		if ($page) {
			--$page;
			if ($max == 0)
				return $rtn;
			$limit = ' LIMIT '.($page * $max + $offset).', '.$max;
		} else $limit = '';
		$rtn = array();
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_FILES.'` WHERE `owner`='.$this->owner.(($filter=='*')?' AND `parent` = '.$this->id:' AND `name_ext` IN ('.$filter.')').' ORDER BY '.$orderBy.' '.$order.$limit.';');
		if ($res) {
			$max = $res->num_rows - $max;
			while ($r = $res->fetch_assoc())
				$rtn[$r['id']] = $r;
		}
		return $rtn;
	}
	/**
	 * Get the list of children folders.
	 * @param string $order
	 * @param string $orderBy
	 * @param uint32 $max
	 * @param uint32 $page
	 * @param uint32 $offset
	 */
	public function listFolders($order = 'ASC', $orderBy = 'id', $max = 10, $page = 0, $offset = 0) {
		$rtn = array();
		if (!$this->isValid())
			return false;
		if ($page) {
			--$page;
			if ($max == 0)
				return $rtn;
			$limit = ' LIMIT '.($page * $max + $offset).', '.$max;
		} else $limit = '';
		$rtn = array();
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_FOLDERS.'` WHERE `owner`='.$this->owner.' AND `parent`='.$this->id.' ORDER BY '.$orderBy.' '.$order.$limit.';');
		if ($res)
			while ($r = $res->fetch_assoc())
				$rtn[$r['id']] = $r;
		return $rtn;
	}
	/**
	 * Get a child folder by name.
	 * @param string $name
	 */
	public function getChildFolder($name) {
		if (!$this->isValid())
			return null;
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_FOLDERS.'` WHERE `parent`='.$this->id.' AND `name`="'.xGbl::$mysql->exec($name).'" LIMIT 1;');
		$r = $res->fetch_assoc();
		$res->free();
		if ($r)
			return self::load($r);
		return null;
	}
	/**
	 * Get a child file by name.
	 * @param string $name
	 */
	public function getChildFile($name) {
		if (!$this->isValid())
			return null;
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_FILES.'` WHERE `parent`='.$this->id.' AND `name`="'.xGbl::$mysql->exec($name).'" LIMIT 1;');
		$r = $res->fetch_assoc();
		$res->free();
		if ($r)
			return xFile::load($r);
		return null;
	}
	/**
	 * Remove a folder
	 */
	public function delete() {
		if (!$this->isValid() || $this->isRoot())
			return false;
		$owner = new xUser($this->owner + 0);
		$this->applyToSubFolders('DELETE FROM', '', true);
		$delDirs = xGbl::$mysql->getAffectRows();
		$this->applyToSubFiles('DELETE FROM');
		$delFiles = xGbl::$mysql->getAffectRows();
		if ($this->size)
			$this->applyToParents('UPDATE', 'SET `folders` = IF(`id` = '.$this->parent.', `folders` - 1, `folders`), `size` = `size` - '.$this->size, false);
		elseif ($this->parent)
			xGbl::$mysql->exec('UPDATE `'.TB_FOLDERS.'` SET `folders` = `folders` - 1, `size` = `size` - '.$this->size.' WHERE `id` = '.$this->parent.' LIMIT 1;');
		$owner->add('disk_usage', '-'.$this->size);
		$owner->add('folders', -$delDirs);
		$owner->add('files', -$delFiles);
		$owner->update();
		return array('folders' => $delDirs, 'files' => $delFiles);
	}
	
	public function containShare() {
		$cr = xGbl::$mysql->exec('SELECT COUNT(0) FROM `'.TB_FOLDERS.'` WHERE `owner`='.$this->owner.' AND `depth` > '.$this->depth.' AND SUBSTR(`path`, 1, '.strlen($this->getFullPath(true)).') = "'.$this->getFullPath(true).'" AND `mr0` & '.FILEM_SHARED_R0.';');
		list($folderc) = $cr->fetch_array();
		$cr->free();
		$cr = xGbl::$mysql->exec('SELECT COUNT(0) FROM `'.TB_FILES.'` WHERE `owner`='.$this->owner.' AND `depth` > '.$this->depth.' AND SUBSTR(`path`, 1, '.strlen($this->getFullPath(true)).') = "'.$this->getFullPath(true).'" AND `mr0` & '.FILEM_SHARED_R0.';');
		list($filec) = $cr->fetch_array();
		$cr->free();
		if($folderc == '0' && $filec == '0')
			return false;
		else
			return true;
	}
}
#.mod[/file]

#.mod[share]
class xShare extends xItemObj {
	/**
	 * Update changes in share info.
	 */
	public function update() {
		return $this->doUpdate(TB_SHARES);
	}
	public function __construct($id = false) {
		if ($id)
			$this->select($id);
	}
	/**
	 * Select a share id and load it into this object.
	 * @param uint $id
	 * @return bool
	 */
	public function select($id) {
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_SHARES.'` WHERE `id`='.$id.' LIMIT 1;');
		$this->m_data = $res->fetch_assoc();
		$res->free();
		if ($this->m_data)
			return true;
		throw new xError($id, xError::ERR_BAD_ARGS);
	}
	public static function load($data) {
		$tmp = new xShare();
		$tmp->m_data = $data;
		return $tmp;
	}
	/**
	 * 
	 * Enter description here ...
	 * @param Mixed(xFile|xFolder) $resObj
	 * @param xUser $to
	 * @param uint32 $toType
	 * @param uint32 $expires
	 * @param string $password
	 * @return xShare
	 */
	public static function create($resObj, $to, $toType = SHARE_TO_ANYONE, $expires = 0, $password = false) {
		if ($resObj instanceof xFile)
			$resType = SHARE_TYPE_FILE;
		elseif ($resObj instanceof xFolder)
			$resType = SHARE_TYPE_FOLDER;
		else
			throw new xError($resObj->name, xError::ERR_BAD_ARGS);
		if ($resObj->is(FILEM_SHARED_R0) || self::access($resObj, 0))
			throw new xError($resObj->name, xError::ERR_DUP);
		xGbl::$mysql->exec('INSERT INTO `'.TB_SHARES.'` SET `restype`='.$resType.', `resid`='.$resObj->id.', `owner`='.$resObj->owner.', `to`='.$to->id.',`shared`='.TIMENOW.', `expires`='.($expires?TIMENOW+$expires:0).',`password`="'.($password?md5($password):'').'";');
		if ($ret = xGbl::$mysql->getInsertID())
			$ret = new xShare(xGbl::$mysql->getInsertID());
		$resObj->mr0 |= FILEM_SHARED_R0;
		$resObj->update();
		return $ret;
	}
	/**
	 * 
	 * Enter description here ...
	 * @param Mixed(xFile|xFolder) $resObj
	 * @param Mixed(xUser) $user
	 * @param uint32 $mr0
	 */
	public static function access($resObj, $user) {

		if ($resObj instanceof xFile)
			$type = SHARE_TYPE_FILE;
		elseif ($resObj instanceof xFolder)
			$type = SHARE_TYPE_FOLDER;
		else
			throw new xError($resObj->name, xError::ERR_BAD_ARGS);
		$items = $resObj->getParentIds();
		$sqlAdd = '';
		if ($items)
			$sqlAdd = ' OR (`restype`='.SHARE_TYPE_FOLDER.' AND `resid` IN ('.join(',', $items).'))';
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_SHARES.'` WHERE (`restype`='.$type.' AND `resid`='.$resObj->id.')'.$sqlAdd.' HAVING (`expires`=0 OR `expires` > '.TIMENOW.')

;');
		$ret = $res->num_rows;
		if ($ret) {
			$t = array();
			while ($r = $res->fetch_assoc())
				$t[$r['resid']] = $r;
			$items[] = $resObj->id;
			$items = array_reverse($items);
			foreach ($items as $id)
				if (isset($t[$id])) {
					$ret = xShare::load($t[$id]);
					break;
				}
		}
		$res->free();
		return $ret;
	}
	public static function listItems($owner = false, $start = 0) {
		$where = '';
		if($owner !== false) {
			$where = ' WHERE s.`owner` = "'.$owner->id.'"';
		}
		if($where == '') $where .= ' WHERE';
		else $where .= ' AND';
		return xGbl::$mysql->exec('SELECT s.*, u.`username`, f.`key` AS `filekey`, d.`key` AS `folderkey`, f.`name` AS `filename`, d.`name` AS `foldername`, f.`size` AS `filesize`, d.`size` AS `foldersize` FROM `'.TB_SHARES.'` s LEFT JOIN `'.TB_USERS.'` u ON u.`id` = s.`owner` LEFT JOIN `'.TB_FILES.'` f ON s.`restype` = '.SHARE_TYPE_FILE.' AND f.`id` = s.`resid` LEFT JOIN `'.TB_FOLDERS.'` d ON s.`restype` = '.SHARE_TYPE_FOLDER.' AND d.`id` = s.`resid`'.$where.' f.`name` IS NOT NULL OR d.`name` IS NOT NULL ORDER BY s.`shared` DESC LIMIT '.$start.', 10;');
	}
	/**
	 * Cancel a sharing of a resource.
	 * @param Mixed(xFile/xFolder) $resObj
	 * @param xUser $owner
	 * @param xUser $to
	 * @param uint32 $toType
	 */
	public static function cancel($resObj, $owner, $to, $toType = SHARE_TO_ANYONE) {
		if ($resObj instanceof xFile)
			$resType = SHARE_TYPE_FILE;
		elseif ($resObj instanceof xFolder)
			$resType = SHARE_TYPE_FOLDER;
		else
			throw new xError($resObj->name, xError::ERR_BAD_ARGS);
		if (!($resObj->mr0 & FILEM_SHARED_R0))
			throw new xError($resObj->name, xError::ERR_INVALID_OBJ);
		$resObj->mr0 &= ~FILEM_SHARED_R0;
		$resObj->update();
		
		xGbl::$mysql->exec('DELETE FROM `'.TB_SHARES.'` WHERE `resid` = '.$resObj->id.' AND `restype`='.$resType.' AND `owner`='.$owner->id.'

LIMIT 1;');
		return xGbl::$mysql->getAffectRows();
	}
	public static function gc($limit = 0) {
		xGbl::$mysql->exec('DELETE FROM `'.TB_SHARES.'` WHERE (`expires` > 0 AND `expires` < '.TIMENOW.') OR (`restype`= '.SHARE_TYPE_FILE.' AND NOT EXISTS (SELECT * FROM `'.TB_FILES.'` WHERE `id` = `resid` LIMIT 1)) OR (`restype` = '.SHARE_TYPE_FOLDER.' AND NOT EXISTS (SELECT * FROM `'.TB_FOLDERS.'` WHERE `id` = `resid` LIMIT 1))'.($limit?' LIMIT '.$limit: ''));
	}
}
#.mod[/share]

#.mod[autosession]
class xAutoSession {
	public static $autologin;
	public static $session_id;
	public static function gc($forced = false) {
		xGbl::$mysql->exec('DELETE FROM `sessions` WHERE `lastdate` < '.(TIMENOW - 2592000));
	}
	protected static function setupCookies() {
		if (self::$session_id) {
		$_COOKIE[session_name()] = self::$session_id;
		setcookie(session_name(), self::$session_id, self::$autologin);
		}
		self::setupAutologinCookies();
	}
	protected static function setupAutologinCookies() {
		if (self::$autologin) {
		$_COOKIE[SES_AUTOLOGIN] = self::$autologin;
		setcookie(SES_AUTOLOGIN, self::$autologin, self::$autologin);
		} elseif (isset($_COOKIE[SES_AUTOLOGIN])) {
		setcookie(SES_AUTOLOGIN, 0, TIMENOW - 3600);
		unset($_COOKIE[SES_AUTOLOGIN]);
		}
	}
	public static function setAutoLogin($status = true) {
		self::$autologin = $status?(TIMENOW + SES_EXPIRE_SEC): 0;
		self::setupAutologinCookies();
	}
	protected static function cleanCookies() {
		unset($_COOKIE[SES_AUTOLOGIN], $_COOKIE[session_name()]);
		setcookie(session_name(), 0, TIMENOW - 3600);
		setcookie(SES_AUTOLOGIN, 0, TIMENOW - 3600);
	}
	public static function start($forceRebuild = false) {
		session_cache_expire(SES_EXPIRES);
		session_name(SES_NAME);
		if (!$forceRebuild && isset($_COOKIE[session_name()]) && strlen($_COOKIE[session_name()])<=32) {
			session_id($_COOKIE[SES_NAME]);
			session_start();
			self::$session_id = session_id();
			if (isset($_COOKIE[SES_AUTOLOGIN]))
				self::setAutoLogin();
			if (!isset($_SESSION['ip'])) {
				if (self::$autologin) {
					$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_SESSIONS.'` WHERE `id`="'.xGbl::$mysql->escape(self::$session_id).'" LIMIT 1;');
					$ses = $res->fetch_assoc();
					$res->free();
					unset($res);
					if ($ses) {
						foreach (array('user', 'lastdate', 'ip') as $key)
							$_SESSION[$key] = 0 + $ses[$key];
						self::setupCookies();
						self::update();
					} else {
						self::register();
						return true;
					}
				} else {
					self::register();
					return true;
				}
			}
			self::setupCookies();
			if (isset($_SESSION['user']) && $_SESSION['user']) {
				if (!xUser::autologin()) { # account doesn't exist
					self::clean();
					return self::start(true);
				}
				if (xUser::$cur->lastip != xIPMgr::$remoteIP) {
					xIPMgr::record($_SESSION['user'], $_SESSION['lastdate']);
					xUser::$cur->lastip = xIPMgr::$remoteIP;
				}
			}
			return true;
		} else {
			if (self::$session_id)
				@self::clean();
			session_name(SES_NAME);
			session_start();
			self::$session_id = session_id();
			self::register();
			self::setupCookies();
			return true;
		}
		return false;
	}
	public static function register() {
		$_SESSION = array('ip' => xIPMgr::$remoteIP, 'user' => 0, 'lastdate' => TIMENOW);
		self::setupCookies();
		if (self::$autologin)
			xGbl::$mysql->exec('INSERT INTO `'.TB_SESSIONS.'` SET `user`='.$_SESSION['user'].', `ip`='.$_SESSION['ip'].', `lastdate`='.TIMENOW.', `id`="'.xGbl::$mysql->escape(self::$session_id).'";');
	}
	public static function update() {
		self::setupCookies();
		if (self::$autologin) {
			xGbl::$mysql->exec('UPDATE `'.TB_SESSIONS.'` SET `ip`='.$_SESSION['ip'].', `user`='.$_SESSION['user'].', `lastdate`='.TIMENOW.' WHERE `id`='.self::$session_id.' LIMIT 1;');
			if (!xGbl::$mysql->getAffectRows())
				xGbl::$mysql->exec('INSERT INTO `'.TB_SESSIONS.'` SET `user`='.$_SESSION['user'].', `ip`='.$_SESSION['ip'].', `lastdate`='.TIMENOW.', `id`="'.xGbl::$mysql->escape(self::$session_id).'";');
		}
	}
	public static function clean() {
		session_destroy();
		self::cleanCookies();
		if (self::$autologin)
			xGbl::$mysql->exec('DELETE FROM `'.TB_SESSIONS.'` WHERE `id`="'.xGbl::$mysql->escape(self::$session_id).'" LIMIT 1;');
		self::$autologin = 0;
		self::register();
	}
}
#.mod[/autosession]

#.mod[lang]
class xLang {
	public static $sCurLangType;
	public static $sCurLang;
	/**
	 * Setup the language.
	 * @param string $lang name of language package.
	 * @param string $autoload auto-load language package if is a filename.
	 */
	public static function setLang($lang = LANG_DEFAULT, $autoload = false) {
		self::$sCurLangType = $lang;
		if ($autoload)
			self::load($autoload);
	}
	/**
	 * Load language package file.
	 * @param string $page language package filename
	 */
	public static function load($page = false) {
		if (!$page) {
			$page = $_SERVER['SCRIPT_NAME'];
			include __LANG__.self::$sCurLangType.$page;
		} else
			include __LANG__.self::$sCurLangType.'/'.$page;
		self::$sCurLang = $_THIS_LANG;
	}
	/**
	 * Get all elements as array from language package.
	 */
	public static function getArray() {
		return self::$sCurLang;
	}
	/**
	 * Get element in language package.
	 * @param string $id element id
	 */
	public static function get($id) {
		return self::$sCurLang[$id];
	}
	/**
	 * Get current language name.
	 * @param unknown_type $id
	 */
	public static function getLang($id) {
		return self::$sCurLangType;
	}
}
#.mod[/lang]

#.mod[key]
class xKey {
	protected $m_data;
	public function __construct($key = false, $module = 0, $length = 32, $user = null, $times = 1, $usage = 0, $expire = 0) {
		if ($key === true)
			$this->create($module, $length, $user, $times, $usage, $expire);
		elseif ($key)
			$this->select($key);
	}
	public function create($module, $length = 32, $user = null, $times = 1, $usage = 0, $expire = 0) {
		$key = '';
		do {
			$key = xItemObj::$stru->randomString($length);
			xGbl::$mysql->exec('INSERT INTO `'.TB_KEYS.'` SET `key`="'.$key.'", `times`='.$times.', `usage`='.$usage.', `expire`='.$expire.', `module`='.$module.', `user`='.$user->id);
		} while (xGbl::$mysql->getErrno() == 1062);
		return $this->select($key);
	}
	public function select($key = false) {
		if ($this->m_data && !$key)
			$key = $this->m_data['id'];
		$res = xGbl::$mysql->exec('SELECT * FROM `'.TB_KEYS.'` WHERE `key`="'.xGbl::$mysql->escape($key).'" LIMIT 1;');
		if ($r = $res->fetch_assoc()) {
			$res->free();
			$this->m_data = $r;
			return true;
		}
		return false;
	}
	public function __get($varName) {
		return $this->m_data[$varName];
	}
	public function __isset($varName) {
		return isset($this->m_data[$varName]);
	}
	public function isUnlimited() {
		return ($this->times == -1);
	}
	public function isLoaded() {
		return $this->m_data? true: false;
	}
	public function x($x = false) {
		if ($this->isLoaded()) {
			$rtn = $this->m_data['x'];
			if (is_int($x))
				$this->m_data['x'] = $x;
			return $rtn;
		}
		return null;
	}
	public function y($y = false) {
		if ($this->isLoaded()) {
			$rtn = $this->m_data['y'];
			if (is_int($y))
				$this->m_data['y'] = $y;
			return $rtn;
		}
		return null;
	}
	public function update() {
		if ($this->isLoaded()) {
			xGbl::$mysql->exec('UPDATE `'.TB_KEYS.'` SET `x`='.$this->m_data['x'].', `y`='.$this->m_data['y'].' WHERE `KEY`="'.$this->m_data['key'].'" LIMIT 1;');
			return xGbl::$mysql->getAffectRows()? true: false;
		}
		return false;
	}
	public function isValid() {
		return ($this->isLoaded() && (!$this->expire || $this->expire > TIMENOW) && $this->times)? true: false;
	}
	public function isUsing() {
		return ($this->isLoaded() && $this->using)? true: false;
	}
	public function begin($module, $usage, $user = 0, $times = 1) {
		if (!$this->isValid() || $this->times < $times)
			return false;
		if ($this->module != $module || $this->usage != $usage || $this->user != $user)
			return false;
		xGbl::$mysql->exec('UPDATE `'.TB_KEYS.'` SET `times` = `times` - IF(`times` = -1, 0, '.$times.'), `using` = '.$times.' WHERE `key`="'.$this->key.'" AND `using`=0 AND (`times`=-1 OR `times`>='.$times.') AND (`expire`=0 OR `expire` > '.TIMENOW.') LIMIT 1;');
		if (!xGbl::$mysql->getAffectRows())
			return false;
		if ($this->m_data['times'] != -1)
			$this->m_data['times'] -= $times;
		$this->m_data['using'] = $times;
		return true;
	}
	public function commit() {
		if (!$this->isUsing())
			return false;
		xGbl::$mysql->exec('UPDATE `'.TB_KEYS.'` SET `using` = 0 WHERE `key`="'.$this->key.'" AND `using` LIMIT 1;');
		if (!xGbl::$mysql->getAffectRows())
			return false;
		$this->m_data['using'] = 0;
		return true;
	}
	public function rollback() {
		if (!$this->isUsing())
			return false;
		xGbl::$mysql->exec('UPDATE `'.TB_KEYS.'` SET `times` = `times` + IF(`times` = -1, 0, `using`), `using` = 0 WHERE `key`="'.$this->key.'" AND `using` LIMIT 1;');
		if (!xGbl::$mysql->getAffectRows())
			return false;
		if ($this->m_data['times'] != -1)
			$this->m_data['times'] += $times;
		$this->m_data['using'] = 0;
		return true;
	}
	public function verify($module, $usage, $user = 0, $times = 1) {
		if (!$this->isValid())
			return false;
		if ($this->module != $module || $this->usage != $usage || $this->user != $user)
			return false;
		xGbl::$mysql->exec('UPDATE `'.TB_KEYS.'` SET `times` = `times` + IF(`times` = -1, 0, -'.$times.') WHERE `key`="'.$this->key.'" AND (`times`=-1 OR `times`>='.$times.') AND (`expire`=0 OR `expire` > '.TIMENOW.') LIMIT 1;');
		if (!xGbl::$mysql->getAffectRows())
			return false;
		if ($this->m_data['times'] != -1)
			$this->m_data['times'] -= $times;
		$this->m_data['using'] = 0;
		return true;
	}
	public static function gc() {
		xGbl::$mysql->exec('DELETE FROM `'.TB_KEYS.'` WHERE (`expire`>0 AND `expire` < '.TIMENOW.') OR (`times`=0 AND `using`=0);');
	}
}
#.mod[/key]

#.mod[net]
class xNet {
	public static function post($url, $data) {
		$ch = curl_init($url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_POST, true);
		curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
		curl_setopt($ch, CURLOPT_TIMEOUT, 10);
		$output = curl_exec($ch);
		curl_close($ch);
		if ($output) return $output;
		else return false;
	}
	
	public static function get($url) {
		$ch = curl_init($url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_TIMEOUT, 10);
		$output = curl_exec($ch);
		curl_close($ch);
		if ($output) return $output;
		else return false;
	}
	
	public static function upload($url, $file = '', $data = array()) {
		$postData = array();
		foreach($data as $key => $val) $postData[$key] = $val;
		$postData['Filedata'] = '@'.$file;
		$ch = curl_init($url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_POST, true);
		curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
		$output = curl_exec($ch);
		curl_close($ch);
		if ($output) return $output;
		else return false;
	}
}
#.mod[/net]
#.end