Someone else asked how to put an internal folder for file uploading with Expression Engine such as:
/home/yourserver/file-folder
Instead of a public one such as:
/home/yourserver/public_html/file-folder
The obvious answer was that this would not work because the user would not have access to that file on your website since it’s not public.
Why would you want this?
For internal files that should not be public. Example, customer files, you don’t want them to be able to link or be indexed by search engines. It’s common for many systems to use an internal folder for such files that is not available through your web server in a public way.
Tonight I had a bit of time to play around, so I decided to come up with a simple solution. This way you can configure an internal folder for your files uploads and then restrict it to only logged in members. This will not require any changes to your existing template or file links.
For example, let’s create a template called ‘view-file”. Make sure to enable PHP on the template and select the parsing option as Input
Restrict the template on your EE settings to the users or roles you want to be able to access. This way only authenticated users will be able to download the files.
Create your internal file uploader, example:” /home/yourserver/file-folder
This should be above your public web path.
Assuming this template is in a template group like ‘customers’ available on example.com/customers/view-file
Your new setting should the following:
{base_url}customers/view-file?f=/
Note: I’m not using the extension here because it looks better for URL’s but if your server does not support this you should use:
{base_url}customers/view-file.php?f=/
Note the trailing slash is added automatically by the EE control panel, but my code will try to fix this. Don’t bother with it.
Next set your upload path to your internal file directory such as:
/home/yourserver/file-folder
Save your settings.
<?php
# Get internal files for Expression Engine for logged users only
// Get from URL
$FileDownload = $_GET["f"];
// We remove the EE trailing slash /
$eeFile = substr($FileDownload, 1);
// Get the file extension
$ext = substr(strrchr($eeFile, '.'), 1);
// Check if file is allowed. Add allowed extensions here
$allowed = array('gif', 'png', 'jpg');
if (!in_array($ext, $allowed)) {
echo 'ERROR: Forbidden file!';
goto end;
}
// Internal file path on server. Replace with your existing upload folder from EE
$file = "/home/yourserver/ee-file-upload-folder/$eeFile";
// Check if file exists or fail otherwise
if (file_exists($file)) {
// Continue
} else {
echo "ERROR: File not found!";
goto end;
}
// Stream to browser
if (file_exists($file)) {
header('Content-Description: File Transfer');
//header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
//header('Pragma: public');
//header('Content-Length: ' . filesize($file));
readfile($file);
}
?>
{exp:http_header content_type="application/octet-stream"}
<?php
// EE does not like PHP exit codes
end:
?>
Change the line in the code to your EE upload folder (leave the $eeFile variable for the file name)
$file = "/home/yourserver/ee-file-upload-folder/$eeFile";
Done!
Your files are now private and only logged users can download them.
If you check the code, it removes the trailing slash from EE, it actually works fine without that but just in case some browsers don’t support it, we remove it to make sure the file passed has the proper name.
Since we are passing anything here as a GET, I added a security check that will check only for the valid extensions. We don’t want someone trying to tamper with the data they can pass to your installation. You can add more extensions by editing the $allowed field.
Improvements that could be added:
The code will send the file as a download to the browser. This can probably be changed in case you want to display the content in the browser (like images) but this will probably fail if you then try to link to another type that should be downloaded. Ideally the code could, should detect which files can be downloaded or output to the browser, but it would require further coding.
For some reason when the file does not exist, or you call the URL directly Expression Engine will output a session with the result. Not very clean, not sure why, but I created this in one hour and did not have time to investigate yet. Probably something related to the headers output.
You could also run the code using EE variables conditionals if you like that check if a user is logged in, for a specific user only or members.
One problem is that while Expression Engine has in the database the owner of the file that uploaded it, there are actually no tags to check this on a template. The issue is that one user can potentially download files from others (assuming they know the file name). A possible quick fix would be renaming the file on upload directly from your EE template to random characters. Will not solve the problem if someone knows the link but at least it can avoid someone from trying to guess popular file names.
For this reason this does not protect the files from other logged-in users. It just keeps them private, so they cannot be viewed without having to log in or linked on public sites.
I just noticed the code works fine outside EE but inside the template it seems to corrupt the file download. It probably has to do that EE does not respect the PHP headers. Had a similar problem with a JSON output in the past.
There is a plugin for this here: https://docs.expressionengine.com/latest/add-ons/http-header.html
I will try to see how to fix this, not sure if this is the issue, but files seems corrupt after download. Code works fine outside of Expression Engine the same files uploaded by EE.
UPDATE FIX
Fixed the corrupt download issue. It’s related to the PHP exit codes in EE which adds an extra EE tag (session?) to the file binary data, and also the headers are sent as HTML instead of a stream. Fixed both issues, and file downloads should now be fine.
I have updated the code above.
But you need to install the Header module add-on in your EE website to make it work:
https://docs.expressionengine.com/latest/add-ons/http-header.html
Future Improvements:
EE does not like the die or exit codes in PHP. It will add extra garbage to the output. If you remove them, then your messages will not be displayed since the header will not be HTML.
UPDATE FIX
Fixed the errors codes. Now they display correctly without using PHP exit or die codes that stops EE execution. Since this was still reading the EE header tag it was not working before as it tried to download the page instead of displaying the errors.
The solution was to set PHP as Input in the parsing stage.
So make sure when you enable PHP on the file template to select Input instead of Output. Code is now updated with the fix.
Packet Tide owns and develops ExpressionEngine. © Packet Tide, All Rights Reserved.