PHPIndex

This page lists files in the current directory. You can view content, get download/execute commands for Wget, Curl, or PowerShell, or filter the list using wildcards (e.g., `*.sh`).

analysis.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/analysis.class.php'
View Content
<?php

class adminAnalysis extends Controller{
	function __construct() {
        parent::__construct();
        $this->model = Model('Analysis');
    }

    public function option(){
        $list   = array('user', 'file', 'access', 'server');
        $type   = Input::get('type','in',null,$list);
        $result = $this->model->option($type);
        show_json($result);
    }

    public function chart(){
        $data = Input::getArray(array(
            'userID'    => array("check"=>"int","default"=>null),
            'groupID'   => array("check"=>"int","default"=>null),
        ));
        $result = $this->model->fileChart($data);
        show_json($result);
    }

    // 计划任务写入记录:regist、store
    public function record(){
		$type   = Input::get('type','in',null,array('regist', 'store'));
		$result = $this->model->record($type);

		$msg = !!$result ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$result);
    }

    // 列表:用户空间、部门空间
    public function table(){
		$type = Input::get('type','in',null,array('user', 'group'));
        $result = $this->model->listTable($type);
        show_json($result);
    }

    /**
     * 趋势:userTrend、storeTrend
     * userTrend: 每日增长(regist,写计划任务)、每日登录(log)
     * storeTrend: 使用空间、时间使用——计划任务
     * @return void
     */
    public function trend(){
        $data = Input::getArray(array(
            'type' => array('check' => 'require', 'default' => 'user'), // user/store
            'time' => array('check' => 'require', 'default' => 'day'),  // day/week/month/year
        ));
        $result = $this->model->trend($data['type'], $data['time']);
        show_json($result);
    }
}
auth.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/auth.class.php'
View Content
<?php
/*
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*/

//权限组管理
class adminAuth extends Controller{
	private $model;
	function __construct()    {
		parent::__construct();
		$this->model = Model('Auth');
	}

	/**
	 * 根据所在部门获取用户列表
	 */
	public function get() {
		$result = $this->model->listData();
		show_json($result,true);
	}

	/**
	 * 添加用户
	 */
	public function add() {
		$data = Input::getArray(array(
			"name" 		=> array("check"=>"require"),
			"display" 	=> array("check"=>"int","default"=>0),
			"auth" 		=> array("check"=>"int"),
			"label" 	=> array("check"=>"require","default"=>null),
		));
		$res = $this->model->add($data);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error') . '! ' . LNG('explorer.pathExists');
		show_json($msg,!!$res);
	}

	/**
	 * 编辑 
	 */
	public function edit() {
		$data = Input::getArray(array(
			"id"		=> array("check"=>"int"),
			"name" 		=> array("check"=>"require","default"=>null),
			"display" 	=> array("check"=>"int","default"=>null),
			"auth" 		=> array("check"=>"int","default"=>null),
			"label" 	=> array("check"=>"require","default"=>null),
			// "sort" 		=> array("check"=>"require","default"=>0),
		));
		$res = $this->model->update($data['id'],$data);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error') . '! ' . LNG('explorer.pathExists');
		return show_json($msg,!!$res);
	}

	/**
	 * 删除
	 */
	public function remove() {
		$id = Input::get('id','int');
		// 是否强制删除;
		if(!isset($this->in['force'])){
			// 判断是否被使用
			$cnt1 = Model('SourceAuth')->where(array('authID' => $id))->count();
			$cnt2 = Model('user_group')->where(array('authID' => $id))->count();
			$count = (int) $cnt1 + (int) $cnt2;
			if($count) show_json(LNG('admin.auth.delErrTips').'('.$count.')', false,'in-user');
		}
		$res = $this->model->remove($id);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}

	// 移动排序、拖拽排序
	public function sort() {
		$ids = Input::get('ids', 'require');
		$ids = explode(',', $ids);
		foreach($ids as $i => $id) {
			$this->model->sort($id,array("sort"=> $i));
		}
		show_json(LNG('explorer.success'));
	}
}
autoRun.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/autoRun.class.php'
View Content
<?php 
/**
 * 自动执行
 */
class adminAutoRun extends Controller {
	function __construct()    {
		parent::__construct();
	}

	public function index(){
        ActionCall('admin.log.hookBind');	// 绑定日志hook
    }
}
autoTask.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/autoTask.class.php'
View Content
<?php
/**
 * 计划任务
 */
class adminAutoTask extends Controller {
	function __construct()    {
		parent::__construct();
		$this->model = Model('SystemTask');
	}

	/**
	 * 计划任务列表
	 * @return void
	 */
    public function get(){
		$result = $this->model->listData();
		$error = false;
		foreach ($result as $i => $item) {	// 兼容列表异常数据
		    if (!isset($item['name'])) {
				unset($result[$i]);
				$error = true;
			}
		}
		if ($error) $result = array_values($result);
		show_json($result,true);
	}

	/**
	 * 计划任务添加
	 */
	public function add(){
		$data = Input::getArray(array(
			'name'		=> array('check'=>'require'),	// 名称
			'type'		=> array('check'=>'require'),	// 类型:方法、URL
			'event'		=> array('check'=>'require'),	// 任务值:方法、url地址
			'time'		=> array('check'=>'require'),	// 周期:json
			'desc'		=> array('default'=>''),		// 描述
			'enable'	=> array('default'=>'0'),		// 是否启用
			'system'	=> array('default'=>'0'),		// 系统默认
		));
		$this->checkEvent($data);
		$result = $this->model->add($data);
		$msg = !!$result ? LNG('explorer.success') : LNG('explorer.repeatError');
		show_json($msg,!!$result);
	}
	
	/**
	 * 更新任务信息
	 */
	public function edit(){
		$data = Input::getArray(array(
			"id"    	=> array("check"=>"number"),
			'name'		=> array('check'=>'require'),	// 名称
			'type'		=> array('check'=>'require'),	// 类型:方法、URL
			'event'		=> array('check'=>'require'),	// 任务值:方法、url地址
			'time'		=> array('check'=>'require'),	// 周期:json
			'desc'		=> array('default'=>''),		// 描述
			'enable'	=> array('default'=>'0'),		// 是否启用
			'system'	=> array('default'=>'0'),		// 系统默认
		));
		$this->checkEvent($data);
		$result = $this->model->update($data['id'],$data);
		$msg = !!$result ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$result);
	}

	private function checkEvent($data) {
		$action = $data['event'];
		if($data['type'] == 'url') {
			if(!Input::check($action, 'url')){
				show_json('url error!', false);
			}
			return;
		} 
		$last 	  	= strrpos($action,'.');
		$className	= substr($action,0,$last);
		$method   	= substr($action,$last + 1);
		$obj 		= Action($className);
		if(!$obj || !method_exists($obj,$method)){
			show_json("[{$action}] method not exists!", false);
		}
	}

	/**
	 * 启动|关闭某个任务
	 */
    public function enable(){
		$data = Input::getArray(array(
			"id"    	=> array("check"=>"number"),
			"enable"	=> array("check"=>"bool"),
		));
		$result = $this->model->enable($data['id'],(bool)$data['enable']);
		$msg = !!$result ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$result);
	}	
	/**
	 * 删除计划任务
	 */
    public function remove(){
		$id = Input::get('id','int');
		$result = $this->model->remove($id);
		$msg = !!$result ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$result);
	}
	
	/**
	 * 手动立即执行某个任务
	 */
    public function run(){
		$id = Input::get('id','int');
		$task  = Model("SystemTask")->listData($id);
        if($task){
            $result = AutoTask::taskRun($task);
		}
		$msg = !!$result ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$result);
	}
	
	
	// 开启关闭计划任务; 
	public function taskSwitch(){
		$data  = Input::getArray(array(
			"status"	=> array("check"=>"bool"),
			"delay"		=> array("check"=>"int","default"=>10),
		));
		// Cache::deleteAll();
		AutoTask::config($data['status'],$data['delay']);
		show_json($data);
	}
	public function taskRestart(){
		AutoTask::restart();
		sleep(1);
		AutoTask::start();
	}

	// 移动排序、拖拽排序
	public function sort() {
		$ids = Input::get('ids', 'require');
		$ids = explode(',', $ids);
		foreach($ids as $i => $id) {
			$this->model->update($id,array("sort"=> $i));
		}
		show_json(LNG('explorer.success'));
	}
}
backup.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/backup.class.php'
View Content
<?php
// 数据备份
class adminBackup extends Controller{
	private $model;
	function __construct()    {
		parent::__construct();
		$this->model = Model('Backup');
	}

	/**
	 * 初始化备份计划任务
	 * @return void
	 */
	public function taskInit($config=false){
		$optModel = Model('systemOption');
		$tskModel = Model('SystemTask');
		// 获取配置
		if (!$config) $config = $this->model->configSimple();

		// 初始化标识
		$autoTaskKey = 'autoTaskInit';
		$fileTaskKey = 'fileTaskInit';
		$isTaskInit = array(
			$autoTaskKey => $optModel->get($autoTaskKey, 'backup'),
			$fileTaskKey => $optModel->get($fileTaskKey, 'backup'),
		);
		$taskEvent = array(
			$autoTaskKey => 'admin.backup.autoTask',
			$fileTaskKey => 'admin.backup.fileTask',
		);

		// 1.没有启用,删除任务
		if ($config['enable'] != '1') {
			foreach ($isTaskInit as $initKey => $initVal) {
				if($initVal != 'ok') continue;
				$task = $tskModel->findByKey('event', $taskEvent[$initKey]);
				if ($task) $tskModel->remove($task['id'],true);
				$optModel->set($initKey, '', 'backup');
			}
			return;
		}
		// 2.有启用,更新原任务(兼容旧版)
		if($optModel->get('autoTaskUpdate','backup') != 'ok') {
			// 旧任务时间,可以直接删除,但还得在添加时更新(时间)
			$task = $tskModel->findByKey('event','admin.backup.start');
			if ($task) {
				$update = array(
					'event'		=> 'admin.backup.autoTask',
					'desc'		=> LNG('admin.backup.taskDbDesc'),
					'enable'	=> 1,
				);
				$tskModel->update($task['id'], $update);
				$isTaskInit[$autoTaskKey] = 'ok';
				$optModel->set($autoTaskKey,'ok','backup');
			}
			$optModel->set('autoTaskUpdate','ok','backup');
		}
		// 3.添加任务
		// 授权失效后已存在的文件备份任务应该删除,考虑到内容已切换,可以不处理
		foreach ($isTaskInit as $initKey => $initVal) {
			if ($initVal == 'ok') continue;
			if ($initKey == $fileTaskKey && $config['content'] != 'all') continue;
			$data = $this->taskInitData($initKey);
			if(!$tskModel->add($data)) return;
			$optModel->set($initKey,'ok','backup');
		}
	}
	private function taskInitData($taskKey) {
		$data = array(
			'autoTaskInit' => array(
				'name'	=> LNG('admin.task.backup'),
				'type'	=> 'method',
				'event' => 'admin.backup.autoTask',
				'time'	=> '{"type":"day","month":"1","week":"1","day":"02:00","minute":"10"}',
				'desc'	=> LNG('admin.backup.taskDbDesc'),
				'enable' => 1,
				'system' => 1,
			),
			'fileTaskInit' => array(
				'name'	=> LNG('admin.task.backup').' ('.LNG('common.file').')',
				'type'	=> 'method',
				'event' => 'admin.backup.fileTask',
				'time'	=> '{"type":"minute","minute":"60"}',
				'desc'	=> LNG('admin.backup.taskFileDesc'),
				'enable' => 1,
				'system' => 1,
			),
		);
		return $data[$taskKey];
	}

	/**
	 * 计划任务配置信息
	 * @return void
	 */
    public function config(){
		// 0.前端(其他)请求
		$this->bakConfig();
		// 1.获取备份配置信息
		$data = $this->model->config(true);
		// pr($data);exit;
		if (!$data) {
			show_json(LNG('admin.backup.errInitTask'), false);
		}
		// 2.获取当前数据库类型
		$database = array_change_key_case($GLOBALS['config']['database']);
		$data['dbType'] = Action('admin.server')->_dbType($database);	// mysql/sqlite
		// 3.获取最近一条备份记录
		$last	= $this->model->lastItem();
		if($data['enable'] != '1') {
		    $process= null;
		    if(isset($last['status'])) $last['status'] = 1;
		} else {
		    $process= $this->model->process();	// 备份进度
		}
		$info	= array('last' => $last, 'info' => $process);
		show_json($data, true, $info);
	}
	private function bakConfig(){
		// 获取最近一条备份记录
		if (Input::get('last',null,0) == '1') {
			$last = $this->model->lastItem();
			// if($last && $last['name'] != date('Ymd')) $last = null;
			show_json($last);
		}
		if (Input::get('check',null,0) != '1') return;
		// 检查备份是否有效
		$io = Input::get('io', 'int');
		$check = $this->checkStore($io);
		if ($check !== true) {
			show_json($chk, false);
		}
		// 检查是否存在系统数据
		$cnt = Model('File')->where(array('ioType'=>$io))->count();
		if ($cnt) {show_json(LNG('admin.backup.addStoreHasFile'), false);}
	}

	/**
	 * 获取备份列表
	 */
	public function get() {
		$id		= Input::get('id',null,null);
		$result	= $this->model->listData($id);
		$info	= $id ? $this->model->process() : array();
		if (!$id) $this->_getDataApply($result);
		show_json($result,true, $info);
	}
	// 追加备份所在存储,便于识别管理
	private function _getDataApply(&$data){
		if (empty($data)) return;
		$list = Model('Storage')->listData();
		$list = array_to_keyvalue($list, 'id', 'name');
		foreach ($data as &$item) {
			$io = $item['io'];
			$item['ioName'] = isset($list[$io]) ? $list[$io] : '0';
		}
	}

	/**
	 * 删除备份记录
	 */
	public function remove() {
		$id  = Input::get('id','int');
		$res = $this->model->remove($id);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
    }
	
	// 激活授权,自动开启备份;(没有开启时,设置仅备份数据库;备份到默认存储)
	public function initStart($status){
		// 1.获取配置信息,已激活则不处理——计划任务不一定开启,暂不处理
		$backup = Model('SystemOption')->get('backup');
		$backup = json_decode($backup, true);
		if (!$backup) $backup = array();
		if ($backup['enable'] == '1') return;

		// 2.添加/更新(激活)配置
		$driver = KodIO::defaultDriver();
		$backup['io'] = $driver['id'];
		$backup['content'] = 'sql';	// 备份内容:all/sql
		$backup['enable'] = 1;
		Model('SystemOption')->set('backup', $backup);

		// 3.添加并激活计划任务
		$this->taskInit($backup);
	}

	/**
	 * 计划任务
	 * @return void
	 */
	public function autoTask() {
		if (!KodUser::isRoot()) return;
		return $this->start(true);
	}
	/**
	 * 计划任务(文件)
	 * @return void
	 */
	public function fileTask() {
		if (!KodUser::isRoot()) return;
		return $this->start(true, 'file');
	}

    /**
	 * 备份——终止http请求,后台运行
	 * @param boolean $runTask
	 * @param boolean $type	备份内容:db/file,默认为db
	 * @return void
	 */
    public function start($runTask=false,$type=''){
		// 0.获取备份内容/类型
		if (empty($type)) {
			$type = Input::get('type',null,'db');
		}
		// 1.检查备份是否开启
		$config = $this->model->config();
		if($config['enable'] != '1') {
			if ($runTask) return;
			show_json(LNG('admin.backup.notOpen'), false);
		}

		// 2.检查存储是否有效
		// 2.1 检查是否为默认存储——文件备份
		if ($type == 'file') {
			$driver = KodIO::defaultDriver();
			if ($driver['id'] == $config['io']) {
				if ($runTask) return;
				show_json(LNG('admin.backup.needNoDefault'), false);
			}
		}
		// 2.2 检查存储是否有效
		$check = $this->checkStore($config['io']);
		if ($check !== true) {
			if ($runTask) return;
			show_json($check, false);
		}

		// 3.检查临时目录是否可写——数据库备份
		if ($type != 'file') {
			mk_dir(TEMP_FILES);
			if(!path_writeable(TEMP_FILES)) {
				show_json(LNG('admin.backup.pathNoWrite'), false);
			}
		}

		// 手动执行,终止http请求
		if (!$runTask) {
			echo json_encode(array('code'=>true,'data'=>'OK'));
			http_close();
		}
		return $this->model->start($type);
    }
	// 检查存储是否有效
	private function checkStore($io){
		$model = Model('Storage');
		$data = $model->listData($io);
		if (!$data) show_json(LNG('admin.backup.storeNotExist'), false);
		return $model->checkConfig($data, true);
	}
    
    /**
	 * 还原,禁止任何操作——未实现
	 * @return void
	 */
    public function restore(){
        $id  = Input::get('id','int');
        echo json_encode(array('code'=>true,'data'=>'OK'));
        http_close();
        $this->model->restore($id);
	}

	/**
	 * 终止备份
	 * @return void
	 */
	public function kill(){
		$id  = Input::get('id','int');
		$res = $this->model->kill($id);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}
}
group.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/group.class.php'
View Content
<?php
/*
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*/

class adminGroup extends Controller{
	private $model;
	function __construct(){
		parent::__construct();
		$this->model = Model('Group');
	}

	/**
	 * 部门子部门获取
	 * 
	 * 搜索用户过滤: 自己可见范围的根部门; 外链分享: 最近+自己所在部门跟部门+对外授权(所在根部门不等于可见根部门时显示); 权限设置同理(根据所在部门传入parentID)
	 * 后台拉取信息: 需要传入requestFromType=admin; 即拉取自己为部门管理员的部门;
	 */
	public function get() {
		$data = Input::getArray(array(
			"parentID"		=> array("check"=>"require",'default'=>''),
			"rootParam" 	=> array("check"=>"require",'default'=>''),
		));
		$userGroupAdmin 	= Action("filter.userGroup")->userGroupAdmin();		// 所在部门为管理员的部门;可多个
		$userGroupAt 		= Action("filter.userGroup")->userGroupAt();		// 所在部门根部门(有包含关系时只显示最上层); 可多个
		$userGroupRootShow  = Action("filter.userGroup")->userGroupRootShow(); 	// 所在部门根部门可见范围;可多个(根据上级部门可见范围决定)
		$requestAdmin   	= isset($this->in['requestFromType']) && $this->in['requestFromType'] == 'admin'; // 后端用户列表;
		
		if(!$data['parentID'] || $data['parentID'] == 'root' || $data['parentID'] == 'rootOuter'){
			if($requestAdmin && $userGroupAdmin){$data['parentID'] = $userGroupAdmin;}
			if(!$requestAdmin && $userGroupAt && $data['parentID'] != 'rootOuter'){$data['parentID']   = $userGroupAt;}
			if($data['parentID'] == 'rootOuter'){$data['parentID'] = $userGroupRootShow;}
			if(KodUser::isRoot()){$data['parentID'] = array('1');}
		}
		// pr($userGroupAdmin,$userGroupAt,$userGroupRootShow,$data);exit;		
		if($data['rootParam']){// 有缓存未更新是否有子部门及用户的问题;
			Model('Group')->cacheFunctionClear('getInfo',$data['parentID']);
		}
		$result = array("list"=>array(),'pageInfo'=>array());
		if($data['parentID'] && is_array($data['parentID'])){
			$result['list'] = Model('Group')->listByID($data['parentID']);
		}else if($data['parentID'] && is_string($data['parentID'])){
			$result = $this->model->listChild($data['parentID']);
		}

		if($data['rootParam'] && strstr($data['rootParam'],'appendShareHistory')){
			$toOuterGroup = array(
				"groupID" 		=> "rootOuter",
				"parentID" 		=> "root",
				"name" 			=> LNG('explorer.auth.toOuter'),
				"isParent"		=> true,
				"disableSelect" => true,
				"disableOpen" 	=> true,//禁用自动展开
				"nodeAddClass" 	=> 'node-append-group',
			);
			$shareHistory = array(
				"groupID" 		=> "-",
				"parentID" 		=> "0",
				"name" 			=> urldecode(LNG('explorer.groupAuthRecent')),
				"isParent"		=> true,
				"disableSelect" => true,
				"nodeAddClass" 	=> 'node-append-shareTarget',
				'children'		=> Action('explorer.userShareTarget')->get(10),
				'icon' 			=> '<i class="font-icon ri-star-fill"></i>',
			);
			
			// 权限设置时,parentID为当前部门; 显示部门最上层;
			if($data['parentID'] && is_string($data['parentID'])){
				$result['list'] = Model('Group')->listByID(array($data['parentID']));
			}
			// 自己可见根目录,不再当前显示列表中,则显示对外授权;
			if($userGroupRootShow != array_to_keyvalue($result['list'],'','groupID')){
				$result['list'][] = $toOuterGroup;
			}
			$children = $shareHistory['children'];
			if(is_array($children) && count($children) > 0){
				$result['list'] = array_merge(array($shareHistory),$result['list']);
			}
		}
		$result['list'] = $this->showGroupfilterAllow($result['list']);
		show_json($result,true);		
	}

	/**
	 * 根据部门id获取信息
	 */
	public function getByID() {
		$id = Input::get('id','[\d,]*');
		$result = $this->model->listByID(explode(',',$id));
		$result = $this->showGroupfilterAllow($result);
		show_json($result,true);
	}
	
	/**
	 * 搜索部门
	 */
	public function search() {
		$data = Input::getArray(array(
			"words" 		=> array("check"=>"require"),
			"parentGroup"	=> array("check"=>"require",'default'=>false),// 支持多个父部门,多个逗号隔开;
		));

		if(!$data['parentGroup']){
			$result = $this->model->listSearch($data);
		}else{
			$groupArr = explode(',',$data['parentGroup']);$result = false;
			foreach ($groupArr as $groupID) {
				$data['parentGroup'] = intval($groupID);
				$listSearch = $this->model->listSearch($data);
				if(!$result){$result = $listSearch;continue;}
				$result['list'] = array_merge($result['list'],$listSearch['list']);
			}
			if(is_array($result) && is_array($result['list'])){
				$result = array_page_split(array_unique($result['list']),$result['pageInfo']['page'],$result['pageInfo']['pageNum']);
			}
		}
		$result['list'] = $this->showGroupfilterAllow($result['list']);
		show_json($result,true);
	}
	
	// 过滤不允许的用户信息(根据当前用户可见部门筛选)
	private function showGroupfilterAllow($list){
		if($GLOBALS['config']["GROUP_MEMBER_ALLOW_ALL"]){return $list;}
		if(!$list || KodUser::isRoot()){return $list;}
		$userGroupRootShow  = Action("filter.userGroup")->userGroupRootShow(); // 用户所在跟部门;可见范围
		$groupAllow = array();
		foreach($list as $group){
			if(!$group['groupID'] || $group['groupID']== '-'){
				if(is_array($group['children'])){ // 子部门情况; appendShareHistory
					$group['children'] = $this->showGroupfilterAllow($group['children']);
				}
				$groupAllow[] = $group;
				continue;
			}
			
			$groupParentAll = Model('Group')->parentLevelArray($group['parentLevel'].$group['groupID'].',');
			$groupParentAll = array_unique($groupParentAll);
			$allowShow = array_intersect($groupParentAll,$userGroupRootShow)  ? true : false; //是否有交集
			if($allowShow){$groupAllow[] = $group;}
		}
		// trace_log([$groupAllow,$list,$userGroupRootShow]);
		// pr($groupAllow,$list,$userGroupRootShow);exit;
		return $groupAllow;
	}
	
	/**
	 * 群组添加
	 * admin/group/add&name=t1&parentID=101&sizeMax=0
	 */
	public function add(){
		$data = Input::getArray(array(
			'groupID'	=> array("check"=>"int","default"=>null),	// 第三方导入
			"name" 		=> array("check"=>"require","default"=>""),
			"sizeMax" 	=> array("check"=>"float","default"=>1024*1024*100),
			"parentID"	=> array("check"=>"int"),
			"sort"		=> array("default"=>null),
			"authShowType" 	=> array("default"=>null),
			"authShowGroup" => array("default"=>null),
			"ioDriver"		=> array("check"=>"int","default"=>null),
		));
		$data['name'] = str_replace('/','',$data['name']);
		$groupID = $this->model->groupAdd($data);
		
		// 添加部门默认目录
		$groupInfo = Model('Group')->getInfo($groupID);
		$sourceID = $groupInfo['sourceInfo']['sourceID'];
		$this->folderDefault($sourceID);
		
		$msg = $groupID ? LNG('explorer.success') : LNG('explorer.error');
		return show_json($msg,!!$groupID,$groupID);
	}

