Files
bookmarkly2/api/image.php
T
2025-10-07 10:12:17 +02:00

149 lines
4.3 KiB
PHP

<?php
require_once '../include/init.php';
require_once '../include/auth.php';
// Set proper headers
header('Content-Type: application/json');
// Check if URL parameter is provided
if (!isset($_GET['url']) || empty($_GET['url'])) {
http_response_code(400);
echo json_encode(['error' => 'URL parameter is required']);
exit;
}
$imageUrl = $_GET['url'];
$width = isset($_GET['w']) ? intval($_GET['w']) : null;
$height = isset($_GET['h']) ? intval($_GET['h']) : null;
$quality = isset($_GET['q']) ? intval($_GET['q']) : 85;
// Validate URL
if (!filter_var($imageUrl, FILTER_VALIDATE_URL)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid URL']);
exit;
}
// Allowed domains (add your CDN domains here)
$allowedDomains = [
'cdn.jsdelivr.net',
'favicon.hibbit.net',
'favicon.hibbit.cloud',
'localhost',
'127.0.0.1'
];
$parsedUrl = parse_url($imageUrl);
$domain = $parsedUrl['host'] ?? '';
if (!in_array($domain, $allowedDomains)) {
http_response_code(403);
echo json_encode(['error' => 'Domain not allowed']);
exit;
}
// Create cache directory
$cacheDir = '../data/images/cache/';
if (!is_dir($cacheDir)) {
mkdir($cacheDir, 0755, true);
}
// Generate cache key
$cacheKey = md5($imageUrl . $width . $height . $quality);
$cacheFile = $cacheDir . $cacheKey;
// Check if cached version exists and is fresh (1 day)
if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < 86400) {
// Serve cached file
$imageInfo = getimagesize($cacheFile);
if ($imageInfo) {
header('Content-Type: ' . $imageInfo['mime']);
header('Content-Length: ' . filesize($cacheFile));
header('Cache-Control: public, max-age=86400');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT');
header('ETag: "' . $cacheKey . '"');
readfile($cacheFile);
exit;
}
}
// Fetch the image
$context = stream_context_create([
'http' => [
'timeout' => 30,
'user_agent' => 'Bookmarkly Image Proxy/1.0',
'follow_location' => true,
'max_redirects' => 5
]
]);
$imageData = @file_get_contents($imageUrl, false, $context);
if ($imageData === false) {
http_response_code(404);
echo json_encode(['error' => 'Failed to fetch image']);
exit;
}
// Check if ImageMagick is available for processing
if (extension_loaded('imagick') && ($width || $height || $quality < 100)) {
try {
$imagick = new Imagick();
$imagick->readImageBlob($imageData);
// Resize if dimensions are specified
if ($width || $height) {
$imagick->resizeImage(
$width ?: 0,
$height ?: 0,
Imagick::FILTER_LANCZOS,
1
);
}
// Set quality for JPEG
if ($imagick->getImageFormat() === 'JPEG') {
$imagick->setImageCompressionQuality($quality);
}
$processedData = $imagick->getImageBlob();
$imagick->destroy();
// Save to cache
file_put_contents($cacheFile, $processedData);
// Set headers
$imageInfo = getimagesize($cacheFile);
header('Content-Type: ' . $imageInfo['mime']);
header('Content-Length: ' . strlen($processedData));
header('Cache-Control: public, max-age=86400');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT');
header('ETag: "' . $cacheKey . '"');
echo $processedData;
exit;
} catch (Exception $e) {
// Fallback to original image if processing fails
error_log('Image processing failed: ' . $e->getMessage());
}
}
// Serve original image and cache it
file_put_contents($cacheFile, $imageData);
$imageInfo = @getimagesize($cacheFile);
if ($imageInfo) {
header('Content-Type: ' . $imageInfo['mime']);
} else {
header('Content-Type: image/jpeg'); // fallback
}
header('Content-Length: ' . strlen($imageData));
header('Cache-Control: public, max-age=86400');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT');
header('ETag: "' . $cacheKey . '"');
echo $imageData;
?>