MediaView.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  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-2011, 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-2011, 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. * - `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. * public function download () {
  41. * $this->viewClass = '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.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. * Constructor
  65. *
  66. * @param Controller $controller The controller with viewVars
  67. */
  68. public function __construct($controller = null) {
  69. parent::__construct($controller);
  70. }
  71. /**
  72. * Display or download the given file
  73. *
  74. * @param string $view Not used
  75. * @param string $layout Not used
  76. * @return mixed
  77. * @throws NotFoundException
  78. */
  79. public function render($view = null, $layout = null) {
  80. $name = $download = $extension = $id = $modified = $path = $cache = $mimeType = $compress = null;
  81. extract($this->viewVars, EXTR_OVERWRITE);
  82. if (is_dir($path)) {
  83. $path = $path . $id;
  84. } else {
  85. $path = APP . $path . $id;
  86. }
  87. if (!is_file($path)) {
  88. if (Configure::read('debug')) {
  89. throw new NotFoundException(sprintf('The requested file %s was not found', $path));
  90. }
  91. throw new NotFoundException('The requested file was not found');
  92. }
  93. if (is_array($mimeType)) {
  94. $this->response->type($mimeType);
  95. }
  96. if (isset($extension) && $this->_isActive()) {
  97. $extension = strtolower($extension);
  98. $chunkSize = 8192;
  99. $buffer = '';
  100. $fileSize = @filesize($path);
  101. $handle = fopen($path, 'rb');
  102. if ($handle === false) {
  103. return false;
  104. }
  105. if (!empty($modified) && !is_numeric($modified)) {
  106. $modified = strtotime($modified, time());
  107. } else {
  108. $modified = time();
  109. }
  110. if ($this->response->type($extension) === false) {
  111. $download = true;
  112. }
  113. if ($cache) {
  114. $this->response->cache($modified, $cache);
  115. } else {
  116. $this->response->header(array(
  117. 'Date' => gmdate('D, d M Y H:i:s', time()) . ' GMT',
  118. 'Expires' => '0',
  119. 'Cache-Control' => 'private, must-revalidate, post-check=0, pre-check=0',
  120. 'Pragma' => 'no-cache'
  121. ));
  122. }
  123. if ($download) {
  124. $agent = env('HTTP_USER_AGENT');
  125. if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) {
  126. $contentType = 'application/octetstream';
  127. } else if (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
  128. $contentType = 'application/force-download';
  129. }
  130. if (!empty($contentType)) {
  131. $this->response->type($contentType);
  132. }
  133. if (is_null($name)) {
  134. $name = $id;
  135. }
  136. $this->response->download($name);
  137. $this->response->header(array('Accept-Ranges' => 'bytes'));
  138. $httpRange = env('HTTP_RANGE');
  139. if (isset($httpRange)) {
  140. list($toss, $range) = explode('=', $httpRange);
  141. $size = $fileSize - 1;
  142. $length = $fileSize - $range;
  143. $this->response->header(array(
  144. 'Content-Length' => $length,
  145. 'Content-Range' => 'bytes ' . $range . $size . '/' . $fileSize
  146. ));
  147. $this->response->statusCode(206);
  148. fseek($handle, $range);
  149. } else {
  150. $this->response->header('Content-Length', $fileSize);
  151. }
  152. } else {
  153. $this->response->header(array(
  154. 'Content-Length' => $fileSize
  155. ));
  156. }
  157. $this->_clearBuffer();
  158. if ($compress) {
  159. $this->_compressionEnabled = $this->response->compress();
  160. }
  161. $this->response->send();
  162. return $this->_sendFile($handle);
  163. }
  164. return false;
  165. }
  166. /**
  167. * Reads out a file handle, and echos the content to the client.
  168. *
  169. * @param resource $handle A file handle or stream
  170. * @return void
  171. */
  172. protected function _sendFile($handle) {
  173. $chunkSize = 8192;
  174. $buffer = '';
  175. while (!feof($handle)) {
  176. if (!$this->_isActive()) {
  177. fclose($handle);
  178. return false;
  179. }
  180. set_time_limit(0);
  181. $buffer = fread($handle, $chunkSize);
  182. echo $buffer;
  183. if (!$this->_compressionEnabled) {
  184. $this->_flushBuffer();
  185. }
  186. }
  187. fclose($handle);
  188. }
  189. /**
  190. * Returns true if connection is still active
  191. *
  192. * @return boolean
  193. */
  194. protected function _isActive() {
  195. return connection_status() == 0 && !connection_aborted();
  196. }
  197. /**
  198. * Clears the contents of the topmost output buffer and discards them
  199. *
  200. * @return boolean
  201. */
  202. protected function _clearBuffer() {
  203. return @ob_end_clean();
  204. }
  205. /**
  206. * Flushes the contents of the output buffer
  207. *
  208. * @return void
  209. */
  210. protected function _flushBuffer() {
  211. @flush();
  212. @ob_flush();
  213. }
  214. }