Setting upload feature on hexo blog

Fedora 42

  1. Install php packages.
sudo dnf install php
  1. Create a upload directory.
mkdir /var/www/html/assets/upload
chmod 777 /var/www/html/assets/upload
  1. Modify the httpd.conf file.
"/etc/httpd/conf/httpd.conf"
<Directory "/var/www/html/assets/upload">
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>

Restart httpd service.

sudo systemctl restart httpd.service
  1. Modify the 99-upload.ini file.
"/etc/php.d/99-upload.ini"
; Maximum allowed size for uploaded files
upload_max_filesize = 100M

; Must be larger than upload_max_filesize
post_max_size = 110M

; Maximum execution time
max_execution_time = 300

; Maximum input time
max_input_time = 300

; Memory limit
memory_limit = 256M

Restart php-fpm service.

sudo systemctl status php-fpm.service
  1. Create a html file.
"/var/www/html/assets/upload/index.html"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload with Progress</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.upload-form {
border: 2px dashed #ccc;
padding: 20px;
text-align: center;
border-radius: 5px;
background: #f9f9f9;
}
.btn {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn:hover {
background-color: #45a049;
}
.btn:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.message {
margin-top: 20px;
padding: 10px;
border-radius: 4px;
}
.success {
background-color: #dff0d8;
color: #3c763d;
}
.error {
background-color: #f2dede;
color: #a94442;
}
.progress-container {
width: 100%;
background-color: #f1f1f1;
border-radius: 4px;
margin: 15px 0;
display: none;
}
.progress-bar {
width: 0%;
height: 30px;
background-color: #4CAF50;
border-radius: 4px;
text-align: center;
line-height: 30px;
color: white;
transition: width 0.3s ease;
}
.processing {
background-color: #2196F3;
}
.file-info {
margin: 10px 0;
font-size: 14px;
color: #555;
}
</style>
</head>
<body>
<h1>File Upload with Progress</h1>

<form class="upload-form" id="uploadForm" enctype="multipart/form-data">
<h2>Select any file to upload:</h2>
</i><input type="file" name="fileToUpload" id="fileToUpload" required>
<div class="file-info" id="fileInfo"></div>
<br>
</i><input type="submit" class="btn" id="uploadBtn" value="Upload File">

<div class="progress-container" id="progressContainer">
<div class="progress-bar" id="progressBar">0%</div>
</div>
</form>

<div id="message"></div>

<script>
// Show file info when selected
document.getElementById('fileToUpload').addEventListener('change', function(e) {
const fileInfo = document.getElementById('fileInfo');
if (this.files.length > 0) {
const file = this.files[0];
fileInfo.innerHTML = `Selected: <strong>${file.name}</strong> (${formatFileSize(file.size)})`;
} else {
fileInfo.innerHTML = '';
}
});

// Format file size
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';
}
}

// Handle form submission
document.getElementById('uploadForm').addEventListener('submit', function(e) {
e.preventDefault();

const fileInput = document.getElementById('fileToUpload');
const file = fileInput.files[0];
const formData = new FormData();
formData.append('fileToUpload', file);

const progressContainer = document.getElementById('progressContainer');
const progressBar = document.getElementById('progressBar');
const uploadBtn = document.getElementById('uploadBtn');
const messageDiv = document.getElementById('message');
const uploadForm = document.getElementById('uploadForm');

// Reset UI
messageDiv.innerHTML = '';
progressBar.style.width = '0%';
progressBar.innerHTML = '0%';
progressBar.className = 'progress-bar';
progressContainer.style.display = 'block';
uploadBtn.disabled = true;
uploadBtn.value = 'Uploading...';

const xhr = new XMLHttpRequest();
let uploadComplete = false;

// Progress event
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable && !uploadComplete) {
const percentComplete = Math.min(99, (e.loaded / e.total) * 100);
progressBar.style.width = percentComplete + '%';
progressBar.innerHTML = Math.round(percentComplete) + '%';
}
}, false);

