Version: current

      1 <?php
      2 /**
      3  * ErrorPage holds the content for the page of an error response.
      4  * Renders the page on each publish action into a static HTML file
      5  * within the assets directory, after the naming convention
      6  * /assets/error-<statuscode>.html.
      7  * This enables us to show errors even if PHP experiences a recoverable error.
      8  * ErrorPages
      9  *
     10  * @see Debug::friendlyError()
     11  *
     12  * @package cms
     13  */
     14 class ErrorPage extends Page {
     15
     16     static $allowed_children = "none";
     17
     18     static $db = array(
     19         "ErrorCode" => "Int",
     20     );
     21
     22     static $defaults = array(
     23         "ShowInMenus" => 0,
     24         "ShowInSearch" => 0,
     25         "ShowInSiteMap" => 0,
     26     );
     27
     28     protected static $static_filepath = ASSETS_PATH;
     29
     30     /**
     31      * Get a {@link SS_HTTPResponse} to response to a HTTP error code if an {@link ErrorPage} for that code is present.
     32      *
     33      * @param int $statusCode
     34      * @return SS_HTTPResponse
     35      */
     36     public static function response_for($statusCode) {
     37         // first attempt to dynamically generate the error page
     38         if($errorPage = DataObject::get_one('ErrorPage', "\"ErrorCode\" = $statusCode")) {
     39             return ModelAsController::controller_for($errorPage)->handleRequest(new SS_HTTPRequest('GET', ''));
     40         }
     41
     42         // then fall back on a cached version
     43         $cachedPath = self::get_filepath_for_errorcode($statusCode, i18n::get_locale());
     44
     45         if(file_exists($cachedPath)) {
     46             $response = new SS_HTTPResponse();
     47
     48             $response->setStatusCode($statusCode);
     49             $response->setBody(file_get_contents($cachedPath));
     50
     51             return $response;
     52         }
     53     }
     54
     55     /**
     56      * Ensures that there is always a 404 page
     57      * by checking if there's an instance of
     58      * ErrorPage with a 404 error code. If there
     59      * is not, one is created when the DB is built.
     60      */
     61     function requireDefaultRecords() {
     62         parent::requireDefaultRecords();
     63
     64         $errorPage = DataObject::get_one('ErrorPage', "\"ErrorCode\" = '404'");
     65         if(!($errorPage && $errorPage->exists())) {
     66             $errorpage = new ErrorPage();
     67             $errorpage->ErrorCode = 404;
     68             $errorpage->Title = _t('ErrorPage.DEFAULTERRORPAGETITLE', 'Page not found');
     69             $errorpage->Content = _t('ErrorPage.DEFAULTERRORPAGECONTENT', '<p>Sorry, it seems you were trying to access a page that doesn\'t exist.</p><p>Please check the spelling of the URL you were trying to access and try again.</p>');
     70             $errorpage->Status = 'New page';
     71             $errorpage->DevEditType = 'Fixed';
     72             $errorpage->Sort = -1;
     73             $errorpage->write();
     74             $errorpage->publish('Stage', 'Live');
     75
     76             DB::alteration_message('404 page created', 'created');
     77         }
     78     }
     79
     80     function onBeforeWrite() {
     81         if ($this->ErrorCode && (
     82             !$this->URLSegment
     83             || substr($this->URLSegment, 0, 4) == 'new-'
     84             || preg_match('/^error\d+$/', $this->URLSegment)
     85             || $this->URLSegment == $this->generateURLSegment($this->Title)
     86         )) {
     87             $this->URLSegment = 'error'. $this->ErrorCode;
     88         }
     89         parent::onBeforeWrite();
     90     }
     91
     92
     93     function getCMSFields() {
     94         $fields = parent::getCMSFields();
     95
     96         $fields->addFieldToTab(
     97             "Root.Content.Main",
     98             new DropdownField(
     99                 "ErrorCode",
    100                 $this->fieldLabel('ErrorCode'),
    101                 array(
    102                     400 => _t('ErrorPage.400', '400 - Bad Request'),
    103                     401 => _t('ErrorPage.401', '401 - Unauthorized'),
    104                     403 => _t('ErrorPage.403', '403 - Forbidden'),
    105                     404 => _t('ErrorPage.404', '404 - Not Found'),
    106                     405 => _t('ErrorPage.405', '405 - Method Not Allowed'),
    107                     406 => _t('ErrorPage.406', '406 - Not Acceptable'),
    108                     407 => _t('ErrorPage.407', '407 - Proxy Authentication Required'),
    109                     408 => _t('ErrorPage.408', '408 - Request Timeout'),
    110                     409 => _t('ErrorPage.409', '409 - Conflict'),
    111                     410 => _t('ErrorPage.410', '410 - Gone'),
    112                     411 => _t('ErrorPage.411', '411 - Length Required'),
    113                     412 => _t('ErrorPage.412', '412 - Precondition Failed'),
    114                     413 => _t('ErrorPage.413', '413 - Request Entity Too Large'),
    115                     414 => _t('ErrorPage.414', '414 - Request-URI Too Long'),
    116                     415 => _t('ErrorPage.415', '415 - Unsupported Media Type'),
    117                     416 => _t('ErrorPage.416', '416 - Request Range Not Satisfiable'),
    118                     417 => _t('ErrorPage.417', '417 - Expectation Failed'),
    119                     500 => _t('ErrorPage.500', '500 - Internal Server Error'),
    120                     501 => _t('ErrorPage.501', '501 - Not Implemented'),
    121                     502 => _t('ErrorPage.502', '502 - Bad Gateway'),
    122                     503 => _t('ErrorPage.503', '503 - Service Unavailable'),
    123                     504 => _t('ErrorPage.504', '504 - Gateway Timeout'),
    124                     505 => _t('ErrorPage.505', '505 - HTTP Version Not Supported'),
    125                 )
    126             ),
    127             "Content"
    128         );
    129
    130         return $fields;
    131     }
    132
    133     /**
    134      * When an error page is published, create a static HTML page with its
    135      * content, so the page can be shown even when SilverStripe is not
    136      * functioning correctly before publishing this page normally.
    137      * @param string|int $fromStage Place to copy from. Can be either a stage name or a version number.
    138      * @param string $toStage Place to copy to. Must be a stage name.
    139      * @param boolean $createNewVersion Set this to true to create a new version number.  By default, the existing version number will be copied over.
    140      */
    141     function doPublish() {
    142         parent::doPublish();
    143
    144         // Run the page
    145         $response = Director::test(Director::makeRelative($this->Link()));
    146
    147         $errorContent = $response->getBody();
    148         // Make the base tag dynamic.
    149         $errorContent = preg_replace('/<base[^>]+href="' . str_replace('/','\\/', Director::absoluteBaseURL()) . '"[^>]*>/i', '<base href="$BaseURL" />', $errorContent);
    150
    151         // Check we have an assets base directory, creating if it we don't
    152         if(!file_exists(ASSETS_PATH)) {
    153             mkdir(ASSETS_PATH, 02775);
    154         }
    155
    156
    157         // if the page is published in a language other than default language,
    158         // write a specific language version of the HTML page
    159         $filePath = self::get_filepath_for_errorcode($this->ErrorCode, $this->Locale);
    160         if($fh = fopen($filePath, "w")) {
    161             fwrite($fh, $errorContent);
    162             fclose($fh);
    163         } else {
    164             $fileErrorText = sprintf(
    165                 _t(
    166                     "ErrorPage.ERRORFILEPROBLEM",
    167                     "Error opening file \"%s\" for writing. Please check file permissions."
    168                 ),
    169                 $errorFile
    170             );
    171             FormResponse::status_message($fileErrorText, 'bad');
    172             FormResponse::respond();
    173             return;
    174         }
    175     }
    176
    177     /**
    178      *
    179      * @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
    180      *
    181      */
    182     function fieldLabels($includerelations = true) {
    183         $labels = parent::fieldLabels($includerelations);
    184         $labels['ErrorCode'] = _t('ErrorPage.CODE', "Error code");
    185
    186         return $labels;
    187     }
    188
    189     /**
    190      * Returns an absolute filesystem path to a static error file
    191      * which is generated through {@link publish()}.
    192      *
    193      * @param int $statusCode A HTTP Statuscode, mostly 404 or 500
    194      * @param String $locale A locale, e.g. 'de_DE' (Optional)
    195      * @return String
    196      */
    197     static function get_filepath_for_errorcode($statusCode, $locale = null) {
    198         if (singleton('ErrorPage')->hasMethod('alternateFilepathForErrorcode')) {
    199             return singleton('ErrorPage')-> alternateFilepathForErrorcode($statusCode, $locale);
    200         }
    201         if(singleton('SiteTree')->hasExtension('Translatable') && $locale && $locale != Translatable::default_locale()) {
    202             return self::$static_filepath . "/error-{$statusCode}-{$locale}.html";
    203         } else {
    204             return self::$static_filepath . "/error-{$statusCode}.html";
    205         }
    206     }
    207
    208     /**
    209      * Set the path where static error files are saved through {@link publish()}.
    210      * Defaults to /assets.
    211      *
    212      * @param string $path
    213      */
    214     static function set_static_filepath($path) {
    215         self::$static_filepath = $path;
    216     }
    217
    218     /**
    219      * @return string
    220      */
    221     static function get_static_filepath() {
    222         return self::$static_filepath;
    223     }
    224 }
    225
    226 /**
    227  * Controller for ErrorPages.
    228  * @package cms
    229  */
    230 class ErrorPage_Controller extends Page_Controller {
    231     function init() {
    232         parent::init();
    233
    234         $action = $this->request->param('Action');
    235         if(!$action || $action == 'index') {
    236             Director::set_status_code($this->failover->ErrorCode ? $this->failover->ErrorCode : 404);
    237         }
    238
    239     }
    240 }
    241
    242
    243 ?>
    244

[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. -