PageRenderTime 54ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Jelix/Routing/ServerResponse.php

https://github.com/gmarrot/jelix
PHP | 315 lines | 153 code | 49 blank | 113 comment | 34 complexity | 59680ac6a24bf02b87d1dd296bf7fd6e MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * @author Laurent Jouanneau
  4. * @contributor Julien Issler, Brice Tence
  5. * @contributor Florian Lonqueu-Brochard
  6. * @copyright 2005-2014 Laurent Jouanneau
  7. * @copyright 2010 Julien Issler, 2011 Brice Tence
  8. * @copyright 2011 Florian Lonqueu-Brochard
  9. * @link http://www.jelix.org
  10. * @licence GNU Lesser General Public Licence see LICENCE file or http://www.gnu.org/licenses/lgpl.html
  11. */
  12. namespace Jelix\Routing;
  13. use Jelix\Core\App;
  14. /**
  15. * base class for response object
  16. * A response object is responsible to generate a content in a specific format.
  17. * @package jelix
  18. * @subpackage core
  19. */
  20. abstract class ServerResponse {
  21. /**
  22. * @var string ident of the response type
  23. */
  24. protected $_type = null;
  25. /**
  26. * @var array list of http headers that will be send to the client
  27. */
  28. protected $_httpHeaders = array();
  29. /**
  30. * @var boolean indicates if http headers have already been sent to the client
  31. */
  32. protected $_httpHeadersSent = false;
  33. /**
  34. * @var string the http status code to send
  35. */
  36. protected $_httpStatusCode ='200';
  37. /**
  38. * @var string the http status message to send
  39. */
  40. protected $_httpStatusMsg ='OK';
  41. /**
  42. * @var boolean Should we output only the headers or the entire response
  43. */
  44. protected $_outputOnlyHeaders = false;
  45. public $httpVersion = '1.1';
  46. public $forcedHttpVersion = false;
  47. /**
  48. * constructor
  49. */
  50. function __construct() {
  51. if( App::config()->httpVersion != "" ) {
  52. $this->httpVersion = App::config()->httpVersion;
  53. $this->forcedHttpVersion = true;
  54. }
  55. }
  56. /**
  57. * Send the response in the correct format. If errors or exceptions appears
  58. * during this method, outputErrors will be called. So the
  59. * the content should be generated using the output buffer if errors can
  60. * be appeared during this generation. Be care of http headers.
  61. *
  62. * @return boolean true if the output is ok
  63. * @internal should take care about errors
  64. */
  65. abstract public function output();
  66. /**
  67. * Send a response with a generic error message.
  68. */
  69. public function outputErrors() {
  70. // if accept text/html
  71. if (isset($_SERVER['HTTP_ACCEPT']) && strstr($_SERVER['HTTP_ACCEPT'],'text/html')) {
  72. require_once(JELIX_LIB_CORE_PATH.'responses/jResponseBasicHtml.class.php');
  73. $response = new \jResponseBasicHtml();
  74. $response->outputErrors();
  75. }
  76. else {
  77. // output text response
  78. header("HTTP/{$this->httpVersion} 500 Internal jelix error");
  79. header('Content-type: text/plain');
  80. echo App::router()->getGenericErrorMessage();
  81. }
  82. }
  83. /**
  84. * return the response type name
  85. * @return string the name
  86. */
  87. public final function getType(){ return $this->_type;}
  88. /**
  89. * return the format type name (eg the family type name)
  90. * @return string the name
  91. */
  92. public function getFormatType(){ return $this->_type;}
  93. /**
  94. * add an http header to the response.
  95. * will be send during the output of the response
  96. * @param string $htype the header type ("Content-Type", "Date-modified"...)
  97. * @param string $hcontent value of the header type
  98. * @param integer $overwrite false or 0 if the value should be set only if it doesn't still exist
  99. * -1 to add the header with the existing values
  100. * true or 1 to replace the existing header
  101. */
  102. public function addHttpHeader($htype, $hcontent, $overwrite=true){
  103. if (isset($this->_httpHeaders[$htype])) {
  104. $val = $this->_httpHeaders[$htype];
  105. if ($overwrite === -1) {
  106. if (!is_array($val))
  107. $this->_httpHeaders[$htype] = array($val, $hcontent);
  108. else
  109. $this->_httpHeaders[$htype][] = $hcontent;
  110. return;
  111. }
  112. else if (!$overwrite) {
  113. return;
  114. }
  115. }
  116. $this->_httpHeaders[$htype]=$hcontent;
  117. }
  118. /**
  119. * delete all http headers
  120. */
  121. public function clearHttpHeaders(){
  122. $this->_httpHeaders = array();
  123. $this->_httpStatusCode ='200';
  124. $this->_httpStatusMsg ='OK';
  125. }
  126. /**
  127. * set the http status code for the http header
  128. * @param string $code the status code (200, 404...)
  129. * @param string $msg the message following the status code ("OK", "Not Found"..)
  130. */
  131. public function setHttpStatus($code, $msg){
  132. $this->_httpStatusCode = $code;
  133. $this->_httpStatusMsg = $msg;
  134. }
  135. /**
  136. * send http headers
  137. */
  138. protected function sendHttpHeaders(){
  139. header( ( isset($_SERVER['SERVER_PROTOCOL']) && !$this->forcedHttpVersion ?
  140. $_SERVER['SERVER_PROTOCOL'] :
  141. 'HTTP/'.$this->httpVersion ) .
  142. ' '.$this->_httpStatusCode.' '.$this->_httpStatusMsg );
  143. foreach($this->_httpHeaders as $ht=>$hc) {
  144. if (is_array($hc)) {
  145. foreach ($hc as $val) {
  146. header($ht.': '.$val);
  147. }
  148. }
  149. else
  150. header($ht.': '.$hc);
  151. }
  152. $this->_httpHeadersSent=true;
  153. }
  154. /**
  155. * Normalize a date into GMT format
  156. * @param mixed $date Can be a jDateTime object, a DateTime object or a string understandable by strtotime
  157. * @return string a date in GMT format
  158. */
  159. protected function _normalizeDate($date){
  160. if ($date instanceof \jDateTime){
  161. return gmdate('D, d M Y H:i:s \G\M\T', $date->toString(\jDateTime::TIMESTAMP_FORMAT));
  162. }
  163. elseif($date instanceof \DateTime){
  164. return gmdate('D, d M Y H:i:s \G\M\T', $date->getTimestamp());
  165. }
  166. else{
  167. return gmdate('D, d M Y H:i:s \G\M\T', strtotime($date));
  168. }
  169. }
  170. /**
  171. * check if the request is of type GET or HEAD
  172. */
  173. protected function _checkRequestType(){
  174. $allowedTypes = array('GET', 'HEAD');
  175. if (in_array($_SERVER['REQUEST_METHOD'], $allowedTypes)){
  176. return true;
  177. }
  178. else {
  179. trigger_error(\Jelix\Locale\Locale::get('jelix~errors.rep.bad.request.method'), E_USER_WARNING);
  180. return false;
  181. }
  182. }
  183. /**
  184. * Clean the differents caches headers
  185. */
  186. public function cleanCacheHeaders(){
  187. $toClean = array('Cache-Control', 'Expires', 'Pragma' );
  188. foreach($toClean as $h){
  189. unset($this->_httpHeaders[$h]);
  190. $this->addHttpHeader($h, '');
  191. }
  192. }
  193. /**
  194. * Set an expires header to the page/ressource.
  195. *
  196. * @param mixed $dateLastModified Can be a jDateTime object, a DateTime object or a string understandable by strtotime
  197. * @param boolean $cleanCacheHeaderTrue for clean/delete other cache headers. Default : true.
  198. *
  199. * @see _normalizeDate
  200. */
  201. public function setExpires($date, $cleanCacheHeader = true) {
  202. if(!$this->_checkRequestType())
  203. return;
  204. if($cleanCacheHeader)
  205. $this->cleanCacheHeaders();
  206. $date = $this->_normalizeDate($date);
  207. $this->addHttpHeader('Expires', $date);
  208. }
  209. /**
  210. * Set a life time for the page/ressource.
  211. *
  212. * @param int $time Time during which the page will be cached. Express in seconds.
  213. * @param boolean $sharedCache True if the lifetime concern a public/shared cache. Default : false.
  214. * @param boolean $cleanCacheHeaderTrue for clean/delete other cache headers. Default : true.
  215. */
  216. public function setLifetime($time, $sharedCache = false, $cleanCacheHeader = true) {
  217. if(!$this->_checkRequestType())
  218. return;
  219. if($cleanCacheHeader)
  220. $this->cleanCacheHeaders();
  221. $type = $sharedCache ? 'public' : 'private';
  222. $this->addHttpHeader('Cache-Control', $type.', '.($sharedCache ? 's-' : '').'maxage='.$time);
  223. }
  224. /**
  225. * Use the HTPP headers Last-Modified to see if the ressource in client cache is fresh
  226. *
  227. * @param mixed $dateLastModified Can be a jDateTime object, a DateTime object or a string understandable by strtotime
  228. * @param boolean $cleanCacheHeader True for clean/delete other cache headers. Default : true.
  229. *
  230. * @return boolean True if the client ressource version is fresh, false otherwise
  231. */
  232. public function isValidCache($dateLastModified = null, $etag = null, $cleanCacheHeader = true){
  233. if(!$this->_checkRequestType())
  234. return false;
  235. $notModified = false;
  236. if($cleanCacheHeader)
  237. $this->cleanCacheHeaders();
  238. if($dateLastModified != null){
  239. $dateLastModified = $this->_normalizeDate($dateLastModified);
  240. $lastModified = App::router()->request->header('If-Modified-Since');
  241. if ($lastModified !== null && $lastModified == $dateLastModified) {
  242. $notModified = true;
  243. }
  244. else {
  245. $this->addHttpHeader('Last-Modified', $dateLastModified);
  246. }
  247. }
  248. if($etag != null){
  249. $headerEtag = App::router()->request->header('If-None-Match');
  250. if ($headerEtag !== null && $etag == $headerEtag) {
  251. $notModified = true;
  252. }
  253. else {
  254. $this->addHttpHeader('Etag', $etag);
  255. }
  256. }
  257. if($notModified) {
  258. $this->_outputOnlyHeaders = true;
  259. $this->setHttpStatus(304, 'Not Modified');
  260. $toClean = array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified', 'Etag');
  261. foreach($toClean as $h)
  262. unset($this->_httpHeaders[$h]);
  263. }
  264. return $notModified;
  265. }
  266. }