	/**
	 * 部门默认目录
	 */
	public function folderDefault($sourceID){
		$folderDefault = Model('SystemOption')->get('newGroupFolder');
		$folderList = explode(',', $folderDefault);
        foreach($folderList as $name){
            $path = "{source:{$sourceID}}/" . $name;
            IO::mkdir($path);
        }
    }

	/**
	 * 编辑 
	 * admin/group/edit&groupID=101&name=warlee&sizeMax=0
	 */
	public function edit() {
		$data = Input::getArray(array(
			"name" 		=> array("default"=>null),
			"sizeMax" 	=> array("check"=>"float","default"=>null),
			"groupID" 	=> array("check"=>"int"),
			"parentID"	=> array("default"=>null),
			"sort"		=> array("default"=>null),
			"authShowType" 	=> array("default"=>null),
			"authShowGroup" => array("default"=>null),
			"ioDriver"		=> array("check"=>"int","default"=>null),
		));
		if(!empty($data['name'])){
			$data['name'] = str_replace('/','',$data['name']);
		}
		$res = $this->model->groupEdit($data['groupID'],$data);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		return show_json($msg,!!$res,$data['groupID']);
	}

	/**
	 * 禁/启用
	 * @return void
	 */
	public function status(){
		$data = Input::getArray(array(
			"groupID" 	=> array("check"=>"int"),
			"status"	=> array("check"=>"in", "param" => array(0, 1)),
		));
		$res = $this->model->groupStatus($data['groupID'], $data['status']);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}

	/**
	 * 删除
	 */
	public function remove() {
		// $id = Input::get('groupID','bigger',null,1);
		$data = Input::getArray(array(
			"groupID" 	=> array("check"=>"require"),
			"delAll"	=> array("default"=>0),
		));
		$del = boolval($data['delAll']);
		$ids = explode(',', $data['groupID']);
		if ($del) {
			$res = $this->model->listChildIds($ids);
			if ($res === false) show_json(LNG('explorer.error'),false);
			$ids = array_merge($ids, $res);
		}
		$code = 0;
		foreach ($ids as $id) {
			$res = $this->model->groupRemove($id,$del);
			$code += ($res ? 1 : 0);
		}
		$msg = $code ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$code);
	}

	/**
	 * 排序
	 */
	public function sort() {
		$ids = Input::get('groupID','require');
		$ids = explode(',', $ids);
		$res = false;
		if (!empty($ids)) {
			$this->model->groupSort($ids);
			$res = true;
		}
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}

	/**
	 * 部门迁移 
	 */
	public function switchGroup(){
		$data = Input::getArray(array(
			"from"		=> array("check"=>"int"),
			"to"		=> array("check"=>"int"),
		));
		$res = $this->model->groupSwitch($data['from'],$data['to']);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}
}
job.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/job.class.php'
View Content
<?php
/*
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*/

//职位管理
class adminJob extends Controller{
	private $model;
	function __construct()    {
		parent::__construct();
		$this->model = Model('UserJob');
	}

	/**
	 * 根据所在部门获取用户列表
	 */
	public function get() {
		$result = $this->model->listData();
		show_json($result,true);
	}
	
	/**
	 * 添加用户
	 */
	public function add() {
		$data = Input::getArray(array(
			"name" 		=> array("check"=>"require"),
			"desc" 		=> array("default"=>""),
		));
		$res = $this->model->add($data);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error') . '! ' . LNG('explorer.pathExists');
		show_json($msg,!!$res);
	}
	
	/**
	 * 编辑 
	 */
	public function edit() {
		$data = Input::getArray(array(
			"id"		=> array("check"=>"int"),
			"name" 		=> array("check"=>"require"),
			"desc" 		=> array("default"=>""),
		));
		$res = $this->model->update($data['id'],$data);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error') . '! ' . LNG('explorer.pathExists');
		show_json($msg,!!$res);
	}

	/**
	 * 删除
	 */
	public function remove() {
		$id = Input::get('id','int');
		$res = $this->model->remove($id);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}
	
	// 移动排序、拖拽排序
	public function sort() {
		$ids = Input::get('ids', 'require');
		$ids = explode(',', $ids);
		foreach($ids as $i => $id) {
			$this->model->update($id,array("sort"=> $i));
		}
		show_json(LNG('explorer.success'));
	}
}
log.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/log.class.php'
View Content
<?php 

class adminLog extends Controller{
    public $actionList = array();
	function __construct() {
        parent::__construct();
        $this->model = Model('SystemLog');
    }

    // 操作类型列表
    private function typeListAll(){
        $typeList = $this->model->typeListAll();
        if (_get($GLOBALS, 'config.settings.fileViewLog') != 1) {
            unset($typeList['file.view']);
        }
        return $typeList;
    }

    /**
     * 操作类型列表
     * this.actions()
     * @return void
     */
    public function typeList(){
        $typeList = $this->typeListAll();
        $list = array(
            'all'   => array('id' => 'all',  'text' => LNG('common.all')),
            'file'  => array('id' => 'file', 'text' => LNG('admin.log.typeFile')),
            'user'  => array('id' => 'user', 'text' => LNG('admin.log.typeUser')),
            'admin' => array('id' => 'admin','text' => LNG('admin.manage')),
        );
        foreach($typeList as $type => $name) {
            $action = explode('.', $type);
            $mod = $action[0];
            if(!isset($list[$mod])) continue;
			if(!is_array($list[$mod]['children'])){$list[$mod]['children'] = array();}
            $list[$mod]['children'][] = array('id' => $type,'text' => $name);
		}
        $fileList = array(
            array('id' => 'explorer.index.zipDownload', 'text' => LNG('admin.log.downFolder')),
            array('id' => 'explorer.index.fileOut', 'text' => LNG('admin.log.downFile')),
            array('id' => 'explorer.index.fileDownload', 'text' => LNG('admin.log.downFile')),
            array('id' => 'explorer.fav.add', 'text' => LNG('explorer.addFav')),
            array('id' => 'explorer.fav.del', 'text' => LNG('explorer.delFav')),
            array('id' => 'explorer.history.remove', 'text' => ''), // 删除历史版本
            array('id' => 'explorer.history.rollback', 'text' => ''),   // 回滚历史版本
            array('id' => 'explorer.history.clear', 'text' => ''),  // 清空历史版本——合并为历史版本操作,无需单独显示
        );
		if(!is_array($list['file']['children'])){$list['file']['children'] = array();}
		$list['file']['children'] = array_merge($list['file']['children'], $fileList);
        $list = $this->typeListMerge($list);
        show_json($list);
	}
	
	// 合并操作日志类型;
	private function typeListMerge($list){
		$mergeList = array(
			'file' => array(
				// 'file.edit,file.rename'  => LNG('admin.log.editFile'),
				// 'file.mkdir,file.mkfile' => '新建文件(夹)',
				'file.copy,file.move,file.moveOut' => LNG('log.file.move'),
				'explorer.fav.add,explorer.fav.del' => LNG('log.file.fav'),
				'explorer.index.fileOut,explorer.index.fileDownload' => LNG('admin.log.downFile'),
				'file.shareLinkAdd,file.shareLinkRemove' => LNG('log.file.shareLink'),
				'file.shareToAdd,file.shareToRemove' => LNG('log.file.shareTo'),
				'explorer.history.remove,explorer.history.rollback,explorer.history.clear' => LNG('explorer.history.action'),
			),
			'user' => array(
				'user.setting.setHeadImage,user.setting.setUserInfo' => LNG('log.user.edit'),
			),
			'admin' => array(
				'admin.group.add,admin.group.edit,admin.group.remove,admin.group.status,admin.group.sort,admin.group.switchGroup' => LNG('log.group.edit'),
				'admin.member.add,admin.member.edit,admin.member.remove,admin.member.addGroup,admin.member.removeGroup,admin.member.switchGroup,admin.member.status' => LNG('log.member.edit'),
				'admin.role.add,admin.role.edit,admin.role.remove' => LNG('log.role.edit'),
				'admin.auth.add,admin.auth.edit,admin.auth.remove' => LNG('log.auth.edit'),
				'admin.storage.add,admin.storage.edit,admin.storage.remove' => LNG('admin.menu.storageDriver'),
			),
		);
		foreach($list as $listKey => $item) {
			if(!$item['children'] || !$mergeList[$item['id']]) continue;
			$actionMake = array();
			foreach ($mergeList[$item['id']] as $actions => $text) {
				$actionArr = explode(',',$actions);				
				$actionMake[$actions] = false;//isMerged 是否合并;
				foreach ($actionArr as $action) {
					$actionMake[$action] = array('data'=>array('id'=> $actions,'text'=> $text),'actions'=>$actions);
				}
			}
			
			$children = array();
			foreach ($item['children'] as $childItem) {
				$action = $childItem['id'];
				if( isset($actionMake[$action]) ){
					$item = $actionMake[$action];
					if( !$actionMake[$item['actions']] ){
						$children[] = $item['data'];
						$actionMake[$item['actions']] = true;
					}
				}else{
					$children[] = $childItem;
				}
			}
			// pr($item,$children,$actionMake);exit;
			$list[$listKey]['children'] = $children;
		}
		$list = array_values($list);
		return $list;
	}
	

	/**
     * 后台管理-日志列表
     * @return void
     */
    public function get(){
        $data = Input::getArray(array(
            'timeFrom'  => array('check' => 'require'),
            'timeTo'    => array('check' => 'require'),
            'userID'    => array('default' => ''),
            'type'      => array('default' => ''),
            'ip'        => array('default' => null),
        ));
		
		// 部门管理员, 只能查询自己为部门管理员部门下的成员操作日志;
		if(!KodUser::isRoot()){
			$filter = Action("filter.UserGroup");
			if($data['userID'] && !$filter->allowChangeUser($data['userID'])){
				show_json(LNG('explorer.noPermissionAction'),false);
			}
			$groupAdmin = $filter->userGroupAdmin();
			if(!$data['userID'] && !in_array('1',$groupAdmin)){
				$groupAll = Model('Group')->groupChildrenAll($groupAdmin);
				$userAll  = Model('User')->groupUserAll($groupAll);
				if(!$userAll){
					show_json(array());
				}
				$data['userID'] = array('in',$userAll);
			}
		}
		
        $res = $this->model->get($data);
        if(empty($res)) show_json(array());
        show_json($res['list'], true, $res['pageInfo']);
    }

    /**
     * 记录日志
     * @param boolean $data
     * @param boolean $info
     * @return void
     */
    public function add($data=false, $info=null){
        if (isset($this->in['disableLog']) && $this->in['disableLog'] == '1') return;
        $this->fileUploadLog($data, $info);
        $typeList = $this->typeListAll();
        if(!isset($typeList[ACTION])) return;
		if($GLOBALS['loginLogSaved'] ==1) return;
        $actionList = array(
            'user.index.logout',
            'user.index.loginSubmit',
        );
        // 操作日志
        if(!in_array(ACTION, $actionList)){
            // 文件类的操作,此处只收集这3个
            if(MOD == 'explorer') {
                $act  = ST . '.' . ACT;
                $func = array(
                    'fav.add', 'fav.del', 
                    'index.fileOut', 'index.fileDownload', 'index.zipDownload',
                    'history.remove', 'history.rollback', 'history.clear',
                );
                if(!in_array($act, $func)) return;
                if (in_array(ACT, array('fileOut', 'fileDownload'))) { // 多线程下载,或某些浏览器会请求多次
                    if (!$this->checkHttpRange()) return;
                } else if (ACT == 'zipDownload') {
                    // 前端压缩下载返回文件列表,列表文件分别请求下载(并记录日志),故此处不记录
                    if (_get($this->in, 'zipClient') == '1' && _get($this->in, 'zipDownloadDisable') == '1') return;
                    // if (isset($this->in['zipClient']) && $this->in['zipClient'] == '1') {
                    //     $data = false;  // 前端压缩下载会返回列表,故下方以$this->in赋值
                    // }
                }
            }
            if(!is_array($data)) $data = $this->filterIn();
        }
        // 第三方绑定
        if(ACTION == 'user.index.loginSubmit'){
            if (!is_array($data)) return;
            return $this->loginLog();
        }
        return $this->model->addLog(ACTION, $data);
    }

    /**
     * 过滤in中多余参数
     * @return void
     */
    private function filterIn(){
        $in = $this->in;
        unset($in['URLrouter'],$in['URLremote'],$in['HTTP_DEBUG_URL'],$in['CSRF_TOKEN'],
			$in['viewToken'],$in['accessToken'],$in[str_replace(".", "/", ACTION)]);
        // 替换密码参数——也可以直接删除
        if (isset($in['password']) && !isset($in['salt'])) {
            $in['password'] = str_repeat('*', strlen($in['password']));
        }
        if (isset($in['_change']['password'])) {
            $in['_change']['password'] = str_repeat('*', strlen($in['_change']['password']));
        }
        return $in;
    }

	// 文件预览下载,是否为从头开始下载(用于下载计数,或统计日志);  允许的情况:没有range; 有range且start=0且end大于5
	public function checkHttpRange(){
		if(strtoupper($_SERVER['REQUEST_METHOD']) == 'HEAD'){return false;}
		if(!isset($_SERVER['HTTP_RANGE'])){return true;}
		
		$start = 0;$end = 100;
		$find = preg_match('/bytes=\s*(\d+)-(\d*)/i',$_SERVER['HTTP_RANGE'],$matches);
		if($find && is_array($matches)){$start = intval($matches[1]);}
		if(!empty($matches[2])){$end = intval($matches[2]);}
		return ($start == 0 && $end >= 5) ? true : false;
	}

    /**
     * 登录日志
     * @param string $action
     * @param [type] $ip
     * @return void
     */
    public function loginLog(){
		if($GLOBALS['loginLogSaved'] == 1 || !Session::get('kodUser')) return;
		$GLOBALS['loginLogSaved'] = 1;
        $data = array(
            'is_wap' => is_wap(),
            'ua' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''
        );
        if(isset($this->in['HTTP_X_PLATFORM'])) {
            $data['is_wap'] = true;
            $data['HTTP_X_PLATFORM'] = $this->in['HTTP_X_PLATFORM'];
        }
        return $this->model->addLog('user.index.loginSubmit', $data);
    }

    /**
     * 个人中心-用户文档日志
     * @return void
     */
    public function userLog(){
        $userID = Input::get('userID', 'int');
        // 获取文件操作类型
		$typeList = $this->typeListAll();
		$types = array();
		foreach($typeList as $key => $value) {
			if(strpos($key, 'file.') === 0) $types[] = $key;
		}
		$add = array(
			'explorer.index.fileOut',
			'explorer.index.fileDownload',
			'explorer.index.zipDownload',
			'explorer.fav.add',
			'explorer.fav.del'
		);
        $types = array_merge($types, $add);
		$data = array(
			'userID' => $userID,
			'type'	 => implode(',', $types)
		);
		$res = $this->model->get($data);
		foreach($res['list'] as $i => &$item) {
			$value = array(
				'type' 			=> $item['type'],
				'createTime'	=> $item['createTime'],
				'title'			=> $item['title'],
				'address'		=> $item['address']
			);
			$item['desc'] = is_array($item['desc']) ? $item['desc']:array();
			$item = array_merge($value, $item['desc']);
		};unset($item);
		show_json($res);
    }
    /**
     * 个人中心-用户登录日志
     * @return void
     */
    public function userLogLogin(){
        $data = Input::getArray(array(
            'type'      => array('check' => 'require'),
            'userID'    => array('check' => 'require'),
        ));
        $res = $this->model->get($data);
        if(empty($res)) show_json(array());
        show_json($res['list'], true, $res['pageInfo']);
    }

    /**
     * hook绑定
     * @return void
     */
    public function hookBind(){
        // 账号、存储管理:删除(及编辑等)操作只有id,没有名称,故提前获取
        if (MOD == 'admin' && in_array(ACT, array('edit','status','remove'))) {
            if (!isset($this->in['name'])) {
                switch (ST) {
                    case 'group':
                        $info = Model('Group')->getInfoSimple($this->in['groupID']);
                        break;
                    case 'member':
                        $info = Model('User')->getInfoSimple($this->in['userID']);
                        break;
                    case 'auth':
                    case 'role':
                        $table = ST == 'auth' ? 'Auth' : 'SystemRole';
                        $info = Model($table)->listData($this->in['id']);
                    break;
                }
                if (!empty($info['name'])) $this->in['name'] = $info['name'];
            }
        }
        // 退出时在请求出记录,其他在出执行结果后记录
        if (ACTION == 'user.index.logout') {
            $user = Session::get('kodUser');
            if (!$user) return;
            $data = array(
                'code' => true,
                'data' => array(
                    'userID'    => $user['userID'], 
                    'name'      => $user['name'],
                    'nickName'  => $user['nickName'],
                )
            );
            return $this->autoLog($data);
        }
        // 存储管理,部分操作参数只有id
        if (MOD.'.'.ST == 'admin.storage' && in_array(ACT,array('add','edit','remove'))) {
            if (ACT == 'remove' && isset($this->in['progress'])) return;
            if (empty($this->in['name'])) {
                $info = Model('Storage')->listData($this->in['id']);
                $this->in['name'] = $info['name'];
                $this->in['driver'] = $info['driver'];  // 谨慎追加参数,避免影响主方法调用
            }
        }
        // 历史版本,追加当前操作的版本号;只记录source文件
        if (ST == 'history' && ACT != 'clear') {
            $parse = KodIO::parse($this->in['path']);
            if ($parse['type'] == KodIO::KOD_SOURCE && $parse['id']) {
                $info = Model('SourceHistory')->fileInfo($this->in['id'],true);
                if ($info) $this->in['fileInfo'] = $info;
            }
        }
        Hook::bind('show_json', array($this, 'autoLog'));
        Hook::bind('explorer.fileDownload', array($this, 'autoLog'));

        // 开启了文件预览日志
        // explorer.index.fileout
        // explorer.editor.fileget
        // explorer.share.fileout
        // explorer.share.file      // 主要用于外链(如用户头像),排除
        // explorer.history.fileout
        if (_get($GLOBALS, 'config.settings.fileViewLog') == 1) {
            Hook::bind('plugin.fileView',  array($this, 'fileViewLog'));
            Hook::bind('explorer.fileOut', array($this, 'fileViewLog'));
            Hook::bind('explorer.fileGet', array($this, 'fileViewLog'));
        }
    }
    // 通用日志
    public function autoLog($data){
        if (isset($data['code']) && !$data['code']) return false;
        if (!isset($data['data']) || !is_array($data)) {
            $data = array('data' => $data);
        }
        // $info = isset($data['info']) ? $data['info'] : null;
        $this->add($data['data'], _get($data, 'info'));
    }
    // 文件预览日志
    public function fileViewLog($path){
        $in = $this->in;
        if (MOD == 'plugin') {
            if (ACTION == 'plugin.fileThumb.cover') {
                if (!isset($in['width']) || $in['width'] == '250') return;
            } else if (ACT != 'index') {
                return;
            }
        }
        if (strtolower(ACT) == 'fileout') {
            if(isset($in['type']) && $in['type'] == 'image'){
                if (isset($in['width']) && $in['width'] == '250') return;
            }
            // 该参数由插件打开时filePathLink调用fileOut追加,排除
            if (isset($in['et'])) return;
        }
        if (isset($in['download']) && $in['download'] == 1) return; // 分享下载:fileDownload=>fileOut
        if (!$this->checkHttpRange()) return;

        // 获取文件信息,写入日志
        $parse = KodIO::parse($path);
		if ($parse['type'] != KodIO::KOD_SOURCE || !$parse['id']) {
            $parse = KodIO::parse($this->in['path']);
            if ($parse['type'] != KodIO::KOD_SOURCE || !$parse['id']) return;
        }
		$sourceID = $parse['id'];
		$sourceInfo = Model("Source")->sourceInfo($sourceID);
        if(!$sourceInfo || $sourceInfo['targetType'] == SourceModel::TYPE_SYSTEM) return;

        // 参考sourceEvent.addSystemLog
        $data = array(
			"sourceID"		=> $sourceID,
			"sourceParent"  => $sourceInfo['parentID'],
			"sourceTarget"  => $sourceID,
			'pathName'		=> $sourceInfo['name'],
			'pathDisplay'	=> !empty($sourceInfo['pathDisplay']) ? $sourceInfo['pathDisplay'] : '',
			"userID" 		=> USER_ID,
			"type" 			=> 'view',
			"desc"		    => $this->filterIn(),
		);
		$this->model->addLog('file.view', $data);	
    }

    // 文件上传日志——非系统目录
    public function fileUploadLog($data, $info=null) {
        if (ACTION != 'explorer.upload.fileUpload') return;
        if (!is_array($info) || !array_key_exists('uploadLinkInfo', $info) || empty($this->in['path'])) return;
        $parse = KodIO::parse($this->in['path']);
		if ($parse['type'] == KodIO::KOD_SOURCE) return;

        $data = $this->filterIn();
        $data['path'] = rtrim($parse['path'], '/') . '/' . $data['name'];
        unset($data['name']);
        $id = $this->model->addLog('file.upload', $data);
        if ($id) $this->model->where(array('id' => $id))->save(array('type' => 'explorer.upload.fileUpload'));
    }

}
member.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/member.class.php'
View Content
<?php
/*
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*/

//用户管理【管理员配置用户,or用户空间大小变更】
class adminMember extends Controller{
	private $model;
	function __construct()    {
		parent::__construct();
		$this->model = Model('User');
		$this->authCheck();
	}

	public function authCheck(){
		if(KodUser::isRoot()) return;
		if(MOD == 'install') return;
		$data = Input::getArray(array(
			"userID"	=> array("default"=>null),
			"roleID"	=> array("default"=>2),
		));
		if(isset($data['userID']) && $data['userID'] == '1') {
			show_json(LNG('admin.member.editNoAuth'), false);
		}
		
		$roleInfo = Model('SystemRole')->listData($data['roleID']);
		if(!in_array(ACTION, array('admin.member.add', 'admin.member.edit'))) return;
		if($roleInfo['administrator'] != 1) return; // 1为系统管理员
		show_json(LNG('admin.member.editNoAuth'), false);
	}

	/**
	 * 根据所在部门获取用户列表
	 */
	public function get() {
		$data = Input::getArray(array(
			"groupID"	=> array("check"=>"require",'default'=>0),
			"fields"	=> array("check"=>"require",'default'=>''),
			"status"	=> array("default"=>null),

			// 时间范围查询,兼容接口需求
			"timeType"	=> array("check"=>"in",'default'=>'createTime','param'=>array('createTime','modifyTime','lastLogin')),
			"timeFrom"	=> array("default"=>null),
			"timeTo"	=> array("default"=>null),
		));
		if(!$data['groupID']){show_json(array(),true);}
		if($data['groupID'] == 1) $data['groupID'] = 0;	// 根部门(id=1)获取全部用户
		$result = $this->model->listByGroup($data['groupID'], $data);
		$result['list'] = $this->showUserfilterAllow($result['list']);
		show_json($result,true);
	}
	
	/**
	 * 根据用户id获取信息
	 */
	public function getByID() {
		$id = Input::get('id','[\d,]*');
		$result = $this->model->listByID(explode(',',$id));
		$result = $this->showUserfilterAllow($result);
		show_json($result,true);
	}

	/**
	 * 搜索用户
	 */
	public function search() {
		$data = Input::getArray(array(
			"words" 		=> array("check"=>"require"),
			"status"		=> array("default"=>null),
			"parentGroup"	=> array("check"=>"require",'default'=>false),// 支持多个父部门,多个逗号隔开;
		));
		
		if(!$data['parentGroup']){
			$result = $this->model->listSearch($data);
		}else{
			$groupArr = explode(',',$data['parentGroup']);$result = false;
			foreach ($groupArr as $groupID) {
				$data['parentGroup'] = intval($groupID);
				$listSearch = $this->model->listSearch($data);
				if(!$result){$result = $listSearch;continue;}
				$result['list'] = array_merge($result['list'],$listSearch['list']);
			}
			if(is_array($result) && is_array($result['list'])){
				$result = array_page_split(array_unique($result['list']),$result['pageInfo']['page'],$result['pageInfo']['pageNum']);
			}
		}
		
		$result['list'] = $this->showUserfilterAllow($result['list']);
		show_json($result,true);
	}
	
