FEATURE: Added RequestHandler->hasAction() and Controller->hasAction() to check if a specific action is defined on a controller.

ENHANCEMENT: Updated ContentController->handleRequest() to use Controller->hasAction() to check whether to fall over to a child page, rather than relying on an error response from Controller->handleRequest().

From: Andrew Short <andrewjshort@gmail.com>

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@88505 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Andrew Short 2009-10-11 00:07:23 +00:00 committed by Sam Minnee
parent 97ac0008b3
commit da4b65c749
5 changed files with 76 additions and 37 deletions

View File

@ -168,30 +168,33 @@ class ContentController extends Controller {
* @return HTTPResponse * @return HTTPResponse
*/ */
public function handleRequest(HTTPRequest $request) { public function handleRequest(HTTPRequest $request) {
Director::set_current_page($this->data()); $child = null;
$response = parent::handleRequest($request); $action = $request->param('Action');
// If the default handler returns an error, due to the action not existing, attempt to fall over to a child // If nested URLs are enabled, and there is no action handler for the current request then attempt to pass
// SiteTree object, then use its corresponding ContentController to handle the request. This allows for the // control to a child controller. This allows for the creation of chains of controllers which correspond to a
// building of nested chains of controllers corresponding to a nested URL. // nested URL.
if(SiteTree::nested_urls() && $response instanceof HTTPResponse && $response->isError()) { if($action && SiteTree::nested_urls() && !$this->hasAction($action)) {
Translatable::disable_locale_filter(); Translatable::disable_locale_filter();
$child = DataObject::get_one('SiteTree', sprintf ( $child = DataObject::get_one('SiteTree', sprintf (
"\"ParentID\" = %s AND \"URLSegment\" = '%s'", "\"ParentID\" = %s AND \"URLSegment\" = '%s'", $this->ID, Convert::raw2sql($action)
$this->ID,
Convert::raw2sql($request->param('Action'))
)); ));
Translatable::enable_locale_filter(); Translatable::enable_locale_filter();
if($child && $child->canView()) {
$request->shiftAllParams();
return ModelAsController::controller_for($child)->handleRequest($request);
}
} }
Director::set_current_page(null); if($child) {
$request->shiftAllParams();
$request->shift();
$response = ModelAsController::controller_for($child)->handleRequest($request);
} else {
Director::set_current_page($this->data());
$response = parent::handleRequest($request);
Director::set_current_page(null);
}
return $response; return $response;
} }

View File

@ -198,7 +198,7 @@ class Controller extends RequestHandler {
return $result; return $result;
} }
} else { } else {
if($this->action == 'index' || $this->hasActionTemplate($this->action)) { if($this->action == 'index' || $this->hasAction($this->action)) {
return $this->getViewer($this->action)->process($this); return $this->getViewer($this->action)->process($this);
} else { } else {
return $this->httpError(404, "The action '$this->action' does not exist in class $this->class"); return $this->httpError(404, "The action '$this->action' does not exist in class $this->class");
@ -306,15 +306,17 @@ class Controller extends RequestHandler {
return new SSViewer($templates); return new SSViewer($templates);
} }
public function hasAction($action) {
return parent::hasAction($action) || $this->hasActionTemplate($action);
}
/** /**
* Returns TRUE if this controller has a template that is specifically designed to handle a specific action. * Returns TRUE if this controller has a template that is specifically designed to handle a specific action.
* *
* @param string $action * @param string $action
* @return bool * @return bool
*/ */
public function hasActionTemplate($action = null) { public function hasActionTemplate($action) {
if(!$action) $action = $this->action;
if(isset($this->templates[$action])) return true; if(isset($this->templates[$action])) return true;
$parentClass = $this->class; $parentClass = $this->class;

25
core/control/RequestHandler.php Normal file → Executable file
View File

@ -176,6 +176,31 @@ class RequestHandler extends ViewableData {
return $this; return $this;
} }
/**
* Checks if this request handler has a specific action (even if the current user cannot access it).
*
* @param string $action
* @return bool
*/
public function hasAction($action) {
if($action == 'index') return true;
$action = strtolower($action);
$actions = Object::combined_static($this->class, 'allowed_actions', 'RequestHandler');
if(is_array($actions)) {
if(array_key_exists($action, $actions) || in_array($action, $actions)) {
return true;
}
}
if(!is_array($actions) || !$this->uninherited('allowed_actions')) {
if($action != 'init' && $action != 'run' && method_exists($this, $action)) return true;
}
return false;
}
/** /**
* Check that the given action is allowed to be called from a URL. * Check that the given action is allowed to be called from a URL.
* It will interrogate {@link self::$allowed_actions} to determine this. * It will interrogate {@link self::$allowed_actions} to determine this.

View File

@ -1343,28 +1343,13 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
* - A page with the same URLSegment that has a conflict. * - A page with the same URLSegment that has a conflict.
* - Conflicts with actions on the parent page. * - Conflicts with actions on the parent page.
* - A conflict caused by a root page having the same URLSegment as a class name. * - A conflict caused by a root page having the same URLSegment as a class name.
* - Conflicts with action-specific templates on the parent page.
* *
* @return bool * @return bool
*/ */
public function validURLSegment() { public function validURLSegment() {
if(self::nested_urls() && $parent = $this->Parent()) { if(self::nested_urls() && $parent = $this->Parent()) {
if($this->URLSegment == 'index') return false;
if($controller = ModelAsController::controller_for($parent)) { if($controller = ModelAsController::controller_for($parent)) {
$actions = Object::combined_static($controller->class, 'allowed_actions', 'RequestHandler'); if($controller instanceof Controller && $controller->hasAction($this->URLSegment)) return false;
// check for a conflict with an entry in $allowed_actions
if(is_array($actions)) {
if(array_key_exists($this->URLSegment, $actions) || in_array($this->URLSegment, $actions)) {
return false;
}
}
// check for a conflict with an action-specific template
if($controller->hasMethod('hasActionTemplate') && $controller->hasActionTemplate($this->URLSegment)) {
return false;
}
} }
} }

View File

@ -103,6 +103,18 @@ class ControllerTest extends FunctionalTest {
$this->assertEquals('/admin/action', Controller::join_links('/admin', 'action')); $this->assertEquals('/admin/action', Controller::join_links('/admin', 'action'));
} }
/**
* @covers Controller::hasAction()
*/
public function testHasAction() {
$controller = new ControllerTest_HasAction();
$this->assertFalse($controller->hasAction('undefined'), 'undefined actions do not exist');
$this->assertTrue($controller->hasAction('allowed_action'), 'allowed actions are recognised');
$this->assertTrue($controller->hasAction('template_action'), 'action-specific templates are recognised');
}
} }
/** /**
@ -165,3 +177,15 @@ class ControllerTest_FullSecuredController extends Controller {
} }
class ControllerTest_UnsecuredController extends ControllerTest_SecuredController {} class ControllerTest_UnsecuredController extends ControllerTest_SecuredController {}
class ControllerTest_HasAction extends Controller {
public static $allowed_actions = array (
'allowed_action'
);
protected $templates = array (
'template_action' => 'template'
);
}