<?php

if(!defined('TEST_MODE')) {
    include("./includes/common.php");
}

$act = isset($_GET['act']) ? $_GET['act'] : null;
$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';

if(!$act && $method !== 'POST' && !defined('TEST_MODE')){
    if($conf['forcelogin']==1 && !$islogin2) exit("<script language='javascript'>alert('请先登录！');window.location.href='./login.php';</script>");
    $title = '上传文件 - '.$conf['title'];
    $csrf_token = md5(mt_rand(0,999).time());
    $_SESSION['csrf_token'] = $csrf_token;
    $selectedFolderId = isset($_GET['folder_id']) ? intval($_GET['folder_id']) : 0;
    if(!$islogin2) $selectedFolderId = 0;
    if($selectedFolderId > 0 && $islogin2){
        $folderRow = $DB->getRow("SELECT folder_id FROM pre_folder WHERE folder_id=:fid AND uid=:uid LIMIT 1", [':fid'=>$selectedFolderId, ':uid'=>$uid]);
        if(!$folderRow) $selectedFolderId = 0;
    }
    $folderOptionsHtml = '<option value="0">根目录</option>';
    if($islogin2){
        $foldersForSelect = $DB->getAll("SELECT folder_id,parent_id,name FROM pre_folder WHERE uid=:uid ORDER BY sort ASC, name ASC", [':uid'=>$uid]);
        $children = [];
        foreach($foldersForSelect as $f){
            $pid = intval($f['parent_id']);
            if(!isset($children[$pid])) $children[$pid] = [];
            $children[$pid][] = $f;
        }
        $buildOptions = function($parentId, $depth) use (&$buildOptions, &$children, $selectedFolderId){
            $html = '';
            if(!isset($children[$parentId])) return $html;
            foreach($children[$parentId] as $f){
                $fid = intval($f['folder_id']);
                $name = htmlspecialchars($f['name']);
                $prefix = str_repeat('　', max(0, $depth));
                $selected = $fid === $selectedFolderId ? ' selected' : '';
                $html .= '<option value="'.$fid.'"'.$selected.'>'.$prefix.$name.'</option>';
                $html .= $buildOptions($fid, $depth + 1);
            }
            return $html;
        };
        $folderOptionsHtml .= $buildOptions(0, 0);
    }
    include SYSTEM_ROOT.'header.php';
?>
<link rel="stylesheet" href="assets/css/upload.css?v=<?php echo VERSION; ?>">

<div class="container upload-page-wrapper">
    <div class="page-bg-orb orb-1"></div>
    <div class="page-bg-orb orb-2"></div>
    <div class="page-bg-orb orb-3"></div>

    <input type="hidden" id="csrf_token" value="<?php echo $csrf_token; ?>">

    <div class="upload-header">
        <h1>
            <span class="header-icon"><i class="fa fa-cloud-upload"></i></span>
            上传文件
        </h1>
        <p>支持拖拽上传、Ctrl+V 粘贴上传，可同时上传多个文件</p>
    </div>

    <div class="upload-card">
        <div class="upload-card-header">
            <h3><i class="fa fa-cog"></i> 上传设置</h3>
            <div class="folder-select-group">
                <label>上传到：</label>
                <select id="folder_id">
                    <?php echo $folderOptionsHtml; ?>
                </select>
                <?php if($islogin2){ ?>
                <a class="btn-disk-link" href="./disk.php">网盘管理</a>
                <?php } ?>
            </div>
        </div>

        <div class="upload-card-body">
            <div id="drop-zone" class="drop-zone">
                <div class="upload-icon-wrapper">
                    <i class="fa fa-cloud-upload"></i>
                </div>
                <h4>点击选择文件 或 拖拽文件到此处</h4>
                <p>支持批量上传，Ctrl+V 粘贴文件也可快速上传</p>
                <input type="file" id="file-input" multiple style="display:none">
            </div>

            <div class="upload-options-row">
                <label class="option-label">
                    <input type="checkbox" id="show-home-chk" checked>
                    在首页文件列表显示
                </label>
            </div>

            <div class="upload-action-buttons">
                <button class="btn-upload-primary" id="btn-start">
                    <i class="fa fa-play"></i> 开始上传
                </button>
                <button class="btn-upload-secondary" id="btn-clear">
                    <i class="fa fa-trash-o"></i> 清除已完成
                </button>
            </div>
        </div>
    </div>

    <div class="upload-table-card">
        <div class="upload-table-header">
            <h3><i class="fa fa-list"></i> 上传队列</h3>
            <span class="queue-count" id="queue-count">0</span>
        </div>

        <div class="upload-table-scroll">
            <table class="upload-table">
                <thead>
                    <tr>
                        <th>文件名</th>
                        <th width="90">大小</th>
                        <th width="180">进度</th>
                        <th width="90">速度</th>
                        <th width="90">状态</th>
                        <th width="55">操作</th>
                    </tr>
                </thead>
                <tbody id="upload-file-list">
                    <tr class="empty-queue-row">
                        <td colspan="6">
                            <div class="empty-queue-state">
                                <i class="fa fa-inbox"></i>
                                <p>暂无上传任务</p>
                            </div>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>

        <div class="upload-table-footer">
            <button class="btn-upload-primary" id="btn-start-bottom">
                <i class="fa fa-play"></i> 开始上传
            </button>
            <button class="btn-upload-secondary" id="btn-clear-bottom">
                <i class="fa fa-trash-o"></i> 清除已完成
            </button>
        </div>
    </div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script>
<script src="assets/js/batch_upload.js?v=<?php echo VERSION; ?>"></script>
<script src="assets/js/upload_page.js?v=<?php echo VERSION; ?>"></script>
<script src="https://s4.zstatic.net/ajax/libs/layer/3.1.1/layer.min.js"></script>

<?php
    include SYSTEM_ROOT.'footer.php';
    exit;
}

