tags:

views:

104

answers:

8

I have a pdf file on my server. I want to create such link that, user can click on it and download that pdf file. I am using Zend frame work with Php.

+1  A: 

I don't know if Zend provides a class for this. In general this is achieved by using the header function. have a look at the PHP site:

http://php.net/manual/en/function.header.php

There are some examples of downloading files.

Good luck!

Stegeman
+1  A: 

place this code inside a php file and call it f.e. "download.php":

<?php

$fullPath = "path/to/your/file.ext";

if ($fd = fopen ($fullPath, "r")) {

    $fsize = filesize($fullPath);
    $path_parts = pathinfo($fullPath);
    $ext = strtolower($path_parts["extension"]);

    header("Content-type: application/pdf");
    header("Content-Disposition: attachment; filename=\"".$path_parts["basename"]."\"");            
    header("Content-length: $fsize");
    header("Cache-control: private");

    while(!feof($fd)) {
        $buffer = fread($fd, 2048);
        echo $buffer;
    }
}

fclose ($fd);
exit;

?>

Example: place this kind of link into the document where the file download is offered:

<a href="download.php?download_file=some_file.pdf">Download here</a>

More Detail:

http://www.finalwebsites.com/forums/topic/php-file-download

NAVEED
+2  A: 
        header("Content-type: application/pdf");
        header("Content-Disposition: attachment; filename=filename.pdf"); 
        $pdfiledata = file_get_contents($filename);
        echo $pdfiledata;
thevikas
Short, concise and to the point, though I'd rather use `fpassthru` http://us3.php.net/manual/en/function.fpassthru.php than `file_get_contents` http://us3.php.net/manual/en/function.file-get-contents.php
Kristoffer S Hansen
off-topic: please suggest more about fpassthru. is it safer? never knew about it.
thevikas
This is horrible for performance, dumping file contents into memory. I wish I had downvotes left.
NullUserException
So... What if the PDF's 50 megabytes, and your `memory_limit` is 10meg?
Marc B
A: 

Zend_Pdf

Alexander.Plutov
+1  A: 

I have been using this reusable action helper for sending files to users. It works well and is better than messing around with headers yourself.

Matt Way
+1  A: 

I assume there is a reason you can't just link to the pdf directly, such as the need to authenticate the user. In that case, you will need to set the correct headers, and, as others have noted, you can use get_file_contents to serve the pdf. However, using get_file_contents requires that you read the file into memory before you send the response. If the file is large, or if you receive many requests at once, you can easily run out of memory. A great solution if you're using Apache or Lighttpd is to use XSendFile. With XSendFile you set the X-Sendfile response header to the file's path, and your web server serves the file directly from disk - without revealing the file's location on disk.

The problem with this solution is that the module must be installed on Apache, and it must be configured to work with Lighttpd.

Once XSendFile is installed, your Zend Framework action code would look something like the following:

// user auth or other code here -- there has to be a reason you're not
// just pointing to the pdf file directly

$this->_helper->layout->disableLayout();
$this->_helper->viewRenderer->setNoRender();

$this->getResponse()->setHeader('Content-type', 'application/pdf')
                    ->setHeader('X-Sendfile', 'path-to-file')
                    ->sendResponse();
Isaac
+1  A: 

I've always used the become_file_download function from the open source BalPHP library which you can plug and play (stick) into your project right away. It allows for:

  • Instant download of the file
  • Easily specify params like content-type, cache life, buffer size.
  • Supports multi-part transactions allowing for faster downloads which won't kill your server for big file transfers.
  • Supports pause/resume functionality.
  • And etags for caching:

You can find the latest version here: http://github.com/balupton/balphp/blob/master/trunk/lib/core/functions/_files.funcs.php#L75

And here it is copy and pasted as of 27 August, 2010:

/**
 * Become a file download, should be the last script that runs in your program
 *
 * http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
 *
 * @version 3, July 18, 2009 (Added suport for data)
 * @since 2, August 11, 2007
 *
 * @author Benjamin "balupton" Lupton <[email protected]> - {@link http://www.balupton.com/}
 *
 * @param string    $file_path
 * @param string    $content_type
 * @param int       $buffer_size
 * @param string    $file_name
 * @param timestamp $file_time
 *
 * @return boolean  true on success, false on error
 */
function become_file_download ( $file_path_or_data, $content_type = NULL, $buffer_size = null, $file_name = null, $file_time = null, $expires = null ) {

    // Prepare
    if ( empty($buffer_size) )
        $buffer_size = 4096;
    if ( empty($content_type) )
        $content_type = 'application/force-download';

    // Check if we are data
    $file_descriptor = null;
    if ( file_exists($file_path_or_data) && $file_descriptor = fopen($file_path_or_data, 'rb') ) {
        // We could be a file
        // Set some variables
        $file_data = null;
        $file_path = $file_path_or_data;
        $file_name = $file_name ? $file_name : basename($file_path);
        $file_size = filesize($file_path);
        $file_time = filemtime($file_path);
        $etag = md5($file_time . $file_name);
    } elseif ( $file_name !== null ) {
        // We are just data
        $file_data = $file_path_or_data;
        $file_path = null;
        $file_size = strlen($file_data);
        $etag = md5($file_data);
        if ( $file_time === null )
            $file_time = time();
        else
            $file_time = ensure_timestamp($file_time);
    } else {
        // We couldn't find the file
        header('HTTP/1.1 404 Not Found');
        return false;
    }

    // Prepare timestamps
    $expires = ensure_timestamp($expires);

    // Set some variables
    $date = gmdate('D, d M Y H:i:s') . ' GMT';
    $expires = gmdate('D, d M Y H:i:s', $expires) . ' GMT';
    $last_modified = gmdate('D, d M Y H:i:s', $file_time) . ' GMT';

    // Say we can go on forever
    set_time_limit(0);

    // Check relevance
    $etag_relevant = !empty($_SERVER['HTTP_IF_NONE_MATCH']) && trim(stripslashes($_SERVER['HTTP_IF_NONE_MATCH']), '\'"') === $etag;
    $date_relevant = !empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $file_time;

    // Handle download
    if ( $etag_relevant || $date_relevant ) {
        // Not modified
        header('HTTP/1.0 304 Not Modified');
        header('Status: 304 Not Modified');

        header('Pragma: public');
        header('Cache-Control: private');

        header('ETag: "' . $etag . '"');
        header('Date: ' . $date);
        header('Expires: ' . $expires);
        header('Last-modified: ' . $last_modified);
        return true;
    } elseif ( !empty($_SERVER['HTTP_RANGE']) ) {
        // Partial download


        /*
         * bytes=0-99,500-1499,4000-
         */

        // Explode RANGE
        list($size_unit,$ranges) = explode($_SERVER['HTTP_RANGE'], '=', 2);

        // Explode RANGES
        $ranges = explode(',', $ranges);

        // Cycle through ranges
        foreach ( $ranges as $range ) {
            // We have a range


            /*
             * All bytes until the end of document, except for the first 500 bytes:
             * Content-Range: bytes 500-1233/1234
             */

            // Set range start
            $range_start = null;
            if ( !empty($range[0]) && is_numeric($range[0]) ) {
                // The range has a start
                $range_start = intval($range[0]);
            } else {
                $range_start = 0;
            }

            // Set range end
            if ( !empty($range[1]) && is_numeric($range[1]) ) {
                // The range has an end
                $range_end = intval($range[1]);
            } else {
                $range_end = $file_size - 1;
            }

            // Set the range size
            $range_size = $range_end - $range_start + 1;

            // Set the headers
            header('HTTP/1.1 206 Partial Content');

            header('Pragma: public');
            header('Cache-Control: private');

            header('ETag: "' . $etag . '"');
            header('Date: ' . $date);
            header('Expires: ' . $expires);
            header('Last-modified: ' . $last_modified);

            header('Content-Transfer-Encoding: binary');
            header('Accept-Ranges: bytes');

            header('Content-Range: bytes ' . $range_start . '-' . $range_end . '/' . $file_size);
            header('Content-Length: ' . $range_size);

            header('Content-Type: ' . $content_type);
            if ( $content_type === 'application/force-download' )
                header('Content-Disposition: attachment; filename=' . urlencode($file_name));

            // Handle our data transfer
            if ( !$file_path ) {
                // We are using file_data
                echo substr($file_data, $range_start, $range_end - $range_start);
            } else {
                // Seek to our location
                fseek($file_descriptor, $range_start);

                // Read the file
                $remaining = $range_size;
                while ( $remaining > 0 ) {
                    // 0-6   | buffer = 3 | remaining = 7
                    // 0,1,2 | buffer = 3 | remaining = 4
                    // 3,4,5 | buffer = 3 | remaining = 1
                    // 6     | buffer = 1 | remaining = 0


                    // Set buffer size
                    $buffer_size = min($buffer_size, $remaining);

                    // Output file contents
                    echo fread($file_descriptor, $buffer_size);
                    flush();
                    ob_flush();

                    // Update remaining
                    $remaining -= $buffer_size;
                }
            }
        }
    } else {
        // Usual download


        // header('Pragma: public');
        // header('Cache-control: must-revalidate, post-check=0, pre-check=0');
        // header('Expires: '.      gmdate('D, d M Y H:i:s').' GMT');


        // Set headers
        header('HTTP/1.1 200 OK');

        header('Pragma: public');
        header('Cache-Control: private');

        header('ETag: "' . $etag . '"');
        header('Date: ' . $date);
        header('Expires: ' . $expires);
        header('Last-modified: ' . $last_modified);

        header('Content-Transfer-Encoding: binary');
        header('Accept-Ranges: bytes');

        header('Content-Length: ' . $file_size);

        header('Content-Type: ' . $content_type);
        if ( $content_type === 'application/force-download' )
            header('Content-Disposition: attachment; filename=' . urlencode($file_name));

        // Handle our data transfer
        if ( !$file_path ) {
            // We are using file_data
            echo $file_data;
        } else {
            // Seek to our location
            // Read the file
            $file_descriptor = fopen($file_path, 'r');
            while ( !feof($file_descriptor) ) {
                // Output file contents
                echo fread($file_descriptor, $buffer_size);
                flush();
                ob_flush();
            }
        }
    }

    // Close the file
    if ( $file_descriptor )
        fclose($file_descriptor);

    // Done
    return true;
}

It also depends on another plug and play function called ensure_timestamp which you can find here: http://github.com/balupton/balphp/blob/master/trunk/lib/core/functions/_datetime.funcs.php#L31

/**
 * Gets the days between two timestamps
 * @version 1, January 28, 2010
 * @param mixed $value
 * @return timestamp
 */
function ensure_timestamp ( $value = null ) {
    $result = null;

    if ( $value === null ) $result = time();
    elseif ( is_numeric($value) ) $result = $value;
    elseif ( is_string($value) ) $result = strtotime($value);
    else throw new Exception('Unknown timestamp type.');

    return $result;
}
balupton
A: 

You should NOT dump the contents of a file in memory using file_get_contents() then echo it. You are wasting resources doing that. Here's a better way to do this:

function serveFile($file) {

    header('Content-Type: application/pdf');  
    header('Content-Disposition: attachment; filename=file.pdf';
    header('Content-Length: ' . filesize($file));
    header('Last-Modified: ' .
            gmdate("D, d M Y H:i:s \G\M\T", filemtime($file)));

    readfile($file);
    exit;
}

// now call this
serveFile('myfile.pdf');

It also fills relevant headers like Content-Length and Last-Modified which will make this closer to what a client sees when the file is served by the server rather than PHP.

NullUserException