	// 过滤不允许的用户信息(根据当前用户可见部门筛选)
	private function showUserfilterAllow($list){
		if($GLOBALS['config']["GROUP_MEMBER_ALLOW_ALL"]){return $list;}
		if(!$list || KodUser::isRoot()){return $list;}
		$userGroupRootShow  = Action("filter.userGroup")->userGroupRootShow(); // 用户所在跟部门;可见范围
		$userGroupAdmin = Action("filter.userGroup")->userGroupAdmin();// 用户所在部门为管理员的部门;
		// if(!$userGroupRootShow){return $list;}

		// 获取完整用户信息, 有部门管理权限且该参数为full才返回用户完整信息,否则精简用户信息(去除邮箱,手机号等敏感信息)
		$userAllow    = array();
		$requestAdmin = isset($this->in['requestFromType']) && $this->in['requestFromType'] == 'admin'; // 后端用户列表;
		foreach($list as $user){
			$groupParentAll = array(); 
			foreach ($user['groupInfo'] as $groupInfo){
				$parents = Model('Group')->parentLevelArray($groupInfo['parentLevel']);
				$groupParentAll = array_merge($parents,$groupParentAll,array($groupInfo['groupID']));
			}
			$groupParentAll = array_unique($groupParentAll);
			$allowShow = array_intersect($groupParentAll,$userGroupRootShow)  ? true : false; //是否有交集
			$allowFull = array_intersect($groupParentAll,$userGroupAdmin) ? true : false;
			if(!$allowShow){continue;}
			if($allowFull && $requestAdmin){$userAllow[] = $user;continue;}
			
			$allowField = explode(',','userID,avatar,name,nickName,sex,groupInfo');//groupInfo
			$userAllow[] = array_field_key($user,$allowField);
		}
		// pr($userAllow,$list,$userGroupRootShow,$userGroupAdmin);exit;
		return $userAllow;
	}
	
	/**
	 * 添加用户
	 */
	public function add() {
		$this->import();
		$data = Input::getArray(array(
			"userID"	=> array("default"=>null),
			"name" 		=> array("check"=>"require"),
			"sizeMax" 	=> array("check"=>"float","default"=>1024*1024*100),
			"roleID"	=> array("check"=>"int"),
			"password" 	=> array("check"=>"require"),
			
			"email" 	=> array("check"=>"email",	"default"=>""),
			"phone" 	=> array("check"=>"phone",	"default"=>""),
			"ioDriver"	=> array("check"=>"int",	"default"=>null),
			"nickName" 	=> array("check"=>"require","default"=>""),
			"avatar" 	=> array("check"=>"require","default"=>""),
			"sex" 		=> array("check"=>"require","default"=>1),//0女1男
			"status" 	=> array("default"=>1),
		));
		$data['password'] = KodUser::parsePass($data['password']);
		if( !ActionCall('filter.userCheck.password',$data['password']) ){
			return ActionCall('filter.userCheck.passwordTips');
		}
		// 1.添加用户
		$res = $userID = $this->model->userAdd($data);
		if($res <= 0) return show_json($this->model->errorLang($res),false);
		
		// 初始化数据,不记录操作日志;
		Model('SourceEvent')->recodeStop();
		$groupInfo = json_decode($this->in['groupInfo'],true);
		if(is_array($groupInfo)){
			$this->model->userGroupSet($userID,$groupInfo,true);
		}

		// 2.添加用户默认配置
		$userInfo = $this->model->getInfo($userID);
		$this->settingDefault($userID);

		// 3.添加用户默认目录
		$sourceID = $userInfo['sourceInfo']['sourceID'];
		$this->folderDefault($sourceID);

		// 4.添加用户默认轻应用
		$desktopID = $userInfo['sourceInfo']['desktop'];
		$this->lightAppDefault($desktopID);
		Model('SourceEvent')->recodeStart();
		return show_json(LNG('explorer.success'), true, $userID);
	}

	/**
     * 用户默认设置——主题、壁纸、界面样式选择等
     */
    public function settingDefault($userID){
		$default = $this->config['settingDefault'];
        $insert = array();
        foreach($default as $key => $value){
            $insert[] = array(
				'type'		=> '',
                'userID'	=> $userID,
				'key'		=> $key,
				'value'		=> $value
            );
        }
        Model('user_option')->addAll($insert);
	}

    /**
     * 用户默认目录
     */
    public function folderDefault($parentID){
        $folderDefault = Model('SystemOption')->get('newUserFolder');
		$folderList = explode(',', $folderDefault);
        foreach($folderList as $name){
            $path = "{source:{$parentID}}/" . $name;
            IO::mkdir($path);
        }
	}
	
	/**
     * 添加用户轻应用
     */
    public function lightAppDefault($desktop){
        $list = Model('SystemLightApp')->listData();
        $appList = array_to_keyvalue($list, 'name');
		$appListID = array_to_keyvalue($list, 'id');

        $defaultApp = Model('SystemOption')->get('newUserApp');
		$defAppList = explode(',', $defaultApp);
        foreach($defAppList as $name){
			$app = _get($appListID,$name,_get($appList,$name));
			if(!$app) continue;
			
			// [user]/desktop/appName.oexe
            $path = "{source:{$desktop}}/" . $app['name'] . '.oexe';
            IO::mkfile($path, json_encode_force($app['content']));
        }
    }

	/**
	 * 编辑 
	 */
	public function edit() {
		$data = Input::getArray(array(
			"userID" 	=> array("check"=>"int"),	// userID=1可以编辑			
			"name" 		=> array("check"=>"require","default"=>null),
			"sizeMax" 	=> array("check"=>"float",	"default"=>null),
			"roleID"	=> array("check"=>"int",	"default"=>null),
			"password" 	=> array("check"=>"require","default"=>''),
			
			"email" 	=> array("check"=>"email",	"default"=>null),
			"phone" 	=> array("check"=>"phone",	"default"=>null),
			"ioDriver"	=> array("check"=>"int",	"default"=>''),
			"nickName" 	=> array("check"=>"require","default"=>null),
			"avatar" 	=> array("check"=>"require","default"=>null),
			"sex" 		=> array("check"=>"require","default"=>null),//0女1男
			
			"status" 	=> array("check"=>"require","default"=>null),//0-未启用 1-启用
		));
		// 后台删除这些字段值时(为空),default=null时data不包含导致没有更新
		foreach (array('email','phone','nickName') as $key) {
			if (!isset($data[$key]) && isset($this->in[$key])) {
				$data[$key] = '';
			}
		}
		if( $data['password'] ) {
			$data['password'] = KodUser::parsePass($data['password']);
			if (!ActionCall('filter.userCheck.password',$data['password']) ){
				return ActionCall('filter.userCheck.passwordTips');
			}
		}
		// 不支持修改自己的权限角色;避免误操作;
		if($data['userID'] == USER_ID && isset($data['roleID'])){
			$user = Session::get('kodUser');
			if($user['roleID'] != $data['roleID']){
				return show_json(LNG('admin.member.errEditSelfRole'),false);
			}
		}
		// 禁止修改超管角色
		$userInfo = $this->model->getInfo($data['userID']);
		if ($data['userID'] == '1' && $data['userID'] != USER_ID) {
			if(isset($data['roleID']) && $data['roleID'] != $userInfo['roleID']){
				return show_json(LNG('admin.member.errEditSelfRole'),false);
			}
		}

		$dataSave = array();$groupSave = false; // 仅处理变化的内容;
		foreach($data as $key => $value) {
			if($key == 'ioDriver'){$dataSave[$key] = $value;}
			if($key == 'userID') continue;
			if($value == $userInfo[$key]) continue;
			$dataSave[$key] = $value;
		}
		
		if($dataSave){$res = $this->model->userEdit($data['userID'],$dataSave);}
		$groupInfo = json_decode($this->in['groupInfo'],true);
		if(isset($this->in['groupInfo'])){ 
			// 编辑用户,必须有至少一个默认部门; 即便是没有权限;
			$groupInfo = is_array($groupInfo) ? $groupInfo : array();
			$userGroup = array_to_keyvalue($userInfo['groupInfo'],'groupID','auth.id',true);

			// 添加到指定部门时;保持原来所在部门权限不变;
			$isGroupAppend = isset($this->in['groupInfoAppend']) && $this->in['groupInfoAppend'] == '1';
			// 仅添加时,用户所在部门不在设置范围内则自动加入;
			foreach ($userGroup as $groupID => $auth){
				if($isGroupAppend && !isset($groupInfo[$groupID])){$groupInfo[$groupID] = $auth;}
			}
			if($userGroup != $groupInfo){
				$groupSave = true;$res = 1;
				$this->model->userGroupSet($data['userID'],$groupInfo,true);
			}
		}
		$this->in['_change'] = $dataSave;
		if($groupSave){$this->in['_change']['groupInfo'] = $_REQUEST['groupInfo'];}
		if(!$dataSave && !$groupSave){$res = 1;}
		
		$msg = $res > 0 ? LNG('explorer.success') : $this->model->errorLang($res);
		return show_json($msg,($res>0),$data['userID']);
	}
	
	/**
	 * 添加到部门
	 */
	public function addGroup() {
		$data = Input::getArray(array(
			"userID" 	=> array("check"=>"int"),
			"groupInfo"	=> array("check"=>"json"),
		));
		$res = $this->model->userGroupAdd($data['userID'],$data['groupInfo']);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}
	/**
	 * 从部门删除
	 */
	public function removeGroup() {
		$data = Input::getArray(array(
			"userID" 	=> array("check"=>"int"),
			"groupID"	=> array("check"=>"int"),
		));
		$res = $this->model->userGroupRemove($data['userID'],$data['groupID']);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}
	/**
	 * 部门迁移 
	 */
	public function switchGroup(){
		$data = Input::getArray(array(
			"userID" 	=> array("check"=>"int"),
			"from"		=> array("check"=>"int"),
			"to"		=> array("check"=>"int"),
		));
		$userInfo = $this->model->getInfo($data['userID']);
		if(!$userInfo) show_json(LNG('ERROR_USER_NOT_EXISTS'), false);
		$authID = 0;
		// 删除来源部门,新增到新部门
		$groupInfo = !empty($userInfo['groupInfo']) ? $userInfo['groupInfo'] : array();
		foreach($groupInfo as $item) {
			if($item['groupID'] != $data['from']) continue;
			if(isset($item['auth']['id'])) {
				$authID = $item['auth']['id'];
			}
			$this->model->userGroupRemove($data['userID'],$item['groupID']);
			break;
		}
		// 权限无效(无来源部门)时,取权限列表中第一个显示项
		if (!$authID) {
			$list = Model('Auth')->listData();
			foreach ($list as $item) {
				if ($item['display'] != '0') {
					$authID = $item['id'];
					break;
				}
			}
		}
		$groupInfo = array($data['to'] => $authID);
		$res = $this->model->userGroupAdd($data['userID'],$groupInfo);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}
	/**
	 * 更新用户状态
	 */
	public function status(){
		$data = Input::getArray(array(
			"userID" 	=> array("check"=>"int"),
			"status"	=> array("check"=>"in", "param" => array(0, 1)),
		));
		$res = $this->model->userStatus($data['userID'], $data['status']);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}
	
	/**
	 * 删除
	 */
	public function remove() {
		$id = Input::get('userID','bigger',null,1);
		$res = $this->model->userRemove($id);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}

	/**
	 * 批量导入用户
	 * @return void
	 */
	private function import(){
		if(!isset($this->in['isImport'])) return;
		// 1.上传
		if(empty($this->in['filePath'])) {
			// 1.1 上传文件——返回前端:>100kb上传分多次请求,无法直接获取结果
			if (empty($this->in['path'])) {
				$path = IO::mkdir(TEMP_FILES . 'import_' . time());
				$this->in['path'] = $path;
				Action('explorer.upload')->fileUpload();
			}
			// 1.2 获取上传文件内容
			$file = $this->in['path'];
			$data = $this->getImport($file);
			del_file($file);
			if(empty($data['list'])) show_json(LNG('admin.member.uploadInvalid'), false);

			$filename = get_path_this($file);
			Cache::set(md5('memberImport'.$filename), $data);
			show_json('success', true, $filename);
		}
		$filename = Input::get('filePath','require');
		// 获取新增用户进度
		$taskId = md5('import-user-'.$filename);
		if (isset($this->in['process'])) {
			$cache = Cache::get($taskId);
			if ($cache) {
				Cache::remove($taskId);
				show_json($cache,true,1);
			}
			$data = Task::get($taskId);
			show_json($data);
		}
		Cache::remove($taskId);
		// 2.读取数据并新增
		$fileData = Cache::get(md5('memberImport'.$filename));
		Cache::remove(md5('memberImport'.$filename));
		if(!$fileData || empty($fileData['list'])) show_json(LNG('admin.member.uploadDataInvalid'), false);

		$data = Input::getArray(array(
			'sizeMax'	=> array('check' => 'require'),
			'roleID'	=> array('check' => 'require'),
			'groupInfo' => array('check' => 'require'),
		));
		$total	 = (int) $fileData['total'];
		$task	 = new Task($taskId,'importUser',$total,LNG('admin.member.userImport'));
		$error	 = array();
		foreach($fileData['list'] as $value) {
			$task->update(1);
			if(!is_array($value)) continue;
			$this->in = array_merge($value, $data);
			$res = ActionCallHook('admin.member.add');
			if (!$res['code']) $error[$this->in['name']] = $res['data'];
		}
		$task->task['error'] = $error;
		Cache::set($taskId, $task->task);
		$task->end();
		$success = $total - count($error);
		$info = array(
			'total'		=> $total,
			'success'	=> $success,
			'error'		=> $error,
		);
		$code  = (boolean) $success;
		$data  = $code ? LNG('admin.member.importSuccess') : LNG('admin.member.importFail');
		show_json($data, $code, $info);
	}

	// 获取csv分隔符——编辑后的csv文件不一定是默认的','
	private function getCsvSep($line) {
		if (empty($line)) return ',';
		$data = array();
		$separators = array(',', ';', ':', "\t", '|');
		foreach ($separators as $separator) {  
			$fields = explode($separator, $line);
			$fields = array_filter($fields, 'trim');
			$data[$separator] = count($fields);
		}  
		// 找到字段数量最多的分隔符
		$maxCnt = max($data);  
		return array_search($maxCnt, $data);  
	}

	/**
	 * 获取导入文件数据
	 * @param [type] $file
	 * @return void
	 */
	private function getImport($file){
		if (!$handle = fopen($file, 'r')) {
			del_file($file);
			show_json('read file error.', false);
		}
		$line = fgets($handle);	// 获取分隔符
		$separator = $this->getCsvSep(trim($line));
		// rewind($handle);	// 重置指针——fgets会移动指针,但第一行本就需要排除(标题),去掉后续的unset即可
		$dataList = array();
		while (($data = fgetcsv($handle,0,$separator)) !== false) {
            $dataList[] = $data;
		}
        fclose($handle);
        // 2.获取列表数据
        // unset($dataList[0]);
		$dataList = array_filter($dataList);
        $list = array();
        $keys = array('name'=>'','nickName'=>'','password'=>'','sex'=>1,'phone'=>'','email'=>'');
        foreach($dataList as $value) {
            $tmp = array();
			$i = 0;
            foreach($keys as $key => $val) {
				$val = trim($value[$i]);
				$i++;
				if($key == 'name' && empty($val)) break;
				if($key == 'password' && empty($val)) break;
				if (is_null($val)) $val = '';
                switch($key) {
					case 'name':
					case 'nickName':
						$val = $this->iconvValue($val);
						break;
					case 'sex':
						$val = $val != '0' ? 1 : 0;
                        break;
                    case 'phone':
                    case 'email':
						$val = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $val);	// 删除不可见的特殊字符,如null
						if(!Input::check($val, $key)) $val = '';
                        break;
                    default: break;
                }
                $tmp[$key] = $val;
			}
			if(empty($tmp) || empty($tmp['name']) || empty($tmp['password'])) continue;
			if(isset($list[$tmp['name']])) continue;
            $list[$tmp['name']] = array_merge($keys,$tmp);
        }
        return array(
			'list'	=> array_values($list), 
			'total' => count($list), 
		);
	}
	// 部分文件转换无效
	private function iconvValue($value){
		// $encoding = array('GB2312', 'GBK', 'GB18030', 'UTF-8', 'ASCII', 'BIG5');
		// $charset = mb_detect_encoding($value,$encoding);
		// $value = iconv_to($value,$charset,'utf-8');
		$charset = get_charset($value);
		if(!in_array($charset,array('utf-8','ascii'))){
			$value = iconv_to($value, $charset, 'utf-8');
		}
		return $value;
	}
}
notice.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/notice.class.php'
View Content
<?php

//通知管理
class adminNotice extends Controller{
	private $model;
	function __construct()    {
		parent::__construct();
		$this->model = Model('SystemNotice');
	}

	/**
	 * 获取列表
	 */
	public function get() {
		$result = $this->model->listData();
		show_json($result,true);
	}

	/**
	 * 添加
	 */
	public function add() {
		$data = Input::getArray(array(
			"name" 		=> array("check"=>"require"),
			"content" 	=> array("check"=>"require"),
			"auth" 		=> array("check"=>"require"),
			"mode" 		=> array("check"=>"require"),
			"time" 		=> array("check"=>"require"),
			"type" 		=> array("default"=>"1"),	// 通知类型,1:系统通知;2:活动通知
			"level" 	=> array("default"=>"0"),	// 提示级别,0:弱提示;1:强提示
			'enable'	=> array('default'=>'0'),		// 是否启用
		));
		$res = $this->model->add($data);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error') . '! ' . LNG('explorer.pathExists');
		return show_json($msg,!!$res, $res);
	}

	/**
	 * 编辑 
	 */
	public function edit() {
		$data = Input::getArray(array(
			"id"		=> array("check"=>"int"),
			"name" 		=> array("check"=>"require"),
			"content" 	=> array("check"=>"require"),
			"auth" 		=> array("check"=>"require"),
			"mode" 		=> array("check"=>"require"),
			"time" 		=> array("check"=>"require"),
			"type" 		=> array("default"=>"1"),
			"level" 	=> array("default"=>"0"),
			'enable'	=> array('default'=>'0'),		// 是否启用
		));
		$res = $this->model->update($data['id'],$data);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error') . '! ' . LNG('explorer.pathExists');
		show_json($msg,!!$res);
	}

	/**
	 * 删除
	 */
	public function remove() {
		$id  = Input::get('id','int');
		$res = $this->model->remove($id);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}

	// 移动排序、拖拽排序
	public function sort() {
		$ids = Input::get('ids', 'require');
		$ids = explode(',', $ids);
		foreach($ids as $i => $id) {
			$this->model->sort($id,array("sort"=> $i));
		}
		show_json(LNG('explorer.success'));
	}

	/**
	 * 启/禁用通知
	 */
	public function enable() {
		$data = Input::getArray(array(
			"id"    	=> array("check"=>"int"),
			"enable"	=> array("check"=>"bool"),
		));
		$res = $this->model->enable($data['id'],(bool)$data['enable']);
		$msg = !!$res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}
	// ----------------------- 用户通知 -----------------------

	// 检查是否具有接受某通知的权限
	private function authCheck($auth){
		if(!KodUser::isLogin()) return false; //未登录
		if(KodUser::isRoot()) return true;
		return  ActionCall('user.authPlugin.checkAuthValue',$auth);
	}
	// 用户获取推送的通知列表
	public function noticeGet($id = false){
		// 根据id获取详情
		if($id) $this->noticeInfo($id);

		// 初始化用户通知列表
		$result = $this->model->listData(false, 'id');	// 通知列表,按id升序
		foreach($result as $value) {
			if(isset($value['enable']) && !$value['enable']) continue;	// 未启用
			if($value['time'] > time()) continue;	// 未到通知时间
			if(!$this->authCheck($value['auth'])) continue;	// 权限
			$this->model->userNoticeAdd($value);
		}
		// 获取列表,过滤已删除项
		$list = $this->model->userNoticeGet();
		foreach($list as $k => $value) {
			if($value['delete']) unset($list[$k]);
		}
		show_json($list);
	}
	// 用户通知详情
	public function noticeInfo($id){
		// 通知列表——先获取列表,避免被初始化为User.noticeList
		$result = $this->model->listData();
		if(!$result) show_json(array(), false);
		$list = array_to_keyvalue($result,'id');
		// 用户通知
		$notice = $this->model->userNoticeGet($id);
		if(!$notice) show_json(array(), false);

		$noticeID = $notice['noticeID'];
		if(!isset($list[$noticeID])) show_json(array(), false);
		$notice['content'] = $list[$noticeID]['content'];

		show_json($notice);
	}
	// 用户通知更新(已查看)
	public function noticeEdit($id = false){
		if(!$id) show_json(LNG('explorer.share.errorParam'), false);
		$update = array('status' => 1);
		$this->model->userNoticeEdit($id, $update);
		show_json(LNG('explorer.success'));
	}
	// 用户通知删除
	public function noticeRemove($id = false){
		$update = array('delete' => 1);
		if($id) { // 单个删除
			$this->model->userNoticeEdit($id, $update);
		}else{ // 清空全部
			$list = $this->model->userNoticeGet();
			foreach($list as $value) {
				if($value['delete'] == '1') continue;
				$this->model->userNoticeEdit($value['id'], $update);
			}
		}
		show_json(LNG('explorer.success'));
	}
}
plugin.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/plugin.class.php'
View Content
<?php
/*
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*
*
* 插件管理:页面;列表;
*/

class adminPlugin extends Controller{
	private $model;
	function __construct() {
		parent::__construct();
		$this->model = Model('Plugin');
	}

	public function appList(){
		$list  = $this->model->viewList();
		show_json($list);
	}
	public function changeStatus(){
		if( !isset($this->in['app']) || 
			!isset($this->in['status'])){
			show_json(LNG('explorer.dataNotFull'),false);
		}
		$app 	= $this->in['app'];
		$status = $this->in['status']?1:0;

		//同步用户的插件,只允许开启一个;其他的开启时自动提示;
		Hook::trigger("pluginApp.changeStatus",$app,$status);
		$eventAsync = 'pluginApp.changeStatus.userAsync';
		$appConfig = $this->model->loadList($app);
		if(!$appConfig){return show_json(LNG('admin.plugin.disabled').' ('.$app.')',false);}
		if($status){
			if( is_array($appConfig['regiest']) && 
				isset($appConfig['regiest'][$eventAsync]) ){
				Hook::trigger($eventAsync,$app);
			}
			//启用插件则检测配置文件,必填字段是否为空;为空则调用配置
			$config	 = $this->model->getConfig($app);
			$package = $this->model->getPackageJson($app);
			$needConfig = false;
			foreach($package['configItem'] as $key=>$item) {
				if( (isset($item['require']) && $item['require']) &&
					(!isset($item['value']) || $item['value'] === '' || $item['value'] === null) &&
					(!isset($config[$key])  || $config[$key] == "")
				){
					$needConfig = true;
					break;
				}
			}
			if($needConfig){
				show_json('needConfig',false);
			}
		}
		$this->model->changeStatus($app,$status);
		ActionCall($app.'Plugin.onChangeStatus',$status,$app);
		$this->appList();
	}

	public function getConfig(){
		$app = Input::get('app');
		$data = $this->model->getConfig($app);
		$package  = $this->model->getPackageJson($app);
		$formData = $package['configItem'];
		$userSelect = array("type"=>"mutil","user"=>"mutil","group"=>"mutil","role"=>"mutil");

		foreach ($formData as $key=>&$item) {
			if(!isset($item['type']) || $item['type'] == 'html' || $item['type'] == 'button') continue;
			if(isset($data[$key])){
				$item['value'] = $data[$key];
			}
			//用户选择,默认值处理;
			if( is_array($item) && $item['type'] == 'userSelect' && !isset($item['info']) ){
				$item['info'] = $userSelect;
			}
		};unset($item);
		
		$result = ActionCall($app.'Plugin.onGetConfig',$formData);
		if(is_array($result)){$formData = $result;}
		show_json($formData);
	}

	public function setConfig(){
		if( !$this->in['app'] || 
			!$this->in['value']){ 
			show_json(LNG('explorer.dataNotFull'),false);
		}
		$json = $this->in['value'];
		$app  = $this->in['app'];
		if($json == 'reset'){
			//重置为默认配置
			$this->model->setConfig($app,false);
			$json = $this->model->getConfigDefault($app);
		}else{
			if(!is_array($json) && !$json = json_decode($json, true)){
				show_json($json,false);
			}
		}
		$this->model->changeStatus($app,1);
		$result = ActionCall($app.'Plugin.onSetConfig',$json);
		if(is_array($result)){$json = $result;}
				
		$this->model->setConfig($app,$json);
		show_json(LNG('explorer.success'));
	}

