Version: current

      1 <?php
      2 /**
      3  * Represents a response returned by a controller.
      4  *
      5  * @package sapphire
      6  * @subpackage control
      7  */
      8 class SS_HTTPResponse {
      9     protected static $status_codes = array(
     10         100 => 'Continue',
     11         101 => 'Switching Protocols',
     12         200 => 'OK',
     13         201 => 'Created',
     14         202 => 'Accepted',
     15         203 => 'Non-Authoritative Information',
     16         204 => 'No Content',
     17         205 => 'Reset Content',
     18         206 => 'Partial Content',
     19         301 => 'Moved Permanently',
     20         302 => 'Found',
     21         303 => 'See Other',
     22         304 => 'Not Modified',
     23         305 => 'Use Proxy',
     24         307 => 'Temporary Redirect',
     25         400 => 'Bad Request',
     26         401 => 'Unauthorized',
     27         403 => 'Forbidden',
     28         404 => 'Not Found',
     29         405 => 'Method Not Allowed',
     30         406 => 'Not Acceptable',
     31         407 => 'Proxy Authentication Required',
     32         408 => 'Request Timeout',
     33         409 => 'Conflict',
     34         410 => 'Gone',
     35         411 => 'Length Required',
     36         412 => 'Precondition Failed',
     37         413 => 'Request Entity Too Large',
     38         414 => 'Request-URI Too Long',
     39         415 => 'Unsupported Media Type',
     40         416 => 'Request Range Not Satisfiable',
     41         417 => 'Expectation Failed',
     42         500 => 'Internal Server Error',
     43         501 => 'Not Implemented',
     44         502 => 'Bad Gateway',
     45         503 => 'Service Unavailable',
     46         504 => 'Gateway Timeout',
     47         505 => 'HTTP Version Not Supported',
     48     );
     49
     50     protected static $redirect_codes = array(
     51         301,
     52         302,
     53         303,
     54         304,
     55         305,
     56         307
     57     );
     58
     59     protected $statusCode = 200;
     60     protected $statusDescription = "OK";
     61
     62     /**
     63      * HTTP Headers like "Content-Type: text/xml"
     64      *
     65      * @see http://en.wikipedia.org/wiki/List_of_HTTP_headers
     66      * @var array
     67      */
     68     protected $headers = array(
     69         "Content-Type" => "text/html; charset=\"utf-8\"",
     70     );
     71
     72     /**
     73      * @var string
     74      */
     75     protected $body = null;
     76
     77     /**
     78      * Create a new HTTP response
     79      * @param $body The body of the response
     80      * @param $statusCode The numeric status code - 200, 404, etc
     81      * @param $statusDescription The text to be given alongside the status code.  This can be accessed by javascript
     82      */
     83     function __construct($body = null, $statusCode = null, $statusDescription = null) {
     84         $this->body = $body;
     85         if($statusCode) $this->setStatusCode($statusCode, $statusDescription);
     86     }
     87
     88     function setStatusCode($code, $description = null) {
     89         if(isset(self::$status_codes[$code])) $this->statusCode = $code;
     90         else user_error("Unrecognised HTTP status code '$code'", E_USER_WARNING);
     91
     92         if($description) $this->statusDescription = $description;
     93         else $this->statusDescription = self::$status_codes[$code];
     94     }
     95
     96     function getStatusCode() {
     97         return $this->statusCode;
     98     }
     99
    100     /**
    101      * @return string Description for a HTTP status code
    102      */
    103     function getStatusDescription() {
    104         return $this->statusDescription;
    105     }
    106
    107     /**
    108      * Returns true if this HTTP response is in error
    109      */
    110     function isError() {
    111         return $this->statusCode && ($this->statusCode < 200 || $this->statusCode > 399);
    112     }
    113
    114     function setBody($body) {
    115         $this->body = $body;
    116     }
    117
    118     function getBody() {
    119         return $this->body;
    120     }
    121
    122     /**
    123      * Add a HTTP header to the response, replacing any header of the same name.
    124      *
    125      * @param string $header Example: "Content-Type"
    126      * @param string $value Example: "text/xml"
    127      */
    128     function addHeader($header, $value) {
    129         $this->headers[$header] = $value;
    130     }
    131
    132     /**
    133      * Return the HTTP header of the given name.
    134      *
    135      * @param string $header
    136      * @returns string
    137      */
    138     function getHeader($header) {
    139         if(isset($this->headers[$header])) {
    140             return $this->headers[$header];
    141         } else {
    142             return null;
    143         }
    144     }
    145
    146     /**
    147      * @return array
    148      */
    149     function getHeaders() {
    150         return $this->headers;
    151     }
    152
    153     /**
    154      * Remove an existing HTTP header by its name,
    155      * e.g. "Content-Type".
    156      *
    157      * @param unknown_type $header
    158      */
    159     function removeHeader($header) {
    160         if(isset($this->headers[$header])) unset($this->headers[$header]);
    161     }
    162
    163     function redirect($dest, $code=302) {
    164         if(!in_array($code, self::$redirect_codes)) $code = 302;
    165         $this->statusCode = $code;
    166         $this->headers['Location'] = $dest;
    167     }
    168
    169     /**
    170      * Send this HTTPReponse to the browser
    171      */
    172     function output() {
    173         // Attach appropriate X-Include-JavaScript and X-Include-CSS headers
    174         if(Director::is_ajax()) {
    175             Requirements::include_in_response($this);
    176         }
    177
    178         if(in_array($this->statusCode, self::$redirect_codes) && headers_sent($file, $line)) {
    179             $url = $this->headers['Location'];
    180             echo
    181             "<p>Redirecting to <a href=\"$url\" title=\"Please click this link if your browser does not redirect you\">$url... (output started on $file, line $line)</a></p>
    182             <meta http-equiv=\"refresh\" content=\"1; url=$url\" />
    183             <script type=\"text/javascript\">setTimeout('window.location.href = \"$url\"', 50);</script>";
    184         } else {
    185             if(!headers_sent()) {
    186                 header($_SERVER['SERVER_PROTOCOL'] . " $this->statusCode " . $this->getStatusDescription());
    187                 foreach($this->headers as $header => $value) {
    188                     header("$header: $value");
    189                 }
    190             }
    191
    192             // Only show error pages or generic "friendly" errors if the status code signifies
    193             // an error, and the response doesn't have any body yet that might contain
    194             // a more specific error description.
    195             if(Director::isLive() && $this->isError() && !$this->body) {
    196                 Debug::friendlyError($this->statusCode, $this->getStatusDescription());
    197             } else {
    198                 echo $this->body;
    199             }
    200
    201         }
    202     }
    203
    204     /**
    205      * Returns true if this response is "finished", that is, no more script execution should be done.
    206      * Specifically, returns true if a redirect has already been requested
    207      */
    208     function isFinished() {
    209         return in_array($this->statusCode, array(301, 302, 401, 403));
    210     }
    211
    212     /**
    213      * @deprecated 2.4 Use {@link HTTP::getLinksIn()} on DOMDocument.
    214      */
    215     public function getLinks() {
    216         user_error (
    217             'SS_HTTPResponse->getLinks() is deprecated, please use HTTP::getLinksIn() or DOMDocument.', E_USER_NOTICE
    218         );
    219
    220         $attributes = array('id', 'href', 'class');
    221         $links      = array();
    222         $results    = array();
    223
    224         if(preg_match_all('/<a[^>]+>/i', $this->body, $links)) foreach($links[0] as $link) {
    225             $processedLink = array();
    226             foreach($attributes as $attribute) {
    227                 $matches = array();
    228                 if(preg_match('/' . $attribute  . '\s*=\s*"([^"]+)"/i', $link, $matches)) {
    229                     $processedLink[$attribute] = $matches[1];
    230                 }
    231             }
    232             $results[] = $processedLink;
    233         }
    234
    235         return $results;
    236     }
    237
    238 }
    239
    240 /**
    241  * A {@link SS_HTTPResponse} encapsulated in an exception, which can interrupt the processing flow and be caught by the
    242  * {@link RequestHandler} and returned to the user.
    243  *
    244  * Example Usage:
    245  * <code>
    246  * throw new SS_HTTPResponse_Exception('This request was invalid.', 400);
    247  * throw new SS_HTTPResponse_Exception(new SS_HTTPResponse('There was an internal server error.', 500));
    248  * </code>
    249  *
    250  * @package sapphire
    251  * @subpackage control
    252  */
    253 class SS_HTTPResponse_Exception extends Exception {
    254
    255     protected $response;
    256
    257     /**
    258      * @see SS_HTTPResponse::__construct();
    259      */
    260      public function __construct($body = null, $statusCode = null, $statusDescription = null) {
    261         if($body instanceof SS_HTTPResponse) {
    262             $this->setResponse($body);
    263         } else {
    264             $this->setResponse(new SS_HTTPResponse($body, $statusCode, $statusDescription));
    265         }
    266
    267         parent::__construct($this->getResponse()->getBody(), $this->getResponse()->getStatusCode());
    268      }
    269
    270      /**
    271       * @return SS_HTTPResponse
    272       */
    273      public function getResponse() {
    274         return $this->response;
    275      }
    276
    277      /**
    278       * @param SS_HTTPResponse $response
    279       */
    280      public function setResponse(SS_HTTPResponse $response) {
    281         $this->response = $response;
    282      }
    283
    284 }
    285

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -