400 lines
16 KiB
PHP
400 lines
16 KiB
PHP
<?php
|
||
require_once 'config.php';
|
||
|
||
if (!isset($_SESSION['user_id'])) {
|
||
header('Location: login.php');
|
||
exit;
|
||
}
|
||
|
||
|
||
try {
|
||
$stmt = $pdo->query("SELECT * FROM tags ORDER BY name");
|
||
$allTags = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
} catch(PDOException $e) {
|
||
$allTags = [];
|
||
}
|
||
|
||
$error = '';
|
||
$success = '';
|
||
$uploadResults = [];
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_FILES['images'])) {
|
||
$is_public = isset($_POST['is_public']) ? 1 : 0;
|
||
$uploadedFiles = $_FILES['images'];
|
||
|
||
$fileCount = count($uploadedFiles['name']);
|
||
$successCount = 0;
|
||
$errorCount = 0;
|
||
|
||
for ($i = 0; $i < $fileCount; $i++) {
|
||
if ($uploadedFiles['error'][$i] === UPLOAD_ERR_NO_FILE) continue;
|
||
|
||
$title = trim($_POST['titles'][$i] ?? '');
|
||
$file_tags = $_POST['tags'][$i] ?? [];
|
||
$file = [
|
||
'name' => $uploadedFiles['name'][$i],
|
||
'type' => $uploadedFiles['type'][$i],
|
||
'tmp_name' => $uploadedFiles['tmp_name'][$i],
|
||
'error' => $uploadedFiles['error'][$i],
|
||
'size' => $uploadedFiles['size'][$i]
|
||
];
|
||
|
||
|
||
if (empty($title)) $title = pathinfo($file['name'], PATHINFO_FILENAME);
|
||
|
||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||
$uploadResults[] = ['success' => false, 'filename' => $file['name'], 'message' => t('upload_failed')];
|
||
$errorCount++;
|
||
continue;
|
||
}
|
||
|
||
if ($file['size'] > MAX_FILE_SIZE) {
|
||
$uploadResults[] = ['success' => false, 'filename' => $file['name'], 'message' => t('max_size') . ': 5MB'];
|
||
$errorCount++;
|
||
continue;
|
||
}
|
||
|
||
$file_extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||
if (!in_array($file_extension, ALLOWED_TYPES)) {
|
||
$uploadResults[] = ['success' => false, 'filename' => $file['name'], 'message' => t('supported_formats') . ': JPG, PNG, GIF, WebP'];
|
||
$errorCount++;
|
||
continue;
|
||
}
|
||
|
||
|
||
$filename = uniqid() . '_' . time() . '.' . $file_extension;
|
||
$upload_path = 'uploads/' . $filename;
|
||
|
||
if (move_uploaded_file($file['tmp_name'], $upload_path)) {
|
||
try {
|
||
$stmt = $pdo->prepare("INSERT INTO images (user_id, title, filename, is_public, file_size, mime_type) VALUES (?, ?, ?, ?, ?, ?)");
|
||
$stmt->execute([$_SESSION['user_id'], $title, $filename, $is_public, $file['size'], $file['type']]);
|
||
|
||
$image_id = $pdo->lastInsertId();
|
||
$tagNames = [];
|
||
|
||
|
||
if (!empty($file_tags)) {
|
||
foreach ($file_tags as $tag_id) {
|
||
$stmt = $pdo->prepare("INSERT INTO image_tags (image_id, tag_id) VALUES (?, ?)");
|
||
$stmt->execute([$image_id, $tag_id]);
|
||
}
|
||
|
||
$tagIds = implode(',', array_map('intval', $file_tags));
|
||
$stmt = $pdo->query("SELECT name FROM tags WHERE id IN ($tagIds)");
|
||
$tagNames = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||
}
|
||
|
||
$uploadResults[] = [
|
||
'success' => true,
|
||
'filename' => $file['name'],
|
||
'title' => $title,
|
||
'tags' => $tagNames,
|
||
'url' => SITE_URL . '/uploads/' . $filename,
|
||
'view_url' => SITE_URL . '/view-image.php?id=' . $image_id
|
||
];
|
||
$successCount++;
|
||
|
||
} catch(PDOException $e) {
|
||
unlink($upload_path);
|
||
$uploadResults[] = ['success' => false, 'filename' => $file['name'], 'message' => t('error') . ': ' . $e->getMessage()];
|
||
$errorCount++;
|
||
}
|
||
} else {
|
||
$uploadResults[] = ['success' => false, 'filename' => $file['name'], 'message' => t('upload_failed')];
|
||
$errorCount++;
|
||
}
|
||
}
|
||
|
||
if ($successCount > 0) {
|
||
$success = t('upload_success') . " {$successCount} " . t('images') . ($errorCount > 0 ? ",{$errorCount} " . t('upload_failed') : "");
|
||
}
|
||
if ($errorCount > 0 && $successCount === 0) {
|
||
$error = t('upload_failed');
|
||
}
|
||
}
|
||
?>
|
||
|
||
<!DOCTYPE html>
|
||
<html lang="<?php echo $lang; ?>" data-theme="<?php echo $currentUserSettings['dark_mode'] ? 'dark' : 'light'; ?>">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title><?php echo t('upload_title'); ?> - <?php echo SITE_NAME; ?></title>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||
<link rel="stylesheet" href="css/style.css">
|
||
</head>
|
||
<body>
|
||
<?php include 'components/navbar.php'; ?>
|
||
|
||
<div class="container">
|
||
<div class="upload-container">
|
||
<div class="upload-card card">
|
||
<h2><i class="fas fa-cloud-upload-alt"></i> <?php echo t('batch_upload'); ?></h2>
|
||
|
||
<?php if($error): ?>
|
||
<div class="alert alert-error">
|
||
<i class="fas fa-exclamation-triangle"></i> <?php echo $error; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if($success): ?>
|
||
<div class="alert alert-success">
|
||
<i class="fas fa-check-circle"></i> <?php echo $success; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<form method="POST" action="" enctype="multipart/form-data" id="uploadForm">
|
||
|
||
<div class="upload-area" id="uploadArea">
|
||
<div>
|
||
<div class="feature-icon">
|
||
<i class="fas fa-cloud-upload-alt"></i>
|
||
</div>
|
||
<h3><?php echo t('drag_drop'); ?></h3>
|
||
<p><?php echo t('supported_formats'); ?>: JPG, PNG, GIF, WebP | <?php echo t('max_size'); ?>: 5MB</p>
|
||
<p class="text-muted"><?php echo t('multiple_files'); ?></p>
|
||
</div>
|
||
<input type="file" id="fileInput" name="images[]" multiple accept="image/*" style="display: none;">
|
||
</div>
|
||
|
||
|
||
<div class="batch-actions mt-2" id="batchActions" style="display: none;">
|
||
<button type="button" class="btn btn-secondary" onclick="setAllTitles()">
|
||
<i class="fas fa-heading"></i> <?php echo t('set_all_titles'); ?>
|
||
</button>
|
||
<button type="button" class="btn btn-secondary" onclick="setAllTags()">
|
||
<i class="fas fa-tags"></i> <?php echo t('set_all_tags'); ?>
|
||
</button>
|
||
<button type="button" class="btn btn-danger" onclick="clearAllFiles()">
|
||
<i class="fas fa-trash"></i> <?php echo t('clear_all'); ?>
|
||
</button>
|
||
<span id="fileCount" class="text-muted">0 <?php echo t('files_selected'); ?></span>
|
||
</div>
|
||
|
||
|
||
<div class="file-list" id="fileList"></div>
|
||
|
||
|
||
<div class="form-group mt-2">
|
||
<label class="checkbox-label">
|
||
<input type="checkbox" name="is_public" checked>
|
||
<span class="checkmark"></span>
|
||
<i class="fas fa-globe"></i> <?php echo t('set_public'); ?>
|
||
</label>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary btn-full mt-2" id="uploadButton" style="display: none;">
|
||
<i class="fas fa-upload"></i> <?php echo t('start_upload'); ?>
|
||
</button>
|
||
</form>
|
||
|
||
|
||
<?php if(!empty($uploadResults)): ?>
|
||
<div class="upload-results mt-3">
|
||
<h3><i class="fas fa-list"></i> <?php echo t('upload_results'); ?></h3>
|
||
<?php foreach($uploadResults as $result): ?>
|
||
<div class="result-item <?php echo $result['success'] ? 'alert alert-success' : 'alert alert-error'; ?>">
|
||
<?php if($result['success']): ?>
|
||
<i class="fas fa-check-circle"></i> <strong><?php echo htmlspecialchars($result['filename']); ?></strong> - <?php echo t('upload_success'); ?>
|
||
<br>
|
||
<small>
|
||
<i class="fas fa-heading"></i> <?php echo t('title'); ?>: <?php echo htmlspecialchars($result['title']); ?> |
|
||
<?php if(!empty($result['tags'])): ?>
|
||
<i class="fas fa-tags"></i> <?php echo t('tags'); ?>: <?php echo implode(', ', $result['tags']); ?> |
|
||
<?php endif; ?>
|
||
<a href="<?php echo $result['view_url']; ?>" target="_blank"><i class="fas fa-eye"></i> <?php echo t('view'); ?></a> |
|
||
<a href="#" onclick="copyToClipboard('<?php echo $result['url']; ?>'); return false;"><i class="fas fa-copy"></i> <?php echo t('copy'); ?></a>
|
||
</small>
|
||
<?php else: ?>
|
||
<i class="fas fa-times-circle"></i> <strong><?php echo htmlspecialchars($result['filename']); ?></strong> - <?php echo t('upload_failed'); ?>: <?php echo $result['message']; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
|
||
const allTags = <?php echo json_encode($allTags); ?>;
|
||
let fileCounter = 0;
|
||
const fileList = document.getElementById('fileList');
|
||
const fileInput = document.getElementById('fileInput');
|
||
const uploadArea = document.getElementById('uploadArea');
|
||
const batchActions = document.getElementById('batchActions');
|
||
const uploadButton = document.getElementById('uploadButton');
|
||
const fileCount = document.getElementById('fileCount');
|
||
|
||
|
||
uploadArea.addEventListener('click', () => fileInput.click());
|
||
|
||
uploadArea.addEventListener('dragover', (e) => {
|
||
e.preventDefault();
|
||
uploadArea.classList.add('dragover');
|
||
});
|
||
|
||
uploadArea.addEventListener('dragleave', () => {
|
||
uploadArea.classList.remove('dragover');
|
||
});
|
||
|
||
uploadArea.addEventListener('drop', (e) => {
|
||
e.preventDefault();
|
||
uploadArea.classList.remove('dragover');
|
||
handleFiles(e.dataTransfer.files);
|
||
});
|
||
|
||
fileInput.addEventListener('change', (e) => {
|
||
handleFiles(e.target.files);
|
||
});
|
||
|
||
|
||
function handleFiles(files) {
|
||
for (let file of files) {
|
||
if (file.type.startsWith('image/')) {
|
||
addFileToList(file);
|
||
} else {
|
||
alert(`文件 "${file.name}" 不是图片格式,已跳过。`);
|
||
}
|
||
}
|
||
updateUI();
|
||
}
|
||
|
||
function addFileToList(file) {
|
||
const fileId = 'file_' + fileCounter++;
|
||
const reader = new FileReader();
|
||
|
||
reader.onload = function(e) {
|
||
const fileItem = document.createElement('div');
|
||
fileItem.className = 'file-item';
|
||
fileItem.id = fileId;
|
||
|
||
fileItem.innerHTML = `
|
||
<div class="file-preview">
|
||
<img src="${e.target.result}" alt="预览">
|
||
</div>
|
||
<div class="file-info">
|
||
<div class="file-name">${file.name}</div>
|
||
<div class="file-size">${formatFileSize(file.size)}</div>
|
||
</div>
|
||
<div class="file-metadata">
|
||
<input type="text" class="file-title-input" name="titles[]"
|
||
placeholder="<?php echo t('title'); ?>(<?php echo t('optional'); ?>)"
|
||
value="${file.name.replace(/\.[^/.]+$/, "")}">
|
||
<div class="tags-selector">
|
||
<select name="tags[]" multiple class="tags-select" style="width: 100%; height: 80px; padding: 5px;">
|
||
<option value=""><?php echo t('select_tags'); ?></option>
|
||
${allTags.map(tag =>
|
||
`<option value="${tag.id}">${tag.name}</option>`
|
||
).join('')}
|
||
</select>
|
||
<small><?php echo t('hold_ctrl_multiselect'); ?></small>
|
||
</div>
|
||
</div>
|
||
<button type="button" class="remove-file" onclick="removeFile('${fileId}')">
|
||
<i class="fas fa-times"></i> <?php echo t('delete'); ?>
|
||
</button>
|
||
`;
|
||
|
||
fileList.appendChild(fileItem);
|
||
updateUI();
|
||
};
|
||
|
||
reader.readAsDataURL(file);
|
||
}
|
||
|
||
function removeFile(fileId) {
|
||
const fileItem = document.getElementById(fileId);
|
||
if (fileItem) {
|
||
fileItem.remove();
|
||
updateUI();
|
||
}
|
||
}
|
||
|
||
function clearAllFiles() {
|
||
if (confirm('<?php echo t('confirm_clear_all'); ?>')) {
|
||
fileList.innerHTML = '';
|
||
fileInput.value = '';
|
||
updateUI();
|
||
}
|
||
}
|
||
|
||
function setAllTitles() {
|
||
const title = prompt('<?php echo t('enter_title_for_all'); ?>:');
|
||
if (title !== null) {
|
||
const inputs = document.querySelectorAll('.file-title-input');
|
||
inputs.forEach(input => {
|
||
input.value = title || input.defaultValue;
|
||
});
|
||
}
|
||
}
|
||
|
||
function setAllTags() {
|
||
const selectedTags = prompt('<?php echo t('enter_tag_ids'); ?>:');
|
||
if (selectedTags) {
|
||
const tagInputs = selectedTags.split(',').map(t => t.trim());
|
||
const selects = document.querySelectorAll('.tags-select');
|
||
|
||
selects.forEach(select => {
|
||
Array.from(select.options).forEach(option => {
|
||
option.selected = false;
|
||
});
|
||
|
||
tagInputs.forEach(tagInput => {
|
||
let option = Array.from(select.options).find(opt => opt.value === tagInput);
|
||
if (!option) {
|
||
option = Array.from(select.options).find(opt =>
|
||
opt.text.toLowerCase() === tagInput.toLowerCase()
|
||
);
|
||
}
|
||
if (option) {
|
||
option.selected = true;
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
function updateUI() {
|
||
const fileItems = fileList.querySelectorAll('.file-item');
|
||
const hasFiles = fileItems.length > 0;
|
||
|
||
batchActions.style.display = hasFiles ? 'flex' : 'none';
|
||
uploadButton.style.display = hasFiles ? 'block' : 'none';
|
||
fileCount.textContent = `${fileItems.length} <?php echo t('files_selected'); ?>`;
|
||
}
|
||
|
||
function formatFileSize(bytes) {
|
||
if (bytes >= 1073741824) return (bytes / 1073741824).toFixed(2) + ' GB';
|
||
else if (bytes >= 1048576) return (bytes / 1048576).toFixed(2) + ' MB';
|
||
else if (bytes >= 1024) return (bytes / 1024).toFixed(2) + ' KB';
|
||
else return bytes + ' bytes';
|
||
}
|
||
|
||
function copyToClipboard(text) {
|
||
if (navigator.clipboard) {
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
alert('<?php echo t('copied_to_clipboard'); ?>');
|
||
});
|
||
} else {
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = text;
|
||
document.body.appendChild(textArea);
|
||
textArea.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(textArea);
|
||
alert('<?php echo t('copied_to_clipboard'); ?>');
|
||
}
|
||
}
|
||
|
||
|
||
document.getElementById('uploadForm').addEventListener('submit', function() {
|
||
const uploadButton = document.getElementById('uploadButton');
|
||
uploadButton.disabled = true;
|
||
uploadButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> <?php echo t('uploading'); ?>...';
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|