MediaView.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. /**
  3. * Methods to display or download any type of file
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package cake.libs.view
  16. * @since CakePHP(tm) v 1.2.0.5714
  17. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  18. */
  19. App::uses('View', 'View');
  20. App::uses('CakeRequest', 'Network');
  21. /**
  22. * Media View provides a custom view implementation for sending files to visitors. Its great
  23. * for making the response of a controller action be a file that is saved somewhere on the filesystem.
  24. *
  25. * An example use comes from the CakePHP internals. MediaView is used to serve plugin and theme assets,
  26. * as they are not normally accessible from an application's webroot. Unlike other views, MediaView
  27. * uses several viewVars that have special meaning:
  28. *
  29. * - `id` The filename on the server's filesystem, including extension.
  30. * - `name` The filename that will be sent to the user, specified without the extension.
  31. * - `download` Set to true to set a `Content-Disposition` header. This is ideal for file downloads.
  32. * - `extension` The extension of the file being served. This is used to set the mimetype
  33. * - `path` The absolute path, including the trailing / on the server's filesystem to `id`.
  34. * - `mimeType` The mime type of the file if CakeResponse doesn't know about it.
  35. *
  36. * ### Usage
  37. *
  38. * {{{
  39. * class ExampleController extends AppController {
  40. * function download () {
  41. * $this->view = 'Media';
  42. * $params = array(
  43. * 'id' => 'example.zip',
  44. * 'name' => 'example',
  45. * 'download' => true,
  46. * 'extension' => 'zip',
  47. * 'path' => APP . 'files' . DS
  48. * );
  49. * $this->set($params);
  50. * }
  51. * }
  52. * }}}
  53. *
  54. * @package cake.libs.view
  55. */
  56. class MediaView extends View {
  57. /**
  58. * Indicates whether response gzip compression was enabled for this class
  59. *
  60. * @var boolean
  61. */
  62. protected $_compressionEnabled = false;
  63. /**
  64. * Reference to the Response object responsible for sending the headers
  65. *
  66. * @var CakeResponse
  67. */
  68. public $response = null;
  69. /**
  70. * Constructor
  71. *
  72. * @param object $controller The controller with viewVars
  73. */
  74. function __construct($controller = null) {
  75. parent::__construct($controller);
  76. if (is_object($controller) && isset($controller->response)) {
  77. $this->response = $controller->response;
  78. } else {
  79. $this->response = new CakeResponse;
  80. }
  81. }
  82. /**
  83. * Display or download the given file
  84. *
  85. * @return mixed
  86. */
  87. function render() {
  88. $name = $download = $extension = $id = $modified = $path = $cache = $mimeType = $compress = null;
  89. extract($this->viewVars, EXTR_OVERWRITE);
  90. if (is_dir($path)) {
  91. $path = $path . $id;
  92. } else {
  93. $path = APP . $path . $id;
  94. }
  95. if (!is_file($path)) {
  96. if (Configure::read('debug')) {
  97. throw new NotFoundException(sprintf('The requested file %s was not found', $path));
  98. }
  99. throw new NotFoundException('The requested file was not found');
  100. }
  101. if (is_array($mimeType)) {
  102. $this->response->type($mimeType);
  103. }
  104. if (isset($extension) && $this->_isActive()) {
  105. $extension = strtolower($extension);
  106. $chunkSize = 8192;
  107. $buffer = '';
  108. $fileSize = @filesize($path);
  109. $handle = fopen($path, 'rb');
  110. if ($handle === false) {
  111. return false;
  112. }
  113. if (!empty($modified) && !is_numeric($modified)) {
  114. $modified = strtotime($modified, time());
  115. } else {
  116. $modified = time();
  117. }
  118. if ($this->response->type($extension) === false) {
  119. $download = true;
  120. }
  121. if ($cache) {
  122. $this->response->cache($modified, $cache);
  123. } else {
  124. $this->response->header(array(
  125. 'Date' => gmdate('D, d M Y H:i:s', time()) . ' GMT',
  126. 'Expires' => '0',
  127. 'Cache-Control' => 'private, must-revalidate, post-check=0, pre-check=0',
  128. 'Pragma' => 'no-cache'
  129. ));
  130. }
  131. if ($download) {
  132. $agent = env('HTTP_USER_AGENT');
  133. if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) {
  134. $contentType = 'application/octetstream';
  135. } else if (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
  136. $contentType = 'application/force-download';
  137. }
  138. if (!empty($contentType)) {
  139. $this->response->type($contentType);
  140. }
  141. if (is_null($name)) {
  142. $name = $id;
  143. }
  144. $this->response->download($name);
  145. $this->response->header(array('Accept-Ranges' => 'bytes'));
  146. $httpRange = env('HTTP_RANGE');
  147. if (isset($httpRange)) {
  148. list($toss, $range) = explode('=', $httpRange);
  149. $size = $fileSize - 1;
  150. $length = $fileSize - $range;
  151. $this->response->header(array(
  152. 'Content-Length' => $length,
  153. 'Content-Range' => 'bytes ' . $range . $size . '/' . $fileSize
  154. ));
  155. $this->response->statusCode(206);
  156. fseek($handle, $range);
  157. } else {
  158. $this->response->header('Content-Length', $fileSize);
  159. }
  160. } else {
  161. $this->response->header(array(
  162. 'Content-Length' => $fileSize
  163. ));
  164. }
  165. $this->_clearBuffer();
  166. if ($compress) {
  167. $this->_compressionEnabled = $this->response->compress();
  168. }
  169. $this->response->send();
  170. return $this->_sendFile($handle);
  171. }
  172. return false;
  173. }
  174. /**
  175. * Reads out a file handle, and echos the content to the client.
  176. *
  177. * @param resource $handle A file handle or stream
  178. * @return void
  179. */
  180. protected function _sendFile($handle) {
  181. $chunkSize = 8192;
  182. $buffer = '';
  183. while (!feof($handle)) {
  184. if (!$this->_isActive()) {
  185. fclose($handle);
  186. return false;
  187. }
  188. set_time_limit(0);
  189. $buffer = fread($handle, $chunkSize);
  190. echo $buffer;
  191. if (!$this->_compressionEnabled) {
  192. $this->_flushBuffer();
  193. }
  194. }
  195. fclose($handle);
  196. }
  197. /**
  198. * Returns true if connection is still active
  199. *
  200. * @return boolean
  201. */
  202. protected function _isActive() {
  203. return connection_status() == 0 && !connection_aborted();
  204. }
  205. /**
  206. * Clears the contents of the topmost output buffer and discards them
  207. *
  208. * @return boolean
  209. */
  210. protected function _clearBuffer() {
  211. return @ob_end_clean();
  212. }
  213. /**
  214. * Flushes the contents of the output buffer
  215. *
  216. * @return void
  217. */
  218. protected function _flushBuffer() {
  219. @flush();
  220. @ob_flush();
  221. }
  222. }