	// download=>fileSize=>unzip=>remove
	public function install(){
		$GLOBALS['IO_NO_HISTORY'] = 1; // 安装插件不记录历史版本;
		$app = Input::get('app','key');
		$appPath = PLUGIN_DIR.$app.'.zip';
		$appPathTemp = $appPath.'.downloading';
		switch($this->in['step']){
			case 'check':
				$info = $this->pluginInfo($app);
				if(!is_array($info)){
					show_json(false,false);
				}
				echo json_encode($info);
				break;
			case 'download':
				$info = $this->pluginInfo($app);
				if(!$info || !$info['code']){
					show_json(LNG('explorer.error'),false);
				}
				$result = Downloader::start($info['data'],$appPath);
				show_json($result['data'],!!$result['code'],$app);
				break;
			case 'fileSize':
				if(file_exists($appPath)){
					show_json(filesize($appPath));
				}
				if(file_exists($appPathTemp)){
					show_json(filesize($appPathTemp));
				}
				show_json(0,false);
				break;
			case 'unzip':
				$GLOBALS['isRoot'] = 1;
				if(!file_exists($appPath)){
					show_json(LNG('explorer.error'),false);
				}
				$result = KodArchive::extract($appPath,PLUGIN_DIR.$app.'/');
				del_file($appPathTemp);
				del_file($appPath);
				show_json($result['data'],!!$result['code']);
				break;
			case 'remove':
				del_file($appPathTemp);
				del_file($appPath);
				show_json(LNG('explorer.success'));
				break;
			case 'update':
				Hook::trigger($app.'Plugin.onUpdate');
				Hook::apply($app.'Plugin.regist');
				show_json(Hook::apply($app.'Plugin.update'));
				break;
			default:break;
		}
	}
	private function pluginInfo($app){
		$api = $this->config['settings']['kodApiServer'].'pluginV5/install';
		$param = array(
			"app"			=> $app,
			"version"		=> KOD_VERSION,
			"versionHash"	=> Model('SystemOption')->get('versionHash'),
			"code"			=> Model('SystemOption')->get('versionUser'),
			"deviceUUID"	=> Model('SystemOption')->get('deviceUUID'),
			"systemOS"		=> $this->config['systemOS'],
			"phpVersion"	=> PHP_VERSION,
			"channel"		=> INSTALL_CHANNEL,	
			"lang"			=> I18n::getType()
		);
		$info   = url_request($api,'POST',$param);
		$result = false;
		if($info && $info['data']){
			$result = json_decode($info['data'],true);
		}
		return $result;
	}

	public function unInstall(){
		$app = Input::get('app','key');
		if( !$this->in['app']){
			show_json(LNG('explorer.dataNotFull'),false);
		}
		if(substr($app,0,3) == 'oem'){
			show_json("专属定制插件不支持卸载,不需要您可以禁用!",false);
		}		
		ActionCall($app.'Plugin.onUninstall');
		$this->model->unInstall($app);
		del_dir(PLUGIN_DIR.$app);
		$this->appList();
	}
	
	// 主程序升级处理; 适应关闭访问物理路径的情况;
	public function appUpdate(){
		$fileName = kodIO::clear($this->in['appName']);
		$savePath = BASIC_PATH.'config/';
		switch($this->in['step']){
			case 'check':
				$canUpdate = $this->appUpdateCheck($savePath.$fileName);
				show_json($canUpdate,!!$canUpdate);
				break;
			case 'download':
				$this->in['url'] = 'http://static.kodcloud.com/update/update/'.$fileName;;
				$this->in['path']= $savePath;
				Action("explorer.upload")->serverDownload();
			case 'update':
				$info = IO::info($savePath.$fileName);
				if(!$info || $info['ext'] != 'zip'){
					show_json(LNG('explorer.error'),false);
				}
				
				$GLOBALS['IO_NO_HISTORY'] = 1; // 安装插件不记录历史版本;
				$result = KodArchive::extract($info['path'],BASIC_PATH);
				@unlink($info['path']);
				show_json($result['data'],!!$result['code']);
				break;
			default:break;
		}
	}
	
	// 目录权限检测;
	private function appUpdateCheck($zipFile){
		if(get_path_ext($zipFile) == 'zip'){
			@unlink($zipFile);
		}
		mk_dir(TEMP_FILES);
		$checkArr = array(
			BASIC_PATH.'app',
			BASIC_PATH.'config',
			BASIC_PATH.'plugins',
			BASIC_PATH.'config/version.php',
			BASIC_PATH.'app/controller/explorer/index.class.php',
		);
		foreach ($checkArr as $path) {
			$info = IO::info($path);
			if(!$info['isWriteable']){return false;}
		}
		return true;
	}
}
repair.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/repair.class.php'
View Content
<?php
/**
 * 数据清理处理
 * autoReset;			// 清空修复异常数据
 * resetSourceEmpty		// source中表清理; sourceHash为空或所属关系错误的条目删除;
 * resetShareTo			// share_to中存在share中不存在的数据清理
 * resetShare			// share中存在,source已不存在的内容清理
 * resetSourceFile		// source中的文件fileID,file中不存在清理;
 * resetFileSource		// file中存在,source中不存在的进行清理
 * resetSourceHistory	// 文件历史版本,fileID不存在的内容清理;
 * resetFileLink		// 重置fileID的linkCount引用计数(source,sourceHistory);
 * clearSameFile		// 清理重复的文件记录
		
 * sql清理操作日志: 
 * delete from `system_log` where createTime < UNIX_TIMESTAMP('2023-03-01 00:00:00')
 */
class adminRepair extends Controller {
	function __construct()    {
		parent::__construct();
		$this->resetPathKey = '';
		$this->pageCount    = 20000;// 分页查询限制; 默认5000, 20000;
	}
	
	/**
	 * 清空修复异常数据(php终止,断电,宕机等引起数据库表错误的进行处理;)
	 * 6个小时执行一次;
	 * 
	 * 手动执行:
	 * http://192.168.1.111/kod/kodbox/?admin/repair/autoReset&done=1
	 * done=1为清理数据;done=2为实际删除不存在的文件记录
	 */
	public function autoReset(){
		$done = isset($this->in['done']) ? intval($this->in['done']) : 0;
		if ($done == 2) {	// 计划任务执行
			$msg = $this->resetPathSource();
			echoLog('异常数据清理,'.$msg.'可在后台任务管理进行中止.');
		} else {
			$cacheKey = 'autoReset';
			$lastTime = Cache::get($cacheKey);
			$lastTime = false;//debug ;
			if($lastTime && time() - intval($lastTime) < 3600*6 ){
				echo '最后一次执行未超过6小时!';return;
			}
			Cache::set($cacheKey,time());
			echoLog('异常数据清理,可在后台任务管理进行中止.');
		}
		// echoLog('请求参数done=1时,不直接删除已缺失的物理文件,可查看“物理文件不存在的数据记录”,确认需要删除后,再执行done=2进行删除');
		echoLog('=====================================================');
		if ($done == 2) {return $this->clearErrorFile();}
		// http_close();

		$this->resetSourceEmpty();		// source中表清理; sourceHash为空或所属关系错误的条目删除;
		$this->resetShareTo();			// share_to中存在share中不存在的数据清理
		$this->resetShare();			// share中存在,source已不存在的内容清理
		$this->resetSourceFile();		// source中的文件fileID,file中不存在清理;
		$this->resetFileSource();		// file中存在,source中不存在的进行清理
		$this->resetSourceHistory();	// 文件历史版本,fileID不存在的内容清理;
		$this->resetFileLink();			// 重置fileID的linkCount引用计数(source,sourceHistory);
		$this->clearSameFile();			// 清理重复的文件记录
		write_log('异常数据清理完成!','sourceRepair');
		echoLog('=====================================================');
		echoLog('异常数据清理完成!');
	}
	
	public function clearEmptyFile(){
		Model('File')->clearEmpty(0);
		pr('ok');
	}

	// 处理指定目录数据
	private function resetPathSource(){
		if (!isset($this->in['path'])) return '';
		$path	= $this->in['path'];
		$parse	= KodIO::parse($path);
		$info	= IO::infoSimple($path);
		$id 	= $parse['id'];
		if (!$id || !$info || $info['isFolder'] != 1) {
			echoLog('指定目录参数错误: path='.$path);exit;
		}
		// 获取指定目录下子文件
		$pathLevel = $info['parentLevel'].$id.',';
		$where	= array(
			'isFolder' => 0,
			'parentLevel' => array('like', $pathLevel.'%')
		);
		$result = Model("Source")->where($where)->select();
		if (!$result) {
			echoLog('指定目录下没有待处理数据: path='.$path);exit;
		}
		$this->resetPathKey = 'repair.reset.path.'.$id;
		Cache::remove($this->resetPathKey);

		$source = $file = array();
		foreach ($result as $item) {
			$source[] = $item['sourceID'];
			$file[] = $item['fileID'];
		}
		$cache = array(
			'source'=> array_filter(array_unique($source)),
			'file'	=> array_filter(array_unique($file)),
		);
		Cache::set($this->resetPathKey, $cache);
		return '执行目录: '.$path.',';
	}
	private function pathWhere($model, $file=false, $shareTo=false) {
		if (!$this->resetPathKey) return;
		$cache = Cache::get($this->resetPathKey);
		if (!$cache) {
			echoLog('缓存数据异常,请尝试重新执行!');exit;
		}
		$key = $file ? 'file' : 'source';
		$ids = $cache[$key];
		$where = array($key.'ID'=>array('in', $ids));
		if ($shareTo) {
			$list	= Model('Share')->where($where)->select();
			if (!$list) {
				$where = array('shareID'=>0);
			} else {
				$ids	= array_to_keyvalue($list, '', 'shareID');
				$ids	= array_filter(array_unique($ids));
				$where	= array('shareID'=>array('in',$ids));
			}
		}
		$model->where($where);
	}
	
	/**
	 * 清除已不存在的物理文件记录
	 * 需先执行autoReset方法,并查看sourceRepair日志【resetFileLink--已不存在的物理文件】,确认是否需要清除
	 * @return void
	 */
	public function clearErrorFile(){
		$cache = Cache::get('clear_file_'.date('Ymd'));
		if (!$cache || !is_array($cache)) {
			echoLog('没有缺失的物理文件记录!');
			echoLog('注意:此记录从缓存中获取,缓存数据在执行done=1时产生,因此请务必先执行done=1.');
			exit;
		}
		echoLog('clearErrorFile,物理文件不存在的数据处理;');
		$model = Model('File');
		$modelSource = Model("Source");$modelHistory = Model("SourceHistory");
		$result = array('file' => 0, 'source' => 0);
		foreach ($cache as $item) {
			$rest = $this->delFileNone($model, $modelSource, $modelHistory, $item);
			$result['file'] += $rest['file'];
			$result['source'] += $rest['source'];
			echoLog('file:'.$result['file'].';source:'.$result['source'],true);
		}
		Cache::remove('clear_file_'.date('Ymd'));
		echoLog('clearErrorFile,finished:清除已不存在的物理文件记录共'.$result['file'].'条,涉及source记录'.$result['source'].'条!');
		exit;
	}

	/**
	 * source表中异常数据处理: 
	 * 1. parentID为0, 但 parentLevel不为0情况处理;
	 * 2. parentID 不存在处理;
	 * 3. sourchHash 为空的数据;
	 */
	public function resetSourceEmpty(){
		$taskID ='resetSourceEmpty';$pageNum = $this->pageCount;$page = 1;$changeNum = 0;
		$model = Model("Source");
		$this->pathWhere($model);
		$list = $model->selectPage($pageNum,$page);
		
		$task = TaskLog::newTask($taskID,'source表异常数据处理',$list['pageInfo']['totalNum']);
		while($list && $page <= $list['pageInfo']['pageTotal']){
			$parentSource = $removeSource = $removeFiles = array();
			foreach ($list['list'] as $item) {
				$levelEnd = ','.$item['parentID'].',';
				$levelEndNow = substr($item['parentLevel'],- strlen($levelEnd));
				if( $item['sourceHash'] == '' || $levelEndNow != $levelEnd ){
					$changeNum++;write_log(array($taskID,$item),'sourceRepair');
					$task->task['desc'] = $task->task['currentTitle'] = $changeNum.'个不存在';
					$parentSource[] = $item['parentID'];
					$removeSource[] = $item['sourceID'];
					$removeFiles[]  = $item['fileID'];
				}
				$task->update(1);
			}
			$model->removeRelevance($removeSource,$removeFiles); // 优化性能;
			$this->folderSizeReset($parentSource);
			$this->pathWhere($model);$page++;
			$list = $model->selectPage($pageNum,$page);
		}
		$task->end();
	}
	
	// source对应fileID 不存在处理;
	public function resetSourceFile(){
		$taskID ='resetSourceFile';$pageNum = $this->pageCount;$page = 1;$changeNum = 0;
		$model = Model("Source");$modelFile = Model("File");
		$this->pathWhere($model);
		$list = $model->selectPage($pageNum,$page);

		$task = TaskLog::newTask($taskID,'source表空数据处理',$list['pageInfo']['totalNum']);
		while($list && $page <= $list['pageInfo']['pageTotal']){
			$parentSource = $removeSource = $removeFiles = array();
			foreach ($list['list'] as $item) {
				if($item['isFolder'] == '0' && !$modelFile->find($item['fileID'])){
					$changeNum++;write_log(array($taskID,$item),'sourceRepair');
					$task->task['desc'] = $task->task['currentTitle'] = $changeNum.'个不存在';
					$parentSource[] = $item['parentID'];
					$removeSource[] = $item['sourceID'];
					$removeFiles[]  = $item['fileID'];
				}
				$task->update(1);
			}
			$model->removeRelevance($removeSource,$removeFiles); // 优化性能;
			$this->folderSizeReset($parentSource);
			$this->pathWhere($model);$page++;
			$list = $model->selectPage($pageNum,$page);
		}
		$task->end();
	}

	// 重置文件夹大小
	private function folderSizeReset($parentSource){
		$model = Model("Source");
		$parentSource = array_filter(array_unique($parentSource));
		foreach ($parentSource as $sourceID) {
			$model->folderSizeReset($sourceID);
		}
	}

	public function resetFileHash(){
		$taskID ='resetFileHash';$pageNum = $this->pageCount;$page = 1;$changeNum = 0;
		$model  = Model('File');
		$this->pathWhere($model, true);
		$list = $model->selectPage($pageNum,$page);
		
		$task = TaskLog::newTask($taskID,'更新文件hash',$list['pageInfo']['totalNum']);
		while($list && $page <= $list['pageInfo']['pageTotal']){
			foreach ($list['list'] as $item) {
				if(!$item['hashSimple'] || !$item['hashMd5']){
					$data = array('hashSimple'=>IO::hashSimple($item['path']) );
					if(!$item['hashMd5']){$data['hashMd5'] = IO::hashMd5($item['path']);}
					
					$changeNum++;write_log(array($taskID,$item),'sourceRepair');
					$task->task['desc'] = $task->task['currentTitle'] = $changeNum.'个修改';
					$model->where(array('fileID'=>$item['fileID']))->save($data);
				}
				$task->update(1);
			}
			$this->pathWhere($model, true);$page++;
			$list = $model->selectPage($pageNum,$page);
		}
		$task->end();
	}
	
	// 重置分享,内部协作分享数据;(删除文件夹,删除对应分享,及内部协作分享)
	public function resetShareTo(){
		$taskID ='resetShareTo';$pageNum = $this->pageCount;$page = 1;$changeNum = 0;
		$model  = Model('share_to');$modelShare = Model("share");
		$this->pathWhere($model, false, true);
		$list = $model->selectPage($pageNum,$page);
		
		$task = TaskLog::newTask($taskID,'重置内部协作数据',$list['pageInfo']['totalNum']);
		while($list && $page <= $list['pageInfo']['pageTotal']){
			foreach ($list['list'] as $item) {
				$where = array("shareID"=>$item['shareID']);
				if(!$modelShare->where($where)->find()){
					$changeNum++;write_log(array($taskID,$item),'sourceRepair');
					$task->task['desc'] = $task->task['currentTitle'] = $changeNum.'个不存在';
					$model->where($where)->delete();
				}
				$task->update(1);
			}
			$this->pathWhere($model, false, true);$page++;
			$list = $model->selectPage($pageNum,$page);
		}
		$task->end();
	}
	
	// 重置分享,内部协作分享数据;(删除文件夹,删除对应分享,及内部协作分享)
	public function resetShare(){
		$taskID ='resetShare';$pageNum = $this->pageCount;$page = 1;$changeNum = 0;
		$model = Model('share');$modelSource = Model("Source");
		$this->pathWhere($model);
		$list = $model->selectPage($pageNum,$page);

		$task = TaskLog::newTask($taskID,'重置分享数据',$list['pageInfo']['totalNum']);
		while($list && $page <= $list['pageInfo']['pageTotal']){
			foreach ($list['list'] as $item) {
				$where = array("sourceID"=>$item['sourceID']);
				if($item['sourceID'] != '0' && !$modelSource->where($where)->find()){
					$changeNum++;write_log(array($taskID,$item),'sourceRepair');
					$task->task['desc'] = $task->task['currentTitle'] = $changeNum.'个不存在';
					
					$where = array('shareID'=>$item['shareID']);
					$model->where($where)->delete();
					Model('share_to')->where($where)->delete();
				}
				$task->update(1);
			}
			$this->pathWhere($model);$page++;
			$list = $model->selectPage($pageNum,$page);
		}
		$task->end();
	}

	// file表中存在, source表中不存在的进行清除;历史记录表等;
	public function resetFileSource(){
		$taskID ='resetFileSource';$pageNum = $this->pageCount;$page = 1;$changeNum = 0;
		$model = Model("File");$modelSource = Model("Source");
		$modelHistory = Model('SourceHistory');;
		$this->pathWhere($model, true);
		$list = $model->selectPage($pageNum,$page);
		$stores = Model('Storage')->listData();
	    $stores = array_to_keyvalue($stores, '', 'id');	// 有效存储列表

		$task = TaskLog::newTask($taskID,'Source记录异常处理',$list['pageInfo']['totalNum']);
		while($list && $page <= $list['pageInfo']['pageTotal']){
			foreach ($list['list'] as $item) {
				$where = array("fileID"=>$item['fileID']);
				$findSource  = $modelSource->where($where)->find();
				$findHistory = $modelHistory->where($where)->find();
				if(!$findSource && !$findHistory){
					// // 正常滞后1天删除的数据,不处理,避免物理文件遗留
					// $fromTime = time() - 3600*24*1;
					// if ($item['linkCount'] == '0' && intval($item['modifyTime']) > $fromTime) continue;

					$changeNum++;write_log(array($taskID,$item),'sourceRepair');
					$task->task['desc'] = $task->task['currentTitle'] = $changeNum.'个不存在';
					// if (in_array($item['ioType'], $stores)) {IO::remove($item['path']);}
					$model->where($where)->delete();
				}
				$task->update(1);
			}
			$this->pathWhere($model, true);$page++;
			$list = $model->selectPage($pageNum,$page);
		}
		$task->end();
	}
	
	// File表中,io不存在的文件进行处理;(被手动删除的)
	public function resetFileLink(){
		$taskID ='resetFileLink';$pageNum = $this->pageCount;$page = 1;$changeNum = 0;
		$model = Model('File');
		$this->pathWhere($model, true);
		$list = $model->selectPage($pageNum,$page);
		$stores = Model('Storage')->listData();
	    $stores = array_to_keyvalue($stores, '', 'id');	// 有效存储列表

		$cache = array();$rest = array('file' => 0, 'source' => 0);
		$task  = TaskLog::newTask($taskID,'重置清理File表引用',$list['pageInfo']['totalNum']);
		while($list && $page <= $list['pageInfo']['pageTotal']){
			foreach ($list['list'] as $item) {
				$ioNone = in_array($item['ioType'], $stores);
				if($ioNone && IO::exist($item['path']) ){
					$model->resetFile($item);
				}else{
					$changeNum++;write_log(array($taskID.'--已不存在的物理文件',$item),'sourceRepair');
					$task->task['desc'] = $task->task['currentTitle'] = $changeNum.'个不存在'.';'.$item['path'];
					$cache[] = array(
						'fileID'	=> $item['fileID'],
						'linkCount' => $item['linkCount'],
					);
				}
				$task->update(1);
			}
			$this->pathWhere($model, true);$page++;
			$list = $model->selectPage($pageNum,$page);
		}
		$task->end();
		if($cache) Cache::set('clear_file_'.date('Ymd'), $cache);
		return $rest;
	}
	// 删除不存在的物理文件
	private function delFileNone($model, $modelSource, $modelHistory, $item){
		$where = array("fileID"=>$item['fileID']);
		$list  = $modelSource->where($where)->select();
		$cnt1  = $modelSource->where($where)->delete();
		$modelHistory->where($where)->delete();

		$model->metaSet($item['fileID'],null,null);
		$cnt2 = $model->where(array('fileID'=>$item['fileID']))->delete();
		
		// 重置父目录大小
		$list = array_to_keyvalue($list, '', 'parentID');
		$this->folderSizeReset($list);	// TODO 不必要每个删除都重置大小,待优化
		return array('source' => intval($cnt1),'file'=> intval($cnt2));
	}

	public function resetSourceHistory(){
		$taskID ='resetSourceHistory';$pageNum = $this->pageCount;$page = 1;$changeNum = 0;
		$model = Model('SourceHistory');$modelSource = Model("Source");
		$modelFile = Model("File");
		$this->pathWhere($model);
		$list = $model->selectPage($pageNum,$page);

		$task = TaskLog::newTask($taskID,'历史版本异常数据处理',$list['pageInfo']['totalNum']);
		while($list && $page <= $list['pageInfo']['pageTotal']){
			foreach ($list['list'] as $item) {
				$where = array("sourceID"=>$item['sourceID']);
				if( !$modelSource->where($where)->find() ){
					$changeNum++;write_log(array($taskID,$item),'sourceRepair');
					$task->task['desc'] = $task->task['currentTitle'] = $changeNum.'个不存在';
					$model->where($where)->delete();
				}
				if( !$modelFile->where(array('fileID'=>$item['fileID']))->find()){
					$changeNum++;write_log(array($taskID.',fileError!',$item),'sourceRepair');
					$task->task['desc'] = $task->task['currentTitle'] = $changeNum.'个不存在';	
					$model->where(array('fileID'=>$item['fileID']))->delete();
				}
				$task->update(1);
			}
			$this->pathWhere($model);$page++;
			$list = $model->selectPage($pageNum,$page);
		}
		$task->end();
	}
	
	// 文件列表自然排序,文件名处理; 升级向下兼容数据处理;
	public function sourceNameInit(){
		// Model("SystemOption")->set('sourceNameSortFlag','');
		if(Model("SystemOption")->get('sourceNameSortFlag')) return;
		$this->sourceNameSort();
	}
	public function sourceNameSort(){
		$taskID ='sourceNameSort';$pageNum = $this->pageCount;$page = 1;$changeNum = 0;
		Model("SystemOption")->set('sourceNameSortFlag','1');
		$model = Model('Source');$modelMeta = Model("io_source_meta");
		$pageNum = $this->pageCount;$page = 1;$changeNum = 0;
		$model->selectPageReset();
		$list = $model->field('sourceID,name')->selectPage($pageNum,$page);
		
		$task = TaskLog::newTask($taskID,'更新Source排序名',$list['pageInfo']['totalNum']);
		while($list && $page <= $list['pageInfo']['pageTotal']){
			$metaAdd = array();
			foreach ($list['list'] as $item){
				if(!$item['name']) continue;
				$metaAdd[] = array(
					'sourceID' 	=> $item['sourceID'],
					'key'		=> 'nameSort',
					'value'		=> KodSort::makeStr($item['name']),
				);
				$task->update(1);
				if(count($metaAdd) >= 1000){
					$modelMeta->addAll($metaAdd,array(),true);$metaAdd = array();
				}
			}
			if(count($metaAdd) > 0){
				$modelMeta->addAll($metaAdd,array(),true);$metaAdd = array();
			}
			$page++;
			$list = $model->field('sourceID,name')->selectPage($pageNum,$page);
		}
		Model("SystemOption")->set('sourceNameSortFlag','2');
		$model->selectPageRestore();
		$task->end();
	}

	/**
	 * 根据sourceID彻底删除文件,sourceID可传多个,如sourceID=1,2,3
	 * @return void
	 */
	public function clearSource(){
		echoLog('根据sourceID彻底删除关联文件!参数sourceID=1,2,3');
		$ids = $this->in['sourceID'];
		if (!$ids) {
			echoLog('无效的参数:sourceID!');exit;
		}
		// 1.根据sourceID查fileID
		$ids = array_filter(explode(',',$ids));
		$where = array(
			'isFolder' => 0,
			'sourceID' => array('in', $ids)
		);
		$list = Model('Source')->where($where)->field('sourceID,fileID')->select();
		if (empty($list)) {
			echoLog('找不到对应的source记录,请检查sourceID是否正确.');
			exit;
		}

		echoLog('删除开始:');
		$ids  = array_to_keyvalue($list, '', 'sourceID');
		$file = array_to_keyvalue($list, '', 'fileID');
		$file = array_filter($file);
		$fCnt = count($file);
		// 2.根据fileID查所有sourceID
		if (!empty($file)) {
			$where = array('fileID'=>array('in', $file));
			$list = Model('Source')->where($where)->field('sourceID')->select();
			$ids = array_to_keyvalue($list, '', 'sourceID');
		}
		$ids  = array_filter($ids);
		$sCnt = count($ids);
		// 3.根据sourceID删除文件
		foreach ($ids as $i => $id) {
			$path = KodIO::make($id);
			IO::remove($path, false);
			echoLog('source记录:'.($i+1), true);
		}
		// 4.删除可能还存在的file记录——实际物理文件删除与否不影响
		if (!empty($file)) {
			$where = array('fileID'=>array('in', $file));
			Model('File')->where($where)->delete();
		}
		echoLog("删除完成!共删除source记录{$sCnt}条;file记录{$fCnt}条.");
	}
	
