Robert 1 year ago
commit c21b9b02f4
  1. 1
      .gitignore
  2. 98
      assets/js/uploading.js
  3. 254
      image_compression.php
  4. 39
      index.html
  5. 84
      upload.php

1
.gitignore vendored

@ -0,0 +1 @@
uploads/

@ -0,0 +1,98 @@
function isEmptyArray(array) {
return Array.isArray(array) && array.length === 0;
}
function handleFiles(files) {
const formData = new FormData();
const validFiles = [];
for (const file of files) {
if (file.size > 0) {
formData.append('files[]', file);
validFiles.push(file);
} else {
console.warn(`The file "${file.name}" is empty and will be skipped.`);
}
}
if (validFiles.length === 0) {
alert('No valid files to upload. All selected files are empty.');
return;
}
fetch('upload.php', {
method: 'POST',
body: formData
}).then(response => {
if (!response.ok) {
throw new Error('Network response was not ok: ' + response.status);
}
return response.json();
}).then(results => {
let successes = [];
let errors = [];
results.forEach(result => {
if (result.status == "success") {
successes.push(result.message);
} else {
errors.push(result.message);
}
});
if (!isEmptyArray(successes)) {
const success = successes.map(text => text).join(", ");
alert("Successfully uploaded: " + success);
}
if (!isEmptyArray(errors)) {
const error = errors.map(text => text).join(", ");
alert("Failed on: " + error);
}
}).catch(error => {
if (error.message.startsWith('Network response')) {
alert('Internal Server Error (500): There was a problem with the server.');
} else {
alert('Error:' + error);
}
}).finally(() => {
for (const key in formData) {
if (formData.hasOwnProperty(key)) {
delete formData[key];
}
}
while (validFiles.length > 0) {
validFiles.pop();
}
});
}
document.addEventListener('DOMContentLoaded', function() {
const uploadContainer = document.getElementById('uploadContainer');
const fileInput = document.getElementById('fileInput');
uploadContainer.addEventListener('click', () => fileInput.click());
uploadContainer.addEventListener('dragover', (e) => {
e.preventDefault();
uploadContainer.classList.add('dragover');
});
uploadContainer.addEventListener('dragleave', () => {
uploadContainer.classList.remove('dragover');
});
uploadContainer.addEventListener('drop', (e) => {
e.preventDefault();
uploadContainer.classList.remove('dragover');
let total = 0;
try {
total = e.dataTransfer.files.length;
} catch (error) {
console.error("Opps" + error.message);
}
if (total > 0) {
handleFiles(e.dataTransfer.files);
} else {
alert("No files dropped. Please try again.");
}
});
fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
});

