We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Download a file [SOLVED]

Hello, I'm really lost trying to get a file download. I have my files for download in this file estructure:

/app
    /controllers
    /models
    /views
/var
    /user_documents <- here
/public
   ...

My files in /user_documents have numbers for names: 1, 2, 3.. And the real name is store in the database. The thing is I don't know how to get to a file like /var/user_documents/1 so I'm able to read it. I don't know if I need to set some routes or permissions there.

When I read it I want to use this code I saw in the forum for downloading it, but I don't know if it's the right way either:

$this->view->setRenderLevel(\Phalcon\Mvc\View::LEVEL_NO_RENDER);
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Description: File Transfer');
header('Content-type: ' . $filetype);
header('Content-length: ' . $filesize);
header('Content-Disposition: attachment; filename="'.$filename.'"');
readfile($file);
die();

Thanks.

If all the files are in one directory, then the URL could be something like this:

https://domain.of.app.tld/file/download/1

Your "fileController.php" file can then have a "downloadAction" method that gets passed "1" as the parameter. You can then do your database stuff and run the code you posted above.



8.9k

In my /public/index.php file I have included the following

//define path to application directory
defined('ROOT_PATH')
    || define('ROOT_PATH' , realpath(dirname(__FILE__)).'/../');

//define path to var directory
defined('VAR_PATH')
    || define('VAR_PATH' , ROOT_PATH.'var');

//define path to library directory
defined('LIBRARY_PATH')
    || define('LIBRARY_PATH' , ROOT_PATH.'library');

So if you added the same in there, I assume you could read the file via:

readfile(VAR_PATH.'user_documents/'.$filename);

And if you are going to use user_documents directory often, you could as well use DEFINE and create a variable for that as well.



18.9k

Yes quasipickle, I've figure it out to that. metialb that worked for me perfect. Thanks.

I copy here the last code for downloading I use:

// Based on Armand Niculescu download.php - Copyright 2012 - MediaDivision.com
public function downloadAction($UsAtCod, $mode = "attachment")
    {
        $res = UserAttachment::findFirst("UsAtCod={$UsAtCod}");
        $filename = $res->UsAtFilename;

        $file_name = pathinfo($filename, PATHINFO_FILENAME);
        $file_ext = pathinfo($filename, PATHINFO_EXTENSION);

        $file_path = VAR_PATH . "/user_documents/{$UsAtCod}";

        if(is_file($file_path)){
            $file_size  = filesize($file_path);
            $file = @fopen($file_path, "rb");

            if ($file){
                // set the headers, prevent caching
                header("Pragma: public");
                header("Expires: -1");
                header("Cache-Control: public, must-revalidate, post-check=0, pre-check=0");

                // set appropriate headers for attachment or streamed file
                if ($mode == "attachment")
                    header("Content-Disposition: attachment; filename=\"{$filename}\"");
                else
                    header("Content-Disposition: inline; filename=\"{$filename}\"");

                // set the mime type based on extension, add yours if needed.
                $ctype_default = "application/octet-stream";
                $content_types = array(
                        "exe" => "application/octet-stream",
                        "zip" => "application/zip",
                        "mp3" => "audio/mpeg",
                        "mpg" => "video/mpeg",
                        "avi" => "video/x-msvideo",
                        "pdf"  => "application/pdf",
                );

                $ctype = isset($content_types[$file_ext]) ? $content_types[$file_ext] : $ctype_default;
                header("Content-Type: {$ctype}");

                // check if http_range is sent by browser (or download manager)
                if(isset($_SERVER['HTTP_RANGE'])) {
                    list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);

                    if ($size_unit == 'bytes') {
                        // multiple ranges could be specified at the same time, but for simplicity only serve the first range
                        list($range, $extra_ranges) = explode(',', $range_orig, 2);
                    } else {
                        $range = '';
                        header('HTTP/1.1 416 Requested Range Not Satisfiable');

                        exit;
                    }
                } else
                    $range = '';

                // figure out download piece from range (if set)
                list($seek_start, $seek_end) = explode('-', $range, 2);

                // set start and end based on range (if set), else set defaults
                // also check for invalid ranges.
                ob_clean();

                $seek_end   = (empty($seek_end)) ? ($file_size - 1) : min(abs(intval($seek_end)),($file_size - 1));
                $seek_start = (empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)), 0);

                // Only send partial content header if downloading a piece of the file (IE workaround)
                if ($seek_start > 0 || $seek_end < ($file_size - 1)) {
                    header('HTTP/1.1 206 Partial Content');
                    header('Content-Range: bytes ' . $seek_start . '-' . $seek_end . '/' . $file_size);
                    header('Content-Length: ' . ($seek_end - $seek_start + 1));
                } else
                    header("Content-Length: {$file_size}");

                header('Accept-Ranges: bytes');

                set_time_limit(0);
                fseek($file, $seek_start);

                while(!feof($file)) {
                    print(@fread($file, 1024 * 8));

                    ob_flush();
                    flush();

                    if (connection_status() != 0) {
                        @fclose($file);
                        exit;
                    }
                }

                // file save was a success
                @fclose($file);
                exit;
            } else {
                // file couldn't be opened
                header("HTTP/1.0 500 Internal Server Error");
                exit;
            }
        } else
            // file does not exist
            header("HTTP/1.0 404 Not Found");
            exit;
    }

Thanks!



8.9k

@ivanblue,

I am glad I could help. Regarding your code, if you have the freedom of controlling/modifying your server, you might wanna check https://tn123.org/mod_xsendfile/ -- I think it works better and definitely needs less lines of code than what you currently have in there.



18.9k

Thanks metialb, I'll check on that :D

@ivanblue Would you please help me? I am creating excel files (xlsx) using phpspreadsheet and I would like to be able to download those files into my client's computer. I tried with your code but it does not working. I will appriciate your help. Thank you.

@ivanblue Would you please help me? I am creating excel files (xlsx) using phpspreadsheet and I would like to be able to download those files into my client's computer. I tried with your code but it does not working. I will appriciate your help. Thank you.

I'd suggest making a new thread, as this one is 5 years old.