	// 指定sourceID重置对应目录大小
	public function resetSizeById($echo=true){
		$id = $this->in['sourceID'];
		if(!$id) return;
		model('Source')->folderSizeResetChildren($id);
		if ($echo) echoLog("更新完成.".KodIO::make($id));
	}
	
	// 重复文件清理; 根据hashMd5处理;
	public function clearSameFile(){
		$taskID ='clearSameFile';$pageNum = $this->pageCount;$page = 1;$changeNum = 0;
		$list = Model()->query('select hashMd5,count(1) from io_file group by hashMd5 having count(hashMd5)>1;');
		$list = is_array($list) ? $list : array();
		$modelFile = Model("File");
		
		$task = TaskLog::newTask($taskID,'重复文件清理',count($list));
		foreach ($list as $item) {
			if(!$item['hashMd5'] || $item['hashMd5'] == '0') continue;
			$where = array("hashMd5"=>$item['hashMd5']);
			
			$files = $modelFile->field('fileID,path,linkCount')->where($where)->order('fileID asc')->select();
			$files = is_array($files) ? $files : array();
			$fileRemove = array();$linkCount = 0;
			foreach ($files as $i=>$file){
				if($i == 0) continue;
				$linkCount += intval($file['linkCount']);
				$fileRemove[] = $file['fileID'];
				if($file['path'] && $file['path'] != $files[0]['path']){
					IO::remove($file['path']);
				}
			}
			if($fileRemove){
				$fileID = $files[0]['fileID'];
				$linkCount += intval($files[0]['linkCount']);
				$fileWhere = array('fileID'=>array('in',$fileRemove));
				$save = array('fileID'=>$fileID);
				Model("Source")->where($fileWhere)->save($save);
				Model("SourceHistory")->where($fileWhere)->save($save);
				Model("share_report")->where($fileWhere)->save($save);
				
				Model("io_file_meta")->where($fileWhere)->delete();
				Model("io_file_contents")->where($fileWhere)->delete();
				Model("io_file_meta")->where($fileWhere)->delete();
				$modelFile->where($fileWhere)->delete();
				$modelFile->where(array('fileID'=>$fileID))->save(array('linkCount'=>$linkCount));
				
				$changeNum++;write_log(array($taskID,$item),'sourceRepair');
				$task->task['desc'] = $task->task['currentTitle'] = $changeNum.'个修改';
			}
			$task->update(1);
		}
		$task->end();
	}

	/**
	 * 清除用户回收站文件
	 * /?admin/repair/clearUserRecycle&limit=100&days=3&userID=1
	 * @return void
	 */
	public function clearUserRecycle() {
		$limit  = intval(_get($this->in, 'limit', 100));
        $days   = intval(_get($this->in, 'days', 3));
        $userID = intval(_get($this->in, 'userID', 0));

        $cckey  = 'clearRecycleFiles-'.implode('-', array($limit, $days, $userID));
        $data	= Cache::get($cckey);

        // 获取待清理列表
        if (!isset($this->in['done']) || !$data) {
            $data = $this->getRecycleList($limit, $days, $userID);
        }

        // 标题
        $title = '清理用户回收站,筛选条件:';
        if ($limit) $title .= '回收站文件超过'.$limit.'个的';
        if ($userID) {
            $title .= '指定用户(userID='.$userID.')';
        } else {
            $title .= '所有用户';
        }
        $title .= '的,回收站中';
        if ($days) {
            $title .= $days.'天前删除的文件';
        } else {
            $title .= '所有的文件';
        }
        echoLog($title.'。');

        // 清理确认
        if (!$data) {
            echoLog('符合条件的数据为空,无需处理。');exit;
        }
        echoLog('符合条件的共'.count($data['list']).'个用户,待清理'.$data['count'].'个文件。');
        echoLog('');
        if (!isset($this->in['done'])) {
            Cache::set($cckey, $data, 600);
            echoLog('如确认需要清理,请在地址中追加参数后再次访问:&done=1');exit;
        }
        Cache::remove($cckey);

        // 按用户循环清理
        echoLog('开始清理...');
        foreach ($data['list'] as $userID => $pathArr) {
            echoLog('开始清理用户('.$userID.')的回收站中,共'.count($pathArr).'个文件;');
            Model('SourceRecycle')->remove($pathArr, $userID);
		    Action('explorer.recycleDriver')->remove($pathArr);
            Model('Source')->targetSpaceUpdate(SourceModel::TYPE_USER,$userID);
        }
        echoLog('清理完成!');exit;
	}
	// 获取回收站文件列表
    private function getRecycleList($limit, $days, $userID=0) {
        $model = Model('SourceRecycle');

        $where = array();
        // >n个文件
		if ($userID) {
			$where['userID'] = $userID;
		} else {
			if ($limit) {
				$sql = "SELECT count(sourceID) as cnt, userID FROM `io_source_recycle` GROUP BY userID HAVING cnt > $limit";
				$list = $model->query($sql);
				if (!$list) return array();
				$list = array_to_keyvalue($list, '', 'userID');
				$where['userID'] = array('in', $list);
			}
		}
		// n天前
        if ($days) {
            $time = date("Y-m-d 23:59:59",strtotime("-$days day"));
            $where['createTime'] = array('<=', strtotime($time));
        }
        if ($where) $model->where($where);

        // 按条件查询用户回收站中的文件sourceID
        $list = $model->field('sourceID, userID')->select();
        if (!$list) return array();
        $count = count($list);

        $list = array_to_keyvalue_group($list, 'userID', 'sourceID');
        return array('list' => $list, 'count' => $count);
    }


	/**
	 * 重置文件层级
	 * 1.parentID和parentLevel[-2]不等,更新parentLevel;剩余部分为不等且parentID无对应记录,更新parentID——还有剩余,则为相等且无对应记录,不处理
	 * 2.查询所有parentID+parentLevel+fileID+isFolder+name重复的记录,文件夹则重命名,文件则删除
	 * @return void
	 */
	public function resetParentLevel(){
		KodUser::checkRoot();
		ignore_timeout();
		$model = Model('Source');

		if (!$this->in['done']) {
			$this->cliEchoLog('本接口用于修复文件层级异常,调用前请务必备份数据库。如已备份且确定执行,请在地址中追加参数后再次访问:&done=1');
			$this->cliEchoLog('重复目录会进行重命名(原名@年月日时分秒-1);重复文件会被删除到当前用户(管理员)个人回收站,执行后可在回收站查看和处理对应文件。');
			exit;
		}
		// 0.重复数据临时表文件
		mk_dir(TEMP_FILES);
		$file = TEMP_FILES.'tmp_level_source_'.date('Ymd').'.txt';
		if (file_exists($file) && !filesize($file)) del_file($file);

		// io_source总记录数超过500w时,建议命令行调用
		$total = $model->count();
		if (!is_cli() && $total > 10000*500) {
			$cmd = 'php '.BASIC_PATH.'index.php "admin/repair/resetParentLevel&accessToken='.Action("user.index")->accessToken().'&done=1"';
			$this->cliEchoLog('数据量过大,为避免执行超时,请在命令行执行:'.$cmd);
			exit;
		}
		$model->execute('SET SESSION group_concat_max_len = 1000000');
		$this->systemMtce(1);

		// 1.父目录层级与PID不匹配
		$timeStart = microtime(true);
		if (!file_exists($file)) {
			$this->cliEchoLog('1.正在处理层级异常数据,共'.$total.'条记录,可能耗时较长,请耐心等待...');
			// 批量查询后批量更新特别慢,改为直接数据库更新
			// // 1.0 删除找不到parentID的记录——暂不处理,可以考虑统一移到某个指定目录
			// $sql = "UPDATE io_source AS t1 LEFT JOIN io_source AS t2 ON t1.parentID = t2.sourceID
			// 		SET t1.isDelete = 1
			// 		WHERE t1.isDelete = 0 AND t1.parentID > 0 AND t2.sourceID IS NULL";

			// 1.1 更新为:parentLevel=>parentID.parentLevel+sourceID——2千万条记录,380万条更新,需要约30分钟
			$sql = "UPDATE io_source AS t1 JOIN io_source AS t2 ON t1.parentID = t2.sourceID
					SET t1.parentLevel = CONCAT(t2.parentLevel, t2.sourceID, ',')
					WHERE t1.isDelete = 0 AND t1.parentID > 0 
					AND t1.parentLevel != '' AND t1.parentID != SUBSTRING_INDEX(SUBSTRING_INDEX(t1.parentLevel, ',', -2), ',', 1)";
			$cnt = $model->execute($sql);
			$timeNow = microtime(true);
			$this->cliEchoLog('>1.1 层级异常数据[1]处理完成,异常文件:'.intval($cnt).',耗时:'.round(($timeNow - $timeStart),1).'秒。');

			// 1.2 不等且parentID对应记录为空(剩余部分为相等且为空,不处理):parentID=>parentLevel[-2]——2千万条记录需要约3分钟,查询需1分钟
			$sql = "UPDATE io_source SET parentID = SUBSTRING_INDEX(SUBSTRING_INDEX(parentLevel, ',', -2), ',', 1)
					WHERE isDelete = 0 AND parentID > 0 
					AND parentLevel != '' AND parentID != SUBSTRING_INDEX(SUBSTRING_INDEX(parentLevel, ',', -2), ',', 1)";
			$cnt = $model->execute($sql);
			$timeStart = microtime(true);
			$this->cliEchoLog('>1.2 层级异常数据[2]处理完成,异常文件:'.intval($cnt).',耗时:'.round(($timeStart - $timeNow),1).'秒。');
		}

		// 2.文件/夹重复:parentLevel/fileID/parentID相同
		// 2.1 创建临时表,获取(parentLevel,fileID)重复的记录
		// $timeStart = microtime(true);
		$this->cliEchoLog('2.准备处理重复文件:');
		if (!file_exists($file)) {
			$this->cliEchoLog('2.1 正在统计重复文件,共'.$total.'条记录...');
			// 1.获取fileID重复的记录
			// 直接查询会因为group_concat导致内存溢出,改为按fileID分批查询
			// $sql = 'SELECT GROUP_CONCAT(sourceID) AS ids, isFolder, COUNT(*) AS cnt 
			// 		FROM io_source 
			// 		WHERE isDelete = 0 AND parentID > 0 GROUP BY parentLevel, fileID, isFolder, name
			// 		HAVING cnt > 1 
			// 		ORDER BY isFolder ASC';
			$idx = 0;
			$tmp = array();
			$sql = 'SELECT fileID,count(*) AS cnt FROM io_source WHERE isDelete = 0 AND parentID > 0
					GROUP BY fileID HAVING cnt > 1 ORDER BY fileID DESC';	// 文件夹排后面
			$res = $model->query($sql);	// 2千万条数据耗时2分钟
			$cnt = count($res);
			// 2.根据sourceID获取parentID+parentLevel+fileID+isFolder+name重复的记录
			$handle = fopen($file, 'w');	// a为追加模式
			foreach ($res as $i => $item) {
				if ($i % 100 == 0 || $i == ($cnt - 1)) {
					$msg = $this->ratioText($i, $cnt);
					$this->cliEchoLog('>['.$idx.'] '.$msg, true);
				}
			    $tmp[$item['fileID']] = intval($item['cnt']);
				if (array_sum($tmp) < 10000) continue;
				$this->groupLevelList($model, $handle, $tmp, $idx);
			}
			$this->groupLevelList($model, $handle, $tmp, $idx);
			fclose($handle);

			if (!filesize($file)) {
				$this->systemMtce();
				$this->cliEchoLog('>文件层级正常,无需处理。');exit;
			}
			$this->cliEchoLog('>重复文件统计完成,耗时:'.round((microtime(true) - $timeStart),1).'秒。');
		} else {
			if (!filesize($file)) {
				$this->systemMtce();
				$this->cliEchoLog('>临时表文件为空,请重试。');
				del_file($file);
				exit;
			}
		}

		// 2.2 比较parentLevel最后一位和parentID,相等说明同一目录下重复,则只保留一条(其他删除);不等则获取parentID对应的parentLevel并更新
		$timeStart = microtime(true);
		$cnt = 0;
		$res = array();
		$handle = fopen($file, 'r');
		while (!feof($handle)) {
			$tmp = json_decode(trim(fgets($handle)), true);
			if (!$tmp) continue;
			$cnt+= intval($tmp['cnt']);
			$res[] = $tmp;
		}
		fclose($handle);
		$this->cliEchoLog('2.2 正在处理重复文件,共'.$cnt.'条数据...');
		$idx = $tmpIdx = 0;
		$ids = $plvls = $updates = $renames = $removes = array();
		// $data = array('repeat' => 0, 'rename' => 0, 'remove' => 0, 'update' => 0);
		$data = array('repeat' => 0, 'rename' => 0);
		foreach($res as $i => $item) {
			$where = array(
				'sourceID'		=> array('in', explode(',', $item['ids'])),
			);
			$tmps = array();
			$list = $model->where($where)->field('sourceID,parentID,parentLevel,name')->order('isDelete,sourceID asc')->select();
			// $cnt += (count($list) - $item['cnt']);	// 执行过程中,总数据量可能有变化,实时更新
			foreach($list as $j => $value) {
				$idx++;
				$tmpIdx++;
				$sid = $value['sourceID'];
				$arr = explode(',', $value['parentLevel']);
				$pid = $arr[count($arr)-2];		// level中的parentID
				$thePid = $value['parentID'];	// 实际的parentID
				
				// 输出进度
				$prfx = '>'.$idx.'['.$sid.'] ';
				if ($tmpIdx >= mt_rand(1000,2000) || $idx == $cnt) {
					$tmpIdx = 0;
					$msg = $this->ratioText($idx, $cnt);
					$msg .= ' '.str_replace(array('{','}','"'),'',json_encode($data));
				}
				if ($idx % 100 == 0 || $idx == $cnt) {
					$this->cliEchoLog($prfx.$msg, true);
				}
				// 2.1 parentID=parentLevel[-2],说明层级正常,判断是否有重名:文件夹重命名;文件删除
				if ($pid == $thePid) {
					$name = $value['name'];
					// 1.不重名,不处理
					if (!in_array($name, $tmps)) {
						$tmps[] = $name;
						continue;
					}
					// 2.文件重名,删除
					if ($item['isFolder'] != '1') {
						$data['repeat']++;
						// TODO 移到到回收站比较慢;直接批量更新会导致文件没有归属
						// $res = $model->remove($sid);	// 删除到回收站
						$removes[] = array(
							'sourceID',$sid, //where
							'isDelete',1, //save,只能更新一个字段
						);
						$this->_saveAll($model, $removes, true);
						continue;
					}
					// 3.文件夹重名,只重命名不删除——无法判断子内容是否相同
					$data['rename']++;
					$renames[] = array(
						'sourceID',$sid, //where
						'name',addslashes($name).'@'.date('YmdHis').'-'.$j //save,只能更新一个字段
					);
					$this->_saveAll($model, $renames);
					continue;
				}
				// 不相等的是sourceID=parentID不存在的数据,不做处理
				// // 2.2 parentID和parentLevel中的不同,说明层级异常,根据parentID查询并更新parentLevel
				// if (!isset($plvls[$thePid])) {
				// 	// update io_source set parentLevel = (select concat(parentLevel,sourceID,',') from io_source where sourceID = 27138034) where sourceID = 27138041
				// 	$res = $model->where(array('sourceID' => $thePid))->field('parentLevel')->find();
				// 	// parentID不存在,删除
				// 	if (!$res) {
				// 		$data['remove']++;
				// 		// $res = IO::remove(KodIO::make($sid));
				// 		// $res = $model->remove($sid);
				// 		$res = $model->where(array('sourceID'=>$sid))->save(array('isDelete'=>1,'modifyTime'=>time()));
				// 		continue;
				// 	}
				// 	$parentLevel = $res['parentLevel'];
				// 	$plvls[$thePid] = $parentLevel;
				// } else {
				// 	$parentLevel = $plvls[$thePid];
				// }
				// $parentLevel = $parentLevel . $thePid . ',';
				// // 批量更新
				// // $update = array('parentLevel' => $parentLevel, 'modifyTime' => time());
				// $updates[] = array(
				// 	'sourceID',$sid, //where
				// 	'parentLevel',$parentLevel //save,只能更新一个字段
				// );
				// $this->_saveAll($model, $updates);
				// $data['update']++;
			}
			$tmps = array();
		}
		$res = $this->_saveAll($model, $removes, true, true);
		$res = $this->_saveAll($model, $renames, false,true);
		// $res = $this->_saveAll($model, $updates, false, true);
		del_file($file);

		$logs = array(
			'重复文件:' . $data['repeat'],
			'重名目录:' . $data['rename'],
			// '无效目录:' . $data['remove'],
			// '层级异常:' . $data['update']
		);
		$this->cliEchoLog('>执行完成,统计文件/夹共:'.$idx.',' . implode(',', $logs) . '。总耗时:'.round((microtime(true) - $timeStart),1).'秒');
		$this->systemMtce();
	}
	// 按fileID分组
	private function groupLevelList($model, $handle, &$data, &$idx) {
		if (empty($data)) return;
		$ids = array_keys($data);
		$data = array();
		// 按 fileID 分批处理——已更新过parentID<>parentLevel[-2]的数据,未能更新的是sourceID=parentID不存在的数据,所以此处group by加上parentID
		$where = 'fileID' . (count($ids) > 1 ? ' IN (' . implode(',', $ids) . ')' : '=' . $ids[0]);
		$sql = "SELECT GROUP_CONCAT(sourceID) AS ids, isFolder, COUNT(*) AS cnt
					FROM io_source
					WHERE {$where} AND isDelete = 0 AND parentID > 0
					GROUP BY parentID, parentLevel, fileID, isFolder, BINARY name
					HAVING cnt > 1";
		$res = $model->query($sql);
		if (!$res) return;
		foreach ($res as $item) {
			$idx += intval($item['cnt']);
			fwrite($handle, json_encode($item) . "\n");
		}
	}
	// 批量更新
	private function _saveAll($model, &$update, $remove=false, $done=false) {
		if (!$done) {
			if (count($update) < 1000) return;
		} else {
			if (empty($update)) return;
		}
		if (empty($update)) return;
		$model->saveAll($update);	// 没有返回结果
		// 添加到个人回收站,管理员自行选择清除
		if ($remove) {
			$ids = array();
			foreach ($update as $value) {$ids[] = $value[1];}
			// 1.将标记为删除的数据写入回收站
			$recModel = Model('SourceRecycle');
			$sql = "insert into io_source_recycle (targetType, targetID, sourceID, userID, parentLevel, createTime) 
					(select targetType, targetID, sourceID, ".USER_ID.", parentLevel, UNIX_TIMESTAMP()
					from io_source where sourceID in (".implode(',', $ids)."))";
			$recModel->execute($sql);
			// 2.获取写入回收站的parentLevel
			$where = array('userID'=>USER_ID, 'sourceID'=>array('in',$ids));
			$list = $recModel->where($where)->field('parentLevel')->select();
			$list = array_unique(array_to_keyvalue($list, '', 'parentLevel'));
			// 3.根据parentLevel获取sourceID,重置对应目录大小
			$list = $this->getResetSizeIds($list);
			$tmpIn = $this->in;
			foreach ($list as $i => $id) {
				$this->in['sourceID'] = $id;
				$this->resetSizeById(false);
			}
			$this->in = $tmpIn;
		}
		$update = array();
	}
	private function cliEchoLog($msg, $rep = false){
		static $iscli;
		if (is_null($iscli)) $iscli = is_cli();
		if (!$iscli) return echoLog($msg, $rep);
		// 替换最后执行时没有换行
		static $repLast;
		if ($rep) {
			if ($repLast) echo "\033[A";  // ANSI 转义码:回到上一行
			$lineLength = (int) exec('tput cols');
			echo "\r" . str_repeat(' ', $lineLength) . "\r" . $msg . "\n";
		} else {
			echo $msg."\n";
		}
		ob_flush(); flush();
		$repLast = $rep;
	}
	private function ratioText($idx, $cnt){
		$now = str_pad($idx, strlen($cnt), ' ', STR_PAD_LEFT);	// 占位,避免内容抖动
		$rto = str_pad(round(($idx / $cnt) * 100, 1), 5, ' ', STR_PAD_LEFT);
		return $now.'/'.$cnt.' | '.$rto.'%';
	}
	private function systemMtce($status=0){
		ActionCall('user.index.maintenance', true, $status);
	}

	// 将异常的(没有归属的)删除数据写入个人回收站,并重置目录大小
	public function resetParentLevelClear(){
		KodUser::checkRoot();
		ignore_timeout();
		echoLog('本接口用于整理异常的删除数据,并重置相关目录大小。执行后可在当前用户(管理员)个人回收站查看和处理相关文件。');
		$model = Model('SourceRecycle');
		$maxId = $model->max('id');

		$sql = "insert into io_source_recycle (targetType, targetID, sourceID, userID, parentLevel, createTime) 
				(select s.targetType, s.targetID, s.sourceID, ".USER_ID.", s.parentLevel, UNIX_TIMESTAMP()
				from io_source as s 
				left join io_source_recycle as r on s.sourceID = r.sourceID 
				where s.isDelete = 1 and r.sourceID is null)";
		$res = $model->execute($sql);
		if (!$res) {
		    echoLog('目录数据正常,无需处理。');exit;
		}
		// $maxId = 1014;
		$where = array('userID'=>USER_ID, 'id'=>array('>',$maxId));
		$list = $model->where($where)->field('parentLevel')->select();
		$list = array_unique(array_to_keyvalue($list, '', 'parentLevel'));

		$list = $this->getResetSizeIds($list);
		echoLog('开始重置目录大小,共计:'.count($list));
		write_log(array('待重置的目录id列表', $list), 'repair');
		foreach ($list as $i => $id) {
			$this->in['sourceID'] = $id;
		    $this->resetSizeById(false);
			echoLog('已重置:'.($i+1).' =>'.KodIO::make($id), true);
		}
		echoLog('重置完成。');
	}
	private function getResetSizeIds($list){
		$data = array();
		$hash = array_flip($list); // 转换为哈希表加速查找
		foreach ($list as $idx => $path) {
			$trimmed = rtrim($path, ',');
			$parts = explode(',', $trimmed);
			$isBase = true;
			// 检查所有可能的上级路径
			for ($i = 1; $i < count($parts); $i++) {
				$parent = implode(',', array_slice($parts, 0, $i)) . ',';
				if (isset($hash[$parent])) {
					$isBase = false;
					break;
				}
			}
			if ($isBase) {
				// $data[] = $path;
				$data[] = end(explode(',',trim($path,',')));
			}
		}
		return array_unique(array_filter($data));
	}