@header('Content-Type: application/json; charset=UTF-8');
if($method !== 'POST' && !checkRefererHost()){
    exit(json_encode(['code' => 403, 'msg' => '非法请求']));
}

class UploadController {
    private $db;
    private $stor;
    private $conf;
    private $uid;
    private $clientip;
    private $islogin;

    public function __construct($db, $stor, $conf, $uid, $clientip, $islogin) {
        $this->db = $db;
        $this->stor = $stor;
        $this->conf = $conf;
        $this->uid = $uid;
        $this->clientip = $clientip;
        $this->islogin = $islogin;

        if($this->islogin && isset($GLOBALS['userrow']) && $GLOBALS['userrow']['level'] > 0){
            $this->conf['upload_limit'] = 0;
            $this->conf['videoreview'] = 0;
            $this->conf['type_block'] = null;
            $this->conf['name_block'] = null;
        }
    }

    public function handleRequest() {
        $act = isset($_GET['act']) ? $_GET['act'] : null;

        try {
            if($this->conf['forcelogin'] == 1 && !$this->islogin) {
                throw new Exception("请先登录后上传");
            }

            if($act) {
                $this->checkCsrf();
            }

            switch($act) {
                case 'pre_upload':
                    $this->checkCsrf();
                    $this->preUpload();
                    break;
                case 'upload_part':
                    $this->checkCsrf();
                    $this->uploadPart();
                    break;
                case 'complete_upload':
                    $this->checkCsrf();
                    $this->completeUpload();
                    break;
                default:
                    echo json_encode(['code' => -4, 'msg' => 'No Act']);
                    break;
            }
        } catch (Exception $e) {
            echo json_encode(['code' => $e->getCode() ?: -1, 'msg' => $e->getMessage()]);
        }
    }

