/** * Calling this will stream the file to the client. * The parameter is a bitstream dao. * Optional second parameter is the download offset in bytes. * * @param BitstreamDao $bitstream * @param int $offset * @param bool $incrementDownload * @throws Zend_Exception */ public function download($bitstream, $offset = 0, $incrementDownload = false) { // Disable gzip output on apache servers (otherwise no progress in browser) if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } $mimetype = $bitstream->getMimetype(); $path = $bitstream->getAssetstore()->getPath() . '/' . $bitstream->getPath(); $name = $bitstream->getName(); if (!file_exists($path)) { throw new Zend_Exception('Unable to find file on the disk'); } $chunkSize = 1024 * 64; $fileSize = UtilityComponent::fileSize($path); $handle = fopen($path, 'rb'); if ($handle === false) { throw new Zend_Exception('Unable to open the file'); } if (!$this->testingmode) { // don't send any headers in testing mode since it will break it $modified = gmdate('D, d M Y H:i:s') . ' GMT'; $contentType = $mimetype; header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Last-Modified: ' . $modified); // if pdf set the content-type accordingly if (!isset($contentType) && pathinfo($name, PATHINFO_EXTENSION) == 'pdf') { $contentType = 'application/pdf'; $enableContentDisposition = false; } if (!isset($contentType)) { $contentType = 'application/octet-stream'; } // Hack for .vsp files (for OSA) if (!isset($contentType) && strlen($name) > 4 && substr($name, strlen($name) - 4, 4) == '.vsp') { $contentType = 'application/isp'; } $agent = env('HTTP_USER_AGENT'); if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent) || preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) { header('Content-Type: ' . $contentType); header('Content-Disposition: attachment; filename="' . $name . '"'); header('Expires: 0'); header('Accept-Ranges: bytes'); header('Cache-Control: private', false); header('Pragma: private'); $httpRange = env('HTTP_RANGE'); if (isset($httpRange)) { // HTTP range is of the form "bytes=n-" where n is the offset list(, $range) = explode('=', $httpRange); $firstByte = strstr($range, '-', true); $lastByte = $fileSize - 1; $length = $fileSize - $firstByte; header('HTTP/1.1 206 Partial Content'); header('Content-Length: ' . $length); header('Content-Range: bytes ' . $firstByte . '-' . $lastByte . '/' . $fileSize); fseek($handle, $firstByte); } else { header('Content-Length: ' . $fileSize); } } else { header('Accept-Ranges: bytes'); header('Expires: 0'); header('Content-Type: ' . $contentType); header('Content-Length: ' . $fileSize); if (!isset($enableContentDisposition) || $enableContentDisposition == true) { header('Content-Disposition: attachment; filename="' . $name . '"'); } if (isset($httpRange)) { list(, $range) = explode('=', $httpRange); $firstByte = strstr($range, '-', true); $lastByte = $fileSize - 1; $length = $fileSize - $firstByte; header('HTTP/1.1 206 Partial Content'); header('Content-Length: ' . $length); header('Content-Range: bytes ' . $firstByte . '-' . $lastByte . '/' . $fileSize); fseek($handle, $firstByte); } } } ignore_user_abort(true); // must call this so the script doesn't end as soon as connection closed // close the database connection so we don't get too many connections problems Zend_Registry::get('dbAdapter')->closeConnection(); session_write_close(); // unlock session writing for concurrent access // kill the whole ob stack (Zend uses double nested output buffers) while (!$this->testingmode && ob_get_level() > 0) { ob_end_clean(); } if (is_numeric($offset) && $offset > 0 && $offset <= $fileSize) { fseek($handle, $offset); } while (!feof($handle) && connection_status() == 0) { echo fread($handle, $chunkSize); } if ($incrementDownload && feof($handle)) { // Only record downloads that actually complete /** @var ItemModel $itemModel */ $itemModel = MidasLoader::loadModel('Item'); $itemModel->incrementDownloadCount($bitstream->getItemrevision()->getItem()); } fclose($handle); if (!$this->testingmode) { // don't exit if we are in testing mode exit; } }