	/**
	 * 清除我的回收站
	 * @return void
	 */
	public function clearMyRecycle(){
		// KodUser::checkRoot();
		ignore_timeout();

		echoLog('本接口用于清空当前(登录)用户的回收站(仅限系统文件)。');
		if (!isset($this->in['done'])) {
            echoLog('如确认需要执行,请在地址中追加参数后再次访问:&done=1');exit;
        }
		// 1.任务
		$recycleModel = Model('SourceRecycle');
		$where = array("userID"=>USER_ID);
		$total = $recycleModel->where($where)->count();
		if (!$total) {
		    echoLog('当前回收站为空,无需处理。'); exit;
		}
		if ($total > 200000) {
		    // $recycleList = array_slice($recycleList, 0, 200000);
		    echoLog('总文件数:'.$total.',为避免内存溢出,单次仅执行200000条,请分多次执行。');
		}
		$recycleList = $recycleModel->where($where)->limit(200000)->select();

		echoLog('开始加载任务...');
		$pList = $sList = $targetArr = array();
		foreach ($recycleList as $item) {
			$sourceID = $item['sourceID'];
			$pList[] = array("path"=>KodIO::make($sourceID));
			$sList[] = $sourceID;
			$key = $item['targetType'].'_'.$item['targetID'];
			$targetArr[$key] = array(
				"targetType"	=> $item['targetType'],
				'targetID'		=> $item['targetID']
			);
		}
		$this->taskCopyCheck($pList);//彻底删除: children数量获取为0,只能是主任务计数;
		unset($pList);
		echoLog('任务加载完成。');

		// 2.删除
		echoLog('开始删除文件,共有:'.count($sList));
		$sourceModel = Model("Source");
		foreach ($sList as $i => $theID) {
			$sourceModel->remove($theID,false);
			$recycleModel->where(array('sourceID'=>$theID))->delete();
			echoLog($i+1, true);
		}
		unset($sList);
		echoLog('文件删除完成。');

		//更新目标空间大小;
		echoLog('开始更新目录空间占用...');
		foreach ($targetArr as $item) {
			$sourceModel->targetSpaceUpdate($item['targetType'],$item['targetID']);
		}
		unset($targetArr);

		// 3.清空回收站时,重新计算大小; 一小时内不再处理;
		echoLog('开始更新个人空间占用...');
		Model('Source')->targetSpaceUpdate(SourceModel::TYPE_USER,USER_ID);
		$cacheKey = 'autoReset_'.USER_ID;
		Cache::set($cacheKey,time());
		$USER_HOME = KodIO::sourceID(MY_HOME);
		Model('Source')->folderSizeResetChildren($USER_HOME);
		Model('Source')->userSpaceReset(USER_ID);
		echoLog('执行完成!');
	}
	// 文件移动; 耗时任务;
	private function taskCopyCheck($list){
		$list = is_array($list) ? $list : array();
		$taskID = 'copyMove-'.USER_ID.'-'.rand_string(8);
		
		$task = new TaskFileTransfer($taskID,'copyMove');
		$task->update(0,true);//立即保存, 兼容文件夹子内容过多,扫描太久的问题;
		for ($i=0; $i < count($list); $i++) {
			$task->addPath($list[$i]['path']);
		}
	}

	/**
	 * 直接删除个人回收站下的文件(不进入系统回收站)
	 * @return void
	 */
	public function clearUserRecycleNow(){
	    KodUser::checkRoot();
		ignore_timeout();
		echoLog('本接口用于清空指定用户的回收站(仅限系统文件),文件将被直接删除而不存放到系统回收站,请谨慎操作。');
		if (!isset($this->in['done'])) {
            echoLog('如确认需要执行,请在地址中追加参数后再次访问:&done=1');exit;
        }
		if (empty($this->in['userID'])) {
			echoLog('请指定用户id:&userID=xx');exit;
		}
		$userID = $this->in['userID'];
		// 查询回收站文件列表
		$list = Model('SourceRecycle')->alias('r')->field('r.sourceID,s.fileID')
				->join("INNER JOIN io_source AS s ON r.sourceID = s.sourceID")
				->where(array("r.userID"=>USER_ID))
				->select();
		if (empty($list)) {
			echoLog('当前回收站为空,无需处理。'); exit;
		}
		$sources = $files = array();
		foreach ($list as $item) {
			$sources[] = $item['sourceID'];
			$files[] = $item['fileID'];
		}
		unset($list);
		// 删除
		echoLog('正在删除,请耐心等待...');
		Model('Source')->removeRelevance($sources,$files);
		echoLog('删除完成,共删除文件/夹:'.count($sources).'个。');exit;
	}

	/**
	 * 获取指定目录(或全部)物理文件不存在的记录
	 * @return void
	 */
	public function listFileNotExists(){
		$ids = array();
		if (!empty($this->in['sourceID'])) {
			$id = $this->in['sourceID'];
			$info = Model('Source')->where(array('sourceID' => $id))->find();
			if (!$info) show_json('指定文件夹id不存在',0);
			$where = array('isFolder' => 0, 'parentLevel' => array('like', $info['parentLevel'].$id.',%'));
			$list = Model('Source')->where($where)->field('fileID')->select();
			$ids = array_to_keyvalue($list, '','fileID');
		}
		
		$pageNum = 1000;$page = 1;
		$model = Model('File');
		if ($ids) { // 指定sourceID
			$model->where(array('fileID' => array('in', $ids)));
		} else if (!empty($this->in['lastFileID'])) {   // 从大于指定fileID开始,不支持同时指定sourceID
			$model->where(array('fileID' => array('gt', $this->in['lastFileID'])));
		}
		$list = $model->selectPage($pageNum,$page);
		$stores = Model('Storage')->listData();
		$stores = array_to_keyvalue($stores, '', 'id'); // 有效存储列表
	
		$file = __DIR__.'/filepathlist-'.date('His').'.txt';
		// del_file($file);
	
		$i = $cnt = 0;
		echoLog('不存在或无法访问的物理文件列表:');
		echoLog('begin------------------------------------------------');
		while($list && $page <= $list['pageInfo']['pageTotal']){
			foreach ($list['list'] as $item) {
				$i++;
				$ioNone = in_array($item['ioType'], $stores);
				if($ioNone && IO::exist($item['path']) ){
					//
					echoLog($i.'.存在:'.$item['path'],true);
				}else{
					$cnt++;
					// echoLog($i.'. '.$item['path']);
					file_put_contents($file, $item['path']."\n", FILE_APPEND);
				}
			}
			$page++;
			if ($ids) {
				$model->where(array('fileID' => array('in', $ids)));
			} else if (!empty($this->in['lastFileID'])) {
				$model->where(array('fileID' => array('gt', $this->in['lastFileID'])));
			}
			$list = $model->selectPage($pageNum,$page);
		}
		echoLog('end------------------------------------------------');
		if ($cnt) {
			echoLog('共有'.$cnt.'条异常记录,具体可查看文件:'.$file);
		}
	}

	// ======================================================== 重置层级结构异常文件 ========================================================

	/**
	 * 检测已删除状态异常文件——上级目录删除,但子文件未删除
	 * @param [type] $model
	 * @return void
	 */
	public function errData2Recycle($model) {
		// select * from io_source where parentID = 1739077 —— 层级重复
		// select isDelete,count(*) from io_source where parentLevel like ',0,1,450518,498485,700552,1351194,1739077,1739276,%' group by isDelete
		echoLog('检测已删除状态异常文件');
	    $where = array(
			'isFolder' => 1,
			'isDelete' => 1,
			'parentID' => array('>', 0)
		);
		$list = $model->where($where)->field('sourceID,parentLevel')->select();
		if (!$list) return;
		$data = array();
		foreach ($list as $item) {
			$data[] = $item['parentLevel'].$item['sourceID'].',';
		}
		$list = array_unique($data);
		$list = $this->getBaseLevels($list);
		echoLog('共有'.count($list).'个已删除目录');
		if (!$list) return;
		foreach ($list as $level) {
			echoLog('开始检测状态异常文件:'.$level);
			$where = array(
				'isDelete' 		=> 0,
				'parentLevel'	=> array('like', $level.'%')
			);
			// $res = $model->where($where)->setField('isDelete',1);//所有字内容设置删除标记
			// echoLog('已处理'.$res.'个状态异常文件');
			// TODO 
			$res = $model->where($where)->field('sourceID')->select();
			if ($res) {
				$res = array_to_keyvalue($res, '', 'sourceID');
				$file = TEMP_FILES . 'reset-active.txt';
				file_put_contents($file, implode(',', $res).',', FILE_APPEND);
				echoLog('已记录'.count($res).'个状态异常文件');
			}
		}
		echoLog('完成已删除文件检测');
	}
	private function getBaseLevels($list) {
		// 去除空路径并排序(从短到长)
		$data = array_filter($list, function($path){return !empty(trim($path, ','));});
		usort($data, function($a, $b){return strlen($a) - strlen($b);});
		$base = array();
		foreach ($data as $path) {
			$isContained = false;
			$trimmedPath = trim($path, ',');
			foreach ($base as $basePath) {
				$trimmedBase = trim($basePath, ',');
				// 检查当前路径是否被已有基础路径包含
				if (strpos($trimmedPath, $trimmedBase) !== false) {
					$isContained = true;
					break;
				}
			}
			if (!$isContained) {
				$base[] = $path;
			}
		}
		return $base;
	}

	/**
	 * 重置层级结构异常文件
	 * @param [type] $model
	 * @return void
	 */
	public function resetSourceLevel($model) {
		// 13093773	—— can't download
		KodUser::checkRoot();
		ignore_timeout();
		// $where = array(
		// 	'isDelete'		=> 0,
		// 	'parentID'		=> array('>',0),
		// 	'parentLevel'	=> array('<>',''),
		// );
		// $where = array();	// 1500w条数据,加条件需要30.5s,不加0.67s
		$total = (int) $model->count();
		if (!$total) return;

		echoLog('任务已提交,可在任务列表查看进度');
		http_close();

		$taskId = __FUNCTION__.'-TASK';
		$task = new Task($taskId,'resetdatatask',$total,'文件层级异常处理');

		$data = $update = $remove = array();
		$pageNum = 50000;
		$lastSid = 0;
		// for ($i=0; $i < $total;$i=$i+$pageNum) {$list = $model->limit($i,$pageNum)->field('sourceID,parentID,parentLevel,isDelete')->select();}
		do {
			$list = $model->where(array('sourceID'=>array('>',$lastSid)))->field('sourceID,parentID,parentLevel,isDelete')->order('sourceID asc')->limit($pageNum)->select();
			if (empty($list)) break;
			$lastSid = end($list)['sourceID'];
			foreach ($list as $item) {
				$task->update(1);
				if ($item['isDelete'] == '1') continue;
				if (!$item['parentID'] || !$item['parentLevel']) continue;	// 部门/用户空间parentID=0
				// parentID!=parentLevel[-2],说明层级不正常
				$parentLevel = explode(',', trim($item['parentLevel'], ','));
				if ($item['parentID'] != $parentLevel[count($parentLevel) - 1]) {
					$data[$item['sourceID']] = $item['parentID'];
				}
			}
			if (!$data) continue;
			$idx = 0;
			$this->resetSourceGet($model,$data,$update,$remove,$idx);
			$this->resetSourceUpdate($model,$update,$remove);
			$data = $update = $remove = array();
		} while (!empty($list));
		$task->end();
	}
	// 获取待更新level的数据
	private function resetSourceGet($model,$data,&$update,&$remove,$idx) {
		$idx++;
		$where = array('sourceID'=>array('in',array_unique(array_values($data))));	// 根据parentID查找io_source记录
		$list = $model->where($where)->field('sourceID,parentID,parentLevel')->select();
		$dataTmp = $updateTmp = array();
		foreach ($list as $item) {
			if (!$item['parentID'] || !$item['parentLevel']) continue;
			$parentLevel = explode(',', trim($item['parentLevel'], ','));
			if ($item['parentID'] == $parentLevel[count($parentLevel) - 1]) {
				$updateTmp[$item['sourceID']] = $item['parentLevel'];
				continue;
			}
			$dataTmp[$item['sourceID']] = $item['parentID'];
		}
		foreach ($data as $sourceID => $parentID) {
			if (isset($updateTmp[$parentID])) {	// 待更新parentLevel的数据
				$update[$sourceID] = $updateTmp[$parentID].$parentID.',';
			} else {
				$remove[] = $sourceID;	// 根据parentID找不到记录的数据
			}
		}
		// $idx 防止递归死循环
		if (!empty($dataTmp) && $idx < 100) {
			$this->resetSourceGet($model,$dataTmp,$update,$remove);
		}
	}
	private function resetSourceUpdate($model,$update,$remove){
		// TODO test
		$file1 = TEMP_FILES . 'reset-update.txt';
		$handle = fopen($file1, 'a+');	// a为追加模式
		foreach ($update as $sourceID => $parentLevel) {
		    fwrite($handle, $sourceID.'=>'.$parentLevel . "\n");
		}
		fclose($handle);
		$file2 = TEMP_FILES . 'reset-remove.txt';
		file_put_contents($file2, implode(',', $remove).',', FILE_APPEND);
		return;

		// 根据parentID找到记录,更新为parent.parentLevel+parentID
		$updateTmp = array();
		foreach ($update as $sourceID => $parentLevel) {
		    $updateTmp[] = array(
				'sourceID', $sourceID,
				'parentLevel', $parentLevel,
			);
		}
		if (!empty($updateTmp)) {
			$res = $model->saveAll($updateTmp);
			// $file1 = TEMP_FILES . 'reset-update.txt';
			// file_put_contents($file1, 'update:'.$res.';ids:'.implode(',',array_keys($update))."\n", FILE_APPEND);
		}
		write_log('resetSourceLevel-update:'.count($updateTmp), 'repair');
		// 找不到记录的,不直接删除,更新回parentID=parentLevel[-2],name=name@time——已存在会重名,所以需要更新name
		$time = date('YmdHis');
		foreach (array_chunk($remove, 500) as $ids) {
			$sql = "UPDATE io_source SET parentID = SUBSTRING_INDEX(SUBSTRING_INDEX(parentLevel, ',', -2), ',', 1), name = CONCAT(name, '@', '{$time}')
				WHERE sourceID IN (" . implode(',', $ids) . ")";
			$res = $model->execute($sql);
			// $file2 = TEMP_FILES . 'reset-update(remove).txt';
			// file_put_contents($file2, 'update(remove):'.$res.';ids:'.implode(',',$ids)."\n", FILE_APPEND);
		}
		write_log('resetSourceLevel-update(mismatch):'.count($remove), 'repair');
	}

	/**
	 * 更新异常层级文件
	 * @return void
	 */
	public function updateSourceLevel() {
		$model = Model('Source');

		echoLog(TEMP_FILES);

		// 1.检测状态异常文件
		$this->errData2Recycle($model);
		exit;
		
		// 2.更新层级异常文件
		// update io_source set parentLevel = ',0,13511771,15453311,14511308,' where parentID = 14511308;
		$this->resetSourceLevel($model);
	}
	
}
role.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/role.class.php'
View Content
<?php
/*
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*/

//权限组管理
class adminRole extends Controller{
	private $model;
	function __construct()    {
		parent::__construct();
		$this->model = Model('SystemRole');
	}

	/**
	 * 根据所在部门获取用户列表
	 */
	public function get() {
		$roleList = $this->model->listData();$result = array();
		$Action = Action('filter.UserGroup');
		foreach($roleList as $item){//过滤比自己权限小的项;
			if($Action->allowChangeUserRole($item['id'])){$result[] = $item;}
		}
		show_json($result,true);
	}

	/**
	 * 添加用户
	 */
	public function add() {
		$data = Input::getArray(array(
			"name" 		=> array("check"=>"require"),
			"display" 	=> array("check"=>"int","default"=>null),
			"auth" 		=> array("check"=>"require"),
			"label" 	=> array("check"=>"require","default"=>null),
			"desc"		=> array("default"=>''),
			"ignoreExt" => array("check"=>"require","default"=>''),
			"ignoreFileSize" => array("check"=>"require","default"=>''),
		));
		$res = $this->model->add($data);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error') . '! ' . LNG('explorer.pathExists');
		return show_json($msg,!!$res, $res);	// $res=>$id
	}

	/**
	 * 编辑 
	 */
	public function edit() {
		$data = Input::getArray(array(
			"id"		=> array("check"=>"int"),
			"name" 		=> array("check"=>"require","default"=>null),
			"display" 	=> array("check"=>"int","default"=>null),
			"auth" 		=> array("check"=>"require","default"=>''),
			"label" 	=> array("check"=>"require","default"=>null),
			"desc"		=> array("default"=>''),
			"ignoreExt" => array("check"=>"require","default"=>''),
			"ignoreFileSize" => array("check"=>"require","default"=>''),
		));
		$res = $this->model->update($data['id'],$data);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error') . '! ' . LNG('explorer.pathExists');
		show_json($msg,!!$res);
	}

	/**
	 * 删除
	 */
	public function remove() {
		$id  = Input::get('id','int');
		// 判断是否有用户使用
		$cnt = Model('User')->where(array('roleID' => $id))->count();
		if($cnt) show_json(LNG('admin.role.delErrTips'), false);
		$res = $this->model->remove($id);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
	}

	// 移动排序、拖拽排序
	public function sort() {
		$ids = Input::get('ids', 'require');
		$ids = explode(',', $ids);
		foreach($ids as $i => $id) {
			$this->model->sort($id,array("sort"=> $i));
		}
		show_json(LNG('explorer.success'));
	}
}
server.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/server.class.php'
View Content
<?php 

/**
 * 服务器(cache、db)相关配置
 */
class adminServer extends Controller {
	function __construct() {
		parent::__construct();
	}

	// phpinfo
	public function srvPinfo(){
		phpinfo();exit;
	}

	/**
	 * 获取服务器缓存、数据库信息
	 * @return void
	 */
	public function srvGet(){
		$this->getSrvState();	// 服务器状态单独获取
		$data = array();
		$data['base'] = $this->getServerInfo();
		$data['cache'] = $GLOBALS['config']['cache'];
		$database = $GLOBALS['config']['database'];
		$data['db'] = array_change_key_case($database);
		$data['db']['db_info'] = $this->getDbInfo($data['db']);
		show_json($data);
	}

	// 获取服务器状态
	public function getSrvState(){
		if(!Input::get('state', null, 0)) return;

		// 系统默认存储使用量
		$sizeDef = Cache::get('kod_def_store_size');
		if (!$sizeDef) {
			$driver = KodIO::defaultDriver();
			// 默认为本地存储,且大小不限制,则获取所在磁盘大小
			if(strtolower($driver['driver']) == 'local' && $driver['sizeMax'] == '0') {
				$path	 = realpath($driver['config']['basePath']);
				$sizeDef = $this->srvSize($path);
			}else{
				$sizeUse = Model('File')->where(array('ioType' => $driver['id']))->sum('size');
				$sizeDef = array(
					'sizeTotal'	=> ((float) $driver['sizeMax']) * 1024 * 1024 * 1024,
					'sizeUse'	=> (float) $sizeUse
				);
			}
			Cache::set('kod_def_store_size', $sizeDef, 600);
		}
		// 系统内存、CPU使用情况
		$server = new ServerInfo();
		$memUsage = $server->memUsage();
		$sizeMem = array(
			'sizeTotal' => $memUsage['total'],
			'sizeUse' => $memUsage['used'],
		);
		$data = array(
			'cpu'		=> $server->cpuUsage(),	// CPU使用率
			'memory'	=> $sizeMem,	// 内存使用率
			'server'	=> $this->srvSize($this->srvPath()),	// 服务器系统盘空间
			'default'	=> $sizeDef,	// 网盘默认存储空间
			'time'		=> array(
				'time'	=> date('Y-m-d H:i:s'), 
				'upTime'=> $this->srvUptime()
			)
		);
		show_json($data);
	}
	// 系统盘路径
	private function srvPath(){
		$path  = '/';
		$isWin = $GLOBALS['config']['systemOS'] == 'windows';
		if($isWin) {
			$path = 'C:/';
			if(function_exists("exec")){
				exec("wmic LOGICALDISK get name",$out);
				$path = $out[1] . '/';
			}
		}
		return !file_exists($path) ? false : $path;
	}
	// 系统盘大小
	private function srvSize ($path){
		$data = array('sizeTotal' => 0, 'sizeUse' => 0);
		if(!function_exists('disk_total_space')){return $data;}
		if($path) {
			$data['sizeTotal'] = @disk_total_space($path);
			$data['sizeUse'] = $data['sizeTotal'] - @disk_free_space($path);
		}
		return $data;
	}
	// 获取服务器持续运行时间
	private function srvUptime(){
		$list = array(
			'day'		=> 0,
			'hour'		=> 0,
			'minute'	=> 0,
			'second'	=> 0,
		);
		$time = '';
		if ($GLOBALS['config']['systemOS'] == 'windows') {
			$res = shell_exec('WMIC OS Get LastBootUpTime');
			$time = explode("\r\n", $res);
			$time = isset($time[1]) ? intval($time[1]) : '';
			if (!$time) return LNG('common.unavailable');
			$time = time() - strtotime($time);
		}else {
			$filePath = '/proc/uptime';
			if (@is_file($filePath)) {
				$time = file_get_contents($filePath);
			}
			if (!$time) return LNG('common.unavailable');
		}
		$num	= (float) $time;
		$second	= (int) fmod($num, 60);
		$num	= (int) ($num / 60);
		$minute	= (int) $num % 60;
		$num	= (int) ($num / 60);
		$hour	= (int) $num % 24;
		$num	= (int) ($num / 24);
		$day	= (int) $num;
		foreach($list as $k => $v) {
			$list[$k] = $$k;
		}
		$str = '';
		foreach($list as $key => $val) {
			$str .= ' ' . ($val ? $val : 0) . ' ' . LNG('common.'.$key);
		}
		return $str;
	}

	// 服务器基本信息
	public function getServerInfo(){
		$data = array(
			'server_state'	=> array(),	// 服务器状态
			'server_info'	=> array(),	// 服务器信息
			'php_info'		=> array(),	// PHP信息
			'db_cache_info' => array(),	// 数据库&缓存信息
			'client_info'	=> array(),	// 我的(客户端)信息
		);

		// 1.服务器状态
		// 2.服务器信息
		$server = $_SERVER;
		$phpVersion = 'PHP/' . PHP_VERSION;
		$data['server_info'] = array(
			'name'		=> $server['SERVER_NAME'],
			'ip'		=> $server['SERVER_ADDR'],
			'time'		=> date('Y-m-d H:i:s'),
			'upTime'	=> '',
			'softWare'	=> $server['SERVER_SOFTWARE'],
			'phpVersion'=> $phpVersion,
			'system'	=> php_uname('a'),
			'webPath'	=> BASIC_PATH,
		);

		// 3.php信息
		$data['php_info']['detail'] = 'phpinfo';
		$data['php_info']['version'] = $phpVersion;
		$info = array('memory_limit', 'post_max_size', 'upload_max_filesize', 'max_execution_time', 'max_input_time');
		foreach($info as $key) {
			$val1 = ini_get($key);	// 系统设置
			$val2 = get_cfg_var($key);	// 配置文件
			$value = min(intval($val1), intval($val2));
			if (!in_array($key, array('max_execution_time', 'max_input_time'))) {
				// 配置文件中没加单位则为B
				$value = stripos($val2, 'M') === false ? $val2.'B' : $value.'M';
			}
			$data['php_info'][$key] = $value;
		}
		$data['php_info']['disable_functions'] = ini_get('disable_functions');
		$exts = get_loaded_extensions();
		$data['php_info']['php_ext'] = implode(',',$exts);
		$data['php_info']['php_ext_need'] = $this->phpExtNeed($exts);

		// 4.数据库&缓存信息
		$database = $GLOBALS['config']['database'];
        $dbType = $database['DB_TYPE'];
        if($dbType == 'pdo') {
            $dsn = explode(":", $database['DB_DSN']);
            $dbType = $dsn[0];
		}
		if(in_array($dbType, array('mysql', 'mysqli'))) {
            $res = Model()->db()->query('select VERSION() as version');
            $version = ($res[0] && isset($res[0]['version'])) ? $res[0]['version'] : 0;
            $dbType = 'MySQL' .  ($version ? '/' . $version : '');
        }else{
			$dbType = ($database['DB_TYPE'] == 'pdo' ? 'PDO-' : '') . str_replace('sqlite', 'SQLite', $dbType);
		}
		$data['db_cache_info'] = array(
			'db' => $dbType,
			'cache' => ucfirst($GLOBALS['config']['cache']['cacheType'])
		);

		// 5.我的信息
		$data['client_info'] = array(
			'ip' => get_client_ip(),
			'ua' => $server['HTTP_USER_AGENT'],
			'language' => $server['HTTP_ACCEPT_LANGUAGE']
		);
		return $data;
	}
	private function phpExtNeed($exts){
		$init = 'cURL,date,Exif,Fileinfo,Ftp,GD,gettext,intl,Iconv,imagick,json,ldap,Mbstring,Mcrypt,Memcached,MySQLi,SQLite3,OpenSSL,PDO,pdo_mysql,pdo_sqlite,Redis,session,Sockets,Swoole,dom,xml,SimpleXML,libxml,bz2,zip,zlib';
		$init = explode(',', $init);
		$data = array();
		foreach($init as $ext) {
			$value = in_array_not_case($ext, $exts) ? 1 : 0;
			$data[$ext] = $value;
		}
		return $data;
	}

	// 数据库信息
	public function getDbInfo($database){
		$type = $this->_dbType($database);
		if($type == 'sqlite') {
			$tables = Model()->db()->getTables();
			$rows = 0;
			foreach($tables as $table) {
				$rows += Model($table)->count();
			}
			// 数据库文件大小
			$file = $database['db_name'];
			if(!isset($database['db_name'])) {
				$file = substr($database['db_dsn'], strlen('sqlite:'));
			}
			$size = @filesize($file);
		}else{
			$tables = Model()->db()->query('show table status from `' . $database['db_name'] . '`');
			$rows = $size = 0;
			foreach($tables as $item) {
				$rows += $item['Rows'];
				$size += ($item['Data_lenth'] + $item['Index_length'] - $item['Data_free']);
			}
		}
		return array(
			'total_tables'	=> count($tables),
			'total_rows'	=> $rows,
			'total_size'	=> $size
		);
	}

