modresponse.class.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. <?php
  2. /*
  3. * This file is part of MODX Revolution.
  4. *
  5. * Copyright (c) MODX, LLC. All Rights Reserved.
  6. *
  7. * For complete copyright and license information, see the COPYRIGHT and LICENSE
  8. * files found in the top-level directory of this distribution.
  9. */
  10. /**
  11. * Encapsulates a MODX response to a web request.
  12. *
  13. * Includes functions to manipluate header data, such as status codes, as well
  14. * as manipulating the response body.
  15. *
  16. * @package modx
  17. */
  18. class modResponse {
  19. /**
  20. * A reference to the modX instance
  21. * @var modX $modx
  22. */
  23. public $modx= null;
  24. /**
  25. * The HTTP header for this Response
  26. * @var string $header
  27. */
  28. public $header= null;
  29. /**
  30. * The body of this response
  31. * @var string $body
  32. */
  33. public $body= null;
  34. /**
  35. * The current content type on the resource
  36. * @var modContentType $contentType
  37. */
  38. public $contentType = null;
  39. /**
  40. * @param modX $modx A reference to the modX instance
  41. */
  42. function __construct(modX &$modx) {
  43. $this->modx= & $modx;
  44. }
  45. /**
  46. * Prepare the final response after the resource has been processed.
  47. *
  48. * @param array $options Various options that can be set.
  49. */
  50. public function outputContent(array $options = array()) {
  51. if (!($this->contentType = $this->modx->resource->getOne('ContentType'))) {
  52. if ($this->modx->getDebug() === true) {
  53. $this->modx->log(modX::LOG_LEVEL_DEBUG, "No valid content type for RESOURCE: " . print_r($this->modx->resource->toArray(), true));
  54. }
  55. $this->modx->log(modX::LOG_LEVEL_FATAL, "The requested resource has no valid content type specified.");
  56. }
  57. if (!$this->contentType->get('binary')) {
  58. $this->modx->resource->prepare();
  59. /*FIXME: only do this for HTML content ?*/
  60. if (strpos($this->contentType->get('mime_type'), 'text/html') !== false) {
  61. $this->modx->invokeEvent('OnBeforeRegisterClientScripts');
  62. /* Insert Startup jscripts & CSS scripts into template - template must have a </head> tag */
  63. if (($js= $this->modx->getRegisteredClientStartupScripts()) && (strpos($this->modx->resource->_output, '</head>') !== false)) {
  64. /* change to just before closing </head> */
  65. $this->modx->resource->_output= preg_replace("/(<\/head>)/i", $js . "\n\\1", $this->modx->resource->_output,1);
  66. }
  67. /* Insert jscripts & html block into template - template must have a </body> tag */
  68. if ((strpos($this->modx->resource->_output, '</body>') !== false) && ($js= $this->modx->getRegisteredClientScripts())) {
  69. $this->modx->resource->_output= preg_replace("/(<\/body>)/i", $js . "\n\\1", $this->modx->resource->_output,1);
  70. }
  71. }
  72. $this->modx->beforeRender();
  73. /* invoke OnWebPagePrerender event */
  74. if (!isset($options['noEvent']) || empty($options['noEvent'])) {
  75. $this->modx->invokeEvent('OnWebPagePrerender');
  76. }
  77. $totalTime= (microtime(true) - $this->modx->startTime);
  78. $queryTime= $this->modx->queryTime;
  79. $queries= isset ($this->modx->executedQueries) ? $this->modx->executedQueries : 0;
  80. $phpTime= $totalTime - $queryTime;
  81. $queryTime= sprintf("%2.4f s", $queryTime);
  82. $totalTime= sprintf("%2.4f s", $totalTime);
  83. $phpTime= sprintf("%2.4f s", $phpTime);
  84. $source= $this->modx->resourceGenerated ? "database" : "cache";
  85. $memory = number_format(memory_get_usage(true) / 1024, 0,","," ") . ' kb';
  86. $this->modx->resource->_output= str_replace("[^q^]", $queries, $this->modx->resource->_output);
  87. $this->modx->resource->_output= str_replace("[^qt^]", $queryTime, $this->modx->resource->_output);
  88. $this->modx->resource->_output= str_replace("[^p^]", $phpTime, $this->modx->resource->_output);
  89. $this->modx->resource->_output= str_replace("[^t^]", $totalTime, $this->modx->resource->_output);
  90. $this->modx->resource->_output= str_replace("[^s^]", $source, $this->modx->resource->_output);
  91. $this->modx->resource->_output= str_replace("[^m^]", $memory, $this->modx->resource->_output);
  92. } else {
  93. $this->modx->beforeRender();
  94. /* invoke OnWebPagePrerender event */
  95. if (!isset($options['noEvent']) || empty($options['noEvent'])) {
  96. $this->modx->invokeEvent("OnWebPagePrerender");
  97. }
  98. }
  99. /* send out content-type, content-disposition, and custom headers from the content type */
  100. if ($this->modx->getOption('set_header')) {
  101. $type= $this->contentType->get('mime_type') ? $this->contentType->get('mime_type') : 'text/html';
  102. $header= 'Content-Type: ' . $type;
  103. if (!$this->contentType->get('binary')) {
  104. $charset= $this->modx->getOption('modx_charset',null,'UTF-8');
  105. $header .= '; charset=' . $charset;
  106. }
  107. header($header);
  108. if (!$this->checkPreview()) {
  109. $dispositionSet= false;
  110. if ($customHeaders= $this->contentType->get('headers')) {
  111. foreach ($customHeaders as $headerKey => $headerString) {
  112. header($headerString);
  113. if (strpos($headerString, 'Content-Disposition:') !== false) $dispositionSet= true;
  114. }
  115. }
  116. if (!$dispositionSet && $this->modx->resource->get('content_dispo')) {
  117. if ($alias= $this->modx->resource->get('uri')) {
  118. $name= basename($alias);
  119. } elseif ($this->modx->resource->get('alias')) {
  120. $name= $this->modx->resource->get('alias');
  121. if ($ext= $this->contentType->getExtension()) {
  122. $name .= "{$ext}";
  123. }
  124. } elseif ($name= $this->modx->resource->get('pagetitle')) {
  125. $name= $this->modx->resource->cleanAlias($name);
  126. if ($ext= $this->contentType->getExtension()) {
  127. $name .= "{$ext}";
  128. }
  129. } else {
  130. $name= 'download';
  131. if ($ext= $this->contentType->getExtension()) {
  132. $name .= "{$ext}";
  133. }
  134. }
  135. $header= 'Cache-Control: public';
  136. header($header);
  137. $header= 'Content-Disposition: attachment; filename=' . $name;
  138. header($header);
  139. $header= 'Vary: User-Agent';
  140. header($header);
  141. }
  142. }
  143. }
  144. /* tell PHP to call _postProcess after returning the response (for caching) */
  145. register_shutdown_function(array (
  146. & $this->modx,
  147. "_postProcess"
  148. ));
  149. if ($this->modx->resource instanceof modStaticResource && $this->contentType->get('binary')) {
  150. $this->modx->resource->process();
  151. } else {
  152. if ($this->contentType->get('binary')) {
  153. $this->modx->resource->_output = $this->modx->resource->process();
  154. }
  155. @session_write_close();
  156. echo $this->modx->resource->_output;
  157. while (ob_get_level() && @ob_end_flush()) {}
  158. flush();
  159. exit();
  160. }
  161. }
  162. /**
  163. * Sends a redirect to the specified URL using the specified method.
  164. *
  165. * Valid $type values include:
  166. * REDIRECT_REFRESH Uses the header refresh method
  167. * REDIRECT_META Sends a a META HTTP-EQUIV="Refresh" tag to the output
  168. * REDIRECT_HEADER Uses the header location method
  169. *
  170. * REDIRECT_HEADER is the default.
  171. *
  172. * @param string $url The URL to redirect the client browser to.
  173. * @param array|boolean $options An array of options for the redirect OR
  174. * indicates if redirect attempts should be counted and limited to 3 (latter is deprecated
  175. * usage; use count_attempts in options array).
  176. * @param string $type The type of redirection to attempt (deprecated, use type in
  177. * options array).
  178. * @param string $responseCode The type of HTTP response code HEADER to send for the
  179. * redirect (deprecated, use responseCode in options array)
  180. * @return void|boolean
  181. */
  182. public function sendRedirect($url, $options= false, $type= '', $responseCode= '') {
  183. if (!is_array($options)) {
  184. $options = array('count_attempts' => (boolean) $options);
  185. }
  186. if ($type) {
  187. $this->modx->deprecated('2.0.5', 'Use type in options array instead.', 'sendRedirect method parameter $type');
  188. }
  189. if ($responseCode) {
  190. $this->modx->deprecated('2.0.5', 'Use responseCode in options array instead.', 'sendRedirect method parameter $responseCode');
  191. }
  192. $options = array_merge(array('count_attempts' => false, 'type' => $type, 'responseCode' => $responseCode), $options);
  193. $url= str_replace('&amp;','&',$url);
  194. if (empty ($url)) {
  195. $this->modx->log(modX::LOG_LEVEL_ERROR, "Attempted to redirect to an empty URL.");
  196. return false;
  197. }
  198. if (!$this->modx->getRequest()) {
  199. $this->modx->log(modX::LOG_LEVEL_FATAL, "Could not load request class.");
  200. }
  201. if (isset($options['preserve_request']) && !empty($options['preserve_request'])) {
  202. $this->modx->request->preserveRequest('referrer.redirected');
  203. }
  204. if ($options['count_attempts']) {
  205. /* append the redirect count string to the url */
  206. $currentNumberOfRedirects= isset ($_REQUEST['err']) ? $_REQUEST['err'] : 0;
  207. if ($currentNumberOfRedirects > 3) {
  208. $this->modx->log(modX::LOG_LEVEL_FATAL, 'Redirection attempt failed - please ensure the resource you\'re trying to redirect to exists. <p>Redirection URL: <i>' . $url . '</i></p>');
  209. } else {
  210. $currentNumberOfRedirects += 1;
  211. if (strpos($url, "?") > 0) {
  212. $url .= "&err=$currentNumberOfRedirects";
  213. } else {
  214. $url .= "?err=$currentNumberOfRedirects";
  215. }
  216. }
  217. }
  218. switch ($options['type']) {
  219. case 'REDIRECT_REFRESH':
  220. $header= 'Refresh: 0;URL=' . $url;
  221. break;
  222. case 'REDIRECT_META':
  223. $header= '<META HTTP-EQUIV="Refresh" CONTENT="0; URL=' . $url . '" />';
  224. echo $header;
  225. exit();
  226. default:
  227. if (strpos($url, '://') === false && !(substr($url, 0, 1) === '/' || substr($url, 0, 2) === './' || substr($url, 0, 3) === '../')) {
  228. $url= $this->modx->getOption('site_url',null,'/') . $url;
  229. }
  230. $header= 'Location: ' . $url;
  231. break;
  232. }
  233. @session_write_close();
  234. if (!empty($options['responseCode']) && (strpos($options['responseCode'], '30') !== false)) {
  235. header($options['responseCode']);
  236. }
  237. header($header);
  238. exit();
  239. }
  240. /**
  241. * Checks to see if the preview parameter is set.
  242. *
  243. * @return boolean
  244. */
  245. public function checkPreview() {
  246. $preview= false;
  247. if ($this->modx->checkSession('mgr') === true) {
  248. if (isset ($_REQUEST['z']) && $_REQUEST['z'] == 'manprev') {
  249. $preview= true;
  250. }
  251. }
  252. return $preview;
  253. }
  254. }