Uploading files
In this recipe we will create a basic file upload form where users can upload files from the frontend. What we need:
- a page called
upload
where we put the form - a page called
storage
where the files will be uploaded to - a template with the form
- a file blueprint with rules
- a controller with the form logic
Allowing users to upload files from the frontend without authentication is not without risks. You should be very careful what file types you allow, where you store them and how you name them. Ideally, you prevent access to those files before you have checked they are safe.
In this example, we will upload the files to the content
folder, but if your use case or hosting allows it, consider uploading files to a location outside the web root.
The upload
and storage
pages
Create an upload
page with an upload.txt
content file. For our means, we only need a title in the content file, the rest is up to you. You could, for example, store an introductory text.
If you want to access the page in the Panel, you also need to create a blueprint for the page. We will skip this step here.
Additionally, create a storage
page. This is the page to which we upload the files.
The upload template
Our upload.php
template contains the form and will display error messages if something goes wrong or a success message if the form was successfully submitted.
The form is displayed by default and hidden once the upload was successful. We also include a honeypot field to ensure a minimum level of spam bot protection.
The honeypot field needs to be positioned off-screen, so we need some styles for it. Add this to your stylesheet (you can also change the class and styling, of course).
The file blueprint
To easily validate if the uploaded files conform to what we want to allow, we set up a files blueprint with the accept
option:
The upload controller
All the form and upload logic is handled by our controller:
Let's break this down step by step:
First we set up our variables $alerts
and $success
which we will display in our template by returning them from our controller:
Checking for the right request
To make sure we are responding to the right request, we listen for a POST
request and whether the request came from our form. Then, we check if a bot got trapped in our honeypot. In this case, we send him back to the page and stop script execution.
Checking for limits
In our example, we want to limit uploads to a maximum of three files per upload. If this limit is exceeded we add an error message and return all messages to end the controller:
Checking for duplicates
Then we loop through $uploads
to check each uploaded file individually. We want to prevent that the same file gets uploaded again and again. To do so, we compare the safe name of the uploaded file to the filename of existing files in the folder minus the prefix (explained below), the mime type and the size.
If a duplicate is found, we add an error message for the current file and continue in the loop with the next uploaded file.
Creating the file
If all went well so far, we try to upload and create the file in a try - catch
block using Kirby's $page->createFile()
method.
The $page->createFile()
method requires the source
attribute, all other parameters are optional. However, we use the template
option here to assign the file blueprint we created earlier. It does all the checking for the allowed mime types and sizes for us.
To obfuscate the file name, we add a prefix to the original filename. Kirby automatically takes care of converting the filename to a safe name. Prefixing the filename makes it hard for the user to guess the filename and opening the file in the browser after upload. This makes it much harder for users to upload a malicious file and call it directly afterwards since the exact name will not be known to them.
We also store the upload date and time as meta data in the content
array. If you want, you can also store additional information.
If everything was successful, we set the success message.
As an additonal security measure, we can prohibit access to the storage
page using a route.
Catching additional errors
If the $page->createFile()
method encounters any errors or rule violations, it will throw an error. By wrapping our code in a try - catch
block, we make sure to get the error message and add it to our alerts array:
Next steps
This recipes presented a basic way to implement file uploads from the frontend. Of course, there are more steps that could follow this:
- Add additional fields to the form
- Combine it with a user registration form
- Add access restrictions to the
storage
page