    private function preUpload() {
        $name = trim(htmlspecialchars($_POST['name'] ?? ''));
        $hash = trim($_POST['hash'] ?? '');
        $size = intval($_POST['size'] ?? 0);
        $show = isset($_POST['show']) ? intval($_POST['show']) : 1;
        $hide = ($show === 1) ? 0 : 1;
        $ispwd = intval($_POST['ispwd'] ?? 0);
        $pwd = ($ispwd == 1) ? trim(htmlspecialchars($_POST['pwd'] ?? '')) : null;
        $folder_id = isset($_POST['folder_id']) ? intval($_POST['folder_id']) : 0;
        $folder_id = max(0, $folder_id);
        $this->assertFolderWritable($folder_id);

        if(empty($name)) throw new Exception("文件名不能为空");
        if(!preg_match('/^[0-9a-z]{32}$/i', $hash)) throw new Exception("Hash格式错误");
        if($ispwd == 1 && !empty($pwd) && !preg_match('/^[a-zA-Z0-9]+$/', $pwd)) {
            throw new Exception("文件密码只能为字母和数字");
        }

        $ext = get_file_ext($name);
        $this->checkFileBlock($name, $ext);

        $limit_size = intval($this->conf['upload_size']);
        if($limit_size > 0 && $size > $limit_size * 1024 * 1024) {
            throw new Exception("上传文件大小限制{$limit_size}MB");
        }

        $this->checkDailyLimit();

        if ($this->uid) {
            $user = $this->db->getRow("SELECT * FROM pre_user WHERE uid=:uid LIMIT 1", [':uid' => $this->uid]);
            if ($user) {
                $quota = check_user_quota($user, false, 'storage');
                if ($quota['code'] !== 0) {
                    throw new Exception($quota['msg'] . "，请联系管理员");
                }

                if ($user['storage_limit'] > 0 && ($user['storage_used'] + $size) > ($user['storage_limit'] * 1024 * 1024)) {
                    throw new Exception("存储空间已不足");
                }
            }
        }

        $row = $this->db->getRow("SELECT * FROM pre_file WHERE hash=:hash", [':hash' => $hash]);

        $fileHelper = new \lib\File($this->db);
        $name = $fileHelper->getUniqueName($this->uid, $folder_id, $name);

        if($row) {
            $sql = "INSERT INTO `pre_file` (`name`,`type`,`size`,`hash`,`addtime`,`ip`,`hide`,`pwd`,`uid`,`folder_id`) VALUES (:name,:type,:size,:hash,NOW(),:ip,:hide,:pwd,:uid,:folder_id)";
            $params = [
                ':name' => $name,
                ':type' => $row['type'],
                ':size' => $row['size'],
                ':hash' => $hash,
                ':ip' => $this->clientip,
                ':hide' => $hide,
                ':pwd' => $pwd,
                ':uid' => $this->uid ? $this->uid : 0,
                ':folder_id' => $folder_id
            ];

            if($this->db->exec($sql, $params)) {
                $id = $this->db->lastInsertId();
                $_SESSION['fileids'][] = $id;
                add_guest_file_id($id);

                if($this->uid){
                    $this->db->exec("UPDATE pre_user SET storage_used=storage_used+:size WHERE uid=:uid", [':size'=>$row['size'], ':uid'=>$this->uid]);
                }

                echo json_encode([
                    'code' => 1,
                    'msg' => '文件上传成功！(秒传)',
                    'exists' => 0,
                    'hash' => $hash,
                    'name' => $name,
                    'size' => $row['size'],
                    'type' => $row['type'],
                    'id' => $id
                ]);
                return;
            } else {
                throw new Exception("数据库写入失败: " . $this->db->error());
            }
        }

        $upload_data = [
            'name' => $name,
            'hash' => $hash,
            'size' => $size,
            'ext' => $ext,
            'hide' => $hide,
            'pwd' => $pwd,
            'folder_id' => $folder_id
        ];

        if(\lib\StorHelper::is_cloud() && $this->conf['uploadfile_type'] == 1) {
            $param = $this->stor->getUploadParam($hash, $name, $limit_size * 1024 * 1024);
            if(!$param) throw new Exception("获取上传参数失败: " . $this->stor->errmsg());

            $upload_data['chunks'] = 1;
            $this->setSession($hash, $upload_data);

            echo json_encode([
                'code' => 0,
                'third' => true,
                'hash' => $hash,
                'url' => $param['url'],
                'post' => $param['post']
            ]);
        } else {
            $chunksize = 8 * 1024 * 1024;
            $chunks = ceil($size / $chunksize);
            $upload_data['chunks'] = $chunks;

            $this->setSession($hash, $upload_data);

            echo json_encode([
                'code' => 0,
                'third' => false,
                'hash' => $hash,
                'chunksize' => $chunksize,
                'chunks' => $chunks
            ]);
        }
    }

