Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

General Questions /

General questions about getting started with SilverStripe that don't fit in any of the categories above.

Moderators: martimiz, Sean, biapar, Willr, Ingo, swaiba, simon_w

RedirectorPage and Director rules conflict?


Go to End
Reply


9 Posts   3124 Views

Avatar
aragonne

Community Member, 26 Posts

8 January 2012 at 3:02pm

Edited: 08/01/2012 3:04pm

Hi there,

I have a weird situation where a request for non-existent page doesn't send the visitor to the 404 Error page but instead to a parent page.

For example, a request is made for:

/understanding/analytes/xyz.

'xyz' is a non-existent (or a unpublished page). Normally the visitor should be sent to the 404 page, but instead is sent to the 'analytes' page, which is an existing page that is, of course, the parent of 'xyz'. I think the reason for this is that the Director rule:

'$URLSegment//$Action/$ID/$OtherID' => 'ModelAsController

in sapphire/_config.php, is matched, where

$URLSegment = /understanding/analytes/
$Action = xyz
$ID = null
$OtherID = null

Instead of redirecting to the 404 page, I think SilverStripe is looking for a method called 'xyz' in the page type for /understanding/analytes. However, /understanding/analytes is a RedirectorPage and since its init() method is the first thing executed, the user is sent to the redirect page.

I need to figure out how to create a subclass of RedirectorPage that when an Action param exists in the url, send the user to the 404 page instead of redirecting to the intended page. If an Action param doesn't exist, execute the normal redirect behavior.

I tried the following subclass code but got the error below:

class Redirector404Page extends RedirectorPage {}

class Redirector404Page_Controller extends RedirectorPage_Controller {
   function init() {
      $action = $this->request->param('Action');
      if ($action) {
         Director::redirect('/page-not-found', 404);
      }
      parent::init();
   }
}

Error message:

[User Warning] Already directed to /page-not-found; now trying to direct to /map/aindex/
GET /understanding/analytes/abc/def?debug_request=1

Line 464 in /Users/steve/__projects/lto_ss/us/silverstripe-us/sapphire/core/control/Controller.php
Source

