307 lines
11 KiB
PHP
Raw Normal View History

2024-08-31 01:03:37 +08:00
<?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 explorerUpload extends Controller{
public function __construct(){
parent::__construct();
$this->model = Model("Source");
}
public function pathAllowReplace($path){
$notAllow = array('\\', ':', '*', '?', '"', '<', '>', '|',"\r","\n");//不允许字符
$db = $this->config['database'];// 文件文件夹名emoji是否支持处理;
if(!isset($db['DB_CHARSET']) || $db['DB_CHARSET'] != 'utf8mb4'){
$path = preg_replace_callback('/./u',function($match){return strlen($match[0]) >= 4 ? '-':$match[0];},$path);
}
return str_replace($notAllow,'_',$path);
}
// 通过上传内容获得上传临时文件;(插件;文件编辑保存)
public function fileUploadTemp(){
$this->in["chunkSize"] = '0';
$this->in["size"] = '0';
$uploader = new Uploader();
$localFile = $uploader->upload();
$uploader->statusSet(false);
return $localFile;
}
/**
* 上传,三个阶段
* checkMd5:上传前;秒传处理、前端上传处理
* uploadLinkSuccess: 前端上传完成处理;
* 其他: 正常通过后端上传上传到后端;
*/
public function fileUpload(){
$this->authorizeCheck();
$uploader = new Uploader();
$savePath = $this->in['path'];
if(!IO::exist($savePath)) show_json(LNG('explorer.upload.errorPath'),false);
if( $this->in['fullPath']){//带文件夹的上传
$fullPath = KodIO::clear($this->in['fullPath']);
$fullPath = $this->pathAllowReplace($fullPath);
$fullPath = get_path_father($fullPath);
$savePath = IO::mkdir(rtrim($savePath,'/').'/'.$fullPath);
}
$repeat = Model('UserOption')->get('fileRepeat');
$repeat = isset($this->in['fileRepeat']) ? $this->in['fileRepeat'] : $repeat;
$repeat = isset($this->in['repeatType']) ? $this->in['repeatType'] : $repeat; // 向下兼容pc客户端
// 上传前同名文件处理(默认覆盖; [覆盖,重命名,跳过])
$uploader->fileName = $this->pathAllowReplace($uploader->fileName);
if($repeat == REPEAT_RENAME){
$uploader->fileName = IO::fileNameAuto($savePath,$uploader->fileName,$repeat);
if(!$uploader->fileName){show_json('skiped',true);}
}
$savePath = rtrim($savePath,'/').'/'.$uploader->fileName;
// 文件保存; 必须已经先存在;
if($this->in['fileSave'] == '1'){
$repeat = REPEAT_REPLACE;
$info = IO::info($this->in['path']);
if(!$info){
show_json(LNG("common.pathNotExists"),false);
}
$this->in['name'] = $info['name'];
$uploader->fileName = $this->pathAllowReplace($info['name']);
$parent = IO::pathFather($info['path']);
if(!$parent){show_json(LNG("common.pathNotExists"),false);}
$savePath = rtrim($parent,'/').'/'.$info['name'];// 重新构造路径 父目录+文件名;
}
// 第三方存储上传完成
if( isset($this->in['uploadLinkSuccess']) ){
$this->fileUploadByClient($savePath,$repeat);
}
if( isset($this->in['checkType']) ){
$this->fileUploadCheckExist($uploader,$savePath,$repeat);
}
// 通过服务器上传;
$localFile = $uploader->upload();
$path = IO::upload($savePath,$localFile,true,$repeat);//本地到本地则用move的方式;
$uploader->clearData();//清空上传临时文件;
// pr($localFile,$path,$savePath,$uploader,$this->in);exit;
if($path){
show_json(LNG("explorer.upload.success"),true,$this->uploadInfo($path));
}else{
show_json(IO::getLastError(LNG("explorer.upload.error")),false);
}
}
private function uploadInfo($path){
$info = IO::info($path);
// 记录文件本身最后修改时间;
if($info && $this->in['modifyTime']){
$modifyTime = abs(intval(substr($this->in['modifyTime'],0,10)));
if($modifyTime > 1000 && $modifyTime < time()){
IO::setModifyTime($path,$modifyTime);
}
}
if($this->in['fileInfo'] != '1') return $path;
$info = array_field_key($info,array("ext",'name','createTime','size','path','pathDisplay'));
$info['downloadPath'] = Action('explorer.share')->link($path);
return $info;
}
// 第三方上传获取凭证
private function authorizeCheck(){
if( !isset($this->in['authorize']) ) return;
$inPath = $this->in['path'];
if(substr(IO::getType($inPath), 0, 2) == 'db'){
$path = KodIO::defaultIO().$inPath;
}else{
$pathBase = substr($inPath, 0, stripos($inPath, '/'));
$path = (!$pathBase ? $inPath : $pathBase) . '/' . $inPath;
}
$paramMore = $this->getParamMore();
$result = IO::multiUploadAuthData($path, $paramMore);
show_json($result, true);
}
// 获取paramMore兼容json和数组
private function getParamMore(){
if(!isset($this->in['paramMore'])) return array();
if(is_array($this->in['paramMore'])) return $this->in['paramMore'];
if($paramMore = json_decode($this->in['paramMore'], true)) return $paramMore;
return array();
}
//秒传及断点续传处理
private function fileUploadCheckExist($uploader,$savePath,$repeat){
$size = $this->in['size'];
$isSource = false;
$hashSimple = isset($this->in['checkHashSimple']) ? $this->in['checkHashSimple']:false;
$hashMd5 = isset($this->in['checkHashMd5']) ? $this->in['checkHashMd5']:false;
if(substr(IO::getType($savePath), 0, 2) == 'db' && $hashSimple ){
$isSource = true;
$file = Model("File")->findByHash($hashSimple,$hashMd5);
}else{
$file = array('hashSimple' => null, 'hashMd5' => null); // 非绑定数据库存储不检查秒传
}
if(!$file['hashMd5']){$file['hashSimple'] = null;}
$checkChunkArray = array();
if($hashSimple){$checkChunkArray = $uploader->checkChunk();} // 断点续传保持处理;
$default = KodIO::defaultDriver();
$infoData = array(
"checkChunkArray" => $checkChunkArray,
"checkFileHash" => array(
"hashSimple"=>$file['hashSimple'],
"hashMd5" =>$file['hashMd5']
),
"uploadLinkInfo" => IO::uploadLink($savePath, $size),//前端上传信息获取;
"uploadToKod" => $isSource,
"uploadChunkSize" => $this->config['settings']['upload']['chunkSize'],
"kodDriverType" => $default['driver'],
);
$linkInfo = &$infoData['uploadLinkInfo'];
if(isset($linkInfo['host'])){ // 前端上传时,自适应处理(避免http,https混合时浏览器拦截问题; )
$linkInfo['host'] = str_replace("http://",'//',$linkInfo['host']);
// $linkInfo['host'] = str_replace("https://",'//',$linkInfo['host']); // 存储只限https访问时去掉会有异常
}
$this->checkAllowUploadWeb($infoData);
// 保留参数部分; kod挂载kod的webdav前端上传;
if($this->in['addUploadParam']){$infoData['addUploadParam'] = $this->in['addUploadParam'];} // server;
if($linkInfo['webdavUploadTo']){$infoData = $linkInfo;} // webdav client 首次检测中转访问;
if( $this->in['checkType'] == 'matchMd5' &&
!empty($this->in['checkHashMd5']) &&
!empty($file['hashMd5']) &&
$this->in['checkHashMd5'] == $file['hashMd5']
){
$path = IO::uploadFileByID($savePath,$file['fileID'],$repeat);
$uploader->clearData();//清空上传临时文件;
show_json(LNG('explorer.upload.secPassSuccess'),true,$this->uploadInfo($path));
}else{
show_json(LNG('explorer.success'),true,$infoData);
}
}
// 检测, 是否允许前端对象存储直传(腾讯cos+Android浏览器form分片上传时,)
private function checkAllowUploadWeb(&$infoData){
if(!$infoData['uploadLinkInfo']){return;}
// if(stristr($_SERVER['HTTP_USER_AGENT'],'android')){$infoData['uploadLinkInfo'] = false;}
}
/**
* 前端上传,完成后记录并处理;
*
* $key是完整路径type为DB即为默认io$savePath={source:x}/$key
* 获取默认io判断{io:n}/$key
* 否则,$savePath={io:x}/$key直接判断
*/
private function fileUploadByClient($savePath,$repeat){
$paramMore = $this->getParamMore();
$remotePath = $this->parsePath(KodIO::defaultDriver(),$this->in['key']);
// 耗时操作;
if(!IO::exist($remotePath)){
show_json(LNG("explorer.upload.error"), false);
}
$path = IO::addFileByRemote($savePath, $remotePath,$paramMore,$this->in['name'],$repeat);
show_json(LNG("explorer.upload.success"),true,$this->uploadInfo($path));
}
private function parsePath($driver,$path){
$bucket = isset($driver['config']['bucket']) ? $driver['config']['bucket'].'/':'';
$pathBase = trim($driver['config']['basePath'], '/');
$pathPre = $bucket.$pathBase;
if(substr($path,0,strlen($pathPre)) == $pathPre){
$path = substr($path,strlen($pathPre));
}else if(!empty($pathBase) && substr($path,0,strlen($pathBase)) == $pathBase){
$path = substr($path,strlen($pathBase));
}
$remotePath = '{io:'.$driver['id'].'}/'.trim($path, '/');
return $remotePath;
}
// 远程下载
public function serverDownload() {
if(!$this->in['uuid']){
$this->in['uuid'] = md5($this->in['url']);
}
$uuid = 'download_'.$this->in['uuid'];
$this->serverDownloadCheck($uuid);
$url = $this->in['url'];
$savePath = rtrim($this->in['path'],'/').'/';
$header = url_header($url);
if (!$header){
show_json(LNG('download_error_exists'),false);
}
$filename = _get($this->in,'name',$header['name']);
$filename = unzip_filter_ext($filename);
$tempFile = TEMP_FILES.md5($uuid);
mk_dir(TEMP_FILES);
Session::set($uuid,array(
'supportRange' => $header['supportRange'],
'length' => $header['length'],
'path' => $tempFile,
'name' => $filename,
));
$this->serverDownloadHashCheck($url,$header,$savePath,$filename,$uuid);
$result = Downloader::start($url,$tempFile);
if($result['code']){
$outPath = IO::copy($tempFile,$savePath,REPEAT_RENAME);
$fileName= IO::fileNameAuto($savePath,$filename,REPEAT_RENAME);
$outPath = IO::rename($outPath,$fileName);
show_json(LNG('explorer.downloaded'),true,IO::info($outPath));
}else{
show_json($result['data'],false);
}
}
/**
* 远程下载秒传处理;
* 小于10M的文件不处理;
*/
private function serverDownloadHashCheck($url,$header,$savePath,$filename,$uuid){
return;// 暂时关闭该特性;
if($header['length'] < 10 * 1024*1024) return false;
$driver = new PathDriverUrl();
$fileHash = $driver->hashSimple($url,$header); // 50个请求;8s左右;
$file = Model("File")->findByHash($fileHash);
if(!$fileHash || !$file) return;
$tempFile = $file['path'];
Session::remove($uuid);
$outPath = IO::copy($tempFile,$savePath,REPEAT_RENAME);
$fileName= IO::fileNameAuto($savePath,$filename,REPEAT_RENAME);
$outPath = IO::rename($outPath,$fileName);
show_json(LNG('explorer.upload.secPassSuccess'),true,IO::info($outPath));
}
private function serverDownloadCheck($uuid){
$data = Session::get($uuid);
if ($this->in['type'] == 'percent') {
if (!$data) show_json('uuid error',false);
$result = array(
'supportRange' => $data['supportRange'],
'uuid' => $this->in['uuid'],
'length' => intval($data['length']),
'name' => $data['name'],
'size' => intval(@filesize($data['path'].'.downloading')),
'time' => mtime()
);
show_json($result);
}else if($this->in['type'] == 'remove'){//取消下载;文件被删掉则自动停止
if($data){
IO::remove($data['path'].'.downloading');
IO::remove($data['path'].'.download.cfg');
Session::remove($uuid);
}
show_json('');
}
}
}