ENHANCEMENT: Added Object::combined_static(), which gets all values of a static property from each class in the hierarchy

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@73473 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2009-03-21 05:10:05 +00:00
parent 6b2cf2a48e
commit a28ea0a69e
4 changed files with 100 additions and 40 deletions

View File

@ -251,6 +251,37 @@ abstract class Object {
return ($inherited != $parent) ? $inherited : null;
}
/**
* Traverse down a class ancestry and attempt to merge all the uninherited static values for a particular static
* into a single variable
*
* @param string $class
* @param string $name the static name
* @param string $ceiling an optional parent class name to begin merging statics down from, rather than traversing
* the entire hierarchy
* @return mixed
*/
public static function combined_static($class, $name, $ceiling = false) {
$ancestry = ClassInfo::ancestry($class);
$values = null;
if($ceiling) while(current($ancestry) != $ceiling && $ancestry) {
array_shift($ancestry);
}
if($ancestry) foreach($ancestry as $ancestor) {
$merge = self::uninherited_static($ancestor, $name);
if(is_array($values) && is_array($merge)) {
$values = array_merge($values, $merge);
} elseif($merge) {
$values = $merge;
}
}
return $values;
}
/**
* Merge in a set of additional static variables
*

View File

@ -81,8 +81,7 @@ class RequestHandler extends ViewableData {
$handlerClass = ($this->class) ? $this->class : get_class($this);
// We stop after RequestHandler; in other words, at ViewableData
while($handlerClass && $handlerClass != 'ViewableData') {
// Todo: ajshort's stat rewriting could be useful here.
$urlHandlers = eval("return $handlerClass::\$url_handlers;");
$urlHandlers = Object::get_static($handlerClass, 'url_handlers');
if($urlHandlers) foreach($urlHandlers as $rule => $action) {
if(isset($_REQUEST['debug_request'])) Debug::message("Testing '$rule' with '" . $request->remaining() . "' on $this->class");
@ -144,65 +143,57 @@ class RequestHandler extends ViewableData {
// If nothing matches, return this object
return $this;
}
/**
* Check that the given action is allowed to be called from a URL.
* It will interrogate {@link self::$allowed_actions} to determine this.
*/
function checkAccessAction($action) {
// Collate self::$allowed_actions from this class and all parent classes
$access = null;
$className = ($this->class) ? $this->class : get_class($this);
while($className && $className != 'RequestHandler') {
// Merge any non-null parts onto $access.
$accessPart = eval("return $className::\$allowed_actions;");
if($accessPart != null) $access = array_merge((array)$access, $accessPart);
// Build an array of parts for checking if part[0] == part[1], which means that this class doesn't directly define it.
$accessParts[] = $accessPart;
$className = get_parent_class($className);
}
$action = strtolower($action);
$allowedActions = Object::combined_static($this->class, 'allowed_actions');
$newAllowedActions = array();
// Add $allowed_actions from extensions
if($this->extension_instances) {
foreach($this->extension_instances as $inst) {
$accessPart = $inst->stat('allowed_actions');
if($accessPart !== null) $access = array_merge((array)$access, $accessPart);
// merge in any $allowed_actions from extensions
if($this->extension_instances) foreach($this->extension_instances as $extension) {
if($extAccess = $extension->stat('allowed_actions')) {
$allowedActions = array_merge($allowedActions, $extAccess);
}
}
if($action == 'index') return true;
// Make checkAccessAction case-insensitive
$action = strtolower($action);
if($access) {
foreach($access as $k => $v) $newAccess[strtolower($k)] = strtolower($v);
$access = $newAccess;
if($allowedActions) {
foreach($allowedActions as $key => $value) {
$newAllowedActions[strtolower($key)] = strtolower($value);
}
$allowedActions = $newAllowedActions;
if(isset($allowedActions[$action])) {
$test = $allowedActions[$action];
if(isset($access[$action])) {
$test = $access[$action];
if($test === true) return true;
if(substr($test,0,2) == '->') {
$funcName = substr($test,2);
return $this->$funcName();
if($test === true) {
return true;
} elseif(substr($test, 0, 2) == '->') {
return $this->{substr($test, 2)}();
} elseif(Permission::check($test)) {
return true;
}
if(Permission::check($test)) return true;
} else if((($key = array_search($action, $access)) !== false) && is_numeric($key)) {
} elseif((($key = array_search($action, $allowedActions)) !== false) && is_numeric($key)) {
return true;
}
}
if($access === null || (isset($accessParts[1]) && $accessParts[0] === $accessParts[1])) {
if($allowedActions === null || !$this->uninherited('allowed_actions')) {
// If no allowed_actions are provided, then we should only let through actions that aren't handled by magic methods
// we test this by calling the unmagic method_exists and comparing it to the magic $this->hasMethod(). This will
// still let through actions that are handled by templates.
return method_exists($this, $action) || !$this->hasMethod($action);
}
return false;
}
/**
* Throw an HTTP error instead of performing the normal processing
* @todo This doesn't work properly right now. :-(

View File

@ -35,6 +35,10 @@ class ControllerTest extends SapphireTest {
$response = Director::test("ControllerTest_SecuredController/adminonly");
$this->assertEquals(403, $response->getStatusCode());
// test that a controller without a specified allowed_actions allows actions through
$response = Director::test('ControllerTest_UnsecuredController/stringaction');
$this->assertEquals(200, $response->getStatusCode());
}
/**
@ -105,4 +109,6 @@ class ControllerTest_SecuredController extends Controller {
function adminonly() {
return "You must be an admin!";
}
}
}
class ControllerTest_UnsecuredController extends ControllerTest_SecuredController {}

View File

@ -52,6 +52,23 @@ class ObjectStaticTest extends SapphireTest {
$this->assertEquals(Object::uninherited_static('ObjectStaticTest_Fourth', 'third', true), null);
}
public function testCombinedStatic() {
// test basic operation
$this->assertEquals (
array('test_1', 'test_2', 'test_3'), Object::combined_static('ObjectStaticTest_Combined3', 'first')
);
// test that null values are ignored, but values on either side are still merged
$this->assertEquals (
array('test_1', 'test_3'), Object::combined_static('ObjectStaticTest_Combined3', 'second')
);
// test the $ceiling param
$this->assertEquals (
array('test_2', 'test_3'), Object::combined_static('ObjectStaticTest_Combined3', 'first', 'ObjectStaticTest_Combined2')
);
}
}
/**#@+
@ -76,4 +93,19 @@ class ObjectStaticTest_Third extends ObjectStaticTest_Second {
class ObjectStaticTest_Fourth extends ObjectStaticTest_Third {
public static $fourth = array('test_4');
}
class ObjectStaticTest_Combined1 extends Object {
public static $first = array('test_1');
public static $second = array('test_1');
}
class ObjectStaticTest_Combined2 extends ObjectStaticTest_Combined1 {
public static $first = array('test_2');
public static $second = null;
}
class ObjectStaticTest_Combined3 extends ObjectStaticTest_Combined2 {
public static $first = array('test_3');
public static $second = array('test_3');
}
/**#@-*/