Add multi-user support with 24-hour image expiration
Implemented complete session management system for multiple users:
**New Features:**
- Isolated user sessions with unique session IDs
- File-based image storage (not in PHP session)
- Automatic 24-hour image expiration
- Session directories: uploads/sessions/{session_id}/
- Images stored with metadata (creation time, expiry, MIME type)
**New Files:**
- session_manager.php - Complete session management class
- cleanup.php - Cron script to delete expired images
- uploads/.htaccess - Security: prevent direct file access
**Updated Files:**
- api.php - Uses SessionManager for file-based storage
- index.php - Loads images from disk via SessionManager
- .gitignore - Exclude user uploads from repository
**Usage:**
- Each user gets isolated session automatically
- Images auto-delete after 24 hours
- Run cleanup.php via cron: `0 * * * * php cleanup.php`
**Security:**
- Session IDs regenerated on first access
- Upload directory protected by .htaccess
- User images isolated by session
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bf5fa7fd48
commit
7d1cd03a32
6 changed files with 448 additions and 79 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -4,6 +4,10 @@ config.php
|
|||
# PHP session files
|
||||
sessions/
|
||||
|
||||
# User uploaded images (24-hour storage)
|
||||
uploads/sessions/
|
||||
!uploads/.htaccess
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
|
|
|
|||
65
api.php
65
api.php
|
|
@ -1,9 +1,12 @@
|
|||
<?php
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Load configuration
|
||||
// Load configuration and session manager
|
||||
require_once 'config.php';
|
||||
require_once 'session_manager.php';
|
||||
|
||||
// Initialize session manager for multi-user support
|
||||
$sessionManager = new SessionManager();
|
||||
|
||||
class NanoBananaProAPI {
|
||||
private $apiKey;
|
||||
|
|
@ -184,10 +187,7 @@ try {
|
|||
|
||||
// Handle reset action
|
||||
if ($action === 'reset') {
|
||||
$_SESSION['conversation_history'] = [];
|
||||
$_SESSION['current_image'] = null;
|
||||
$_SESSION['current_image_mime'] = 'image/png';
|
||||
$_SESSION['image_history'] = [];
|
||||
$sessionManager->reset();
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
|
|
@ -207,13 +207,10 @@ try {
|
|||
}
|
||||
|
||||
// Handle uploaded image
|
||||
if ($uploadedImage && !$_SESSION['current_image']) {
|
||||
$currentImage = $sessionManager->getCurrentImage();
|
||||
if ($uploadedImage && !$currentImage) {
|
||||
error_log("Processing uploaded image (type: $uploadedImageType)");
|
||||
|
||||
// Store the uploaded image directly in session
|
||||
$_SESSION['current_image'] = $uploadedImage;
|
||||
$_SESSION['current_image_mime'] = $uploadedImageType;
|
||||
|
||||
// If there's a prompt, apply it to the uploaded image
|
||||
if ($prompt) {
|
||||
error_log("Applying prompt to uploaded image: $prompt");
|
||||
|
|
@ -225,23 +222,19 @@ try {
|
|||
$response = $api->generateImage($prompt, $aspectRatio, $imageSize, $uploadedImage);
|
||||
$imageData = $api->extractImageData($response);
|
||||
|
||||
// Update session with the edited image
|
||||
$_SESSION['current_image'] = $imageData['base64'];
|
||||
$_SESSION['current_image_mime'] = $imageData['mime_type'];
|
||||
// Save edited image to disk
|
||||
$filename = $sessionManager->saveImage($imageData['base64'], $imageData['mime_type']);
|
||||
$sessionManager->setCurrentImage($filename, $imageData['mime_type']);
|
||||
|
||||
// Add to conversation history
|
||||
$_SESSION['conversation_history'][] = [
|
||||
'prompt' => 'Uploaded image + ' . $prompt,
|
||||
'timestamp' => time(),
|
||||
'type' => 'upload_edit'
|
||||
];
|
||||
$sessionManager->addToHistory('Uploaded image + ' . $prompt, 'upload_edit');
|
||||
} else {
|
||||
// Just uploaded, no prompt yet
|
||||
$_SESSION['conversation_history'][] = [
|
||||
'prompt' => 'Image uploaded',
|
||||
'timestamp' => time(),
|
||||
'type' => 'upload'
|
||||
];
|
||||
// Just uploaded, no prompt yet - save to disk
|
||||
$filename = $sessionManager->saveImage($uploadedImage, $uploadedImageType);
|
||||
$sessionManager->setCurrentImage($filename, $uploadedImageType);
|
||||
|
||||
// Add to conversation history
|
||||
$sessionManager->addToHistory('Image uploaded', 'upload');
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
|
|
@ -260,29 +253,19 @@ try {
|
|||
$api = new NanoBananaProAPI(GEMINI_API_KEY);
|
||||
|
||||
// Get current image if editing
|
||||
$inputImage = $_SESSION['current_image'] ?? null;
|
||||
$currentImage = $sessionManager->getCurrentImage();
|
||||
$inputImage = $currentImage ? $currentImage['data'] : null;
|
||||
|
||||
// Generate or edit image
|
||||
$response = $api->generateImage($prompt, $aspectRatio, $imageSize, $inputImage);
|
||||
$imageData = $api->extractImageData($response);
|
||||
|
||||
// Save to session
|
||||
$_SESSION['current_image'] = $imageData['base64'];
|
||||
$_SESSION['current_image_mime'] = $imageData['mime_type'];
|
||||
// Save to disk
|
||||
$filename = $sessionManager->saveImage($imageData['base64'], $imageData['mime_type']);
|
||||
$sessionManager->setCurrentImage($filename, $imageData['mime_type']);
|
||||
|
||||
// Add to conversation history
|
||||
$_SESSION['conversation_history'][] = [
|
||||
'prompt' => $prompt,
|
||||
'timestamp' => time(),
|
||||
'type' => $inputImage ? 'edit' : 'generate'
|
||||
];
|
||||
|
||||
// Add to image history
|
||||
$_SESSION['image_history'][] = [
|
||||
'image' => $imageData['base64'],
|
||||
'prompt' => $prompt,
|
||||
'timestamp' => time()
|
||||
];
|
||||
$sessionManager->addToHistory($prompt, $inputImage ? 'edit' : 'generate');
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
|
|
|
|||
64
cleanup.php
Normal file
64
cleanup.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
/**
|
||||
* Cleanup Script for Expired Images
|
||||
* Run this via cron every hour to delete images older than 24 hours
|
||||
*
|
||||
* Cron example (runs every hour):
|
||||
* 0 * * * * /usr/bin/php /path/to/cleanup.php >> /path/to/cleanup.log 2>&1
|
||||
*/
|
||||
|
||||
require_once 'session_manager.php';
|
||||
|
||||
// Check if running from command line or web
|
||||
$isCLI = php_sapi_name() === 'cli';
|
||||
|
||||
if (!$isCLI) {
|
||||
// If accessed via web, require authentication or disable
|
||||
// For now, we'll allow it but you should add authentication in production
|
||||
header('Content-Type: application/json');
|
||||
}
|
||||
|
||||
try {
|
||||
$result = SessionManager::cleanupExpiredImages();
|
||||
|
||||
$output = [
|
||||
'success' => true,
|
||||
'message' => "Cleanup completed successfully",
|
||||
'cleaned_images' => $result['cleaned'],
|
||||
'errors' => $result['errors'],
|
||||
'timestamp' => $result['timestamp']
|
||||
];
|
||||
|
||||
if ($isCLI) {
|
||||
echo "=== Image Cleanup Report ===\n";
|
||||
echo "Timestamp: {$result['timestamp']}\n";
|
||||
echo "Images cleaned: {$result['cleaned']}\n";
|
||||
if (!empty($result['errors'])) {
|
||||
echo "Errors encountered: " . count($result['errors']) . "\n";
|
||||
foreach ($result['errors'] as $error) {
|
||||
echo " - $error\n";
|
||||
}
|
||||
}
|
||||
echo "===========================\n\n";
|
||||
} else {
|
||||
echo json_encode($output, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
exit(0);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$output = [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
if ($isCLI) {
|
||||
echo "ERROR: {$e->getMessage()}\n";
|
||||
exit(1);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode($output, JSON_PRETTY_PRINT);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
69
index.php
69
index.php
|
|
@ -1,19 +1,11 @@
|
|||
<?php
|
||||
session_start();
|
||||
// Initialize session manager for multi-user support
|
||||
require_once 'session_manager.php';
|
||||
$sessionManager = new SessionManager();
|
||||
|
||||
// Initialize session variables if not set
|
||||
if (!isset($_SESSION['conversation_history'])) {
|
||||
$_SESSION['conversation_history'] = [];
|
||||
}
|
||||
if (!isset($_SESSION['current_image'])) {
|
||||
$_SESSION['current_image'] = null;
|
||||
}
|
||||
if (!isset($_SESSION['current_image_mime'])) {
|
||||
$_SESSION['current_image_mime'] = 'image/png';
|
||||
}
|
||||
if (!isset($_SESSION['image_history'])) {
|
||||
$_SESSION['image_history'] = [];
|
||||
}
|
||||
// Get current image from disk
|
||||
$currentImage = $sessionManager->getCurrentImage();
|
||||
$conversationHistory = $sessionManager->getHistory();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
|
@ -774,7 +766,7 @@ if (!isset($_SESSION['image_history'])) {
|
|||
<h2>Create or Edit Image</h2>
|
||||
|
||||
<form id="imageForm">
|
||||
<?php if (!$_SESSION['current_image']): ?>
|
||||
<?php if (!$currentImage): ?>
|
||||
<div class="form-group">
|
||||
<label for="uploadImage">Upload Your Own Image (Optional)</label>
|
||||
<input
|
||||
|
|
@ -791,11 +783,11 @@ if (!isset($_SESSION['image_history'])) {
|
|||
<?php endif; ?>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="prompt">Prompt <?php echo !$_SESSION['current_image'] ? '(Optional if uploading)' : ''; ?></label>
|
||||
<label for="prompt">Prompt <?php echo !$currentImage ? '(Optional if uploading)' : ''; ?></label>
|
||||
<textarea
|
||||
id="prompt"
|
||||
name="prompt"
|
||||
placeholder="<?php echo $_SESSION['current_image'] ? 'Enter edit instructions (e.g., "make it red", "add sunset", "remove background")' : 'Describe the image you want to create or edit... Leave empty to just upload. Examples: "A cyberpunk city at night with neon signs", "Make this photo look like a painting"'; ?>"
|
||||
placeholder="<?php echo $currentImage ? 'Enter edit instructions (e.g., "make it red", "add sunset", "remove background")' : 'Describe the image you want to create or edit... Leave empty to just upload. Examples: "A cyberpunk city at night with neon signs", "Make this photo look like a painting"'; ?>"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
|
|
@ -822,17 +814,17 @@ if (!isset($_SESSION['image_history'])) {
|
|||
</div>
|
||||
|
||||
<button type="submit" class="btn" id="generateBtn">
|
||||
<?php echo $_SESSION['current_image'] ? 'Edit Image' : 'Generate Image'; ?>
|
||||
<?php echo $currentImage ? 'Edit Image' : 'Generate Image'; ?>
|
||||
</button>
|
||||
|
||||
<?php if ($_SESSION['current_image']): ?>
|
||||
<?php if ($currentImage): ?>
|
||||
<button type="button" class="btn btn-secondary" id="resetBtn">
|
||||
Start New Image
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<?php if ($_SESSION['current_image']): ?>
|
||||
<?php if ($currentImage): ?>
|
||||
<div class="quick-actions">
|
||||
<button class="quick-btn" onclick="quickEdit('Add dramatic lighting')">Add Lighting</button>
|
||||
<button class="quick-btn" onclick="quickEdit('Add sunset in background')">Add Sunset</button>
|
||||
|
|
@ -843,10 +835,10 @@ if (!isset($_SESSION['image_history'])) {
|
|||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($_SESSION['conversation_history'])): ?>
|
||||
<?php if (!empty($conversationHistory)): ?>
|
||||
<div class="history">
|
||||
<h2>Conversation History</h2>
|
||||
<?php foreach (array_reverse($_SESSION['conversation_history']) as $item): ?>
|
||||
<?php foreach (array_reverse($conversationHistory) as $item): ?>
|
||||
<div class="history-item">
|
||||
<div class="history-prompt"><?php echo htmlspecialchars($item['prompt']); ?></div>
|
||||
<div class="history-time"><?php echo date('g:i A', $item['timestamp']); ?></div>
|
||||
|
|
@ -860,9 +852,9 @@ if (!isset($_SESSION['image_history'])) {
|
|||
<div class="panel">
|
||||
<h2>Generated Image</h2>
|
||||
|
||||
<div class="image-display <?php echo $_SESSION['current_image'] ? 'has-image' : ''; ?>" id="imageDisplay">
|
||||
<?php if ($_SESSION['current_image']): ?>
|
||||
<img src="data:<?php echo $_SESSION['current_image_mime']; ?>;base64,<?php echo $_SESSION['current_image']; ?>" alt="Generated Image" id="currentImage">
|
||||
<div class="image-display <?php echo $currentImage ? 'has-image' : ''; ?>" id="imageDisplay">
|
||||
<?php if ($currentImage): ?>
|
||||
<img src="data:<?php echo $currentImage['mime_type']; ?>;base64,<?php echo $currentImage['data']; ?>" alt="Generated Image" id="currentImage">
|
||||
<?php else: ?>
|
||||
<div class="placeholder">
|
||||
<div class="placeholder-icon">🎨</div>
|
||||
|
|
@ -871,7 +863,7 @@ if (!isset($_SESSION['image_history'])) {
|
|||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($_SESSION['current_image']): ?>
|
||||
<?php if ($currentImage): ?>
|
||||
<div class="image-actions">
|
||||
<button class="btn" onclick="downloadImage()">Download Image</button>
|
||||
</div>
|
||||
|
|
@ -887,11 +879,12 @@ if (!isset($_SESSION['image_history'])) {
|
|||
|
||||
<div class="debug-section">
|
||||
<h4>Session Status</h4>
|
||||
<pre>Has Current Image: <?php echo $_SESSION['current_image'] ? 'YES' : 'NO'; ?>
|
||||
Image MIME Type: <?php echo $_SESSION['current_image_mime'] ?? 'Not Set'; ?>
|
||||
Image Data Length: <?php echo $_SESSION['current_image'] ? strlen($_SESSION['current_image']) . ' chars' : '0'; ?>
|
||||
Conversation History: <?php echo count($_SESSION['conversation_history']); ?> items
|
||||
Image History: <?php echo count($_SESSION['image_history']); ?> items</pre>
|
||||
<pre>Session ID: <?php echo $sessionManager->getSessionId(); ?>
|
||||
Has Current Image: <?php echo $currentImage ? 'YES' : 'NO'; ?>
|
||||
Image MIME Type: <?php echo $currentImage ? $currentImage['mime_type'] : 'Not Set'; ?>
|
||||
Image Data Length: <?php echo $currentImage ? strlen($currentImage['data']) . ' chars' : '0'; ?>
|
||||
Conversation History: <?php echo count($conversationHistory); ?> items
|
||||
Session Directory: <?php echo basename($sessionManager->getSessionDir()); ?></pre>
|
||||
<button class="quick-btn" onclick="loadServerLogs()" style="margin-top: 10px;">📋 Load Server Logs</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -900,11 +893,11 @@ Image History: <?php echo count($_SESSION['image_history']); ?> items</pre>
|
|||
<pre id="serverLogsData"></pre>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($_SESSION['conversation_history'])): ?>
|
||||
<?php if (!empty($conversationHistory)): ?>
|
||||
<div class="debug-section">
|
||||
<h4>Recent Prompts</h4>
|
||||
<pre><?php
|
||||
$recent = array_slice($_SESSION['conversation_history'], -3);
|
||||
$recent = array_slice($conversationHistory, -3);
|
||||
foreach ($recent as $item) {
|
||||
echo date('H:i:s', $item['timestamp']) . ' [' . $item['type'] . ']: ' . htmlspecialchars($item['prompt']) . "\n";
|
||||
}
|
||||
|
|
@ -948,7 +941,7 @@ Image History: <?php echo count($_SESSION['image_history']); ?> items</pre>
|
|||
|
||||
// Check if we have either a prompt or an upload
|
||||
const hasUploadFile = uploadInput && uploadInput.files && uploadInput.files[0];
|
||||
const hasExistingImage = <?php echo $_SESSION['current_image'] ? 'true' : 'false'; ?>;
|
||||
const hasExistingImage = <?php echo $currentImage ? 'true' : 'false'; ?>;
|
||||
|
||||
if (!prompt && !hasUploadFile && !hasExistingImage) {
|
||||
showError('Please enter a prompt or upload an image');
|
||||
|
|
@ -991,7 +984,7 @@ Image History: <?php echo count($_SESSION['image_history']); ?> items</pre>
|
|||
prompt: prompt,
|
||||
aspectRatio: aspectRatio,
|
||||
imageSize: imageSize,
|
||||
hasExistingImage: <?php echo $_SESSION['current_image'] ? 'true' : 'false'; ?>,
|
||||
hasExistingImage: <?php echo $currentImage ? 'true' : 'false'; ?>,
|
||||
hasUpload: hasUpload
|
||||
});
|
||||
|
||||
|
|
@ -1014,17 +1007,17 @@ Image History: <?php echo count($_SESSION['image_history']); ?> items</pre>
|
|||
showError(result.error || 'Failed to generate image');
|
||||
logDebug('error', result.error || 'Failed to generate image');
|
||||
generateBtn.disabled = false;
|
||||
generateBtn.textContent = '<?php echo $_SESSION['current_image'] ? 'Edit Image' : 'Generate Image'; ?>';
|
||||
generateBtn.textContent = '<?php echo $currentImage ? 'Edit Image' : 'Generate Image'; ?>';
|
||||
}
|
||||
} catch (error) {
|
||||
showError('Network error: ' + error.message);
|
||||
logDebug('error', 'Network error: ' + error.message);
|
||||
generateBtn.disabled = false;
|
||||
generateBtn.textContent = '<?php echo $_SESSION['current_image'] ? 'Edit Image' : 'Generate Image'; ?>';
|
||||
generateBtn.textContent = '<?php echo $currentImage ? 'Edit Image' : 'Generate Image'; ?>';
|
||||
}
|
||||
});
|
||||
|
||||
<?php if ($_SESSION['current_image']): ?>
|
||||
<?php if ($currentImage): ?>
|
||||
resetBtn.addEventListener('click', async () => {
|
||||
if (confirm('Are you sure you want to start a new image? This will clear your current image and history.')) {
|
||||
const formData = new FormData();
|
||||
|
|
|
|||
314
session_manager.php
Normal file
314
session_manager.php
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
<?php
|
||||
/**
|
||||
* Session Manager for Multi-User Support
|
||||
* Handles isolated sessions, file-based image storage, and automatic cleanup
|
||||
*/
|
||||
|
||||
class SessionManager {
|
||||
private $sessionId;
|
||||
private $uploadDir;
|
||||
private $sessionDir;
|
||||
|
||||
public function __construct() {
|
||||
// Configure session for better security
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
ini_set('session.use_only_cookies', 1);
|
||||
ini_set('session.cookie_lifetime', 86400); // 24 hours
|
||||
ini_set('session.gc_maxlifetime', 86400); // 24 hours
|
||||
|
||||
// Start session if not already started
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Regenerate session ID on first access for security
|
||||
if (!isset($_SESSION['initialized'])) {
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['initialized'] = true;
|
||||
$_SESSION['created_at'] = time();
|
||||
}
|
||||
|
||||
$this->sessionId = session_id();
|
||||
$this->uploadDir = __DIR__ . '/uploads/sessions';
|
||||
$this->sessionDir = $this->uploadDir . '/' . $this->sessionId;
|
||||
|
||||
// Create session directory if it doesn't exist
|
||||
if (!is_dir($this->sessionDir)) {
|
||||
mkdir($this->sessionDir, 0755, true);
|
||||
mkdir($this->sessionDir . '/images', 0755, true);
|
||||
}
|
||||
|
||||
// Initialize session data
|
||||
$this->initializeSessionData();
|
||||
}
|
||||
|
||||
private function initializeSessionData() {
|
||||
if (!isset($_SESSION['conversation_history'])) {
|
||||
$_SESSION['conversation_history'] = [];
|
||||
}
|
||||
if (!isset($_SESSION['image_history'])) {
|
||||
$_SESSION['image_history'] = [];
|
||||
}
|
||||
if (!isset($_SESSION['current_image_path'])) {
|
||||
$_SESSION['current_image_path'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function getSessionId() {
|
||||
return $this->sessionId;
|
||||
}
|
||||
|
||||
public function getSessionDir() {
|
||||
return $this->sessionDir;
|
||||
}
|
||||
|
||||
public function getImagesDir() {
|
||||
return $this->sessionDir . '/images';
|
||||
}
|
||||
|
||||
/**
|
||||
* Save image to disk and return file path
|
||||
*/
|
||||
public function saveImage($imageData, $mimeType = 'image/png') {
|
||||
$extension = $this->getExtensionFromMime($mimeType);
|
||||
$timestamp = time();
|
||||
$filename = 'image_' . $timestamp . '_' . uniqid() . '.' . $extension;
|
||||
$filepath = $this->getImagesDir() . '/' . $filename;
|
||||
|
||||
// Decode base64 if needed
|
||||
if (base64_decode($imageData, true) !== false) {
|
||||
$imageData = base64_decode($imageData);
|
||||
}
|
||||
|
||||
// Save image
|
||||
if (file_put_contents($filepath, $imageData) === false) {
|
||||
throw new Exception('Failed to save image to disk');
|
||||
}
|
||||
|
||||
// Save metadata
|
||||
$metadataFile = $filepath . '.meta';
|
||||
$metadata = [
|
||||
'created_at' => $timestamp,
|
||||
'mime_type' => $mimeType,
|
||||
'session_id' => $this->sessionId,
|
||||
'expires_at' => $timestamp + 86400 // 24 hours
|
||||
];
|
||||
file_put_contents($metadataFile, json_encode($metadata));
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image data from disk
|
||||
*/
|
||||
public function getImage($filename) {
|
||||
$filepath = $this->getImagesDir() . '/' . $filename;
|
||||
|
||||
if (!file_exists($filepath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if image has expired
|
||||
$metadataFile = $filepath . '.meta';
|
||||
if (file_exists($metadataFile)) {
|
||||
$metadata = json_decode(file_get_contents($metadataFile), true);
|
||||
if ($metadata && $metadata['expires_at'] < time()) {
|
||||
// Image expired, delete it
|
||||
$this->deleteImage($filename);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'data' => base64_encode(file_get_contents($filepath)),
|
||||
'mime_type' => $this->getMimeTypeFromFile($filepath)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete image from disk
|
||||
*/
|
||||
public function deleteImage($filename) {
|
||||
$filepath = $this->getImagesDir() . '/' . $filename;
|
||||
$metadataFile = $filepath . '.meta';
|
||||
|
||||
if (file_exists($filepath)) {
|
||||
unlink($filepath);
|
||||
}
|
||||
if (file_exists($metadataFile)) {
|
||||
unlink($metadataFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current image
|
||||
*/
|
||||
public function setCurrentImage($filename, $mimeType = 'image/png') {
|
||||
$_SESSION['current_image_path'] = $filename;
|
||||
$_SESSION['current_image_mime'] = $mimeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current image
|
||||
*/
|
||||
public function getCurrentImage() {
|
||||
if (!isset($_SESSION['current_image_path']) || !$_SESSION['current_image_path']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getImage($_SESSION['current_image_path']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear current image
|
||||
*/
|
||||
public function clearCurrentImage() {
|
||||
if (isset($_SESSION['current_image_path']) && $_SESSION['current_image_path']) {
|
||||
$this->deleteImage($_SESSION['current_image_path']);
|
||||
}
|
||||
$_SESSION['current_image_path'] = null;
|
||||
$_SESSION['current_image_mime'] = 'image/png';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to conversation history
|
||||
*/
|
||||
public function addToHistory($prompt, $type = 'edit') {
|
||||
$_SESSION['conversation_history'][] = [
|
||||
'prompt' => $prompt,
|
||||
'type' => $type,
|
||||
'timestamp' => time()
|
||||
];
|
||||
|
||||
// Keep only last 50 items
|
||||
if (count($_SESSION['conversation_history']) > 50) {
|
||||
$_SESSION['conversation_history'] = array_slice($_SESSION['conversation_history'], -50);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation history
|
||||
*/
|
||||
public function getHistory() {
|
||||
return $_SESSION['conversation_history'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all session data and images
|
||||
*/
|
||||
public function reset() {
|
||||
// Delete all images in session directory
|
||||
$imagesDir = $this->getImagesDir();
|
||||
if (is_dir($imagesDir)) {
|
||||
$files = glob($imagesDir . '/*');
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear session data
|
||||
$_SESSION['conversation_history'] = [];
|
||||
$_SESSION['image_history'] = [];
|
||||
$_SESSION['current_image_path'] = null;
|
||||
$_SESSION['current_image_mime'] = 'image/png';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file extension from MIME type
|
||||
*/
|
||||
private function getExtensionFromMime($mimeType) {
|
||||
$mimeMap = [
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/jpg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/webp' => 'webp',
|
||||
'image/gif' => 'gif'
|
||||
];
|
||||
|
||||
return $mimeMap[$mimeType] ?? 'png';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MIME type from file
|
||||
*/
|
||||
private function getMimeTypeFromFile($filepath) {
|
||||
$extension = strtolower(pathinfo($filepath, PATHINFO_EXTENSION));
|
||||
|
||||
$extMap = [
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'webp' => 'image/webp',
|
||||
'gif' => 'image/gif'
|
||||
];
|
||||
|
||||
return $extMap[$extension] ?? 'image/png';
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method to clean up expired images across all sessions
|
||||
*/
|
||||
public static function cleanupExpiredImages($uploadDir = null) {
|
||||
if ($uploadDir === null) {
|
||||
$uploadDir = __DIR__ . '/uploads/sessions';
|
||||
}
|
||||
|
||||
if (!is_dir($uploadDir)) {
|
||||
return ['cleaned' => 0, 'errors' => []];
|
||||
}
|
||||
|
||||
$cleaned = 0;
|
||||
$errors = [];
|
||||
$currentTime = time();
|
||||
|
||||
// Scan all session directories
|
||||
$sessionDirs = glob($uploadDir . '/*', GLOB_ONLYDIR);
|
||||
|
||||
foreach ($sessionDirs as $sessionDir) {
|
||||
$imagesDir = $sessionDir . '/images';
|
||||
|
||||
if (!is_dir($imagesDir)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find all metadata files
|
||||
$metadataFiles = glob($imagesDir . '/*.meta');
|
||||
|
||||
foreach ($metadataFiles as $metadataFile) {
|
||||
try {
|
||||
$metadata = json_decode(file_get_contents($metadataFile), true);
|
||||
|
||||
// Check if expired
|
||||
if ($metadata && isset($metadata['expires_at']) && $metadata['expires_at'] < $currentTime) {
|
||||
// Delete image and metadata
|
||||
$imageFile = str_replace('.meta', '', $metadataFile);
|
||||
|
||||
if (file_exists($imageFile)) {
|
||||
unlink($imageFile);
|
||||
}
|
||||
unlink($metadataFile);
|
||||
|
||||
$cleaned++;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty session directories
|
||||
$remainingFiles = glob($imagesDir . '/*');
|
||||
if (empty($remainingFiles)) {
|
||||
rmdir($imagesDir);
|
||||
rmdir($sessionDir);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'cleaned' => $cleaned,
|
||||
'errors' => $errors,
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
}
|
||||
11
uploads/.htaccess
Normal file
11
uploads/.htaccess
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Secure uploads directory
|
||||
# Prevent direct access to uploaded images via browser
|
||||
|
||||
# Deny access to all files
|
||||
Order Deny,Allow
|
||||
Deny from all
|
||||
|
||||
# Allow PHP scripts to access files
|
||||
<FilesMatch "\.php$">
|
||||
Allow from all
|
||||
</FilesMatch>
|
||||
Loading…
Add table
Reference in a new issue