455       }
456    }
457    
458    /**
459     * Redirct to the given URL.
460     * It is generally recommended to call Director::redirect() rather than calling this function directly.
461     */
462    function redirect($url, $code=302) {
463       if($this->response->getHeader('Location')) {
464          user_error("Already directed to " . $this->response->getHeader('Location') . "; now trying to direct to $url", E_USER_WARNING);
465          return;
466       }
467
468       // Attach site-root to relative links, if they have a slash in them
469       if($url == "" || $url[0] == '?' || (substr($url,0,4) != "http" && $url[0] != "/" && strpos($url,'/') !== false)){
470          $url = Director::baseURL() . $url;

Trace

Already directed to /page-not-found; now trying to direct to /map/aindex/
Line 464 of Controller.php
Controller->redirect(/map/aindex/,301)
Line 410 of Director.php
Director::redirect(/map/aindex/,301)
Line 159 of RedirectorPage.php
RedirectorPage_Controller->init()
Line 26 of Redirector404Page.php
Redirector404Page_Controller->init()
Line 136 of Controller.php
Controller->handleRequest(SS_HTTPRequest)
Line 199 of ContentController.php
ContentController->handleRequest(SS_HTTPRequest)
Line 184 of ContentController.php
ContentController->handleRequest(SS_HTTPRequest)
Line 67 of ModelAsController.php
ModelAsController->handleRequest(SS_HTTPRequest)
Line 282 of Director.php
Director::handleRequest(SS_HTTPRequest,Session)
Line 125 of Director.php
Director::direct(/understanding/analytes/xyz)
Line 127 of main.php

Can anyone help with advice on what code I can use in this subclass' init() method to send the user to a 404 page if an Action param exists?

Also, if there is a solution, is there away to specify the 404 error page, without hardcoding it with its URLSegment, /page-not-found?

thanks!

Avatar
martimiz

Forum Moderator, 1132 Posts

9 January 2012 at 12:28am

Edited: 09/01/2012 2:52am

Something like this maybe?


if (!empty($action)) {
$this->httpError(404, 'The requested page could not be found.');
}

[EDIT] Possibly better yet:

if($response = ErrorPage::response_for(404)) {
   return $response;
} else {
   $this->httpError(404, 'The requested page could not be found.');
}

Avatar
aragonne

Community Member, 26 Posts

9 January 2012 at 10:42pm

Thanks for the tip martimiz!

I updated the subclass' code to:

<?php

class Redirector404Page extends RedirectorPage {

}

class Redirector404Page_Controller extends RedirectorPage_Controller {
   function init() {
      $action = $this->request->param('Action');
      if ($action) {
         if(response = ErrorPage::response_for(404)) {
          return response;
         } else {
          $this->httpError(404, 'The requested page could not be found.');
         }
      }
      parent::init();
   }
}

but now getting this error when requesting the page:

[User Warning] init() method on class 'Redirector404Page_Controller' doesn't call Controller::init(). Make sure that you have parent::init() included.

However, I don't want to call RedirectorPage's (the parent class) init() method unless the if block fails. How can I get around this?

thanks!

Avatar
martimiz

Forum Moderator, 1132 Posts

9 January 2012 at 11:51pm

Edited: 09/01/2012 11:55pm

I would think you could add it at the bottom of the function, because if a 404 is generated beforehand, it won't get to that point anyway - [EDIT] but I see you already did that... :-(

Avatar
martimiz

Forum Moderator, 1132 Posts

10 January 2012 at 1:00am

Edited: 10/01/2012 1:02am

I'm beginning to think you shouldn't use the init() method at all, but instead extend the handleRequest method. The rest would stay almost the same:

function handleRequest(SS_HTTPRequest $request) {

   $action = $request->param('Action');
   if ($action) {
      if (response = ErrorPage::response_for(404)) {
         return response;
      } else {
         return $this->httpError(404, 'The requested page could not be found.');
      }
   }
   return parent::handleRequest($request);
}

Again - didn't test it. What do you think?

Avatar
aragonne

Community Member, 26 Posts

10 January 2012 at 1:05pm

Edited: 11/01/2012 1:37am

Hi martimiz,

Wow, thanks, this gets me a lot closer now and helps me understand the order of execution in which methods are called. I initially thought init() was the first method called, but now realize handleRequest() comes before that.

A request for an unpublished/non-existent child page of this new Redirector404Page now correctly returns the 404 page. However, a request for any existing/published child page of Redirector404Page now also returns a 404, which should not be the case. The check for the 'Action' param doesn't appear to be the correct check as I initially thought. I think this is because SilverStripe tries to match the Director rules first before trying to find the actual page?

For example, let's say an existing/published page '/understanding/analytes/sodium' is requested. Before page type reassignment to this new subclass, SilverStripe would have correctly returned this page. Now, what I think what is happening is:

1. SilverStripe receives the request
2. the url matches a Director rule '$URLSegment//$Action/$ID/$OtherID' first instead of finding if there is actually a page with this Link pattern
3. SS resolves the URLSegment as '/understanding/analytes' (a Redirector404Page) and that an Action param ('sodium') exists.
4. Of course, now in the handleRequest() method of Redirector404Page, SS sees the Action param exists and returns the 404 page.

I was thinking of changing from the check for the existence of the Action param to a check for the existence of a published page associated with the requested url, ie, /understanding/analytes/sodium. I found that the requested url can be fetched via request->getURL(). But after this, I'm somewhat lost as how to check if the page exists and if it does, return the page instead of the 404.

In pseudo-code:

$url = $request->getURL();
if (any page object Link matches with $url) {
return the page
}
else {
# same code you suggested goes below
if (action param exists) {
return 404
}
else {
redirect as usual
}
}

thanks for helping out martimiz!

(p.s. Is there a fresher way to think about this problem that I'm missing?)

Avatar
martimiz

Forum Moderator, 1132 Posts

11 January 2012 at 1:20am

Hi aragonne,

I've just now installed your redirectorpage to reproduce things. I think this really is a bug, since other pagetypes don't behave this way at all. But I can't seem to grasp what causes it - I guess it would take some serious core knowledge So I wouldn't delve too miserably deep in HandleRequest after all - unless for finding the real bugfix.

Your init() version almost works - if not for the Controller::init() warning :-( I've see other bits of core code where they manually disable init() at some point to avoid loops... So I tried manually 'fooling' Controller::init() in thinking it has been called. Not nice, but it does work. And as it's only your own 404redirector page that is affected, we might be in the clear for now? :-)

   function init() {
      $action = $this->request->param('Action');
      if ($action) {
          
          // fool Controller::init() - brrrrr...
          $this->baseInitCalled = true;

         if($response = ErrorPage::response_for(404)) {
            return $response;
         } else {
            Director::redirect('/page-not-found', 404);
         }
         return;
      }
      parent::init();
   }

Btw: $this->httpError(404, 'The requested page could not be found!!'); doesn't seem to work at all in this spot...

Avatar
aragonne

Community Member, 26 Posts

11 January 2012 at 2:31am

Edited: 11/01/2012 10:47am

Hi martimiz,

I totally agree with you that this is a bug of RedirectorPage. The use case is that any http request for a descendant unpublished (or non-existing) page of RedirectorPage will always return the redirect to page instead of the 404 page. I've submitted a bug ticket at http://open.silverstripe.org/ticket/6832.

Your recent suggestion appears to be good temporary workaround and I will use it until a patch is hopefully delivered. Here is the working code:

class Redirector404Page extends RedirectorPage {}

class Redirector404Page_Controller extends RedirectorPage_Controller {

   function init() {
      $action = $this->request->param('Action');
      if ($action) {

         // NOTE: fool Controller::init() by setting baseInitCalled, otherwise will
         //          get following error:
         //          [User Warning] init() method on class
         //          'Redirector404Page_Controller'
         //          doesn't call Controller::init(). Make sure that you have
         //          parent::init() included.
         $this->baseInitCalled = true;

         if ($response = ErrorPage::response_for(404)) {
            return $response;
         }
         else {
            return $this->httpError(404, 'The requested page could not be found.');
         }
      }
      parent::init();
   }
}

thanks again for the gracious help!
Steve

Go to Top