mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
FIX: Fix type preservation in <% include %> arguments
This commit is contained in:
parent
4339e4d02c
commit
d6e8229352
@ -162,16 +162,17 @@ class SSViewer_DataPresenter extends SSViewer_Scope
|
|||||||
public function getInjectedValue($property, array $params, $cast = true)
|
public function getInjectedValue($property, array $params, $cast = true)
|
||||||
{
|
{
|
||||||
// Get source for this value
|
// Get source for this value
|
||||||
$source = $this->getValueSource($property);
|
$result = $this->getValueSource($property);
|
||||||
if (!$source) {
|
if (!array_key_exists('source', $result)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the value - either from a callable, or from a directly provided value
|
// Look up the value - either from a callable, or from a directly provided value
|
||||||
|
$source = $result['source'];
|
||||||
$res = [];
|
$res = [];
|
||||||
if (isset($source['callable'])) {
|
if (isset($source['callable'])) {
|
||||||
$res['value'] = $source['callable'](...$params);
|
$res['value'] = $source['callable'](...$params);
|
||||||
} elseif (isset($source['value'])) {
|
} elseif (array_key_exists('value', $source)) {
|
||||||
$res['value'] = $source['value'];
|
$res['value'] = $source['value'];
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidArgumentException(
|
throw new InvalidArgumentException(
|
||||||
@ -298,6 +299,8 @@ class SSViewer_DataPresenter extends SSViewer_Scope
|
|||||||
$obj = $val['obj'];
|
$obj = $val['obj'];
|
||||||
if ($name === 'hasValue') {
|
if ($name === 'hasValue') {
|
||||||
$result = ($obj instanceof ViewableData) ? $obj->exists() : (bool)$obj;
|
$result = ($obj instanceof ViewableData) ? $obj->exists() : (bool)$obj;
|
||||||
|
} elseif (is_null($obj) || (is_scalar($obj) && !is_string($obj))) {
|
||||||
|
$result = $obj; // Nulls and non-string scalars don't need casting
|
||||||
} else {
|
} else {
|
||||||
$result = $obj->forTemplate(); // XML_val
|
$result = $obj->forTemplate(); // XML_val
|
||||||
}
|
}
|
||||||
@ -310,16 +313,18 @@ class SSViewer_DataPresenter extends SSViewer_Scope
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate a template override
|
* Evaluate a template override. Returns an array where the presence of
|
||||||
|
* a 'value' key indiciates whether an override was successfully found,
|
||||||
|
* as null is a valid override value
|
||||||
*
|
*
|
||||||
* @param string $property Name of override requested
|
* @param string $property Name of override requested
|
||||||
* @param array $overrides List of overrides available
|
* @param array $overrides List of overrides available
|
||||||
* @return null|array Null if not provided, or array with 'value' or 'callable' key
|
* @return array An array with a 'value' key if a value has been found, or empty if not
|
||||||
*/
|
*/
|
||||||
protected function processTemplateOverride($property, $overrides)
|
protected function processTemplateOverride($property, $overrides)
|
||||||
{
|
{
|
||||||
if (!isset($overrides[$property])) {
|
if (!array_key_exists($property, $overrides)) {
|
||||||
return null;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect override type
|
// Detect override type
|
||||||
@ -331,38 +336,40 @@ class SSViewer_DataPresenter extends SSViewer_Scope
|
|||||||
|
|
||||||
// Late override may yet return null
|
// Late override may yet return null
|
||||||
if (!isset($override)) {
|
if (!isset($override)) {
|
||||||
return null;
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [ 'value' => $override ];
|
return ['value' => $override];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine source to use for getInjectedValue
|
* Determine source to use for getInjectedValue. Returns an array where the presence of
|
||||||
|
* a 'source' key indiciates whether a value source was successfully found, as a source
|
||||||
|
* may be a null value returned from an override
|
||||||
*
|
*
|
||||||
* @param string $property
|
* @param string $property
|
||||||
* @return array|null
|
* @return array An array with a 'source' key if a value source has been found, or empty if not
|
||||||
*/
|
*/
|
||||||
protected function getValueSource($property)
|
protected function getValueSource($property)
|
||||||
{
|
{
|
||||||
// Check for a presenter-specific override
|
// Check for a presenter-specific override
|
||||||
$overlay = $this->processTemplateOverride($property, $this->overlay);
|
$result = $this->processTemplateOverride($property, $this->overlay);
|
||||||
if (isset($overlay)) {
|
if (array_key_exists('value', $result)) {
|
||||||
return $overlay;
|
return ['source' => $result];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the method to-be-called exists on the target object - if so, don't check any further
|
// Check if the method to-be-called exists on the target object - if so, don't check any further
|
||||||
// injection locations
|
// injection locations
|
||||||
$on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
|
$on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
|
||||||
if (isset($on->$property) || method_exists($on, $property ?? '')) {
|
if (isset($on->$property) || method_exists($on, $property ?? '')) {
|
||||||
return null;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a presenter-specific override
|
// Check for a presenter-specific override
|
||||||
$underlay = $this->processTemplateOverride($property, $this->underlay);
|
$result = $this->processTemplateOverride($property, $this->underlay);
|
||||||
if (isset($underlay)) {
|
if (array_key_exists('value', $result)) {
|
||||||
return $underlay;
|
return ['source' => $result];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then for iterator-specific overrides
|
// Then for iterator-specific overrides
|
||||||
@ -381,16 +388,19 @@ class SSViewer_DataPresenter extends SSViewer_Scope
|
|||||||
// If we don't actually have an iterator at the moment, act like a list of length 1
|
// If we don't actually have an iterator at the moment, act like a list of length 1
|
||||||
$implementor->iteratorProperties(0, 1);
|
$implementor->iteratorProperties(0, 1);
|
||||||
}
|
}
|
||||||
return $source;
|
|
||||||
|
return ($source) ? ['source' => $source] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// And finally for global overrides
|
// And finally for global overrides
|
||||||
if (array_key_exists($property, self::$globalProperties)) {
|
if (array_key_exists($property, self::$globalProperties)) {
|
||||||
return self::$globalProperties[$property]; //get the method call
|
return [
|
||||||
|
'source' => self::$globalProperties[$property] // get the method call
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// No value
|
// No value
|
||||||
return null;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -402,8 +412,8 @@ class SSViewer_DataPresenter extends SSViewer_Scope
|
|||||||
*/
|
*/
|
||||||
protected function castValue($value, $source)
|
protected function castValue($value, $source)
|
||||||
{
|
{
|
||||||
// Already cast
|
// If the value has already been cast, is null, or is a non-string scalar
|
||||||
if (is_object($value)) {
|
if (is_object($value) || is_null($value) || (is_scalar($value) && !is_string($value))) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -713,63 +713,86 @@ after'
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTypesArePreserved()
|
public function typePreservationDataProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// Null
|
||||||
|
['NULL:', 'null'],
|
||||||
|
['NULL:', 'NULL'],
|
||||||
|
// Booleans
|
||||||
|
['boolean:1', 'true'],
|
||||||
|
['boolean:1', 'TRUE'],
|
||||||
|
['boolean:', 'false'],
|
||||||
|
['boolean:', 'FALSE'],
|
||||||
|
// Strings which loosely look like booleans
|
||||||
|
['string:truthy', 'truthy'],
|
||||||
|
['string:falsy', 'falsy'],
|
||||||
|
// Integers
|
||||||
|
['integer:0', '0'],
|
||||||
|
['integer:1', '1'],
|
||||||
|
['integer:15', '15'],
|
||||||
|
['integer:-15', '-15'],
|
||||||
|
// Octal integers
|
||||||
|
['integer:83', '0123'],
|
||||||
|
['integer:-83', '-0123'],
|
||||||
|
// Hexadecimal integers
|
||||||
|
['integer:26', '0x1A'],
|
||||||
|
['integer:-26', '-0x1A'],
|
||||||
|
// Binary integers
|
||||||
|
['integer:255', '0b11111111'],
|
||||||
|
['integer:-255', '-0b11111111'],
|
||||||
|
// Floats (aka doubles)
|
||||||
|
['double:0', '0.0'],
|
||||||
|
['double:1', '1.0'],
|
||||||
|
['double:15.25', '15.25'],
|
||||||
|
['double:-15.25', '-15.25'],
|
||||||
|
['double:1200', '1.2e3'],
|
||||||
|
['double:-1200', '-1.2e3'],
|
||||||
|
['double:0.07', '7E-2'],
|
||||||
|
['double:-0.07', '-7E-2'],
|
||||||
|
// Explicitly quoted strings
|
||||||
|
['string:0', '"0"'],
|
||||||
|
['string:1', '\'1\''],
|
||||||
|
['string:foobar', '"foobar"'],
|
||||||
|
['string:foo bar baz', '"foo bar baz"'],
|
||||||
|
// Implicit strings
|
||||||
|
['string:foobar', 'foobar'],
|
||||||
|
['string:foo bar baz', 'foo bar baz']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider typePreservationDataProvider
|
||||||
|
*/
|
||||||
|
public function testTypesArePreserved($expected, $templateArg)
|
||||||
{
|
{
|
||||||
$data = new ArrayData([
|
$data = new ArrayData([
|
||||||
'Test' => new TestViewableData()
|
'Test' => new TestViewableData()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Null
|
$this->assertEquals($expected, $this->render("\$Test.Type({$templateArg})", $data));
|
||||||
$this->assertEquals('NULL:', $this->render('$Test.Type(null)', $data));
|
}
|
||||||
$this->assertEquals('NULL:', $this->render('$Test.Type(NULL)', $data));
|
|
||||||
|
|
||||||
// Booleans
|
/**
|
||||||
$this->assertEquals('boolean:1', $this->render('$Test.Type(TRUE)', $data));
|
* @dataProvider typePreservationDataProvider
|
||||||
$this->assertEquals('boolean:1', $this->render('$Test.Type(true)', $data));
|
*/
|
||||||
$this->assertEquals('boolean:', $this->render('$Test.Type(FALSE)', $data));
|
public function testTypesArePreservedAsIncludeArguments($expected, $templateArg)
|
||||||
$this->assertEquals('boolean:', $this->render('$Test.Type(false)', $data));
|
{
|
||||||
|
$data = new ArrayData([
|
||||||
|
'Test' => new TestViewableData()
|
||||||
|
]);
|
||||||
|
|
||||||
// Strings which loosely look like booleans
|
$this->assertEquals(
|
||||||
$this->assertEquals('string:truthy', $this->render('$Test.Type(truthy)', $data));
|
$expected,
|
||||||
$this->assertEquals('string:falsy', $this->render('$Test.Type(falsy)', $data));
|
$this->render("<% include SSViewerTestTypePreservation Argument={$templateArg} %>", $data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Integers
|
public function testTypePreservationInConditionals()
|
||||||
$this->assertEquals('integer:0', $this->render('$Test.Type(0)', $data));
|
{
|
||||||
$this->assertEquals('integer:1', $this->render('$Test.Type(1)', $data));
|
$data = new ArrayData([
|
||||||
$this->assertEquals('integer:15', $this->render('$Test.Type(15)', $data));
|
'Test' => new TestViewableData()
|
||||||
$this->assertEquals('integer:-15', $this->render('$Test.Type(-15)', $data));
|
]);
|
||||||
|
|
||||||
# Octal integers
|
|
||||||
$this->assertEquals('integer:83', $this->render('$Test.Type(0123)', $data));
|
|
||||||
$this->assertEquals('integer:-83', $this->render('$Test.Type(-0123)', $data));
|
|
||||||
|
|
||||||
# Hexadecimal integers
|
|
||||||
$this->assertEquals('integer:26', $this->render('$Test.Type(0x1A)', $data));
|
|
||||||
$this->assertEquals('integer:-26', $this->render('$Test.Type(-0x1A)', $data));
|
|
||||||
|
|
||||||
# Binary integers
|
|
||||||
$this->assertEquals('integer:255', $this->render('$Test.Type(0b11111111)', $data));
|
|
||||||
$this->assertEquals('integer:-255', $this->render('$Test.Type(-0b11111111)', $data));
|
|
||||||
|
|
||||||
// Floats (aka doubles)
|
|
||||||
$this->assertEquals('double:0', $this->render('$Test.Type(0.0)', $data));
|
|
||||||
$this->assertEquals('double:1', $this->render('$Test.Type(1.0)', $data));
|
|
||||||
$this->assertEquals('double:15.25', $this->render('$Test.Type(15.25)', $data));
|
|
||||||
$this->assertEquals('double:-15.25', $this->render('$Test.Type(-15.25)', $data));
|
|
||||||
$this->assertEquals('double:1200', $this->render('$Test.Type(1.2e3)', $data));
|
|
||||||
$this->assertEquals('double:-1200', $this->render('$Test.Type(-1.2e3)', $data));
|
|
||||||
$this->assertEquals('double:0.07', $this->render('$Test.Type(7E-2)', $data));
|
|
||||||
$this->assertEquals('double:-0.07', $this->render('$Test.Type(-7E-2)', $data));
|
|
||||||
|
|
||||||
// Explicitly quoted strings
|
|
||||||
$this->assertEquals('string:0', $this->render('$Test.Type("0")', $data));
|
|
||||||
$this->assertEquals('string:1', $this->render('$Test.Type(\'1\')', $data));
|
|
||||||
$this->assertEquals('string:foobar', $this->render('$Test.Type("foobar")', $data));
|
|
||||||
$this->assertEquals('string:foo bar baz', $this->render('$Test.Type("foo bar baz")', $data));
|
|
||||||
|
|
||||||
// Implicit strings
|
|
||||||
$this->assertEquals('string:foobar', $this->render('$Test.Type(foobar)', $data));
|
|
||||||
$this->assertEquals('string:foo bar baz', $this->render('$Test.Type(foo bar baz)', $data));
|
|
||||||
|
|
||||||
// Types in conditionals
|
// Types in conditionals
|
||||||
$this->assertEquals('pass', $this->render('<% if true %>pass<% else %>fail<% end_if %>', $data));
|
$this->assertEquals('pass', $this->render('<% if true %>pass<% else %>fail<% end_if %>', $data));
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
$Test.Type($Argument)
|
Loading…
Reference in New Issue
Block a user