mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Compare commits
7 Commits
b8bce035df
...
75787d4819
Author | SHA1 | Date | |
---|---|---|---|
|
75787d4819 | ||
|
84556e037e | ||
|
aa2b8c380e | ||
|
27708c7ebb | ||
|
483e944601 | ||
|
d7fa53139f | ||
|
862a65eacc |
@ -27,7 +27,7 @@ class NavigateCommand extends Command
|
|||||||
|
|
||||||
// Handle request and output resonse body
|
// Handle request and output resonse body
|
||||||
$response = $app->handle($request);
|
$response = $app->handle($request);
|
||||||
$output->writeln($response->getBody(), OutputInterface::OUTPUT_RAW);
|
$output->writeln($response->getBody() ?? '', OutputInterface::OUTPUT_RAW);
|
||||||
|
|
||||||
// Transform HTTP status code into sensible exit code
|
// Transform HTTP status code into sensible exit code
|
||||||
$responseCode = $response->getStatusCode();
|
$responseCode = $response->getStatusCode();
|
||||||
|
@ -448,7 +448,7 @@ class ModelData implements Stringable
|
|||||||
* that have been specified.
|
* that have been specified.
|
||||||
*
|
*
|
||||||
* @return object|DBField|null The specific object representing the field, or null if there is no
|
* @return object|DBField|null The specific object representing the field, or null if there is no
|
||||||
* property, method, or dynamic data available for that field or if the value is explicitly null.
|
* property, method, or dynamic data available for that field.
|
||||||
*/
|
*/
|
||||||
public function obj(
|
public function obj(
|
||||||
string $fieldName,
|
string $fieldName,
|
||||||
@ -482,7 +482,7 @@ class ModelData implements Stringable
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = CastingService::singleton()->cast($value, $this, $fieldName);
|
$value = CastingService::singleton()->cast($value, $this, $fieldName, true);
|
||||||
|
|
||||||
// Record in cache
|
// Record in cache
|
||||||
if ($cache) {
|
if ($cache) {
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace SilverStripe\View;
|
namespace SilverStripe\View;
|
||||||
|
|
||||||
|
use LogicException;
|
||||||
|
use SilverStripe\Core\ClassInfo;
|
||||||
use SilverStripe\Core\Injector\Injectable;
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
use SilverStripe\Core\Injector\Injector;
|
use SilverStripe\Core\Injector\Injector;
|
||||||
use SilverStripe\Model\ArrayData;
|
use SilverStripe\Model\ArrayData;
|
||||||
@ -16,11 +18,18 @@ class CastingService
|
|||||||
{
|
{
|
||||||
use Injectable;
|
use Injectable;
|
||||||
|
|
||||||
public function cast(mixed $data, null|array|ModelData $source = null, string $fieldName = ''): ?object
|
/**
|
||||||
|
* Cast a value to the relevant object (usually a DBField instance) for use in the view layer.
|
||||||
|
*
|
||||||
|
* @param null|array|ModelData $source Where the data originates from. This is used both to check for casting helpers
|
||||||
|
* and to help set the value in cast DBField instances.
|
||||||
|
* @param bool $strict If true, an object will be returned even if $data is null.
|
||||||
|
*/
|
||||||
|
public function cast(mixed $data, null|array|ModelData $source = null, string $fieldName = '', bool $strict = false): ?object
|
||||||
{
|
{
|
||||||
// null is null - we shouldn't cast it to an object, because that makes it harder
|
// null is null - we shouldn't cast it to an object, because that makes it harder
|
||||||
// for downstream checks to know there's "no value".
|
// for downstream checks to know there's "no value".
|
||||||
if ($data === null) {
|
if (!$strict && $data === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,11 +48,14 @@ class CastingService
|
|||||||
// Explicit casts take precedence over array casting
|
// Explicit casts take precedence over array casting
|
||||||
if ($service) {
|
if ($service) {
|
||||||
$castObject = Injector::inst()->create($service, $fieldName);
|
$castObject = Injector::inst()->create($service, $fieldName);
|
||||||
|
if (!ClassInfo::hasMethod($castObject, 'setValue')) {
|
||||||
|
throw new LogicException('Explicit casting service must have a setValue method.');
|
||||||
|
}
|
||||||
$castObject->setValue($data, $source);
|
$castObject->setValue($data, $source);
|
||||||
return $castObject;
|
return $castObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap list arrays in ModelData so templates can handle them
|
// Wrap arrays in ModelData so templates can handle them
|
||||||
if (is_array($data)) {
|
if (is_array($data)) {
|
||||||
return array_is_list($data) ? ArrayList::create($data) : ArrayData::create($data);
|
return array_is_list($data) ? ArrayList::create($data) : ArrayData::create($data);
|
||||||
}
|
}
|
||||||
@ -51,6 +63,9 @@ class CastingService
|
|||||||
// Fall back to default casting
|
// Fall back to default casting
|
||||||
$service = $this->defaultService($data, $source, $fieldName);
|
$service = $this->defaultService($data, $source, $fieldName);
|
||||||
$castObject = Injector::inst()->create($service, $fieldName);
|
$castObject = Injector::inst()->create($service, $fieldName);
|
||||||
|
if (!ClassInfo::hasMethod($castObject, 'setValue')) {
|
||||||
|
throw new LogicException('Default service must have a setValue method.');
|
||||||
|
}
|
||||||
$castObject->setValue($data, $source);
|
$castObject->setValue($data, $source);
|
||||||
return $castObject;
|
return $castObject;
|
||||||
}
|
}
|
||||||
|
@ -247,7 +247,7 @@ class SSTemplateParser extends Parser implements TemplateParser
|
|||||||
}
|
}
|
||||||
|
|
||||||
$res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] :
|
$res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] :
|
||||||
str_replace('$$FINAL', 'getOutputValue', $sub['php'] ?? '');
|
str_replace('$$FINAL', 'getValueAsArgument', $sub['php'] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!*
|
/*!*
|
||||||
@ -286,9 +286,11 @@ class SSTemplateParser extends Parser implements TemplateParser
|
|||||||
|
|
||||||
if (isset($sub['Call']['CallArguments']) && isset($sub['Call']['CallArguments']['php'])) {
|
if (isset($sub['Call']['CallArguments']) && isset($sub['Call']['CallArguments']['php'])) {
|
||||||
$arguments = $sub['Call']['CallArguments']['php'];
|
$arguments = $sub['Call']['CallArguments']['php'];
|
||||||
$res['php'] .= "->$method('$property', [$arguments], 'method', true)";
|
$type = ViewLayerData::TYPE_METHOD;
|
||||||
|
$res['php'] .= "->$method('$property', [$arguments], '$type', true)";
|
||||||
} else {
|
} else {
|
||||||
$res['php'] .= "->$method('$property', [], 'property', true)";
|
$type = ViewLayerData::TYPE_PROPERTY;
|
||||||
|
$res['php'] .= "->$method('$property', [], '$type', true)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,7 +572,7 @@ class SSTemplateParser extends Parser implements TemplateParser
|
|||||||
}
|
}
|
||||||
|
|
||||||
$res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] :
|
$res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] :
|
||||||
str_replace('$$FINAL', 'getOutputValue', $sub['php'] ?? '');
|
str_replace('$$FINAL', 'getValueAsArgument', $sub['php'] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Call: Method:Word ( "(" < :CallArguments? > ")" )? */
|
/* Call: Method:Word ( "(" < :CallArguments? > ")" )? */
|
||||||
@ -777,9 +777,11 @@ class SSTemplateParser extends Parser implements TemplateParser
|
|||||||
|
|
||||||
if (isset($sub['Call']['CallArguments']) && isset($sub['Call']['CallArguments']['php'])) {
|
if (isset($sub['Call']['CallArguments']) && isset($sub['Call']['CallArguments']['php'])) {
|
||||||
$arguments = $sub['Call']['CallArguments']['php'];
|
$arguments = $sub['Call']['CallArguments']['php'];
|
||||||
$res['php'] .= "->$method('$property', [$arguments], 'method', true)";
|
$type = ViewLayerData::TYPE_METHOD;
|
||||||
|
$res['php'] .= "->$method('$property', [$arguments], '$type', true)";
|
||||||
} else {
|
} else {
|
||||||
$res['php'] .= "->$method('$property', [], 'property', true)";
|
$type = ViewLayerData::TYPE_PROPERTY;
|
||||||
|
$res['php'] .= "->$method('$property', [], '$type', true)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,13 +6,9 @@ use ArrayIterator;
|
|||||||
use Countable;
|
use Countable;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Iterator;
|
use Iterator;
|
||||||
|
use LogicException;
|
||||||
use SilverStripe\Core\ClassInfo;
|
use SilverStripe\Core\ClassInfo;
|
||||||
use SilverStripe\Model\List\ArrayList;
|
use SilverStripe\Core\Injector\Injector;
|
||||||
use SilverStripe\Model\ModelData;
|
|
||||||
use SilverStripe\ORM\FieldType\DBBoolean;
|
|
||||||
use SilverStripe\ORM\FieldType\DBText;
|
|
||||||
use SilverStripe\ORM\FieldType\DBFloat;
|
|
||||||
use SilverStripe\ORM\FieldType\DBInt;
|
|
||||||
use SilverStripe\ORM\FieldType\DBField;
|
use SilverStripe\ORM\FieldType\DBField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -226,26 +222,6 @@ class SSViewer_Scope
|
|||||||
) = end($this->itemStack);
|
) = end($this->itemStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getObj(string $name, array $arguments = [], string $type = '', bool $cache = false, ?string $cacheName = null): ?ViewLayerData
|
|
||||||
{
|
|
||||||
$overlay = $this->getOverlay($name, $arguments);
|
|
||||||
if ($overlay !== null) {
|
|
||||||
return $overlay;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @TODO caching
|
|
||||||
$on = $this->getCurrentItem();
|
|
||||||
if ($on && isset($on->$name)) {
|
|
||||||
if ($type === 'method') {
|
|
||||||
return $on->$name(...$arguments);
|
|
||||||
}
|
|
||||||
// property
|
|
||||||
return $on->$name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->getUnderlay($name, $arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set scope to an intermediate value, which will be used for getting output later on.
|
* Set scope to an intermediate value, which will be used for getting output later on.
|
||||||
*/
|
*/
|
||||||
@ -444,20 +420,61 @@ class SSViewer_Scope
|
|||||||
return $retval === null ? '' : $retval->__toString();
|
return $retval === null ? '' : $retval->__toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value to pass as an argument to a method.
|
||||||
|
*/
|
||||||
|
public function getValueAsArgument(string $name, array $arguments = [], string $type = '', bool $cache = false, ?string $cacheName = null): mixed
|
||||||
|
{
|
||||||
|
$retval = null;
|
||||||
|
|
||||||
|
if ($this->hasOverlay($name)) {
|
||||||
|
$retval = $this->getOverlay($name, $arguments, true);
|
||||||
|
} else {
|
||||||
|
// @TODO caching
|
||||||
|
$on = $this->getCurrentItem();
|
||||||
|
if ($on && isset($on->$name)) {
|
||||||
|
$retval = $on->getRawDataValue($name, $type, $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($retval === null) {
|
||||||
|
$retval = $this->getUnderlay($name, $arguments, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($retval instanceof DBField) {
|
||||||
|
$retval = $retval->getValue(); // Workaround because we're still calling obj in ViewLayerData
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->resetLocalScope();
|
||||||
|
return $retval;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current item in scope has a value for the named field.
|
* Check if the current item in scope has a value for the named field.
|
||||||
*/
|
*/
|
||||||
public function hasValue(string $name, array $arguments = [], string $type = '', bool $cache = false, ?string $cacheName = null): bool
|
public function hasValue(string $name, array $arguments = [], string $type = '', bool $cache = false, ?string $cacheName = null): bool
|
||||||
{
|
{
|
||||||
// return $this->getCurrentItem()->hasValue($name, $arguments); // eww
|
// @TODO: look for ways to remove the need to call hasValue (e.g. using isset($this->getCurrentItem()->$name) and an equivalent for over/underlays)
|
||||||
// Kinda need a combination of the above, plus checking over-and-underlays.
|
$retval = null;
|
||||||
$obj = $this->getObj($name, $arguments, $type, $cache, $cacheName);
|
$overlay = $this->getOverlay($name, $arguments);
|
||||||
$this->resetLocalScope();
|
if ($overlay && $overlay->hasDataValue()) {
|
||||||
if (!$obj) {
|
$retval = true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
// @TODO: look for ways to remove the need to call this method (e.g. using isset($this->getCurrentItem()->$name) and an equivalent for over/underlays)
|
|
||||||
return $obj->hasValue();
|
if ($retval === null) {
|
||||||
|
$on = $this->getCurrentItem();
|
||||||
|
if ($on) {
|
||||||
|
$retval = $on->hasDataValue($name, $arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$retval) {
|
||||||
|
$underlay = $this->getUnderlay($name, $arguments);
|
||||||
|
$retval = $underlay && $underlay->hasDataValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->resetLocalScope();
|
||||||
|
return $retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -594,21 +611,46 @@ class SSViewer_Scope
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getOverlay(string $property, array $args)
|
private function getObj(string $name, array $arguments = [], string $type = '', bool $cache = false, ?string $cacheName = null): ?ViewLayerData
|
||||||
|
{
|
||||||
|
if ($this->hasOverlay($name)) {
|
||||||
|
return $this->getOverlay($name, $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO caching
|
||||||
|
$on = $this->getCurrentItem();
|
||||||
|
if ($on && isset($on->$name)) {
|
||||||
|
if ($type === ViewLayerData::TYPE_METHOD) {
|
||||||
|
return $on->$name(...$arguments);
|
||||||
|
}
|
||||||
|
// property
|
||||||
|
return $on->$name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getUnderlay($name, $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasOverlay(string $property): bool
|
||||||
|
{
|
||||||
|
$result = $this->processTemplateOverride($property, $this->overlay);
|
||||||
|
return array_key_exists('value', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getOverlay(string $property, array $args, bool $getRaw = false): mixed
|
||||||
{
|
{
|
||||||
$result = $this->processTemplateOverride($property, $this->overlay);
|
$result = $this->processTemplateOverride($property, $this->overlay);
|
||||||
if (array_key_exists('value', $result)) {
|
if (array_key_exists('value', $result)) {
|
||||||
return $this->getInjectedValue($result, $property, $args);
|
return $this->getInjectedValue($result, $property, $args, $getRaw);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getUnderlay(string $property, array $args)
|
private function getUnderlay(string $property, array $args, bool $getRaw = false): mixed
|
||||||
{
|
{
|
||||||
// Check for a presenter-specific override
|
// Check for a presenter-specific override
|
||||||
$result = $this->processTemplateOverride($property, $this->underlay);
|
$result = $this->processTemplateOverride($property, $this->underlay);
|
||||||
if (array_key_exists('value', $result)) {
|
if (array_key_exists('value', $result)) {
|
||||||
return $this->getInjectedValue($result, $property, $args);
|
return $this->getInjectedValue($result, $property, $args, $getRaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then for iterator-specific overrides
|
// Then for iterator-specific overrides
|
||||||
@ -628,7 +670,7 @@ class SSViewer_Scope
|
|||||||
$implementor->iteratorProperties(0, 1);
|
$implementor->iteratorProperties(0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getInjectedValue($source, $property, $args);
|
return $this->getInjectedValue($source, $property, $args, $getRaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// And finally for global overrides
|
// And finally for global overrides
|
||||||
@ -636,15 +678,20 @@ class SSViewer_Scope
|
|||||||
return $this->getInjectedValue(
|
return $this->getInjectedValue(
|
||||||
SSViewer_Scope::$globalProperties[$property],
|
SSViewer_Scope::$globalProperties[$property],
|
||||||
$property,
|
$property,
|
||||||
$args
|
$args,
|
||||||
|
$getRaw
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getInjectedValue(array|TemplateGlobalProvider|TemplateIteratorProvider $source, string $property, array $params)
|
private function getInjectedValue(
|
||||||
{
|
array|TemplateGlobalProvider|TemplateIteratorProvider $source,
|
||||||
|
string $property,
|
||||||
|
array $params,
|
||||||
|
bool $getRaw = false
|
||||||
|
) {
|
||||||
// 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
|
||||||
$value = null;
|
$value = null;
|
||||||
if (isset($source['callable'])) {
|
if (isset($source['callable'])) {
|
||||||
@ -657,6 +704,20 @@ class SSViewer_Scope
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ViewLayerData::create($value);
|
if ($value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateGlobalProviders can provide an explicit service to cast to which works outside of the regular cast flow
|
||||||
|
if (!$getRaw && isset($source['casting'])) {
|
||||||
|
$castObject = Injector::inst()->create($source['casting'], $property);
|
||||||
|
if (!ClassInfo::hasMethod($castObject, 'setValue')) {
|
||||||
|
throw new LogicException('Explicit cast from template global provider must have a setValue method.');
|
||||||
|
}
|
||||||
|
$castObject->setValue($value);
|
||||||
|
$value = $castObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $getRaw ? $value : ViewLayerData::create($value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\View;
|
|||||||
|
|
||||||
use BadMethodCallException;
|
use BadMethodCallException;
|
||||||
use Countable;
|
use Countable;
|
||||||
|
use InvalidArgumentException;
|
||||||
use IteratorAggregate;
|
use IteratorAggregate;
|
||||||
use SilverStripe\Core\ClassInfo;
|
use SilverStripe\Core\ClassInfo;
|
||||||
use SilverStripe\Core\Injector\Injectable;
|
use SilverStripe\Core\Injector\Injectable;
|
||||||
@ -15,10 +16,16 @@ class ViewLayerData implements IteratorAggregate, Stringable, Countable
|
|||||||
{
|
{
|
||||||
use Injectable;
|
use Injectable;
|
||||||
|
|
||||||
private mixed $data;
|
public const TYPE_PROPERTY = 'property';
|
||||||
|
public const TYPE_METHOD = 'method';
|
||||||
|
|
||||||
|
private object $data;
|
||||||
|
|
||||||
public function __construct(mixed $data, mixed $source = null, string $name = '')
|
public function __construct(mixed $data, mixed $source = null, string $name = '')
|
||||||
{
|
{
|
||||||
|
if ($data === null) {
|
||||||
|
throw new InvalidArgumentException('$data must not be null');
|
||||||
|
}
|
||||||
if ($data instanceof ViewLayerData) {
|
if ($data instanceof ViewLayerData) {
|
||||||
$data = $data->data;
|
$data = $data->data;
|
||||||
} else {
|
} else {
|
||||||
@ -27,17 +34,25 @@ class ViewLayerData implements IteratorAggregate, Stringable, Countable
|
|||||||
$this->data = $data;
|
$this->data = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needed so we can rewind in SSViewer_Scope::next() after getting itemIteratorTotal without throwing an exception.
|
||||||
|
* @TODO see if we can remove the need for this
|
||||||
|
*/
|
||||||
public function count(): int
|
public function count(): int
|
||||||
{
|
{
|
||||||
// This will throw an exception if the data item isn't Countable,
|
if (is_countable($this->data)) {
|
||||||
// but we have to have this so we can rewind in SSViewer_Scope::next()
|
return count($this->data);
|
||||||
// after getting itemIteratorTotal without throwing an exception.
|
}
|
||||||
// This could be avoided if we just return $this->data->getIterator() in
|
if (ClassInfo::hasMethod($this->data, 'getIterator')) {
|
||||||
// the getIterator() method (or omit that method entirely and let it be
|
return count($this->data->getIterator());
|
||||||
// handled with __call()) but then any time you loop you're using objects
|
}
|
||||||
// that aren't ViewLayerData objects and therefore won't be cast or
|
if (ClassInfo::hasMethod($this->data, 'count')) {
|
||||||
// escaped correctly by Twig.
|
return $this->data->count();
|
||||||
return count($this->data);
|
}
|
||||||
|
if (isset($this->data->count)) {
|
||||||
|
return $this->data->count;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIterator(): Traversable
|
public function getIterator(): Traversable
|
||||||
@ -51,26 +66,12 @@ class ViewLayerData implements IteratorAggregate, Stringable, Countable
|
|||||||
if (!is_iterable($iterator)) {
|
if (!is_iterable($iterator)) {
|
||||||
$iterator = $this->data->getIterator();
|
$iterator = $this->data->getIterator();
|
||||||
}
|
}
|
||||||
|
$source = $this->data instanceof ModelData ? $this->data : null;
|
||||||
foreach ($iterator as $item) {
|
foreach ($iterator as $item) {
|
||||||
yield ViewLayerData::create($item, $this->data);
|
yield $item === null ? null : ViewLayerData::create($item, $source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporary fix - need to remove later. Can't rely on this 'cause other engines won't be calling it
|
|
||||||
public function hasValue(?string $name = null, array $arguments = []): bool
|
|
||||||
{
|
|
||||||
if ($name) {
|
|
||||||
if ($this->data instanceof ModelData) {
|
|
||||||
return $this->data->hasValue($name, $arguments);
|
|
||||||
}
|
|
||||||
return isset($this->$name);
|
|
||||||
}
|
|
||||||
if ($this->data instanceof ModelData) {
|
|
||||||
return $this->data->exists();
|
|
||||||
}
|
|
||||||
return (bool) $this->data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __isset(string $name): bool
|
public function __isset(string $name): bool
|
||||||
{
|
{
|
||||||
// Might be worth reintroducing the way ss template engine checks if lists/countables "exist" here,
|
// Might be worth reintroducing the way ss template engine checks if lists/countables "exist" here,
|
||||||
@ -86,32 +87,61 @@ class ViewLayerData implements IteratorAggregate, Stringable, Countable
|
|||||||
|
|
||||||
public function __get(string $name): ?ViewLayerData
|
public function __get(string $name): ?ViewLayerData
|
||||||
{
|
{
|
||||||
if ($this->data instanceof ModelData) { // temporary while I move things across.
|
$value = $this->getRawDataValue($name, ViewLayerData::TYPE_PROPERTY);
|
||||||
$value = $this->data->obj($name);
|
$source = $this->data instanceof ModelData ? $this->data : null;
|
||||||
} else {
|
return ViewLayerData::create($value, $source, $name); // @TODO maybe not return this here, but wrap it again in the next layer? This may not play nicely with twig when passing values into args?
|
||||||
$value = isset($this->data->$name) ? $this->data->$name : null;
|
|
||||||
}
|
|
||||||
if ($value === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ViewLayerData::create($value, $this->data, $name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __call(string $name, array $arguments = []): ?ViewLayerData
|
public function __call(string $name, array $arguments = []): ?ViewLayerData
|
||||||
{
|
{
|
||||||
if ($this->data instanceof ModelData) { // temporary while I move things across.
|
$value = $this->getRawDataValue($name, ViewLayerData::TYPE_METHOD, $arguments);
|
||||||
$value = $this->data->obj($name, $arguments);
|
$source = $this->data instanceof ModelData ? $this->data : null;
|
||||||
} else {
|
return ViewLayerData::create($value, $source, $name); // @TODO maybe not return this here, but wrap it again in the next layer? This may not play nicely with twig when passing values into args?
|
||||||
$value = ClassInfo::hasMethod($this->data, $name) ? $this->data->$name(...$arguments) : null;
|
|
||||||
}
|
|
||||||
if ($value === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ViewLayerData::create($value, $this->data, $name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
|
if ($this->data instanceof ModelData) {
|
||||||
|
return $this->data->forTemplate();
|
||||||
|
}
|
||||||
return (string) $this->data;
|
return (string) $this->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @TODO We need this right now for the ss template engine, but need to check if
|
||||||
|
// we can rely on it, since twig won't be calling this at all
|
||||||
|
public function hasDataValue(?string $name = null, array $arguments = []): bool
|
||||||
|
{
|
||||||
|
if ($name) {
|
||||||
|
if ($this->data instanceof ModelData) {
|
||||||
|
return $this->data->hasValue($name, $arguments);
|
||||||
|
}
|
||||||
|
return isset($this->$name);
|
||||||
|
}
|
||||||
|
if ($this->data instanceof ModelData) {
|
||||||
|
return $this->data->exists();
|
||||||
|
}
|
||||||
|
return (bool) $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO We need this right now for the ss template engine, but need to check if
|
||||||
|
// we can rely on it, since twig won't be calling this at all
|
||||||
|
public function getRawDataValue(string $name, string $type, array $arguments = []): mixed
|
||||||
|
{
|
||||||
|
if ($type === ViewLayerData::TYPE_PROPERTY) {
|
||||||
|
if ($this->data instanceof ModelData) { // temporary while I move things across.
|
||||||
|
return $this->data->obj($name);
|
||||||
|
}
|
||||||
|
return isset($this->data->$name) ? $this->data->$name : null;
|
||||||
|
}
|
||||||
|
if ($type === ViewLayerData::TYPE_METHOD) {
|
||||||
|
// If it's not a property it's a method
|
||||||
|
if ($this->data instanceof ModelData) { // temporary while I move things across.
|
||||||
|
return $this->data->obj($name, $arguments);
|
||||||
|
} elseif (ClassInfo::hasMethod($this->data, $name) || method_exists($this->data, '__call')) {
|
||||||
|
return $this->data->$name(...$arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException('$type must be one of the TYPE_* constant values');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ use Symfony\Component\Console\Output\BufferedOutput;
|
|||||||
|
|
||||||
class NavigateCommandTest extends SapphireTest
|
class NavigateCommandTest extends SapphireTest
|
||||||
{
|
{
|
||||||
protected $usesDatabase = false;
|
protected $usesDatabase = true;
|
||||||
|
|
||||||
public static function provideExecute(): array
|
public static function provideExecute(): array
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,8 @@ class DateFieldDisabledTest extends SapphireTest
|
|||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
i18n::set_locale('en_NZ');
|
// Set to an explicit locale so project-level locale swapping doesn't affect tests
|
||||||
|
i18n::set_locale('en_US');
|
||||||
DBDatetime::set_mock_now('2011-02-01 8:34:00');
|
DBDatetime::set_mock_now('2011-02-01 8:34:00');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ class DateFieldDisabledTest extends SapphireTest
|
|||||||
$actual = DateField_Disabled::create('Test')
|
$actual = DateField_Disabled::create('Test')
|
||||||
->setValue('2011-02-01')
|
->setValue('2011-02-01')
|
||||||
->Field();
|
->Field();
|
||||||
$expected = '<span class="readonly" id="Test">1/02/2011 (today)</span>';
|
$expected = '<span class="readonly" id="Test">Feb 1, 2011 (today)</span>';
|
||||||
$this->assertEquals($expected, $actual);
|
$this->assertEquals($expected, $actual);
|
||||||
|
|
||||||
// Test today's date with time
|
// Test today's date with time
|
||||||
@ -38,14 +39,14 @@ class DateFieldDisabledTest extends SapphireTest
|
|||||||
$actual = DateField_Disabled::create('Test')
|
$actual = DateField_Disabled::create('Test')
|
||||||
->setValue('2011-01-27')
|
->setValue('2011-01-27')
|
||||||
->Field();
|
->Field();
|
||||||
$expected = '<span class="readonly" id="Test">27/01/2011, 5 days ago</span>';
|
$expected = '<span class="readonly" id="Test">Jan 27, 2011, 5 days ago</span>';
|
||||||
$this->assertEquals($expected, $actual);
|
$this->assertEquals($expected, $actual);
|
||||||
|
|
||||||
// Test future
|
// Test future
|
||||||
$actual = DateField_Disabled::create('Test')
|
$actual = DateField_Disabled::create('Test')
|
||||||
->setValue('2011-02-06')
|
->setValue('2011-02-06')
|
||||||
->Field();
|
->Field();
|
||||||
$expected = '<span class="readonly" id="Test">6/02/2011, in 5 days</span>';
|
$expected = '<span class="readonly" id="Test">Feb 6, 2011, in 5 days</span>';
|
||||||
$this->assertEquals($expected, $actual);
|
$this->assertEquals($expected, $actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,8 @@ class DatetimeFieldTest extends SapphireTest
|
|||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
i18n::set_locale('en_NZ');
|
// Set to an explicit locale so project-level locale swapping doesn't affect tests
|
||||||
|
i18n::set_locale('en_US');
|
||||||
// Fix now to prevent race conditions
|
// Fix now to prevent race conditions
|
||||||
DBDatetime::set_mock_now('2010-04-04');
|
DBDatetime::set_mock_now('2010-04-04');
|
||||||
$this->timezone = date_default_timezone_get();
|
$this->timezone = date_default_timezone_get();
|
||||||
@ -141,14 +142,14 @@ class DatetimeFieldTest extends SapphireTest
|
|||||||
|
|
||||||
$datetimeField
|
$datetimeField
|
||||||
->setHTML5(false)
|
->setHTML5(false)
|
||||||
->setLocale('en_NZ');
|
->setLocale('de_DE');
|
||||||
|
|
||||||
$datetimeField->setSubmittedValue('29/03/2003 11:00:00 pm');
|
$datetimeField->setSubmittedValue('29/03/2003 23:00:00');
|
||||||
$this->assertEquals($datetimeField->dataValue(), '2003-03-29 23:00:00');
|
$this->assertEquals('2003-03-29 23:00:00', $datetimeField->dataValue());
|
||||||
|
|
||||||
// Some localisation packages exclude the ',' in default medium format
|
// Some localisation packages exclude the ',' in default medium format
|
||||||
$this->assertMatchesRegularExpression(
|
$this->assertMatchesRegularExpression(
|
||||||
'#29/03/2003(,)? 11:00:00 (PM|pm)#',
|
'#29.03.2003(,)? 23:00:00#',
|
||||||
$datetimeField->Value(),
|
$datetimeField->Value(),
|
||||||
'User value is formatted, and in user timezone'
|
'User value is formatted, and in user timezone'
|
||||||
);
|
);
|
||||||
|
@ -21,7 +21,8 @@ class DBDateTest extends SapphireTest
|
|||||||
$this->oldError = error_reporting();
|
$this->oldError = error_reporting();
|
||||||
// Validate setup
|
// Validate setup
|
||||||
assert(date_default_timezone_get() === 'UTC');
|
assert(date_default_timezone_get() === 'UTC');
|
||||||
i18n::set_locale('en_NZ');
|
// Set to an explicit locale so project-level locale swapping doesn't affect tests
|
||||||
|
i18n::set_locale('en_US');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
@ -49,42 +50,42 @@ class DBDateTest extends SapphireTest
|
|||||||
public function testNiceDate()
|
public function testNiceDate()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'31/03/2008',
|
'Mar 31, 2008',
|
||||||
DBField::create_field('Date', 1206968400)->Nice(),
|
DBField::create_field('Date', 1206968400)->Nice(),
|
||||||
"Date->Nice() works with timestamp integers"
|
"Date->Nice() works with timestamp integers"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'30/03/2008',
|
'Mar 30, 2008',
|
||||||
DBField::create_field('Date', 1206882000)->Nice(),
|
DBField::create_field('Date', 1206882000)->Nice(),
|
||||||
"Date->Nice() works with timestamp integers"
|
"Date->Nice() works with timestamp integers"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'31/03/2008',
|
'Mar 31, 2008',
|
||||||
DBField::create_field('Date', '1206968400')->Nice(),
|
DBField::create_field('Date', '1206968400')->Nice(),
|
||||||
"Date->Nice() works with timestamp strings"
|
"Date->Nice() works with timestamp strings"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'30/03/2008',
|
'Mar 30, 2008',
|
||||||
DBField::create_field('Date', '1206882000')->Nice(),
|
DBField::create_field('Date', '1206882000')->Nice(),
|
||||||
"Date->Nice() works with timestamp strings"
|
"Date->Nice() works with timestamp strings"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'4/03/2003',
|
'Mar 4, 2003',
|
||||||
DBField::create_field('Date', '4.3.2003')->Nice(),
|
DBField::create_field('Date', '4.3.2003')->Nice(),
|
||||||
"Date->Nice() works with D.M.YYYY format"
|
"Date->Nice() works with D.M.YYYY format"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'4/03/2003',
|
'Mar 4, 2003',
|
||||||
DBField::create_field('Date', '04.03.2003')->Nice(),
|
DBField::create_field('Date', '04.03.2003')->Nice(),
|
||||||
"Date->Nice() works with DD.MM.YYYY format"
|
"Date->Nice() works with DD.MM.YYYY format"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'4/03/2003',
|
'Mar 4, 2003',
|
||||||
DBField::create_field('Date', '2003-3-4')->Nice(),
|
DBField::create_field('Date', '2003-3-4')->Nice(),
|
||||||
"Date->Nice() works with YYYY-M-D format"
|
"Date->Nice() works with YYYY-M-D format"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'4/03/2003',
|
'Mar 4, 2003',
|
||||||
DBField::create_field('Date', '2003-03-04')->Nice(),
|
DBField::create_field('Date', '2003-03-04')->Nice(),
|
||||||
"Date->Nice() works with YYYY-MM-DD format"
|
"Date->Nice() works with YYYY-MM-DD format"
|
||||||
);
|
);
|
||||||
@ -108,7 +109,7 @@ class DBDateTest extends SapphireTest
|
|||||||
{
|
{
|
||||||
// iso8601 expects year first, but support year last
|
// iso8601 expects year first, but support year last
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'4/03/2003',
|
'Mar 4, 2003',
|
||||||
DBField::create_field('Date', '04-03-2003')->Nice(),
|
DBField::create_field('Date', '04-03-2003')->Nice(),
|
||||||
"Date->Nice() works with DD-MM-YYYY format"
|
"Date->Nice() works with DD-MM-YYYY format"
|
||||||
);
|
);
|
||||||
@ -153,32 +154,32 @@ class DBDateTest extends SapphireTest
|
|||||||
public function testLongDate()
|
public function testLongDate()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'31 March 2008',
|
'March 31, 2008',
|
||||||
DBField::create_field('Date', 1206968400)->Long(),
|
DBField::create_field('Date', 1206968400)->Long(),
|
||||||
"Date->Long() works with numeric timestamp"
|
"Date->Long() works with numeric timestamp"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'31 March 2008',
|
'March 31, 2008',
|
||||||
DBField::create_field('Date', '1206968400')->Long(),
|
DBField::create_field('Date', '1206968400')->Long(),
|
||||||
"Date->Long() works with string timestamp"
|
"Date->Long() works with string timestamp"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'30 March 2008',
|
'March 30, 2008',
|
||||||
DBField::create_field('Date', 1206882000)->Long(),
|
DBField::create_field('Date', 1206882000)->Long(),
|
||||||
"Date->Long() works with numeric timestamp"
|
"Date->Long() works with numeric timestamp"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'30 March 2008',
|
'March 30, 2008',
|
||||||
DBField::create_field('Date', '1206882000')->Long(),
|
DBField::create_field('Date', '1206882000')->Long(),
|
||||||
"Date->Long() works with numeric timestamp"
|
"Date->Long() works with numeric timestamp"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'3 April 2003',
|
'April 3, 2003',
|
||||||
DBField::create_field('Date', '2003-4-3')->Long(),
|
DBField::create_field('Date', '2003-4-3')->Long(),
|
||||||
"Date->Long() works with YYYY-M-D"
|
"Date->Long() works with YYYY-M-D"
|
||||||
);
|
);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'3 April 2003',
|
'April 3, 2003',
|
||||||
DBField::create_field('Date', '3.4.2003')->Long(),
|
DBField::create_field('Date', '3.4.2003')->Long(),
|
||||||
"Date->Long() works with D.M.YYYY"
|
"Date->Long() works with D.M.YYYY"
|
||||||
);
|
);
|
||||||
@ -187,7 +188,7 @@ class DBDateTest extends SapphireTest
|
|||||||
public function testFull()
|
public function testFull()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'Monday, 31 March 2008',
|
'Monday, March 31, 2008',
|
||||||
DBField::create_field('Date', 1206968400)->Full(),
|
DBField::create_field('Date', 1206968400)->Full(),
|
||||||
"Date->Full() works with timestamp integers"
|
"Date->Full() works with timestamp integers"
|
||||||
);
|
);
|
||||||
|
@ -15,7 +15,8 @@ class DBDatetimeTest extends SapphireTest
|
|||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
i18n::set_locale('en_NZ');
|
// Set to an explicit locale so project-level locale swapping doesn't affect tests
|
||||||
|
i18n::set_locale('en_US');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNowWithSystemDate()
|
public function testNowWithSystemDate()
|
||||||
@ -127,23 +128,23 @@ class DBDatetimeTest extends SapphireTest
|
|||||||
$date = DBDatetime::create_field('Datetime', '2001-12-11 22:10:59');
|
$date = DBDatetime::create_field('Datetime', '2001-12-11 22:10:59');
|
||||||
|
|
||||||
// note: Some localisation packages exclude the ',' in default medium format
|
// note: Some localisation packages exclude the ',' in default medium format
|
||||||
i18n::set_locale('en_NZ');
|
i18n::set_locale('de_DE');
|
||||||
$this->assertMatchesRegularExpression('#11/12/2001(,)? 10:10 PM#i', $date->Nice());
|
$this->assertMatchesRegularExpression('#11.12.2001(,)? 22:10#i', $date->Nice());
|
||||||
|
|
||||||
i18n::set_locale('en_US');
|
i18n::set_locale('en_US');
|
||||||
$this->assertMatchesRegularExpression('#Dec 11(,)? 2001(,)? 10:10 PM#i', $date->Nice());
|
$this->assertMatchesRegularExpression('#Dec 11(,)? 2001(,)? 10:10\hPM#iu', $date->Nice());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDate()
|
public function testDate()
|
||||||
{
|
{
|
||||||
$date = DBDatetime::create_field('Datetime', '2001-12-31 22:10:59');
|
$date = DBDatetime::create_field('Datetime', '2001-12-31 22:10:59');
|
||||||
$this->assertEquals('31/12/2001', $date->Date());
|
$this->assertEquals('Dec 31, 2001', $date->Date());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTime()
|
public function testTime()
|
||||||
{
|
{
|
||||||
$date = DBDatetime::create_field('Datetime', '2001-12-31 22:10:59');
|
$date = DBDatetime::create_field('Datetime', '2001-12-31 22:10:59');
|
||||||
$this->assertMatchesRegularExpression('#10:10:59 PM#i', $date->Time());
|
$this->assertMatchesRegularExpression('#10:10:59\hPM#iu', $date->Time());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTime24()
|
public function testTime24()
|
||||||
|
@ -49,12 +49,12 @@ class DBTimeTest extends SapphireTest
|
|||||||
public function testNice()
|
public function testNice()
|
||||||
{
|
{
|
||||||
$time = DBTime::create_field('Time', '17:15:55');
|
$time = DBTime::create_field('Time', '17:15:55');
|
||||||
$this->assertMatchesRegularExpression('#5:15:55 PM#i', $time->Nice());
|
$this->assertMatchesRegularExpression('#5:15:55\hPM#iu', $time->Nice());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testShort()
|
public function testShort()
|
||||||
{
|
{
|
||||||
$time = DBTime::create_field('Time', '17:15:55');
|
$time = DBTime::create_field('Time', '17:15:55');
|
||||||
$this->assertMatchesRegularExpression('#5:15 PM#i', $time->Short());
|
$this->assertMatchesRegularExpression('#5:15\hPM#iu', $time->Short());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,20 +197,6 @@ class EndsWithFilterTest extends SapphireTest
|
|||||||
'modifiers' => [],
|
'modifiers' => [],
|
||||||
'matches' => false,
|
'matches' => false,
|
||||||
],
|
],
|
||||||
// These will both evaluate to true because the __toString() method just returns the class name.
|
|
||||||
// We're testing this scenario because ArrayList might contain arbitrary values
|
|
||||||
[
|
|
||||||
'filterValue' => new ArrayData(['SomeField' => 'some value']),
|
|
||||||
'matchValue' => new ArrayData(['SomeField' => 'some value']),
|
|
||||||
'modifiers' => [],
|
|
||||||
'matches' => true,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'filterValue' => new ArrayData(['SomeField' => 'SoMe VaLuE']),
|
|
||||||
'matchValue' => new ArrayData(['SomeField' => 'some value']),
|
|
||||||
'modifiers' => [],
|
|
||||||
'matches' => true,
|
|
||||||
],
|
|
||||||
// case insensitive
|
// case insensitive
|
||||||
[
|
[
|
||||||
'filterValue' => 'somevalue',
|
'filterValue' => 'somevalue',
|
||||||
|
@ -197,20 +197,6 @@ class PartialMatchFilterTest extends SapphireTest
|
|||||||
'modifiers' => [],
|
'modifiers' => [],
|
||||||
'matches' => false,
|
'matches' => false,
|
||||||
],
|
],
|
||||||
// These will both evaluate to true because the __toString() method just returns the class name.
|
|
||||||
// We're testing this scenario because ArrayList might contain arbitrary values
|
|
||||||
[
|
|
||||||
'filterValue' => new ArrayData(['SomeField' => 'some value']),
|
|
||||||
'matchValue' => new ArrayData(['SomeField' => 'some value']),
|
|
||||||
'modifiers' => [],
|
|
||||||
'matches' => true,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'filterValue' => new ArrayData(['SomeField' => 'SoMe VaLuE']),
|
|
||||||
'matchValue' => new ArrayData(['SomeField' => 'some value']),
|
|
||||||
'modifiers' => [],
|
|
||||||
'matches' => true,
|
|
||||||
],
|
|
||||||
// case insensitive
|
// case insensitive
|
||||||
[
|
[
|
||||||
'filterValue' => 'somevalue',
|
'filterValue' => 'somevalue',
|
||||||
|
@ -197,20 +197,6 @@ class StartsWithFilterTest extends SapphireTest
|
|||||||
'modifiers' => [],
|
'modifiers' => [],
|
||||||
'matches' => false,
|
'matches' => false,
|
||||||
],
|
],
|
||||||
// These will both evaluate to true because the __toString() method just returns the class name.
|
|
||||||
// We're testing this scenario because ArrayList might contain arbitrary values
|
|
||||||
[
|
|
||||||
'filterValue' => new ArrayData(['SomeField' => 'some value']),
|
|
||||||
'matchValue' => new ArrayData(['SomeField' => 'some value']),
|
|
||||||
'modifiers' => [],
|
|
||||||
'matches' => true,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'filterValue' => new ArrayData(['SomeField' => 'SoMe VaLuE']),
|
|
||||||
'matchValue' => new ArrayData(['SomeField' => 'some value']),
|
|
||||||
'modifiers' => [],
|
|
||||||
'matches' => true,
|
|
||||||
],
|
|
||||||
// case insensitive
|
// case insensitive
|
||||||
[
|
[
|
||||||
'filterValue' => 'somevalue',
|
'filterValue' => 'somevalue',
|
||||||
|
@ -360,8 +360,9 @@ SS;
|
|||||||
'z<div></div>z',
|
'z<div></div>z',
|
||||||
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLFragment)')
|
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLFragment)')
|
||||||
);
|
);
|
||||||
|
// Don't escape value when passing into a method call
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
'z<div></div>z',
|
'z<div></div>z',
|
||||||
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLEscaped)')
|
$this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLEscaped)')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1118,57 +1119,59 @@ after'
|
|||||||
public function testIncludeWithArguments()
|
public function testIncludeWithArguments()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->render('<% include SSViewerTestIncludeWithArguments %>'),
|
'<p>[out:Arg1]</p><p>[out:Arg2]</p><p>[out:Arg2.Count]</p>',
|
||||||
'<p>[out:Arg1]</p><p>[out:Arg2]</p><p>[out:Arg2.Count]</p>'
|
$this->render('<% include SSViewerTestIncludeWithArguments %>')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'),
|
'<p>A</p><p>[out:Arg2]</p><p>[out:Arg2.Count]</p>',
|
||||||
'<p>A</p><p>[out:Arg2]</p><p>[out:Arg2.Count]</p>'
|
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'),
|
'<p>A</p><p>B</p><p></p>',
|
||||||
'<p>A</p><p>B</p><p></p>'
|
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'),
|
'<p>A Bare String</p><p>B Bare String</p><p></p>',
|
||||||
'<p>A Bare String</p><p>B Bare String</p><p></p>'
|
$this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
|
'<p>A</p><p>Bar</p><p></p>',
|
||||||
$this->render(
|
$this->render(
|
||||||
'<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>',
|
'<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>',
|
||||||
new ArrayData(['B' => 'Bar'])
|
new ArrayData(['B' => 'Bar'])
|
||||||
),
|
)
|
||||||
'<p>A</p><p>Bar</p><p></p>'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
|
'<p>A</p><p>Bar</p><p></p>',
|
||||||
$this->render(
|
$this->render(
|
||||||
'<% include SSViewerTestIncludeWithArguments Arg1="A" %>',
|
'<% include SSViewerTestIncludeWithArguments Arg1="A" %>',
|
||||||
new ArrayData(['Arg1' => 'Foo', 'Arg2' => 'Bar'])
|
new ArrayData(['Arg1' => 'Foo', 'Arg2' => 'Bar'])
|
||||||
),
|
)
|
||||||
'<p>A</p><p>Bar</p><p></p>'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->render('<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=0 %>'),
|
'<p>A</p><p>0</p><p></p>',
|
||||||
'<p>A</p><p>0</p><p></p>'
|
$this->render('<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=0 %>')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->render('<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=false %>'),
|
'<p>A</p><p></p><p></p>',
|
||||||
'<p>A</p><p></p><p></p>'
|
$this->render('<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=false %>')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->render('<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=null %>'),
|
'<p>A</p><p></p><p></p>',
|
||||||
'<p>A</p><p></p><p></p>'
|
// Note Arg2 is explicitly overridden with null
|
||||||
|
$this->render('<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=null %>')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
|
'SomeArg - Foo - Bar - SomeArg',
|
||||||
$this->render(
|
$this->render(
|
||||||
'<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>',
|
'<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>',
|
||||||
new ArrayData(
|
new ArrayData(
|
||||||
@ -1179,19 +1182,19 @@ after'
|
|||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
'SomeArg - Foo - Bar - SomeArg'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
|
'A - B - A',
|
||||||
$this->render(
|
$this->render(
|
||||||
'<% include SSViewerTestIncludeScopeInheritanceWithArgsInWith Title="A" %>',
|
'<% include SSViewerTestIncludeScopeInheritanceWithArgsInWith Title="A" %>',
|
||||||
new ArrayData(['Item' => new ArrayData(['Title' =>'B'])])
|
new ArrayData(['Item' => new ArrayData(['Title' =>'B'])])
|
||||||
),
|
)
|
||||||
'A - B - A'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
|
'A - B - C - B - A',
|
||||||
$this->render(
|
$this->render(
|
||||||
'<% include SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith Title="A" %>',
|
'<% include SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith Title="A" %>',
|
||||||
new ArrayData(
|
new ArrayData(
|
||||||
@ -1202,11 +1205,11 @@ after'
|
|||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
'A - B - C - B - A'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
|
'A - A - A',
|
||||||
$this->render(
|
$this->render(
|
||||||
'<% include SSViewerTestIncludeScopeInheritanceWithUpAndTop Title="A" %>',
|
'<% include SSViewerTestIncludeScopeInheritanceWithUpAndTop Title="A" %>',
|
||||||
new ArrayData(
|
new ArrayData(
|
||||||
@ -1217,8 +1220,7 @@ after'
|
|||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
'A - A - A'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$data = new ArrayData(
|
$data = new ArrayData(
|
||||||
@ -2202,7 +2204,66 @@ EOC;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCallsWithArguments()
|
public static function provideCallsWithArguments(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'template' => '$Level.output(1)',
|
||||||
|
'expected' => '1-1',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '$Nest.Level.output($Set.First.Number)',
|
||||||
|
'expected' => '2-1',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '<% with $Set %>$Up.Level.output($First.Number)<% end_with %>',
|
||||||
|
'expected' => '1-1',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>',
|
||||||
|
'expected' => '2-1',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>',
|
||||||
|
'expected' => '2-12-22-32-42-5',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '<% loop $Set %>$Top.Level.output($Number)<% end_loop %>',
|
||||||
|
'expected' => '1-11-21-31-41-5',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>',
|
||||||
|
'expected' => '2-1',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>',
|
||||||
|
'expected' => '1-5',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>',
|
||||||
|
'expected' => '5-hi',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>',
|
||||||
|
'expected' => '!0',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '<% with $Nest %>
|
||||||
|
<% with $Level.forWith($Up.Set.First.Number) %>$output("hi")<% end_with %>
|
||||||
|
<% end_with %>',
|
||||||
|
'expected' => '1-hi',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'template' => '<% with $Nest %>
|
||||||
|
<% loop $Level.forLoop($Top.Set.Last.Number) %>$Number<% end_loop %>
|
||||||
|
<% end_with %>',
|
||||||
|
'expected' => '!0!1!2!3!4',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('provideCallsWithArguments')]
|
||||||
|
public function testCallsWithArguments(string $template, string $expected): void
|
||||||
{
|
{
|
||||||
$data = new ArrayData(
|
$data = new ArrayData(
|
||||||
[
|
[
|
||||||
@ -2222,28 +2283,7 @@ EOC;
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$tests = [
|
$this->assertEquals($expected, trim($this->render($template, $data) ?? ''));
|
||||||
'$Level.output(1)' => '1-1',
|
|
||||||
'$Nest.Level.output($Set.First.Number)' => '2-1',
|
|
||||||
'<% with $Set %>$Up.Level.output($First.Number)<% end_with %>' => '1-1',
|
|
||||||
'<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>' => '2-1',
|
|
||||||
'<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>' => '2-12-22-32-42-5',
|
|
||||||
'<% loop $Set %>$Top.Level.output($Number)<% end_loop %>' => '1-11-21-31-41-5',
|
|
||||||
'<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>' => '2-1',
|
|
||||||
'<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>' => '1-5',
|
|
||||||
'<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>' => '5-hi',
|
|
||||||
'<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>' => '!0',
|
|
||||||
'<% with $Nest %>
|
|
||||||
<% with $Level.forWith($Up.Set.First.Number) %>$output("hi")<% end_with %>
|
|
||||||
<% end_with %>' => '1-hi',
|
|
||||||
'<% with $Nest %>
|
|
||||||
<% loop $Level.forLoop($Top.Set.Last.Number) %>$Number<% end_loop %>
|
|
||||||
<% end_with %>' => '!0!1!2!3!4',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($tests as $template => $expected) {
|
|
||||||
$this->assertEquals($expected, trim($this->render($template, $data) ?? ''));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRepeatedCallsAreCached()
|
public function testRepeatedCallsAreCached()
|
||||||
|
@ -2,23 +2,78 @@
|
|||||||
|
|
||||||
namespace SilverStripe\View\Tests\SSViewerTest;
|
namespace SilverStripe\View\Tests\SSViewerTest;
|
||||||
|
|
||||||
use SilverStripe\Model\List\ArrayList;
|
use ReflectionClass;
|
||||||
use SilverStripe\Model\ModelData;
|
use SilverStripe\Dev\TestOnly;
|
||||||
|
use SilverStripe\View\SSViewer_Scope;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A test fixture that will echo back the template item
|
* A test fixture that will echo back the template item
|
||||||
*/
|
*/
|
||||||
class TestFixture extends ModelData
|
class TestFixture implements TestOnly, Stringable
|
||||||
{
|
{
|
||||||
protected $name;
|
private ?string $name;
|
||||||
|
|
||||||
public function __construct($name = null)
|
public function __construct($name = null)
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
parent::__construct();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function argedName($fieldName, $arguments)
|
public function __call(string $name, array $arguments = []): static|array|null
|
||||||
|
{
|
||||||
|
return $this->getValue($name, $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get(string $name): static|array|null
|
||||||
|
{
|
||||||
|
return $this->getValue($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __isset(string $name): bool
|
||||||
|
{
|
||||||
|
if (preg_match('/NotSet/i', $name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$reflectionScope = new ReflectionClass(SSViewer_Scope::class);
|
||||||
|
$globalProperties = $reflectionScope->getStaticPropertyValue('globalProperties');
|
||||||
|
if (array_key_exists($name, $globalProperties)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
if (preg_match('/NotSet/i', $this->name ?? '')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (preg_match('/Raw/i', $this->name ?? '')) {
|
||||||
|
return $this->name ?? '';
|
||||||
|
}
|
||||||
|
return '[out:' . $this->name . ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getValue(string $name, array $arguments = []): static|array|null
|
||||||
|
{
|
||||||
|
$childName = $this->argedName($name, $arguments);
|
||||||
|
|
||||||
|
// Special field name Loop### to create a list
|
||||||
|
if (preg_match('/^Loop([0-9]+)$/', $name ?? '', $matches)) {
|
||||||
|
$output = [];
|
||||||
|
for ($i = 0; $i < $matches[1]; $i++) {
|
||||||
|
$output[] = new TestFixture($childName);
|
||||||
|
}
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/NotSet/i', $name)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TestFixture($childName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function argedName(string $fieldName, array $arguments): string
|
||||||
{
|
{
|
||||||
$childName = $this->name ? "$this->name.$fieldName" : $fieldName;
|
$childName = $this->name ? "$this->name.$fieldName" : $fieldName;
|
||||||
if ($arguments) {
|
if ($arguments) {
|
||||||
@ -27,51 +82,4 @@ class TestFixture extends ModelData
|
|||||||
return $childName;
|
return $childName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function obj(
|
|
||||||
string $fieldName,
|
|
||||||
array $arguments = [],
|
|
||||||
bool $cache = false,
|
|
||||||
?string $cacheName = null
|
|
||||||
): ?object {
|
|
||||||
$childName = $this->argedName($fieldName, $arguments);
|
|
||||||
|
|
||||||
// Special field name Loop### to create a list
|
|
||||||
if (preg_match('/^Loop([0-9]+)$/', $fieldName ?? '', $matches)) {
|
|
||||||
$output = new ArrayList();
|
|
||||||
for ($i = 0; $i < $matches[1]; $i++) {
|
|
||||||
$output->push(new TestFixture($childName));
|
|
||||||
}
|
|
||||||
return $output;
|
|
||||||
} else {
|
|
||||||
if (preg_match('/NotSet/i', $fieldName ?? '')) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return new TestFixture($childName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function XML_val(string $fieldName, array $arguments = [], bool $cache = false): string
|
|
||||||
{
|
|
||||||
if (preg_match('/NotSet/i', $fieldName ?? '')) {
|
|
||||||
return '';
|
|
||||||
} else {
|
|
||||||
if (preg_match('/Raw/i', $fieldName ?? '')) {
|
|
||||||
return $fieldName;
|
|
||||||
} else {
|
|
||||||
return '[out:' . $this->argedName($fieldName, $arguments) . ']';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasValue(string $fieldName, array $arguments = [], bool $cache = true): bool
|
|
||||||
{
|
|
||||||
return (bool)$this->XML_val($fieldName, $arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function forTemplate(): string
|
|
||||||
{
|
|
||||||
return '[out:' . $this->name . ']';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user