Files
image-pichost/upload.php
2025-11-30 13:06:45 +00:00

400 lines
16 KiB
PHP
Raw Permalink 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
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>