// Load event
xhr.addEventListener('load', function(e) {
uploadComplete = true;
progressBar.style.width = '100%';
progressBar.innerHTML = '100%';

try {
const response = JSON.parse(xhr.responseText);
if (response.success) {
messageDiv.innerHTML = `
<div class="message success">
File <strong>${response.filename}</strong> uploaded successfully!<br>
Size: ${formatFileSize(response.filesize)}
</div>
`;
uploadForm.reset();
document.getElementById('fileInfo').innerHTML = '';
} else {
messageDiv.innerHTML = `<div class="message error">${response.error}</div>`;
}
} catch (e) {
messageDiv.innerHTML = '<div class="message error">Invalid server response</div>';
}

progressContainer.style.display = 'none';
uploadBtn.disabled = false;
uploadBtn.value = 'Upload File';
}, false);

// Error event
xhr.addEventListener('error', function() {
uploadComplete = true;
let errorMsg = 'Upload failed. Please try again.';

try {
const response = JSON.parse(xhr.responseText);
if (response && response.error) {
errorMsg = response.error;
}
} catch (e) {
console.error('Error parsing server response:', e);
}

messageDiv.innerHTML = `<div class="message error">${errorMsg}</div>`;
progressBar.className = 'progress-bar';
progressContainer.style.display = 'none';
uploadBtn.disabled = false;
uploadBtn.value = 'Upload File';
}, false);

xhr.open('POST', 'upload_handler.php', true);
xhr.send(formData);
});
</script>
</body>
</html>
  1. Create a php file.
"/var/www/html/assets/upload/upload_handler.php"
<?php
// Set higher limits for file uploads
ini_set('upload_max_filesize', '100M');
ini_set('post_max_size', '110M');
ini_set('memory_limit', '256M');
set_time_limit(0);

header('Content-Type: application/json');

// Error messages
$uploadErrors = [
0 => 'File uploaded successfully',
1 => 'File exceeds server limit (upload_max_filesize)',
2 => 'File exceeds form limit (MAX_FILE_SIZE)',
3 => 'File only partially uploaded',
4 => 'No file was uploaded',
6 => 'Missing temporary folder',
7 => 'Failed to write file to disk',
8 => 'PHP extension stopped the upload'
];

$targetDir = "/var/www/html/assets/upload/";
$fileName = basename($_FILES["fileToUpload"]["name"]);
$targetFile = $targetDir . $fileName;
$fileSize = $_FILES["fileToUpload"]["size"];
$maxFileSize = 100 * 1024 * 1024; // 100MB in bytes

// Check for upload errors
if ($_FILES["fileToUpload"]["error"] !== UPLOAD_ERR_OK) {
die(json_encode([
'success' => false,
'error' => $uploadErrors[$_FILES["fileToUpload"]["error"]] ?? 'Unknown upload error'
]));
}

// Check file size against our limit
if ($fileSize > $maxFileSize) {
die(json_encode([
'success' => false,
'error' => 'File too large. Maximum allowed: 100MB'
]));
}

// Create directory if needed
if (!file_exists($targetDir)) {
if (!mkdir($targetDir, 0777, true)) {
die(json_encode([
'success' => false,
'error' => 'Failed to create upload directory'
]));
}
}

// Check if directory is writable
if (!is_writable($targetDir)) {
die(json_encode([
'success' => false,
'error' => 'Upload directory is not writable'
]));
}

// Handle duplicate files
if (file_exists($targetFile)) {
$fileInfo = pathinfo($fileName);
$fileName = $fileInfo['filename'] . '_' . time() . '.' . $fileInfo['extension'];
$targetFile = $targetDir . $fileName;
}

// Move the uploaded file
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $targetFile)) {
echo json_encode([
'success' => true,
'filename' => htmlspecialchars($fileName),
'filesize' => filesize($targetFile)
]);
} else {
$error = error_get_last();
die(json_encode([
'success' => false,
'error' => 'Failed to move uploaded file',
'details' => $error['message'] ?? 'Unknown error'
]));
}
?>