	// 数据库类型:sqlite、mysql
	public function _dbType($database){
		$type = $database['db_type'];
		if($type == 'pdo') {
			$dsn = explode(':', $database['db_dsn']);
			$type = $dsn[0];
		}
		$typeArr = array('sqlite3' => 'sqlite', 'mysqli' => 'mysql');
		if(isset($typeArr[$type])) $type = $typeArr[$type];
		return $type;
	}

    /**
	 * 缓存配置切换检测、保存
	 */
	public function cacheSave(){
		$check = Input::get('check', null, 0);
		if($check){
			$type = Input::get('type','in',null,array('file','redis','memcached'));
		}else{
			$type = Input::get('cacheType','in',null,array('file','redis','memcached'));
		}
		if(in_array($type, array('redis','memcached'))) {
			$data = $this->_cacheCheck($type);
			if($check) {
				show_json(LNG('admin.setting.checkPassed'));
			}
		}
		// 更新setting_user.php
		$file = BASIC_PATH . 'config/setting_user.php';
		$text = array(
			PHP_EOL . PHP_EOL,
            "\$config['cache']['sessionType'] = '{$type}';",
            "\$config['cache']['cacheType'] = '{$type}';"
		);
		if($type != 'file'){
			$text[] = "\$config['cache']['{$type}']['host'] = '".$data['host']."';";
			$text[] = "\$config['cache']['{$type}']['port'] = '".$data['port']."';";
			if ($type == 'redis' && $data['auth']) {
				$text[] = "\$config['cache']['{$type}']['auth'] = '".$data['auth']."';";
			}
		}
		$content = implode(PHP_EOL, $text);
		if(!file_put_contents($file, $content, FILE_APPEND)) {
            show_json(LNG('explorer.error'), false);
		}
		Cache::deleteAll();
		if (function_exists('opcache_reset')) {
            opcache_reset();
        }
		show_json(LNG('explorer.success'));
	}
	private function _cacheCheck($type){
		if(!extension_loaded($type)){
			show_json(sprintf(LNG('common.env.invalidExt'), "[php-{$type}]"), false);
		}
		$data = Input::getArray(array(
			"{$type}Host" => array('check'=>'require', 'aliasKey'=>'host'),
			"{$type}Port" => array('check'=>'require', 'aliasKey'=>'port')
		));
		$cacheType = ucfirst($type);
        $handle = new $cacheType();
		try{
			if($type == 'redis') {
				$handle->connect($data['host'], $data['port'], 1);
				$auth = Input::get('redisAuth');
				if ($auth) {
					$data['auth'] = $auth;
					$handle->auth($auth);
				}
				$conn = $handle->ping();
			}else{
				$conn = $handle->addServer($data['host'], $data['port']);
				if($conn && !$handle->getStats()) $conn = false;
			}
			if(!$conn) show_json(sprintf(LNG('admin.install.cacheError'),"[{$type}]"), false);
		}catch(Exception $e){
			$msg = sprintf(LNG('admin.install.cacheConnectError'),"[{$type}]");
			$msg .= '<br/>'.$e->getMessage();
			show_json($msg, false);
		}
		return $data;
	}

	/**
	 * 数据库切换检测、保存
	 * @return void
	 */
	public function dbSave(){
		$this->taskGet('change');		// 获取任务状态
		$this->taskClear('change');		// 清除失败的数据
		// 当前数据库配置
		$database = $GLOBALS['config']['database'];
		$database = array_change_key_case($database);
		$type = $this->_dbType($database);

		// 目标数据库类型
		$data = Input::getArray(array(
			'db_type' => array('check' => 'in', 'param' => array('sqlite', 'mysql', 'pdo')),
			'db_dsn'  => array('default' => ''),	// mysql/sqlite
		));
		$dbType = !empty($data['db_dsn']) ? $data['db_dsn'] : $data['db_type'];
		$pdo = !empty($data['db_dsn']) ? 'pdo' : '';
		$dbList = $this->validDbList();
		// 判断系统环境是否支持选择的数据库类型
		if($pdo == 'pdo') {
			if(!in_array('pdo_'.$dbType, $dbList)) {
				show_json(sprintf(LNG('common.env.invalidExt'), 'pdo_'.$dbType), false);
			}
		}else{
			$allow = false;
			foreach($dbList as $value) {
				if($value == $dbType || stripos($value, $dbType) === 0) {
					$allow = true;
					break;
				}
			}
			if(!$allow) show_json(sprintf(LNG('common.env.invalidExt'), $dbType), false);
		}
		$this->checkSetWrt();

		// 1. 切换了数据库类型,则全新安装,走完整流程
		if($dbType != $type) {
			return $this->dbChangeSave($dbType, $pdo, $database);
		}

		// 2. 没有改变数据库类型:pdo连接、配置参数、数据库变更等
		if($type == 'sqlite') {
			// 无论是检测还是保存,都直接返回
			show_json(LNG('admin.setting.dbNeedOthers'), false);
		}
		$data = $this->filterMysqlData();
		$match = true;
		foreach($data as $key => $value) {
			if ($value != $database[$key]) {
				$match = false;
				break;
			}
		}
		// 2.2.1 配置参数不同
		if(!$match) {
			return $this->dbChangeSave($dbType, $pdo, $database);
		}
		$check = Input::get('check', null, false);
		// 2.2.2 配置参数相同,都是or不是pdo方式
		if($pdo == $database['db_type'] ||
			(!$pdo && $database['db_type'] != 'pdo')) {
			if($check) show_json(LNG('admin.setting.dbNeedChange'), false); // 说明没有修改,禁止切换
			show_json(LNG('explorer.success'));
		}
		if($check) show_json(LNG('admin.setting.checkPassed'));
		// 2.2.3 只是变更了pdo连接方式,更新配置文件,无需其他操作
		$option = $this->filterDatabase($pdo, $type, $database, $dbList);
		$option = array_merge($option, $data);

		$option = array_merge($option, $this->dbExtend($database));
		$option = array_change_key_case($option, CASE_UPPER);

        // 3. 保存配置
        $this->settingSave($dbType, $option, 'change');

		show_json(LNG('explorer.success'));
	}
    // 获取db_type、db_name/dsn配置
    private function filterDatabase($pdo, $type, $data, $dbList){
        if($pdo == 'pdo') {
            $option = array(
                'db_type'	=> 'pdo',
				'db_dsn'	=> $type,
				'db_name'	=> $data['db_name']
            );
            $dsn = $data['db_name'];
            if($type == 'mysql') {
				$port = (isset($data['db_port']) && $data['db_port'] != '3306') ? "port={$data['db_port']};" : '';
                $dsn = "host={$data['db_host']};{$port}dbname={$data['db_name']}";
            }
            $option['db_dsn'] .= ':' . $dsn;
        }else{
            $option = array(
                'db_type'	=> $type,
                'db_name'	=> $data['db_name']
            );
            if($type == 'sqlite') {
                if(in_array('sqlite3', $dbList)) $option['db_type'] = 'sqlite3';
            }else{
                if(in_array('mysqli', $dbList)) $option['db_type'] = 'mysqli';
            }
        }
        return $option;
    }
    // 获取mysql配置参数
    private function filterMysqlData(){
		$data = Input::getArray(array(
            'db_host'	=> array('check' => 'require'),
            'db_port'	=> array('default' => 3306),
            'db_user'	=> array('check' => 'require'),
            'db_pwd'	=> array('check' => 'require', 'default' => ''),
            'db_name'	=> array('check' => 'require'),
		));
		$host = explode(':', $data['db_host']);
        if(isset($host[1])) {
            $data['db_host'] = $host[0];
            $data['db_port'] = (int) $host[1];
		}
		return $data;
    }
    // 数据库配置追加内容
    private function dbExtend($database){
		$keys = array('db_sql_log', 'db_fields_cache', 'db_sql_build_cache');
		$data = array();
		foreach($keys as $key) {
			$data[$key] = $database[$key];
		}
		return $data;
	}
    // 写入配置文件的sqlite信息位置过滤
	private function filterSqliteSet($content) {
		$replaceFrom = "'DB_NAME' => '".USER_SYSTEM;
		$replaceTo   = "'DB_NAME' => USER_SYSTEM.'";
		$replaceFrom2= "'DB_DSN' => 'sqlite:".USER_SYSTEM;
		$replaceTo2  = "'DB_DSN' => 'sqlite:'.USER_SYSTEM.'";
		$content = str_replace($replaceFrom,$replaceTo,$content);
		$content = str_replace($replaceFrom2,$replaceTo2,$content);
		return $content;
    }
    // 有效的数据库扩展
    private function validDbList(){
        $db_exts = array('sqlite', 'sqlite3', 'mysql', 'mysqli', 'pdo_sqlite', 'pdo_mysql');
        $dblist = array_map(function($ext){
            if (extension_loaded($ext)){
                return $ext;
            }
        }, $db_exts);
        return array_filter($dblist);
    }

    // 数据库配置保存到setting_user.php
    private function settingSave($dbType, $option, $type){
        $option = var_export($option, true);
		$file = BASIC_PATH . 'config/setting_user.php';
        $content = PHP_EOL . PHP_EOL . "\$config['database'] = {$option};";
        if($dbType == 'sqlite') {
            $content = $this->filterSqliteSet($content);
        }
		if(!file_put_contents($file, $content, FILE_APPEND)) {
			// 删除复制的数据表文件
			del_dir($this->tmpActPath($type));
            show_json(LNG('explorer.error'), false);
        }
    }

    // 生成全新的数据库
	private function dbChangeSave($dbType, $pdo, $database){
        // 1. 获取数据库配置信息
		$dbList = $this->validDbList();
		if($dbType == 'sqlite') {
			if(Input::get('check', null, false)) {
				show_json(LNG('admin.setting.checkPassed'));
			}
			$dbFile = USER_SYSTEM . rand_string(12) . '.php';
			if(!@touch($dbFile)) {
				show_json(LNG('admin.setting.dbCreateError'), false);
			}
            $data = array('db_name' => $dbFile);
            $option = $this->filterDatabase($pdo, $dbType, $data, $dbList);
		}else{
            $data = $this->filterMysqlData();
            $option = $this->filterDatabase($pdo, $dbType, $data, $dbList);
			$option = array_merge($option, $data);
		}
		$option = array_merge($option, $this->dbExtend($database));
		$option = array_change_key_case($option, CASE_UPPER);

		// 数据库配置存缓存,用于清除获取
		$key = 'db_change.new_config.' . date('Y-m-d');
		Cache::set($key, array('type' => $dbType, 'db' => $option), 3600*24);

        // 2. 复制数据库——读取当前库表结构、表数据,写入到新增库
        $this->dbChangeAct($database, $option, $dbType);

        // 3.保存配置
		$taskSet = new Task('db.setting_user.set', $dbType, 1, LNG('admin.setting.dbSetSave'));
		$this->settingSave($dbType, $option, 'change');
		$taskSet->update(1);
		$this->taskToCache($taskSet);

		show_json(LNG('explorer.success'));
	}

	// 任务进度入缓存
	private function taskToCache($task, $id = ''){
		$total = isset($task->task['taskTotal']) ? $task->task['taskTotal'] : $task->task['taskFinished'];
        $cache = array(
            'currentTitle'  => $task->task['currentTitle'],
            'taskTotal'     => $total,
            'taskFinished'  => $task->task['taskFinished'],
		);
		if($cache['taskFinished'] > 0 && $cache['taskTotal'] == $cache['taskFinished']) {
			$cache['success'] = 1;
		}
		// 某些环境下进度请求获取不到缓存,加上过期时间后正常,原因未知——应该是时间过短,被即刻删除了
		$key = !empty($task->task['id']) ? $task->task['id'] : $id;
		Cache::set('task_'.$key, $cache, 3600*24);
		$task->end();
    }

	/**
	 * 复制数据库:当前到新增
	 * @param [type] $database
	 * @param [type] $option
	 * @param [type] $type	新增db类型
	 * @return void
	 */
    private function dbChangeAct($database, $option, $type){
		// 1.初始化db
		$manageOld = new DbManage($database);
		$manageNew = new DbManage($option);
		$dbNew = $manageNew->db(true);
		if (!$dbNew) {
			show_json(LNG('explorer.error').$manageNew->message, false);
		}

		// 2.指定库存在数据表,提示重新指定;不存在则继续
		$tableNew = $dbNew->getTables();
		if(!empty($tableNew)) {
			show_json(LNG('admin.setting.recDbExist'), false);
		}
		if(Input::get('check', null, false)) {
			show_json(LNG('admin.setting.checkPassed'));
		}
		// 3.获取建表sql文件——提前获取,避免异常时无法报错
		$file = $manageOld->getSqlFile($type);
		if (!$file) show_json(LNG('admin.install.dbFileError').'(install/'.$type.'.sql)', false);

		// 截断http请求,后面的操作继续执行
		echo json_encode(array('code'=>true,'data'=>'OK', 'info'=>1));
		http_close();

		$taskId	 = 'db.new.table.create';
		$taskCrt = new Task($taskId, $type, 0, LNG('admin.setting.dbCreate'));
		// 3.表结构写入目标库
		// $file = $manageOld->getSqlFile($type);
        $manageNew->createTable($file, $taskCrt);
		$this->taskToCache($taskCrt, $taskId);
		$tableNew = $dbNew->getTables();
		del_dir(get_path_father($file));

		// 4.获取当前表数据,写入sql文件
		$pathLoc = $this->tmpActPath('change');
		del_dir($pathLoc); mk_dir($pathLoc);

		$fileList = array();
		$tableOld = $manageOld->db()->getTables();
		$tableOld = array_diff($tableOld, array('______', 'sqlite_sequence'));	// 排除sqlite系统表
		$total = 0;
        foreach($tableOld as $table) {
			if(!in_array($table, $tableNew)) continue;
			$total += $manageOld->model($table)->count();
		}
		$taskId	 = 'db.old.table.select';
		$taskGet = new Task('db.old.table.select', $type, $total, LNG('admin.setting.dbSelect'));
        foreach($tableOld as $table) {
			// 对比原始库,当前库如有新增表(不存在的表),直接跳过
			if(!in_array($table, $tableNew)) continue;
			$file = $pathLoc . $table . '.sql';
            $manageOld->sqlFromDb($table, $file, $taskGet);
            $fileList[] = $file;
		}
		// 这里的task缺失id等参数,导致cache无法保存,原因未知
		$this->taskToCache($taskGet, $taskId);

		$taskId	 = 'db.new.table.insert';
		$taskAdd = new Task($taskId, $type, 0, LNG('admin.setting.dbInsert'));
		// 5.读取sql文件,写入目标库
        $manageNew->insertTable($fileList, $taskAdd);
		$this->taskToCache($taskAdd, $taskId);

		// 6.删除临时sql文件
        del_dir($pathLoc);
	}

	/**
	 * 数据库恢复——可以抽象为独立类,但涉及进度、任务等内容较多,暂不处理
	 * @return void
	 */
	public function recoverySave(){
		$this->recoveryFileSave();
		// TODO 待优化问题:
		// 备份文件先下载至临时目录,如果本就在本地,则没有必要;中途失败,显示提示到弹窗下;中途失败不能继续(包括切换)
		$this->taskGet('recovery');		// 获取任务状态
		$this->taskClear('recovery');	// 清除失败的数据
		$data = Input::getArray(array(
			'dbType'	=> array('check' => 'in', 'param' => array('sqlite', 'mysql'), 'aliasKey' => 'type'),
			'dbPath'	=> array('check' => 'require', 'aliasKey' => 'path'),
		));
		$info = IO::info($data['path']);
		if(!$info || $info['type'] != 'folder'){
			show_json(LNG('admin.setting.recPathErr'), false);
		}
		$this->checkSetWrt();

		// 1.判断选择的路径是否有效
		$type = $data['type'];
		$path = $info['path'];
		// 1.1 检查备份列表是否有效——只检查关键的几个表文件
		$manage = new DbManage();
		// 获取解密名称列表,兼容旧版(明文)
		$mxList = $manage->unmixBakListGet($path);
		if (!$mxList) $mxList = array();
		$list = IO::listPath($path, true);
		$list = array_to_keyvalue($list['fileList'], '', 'name');
		$tables = array($type, 'group', 'user', 'io_source', 'io_file', 'system_option');
		foreach ($tables as $table) {
			$name = $table . '.sql';
			$name = _get($mxList, $name, $name);	// 没有解密名则取原名(兼容旧版)
			if (!in_array($name, $list)) {
				show_json(LNG('admin.setting.recSysTbErr'), false);
			}
		}
		// 1.2 检测是否有建库权限
		if ($type == 'mysql') {
			// 没有权限时,create/use/drop database 会直接报错;show databases 不会(返回空)
			try {
				$dbname = 'kod_rebuild_test';
				$res = Model()->db()->execute("create database `{$dbname}`");
				if ($res) {Model()->db()->execute("drop database if exists `{$dbname}`");}
			} catch(Exception $e) {
				show_json(LNG('explorer.error').$e->getMessage(), false);
			}
		}
		echo json_encode(array('code'=>true,'data'=>'OK', 'info'=>1));
		http_close();

		// 2.导入数据库
		ActionCall('user.index.maintenance', true, 1);
		register_shutdown_function(function() {
            ActionCall('user.index.maintenance', true, 0);
        });
		// 2.1 下载备份文件到本地临时目录
		$pathLoc = $this->tmpActPath('recovery');
		$path = $this->recLocPath($type, $path, $pathLoc);
		if (!$path || !is_dir($path)) {
			show_json(LNG('admin.setting.dbFileDownErr'), false);
		}
		$manage->unmixBakList($path);
		$list = IO::listPath($path, true);
		$fileList = array_to_keyvalue($list['fileList'], 'name', 'path');
		$file = $fileList[$type . '.sql'];	// sqlite.sql、mysql.sql
		if(!$file) {
			show_json(LNG('admin.setting.dbFileDownErr'), false);
		}

		// 2.2 新建数据库
		$database = $this->recDatabase($data);
        $manage = new DbManage($database);
        $dbNew = $manage->db(true);   // 新建数据库
		if (!$dbNew) {
			show_json(LNG('explorer.error').$manage->message, false);
		}

        // 2.3 新建数据表
		$taskId	 = 'recovery.db.table.create';
        $taskCrt = new Task($taskId, $type, 0, LNG('admin.setting.dbCreate'));
        $manage->createTable($file, $taskCrt);
		$this->taskToCache($taskCrt, $taskId);

		// 2.4 读取sql文件,写入目标库
		$taskId	 = 'recovery.db.table.insert';
        $taskAdd = new Task($taskId, $type, 0, LNG('admin.setting.dbInsert'));
        $manage->insertTable($fileList, $taskAdd);
		$this->taskToCache($taskAdd, $taskId);

		// 2.5 删除临时sql文件
        del_dir($pathLoc);
		$database = array_change_key_case($database, CASE_UPPER);

        // 3.保存配置
		$taskSet = new Task('recovery.db.setting_user.set', $type, 1, LNG('admin.setting.dbSetSave'));
		$this->settingSave($type, $database, 'recovery');
		$taskSet->update(1);
		$this->taskToCache($taskSet);

		// $this->in['clear'] = 1;
		// $this->in['success'] = 1;
		// $this->taskClear('recovery');

		show_json(LNG('explorer.success'));
	}

	// sql文件下载到本地临时目录
	private function recLocPath($type, $path, $pathLoc){
		del_dir($pathLoc); mk_dir($pathLoc);
		$task = new TaskFileTransfer('recovery.db.file.download', $type, 0, LNG('admin.setting.dbFileDown'));
		$task->addPath($path);
		$path = IO::copy($path, $pathLoc);
		$this->taskToCache($task);
		return $path;
	}

	// 数据恢复使用的db配置信息
	private function recDatabase($data, $name = '') {
		$type = $data['type'];
		$database = $GLOBALS['config']['database'];
		$database = array_change_key_case($database);
		if($type == 'sqlite') {
			if(!$name) {
				$name = USER_SYSTEM . rand_string(12) . '.php';
				if(!@touch($name)) {
					show_json(LNG('admin.setting.dbCreateError'), false);
				}
			}
		}else{
			$name = $database['db_name'] . '_' . date('Ymd') . '_rebuild';
			$name = substr($name, 0, 64);	// 长度限制64
		}
		$database['db_name'] = $name;
		if($database['db_type'] == 'pdo') {
			if($type == 'mysql') {
				$dsn = explode(';', $database['db_dsn']);
				$dsn[count($dsn) - 1] = 'dbname=' . $name;
				$dsn = implode(';', $dsn);
			}else{
				$dsn = $type . ':' . $name;
			}
			$database['db_dsn'] = $dsn;
		}
		$key = 'db_recovery.new_config.' . date('Y-m-d');
		Cache::set($key, array('type' => $type, 'db' => $database), 3600*24);
		return $database;
	}

	// 临时目录
	private function tmpActPath($type){
		return TEMP_FILES . 'db_' . $type . '_' . date('Ymd') . '/';
	}

	// 获取(切换、恢复)任务名称
	private function actTask($type, $step = '') {
		$task = array(
			'change' 	=> array(
				'step1' => 'db.new.table.create',
				'step2' => 'db.old.table.select',
				'step3' => 'db.new.table.insert',
				// 'step4' => 'db.temp_dir.del',
				'step4' => 'db.setting_user.set',
			),
			'recovery'	=> array(
				'step1' => 'recovery.db.file.download',
				'step2' => 'recovery.db.table.create',
				'step3' => 'recovery.db.table.insert',
				'step4' => 'recovery.db.setting_user.set',
			)
		);
		return $step ? $task[$type][$step] : $task[$type];
	}
	// 进行中(切换、恢复)任务进度获取
	private function taskGet($type){
		if(!Input::get('task', null, 0)) return;
		$task = $this->actTask($type);
		$data = array();
		foreach($task as $k => $val) {
			$value = Cache::get('task_'.$val);
			if(!$value) $value = Task::get($val);
			// if(isset($value['status']) && $value['status'] == 'kill') $value = false;
			$data[$k] = $value;
		}
		show_json($data);
	}
	// 结束后(切换、恢复)任务、及其他清除
	private function taskClear($type){
		if(!Input::get('clear', null, 0)) return;
		if(Input::get('success', null, false)) {
			echo json_encode(array('code'=>true,'data'=>LNG('explorer.success')));
			http_close();
			Action('admin.setting')->clearCache();exit;
		}
		// 1.杀掉任务、清除缓存
		$task = $this->actTask($type);
		foreach($task as $key) {
			Task::kill($key);
			Cache::remove('task_'.$key);
		}
		// 2.删除临时sql目录
		del_dir($this->tmpActPath($type));
		// 3.删除导入失败的数据库
		$this->dropErrorDb($type);
		show_json(LNG('explorer.success'));
	}
	// 删除导入失败的数据表
	private function dropErrorDb($type){
		$key = 'db_'.$type.'.new_config.'.date('Y-m-d');
		if(!$cache = Cache::get($key) || empty($cache['db'])) return;

		$type = $cache['type'];
		if($type == 'sqlite') {
			del_file($cache['db']['db_name']);
		}else if ($type == 'mysql') {
			$manage = new DbManage($cache['db']);
			// if($manage) $manage->dropTable();
			if($manage) {
				$dbname = $cache['db']['db_name'];
				try {
					model()->db()->execute("drop database if exists `{$dbname}`");
				} catch(Exception $e) {}
			}
		}
		Cache::remove($key);
	}

	// 检查配置文件(是否可写)
	private function checkSetWrt() {
		$file = BASIC_PATH . 'config/setting_user.php';
		if (path_writeable($file)) return;
		show_json(LNG('explorer.wrtSetUserError'), false);
	}

	/**
	 * 文件还原
	 * @return void
	 */
	public function recoveryFileSave() {
		$in = $this->in;
		if ($in['tab'] != 'recovery' || $in['action'] != 'save' || $in['recType'] != 'file') return;
		$path = Input::get('filePath', 'require');
		// 获取进度
		if ($in['process'] == '1') {
			$task = Task::get('restore.file');	// 没有任务或已结束时都为false
			// 可能存在进度请求未发出但任务即已结束的情况,导致任务为false,故存缓存
			$taskUuid  = _get($in, 'taskUuid', 'recFileTaskUuid');
			$taskCache = Cache::get($taskUuid);
			if ($task) {
				Cache::set($taskUuid, $task);
			} else {
				if ($taskCache) {$task['taskPercent'] = 1;}
			}
			// 备份结果信息
			$info = null;
			if ($task['taskPercent'] == 1) {
				Cache::remove($taskUuid);
				$info = Model('SystemOption')->get('fileTaskInfo', 'restore');
				$info = json_decode($info, true);
			}
			show_json($task, true, $info);
		}
		// 执行还原
		$restore = new RestoreFile();
        $code = $restore->start($path);
		$data = $restore->message;
		if ($code && !$data) $data = LNG('explorer.success');
		show_json($data, $code);
	}

}
setting.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/setting.class.php'
View Content
<?php

