MediaView.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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-2012, 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-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package Cake.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. * If not provided its extracted from filename provided as `id`.
  34. * - `path` The absolute path, including the trailing / on the server's filesystem to `id`.
  35. * - `mimeType` The mime type of the file if CakeResponse doesn't know about it.
  36. * Must be an associative array with extension as key and mime type as value eg. array('ini' => 'text/plain')
  37. *
  38. * ### Usage
  39. *
  40. * {{{
  41. * class ExampleController extends AppController {
  42. * public function download() {
  43. * $this->viewClass = 'Media';
  44. * $params = array(
  45. * 'id' => 'example.zip',
  46. * 'name' => 'example',
  47. * 'download' => true,
  48. * 'extension' => 'zip',
  49. * 'path' => APP . 'files' . DS
  50. * );
  51. * $this->set($params);
  52. * }
  53. * }
  54. * }}}
  55. *
  56. * @package Cake.View
  57. */
  58. class MediaView extends View {
  59. /**
  60. * Indicates whether response gzip compression was enabled for this class
  61. *
  62. * @var boolean
  63. */
  64. protected $_compressionEnabled = false;
  65. /**
  66. * Display or download the given file
  67. *
  68. * @param string $view Not used
  69. * @param string $layout Not used
  70. * @return mixed
  71. * @throws NotFoundException
  72. */
  73. public function render($view = null, $layout = null) {
  74. $name = $download = $extension = $id = $modified = $path = $cache = $mimeType = $compress = null;
  75. extract($this->viewVars, EXTR_OVERWRITE);
  76. if (is_dir($path)) {
  77. $path = $path . $id;
  78. } else {
  79. $path = APP . $path . $id;
  80. }
  81. if (!is_file($path)) {
  82. if (Configure::read('debug')) {
  83. throw new NotFoundException(sprintf('The requested file %s was not found', $path));
  84. }
  85. throw new NotFoundException('The requested file was not found');
  86. }
  87. if (is_array($mimeType)) {
  88. $this->response->type($mimeType);
  89. }
  90. if (!isset($extension)) {
  91. $extension = pathinfo($id, PATHINFO_EXTENSION);
  92. }
  93. if ($this->_isActive()) {
  94. $extension = strtolower($extension);
  95. $chunkSize = 8192;
  96. $buffer = '';
  97. $fileSize = @filesize($path);
  98. $handle = fopen($path, 'rb');
  99. if ($handle === false) {
  100. return false;
  101. }
  102. if (!empty($modified) && !is_numeric($modified)) {
  103. $modified = strtotime($modified, time());
  104. } else {
  105. $modified = time();
  106. }
  107. if (!$extension || $this->response->type($extension) === false) {
  108. $download = true;
  109. }
  110. if ($cache) {
  111. $this->response->cache($modified, $cache);
  112. } else {
  113. $this->response->header(array(
  114. 'Date' => gmdate('D, d M Y H:i:s', time()) . ' GMT',
  115. 'Expires' => '0',
  116. 'Cache-Control' => 'private, must-revalidate, post-check=0, pre-check=0',
  117. 'Pragma' => 'no-cache'
  118. ));
  119. }
  120. if ($download) {
  121. $agent = env('HTTP_USER_AGENT');
  122. if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) {
  123. $contentType = 'application/octetstream';
  124. } elseif (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
  125. $contentType = 'application/force-download';
  126. }
  127. if (!empty($contentType)) {
  128. $this->response->type($contentType);
  129. }
  130. if (is_null($name)) {
  131. $name = $id;
  132. } elseif ($extension) {
  133. $name .= '.' . $extension;
  134. }
  135. $this->response->download($name);
  136. $this->response->header(array('Accept-Ranges' => 'bytes'));
  137. $httpRange = env('HTTP_RANGE');
  138. if (isset($httpRange)) {
  139. list($toss, $range) = explode('=', $httpRange);
  140. $size = $fileSize - 1;
  141. $length = $fileSize - $range;
  142. $this->response->header(array(
  143. 'Content-Length' => $length,
  144. 'Content-Range' => 'bytes ' . $range . $size . '/' . $fileSize
  145. ));
  146. $this->response->statusCode(206);
  147. fseek($handle, $range);
  148. } else {
  149. $this->response->header('Content-Length', $fileSize);
  150. }
  151. } else {
  152. $this->response->header(array(
  153. 'Content-Length' => $fileSize
  154. ));
  155. }
  156. $this->_clearBuffer();
  157. if ($compress) {
  158. $this->_compressionEnabled = $this->response->compress();
  159. }
  160. $this->response->send();
  161. return $this->_sendFile($handle);
  162. }
  163. return false;
  164. }
  165. /**
  166. * Reads out a file handle, and echos the content to the client.
  167. *
  168. * @param resource $handle A file handle or stream
  169. * @return void
  170. */
  171. protected function _sendFile($handle) {
  172. $chunkSize = 8192;
  173. $buffer = '';
  174. while (!feof($handle)) {
  175. if (!$this->_isActive()) {
  176. fclose($handle);
  177. return false;
  178. }
  179. set_time_limit(0);
  180. $buffer = fread($handle, $chunkSize);
  181. echo $buffer;
  182. if (!$this->_compressionEnabled) {
  183. $this->_flushBuffer();
  184. }
  185. }
  186. fclose($handle);
  187. }
  188. /**
  189. * Returns true if connection is still active
  190. *
  191. * @return boolean
  192. */
  193. protected function _isActive() {
  194. return connection_status() == 0 && !connection_aborted();
  195. }
  196. /**
  197. * Clears the contents of the topmost output buffer and discards them
  198. *
  199. * @return boolean
  200. */
  201. protected function _clearBuffer() {
  202. return @ob_end_clean();
  203. }
  204. /**
  205. * Flushes the contents of the output buffer
  206. *
  207. * @return void
  208. */
  209. protected function _flushBuffer() {
  210. @flush();
  211. @ob_flush();
  212. }
  213. }