@Dmytro_Shved Heres some code, but its not tested, you should make sure it works with console.logs, because Im blindly writing it.
<div><!-- Extra wrapping div, because alpine sometimes doesnt play nice with livewire -->
<div x-data="fileUploader($wire)">
<div>
<input type="file" id="upload-file" accept="{{ $allowedMimes }}" x-ref="uploadingFile"
class="hidden" x-on:change="uploadFile(event)">
<x-button x-on:click.prevent="$refs.uploadingFile.click();">Upload File</x-button>
</div>
</div>
</div>
Some explanation: No one wants the default upload button, so what this does is it hides the default upload button and replaces it with a <x-button> (Blade component, or you can use <button> html). Its made so that when this button is clicked, it acts as if the hidden input has been clicked. So, its just frontend manipulation. The x-data initializes AlpineJS, and I pass the $wire component in it. Im not sure if I wrap alpineJS with @script tags it would work, so this is a workaround to have a $wire ( Read more about $wire ) without @script tags wrapping the <script> tags. Read More about the @script
Then also in the frontend you would have to put this script somewhere:
<script>
function fileUploader($wire){
return {
uploading: false,
allowedTypes: {!! json_encode(explode(',', $allowedMimes)) !!},
maxSizePerType: {
'application/pdf': /* file limit in bytes, like 8 * 1024 would be 8MBs */,
'image/jpg': /* image limit in bytes */,
'image/jpeg': /* image limit in bytes */,
'image/png': {{ $uploadFileLimit * 1024 }} /* Example to use the backend variable, ITS IN BYTES, remember, not KBs, not MBs, bytes */
},
uploadFile(event) {
let files = Array.from(event.target.files || []);
if (!files.length) return;
const file = files[0];
let mimeType = file.type;
let maxSize = this.maxSizePerType[mimeType];
let isFileValid = this.allowedTypes.includes(mimeType) && maxSize && file.size <= maxSize;
if(isFileValid) {
$wire.$upload('uploadedFile', file); //Sets the public $uploadedFile that is in the backend
else {
$wire.$set('uploadedFile', null);
files = [];
}
}
}
}
</script>
Note again, that you should test every step of the way, with console logs, to check if its exactly what you want. I know some variables should be const instead of let, but doesnt really matter that much to me. These are just the basic checks for the frontend.
Then in the backend you would have:
#[Locked] //Make sure Locked is here, to prevent manipulation
public $allowedMimes = 'application/pdf,image/jpg,image/jpeg,image/png' //Or whatever value you need
#[Locked]
public $uploadFileLimit = 8 * 1024;
public $uploadedFile = null; //Instance of TemporaryUplaodedFile
$this->previewImageUrl = null;
Somewhere in the function have this, which is a livewire cycle hook:
public function updatedUploadedFile()
{
if($this->uploadedFile instanceof TemporaryUploadedFile) {
$uploadedFilePath = $uploadedFile->getRealPath();
if(file_exists($uploadedFilePath)) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$fileType = finfo_file($finfo, $uploadedFilePath);
finfo_close($finfo);
$fileSize = filesize($uploadedFilePath) / 1024; // KBs MAKE SURE TO TEST THIS, I might be mixing up KBs, MBs and or bytes. You might have to do some extra */ 1024 depending on what you get, Im just trying to write this code fast
$imgUploadLimit = $imgUploadLimitMB;
if ($imgSize > $imgUploadLimit) {
$this->previewImageUrl = null; //Make sure to use this appropriately on the right places, it depends on this what is shown in the frontend.
// File exceeds limit, so set uploadedFile to null and throw error
}
if (($fileType === 'image/jpeg' && @imagecreatefromjpeg($tmpImgPath) === false) ||
($fileType === 'image/png' && @imagecreatefrompng($tmpImgPath) === false)) {
// File is invalid, $this->uplaodedFile to null and throw error. This checks just for IMAGES, you should add more cases here handling more files if you have more files. I understand this is just for images and preview images, but I want to explain that you should check each file type separately. Check at the end for code for checking if PDF documents are spoofed for example
}
// If all checks pass and the uploadedFile is not null, set the previewImageUrl:
if(isset($this->uploadedFile)) $this->previewImageUrl = $this->uploadedFile->temporaryUrl();
}
}
}
With this, everytime you pass a file from the frontend to the backend, this function gets called and it processes the file.
So at the end you jsut need a check for the preiew:
@if ($previewImageUrl)
<img src="{{ $previewImageUrl }}" alt="Preview Image" style="max-width: 100%; height: auto;">
@endif
Also, add in <style> tags:
[x-cloak] { display: none !important; }
To make Alpine's x-cloak work
Example to check if a PDF is spoofed:
//Spoofer Doofer
$stream = fopen($tmpDocPath, 'rb');
if ($stream) {
$firstBytes = fread($stream, 5);
fclose($stream);
if (!str_starts_with($firstBytes, '%PDF-')) {
//Return invalid file type error message
}
I believe I have given you more than enough to have code to work with, again, remember, this is just REFERENCE code, its not tested and you should build it step by step and test each step accordingly.