    private function uploadPart() {
        if(!isset($_FILES['file'])) throw new Exception("请选择文件");

        $chunk = intval($_POST['chunk'] ?? 0);
        $hash = trim($_POST['hash'] ?? '');

        $session_data = $this->getSession($hash);
        if(!$session_data) throw new Exception("参数校验失败，请刷新页面重试");

        if(!preg_match('/^[0-9a-z]{32}$/i', $hash)) throw new Exception("Hash格式错误");

        $chunks = intval($session_data['chunks']);
        $ext = $session_data['ext'];
        $tempFile = $_FILES['file']['tmp_name'];

        $real_hash = '';
        $real_size = 0;

        if($chunks > 1) {
            $partFile = sys_get_temp_dir() . '/' . $hash . '.part' . $chunk;
            if(!move_uploaded_file($tempFile, $partFile)) {
                throw new Exception("文件第{$chunk}分块上传失败");
            }

            if($chunks == $chunk) {
                $savePathTemp = file_part_merge($hash, $chunks);
                $real_hash = md5_file($savePathTemp);
                $real_size = filesize($savePathTemp);

                $result = $this->stor->savefile($hash, $savePathTemp, minetype($ext));
                if(!$result) throw new Exception("文件保存失败: " . $this->stor->errmsg());
                echo json_encode(['code' => 0, 'chunk' => $chunk, 'completed' => true]);
                return;
            } else {
                echo json_encode(['code' => 0, 'chunk' => $chunk]);
                return;
            }
        } else {
            $real_hash = md5_file($tempFile);
            $real_size = filesize($tempFile);
            $result = $this->stor->upload($hash, $tempFile, minetype($ext));
            if(!$result) throw new Exception("文件上传失败: " . $this->stor->errmsg());
        }

        if($real_size != $session_data['size']) throw new Exception("文件大小校验失败");
        if($real_hash != $hash) throw new Exception("文件MD5校验失败");

        echo json_encode(['code' => 0, 'completed' => true]);
    }

    private function completeUpload() {
        $hash = trim($_POST['hash'] ?? '');

        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        $session_data = $this->getSession($hash);
        if(!$session_data) throw new Exception("参数校验失败，请刷新页面重试");

        $this->finalizeUpload($hash, $session_data);
    }

    private function finalizeUpload($hash, $session_data) {
        if(!preg_match('/^[0-9a-z]{32}$/i', $hash)) throw new Exception("Hash格式错误");

        $fileHelper = new \lib\File($this->db);

        $name = $session_data['name'];
        $folder_id = isset($session_data['folder_id']) ? intval($session_data['folder_id']) : 0;
        $folder_id = max(0, $folder_id);
        $this->assertFolderWritable($folder_id);

        $name = $fileHelper->getUniqueName($this->uid, $folder_id, $name);
        $session_data['name'] = $name;

        $row = $this->db->getRow("SELECT * FROM pre_file WHERE hash=:hash", [':hash' => $hash]);

        $data = [
            ':name' => $name,
            ':type' => $session_data['ext'],
            ':size' => $session_data['size'],
            ':hash' => $hash,
            ':ip' => $this->clientip,
            ':hide' => isset($session_data['hide']) ? $session_data['hide'] : 0,
            ':pwd' => isset($session_data['pwd']) ? $session_data['pwd'] : null,
            ':uid' => $this->uid ? $this->uid : 0,
            ':folder_id' => $folder_id
        ];

        $sql = "INSERT INTO `pre_file` (`name`,`type`,`size`,`hash`,`addtime`,`ip`,`hide`,`pwd`,`uid`,`folder_id`) VALUES (:name,:type,:size,:hash,NOW(),:ip,:hide,:pwd,:uid,:folder_id)";

        if(!$this->db->exec($sql, $data)) {
            throw new Exception("数据库写入失败: " . $this->db->error());
        }

        $id = $this->db->lastInsertId();

        if($this->uid){
            $this->db->exec("UPDATE pre_user SET storage_used=storage_used+:size WHERE uid=:uid", [':size'=>$session_data['size'], ':uid'=>$this->uid]);
        }

        $this->checkContentSafety($id, $hash, $session_data['ext']);

        $_SESSION['fileids'][] = $id;
        add_guest_file_id($id);
        $this->clearSession($hash);

        echo json_encode([
            'code' => 1,
            'msg' => '文件上传成功！',
            'exists' => 0,
            'hash' => $hash,
            'name' => $name,
            'size' => $session_data['size'],
            'type' => $session_data['ext'],
            'id' => $id
        ]);
    }

