Files
BESCMS/dayrui/Fcms/Control/Admin/Attachments.php
T

783 lines
25 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php namespace Phpcmf\Control\Admin;
/**
* https://www.besyun.com
* BESCMS
* 本文件是框架系统文件,二次开发时不可以修改本文件
**/
// 附件
class Attachments extends \Phpcmf\Table {
public function __construct() {
parent::__construct();
\Phpcmf\Service::V()->assign([
'menu' => \Phpcmf\Service::M('auth')->_admin_menu(
[
'附件管理' => [\Phpcmf\Service::L('Router')->class.'/index', 'fa fa-folder'],
'已归档的附件' => [\Phpcmf\Service::L('Router')->class.'/db_index', 'fa fa-folder'],
'未归档的附件' => [\Phpcmf\Service::L('Router')->class.'/unused_index', 'fa fa-folder-o'],
'变更储存策略' => ['add:'.\Phpcmf\Service::L('Router')->class.'/remote_edit', 'fa fa-edit', '500px', '400px'],
'help' => [356],
]
)
]);
}
// 附件目录(资源管理器)
public function index() {
$rel = $this->_dir_rel_path(\Phpcmf\Service::L('input')->get('path'));
$listing = $this->_dir_listing($rel);
if ($listing === null) {
$rel = '';
$listing = $this->_dir_listing($rel);
}
$p = [
'size' => 9999,
'exts' => '*',
'count' => 20,
'attachment' => 0,
'image_reduce' => 0,
'chunk' => 10 * 1024 * 1024,
];
$upload = [
'url' => dr_web_prefix(SELF.'?c='.\Phpcmf\Service::L('Router')->class.'&m=dir_upload')
.'&siteid='.SITE_ID,
'param' => $p,
];
$uriprefix = trim(APP_DIR.'/'.\Phpcmf\Service::L('Router')->class, '/');
$parent_rel = '';
if ($listing['rel'] !== '') {
$norm = str_replace('\\', '/', $listing['rel']);
$d = dirname($norm);
$parent_rel = ($d === '.' || $d === '') ? '' : str_replace('\\', '/', $d);
}
\Phpcmf\Service::V()->assign([
'dir' => IS_DEV ? SYS_UPLOAD_PATH : dr_safe_replace_path(SYS_UPLOAD_PATH),
'url' => SYS_UPLOAD_URL,
'rel' => $listing['rel'],
'parent_rel' => $parent_rel,
'dirs' => $listing['dirs'],
'files' => $listing['files'],
'breadcrumb' => $this->_dir_breadcrumb($listing['rel']),
'upload' => $upload,
'uriprefix' => $uriprefix,
'dir_delete_url' => dr_url($uriprefix.'/dir_delete'),
'dir_mkdir_url' => dr_url($uriprefix.'/dir_mkdir'),
'dir_replace_url' => dr_url($uriprefix.'/dir_replace'),
'dir_folder_size_url' => dr_url($uriprefix.'/dir_folder_size'),
'dir_js' => [
'confirm_del' => addslashes(dr_lang('你确定要删除它们吗?')),
'mkdir_title' => addslashes(dr_lang('新建文件夹')),
'name_empty' => addslashes(dr_lang('名称不能为空')),
'upload_fail' => addslashes(dr_lang('上传失败')),
],
'meta_title' => dr_lang('附件目录'),
]);
\Phpcmf\Service::V()->display('attachment_dir.html');
}
/**
* AJAX:递归统计文件夹及子目录文件总大小
*/
public function dir_folder_size() {
if (function_exists('set_time_limit')) {
@set_time_limit(300);
}
$rel = $this->_dir_rel_path(\Phpcmf\Service::L('input')->get('path'));
if ($rel === '') {
$this->_json(0, dr_lang('参数错误'));
}
list($ok, $abs, $r) = $this->_dir_safe($rel);
if (!$ok || !is_dir($abs)) {
$this->_json(0, dr_lang('目录不存在'));
}
$root = rtrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, SYS_UPLOAD_PATH), DIRECTORY_SEPARATOR);
$rootReal = realpath($root);
if ($rootReal === false) {
$this->_json(0, dr_lang('目录无效或不存在'));
}
$bytes = $this->_dir_tree_size($abs, $rootReal);
$this->_json(1, 'ok', [
'size' => $bytes,
'size_text' => dr_format_file_size($bytes),
]);
}
/**
* 递归统计目录下所有文件字节(不跟随符号链接)
*/
protected function _dir_tree_size($absDir, $rootReal) {
$total = 0;
if (!is_dir($absDir)) {
return 0;
}
$absDir = rtrim($absDir, DIRECTORY_SEPARATOR);
if (is_link($absDir)) {
return 0;
}
if ($fp = @opendir($absDir)) {
while (($name = readdir($fp)) !== false) {
if ($name === '.' || $name === '..') {
continue;
}
$sub = $absDir.DIRECTORY_SEPARATOR.$name;
if (is_link($sub)) {
continue;
}
if (is_file($sub)) {
$sz = @filesize($sub);
if ($sz !== false) {
$total += $sz;
}
} elseif (is_dir($sub)) {
$rp = realpath($sub);
if ($rp !== false && $this->_dir_under_root($rootReal, $rp)) {
$total += $this->_dir_tree_size($rp, $rootReal);
}
}
}
closedir($fp);
}
return $total;
}
/**
* 附件目录内上传(写入当前文件夹,并走附件归档)
*/
public function dir_upload() {
if (!IS_POST) {
$this->_json(0, dr_lang('请求错误'));
}
$rel = $this->_dir_rel_path(\Phpcmf\Service::L('input')->post('path'));
list($ok, $abs, $r) = $this->_dir_safe($rel);
if (!$ok || !is_dir($abs)) {
$this->_json(0, dr_lang('目录无效或不存在'));
}
$cfg = [
'form_name' => 'file_data',
'file_exts' => ['*'],
'file_size' => 9999 * 1024 * 1024,
'attachment' => \Phpcmf\Service::M('Attachment')->get_attach_info(0),
'watermark' => 0,
'save_name' => 'null',
];
if ($r !== '') {
$cfg['path'] = $r;
}
$rt = \Phpcmf\Service::L('upload')->upload_file($cfg);
if (!$rt['code']) {
exit(dr_array2string($rt));
}
$data = [];
if (defined('SYS_ATTACHMENT_CF') && SYS_ATTACHMENT_CF && $rt['data']['md5']) {
$att = \Phpcmf\Service::M()->table('attachment')
->where('uid', $this->uid)
->where('filemd5', $rt['data']['md5'])
->where('fileext', $rt['data']['ext'])
->where('filesize', $rt['data']['size'])
->getRow();
if ($att) {
$storage = new \Phpcmf\Library\Storage($this);
$storage->delete(\Phpcmf\Service::M('Attachment')->get_attach_info(0), $rt['data']['file']);
$rt['data'] = $this->get_attachment($att['id']);
if ($rt['data']) {
$rt['data']['name'] = $rt['data']['filename'];
}
$data = dr_return_data($att['id'], 'ok');
}
}
if (!$data) {
$data = \Phpcmf\Service::M('Attachment')->save_data($rt['data']);
if (!$data['code']) {
exit(dr_array2string($data));
}
}
exit(dr_array2string(['code' => 1, 'msg' => dr_lang('上传成功'), 'id' => $data['code'], 'info' => $rt['data']]));
}
/**
* 覆盖重传指定文件(扩展名须一致)
*/
public function dir_replace() {
if (!IS_POST) {
$this->_json(0, dr_lang('请求错误'));
}
$file = isset($_FILES['file_data']) ? $_FILES['file_data'] : null;
$maxBytes = 9999 * 1024 * 1024;
if ($file && !empty($file['size']) && $file['size'] > $maxBytes) {
$this->_json(0, dr_lang('文件大小超出网站限制'));
}
$rel = $this->_dir_rel_path(\Phpcmf\Service::L('input')->post('replace'));
if ($rel === '') {
$this->_json(0, dr_lang('参数错误'));
}
list($ok, $abs, $r) = $this->_dir_safe($rel);
if (!$ok || !is_file($abs)) {
$this->_json(0, dr_lang('文件不存在'));
}
$name = basename(str_replace('\\', '/', $abs));
$oldExt = strpos($name, '.') !== false ? strtolower(trim(strrchr($name, '.'), '.')) : '';
$cfg = [
'form_name' => 'file_data',
'file_exts' => $oldExt !== '' ? [$oldExt] : ['*'],
'file_name' => $abs,
];
$rt = \Phpcmf\Service::L('upload')->update_file($cfg);
if (!$rt['code']) {
exit(dr_array2string($rt));
}
exit(dr_array2string(['code' => 1, 'msg' => dr_lang('上传成功')]));
}
/**
* 删除所选文件或空目录
*/
public function dir_delete() {
if (!IS_POST) {
$this->_json(0, dr_lang('请求错误'));
}
$paths = \Phpcmf\Service::L('input')->post('paths');
if (!$paths || !is_array($paths)) {
$this->_json(0, dr_lang('你还没有选择呢'));
}
foreach ($paths as $p) {
$rel = $this->_dir_rel_path($p);
if ($rel === '') {
$this->_json(0, dr_lang('禁止删除附件根目录'));
}
list($ok, $abs, $r) = $this->_dir_safe($rel);
if (!$ok) {
continue;
}
if (is_file($abs)) {
if (!@unlink($abs)) {
$this->_json(0, dr_lang('文件删除失败'));
}
} elseif (is_dir($abs)) {
$items = @scandir($abs);
$left = $items ? array_diff($items, ['.', '..']) : [];
if ($left) {
$this->_json(0, dr_lang('目录非空,无法删除'));
}
if (!@rmdir($abs)) {
$this->_json(0, dr_lang('目录删除失败'));
}
}
}
$this->_json(1, dr_lang('操作成功'));
}
/**
* 在当前目录下新建文件夹
*/
public function dir_mkdir() {
if (!IS_POST) {
$this->_json(0, dr_lang('请求错误'));
}
$rel = $this->_dir_rel_path(\Phpcmf\Service::L('input')->post('path'));
$name = trim((string) \Phpcmf\Service::L('input')->post('name'));
$name = dr_safe_filename($name);
if ($name === '' || strpos($name, '.') === 0) {
$this->_json(0, dr_lang('名称不规范'));
}
list($ok, $parentAbs, $r) = $this->_dir_safe($rel);
if (!$ok || !is_dir($parentAbs)) {
$this->_json(0, dr_lang('当前目录无效'));
}
$newAbs = rtrim($parentAbs, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$name;
if (file_exists($newAbs)) {
$this->_json(0, dr_lang('已存在同名文件或目录'));
}
if (!dr_mkdirs($newAbs)) {
$this->_json(0, dr_lang('目录创建失败'));
}
$this->_json(1, dr_lang('操作成功'));
}
/**
* 规范化相对路径(相对 SYS_UPLOAD_PATH
* 按段解析,禁止路径跳出上传根(..),避免简单替换字符串被绕过
*/
protected function _dir_rel_path($raw) {
$raw = (string) $raw;
if ($raw === '' || strpos($raw, "\0") !== false) {
return '';
}
$raw = str_replace('\\', '/', $raw);
$raw = str_replace(["\r", "\n", '<', '>', '{', '}'], '', $raw);
$raw = trim($raw, '/');
if ($raw === '') {
return '';
}
$parts = explode('/', $raw);
$out = [];
foreach ($parts as $p) {
if ($p === '' || $p === '.') {
continue;
}
if ($p === '..') {
if (empty($out)) {
return '';
}
array_pop($out);
continue;
}
$p = trim($p);
if ($p === '' || $p === '.' || $p === '..') {
continue;
}
$out[] = $p;
}
return implode('/', $out);
}
/**
* 是否位于上传根目录之下
*/
protected function _dir_under_root($rootReal, $candidate) {
$root = strtolower(str_replace('\\', '/', rtrim($rootReal, '/\\')));
$path = strtolower(str_replace('\\', '/', $candidate));
return $path === $root || strpos($path, $root.'/') === 0;
}
/**
* 解析安全绝对路径
*/
protected function _dir_safe($rel) {
$rel = $this->_dir_rel_path($rel);
$root = rtrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, SYS_UPLOAD_PATH), DIRECTORY_SEPARATOR);
if (!is_dir($root)) {
return [false, '', ''];
}
$rootReal = realpath($root);
if ($rootReal === false) {
return [false, '', ''];
}
if ($rel === '') {
return [true, $rootReal.DIRECTORY_SEPARATOR, ''];
}
$sub = str_replace('/', DIRECTORY_SEPARATOR, $rel);
$full = $rootReal.DIRECTORY_SEPARATOR.$sub;
if (is_file($full) || is_dir($full)) {
$rp = realpath($full);
if ($rp !== false && $this->_dir_under_root($rootReal, $rp)) {
return [true, $rp, $rel];
}
return [false, '', ''];
}
return [false, '', ''];
}
/**
* 列出目录内容
*/
protected function _dir_listing($rel) {
list($ok, $abs, $r) = $this->_dir_safe($rel);
if (!$ok || !is_dir($abs)) {
return null;
}
$dirs = [];
$files = [];
if ($fp = @opendir($abs)) {
while (($name = readdir($fp)) !== false) {
if ($name === '.' || $name === '..' || $name === '.DS_Store') {
continue;
}
if (strtolower(substr($name, -4)) === '.php') {
continue;
}
$full = $abs.DIRECTORY_SEPARATOR.$name;
$childRel = $r === '' ? $name : $r.'/'.$name;
if (is_dir($full)) {
$dirs[] = [
'name' => $name,
'path' => $childRel,
'path_esc' => htmlspecialchars($childRel, ENT_QUOTES, 'UTF-8'),
'mtime' => filemtime($full),
];
} elseif (is_file($full)) {
$ext = strtolower(trim(strrchr($name, '.'), '.'));
$files[] = [
'name' => $name,
'path' => $childRel,
'path_esc' => htmlspecialchars($childRel, ENT_QUOTES, 'UTF-8'),
'size' => filesize($full),
'mtime' => filemtime($full),
'ext' => $ext,
'url' => SYS_UPLOAD_URL.str_replace('\\', '/', $childRel),
'is_image' => dr_is_image($ext),
'is_mp4' => $ext === 'mp4',
];
}
}
closedir($fp);
}
usort($dirs, function ($a, $b) {
return strcasecmp($a['name'], $b['name']);
});
usort($files, function ($a, $b) {
return strcasecmp($a['name'], $b['name']);
});
return [
'rel' => $r,
'dirs' => $dirs,
'files' => $files,
];
}
/**
* 面包屑
*/
protected function _dir_breadcrumb($rel) {
$crumbs = [['name' => dr_lang('附件根目录'), 'path' => '']];
if ($rel === '') {
return $crumbs;
}
$parts = explode('/', $rel);
$acc = '';
foreach ($parts as $p) {
if ($p === '') {
continue;
}
$acc = $acc === '' ? $p : $acc.'/'.$p;
$crumbs[] = ['name' => $p, 'path' => $acc];
}
return $crumbs;
}
// 已归档管理
public function db_index() {
$field = [
'uid' => [
'ismain' => 1,
'fieldtype' => 'Text',
'fieldname' => 'uid',
'name' => '账号',
],
'related' => [
'ismain' => 1,
'fieldtype' => 'Text',
'fieldname' => 'related',
'name' => dr_lang('附件归属'),
],
'fileext' => [
'ismain' => 1,
'fieldtype' => 'Text',
'fieldname' => 'fileext',
'name' => dr_lang('扩展名'),
],
'filename' => [
'ismain' => 1,
'fieldtype' => 'Text',
'fieldname' => 'filename',
'name' => dr_lang('附件名称'),
],
'attachment' => [
'ismain' => 1,
'fieldtype' => 'Text',
'fieldname' => 'attachment',
'name' => dr_lang('附件路径'),
],
];
$remote = (int)$_GET['remote'];
$where_list = 'id in (select id from '.\Phpcmf\Service::M()->dbprefix('attachment').' where siteid='.SITE_ID.')';
$remote && $where_list.= ' and `remote`='.$remote;
$this->_init([
'table' => 'attachment_data',
'field' => $field,
'order_by' => 'id desc',
'where_list' => $where_list,
'date_field' => 'inputtime',
]);
$this->_List();
// 快捷上传字段参数
$p = [
'size' => 9999,
'exts' => '*',
'count' => 20,
'attachment' => $remote,
'image_reduce' => 0,
'chunk' => 10 * 1024 * 1024,
];
$upload = [
'url' => dr_web_prefix(SELF.'?c=api&token='.dr_get_csrf_token())
.'&siteid='.SITE_ID.'&m=upload&p='.dr_authcode($p, 'ENCODE'),
'param' => $p,
'back' => dr_now_url(),
];
\Phpcmf\Service::V()->assign([
'field' => $field,
'table' => 'data',
'remote' => \Phpcmf\Service::M()->table('attachment_remote')->getAll(0, 'id'),
'upload' => $upload,
]);
\Phpcmf\Service::V()->display('attachment_admin.html');
}
// 未归档的附件
public function unused_index() {
$field = [
'author' => [
'ismain' => 1,
'fieldtype' => 'Text',
'fieldname' => 'author',
'name' => dr_lang('账号'),
],
'fileext' => [
'ismain' => 1,
'fieldtype' => 'Text',
'fieldname' => 'fileext',
'name' => dr_lang('扩展名'),
],
'uid' => [
'ismain' => 1,
'fieldtype' => 'Text',
'fieldname' => 'uid',
'name' => 'uid',
],
];
$remote = (int)$_GET['remote'];
$where_list = 'siteid='.SITE_ID;
$remote && $where_list.= ' and `remote`='.$remote;
$this->_init([
'table' => 'attachment_unused',
'field' => $field,
'order_by' => 'id desc',
'where_list' => $where_list,
'date_field' => 'inputtime',
]);
$this->_List();
\Phpcmf\Service::V()->assign([
'field' => $field,
'table' => 'unused',
'remote' => \Phpcmf\Service::M()->table('attachment_remote')->getAll(0, 'id'),
]);
\Phpcmf\Service::V()->display('attachment_admin.html');
}
public function remote_edit() {
if (IS_POST) {
$post = \Phpcmf\Service::L('input')->post('data');
if ($post['o'] == $post['n']) {
$this->_json(0, dr_lang('储存策略不能相同'));
}
\Phpcmf\Service::M()->table('attachment_unused')->where('remote', intval($post['o']))->update(0, [
'remote' => intval($post['n'])
]);
\Phpcmf\Service::M()->table('attachment_data')->where('remote', intval($post['o']))->update(0, [
'remote' => intval($post['n'])
]);
dr_dir_delete(WRITEPATH.'attach');
dr_mkdirs(WRITEPATH.'attach');
$this->_json(1, dr_lang('操作成功'));
}
\Phpcmf\Service::V()->assign([
'form' => dr_form_hidden(),
'remote' => \Phpcmf\Service::M()->table('attachment_remote')->getAll(0, 'id'),
]);
\Phpcmf\Service::V()->display('attachment_remote_edit.html');
}
public function del() {
$ids = \Phpcmf\Service::L('input')->get_post_ids();
if (!$ids) {
$this->_json(0, dr_lang('你还没有选择呢'));
}
$table = \Phpcmf\Service::L('input')->post('table');
$table != 'data' && $table = 'unused';
$data = \Phpcmf\Service::M()->db->table('attachment_'.$table)->whereIn('id', $ids)->get()->getResultArray();
if (!$data) {
$this->_json(0, dr_lang('所选附件不存在'));
}
foreach ($data as $t) {
$rt = \Phpcmf\Service::M('attachment')->_delete_file($t);
if (!$rt['code']) {
return dr_return_data(0, $rt['msg']);
}
}
$this->_json(1, dr_lang('操作成功'));
}
// 强制归档
public function edit() {
$ids = \Phpcmf\Service::L('input')->get_post_ids();
if (!$ids) {
$this->_json(0, dr_lang('你还没有选择呢'));
}
$data = \Phpcmf\Service::M()->db->table('attachment_unused')->whereIn('id', $ids)->get()->getResultArray();
if (!$data) {
$this->_json(0, dr_lang('所选附件不存在'));
}
$related = 'Save';
foreach ($data as $t) {
// 更新主索引表
\Phpcmf\Service::M()->table('attachment')->update($t['id'], array(
'related' => $related
));
\Phpcmf\Service::M()->table('attachment_data')->insert(array(
'id' => $t['id'],
'uid' => $t['uid'],
'remote' => $t['remote'],
'author' => $t['author'],
'related' => $related,
'fileext' => $t['fileext'],
'filesize' => $t['filesize'],
'filename' => $t['filename'],
'inputtime' => $t['inputtime'],
'attachment' => $t['attachment'],
'attachinfo' => '',
));
// 删除未归档附件
\Phpcmf\Service::M()->table('attachment_unused')->delete($t['id']);
}
$this->_json(1, dr_lang('操作成功'));
}
// 变更储存策略
public function type_edit() {
$ids = \Phpcmf\Service::L('input')->get_post_ids();
if (!$ids) {
$this->_json(0, dr_lang('你还没有选择呢'));
}
$rid = intval(\Phpcmf\Service::L('input')->post('remote'));
if ($rid < 0) {
$this->_json(0, dr_lang('你还没有选择储存策略'));
}
$table = \Phpcmf\Service::L('input')->post('table');
$table != 'data' && $table = 'unused';
\Phpcmf\Service::M()->table('attachment_'.$table)->where_in('id', $ids)->update(0, [
'remote' => $rid
]);
dr_dir_delete(WRITEPATH.'attach');
dr_mkdirs(WRITEPATH.'attach');
$this->_json(1, dr_lang('操作成功'));
}
// 重新上传附件
public function file_edit() {
$id = \Phpcmf\Service::L('input')->get('id');
if (!$id) {
$this->_json(0, dr_lang('你还没有选择呢'));
}
$data = $this->get_attachment($id, true);
if (!$data) {
$this->_json(0, dr_lang('附件信息不存在'));
}
\Phpcmf\Service::V()->assign([
'data' => $data,
]);
\Phpcmf\Service::V()->display('attachment_upload.html');
}
// 重新上传附件
public function upload_edit() {
$id = \Phpcmf\Service::L('input')->get('id');
if (!$id) {
$this->_json(0, dr_lang('你还没有选择呢'));
}
$data = $this->get_attachment($id, true);
if (!$data) {
$this->_json(0, dr_lang('附件信息不存在'));
}
$rt = \Phpcmf\Service::L('upload')->upload_file([
'save_name' => str_replace('.'.$data['fileext'], '', basename($data['attachment'])),
'path' => dirname($data['attachment']),
'form_name' => 'file_data',
'file_exts' => [$data['fileext']],
'file_size' => 1000 * 1024 * 1024,
'attachment' => \Phpcmf\Service::M('Attachment')->get_attach_info($data['remote']),
]);
if (!$rt['code']) {
exit(dr_array2string($rt));
}
\Phpcmf\Service::M()->table('attachment_data')->update($id, ['filesize' => $rt['data']['size']]);
\Phpcmf\Service::M()->table('attachment_unused')->update($id, ['filesize' => $rt['data']['size']]);
$this->_json(1, dr_lang('上传成功'), $rt['data']);
}
}