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`).
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/analysis.class.php'
<?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);
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/auth.class.php'
<?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'));
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/autoRun.class.php'
<?php
/**
* 自动执行
*/
class adminAutoRun extends Controller {
function __construct() {
parent::__construct();
}
public function index(){
ActionCall('admin.log.hookBind'); // 绑定日志hook
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/autoTask.class.php'
<?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'));
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/backup.class.php'
<?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);
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/group.class.php'
<?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);
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/job.class.php'
<?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'));
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/log.class.php'
<?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'));
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/member.class.php'
<?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;
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/notice.class.php'
<?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'));
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/plugin.class.php'
<?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;
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/repair.class.php'
<?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);
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/role.class.php'
<?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'));
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/server.class.php'
<?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);
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/setting.class.php'
<?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!");
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/share.class.php'
<?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);
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/storage.class.php'
<?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');
}
}
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/admin/task.class.php'
<?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);
}
}