    private function checkContentSafety($id, $hash, $ext) {
        $type_image = explode('|', $this->conf['type_image']);
        $type_video = explode('|', $this->conf['type_video']);

        if($this->conf['green_check'] > 0 && in_array($ext, $type_image)) {
            if(checkImage($hash, $ext)) {
                $this->db->exec("UPDATE `pre_file` SET `block`=1 WHERE `id`='{$id}' LIMIT 1");
            }
        }
        if($this->conf['videoreview'] == 1 && in_array($ext, $type_video)) {
            $this->db->exec("UPDATE `pre_file` SET `block`=2 WHERE `id`='{$id}' LIMIT 1");
        }
    }

    private function checkCsrf() {
        if(!isset($_POST['csrf_token']) || $_POST['csrf_token'] != $_SESSION['csrf_token']) {
            throw new Exception("CSRF TOKEN ERROR", -1);
        }
    }

    private function checkFileBlock($name, $ext) {
        if($this->conf['type_block']) {
            $type_block = explode('|', $this->conf['type_block']);
            if(in_array($ext, $type_block)) throw new Exception("不支持上传该格式文件");
        }
        if($this->conf['name_block']) {
            $name_block = explode('|', $this->conf['name_block']);
            foreach($name_block as $row) {
                if(strpos($name, $row) !== false) throw new Exception("文件名包含违规字符");
            }
        }
    }

    private function assertFolderWritable($folderId) {
        if($folderId <= 0) return;
        if(!$this->uid) throw new Exception("请先登录后选择文件夹");
        $row = $this->db->getRow("SELECT folder_id FROM pre_folder WHERE folder_id=:fid AND uid=:uid LIMIT 1", [':fid'=>$folderId, ':uid'=>$this->uid]);
        if(!$row) throw new Exception("目标文件夹不存在或无权限");
    }

    private function checkDailyLimit() {
        if($this->conf['upload_limit'] > 0) {
            $thisday = date("Y-m-d 00:00:00");
            if($this->islogin) {
                $ipcount = $this->db->getColumn("SELECT count(*) from pre_file WHERE uid='{$this->uid}' AND addtime>='{$thisday}'");
            } else {
                $ipcount = $this->db->getColumn("SELECT count(*) from pre_file WHERE ip='{$this->clientip}' AND addtime>='{$thisday}'");
            }
            if($ipcount > $this->conf['upload_limit']) {
                throw new Exception("你今天上传文件的数量已超过限制");
            }
        }
    }

    private function setSession($hash, $data) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        $_SESSION['upload_sessions'][$hash] = $data;
        $_SESSION['upload'] = $data;
    }

    private function getSession($hash) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        if(isset($_SESSION['upload_sessions'][$hash])) {
            return $_SESSION['upload_sessions'][$hash];
        }
        if(isset($_SESSION['upload']) && isset($_SESSION['upload']['hash']) && $_SESSION['upload']['hash'] == $hash) {
            return $_SESSION['upload'];
        }
        return null;
    }

    private function clearSession($hash) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        if(isset($_SESSION['upload_sessions'][$hash])) {
            unset($_SESSION['upload_sessions'][$hash]);
        }
        if(isset($_SESSION['upload']) && $_SESSION['upload']['hash'] == $hash) {
            unset($_SESSION['upload']);
        }
    }
}

if(!defined('TEST_MODE')) {
    $controller = new UploadController($DB, $stor, $conf, $uid, $clientip, $islogin2);
    $controller->handleRequest();
}
?>