@ -0,0 +1,254 @@
<?php
$min_pixels_before_compression = 999;
function generate_unique_filename(string $prefix = ''): string {
$timestamp = microtime(true); // Current timestamp with microseconds
$random = mt_rand(); // Random number (better than rand() for uniqueness)
$hash = md5($timestamp . $random); // MD5 hash of timestamp and random number
// Combine prefix (if provided) with the hash
$filename = $prefix . $hash;
return $filename;
}
/**
* Make sure uploads (LIKE Images, etc...) do NOT run PHP code!!
* Checks for PHP tags inside of file.
* @param string $file
* @return bool true if PHP was found
*/
function file_contains_php(string $file): bool {
$file_handle = fopen($file, "r");
while (!feof($file_handle)) {
$line = fgets($file_handle);
$pos = strpos($line, '<?php');
if ($pos !== false) {
fclose($file_handle);
return true;
}
}
fclose($file_handle);
return false;
}
function rename_upload(string $uploadName, string $fileName, false|string $rndFile): false|string {
if (file_contains_php($uploadName)) {
@unlink($uploadName); // stop attacks by a Delete of the PHP Image file!!
return false;
}
if ($rndFile === false) {
return false;
}
$success = rename($uploadName, $GLOBALS['uploadDir'] . 'orignal_' . $rndFile . '_' . $fileName);
return ($success) ? $GLOBALS['uploadDir'] . 'orignal_' . $rndFile . $fileName . '_': false;
}
function get_size(int $target, int $original): int {
$min_pixels_before_compression = $GLOBALS['min_pixels_before_compression'];
if ($target == 0) {
return ($original > $min_pixels_before_compression) ? $original / 2 : $original;
}
if ($target < $original) {
return $target;
}
return $original;
}
function image_compression(string $uploadName, string $fileName, array $images): array {
$rnd_name = generate_unique_filename();
if (!count($images)) {
return [];
}
$a_of_images = [];
foreach($images as $image) {
$name = $image['name'] ?? false;
$width = $image['width'] ?? false;
$height = $image['height'] ?? false;
$crop = $image['crop'] ?? false;
if ($name === false || $width === false || $height === false) {
continue;
}
$name .= '_' . $rnd_name;
if ($crop) {
$file = do_image_crop($uploadName, $fileName, $name, $width, $height, $rnd_name);
} else {
$file = do_image_compression($uploadName, $fileName, $name, $width, $height);
}
if ($file === false) {
continue;
}
$a_of_images[] = $file;
}
rename_upload($uploadName, $fileName, $rnd_name);
return $a_of_images;
}
function do_image_compression(string $uploadName, string $fileName, string $newName, int $targetWidth, int $targetHeight): false|string {
list($originalWidth, $originalHeight, $imageType) = getimagesize($uploadName);
if ($originalWidth == 0 || $originalHeight == 0) {
return $GLOBALS['image_error'];
}
// File Name without EXT type or Path
$fileNameWithoutExt = pathinfo($fileName, PATHINFO_FILENAME);
switch ($imageType) {
case IMAGETYPE_JPEG:
$img = imagecreatefromjpeg($uploadName);
$type = '.jpg';
break;
case IMAGETYPE_PNG:
$img = imagecreatefrompng($uploadName);
$type = '.png';
break;
case IMAGETYPE_GIF:
$img = imagecreatefromgif($uploadName);
$type = '.gif';
break;
default:
return false;
}
if ($img === false) {
return false;
}
$scaledFileName = $GLOBALS['uploadDir'] . $newName . '_' . $fileNameWithoutExt . $type;
// Calculate half image sizes for scaling
$newWidth = get_size($targetWidth, $originalWidth);
$newHeight = get_size($targetHeight, $originalHeight);
// Create a new true color image
$scaledImage = imagecreatetruecolor($newWidth, $newHeight);
// Enable transparency
imagealphablending($scaledImage, false);
imagesavealpha($scaledImage, true);
// Copy and resize the original image into the new image
imagecopyresampled(
$scaledImage, $img,
0, 0, 0, 0,
$newWidth, $newHeight,
$originalWidth, $originalHeight
);
switch ($imageType) {
case IMAGETYPE_JPEG:
$quality = $GLOBALS['JPEG_quality'];
imagejpeg($scaledImage, $scaledFileName, $quality);
break;
case IMAGETYPE_PNG:
$compression =$GLOBALS['PNG_compression'];
$transparentColor = imagecolorallocatealpha($scaledImage, 0, 0, 0, 127);
imagefill($scaledImage, 0, 0, $transparentColor);
imagepng($scaledImage, $scaledFileName, $compression);
break;
case IMAGETYPE_GIF:
$number_of_colors = $GLOBALS['GIF_number_of_colors'];
// Reduce the number of colors to make the file smaller
imagetruecolortopalette($scaledImage, false, $number_of_colors);
imagegif($scaledImage, $scaledFileName);
break;
}
// Free up memory
imagedestroy($img);
imagedestroy($scaledImage);
return $newName . '_' . $fileNameWithoutExt . $type;
}
function do_image_crop(string $uploadName, string $fileName, string $newName, int $targetWidth, int $targetHeight, string $rnd_name): false|string {
if (! file_exists($GLOBALS['uploadDir'] . 'half_' . $rnd_name . '_' . $fileName)) {
$scaledFileName = $GLOBALS['uploadDir'] . do_image_compression($uploadName, $fileName, "half_" . $rnd_name, $targetWidth, $targetHeight);
} else {
$scaledFileName = $GLOBALS['uploadDir'] . 'half_' . $rnd_name . '_' . $fileName;
}
list($originalWidth, $originalHeight, $imageType) = getimagesize($scaledFileName);
// File Name without EXT type or Path
$fileNameWithoutExt = pathinfo($fileName, PATHINFO_FILENAME);
switch ($imageType) {
case IMAGETYPE_JPEG:
$img = imagecreatefromjpeg($scaledFileName);
$type = '.jpg';
break;
case IMAGETYPE_PNG:
$img = imagecreatefrompng($scaledFileName);
$type = '.png';
break;
case IMAGETYPE_GIF:
$img = imagecreatefromgif($scaledFileName);
$type = '.gif';
break;
default:
return false;
}
if ($img === false) {
return false;
}
$croppedFileName = $GLOBALS['uploadDir'] . $newName . '_' . $fileNameWithoutExt . $type;
// Calculate the coordinates for the cropping
if ($originalWidth <= $targetWidth) {
$cropX = 0;
$targetWidth = $originalWidth;
} else {
$cropX = ($originalWidth - $targetWidth) / 2; // Crop from the center horizontally
}
if ($originalHeight <= $targetHeight) {
$cropY = 0;
$targetHeight = $originalHeight;
} else {
$cropY = ($originalHeight - $targetHeight) / 2; // Crop from the center vertically
}
switch ($imageType) {
case IMAGETYPE_JPEG:
// Create a new image with the cropped dimensions
$croppedImg = imagecrop($img, ['x' => $cropX, 'y' => $cropY, 'width' => $targetWidth, 'height' => $targetHeight]);
$quality = $GLOBALS['JPEG_quality'];
imagejpeg($croppedImg, $croppedFileName, $quality);
break;
case IMAGETYPE_PNG:
// Create a new true color image with the desired dimensions
$croppedImg = imagecreatetruecolor($targetWidth, $targetHeight);
// Enable transparency
imagealphablending($croppedImg, false);
imagesavealpha($croppedImg, true);
// Allocate a color for the background (fully transparent)
$transparentColor = imagecolorallocatealpha($croppedImg, 0, 0, 0, 127);
imagefill($croppedImg, 0, 0, $transparentColor);
// Copy the relevant portion of the source image to the new image
imagecopy($croppedImg, $img, 0, 0, $cropX, $cropY, $targetWidth, $targetHeight);
$compression = $GLOBALS['PNG_compression'];
// Save the cropped image
imagepng($croppedImg, $croppedFileName, $compression);
break;
case IMAGETYPE_GIF:
// Create a new image with the cropped dimensions
$croppedImg = imagecrop($img, ['x' => $cropX, 'y' => $cropY, 'width' => $targetWidth, 'height' => $targetHeight]);
$number_of_colors = $GLOBALS['GIF_number_of_colors'];
// Reduce the number of colors to make the file smaller
imagetruecolortopalette($croppedImg, false, $number_of_colors);
imagegif($croppedImg, $croppedFileName);
break;
}
// Free up memory
imagedestroy($img);
imagedestroy($croppedImg);
return $newName . '_' . $fileNameWithoutExt . $type;
}

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Drag and Drop Multiple File Upload</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.upload-container {
width: 300px;
padding: 20px;
border: 2px dashed #ccc;
text-align: center;
cursor: pointer;
}
.upload-container.dragover {
background-color: #f0f0f0;
}
</style>
</head>
<body>
<div class="upload-container" id="uploadContainer">
Drag and drop image files here or click to select files
<input type="file" id="fileInput" style="display: none;" accept="image/*" multiple>
</div>
<script src="./assets/js/uploading.js"></script>
<noscript>
<h1>Error! :</h1>
<p>This website requires JavaScript to function properly.</p>
</noscript>
</body>
</html>