/*
 * @link http://kodcloud.com/
 * @author warlee | e-mail:kodcloud@qq.com
 * @copyright warlee 2014.(Shanghai)Co.,Ltd
 * @license http://kodcloud.com/tools/license/license.txt
 */

class adminSetting extends Controller {
	function __construct() {
		parent::__construct();
	}

	public function get(){
		$data = Model('SystemOption')->get();
		if(!$data['shareOutSiteApiKey']){
			Model('SystemOption')->set('shareOutSiteApiKey',rand_string(16));
			$data = Model('SystemOption')->get();
		}
		
		$data = array_merge($this->config['settingSystemDefault'],$data);
		$removeKey = array(
			'versionLicense','versionUser','versionHashUser','versionHash',
			'systemSecret','systemPassword','deviceUUID',
		);
		foreach ($removeKey as $key) {
			unset($data[$key]);
		}
		unset($data['regist']['loginWith']);	// 兼容旧版已存在的数据
		
		// 根部门名称;
		$groupRoot = Model('Group')->where(array("parentID"=>0))->find();
		if($groupRoot){$data['groupRootName'] = $groupRoot['name'];}
		show_json($data);
	}
	//管理员  系统设置全局数据
	public function set() {
		$data = json_decode($this->in['data'], true);
		if (!$data) {
			show_json(LNG('explorer.error'), false);
		}
		
		if (isset($data['chunkSize'])) {
			$postMax = get_post_max();
			if($data['chunkSize']*1024*1024  >= $postMax){
				$sizeTips = ($postMax/(1024*1024)) .'MB';
				show_json(LNG('admin.setting.transferChunkSizeDescError1').
				":$sizeTips,<br/>".LNG('admin.setting.transferChunkSizeDescError2'),false);
			}
		}
		// $data['menu'] = $this->config['settingSystemDefault']['menu']; // 重置菜单;
		Model('SystemOption')->set($data);
		show_json(LNG('explorer.success'));
	}
	
	/**
	 * 发送邮件测试-用户注册功能设置
	 */
	public function mailTest() {
		$input = Input::get('address', 'require');

		$systemName = Input::get('systemName');
		if (!$systemName) $systemName = Model('SystemOption')->get('systemName');
		$systemDesc = Input::get('systemDesc');
		if (!$systemDesc) $systemDesc = Model('SystemOption')->get('systemDesc');

		$user = Session::get('kodUser');
		$name = _get($user, 'nickName', _get($user, 'name'));
		$data = array(
			'type'			=> 'email',
			'input'			=> $input,
			'emailType' 	=> 1,
			'action'		=> 'email_test',
			'config'		=> array(
				'address'	=> $input,
				'subject'	=> "[{$systemName}]" . LNG('user.emailVerify') . '-' . LNG('common.test'),
				'content'	=> array(
					'type'	=> 'code', 
					'data'	=> array(
						'user' => $name,
						'code' => rand_string(6)
					)
				),
				'system'	=> array(	// 系统信息
					'icon'	=> STATIC_PATH.'images/icon/fav.png',
					'name'	=> $systemName,
					'desc'	=> $systemDesc
				),
				'server'		=> Input::getArray(array(
					'smtp'		=> array('default' => 1),
					'host'		=> array('check' => 'require'),
					'email'		=> array('check' => 'require'),
					'password'	=> array('check' => 'require'),
					'secure'	=> array('default' => 'null'),
				))
			)
		);
		$res = Action('user.msg')->send($data);
		if (!$res['code']) {
			show_json(LNG('user.sendFail') . ': ' . $res['data'], false);
		}
		show_json(LNG('user.sendSuccess'), true);
	}
	
	/**
	 * 动态添加菜单;
	 */
	public function addMenu($options,$menu=array()){
		return $options;
	}

	public function clearCache(){
		if($this->clearCacheType()){return;}
		Cache::clearTimeout();
		Cache::deleteAll();
		http_close();
		@del_dir(TEMP_PATH);
		@del_dir('/tmp/fileThumb');
		mk_dir(TEMP_PATH . 'log');
		Model("File")->clearEmpty(0);
		$this->removeFolder('zipView');
		$this->removeEmptyFolder();
		Action('explorer.attachment')->clearCache();
		AutoTask::restart();//停止计划任务; (再次访问自动开启)
	}

	private function clearCacheType(){
		$clearType = $this->in['type'];
		switch($clearType){
			case 'image':$this->removeFolder('thumb');break;
			case 'video':$this->removeFolder('plugin/fileThumb');break;
			case 'plugin':
				$this->removeFolder('zipView');
				$this->removeFolder('plugin',true);
				break;
			default:$clearType = false;break;
		}
		if(!$clearType) return;
		$this->removeEmptyFolder();
		return true;
	}
	
	// 清理插件目录下的空文件夹;
	private function removeEmptyFolder(){
		$info  = IO::infoFullSimple(IO_PATH_SYSTEM_TEMP.'plugin');
		if (!$info || !$info['sourceID'] || !$info['parentLevel']) return;
		$where = array('parentLevel'=>array('like',$info['parentLevel'].'%'),'size'=>0);
		$lists = Model("Source")->field('sourceID,name')->where($where)->limit(5000)->select();
		$lists = $lists ? $lists : array();
		foreach ($lists as $item){
			Model("Source")->removeNow($item['sourceID'],false);
		}
	}
	
	// 清理文件夹;
	private function removeFolder($folder,$children=false){
		$model = Model("Source");
		$pathInfo = IO::infoFullSimple(IO_PATH_SYSTEM_TEMP . $folder);
		if(!$folder || !$pathInfo || !$pathInfo['sourceID']) return;
		if(!$children){$model->removeNow($pathInfo['sourceID'],false);return;}
		
		$where = array('parentID'=>$pathInfo['sourceID']);
		$lists = $model->field("sourceID,name")->where($where)->select();
		$lists = $lists ? $lists : array();
		foreach ($lists as $item){
			$model->removeNow($item['sourceID'],false);
		}
	}

	/**
	 * 服务器管理:基础信息、缓存、db
	 * @return void
	 */
	public function server(){
		$data = Input::getArray(array(
			'tab'	 => array('default'=>'', 'aliasKey'=>'type'),	// array('cache','db','recovery')
			'action' => array('check'=>'in', 'param'=>array('get', 'pinfo', 'save', 'task', 'clear'))
		));
		// srvGet/srvPinfo/cacheSave/dbSave/recoverySave
		$function = ($data['type'] ? $data['type'] : 'srv') . ucfirst($data['action']);
		$svcAct = Action('admin.server');
		if (!method_exists($svcAct, $function)) {
			show_json(LNG('common.illegalRequest'), false);
		}
		$svcAct->$function();
	}
	
	// 将mysql表转为mb4编码; >= 5.53支持mb4;
	public function updateMysqlCharset(){
		$dbType = $this->config['database']['DB_TYPE'];
		if($dbType != 'mysql' && $dbType != 'mysqli'){echoLog("not Mysql!");return;}
		
		$version = Model()->query('select VERSION() as version');
		$version = ($version[0] && isset($version[0]['version'])) ? floatval($version[0]['version']) : 0;
		if($version < 5.53){echoLog("Mysql version need bigger than 5.53");return;}
		
		//CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
		$db = Model()->db();
		$reset   = isset($this->in['reset']) && $this->in['reset'] == '1';
		$charsetSimple = $reset ? 'utf8':'utf8mb4';
		$charset = $reset ? 'utf8_general_ci':'utf8mb4_general_ci';
		$tables  = $db->getTables();
		$tables  = is_array($tables) ? $tables:array();
		
		$sqlArray = array(// 索引长度要小于1000;否则转为utf8mb4_general_ci会失败(转mb4后会变成4字节)
			"ALTER TABLE `comment_meta` ADD INDEX `key` (`key`(200)),DROP INDEX `key`",
			"ALTER TABLE `group` ADD INDEX `name` (`name`(200)),DROP INDEX `name`",
			"ALTER TABLE `comment_meta` ADD UNIQUE `commentID_key` (`commentID`, `key`(200)),DROP INDEX `commentID_key`",
			"ALTER TABLE `group_meta` ADD INDEX `key` (`key`(200)),DROP INDEX `key`",
			"ALTER TABLE `group_meta` ADD UNIQUE `groupID_key` (`groupID`, `key`(200)),DROP INDEX `groupID_key`;",
			"ALTER TABLE `io_file` ADD INDEX `name` (`name`(200)),	DROP INDEX `name`",
			"ALTER TABLE `io_file` ADD INDEX `path` (`path`(200)),	DROP INDEX `path`",
			"ALTER TABLE `io_file_meta` ADD INDEX `key` (`key`(200)),DROP INDEX `key`",
			"ALTER TABLE `io_file_meta` ADD UNIQUE `fileID_key` (`fileID`, `key`(200)),DROP INDEX `fileID_key`",
			"ALTER TABLE `io_source` ADD INDEX `name` (`name`(200)),DROP INDEX `name`",
			
			"ALTER TABLE `io_source_meta` ADD INDEX `key` (`key`(200)),DROP INDEX `key`",
			"ALTER TABLE `io_source_meta` ADD UNIQUE `sourceID_key` (`sourceID`, `key`(200)),DROP INDEX `sourceID_key`",
			"ALTER TABLE `user_meta` ADD INDEX `metaKey` (`key`(200)),DROP INDEX `metaKey`",
			"ALTER TABLE `user_meta` ADD UNIQUE `userID_metaKey` (`userID`, `key`(200)),DROP INDEX `userID_metaKey`",
			"ALTER TABLE `user_option` ADD INDEX `key` (`key`(200)),DROP INDEX `key`",
			"ALTER TABLE `user_option` ADD UNIQUE `userID_key_type` (`userID`, `key`(200), `type`),DROP INDEX `userID_key_type`",
			"ALTER TABLE `system_option` ADD UNIQUE `key_type` (`key`(200), `type`),DROP INDEX `key_type`",
		);
		
		echoLog("update index:".count($sqlArray));
		foreach ($sqlArray as $i=>$sql){
			// $sql = str_replace('(200)','',$sql);
			echoLog($sql);
			try{
				$db->execute($sql);
			}catch(Exception $e){echoLog("==error==:".$e->getMessage());}
		}
		
		// $config['databaseDefault'] = array('DB_CHARSET' => 'utf8')      // 数据库编码默认采用utf8
		// ALTER TABLE `group_meta` CHANGE `key` `key` varchar(255) NOT NULL COMMENT '存储key'
		echoLog(str_repeat('-',50)."\ntabls:".count($tables)." (speed ≈ 5w row/s);\n".str_repeat('-',50)."\n");
		foreach ($tables as $i=>$table){ //速度取决于表大小; 5w/s; 
			echoLog($table.":".$charset.";row=".Model($table)->count().'; '.($i+1).'/'.count($tables));
			//$res = $db->query('show create table `'.$table.'`');// 字段varchar 编码和表编码维持一致;
			// $sql = "ALTER TABLE `".$table."` COLLATE ".$charset;
			$sql = "ALTER TABLE `".$table."` convert to character set ".$charsetSimple." collate ".$charset;
			try {
				$db->execute($sql);
			}catch(Exception $e){echoLog("==error== ".$table.':'.$e->getMessage()."; ".$sql);}
		}
		
		// 自动更新数据库配置charset;
		$content = file_get_contents(BASIC_PATH.'config/setting_user.php');
		$content = preg_replace("/[ \t]*'DB_CHARSET'\s*=\>.*\n?/",'',$content);
		if(!$reset){
			$replaceTo = "  'DB_SQL_BUILD_CACHE'=>false,\n  'DB_CHARSET'=>'utf8mb4',\n";
			$content = preg_replace("/[ \t]*'DB_SQL_BUILD_CACHE'\s*=\>.*\n?/",$replaceTo,$content);
		}
		file_put_contents(BASIC_PATH.'config/setting_user.php',$content);
		
		echoLog(str_repeat('-',50));
		echoLog("update config/setting_user.php DB_CHARSET'=>'${charsetSimple}',");
		echoLog("Successfull!");
	}
}
share.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/share.class.php'
View Content
<?php
// 分享管理
class adminShare extends Controller{
	private $model;
	function __construct()    {
		parent::__construct();
		$this->model = Model('Share');
    }

    // 分享列表
    public function get(){
        // 举报列表
        if(isset($this->in['table']) && $this->in['table'] == 'report') {
            return $this->reportList();
        }
        // 分享详情
        if ($this->in['shareID']) {
            $info = $this->model->getInfo($this->in['shareID'], true);
            show_json($info);
        }
        // 分享列表
        $data = Input::getArray(array(
            'timeFrom'  => array('default' => null),
            'timeTo'    => array('default' => null),
            'type'      => array('default' => ''),
            'userID'    => array('default' => ''),
            'words'     => array('default' => ''),
        ));
        $res = $this->model->listAll($data);
        if(!$res) $res = array();
        show_json($res);
    }

    // 取消分享
    public function remove(){
        $id  = Input::get('id','int');
        $res = $this->model->remove($id);
        $msg = $res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
    }

    // 分享举报列表
    private function reportList(){
        $data = Input::getArray(array(
            'timeFrom'  => array('default' => null),
            'timeTo'    => array('default' => null),
            'type'      => array('default' => ''),
            'status'    => array('default' => ''),
            'words'     => array('default' => ''),
        ));
        $res = $this->model->reportList($data);
        if(!$res) $res = array();
        show_json($res);
    }

    // 举报处理
    public function status(){
        $data = Input::getArray(array(
            'id'        => array('check' => 'int'),
            'status'    => array('check' => 'int'),
        ));
        $res = $this->model->reportStatus($data);
        $msg = !!$res ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,!!$res);
    }
}
storage.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/storage.class.php'
View Content
<?php

class adminStorage extends Controller {
    public function __construct() {
        parent::__construct();
        $this->model = Model('Storage');
	}

	/**
	 * 获取存储列表
	 * @return void
	 */
    public function get() {
		$list = $this->model->listData();
		$this->parseData($list);
		$list = Hook::filter('admin.storage.get.parse', $list);
		show_json($list);
	}
	private function parseData(&$list){
		$ids = array_to_keyvalue($list, '', 'id');

		// 获取各存储中占用空间、文件数(io_file)、存储状态
		$key = md5('io.list.get.'.implode(',',$ids));
		$res = Cache::get($key);
		if ($ids && $res === false && $this->in['usage'] == '1') {
			$where = array('ioType'=>array('in',implode(',',$ids)));
			$res = Model('File')->field(array('ioType'=>'id','count(ioType)'=>'cnt','sum(size)'=>'size'))->where($where)->group('ioType')->select();
			$res = array_to_keyvalue($res, 'id');	// 可能没有文件,返回空数组
			Cache::set($key, $res, 600);	// 10分钟
		}
		$fileUse = $res !== false ? true : false;
		foreach ($list as &$item) {
			$item['sizeUse'] = intval(_get($res, $item['id'].'.size', 0));
			$item['fileNum'] = intval(_get($res, $item['id'].'.cnt', 0));
			$item['fileUse'] = $fileUse;	// 是否含文件使用信息
			$item['status']	 = 1;
			$driver = strtolower($item['driver']);
			$item['groupType'] = $this->ioGroupType($driver);
			if ($driver != 'local') continue;
			$config = $this->model->getConfig($item['id']);
			$path = realpath($config['basePath']);
			if (!$path || !mk_dir($path) || !path_writeable($path)) {
				$item['status'] = 0;
			}
		}
	}
	// 存储类别
	private function ioGroupType($driver) {
		$ioList = array(
			// 'def' => array(),
			'loc' => array('local'),
			'obj' => array('oss','cos','qiniu','s3','oos','uss','minio','eos','eds','obs','jos','moss','nos'),
			'net' => array('ftp','webdav'),
			'oth' => array('baidu','onedrive')
		);
		foreach ($ioList as $type => $list) {
			if (in_array($driver, $list)) return $type;
		}
		return 'oth';
	}

	/**
	 * 存储配置信息
	 */
	public function getConfig(){
		$id = Input::get('id','int');
		$res = $this->model->getConfig($id);
		// 隐藏密码
		$arr = array('secret','userpass','password');	// os、ftp/uss、dav
		foreach ($arr as $key) {
			if (isset($res[$key])) {
				$res[$key] = str_repeat('*', strlen($res[$key]));
				break;
			}
		}
		show_json($res,true);
	}

	/**
	 * 添加
	 */
	public function add() {
		$data = Input::getArray(array(
			"name" 		=> array("check"=>"require"),
			"sizeMax" 	=> array("check"=>"require","default"=>0),
			"driver" 	=> array("check"=>"require"),
			"default" 	=> array("check"=>"require","default"=>0),
			"system" 	=> array("check"=>"bool","default"=>0),
			"config" 	=> array("check"=>"require"),
		));
		$res = $this->model->add($data);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.repeatError');
		show_json($msg,!!$res, $res);
	}

	/**
	 * 编辑 
	 */
	public function edit() {
		$data = Input::getArray(array(
			"id"		=> array("check"=>"int"),
			"name" 		=> array("check"=>"require","default"=>null),
			"sizeMax" 	=> array("check"=>"require","default"=>null),
			"driver" 	=> array("check"=>"require","default"=>null),
			"default" 	=> array("check"=>"require","default"=>0),
			"editForce"	=> array("default"=>0),
			"config" 	=> array("check"=>"require","default"=>null),
		));
		$res = $this->model->update($data['id'],$data);
		$msg = $res ? LNG('explorer.success') : LNG('explorer.repeatError');
		show_json($msg,!!$res);
	}

	/**
	 * 删除、迁移
	 */
	public function remove() {
		$id = Input::get('id','int');
		$action = Input::get('action','in',null,array('remove','move'));

		// 1.获取删除(迁移)进度
		$taskId = $action.'.storage.'.$id;	// remove/move.storage.id
		if (isset($this->in['progress'])) {
			$data = Cache::get($taskId);
			if ($data) {
				Cache::remove($taskId);
				show_json($data, true, 1);
			}
			$data = Task::get($taskId);
			show_json($data);
		}
		Cache::remove($taskId);

		// 2.删除存储
		$done = isset($this->in['done']) ? true : false;
		// 备份数据没有数据库记录,需单独处理
		if (!$done && $action == 'remove') {
			if (Model('Backup')->findByKey('io', $id)) {
				show_json(LNG('admin.storage.ifRmBakNow'), false, 100110);
			}
		}
		// 存储中有file记录,先迁移文件再删除存储,否则直接删除存储
		$res = Model('File')->where(array('ioType' => $id))->field(array('count(*)'=>'cnt','sum(size)'=>'size'))->find();
		$GLOBALS['IO_FILE_RES'] = $res;	// 避免后面再次查询(数据量大时比较耗时)
		if(intval($res['cnt'])) {
			$info = $this->model->listData($id);
			$chks = $this->model->checkConfig($info,true);
			// 存储无法链接,确认后直接删除
			if ($chks !== true) {
				if ($action == 'move') {
					show_json(LNG('admin.storage.moveErr'), false);
				}
				if (!$done) {
					show_json(LNG('admin.storage.ifRmErrNow'), false, 100110);
				}
			}
			$res = $this->model->removeWithFile($id, $action, $info, $done);
		}else{
			$res = $action == 'remove' ? $this->model->remove($id) : true;
		}
		$code = !!$res;
		$msg = $code ? LNG('explorer.success') : LNG('explorer.error');
		show_json($msg,$code,($code ? 1 : ''));
	}
	
	// 系统回收站,自动清空;
	public function systemRecycleClear(){
		$options 	= Model('systemOption')->get();
		$clearDay 	= intval($options['systemRecycleClear']);
		$this->taskInit();
		if($options['systemRecycleOpen'] != '1') return;
		if($clearDay <= 0) return;

		$pathRecycle = KodIO::sourceID(IO_PATH_SYSTEM_RECYCLE);
		$whereEmpty  = array("parentID"	=> $pathRecycle,'size'=>0);
		$this->removeSource($whereEmpty); //清除内容为空的文件夹;

		$pathList 	 = Model('Source')->field('sourceID')->where(array("parentID"=> $pathRecycle))->select();
		$pathList	 = array_to_keyvalue($pathList,'','sourceID');
		if(!$pathList) return;
		
		$timeEnd = time() - ($clearDay * 24 * 3600);
		$whereChild = array('parentID'=>array('in',$pathList),'modifyTime' => array('<=',$timeEnd));
		$this->removeSource($whereChild);
		$this->removeSource($whereEmpty);
	}
	private function removeSource($where){
		$model = Model('Source');
		$pathList = $model->field('sourceID')->where($where)->select();
		if(!$pathList) return;
		foreach ($pathList as $item) {
			$model->removeNow($item['sourceID'],false);
		}
	}
	// 计划任务自动添加和移除;
	private function taskInit(){
		$options = Model('systemOption')->get();
		$action  = 'admin.storage.systemRecycleClear';
		$taskInitKey = 'systemRecycleTaskInit';
		
		if($options['systemRecycleOpen'] != '1'){
			if($options[$taskInitKey] == 'ok'){
				$task = Model('SystemTask')->findByKey('event',$action);
				Model('SystemTask')->remove($task['id'],true);
				Model('systemOption')->set($taskInitKey,'');
			}
			return;
		}
		
		// 已开启;
		if($options[$taskInitKey] == 'ok') return;
		$data = array (
			'name'	=> LNG('explorer.recycle.taskTitle'),
			'type'	=> 'method',
			'event' => $action,
			'time'	=> '{"type":"day","day":"02:00"}',
			'desc'	=> LNG('explorer.recycle.taskDesc'),
			'enable' => '1',
			'system' => '1',
		);
		Model('SystemTask')->add($data); 
		Model('systemOption')->set($taskInitKey,'ok');
	}
}
task.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/task.class.php'
View Content
<?php
/**
 * 任务管理
 */
class adminTask extends Controller {
	function __construct()    {
		parent::__construct();
	}

	public function taskList($userID=false){
		// Cache::deleteAll();
		$result  = Task::taskListUser($userID);// pr($result);
		$userArr = array_to_keyvalue($result,'','userID');
		$userArr = Model('User')->userListInfo($userArr);		
		foreach ($result as $key =>$value) {
			if (!$value || !is_array($value)) {
		        unset($result[$key]);
		        continue;
		    }
			if( $value['status'] == 'kill' && 
				timeFloat() - $value['timeUpdate'] >= 10){
				Task::valueSet($value['id'],false);
			}
			if( timeFloat() - $value['timeUpdate'] >= 600 ){
				Task::valueSet($value['id'],false);//超过10分钟没有更新则删除;
			}
			$result[$key]['userInfo'] = $userArr[$value['userID']];
		}
		
		// 后台任务运行状态;
		$taskInfo = false;
		if(KodUser::isRoot()){
			$taskInfo = array(//status,running,lastRun,delay
				'autoTask' 			=> AutoTask::valueGet(false), 
				'taskQueue'			=> TaskQueue::count(),
				'taskQueueLastRun'	=> TaskQueue::lastTime(),
				'taskQueueThread'	=> TaskQueue::threadCount(),
			);
		}
		$result = array_slice($result,0,50);//最多50
		show_json($result,true,$taskInfo);
	}
	public function taskKillAll($userID=false){
		$result  = Task::taskListUser($userID);
		foreach ($result as $item) {
			Task::kill($item['id']);
		}
		$this->taskList($userID);
	}
	public function taskAction(){
		$result = $this->taskActionRun(false);
		if( !is_array($result['taskInfo']) ){
			show_json(LNG('common.notExists'),false);
		}
		show_json($result['result'],true);
	}
	
	// 不允许直接访问;
	public function taskActionRun($param){
		$allow = array('get','kill','stop','restart');
		$param = Input::getArray(array(
			"action"	=> array("check"=>"in","param"=>$allow),
			"id"		=> array("check"=>"key"),
		));
		$taskInfo = Task::get($param['id']);
		if(!$taskInfo){
			// 结束数据缓存并返回;有数据时输出并清空缓存;
			if($param['action'] == 'get'){
				$data = Cache::get('result_'.$param['id']);
				if($data){
					Cache::remove('result_'.$param['id']);
					show_json($data,true,'task_finished');
				}
			}
			return array('result'=>false,'taskInfo'=>false);
		}
		switch($param['action']){
			case 'get':		$result = $taskInfo;break;
			case 'stop':	$result = Task::stop($param['id']);break;
			case 'restart':	$result = Task::restart($param['id']);break;
			case 'kill':	$result = Task::kill($param['id']);break;
			default:break;
		}
		return array('result'=>$result,'taskInfo'=>$taskInfo);
	}
}