Files
image-pichost/upload.php

400 lines
16 KiB
PHP
Raw Normal View History

2025-11-30 13:06:45 +00:00
<?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>