Search

A new extension, "Private Upload" is now available for download. Comments and feedback can be left here but if you discover any issues, please post it on the issue tracker.

Private File Upload

What does this extension do?

Ever asked private data from your visitors in a upload form? Stuff like a resume? Or pictures? Or a copy of their drivers licence? Or do you need to store some data somewhere safe to be sure it doesn't get found or indexed?

Then you'd better make sure to store it properly! Don't store it in your workspace/uploads-folder, since that is publicly accessible, but store it on a folder on your server on a lower leven than your public_html or httpdocs-folder. This way, it's not accessible by just entering the URL to it.

How does it work?

It's a mod of a regular upload-field, with the difference that instead of selecting a location to store your file, you can enter the exact path on your server, allowing for storing in folders outside of your public_html or httpdocs.

The file is only downloadable in Symphony, when you're logged in, by clicking in the URL in the publish screen.

Please note

  • Not all servers allow storing content outside your public_html folder. Check your specific host for more information about this.
  • Don't forget to change the server location when you move your site from development to production environment.
  • If you wish to access the files on the frontend (for example, after a member login), you need to write some custom event-code for that. Check the file content/content.index.php for more information on this.

Or... put a .htaccess in the upload folder containing

order allow,deny
deny from all

:-)

But it still needs to be accesible for the client (after a login)

@kanduvisla, nice extension. I posted an issue regarding the Remove File action.

I'd also love a more detailed example of how to create the custom event, using this code:

    if (file_exists($_GET['file'])) {
        header('Content-Description: File Transfer');
        header('Content-Disposition: attachment; filename='.basename($_GET['file']));
        header('Content-Transfer-Encoding: binary');
        header('Expires: 0');
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        header('Content-Length: ' . filesize($_GET['file']));
        ob_clean();
        flush();
        readfile($_GET['file']);
        exit;
    } else {
        die('File does not exist!');
    }

What does the $_GET variable expect? The full path to the file?

Actually, I think I got it:

<?php

    require_once(TOOLKIT . '/class.event.php');

    Class eventdownload_file extends Event{

        const ROOTELEMENT = 'download-file';

        public $eParamFILTERS = array(

        );

        public static function about(){
            return array(
                'name' => 'Download File',
                'author' => array(
                    'name' => 'Stephen Bau',
                    'website' => 'http://www.domain7.com/',
                    'email' => 'stephen@domain7.com'),
                'version' => '1.0',
                'release-date' => '2011-05-05T06:02:34+00:00',
                'trigger-condition' => 'action[download-file]'
            );
        }

        public static function getSource(){
        }

        public static function allowEditorToParse(){
            return false;
        }

        public static function documentation(){
            return '
        <h3>Download File</h3>
        <p>Attach this event to a page with a private upload file and use a <code>GET</code>.variable <code>?file=/path/to/filename.ext</code> to download the file.</p>
        <pre class="XML"><code>
&lt;p class="resource-url">Download: 
    &lt;a href="{$current-url}?file={file/@path}/{file/filename}">
        &lt;xsl:value-of select="file/filename" />
    &lt;/a>
&lt;/p>
        </code></pre>';
        }

        public function load(){
            if(isset($_GET['file'])) return $this->__trigger();
        }

        protected function __trigger(){

            if (file_exists($_GET['file'])) {
                header('Content-Description: File Transfer');
                header('Content-Disposition: attachment; filename='.basename($_GET['file']));
                header('Content-Transfer-Encoding: binary');
                header('Expires: 0');
                header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
                header('Pragma: public');
                header('Content-Length: ' . filesize($_GET['file']));
                ob_clean();
                flush();
                readfile($_GET['file']);
                exit;
            } else {
                die('File does not exist!');
            }

        }

    }

Although a PDF file downloads like this filename.pdf.html in Safari. :-(

To solve the problem of the .html extension being appended to file names, I add a Content-Type header. I wasn't sure how best to send the MIME type, so I just found the file extension and used a switch statement to set the MIME type. The event now looks like this:

<?php

    require_once(TOOLKIT . '/class.event.php');

    Class eventdownload_file extends Event{

        const ROOTELEMENT = 'download-file';

        public $eParamFILTERS = array(

        );

        public static function about(){
            return array(
                'name' => 'Download File',
                'author' => array(
                    'name' => 'Stephen Bau',
                    'website' => 'http://www.domain7.com/',
                    'email' => 'stephen@domain7.com'),
                'version' => '1.0',
                'release-date' => '2011-05-05T06:02:34+00:00',
                'trigger-condition' => 'action[download-file]'
            );
        }

        public static function getSource(){
        }

        public static function allowEditorToParse(){
            return false;
        }

        public static function documentation(){
            return '
        <h3>Download File</h3>
        <p>Attach this event to a page with a private upload file and use a <code>GET</code>.variable <code>?file=filename.ext</code> to download the file.</p>
        <pre class="XML"><code>
&lt;p class="resource-url">Download: 
    &lt;a href="{$current-url}?file={file/filename}">
        &lt;xsl:value-of select="file/filename" />
    &lt;/a>
&lt;/p>
        </code></pre>';
        }

        public function load(){
            if(isset($_GET['file'])) return $this->__trigger();
        }

        protected function __trigger(){

            $filename = $_GET['file'];
            $path = DOCROOT.'/workspace/assets/resources/files/'.$filename;
            $path_parts = pathinfo($path);
            $extension = $path_parts['extension'];

            switch ($extension) {
                case 'doc':
                    $type = 'application/doc';
                    break;
                case 'pdf':
                    $type = 'application/pdf';
                    break;
                case 'rtf':
                    $type = 'text/richtext';
                    break;
                case 'txt':
                    $type = 'text/plain';
                    break;
            }

            if (file_exists($path)) {
                header('Content-Description: File Transfer');
                header('Content-Disposition: attachment; filename='.$filename);
                header('Content-Transfer-Encoding: binary');
                header('Content-Type: '.$type);
                header('Expires: 0');
                header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
                header('Pragma: public');
                header('Content-Length: ' . filesize($path));
                ob_clean();
                flush();
                readfile($path);
                exit;
            } else {
                die('File does not exist!');
            }

        }

    }

And I'm just using the core upload field with an .htaccess file in the file upload directory:

order allow,deny
deny from all

ErrorDocument 403 http://www.example.com/forbidden/
And I'm just using the core upload field with an .htaccess file in the file upload directory:

Hi Stephen, does this mean your NOT using the private upload extension here and just a plain old upload field?

Correct. I'm not using the private upload extension. Just the plain old upload field.

Figured! thanks for the code snippet! works a treat! :) Happy New Year

I need to build a site where only members have access to download uploaded files. Problem now is if a user knows the path, they can download. Is it possible with this extension only to allow download from members with approperiate rights? Thanks for the answer.

yes, this extension allows for uploads outside your httpdocs/public_html-folder. For members to allow downloading, you can do something like this in a custom event or datasource:

$userIsLoggedIn = someCustomCodeToDetermineIfTheUserIsLoggedInOrNot();
if($userIsLoggedIn) {
    if (file_exists($_GET['file'])) {
        header('Content-Description: File Transfer');
        header('Content-Disposition: attachment; filename='.basename($_GET['file']));
        header('Content-Transfer-Encoding: binary');
        header('Expires: 0');
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        header('Content-Length: ' . filesize($_GET['file']));
        ob_clean();
        flush();
        readfile($_GET['file']);
        exit;
    } else {
        die('File does not exist!');
    }    
}

Anyone interested in helping us creating the custom event? PM info@e-xperience.be

Private Upload updated to version 1.1 on 15th of May 2012

  • Updated for S2.3

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details