@ -0,0 +1,84 @@
<?php
// Upload Directory
$uploadDir = 'uploads/';
$maxFileSize = 5 * 1024 * 1024; // 5MB in bytes
$validTypes = ['jpg', 'jpeg', 'png', 'gif']; // Note .SVG is dangerious
$a_crops = [
['name'=>'half', 'width'=>0, 'height'=>0, 'crop'=>false],
['name'=>'hd', 'width'=>1920, 'height'=>1080, 'crop'=>false],
['name'=>'thumbnail', 'width'=>300, 'height'=>300, 'crop'=>false],
//['name'=>'crop', 'width'=>300, 'height'=>300, 'crop'=>true],
];
// Set the quality (0-100)
$JPEG_quality = 75; // Adjust this value as needed
// Set the compression level (0-9)
$PNG_compression = 9; // Adjust this value as needed
$GIF_number_of_colors = 128; // Adjust number of Colors as needed
/**
* nano /etc/php/8.3/apache2/php.ini
* edit php.ini for the following:
*/
ini_set('upload_max_filesize', '6M'); // Adjust upload limits
ini_set('post_max_size', '6M');
ini_set('max_file_uploads', '15');
ini_set('max_execution_time', '90'); // 90 seconds = 1.5 minutes
ini_set('max_input_time', '90'); // 1.5 minutes
require_once "image_compression.php";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_FILES['files'])) {
$files = $_FILES['files'];
$responses = [];
// Ensure the uploads directory exists
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0775, true);
}
for ($i = 0; $i < count($files['name']); $i++) {
$fileName = basename($files['name'][$i]);
$fileTmpName = $files['tmp_name'][$i];
$fileSize = $files['size'][$i];
$fileType = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$uploadFile = $uploadDir . $fileName;
// Validate file type (only images allowed)
if (!in_array($fileType, $validTypes)) {
$responses[] = ['status' => 'error', 'message' => "Invalid file type for {$fileName}. Only images are allowed."];
@unlink($fileTmpName);
continue;
}
// Validate file size (limit to 5MB)
if ($fileSize > $maxFileSize) {
$responses[] = ['status' => 'error', 'message' => "File size for $fileName exceeds the limit of 5MB."];
@unlink($fileTmpName);
continue;
}
// Move the uploaded file to the server
if (move_uploaded_file($fileTmpName, $uploadFile)) {
$compressed_files = image_compression($uploadFile, $fileName, $GLOBALS['a_crops']);
if (count($compressed_files)) {
$responses[] = ['status' => 'success', 'message' => "$fileName"];
foreach($compressed_files as $file) {
$save_to_DB = $uploadDir . $file;
// HANDLE $save_to_DB to save to Database
}
} else {
$responses[] = ['status' => 'error', 'message' => "Failed to Scale image$fileName."];
}
} else {
$responses[] = ['status' => 'error', 'message' => "Failed to upload $fileName."];
}
}
echo json_encode($responses);
} else {
echo json_encode([['status' => 'error', 'message' => 'No files were uploaded.']]);
}
} else {
echo json_encode([['status' => 'error', 'message' => 'Invalid request method.']]);
}
Loading…
Cancel
Save