上传文件至 /

This commit is contained in:
2025-11-30 13:05:45 +00:00
parent fec1e2c068
commit f2106c2fbf
5 changed files with 1165 additions and 0 deletions

286
about.php Normal file
View File

@@ -0,0 +1,286 @@
<?php
require_once 'config.php';
?>
<!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 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="about-container">
<div class="about-header text-center">
<h1><i class="fas fa-info-circle"></i> 关于 <?php echo SITE_NAME; ?></h1>
<p class="lead">安全、快速、免费的图片托管服务</p>
</div>
<div class="about-content">
<div class="about-section card">
<h2><i class="fas fa-rocket"></i> 我们的使命</h2>
<p>为用户提供简单易用、功能强大的图片托管解决方案,让每个人都能轻松分享和管理自己的图片资源。</p>
</div>
<div class="about-section card">
<h2><i class="fas fa-star"></i> 主要特性</h2>
<div class="features-grid">
<div class="feature-item">
<i class="fas fa-cloud-upload-alt"></i>
<h3>批量上传</h3>
<p>支持多文件同时上传,拖拽操作</p>
</div>
<div class="feature-item">
<i class="fas fa-tags"></i>
<h3>标签分类</h3>
<p>智能标签系统,方便图片管理</p>
</div>
<div class="feature-item">
<i class="fas fa-code"></i>
<h3>API 支持</h3>
<p>完整的 RESTful API 接口</p>
</div>
<div class="feature-item">
<i class="fas fa-palette"></i>
<h3>深色模式</h3>
<p>支持明暗主题切换</p>
</div>
<div class="feature-item">
<i class="fas fa-shield-alt"></i>
<h3>安全可靠</h3>
<p>多重安全验证机制</p>
</div>
<div class="feature-item">
<i class="fas fa-mobile-alt"></i>
<h3>响应式设计</h3>
<p>完美适配各种设备</p>
</div>
</div>
</div>
<div class="about-section card">
<h2><i class="fas fa-cogs"></i> 技术栈</h2>
<div class="tech-stack">
<div class="tech-item">
<i class="fab fa-php"></i>
<span>PHP</span>
</div>
<div class="tech-item">
<i class="fas fa-database"></i>
<span>MySQL</span>
</div>
<div class="tech-item">
<i class="fab fa-js"></i>
<span>JavaScript</span>
</div>
<div class="tech-item">
<i class="fab fa-html5"></i>
<span>HTML5</span>
</div>
<div class="tech-item">
<i class="fab fa-css3-alt"></i>
<span>CSS3</span>
</div>
</div>
</div>
<div class="about-section card">
<h2><i class="fas fa-chart-line"></i> 统计数据</h2>
<div class="stats-grid">
<?php
try {
$stmt = $pdo->query("SELECT COUNT(*) as total FROM users");
$totalUsers = $stmt->fetch(PDO::FETCH_ASSOC)['total'];
$stmt = $pdo->query("SELECT COUNT(*) as total FROM images");
$totalImages = $stmt->fetch(PDO::FETCH_ASSOC)['total'];
$stmt = $pdo->query("SELECT SUM(file_size) as total FROM images");
$totalSize = $stmt->fetch(PDO::FETCH_ASSOC)['total'] ?: 0;
$stmt = $pdo->query("SELECT COUNT(*) as total FROM images WHERE DATE(uploaded_at) = CURDATE()");
$todayImages = $stmt->fetch(PDO::FETCH_ASSOC)['total'];
} catch(PDOException $e) {
$totalUsers = $totalImages = $totalSize = $todayImages = 0;
}
?>
<div class="stat-item">
<div class="stat-number"><?php echo $totalUsers; ?></div>
<div class="stat-label">注册用户</div>
</div>
<div class="stat-item">
<div class="stat-number"><?php echo $totalImages; ?></div>
<div class="stat-label">托管图片</div>
</div>
<div class="stat-item">
<div class="stat-number"><?php echo formatFileSize($totalSize); ?></div>
<div class="stat-label">存储空间</div>
</div>
<div class="stat-item">
<div class="stat-number"><?php echo $todayImages; ?></div>
<div class="stat-label">今日上传</div>
</div>
</div>
</div>
<div class="about-section card">
<h2><i class="fas fa-question-circle"></i> 常见问题</h2>
<div class="faq-list">
<div class="faq-item">
<h3>支持哪些图片格式?</h3>
<p>支持 JPG、PNG、GIF、WebP 格式,单文件最大 5MB。</p>
</div>
<div class="faq-item">
<h3>图片会永久保存吗?</h3>
<p>是的,所有上传的图片都会永久保存,除非用户主动删除。</p>
</div>
<div class="faq-item">
<h3>如何获取 API 密钥?</h3>
<p>登录后在 API 文档页面可以生成和管理您的 API 密钥。</p>
</div>
<div class="faq-item">
<h3>支持外链吗?</h3>
<p>支持,每张图片都提供直接链接,可以嵌入到其他网站。</p>
</div>
</div>
</div>
<div class="about-section card">
<h2><i class="fas fa-envelope"></i> 联系我们</h2>
<div class="contact-info">
<p>如果您有任何问题或建议,欢迎通过以下方式联系我们:</p>
<div class="contact-methods">
<div class="contact-method">
<i class="fas fa-envelope"></i>
<span>邮箱: support@66ghz.com</span>
</div>
<div class="contact-method">
<i class="fas fa-bug"></i>
<span>问题反馈: <a href="feedback.php">反馈页面</a></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.about-container {
max-width: 1000px;
margin: 0 auto;
}
.about-header {
margin-bottom: 40px;
}
.about-header h1 {
color: var(--primary-color);
margin-bottom: 10px;
}
.lead {
font-size: 1.2rem;
color: var(--text-color);
opacity: 0.8;
}
.about-section {
margin-bottom: 30px;
}
.about-section h2 {
color: var(--primary-color);
margin-bottom: 20px;
border-bottom: 2px solid var(--primary-color);
padding-bottom: 10px;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 20px;
}
.feature-item {
text-align: center;
padding: 20px;
}
.feature-item i {
font-size: 2.5rem;
color: var(--primary-color);
margin-bottom: 15px;
}
.feature-item h3 {
margin-bottom: 10px;
color: var(--text-color);
}
.tech-stack {
display: flex;
flex-wrap: wrap;
gap: 15px;
justify-content: center;
}
.tech-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 15px;
background: var(--bg-color);
border-radius: var(--radius);
min-width: 80px;
}
.tech-item i {
font-size: 2rem;
margin-bottom: 8px;
color: var(--primary-color);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
margin-top: 20px;
}
.stat-item {
text-align: center;
padding: 20px;
background: var(--bg-color);
border-radius: var(--radius);
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: var(--primary-color);
margin-bottom: 5px;
}
.faq-list {
margin-top: 20px;
}
.faq-item {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid var(--border-color);
}
.faq-item:last-child {
border-bottom: none;
}
.faq-item h3 {
color: var(--text-color);
margin-bottom: 10px;
}
.contact-methods {
margin-top: 15px;
}
.contact-method {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.contact-method i {
color: var(--primary-color);
width: 20px;
}
</style>
</body>
</html>

152
api-docs.php Normal file
View File

@@ -0,0 +1,152 @@
<?php
require_once 'config.php';
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
try {
$stmt = $pdo->prepare("SELECT api_key FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
} catch(PDOException $e) {
$user = ['api_key' => null];
}
?>
<!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('api_docs'); ?> - <?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">
<h1><i class="fas fa-code"></i> <?php echo t('api_documentation'); ?></h1>
<div class="api-docs-container">
<section class="api-section card">
<h2><i class="fas fa-key"></i> <?php echo t('api_management'); ?></h2>
<?php if(empty($user['api_key'])): ?>
<p><?php echo t('no_api_key_message'); ?></p>
<form method="POST" action="generate-api-key.php">
<button type="submit" class="btn btn-primary">
<i class="fas fa-plus"></i> <?php echo t('generate_api_key'); ?>
</button>
</form>
<?php else: ?>
<div class="api-key-display">
<label><strong><i class="fas fa-key"></i> <?php echo t('your_api_key'); ?></strong></label>
<div class="api-key-input">
<input type="text" value="<?php echo $user['api_key']; ?>" id="apiKey" readonly>
<button onclick="copyApiKey()" class="btn">
<i class="fas fa-copy"></i> <?php echo t('copy'); ?>
</button>
<form method="POST" action="generate-api-key.php" style="display: inline;">
<button type="submit" class="btn btn-secondary" onclick="return confirm('<?php echo t('confirm_regenerate_api_key'); ?>')">
<i class="fas fa-sync"></i> <?php echo t('regenerate_key'); ?>
</button>
</form>
</div>
<small><i class="fas fa-shield-alt"></i> <?php echo t('keep_secret'); ?></small>
</div>
<?php endif; ?>
</section>
<section class="api-section card mt-3">
<h2><i class="fas fa-plug"></i> <?php echo t('api_endpoints'); ?></h2>
<div class="api-endpoint">
<h3><i class="fas fa-cloud-upload-alt"></i> 1. <?php echo t('upload_image'); ?></h3>
<div class="code-block">
<span class="endpoint">POST</span> <?php echo SITE_URL; ?>/api/upload.php<br><br>
<span class="comment">// <?php echo t('headers'); ?></span><br>
Content-Type: multipart/form-data<br><br>
<span class="comment">// <?php echo t('parameters'); ?></span><br>
api_key = <span class="param">string</span> (<?php echo t('required'); ?>)<br>
image = <span class="param">file</span> (<?php echo t('required'); ?>)<br>
title = <span class="param">string</span> (<?php echo t('optional'); ?>)<br>
tags = <span class="param">string</span> (<?php echo t('optional'); ?>)<br>
is_public = <span class="param">integer</span> (<?php echo t('optional'); ?>)<br><br>
<span class="comment">// <?php echo t('success_response'); ?></span><br>
{<br>
&nbsp;&nbsp;"success": true,<br>
&nbsp;&nbsp;"data": {<br>
&nbsp;&nbsp;&nbsp;&nbsp;"id": 123,<br>
&nbsp;&nbsp;&nbsp;&nbsp;"title": "<?php echo t('image_title'); ?>",<br>
&nbsp;&nbsp;&nbsp;&nbsp;"url": "<?php echo SITE_URL; ?>/view-image.php?id=123",<br>
&nbsp;&nbsp;&nbsp;&nbsp;"direct_url": "<?php echo SITE_URL; ?>/uploads/filename.jpg",<br>
&nbsp;&nbsp;&nbsp;&nbsp;"tags": ["<?php echo t('landscape'); ?>", "<?php echo t('nature'); ?>"]<br>
&nbsp;&nbsp;}<br>
}
</div>
</div>
<div class="api-endpoint mt-3">
<h3><i class="fas fa-images"></i> 2. <?php echo t('get_images'); ?></h3>
<div class="code-block">
<span class="endpoint">GET</span> <?php echo SITE_URL; ?>/api/images.php?api_key=<?php echo $user['api_key'] ?? 'YOUR_API_KEY'; ?>&page=1&limit=20<br><br>
<span class="comment">// <?php echo t('parameters'); ?></span><br>
api_key = <span class="param">string</span> (<?php echo t('required'); ?>)<br>
page = <span class="param">integer</span> (<?php echo t('optional'); ?>)<br>
limit = <span class="param">integer</span> (<?php echo t('optional'); ?>)<br><br>
<span class="comment">// <?php echo t('success_response'); ?></span><br>
{<br>
&nbsp;&nbsp;"success": true,<br>
&nbsp;&nbsp;"data": [<br>
&nbsp;&nbsp;&nbsp;&nbsp;{<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"id": 123,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"title": "<?php echo t('image_title'); ?>",<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"url": "<?php echo SITE_URL; ?>/view-image.php?id=123",<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"direct_url": "<?php echo SITE_URL; ?>/uploads/filename.jpg",<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"is_public": 1,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"views": 45,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"file_size_formatted": "2.5 MB"<br>
&nbsp;&nbsp;&nbsp;&nbsp;}<br>
&nbsp;&nbsp;],<br>
&nbsp;&nbsp;"pagination": {<br>
&nbsp;&nbsp;&nbsp;&nbsp;"page": 1,<br>
&nbsp;&nbsp;&nbsp;&nbsp;"limit": 20,<br>
&nbsp;&nbsp;&nbsp;&nbsp;"total": 150,<br>
&nbsp;&nbsp;&nbsp;&nbsp;"pages": 8<br>
&nbsp;&nbsp;}<br>
}
</div>
</div>
<div class="api-endpoint mt-3">
<h3><i class="fas fa-terminal"></i> 3. <?php echo t('curl_examples'); ?></h3>
<div class="code-block">
<span class="comment"># <?php echo t('upload_image'); ?></span><br>
curl -X POST \<br>
&nbsp;&nbsp;-F "api_key=<?php echo $user['api_key'] ?? 'YOUR_API_KEY'; ?>" \<br>
&nbsp;&nbsp;-F "title=<?php echo t('my_image'); ?>" \<br>
&nbsp;&nbsp;-F "is_public=1" \<br>
&nbsp;&nbsp;-F "image=@/path/to/your/image.jpg" \<br>
&nbsp;&nbsp;"<?php echo SITE_URL; ?>/api/upload.php"
</div>
<div class="code-block mt-2">
<span class="comment"># <?php echo t('get_images'); ?></span><br>
curl "<?php echo SITE_URL; ?>/api/images.php?api_key=<?php echo $user['api_key'] ?? 'YOUR_API_KEY'; ?>&page=1&limit=10"
</div>
</div>
</section>
</div>
</div>
<script>
function copyApiKey() {
const apiKey = document.getElementById('apiKey');
apiKey.select();
document.execCommand('copy');
alert('<?php echo t('api_key_copied'); ?>');
}
</script>
</body>
</html>

43
captcha.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
session_start();
header('Content-type: image/png');
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
$width = 120;
$height = 40;
$chars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
$code = '';
for ($i = 0; $i < 4; $i++) {
$code .= $chars[rand(0, strlen($chars) - 1)];
}
$_SESSION['captcha'] = $code;
$image = imagecreate($width, $height);
$bg_color = imagecolorallocate($image, 255, 255, 255);
$text_color = imagecolorallocate($image, 0, 0, 0);
$noise_color = imagecolorallocate($image, 200, 200, 200);
for ($i = 0; $i < 100; $i++) {
imagesetpixel($image, rand(0, $width), rand(0, $height), $noise_color);
}
for ($i = 0; $i < 5; $i++) {
imageline($image, rand(0, $width), rand(0, $height), rand(0, $width), rand(0, $height), $noise_color);
}
$font = 5;
$x = 10;
for ($i = 0; $i < 4; $i++) {
$char_color = imagecolorallocate($image, rand(0, 150), rand(0, 150), rand(0, 150));
imagestring($image, $font, $x, rand(8, 12), $code[$i], $char_color);
$x += 25;
}
imagepng($image);
imagedestroy($image);
?>

402
config.php Normal file
View File

@@ -0,0 +1,402 @@
<?php
session_start();
error_reporting(E_ALL);
ini_set('display_errors', 1);
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
require_once 'phpmailer/src/Exception.php';
require_once 'phpmailer/src/PHPMailer.php';
require_once 'phpmailer/src/SMTP.php';
define('DB_HOST', 'HOST');
define('DB_NAME', 'NAME');
define('DB_USER', 'USER');
define('DB_PASS', 'PASS');
define('SITE_URL', 'WEB');
define('SITE_NAME', 'SITE');
define('MAX_FILE_SIZE', 5 * 1024 * 1024);
define('ALLOWED_TYPES', ['jpg', 'jpeg', 'png', 'gif', 'webp']);
define('API_KEY_LENGTH', 32);
define('SMTP_HOST', 'EHOST');
define('SMTP_PORT', NUM);
define('SMTP_USERNAME', 'EUSER');
define('SMTP_PASSWORD', 'PASS');
define('SMTP_FROM_EMAIL', 'FUSER');
define('SMTP_FROM_NAME', 'FNAME');
define('SMTP_SECURE', 'SECURE');
define('SMTP_DEBUG', false);
define('BING_API_URL', 'https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN');
define('CAPTCHA_ENABLED', true);
define('PASSWORD_RESET_EXPIRE', 3600);
define('DEFAULT_LANGUAGE', 'zh-CN');
$supported_languages = ['zh-CN', 'en'];
try {
$pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
die("数据库连接失败: " . $e->getMessage());
}
if (!file_exists('uploads')) {
mkdir('uploads', 0755, true);
file_put_contents('uploads/index.html', '');
file_put_contents('uploads/.htaccess', 'Deny from all');
}
if (!file_exists('languages')) mkdir('languages', 0755, true);
if (!file_exists('admin')) mkdir('admin', 0755, true);
if (!file_exists('components')) mkdir('components', 0755, true);
if (!file_exists('api')) mkdir('api', 0755, true);
function getLanguage() {
global $supported_languages;
if (isset($_SESSION['language'])) {
return $_SESSION['language'];
}
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$browser_lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
if ($browser_lang === 'zh') return 'zh-CN';
if ($browser_lang === 'en') return 'en';
}
return DEFAULT_LANGUAGE;
}
function loadLanguage($lang) {
$lang_file = "languages/{$lang}.php";
if (file_exists($lang_file)) {
return include $lang_file;
}
return include "languages/" . DEFAULT_LANGUAGE . ".php";
}
$lang = getLanguage();
$translations = loadLanguage($lang);
$_SESSION['language'] = $lang;
function t($key) {
global $translations;
return $translations[$key] ?? $key;
}
function getUserSettings($user_id) {
global $pdo;
$stmt = $pdo->prepare("SELECT * FROM user_settings WHERE user_id = ?");
$stmt->execute([$user_id]);
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$settings) {
$stmt = $pdo->prepare("INSERT INTO user_settings (user_id) VALUES (?)");
$stmt->execute([$user_id]);
return [
'dark_mode' => false,
'language' => 'zh-CN',
'items_per_page' => 20,
'email_notifications' => true,
'browser_notifications' => true
];
}
return $settings;
}
function updateUserSettings($user_id, $settings) {
global $pdo;
$stmt = $pdo->prepare("INSERT INTO user_settings (user_id, dark_mode, language, items_per_page, email_notifications, browser_notifications)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
dark_mode = VALUES(dark_mode),
language = VALUES(language),
items_per_page = VALUES(items_per_page),
email_notifications = VALUES(email_notifications),
browser_notifications = VALUES(browser_notifications)");
return $stmt->execute([
$user_id,
$settings['dark_mode'] ? 1 : 0,
$settings['language'],
$settings['items_per_page'],
$settings['email_notifications'] ? 1 : 0,
$settings['browser_notifications'] ? 1 : 0
]);
}
function getUserNotificationSettings($user_id) {
global $pdo;
$stmt = $pdo->prepare("SELECT nt.name, uns.enabled FROM user_notification_settings uns
JOIN notification_types nt ON uns.notification_type_id = nt.id
WHERE uns.user_id = ?");
$stmt->execute([$user_id]);
$settings = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
if (empty($settings)) {
initUserNotificationSettings($user_id);
return getUserNotificationSettings($user_id);
}
return $settings;
}
function initUserNotificationSettings($user_id) {
global $pdo;
$stmt = $pdo->prepare("INSERT INTO user_notification_settings (user_id, notification_type_id, enabled)
SELECT ?, id, TRUE FROM notification_types");
return $stmt->execute([$user_id]);
}
function updateUserNotificationSettings($user_id, $settings) {
global $pdo;
foreach ($settings as $type_id => $enabled) {
$stmt = $pdo->prepare("INSERT INTO user_notification_settings (user_id, notification_type_id, enabled)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE enabled = VALUES(enabled)");
$stmt->execute([$user_id, $type_id, $enabled ? 1 : 0]);
}
return true;
}
function sendNotification($user_id, $type_name, $title, $message, $related_url = null) {
global $pdo;
$stmt = $pdo->prepare("SELECT uns.enabled FROM user_notification_settings uns
JOIN notification_types nt ON uns.notification_type_id = nt.id
WHERE uns.user_id = ? AND nt.name = ?");
$stmt->execute([$user_id, $type_name]);
$setting = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$setting || !$setting['enabled']) return false;
$stmt = $pdo->prepare("SELECT id FROM notification_types WHERE name = ?");
$stmt->execute([$type_name]);
$type = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$type) return false;
$stmt = $pdo->prepare("INSERT INTO notifications (user_id, type_id, title, message, related_url)
VALUES (?, ?, ?, ?, ?)");
return $stmt->execute([$user_id, $type['id'], $title, $message, $related_url]);
}
function getUnreadNotificationCount($user_id) {
global $pdo;
$stmt = $pdo->prepare("SELECT COUNT(*) as count FROM notifications WHERE user_id = ? AND is_read = FALSE");
$stmt->execute([$user_id]);
return $stmt->fetch(PDO::FETCH_ASSOC)['count'];
}
function getUserNotifications($user_id, $limit = 10) {
global $pdo;
$stmt = $pdo->prepare("SELECT n.*, nt.name as type_name FROM notifications n
JOIN notification_types nt ON n.type_id = nt.id
WHERE n.user_id = ?
ORDER BY n.created_at DESC
LIMIT ?");
$stmt->bindValue(1, $user_id, PDO::PARAM_INT);
$stmt->bindValue(2, $limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
function generateRandomString($length = 10) {
return substr(str_shuffle(str_repeat($x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
ceil($length / strlen($x)))), 1, $length);
}
function formatFileSize($bytes) {
if ($bytes >= 1073741824) return number_format($bytes / 1073741824, 2) . ' GB';
elseif ($bytes >= 1048576) return number_format($bytes / 1048576, 2) . ' MB';
elseif ($bytes >= 1024) return number_format($bytes / 1024, 2) . ' KB';
else return $bytes . ' bytes';
}
function getBingDailyImage() {
$response = @file_get_contents(BING_API_URL);
if ($response) {
$data = json_decode($response, true);
if ($data && isset($data['images'][0])) {
$image = $data['images'][0];
return [
'url' => 'https://www.bing.com' . $image['url'],
'title' => $image['title'],
'copyright' => $image['copyright'],
'copyrightlink' => $image['copyrightlink']
];
}
}
return null;
}
function sendEmail($to, $subject, $message) {
$mail = new PHPMailer(true);
try {
// 服务器设置
$mail->isSMTP();
$mail->Host = SMTP_HOST;
$mail->SMTPAuth = true;
$mail->Username = SMTP_USERNAME;
$mail->Password = SMTP_PASSWORD;
$mail->SMTPSecure = SMTP_SECURE;
$mail->Port = SMTP_PORT;
if (SMTP_DEBUG) {
$mail->SMTPDebug = SMTP::DEBUG_SERVER;
}
// 收件人
$mail->setFrom(SMTP_FROM_EMAIL, SMTP_FROM_NAME);
$mail->addAddress($to);
// 内容
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body = $message;
$mail->AltBody = strip_tags($message);
return $mail->send();
} catch (Exception $e) {
error_log("邮件发送失败: " . $mail->ErrorInfo);
return false;
}
}
function sendVerificationEmail($email, $username, $verification_code) {
$verification_link = SITE_URL . "/verify.php?code=" . $verification_code;
$subject = SITE_NAME . " - 邮箱验证";
$message = "
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #3498db; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; background: #f9f9f9; }
.button { display: inline-block; padding: 12px 24px; background: #3498db; color: white; text-decoration: none; border-radius: 5px; }
.footer { text-align: center; padding: 20px; color: #666; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
<h1>" . SITE_NAME . "</h1>
</div>
<div class='content'>
<h2>亲爱的 {$username}</h2>
<p>感谢您注册" . SITE_NAME . "图床!</p>
<p>请点击下面的按钮验证您的邮箱地址:</p>
<p style='text-align: center;'>
<a href='{$verification_link}' class='button'>验证邮箱</a>
</p>
<p>如果按钮无法点击,请复制以下链接到浏览器地址栏:</p>
<p><small>{$verification_link}</small></p>
<p>此验证链接 24 小时内有效。</p>
</div>
<div class='footer'>
<p>&copy; " . date('Y') . " " . SITE_NAME . ". 保留所有权利.</p>
</div>
</div>
</body>
</html>";
return sendEmail($email, $subject, $message);
}
function sendPasswordResetEmail($email, $username, $token) {
$reset_link = SITE_URL . "/reset-password.php?token=" . $token;
$subject = SITE_NAME . " - 密码重置";
$message = "
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #e74c3c; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; background: #f9f9f9; }
.button { display: inline-block; padding: 12px 24px; background: #e74c3c; color: white; text-decoration: none; border-radius: 5px; }
.footer { text-align: center; padding: 20px; color: #666; }
.warning { background: #fff3cd; border: 1px solid #ffeaa7; padding: 10px; border-radius: 5px; margin: 15px 0; }
</style>
</head>
<body>
<div class='container'>
<div class='header'>
<h1>" . SITE_NAME . " - 密码重置</h1>
</div>
<div class='content'>
<h2>亲爱的 {$username}</h2>
<p>我们收到了您重置密码的请求。</p>
<p>请点击下面的按钮重置您的密码:</p>
<p style='text-align: center;'>
<a href='{$reset_link}' class='button'>重置密码</a>
</p>
<p>如果按钮无法点击,请复制以下链接到浏览器地址栏:</p>
<p><small>{$reset_link}</small></p>
<div class='warning'>
<strong>注意:</strong>此链接在 <font color='red'>1</font> 小时内有效。如果您没有请求重置密码,请忽略此邮件。
</div>
</div>
<div class='footer'>
<p>&copy; " . date('Y') . " " . SITE_NAME . ". 保留所有权利.</p>
</div>
</div>
</body>
</html>";
return sendEmail($email, $subject, $message);
}
function sendEmailNotification($user_id, $subject, $message) {
global $pdo;
// 获取用户邮箱和设置
$stmt = $pdo->prepare("
SELECT u.email, us.email_notifications
FROM users u
LEFT JOIN user_settings us ON u.id = us.user_id
WHERE u.id = ?
");
$stmt->execute([$user_id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user || !$user['email_notifications']) {
return false;
}
return sendEmail($user['email'], $subject, $message);
}
$currentUserSettings = isset($_SESSION['user_id']) ? getUserSettings($_SESSION['user_id']) : [
'dark_mode' => false,
'language' => $lang,
'items_per_page' => 20,
'email_notifications' => true,
'browser_notifications' => true
];
if (!isset($_SESSION['user_id'])) {
$currentUserSettings['dark_mode'] = isset($_COOKIE['dark_mode']) ? (bool)$_COOKIE['dark_mode'] : false;
$currentUserSettings['language'] = $lang;
}
?>

282
dashboard.php Normal file
View File

@@ -0,0 +1,282 @@
<?php
require_once 'config.php';
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
try {
$stmt = $pdo->prepare("SELECT * FROM images WHERE user_id = ? ORDER BY uploaded_at DESC");
$stmt->execute([$_SESSION['user_id']]);
$userImages = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch(PDOException $e) {
$userImages = [];
}
try {
$stmt = $pdo->prepare("SELECT api_key FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
} catch(PDOException $e) {
$user = ['api_key' => null];
}
$success = $_SESSION['success'] ?? '';
$error = $_SESSION['error'] ?? '';
unset($_SESSION['success'], $_SESSION['error']);
try {
$stmt = $pdo->prepare("SELECT COUNT(*) as total FROM images WHERE user_id = ?");
$stmt->execute([$_SESSION['user_id']]);
$totalImages = $stmt->fetch(PDO::FETCH_ASSOC)['total'];
$stmt = $pdo->prepare("SELECT COUNT(*) as public FROM images WHERE user_id = ? AND is_public = 1");
$stmt->execute([$_SESSION['user_id']]);
$publicImages = $stmt->fetch(PDO::FETCH_ASSOC)['public'];
$stmt = $pdo->prepare("SELECT SUM(views) as total_views FROM images WHERE user_id = ?");
$stmt->execute([$_SESSION['user_id']]);
$totalViews = $stmt->fetch(PDO::FETCH_ASSOC)['total_views'] ?: 0;
$stmt = $pdo->prepare("SELECT SUM(file_size) as total_size FROM images WHERE user_id = ?");
$stmt->execute([$_SESSION['user_id']]);
$totalSize = $stmt->fetch(PDO::FETCH_ASSOC)['total_size'] ?: 0;
} catch(PDOException $e) {
$totalImages = $publicImages = $totalViews = $totalSize = 0;
}
?>
<!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('dashboard'); ?> - <?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="stats-grid">
<div class="stat-card card users">
<div class="stat-number"><?php echo $totalImages; ?></div>
<div class="stat-label"><?php echo t('total_images'); ?></div>
<small><?php echo t('public_images'); ?>: <?php echo $publicImages; ?></small>
</div>
<div class="stat-card card images">
<div class="stat-number"><?php echo $publicImages; ?></div>
<div class="stat-label"><?php echo t('public_images'); ?></div>
<small><?php echo t('private'); ?>: <?php echo $totalImages - $publicImages; ?></small>
</div>
<div class="stat-card card storage">
<div class="stat-number"><?php echo $totalViews; ?></div>
<div class="stat-label"><?php echo t('total_views'); ?></div>
<small><?php echo t('total_engagement'); ?></small>
</div>
<div class="stat-card card feedbacks">
<div class="stat-number"><?php echo formatFileSize($totalSize); ?></div>
<div class="stat-label"><?php echo t('storage_used'); ?></div>
<small><?php echo t('total_size'); ?></small>
</div>
</div>
<div class="dashboard-header">
<h1><i class="fas fa-th-large"></i> <?php echo t('my_gallery'); ?></h1>
<a href="upload.php" class="btn btn-primary">
<i class="fas fa-cloud-upload-alt"></i> <?php echo t('upload_new'); ?>
</a>
</div>
<?php if($success): ?>
<div class="alert alert-success">
<i class="fas fa-check-circle"></i> <?php echo $success; ?>
</div>
<?php endif; ?>
<?php if($error): ?>
<div class="alert alert-error">
<i class="fas fa-exclamation-triangle"></i> <?php echo $error; ?>
</div>
<?php endif; ?>
<div class="gallery-actions">
<div class="search-box">
<input type="text" id="searchInput" placeholder="<?php echo t('search_placeholder'); ?>" onkeyup="searchImages()">
<select id="filterSelect" onchange="filterImages()">
<option value="all"><?php echo t('all_images'); ?></option>
<option value="public"><?php echo t('public'); ?></option>
<option value="private"><?php echo t('private'); ?></option>
</select>
</div>
<div>
<span><?php echo t('sort'); ?></span>
<select id="sortSelect" onchange="sortImages()">
<option value="newest"><?php echo t('sort_newest'); ?></option>
<option value="oldest"><?php echo t('sort_oldest'); ?></option>
<option value="views"><?php echo t('sort_views'); ?></option>
<option value="name"><?php echo t('sort_name'); ?></option>
</select>
</div>
</div>
<?php if(empty($userImages)): ?>
<div class="empty-state card">
<h3><i class="fas fa-images"></i> <?php echo t('no_images'); ?></h3>
<p><?php echo t('upload_first'); ?></p>
<a href="upload.php" class="btn btn-primary">
<i class="fas fa-cloud-upload-alt"></i> <?php echo t('upload'); ?>
</a>
</div>
<?php else: ?>
<div class="gallery" id="imageGallery">
<?php foreach($userImages as $image): ?>
<div class="gallery-item card" data-title="<?php echo htmlspecialchars($image['title'] ?: ''); ?>" data-public="<?php echo $image['is_public']; ?>" data-views="<?php echo $image['views']; ?>">
<a href="view-image.php?id=<?php echo $image['id']; ?>">
<img src="uploads/<?php echo $image['filename']; ?>"
alt="<?php echo htmlspecialchars($image['title'] ?: t('untitled')); ?>"
loading="lazy">
</a>
<div class="image-info">
<div class="image-title">
<?php echo htmlspecialchars($image['title'] ?: t('untitled')); ?>
</div>
<div class="image-meta">
<small><i class="fas fa-calendar"></i> <?php echo date('m-d H:i', strtotime($image['uploaded_at'])); ?></small>
<small><i class="fas fa-eye"></i> <?php echo $image['views']; ?></small>
<small><i class="fas fa-weight"></i> <?php echo formatFileSize($image['file_size'] ?? 0); ?></small>
</div>
<div class="image-actions">
<span class="status-badge <?php echo $image['is_public'] ? 'status-public' : 'status-private'; ?>">
<?php echo $image['is_public'] ? t('public') : t('private'); ?>
</span>
<div class="action-buttons">
<button class="btn btn-sm"
onclick="copyToClipboard('<?php echo SITE_URL . '/uploads/' . $image['filename']; ?>')">
<i class="fas fa-copy"></i> <?php echo t('copy'); ?>
</button>
<a href="view-image.php?id=<?php echo $image['id']; ?>" class="btn btn-sm">
<i class="fas fa-eye"></i> <?php echo t('view'); ?>
</a>
<a href="delete-image.php?id=<?php echo $image['id']; ?>"
class="btn btn-sm btn-danger"
onclick="return confirm('<?php echo t('confirm_delete_image'); ?>')">
<i class="fas fa-trash"></i> <?php echo t('delete'); ?>
</a>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="api-section mt-3">
<h2><i class="fas fa-code"></i> <?php echo t('api_management'); ?></h2>
<div class="api-card card">
<p><?php echo t('api_migrated_message'); ?></p>
<a href="api-docs.php" class="btn btn-primary">
<i class="fas fa-book"></i> <?php echo t('view_api_docs'); ?>
</a>
<?php if(!empty($user['api_key'])): ?>
<div class="mt-2">
<label><strong><i class="fas fa-key"></i> <?php echo t('your_api_key'); ?></strong></label>
<div class="api-key-input">
<input type="text" value="<?php echo $user['api_key']; ?>" id="apiKey" readonly>
<button onclick="copyApiKey()" class="btn">
<i class="fas fa-copy"></i> <?php echo t('copy'); ?>
</button>
</div>
<small><?php echo t('keep_secret'); ?></small>
</div>
<?php endif; ?>
</div>
</div>
</div>
<script>
function copyApiKey() {
const apiKey = document.getElementById('apiKey');
apiKey.select();
document.execCommand('copy');
alert('<?php echo t('api_key_copied'); ?>');
}
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'); ?>');
}
}
function searchImages() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const items = document.querySelectorAll('.gallery-item');
items.forEach(item => {
const title = item.getAttribute('data-title').toLowerCase();
if (title.includes(searchTerm)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
}
function filterImages() {
const filter = document.getElementById('filterSelect').value;
const items = document.querySelectorAll('.gallery-item');
items.forEach(item => {
const isPublic = item.getAttribute('data-public');
if (filter === 'all' ||
(filter === 'public' && isPublic === '1') ||
(filter === 'private' && isPublic === '0')) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
}
function sortImages() {
const sortBy = document.getElementById('sortSelect').value;
const gallery = document.getElementById('imageGallery');
const items = Array.from(document.querySelectorAll('.gallery-item'));
items.sort((a, b) => {
switch(sortBy) {
case 'newest':
return 0;
case 'oldest':
return 1;
case 'views':
const viewsA = parseInt(a.getAttribute('data-views'));
const viewsB = parseInt(b.getAttribute('data-views'));
return viewsB - viewsA;
case 'name':
const nameA = a.getAttribute('data-title').toLowerCase();
const nameB = b.getAttribute('data-title').toLowerCase();
return nameA.localeCompare(nameB);
default:
return 0;
}
});
items.forEach(item => gallery.appendChild(item));
}
</script>
</body>
</html>