diff --git a/_config/view.yml b/_config/view.yml
new file mode 100644
index 000000000..fd8293c9f
--- /dev/null
+++ b/_config/view.yml
@@ -0,0 +1,6 @@
+---
+Name: view-config
+---
+SilverStripe\Core\Injector\Injector:
+ SilverStripe\View\TemplateEngine:
+ class: 'SilverStripe\View\SSTemplateEngine'
diff --git a/src/Control/ContentNegotiator.php b/src/Control/ContentNegotiator.php
index aeb3b9f54..4bb9b46c1 100644
--- a/src/Control/ContentNegotiator.php
+++ b/src/Control/ContentNegotiator.php
@@ -225,7 +225,7 @@ class ContentNegotiator
// Fix base tag
$content = preg_replace(
'//',
- '',
+ '',
$content ?? ''
);
diff --git a/src/Control/Controller.php b/src/Control/Controller.php
index d1abe672f..eb23078f5 100644
--- a/src/Control/Controller.php
+++ b/src/Control/Controller.php
@@ -3,11 +3,14 @@
namespace SilverStripe\Control;
use SilverStripe\Core\ClassInfo;
+use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Debug;
+use SilverStripe\Model\ModelData;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
use SilverStripe\View\SSViewer;
+use SilverStripe\View\TemplateEngine;
use SilverStripe\View\TemplateGlobalProvider;
/**
@@ -88,6 +91,8 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
'handleIndex',
];
+ protected ?TemplateEngine $templateEngine = null;
+
public function __construct()
{
parent::__construct();
@@ -401,7 +406,7 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
$templates = array_unique(array_merge($actionTemplates, $classTemplates));
}
- return SSViewer::create($templates);
+ return SSViewer::create($templates, $this->getTemplateEngine());
}
/**
@@ -453,9 +458,10 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
}
$class = static::class;
- while ($class != 'SilverStripe\\Control\\RequestHandler') {
+ $engine = $this->getTemplateEngine();
+ while ($class !== RequestHandler::class) {
$templateName = strtok($class ?? '', '_') . '_' . $action;
- if (SSViewer::hasTemplate($templateName)) {
+ if ($engine->hasTemplate($templateName)) {
return $class;
}
@@ -487,17 +493,25 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
$parentClass = get_parent_class($parentClass ?? '');
}
- return SSViewer::hasTemplate($templates);
+ $engine = $this->getTemplateEngine();
+ return $engine->hasTemplate($templates);
+ }
+
+ public function renderWith($template, ModelData|array $customFields = []): DBHTMLText
+ {
+ // Ensure template engine is used, unless the viewer was already explicitly instantiated
+ if (!($template instanceof SSViewer)) {
+ $template = SSViewer::create($template, $this->getTemplateEngine());
+ }
+ return parent::renderWith($template, $customFields);
}
/**
* Render the current controller with the templates determined by {@link getViewer()}.
*
* @param array $params
- *
- * @return string
*/
- public function render($params = null)
+ public function render($params = null): DBHTMLText
{
$template = $this->getViewer($this->getAction());
@@ -737,4 +751,12 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
'CurrentPage' => 'curr',
];
}
+
+ protected function getTemplateEngine(): TemplateEngine
+ {
+ if (!$this->templateEngine) {
+ $this->templateEngine = Injector::inst()->create(TemplateEngine::class);
+ }
+ return $this->templateEngine;
+ }
}
diff --git a/src/Control/Email/Email.php b/src/Control/Email/Email.php
index aa8bddd5c..8ee604058 100644
--- a/src/Control/Email/Email.php
+++ b/src/Control/Email/Email.php
@@ -46,7 +46,7 @@ class Email extends SymfonyEmail
private static string|array $admin_email = '';
/**
- * The name of the HTML template to render the email with (without *.ss extension)
+ * The name of the HTML template to render the email with
*/
private string $HTMLTemplate = '';
@@ -398,26 +398,21 @@ class Email extends SymfonyEmail
return $this;
}
- public function getHTMLTemplate(): string
+ public function getHTMLTemplate(): string|array
{
if ($this->HTMLTemplate) {
return $this->HTMLTemplate;
}
- return ThemeResourceLoader::inst()->findTemplate(
- SSViewer::get_templates_by_class(static::class, '', Email::class),
- SSViewer::get_themes()
- );
+ return SSViewer::get_templates_by_class(static::class, '', Email::class);
}
/**
- * Set the template to render the email with
+ * Set the template to render the email with.
+ * Do not include a file extension unless you are referencing a full absolute file path.
*/
public function setHTMLTemplate(string $template): static
{
- if (substr($template ?? '', -3) == '.ss') {
- $template = substr($template ?? '', 0, -3);
- }
$this->HTMLTemplate = $template;
return $this;
}
@@ -431,13 +426,11 @@ class Email extends SymfonyEmail
}
/**
- * Set the template to render the plain part with
+ * Set the template to render the plain part with.
+ * Do not include a file extension unless you are referencing a full absolute file path.
*/
public function setPlainTemplate(string $template): static
{
- if (substr($template ?? '', -3) == '.ss') {
- $template = substr($template ?? '', 0, -3);
- }
$this->plainTemplate = $template;
return $this;
}
diff --git a/src/Control/HTTPResponse.php b/src/Control/HTTPResponse.php
index 3cb4a498b..8cb6589aa 100644
--- a/src/Control/HTTPResponse.php
+++ b/src/Control/HTTPResponse.php
@@ -8,11 +8,12 @@ use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\View\Requirements;
+use Stringable;
/**
* Represents a response returned by a controller.
*/
-class HTTPResponse
+class HTTPResponse implements Stringable
{
use Injectable;
@@ -444,10 +445,8 @@ EOT
/**
* The HTTP response represented as a raw string
- *
- * @return string
*/
- public function __toString()
+ public function __toString(): string
{
$headers = [];
foreach ($this->getHeaders() as $header => $values) {
diff --git a/src/Control/RSS/RSSFeed_Entry.php b/src/Control/RSS/RSSFeed_Entry.php
index 1ebaae7e7..66034d711 100644
--- a/src/Control/RSS/RSSFeed_Entry.php
+++ b/src/Control/RSS/RSSFeed_Entry.php
@@ -47,7 +47,7 @@ class RSSFeed_Entry extends ModelData
*/
public function __construct($entry, $titleField, $descriptionField, $authorField)
{
- $this->failover = $entry;
+ $this->setFailover($entry);
$this->titleField = $titleField;
$this->descriptionField = $descriptionField;
$this->authorField = $authorField;
@@ -58,7 +58,7 @@ class RSSFeed_Entry extends ModelData
/**
* Get the description of this entry
*
- * @return DBField Returns the description of the entry.
+ * @return DBField|null Returns the description of the entry.
*/
public function Title()
{
@@ -68,7 +68,7 @@ class RSSFeed_Entry extends ModelData
/**
* Get the description of this entry
*
- * @return DBField Returns the description of the entry.
+ * @return DBField|null Returns the description of the entry.
*/
public function Description()
{
@@ -85,7 +85,7 @@ class RSSFeed_Entry extends ModelData
/**
* Get the author of this entry
*
- * @return DBField Returns the author of the entry.
+ * @return DBField|null Returns the author of the entry.
*/
public function Author()
{
@@ -96,7 +96,7 @@ class RSSFeed_Entry extends ModelData
* Return the safely casted field
*
* @param string $fieldName Name of field
- * @return DBField
+ * @return DBField|null
*/
public function rssField($fieldName)
{
diff --git a/src/Core/Manifest/ModuleResource.php b/src/Core/Manifest/ModuleResource.php
index e89b90ac5..8cb1c7770 100644
--- a/src/Core/Manifest/ModuleResource.php
+++ b/src/Core/Manifest/ModuleResource.php
@@ -5,12 +5,13 @@ namespace SilverStripe\Core\Manifest;
use InvalidArgumentException;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Path;
+use Stringable;
/**
* This object represents a single resource file attached to a module, and can be used
* as a reference to this to be later turned into either a URL or file path.
*/
-class ModuleResource
+class ModuleResource implements Stringable
{
/**
* @var Module
@@ -114,10 +115,8 @@ class ModuleResource
/**
* Get relative path
- *
- * @return string
*/
- public function __toString()
+ public function __toString(): string
{
return $this->getRelativePath();
}
diff --git a/src/Dev/Backtrace.php b/src/Dev/Backtrace.php
index 62d402efc..9aa7b85ad 100644
--- a/src/Dev/Backtrace.php
+++ b/src/Dev/Backtrace.php
@@ -149,11 +149,11 @@ class Backtrace
if ($showArgs && isset($item['args'])) {
$args = [];
foreach ($item['args'] as $arg) {
- if (!is_object($arg) || method_exists($arg, '__toString')) {
+ if (is_object($arg)) {
+ $args[] = get_class($arg);
+ } else {
$sarg = is_array($arg) ? 'Array' : strval($arg);
$args[] = (strlen($sarg ?? '') > $argCharLimit) ? substr($sarg, 0, $argCharLimit) . '...' : $sarg;
- } else {
- $args[] = get_class($arg);
}
}
diff --git a/src/Dev/TestSession.php b/src/Dev/TestSession.php
index 2c1ff07a6..ae61630e4 100644
--- a/src/Dev/TestSession.php
+++ b/src/Dev/TestSession.php
@@ -4,6 +4,7 @@ namespace SilverStripe\Dev;
use Exception;
use InvalidArgumentException;
+use LogicException;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Cookie_Backend;
use SilverStripe\Control\Director;
@@ -214,7 +215,7 @@ class TestSession
$formCrawler = $page->filterXPath("//form[@id='$formID']");
$form = $formCrawler->form();
} catch (InvalidArgumentException $e) {
- user_error("TestSession::submitForm failed to find the form {$formID}");
+ throw new LogicException("TestSession::submitForm failed to find the form '{$formID}'");
}
foreach ($data as $fieldName => $value) {
@@ -235,7 +236,7 @@ class TestSession
if ($button) {
$btnXpath = "//button[@name='$button'] | //input[@name='$button'][@type='button' or @type='submit']";
if (!$formCrawler->children()->filterXPath($btnXpath)->count()) {
- throw new Exception("Can't find button '$button' to submit as part of test.");
+ throw new LogicException("Can't find button '$button' to submit as part of test.");
}
$values[$button] = true;
}
diff --git a/src/Forms/DropdownField.php b/src/Forms/DropdownField.php
index ed5da3000..9e3124525 100644
--- a/src/Forms/DropdownField.php
+++ b/src/Forms/DropdownField.php
@@ -68,7 +68,7 @@ use SilverStripe\Model\ArrayData;
* DropdownField::create(
* 'Country',
* 'Country',
- * singleton(MyObject::class)->dbObject('Country')->enumValues()
+ * singleton(MyObject::class)->dbObject('Country')?->enumValues()
* );
*
*
diff --git a/src/Forms/FieldGroup.php b/src/Forms/FieldGroup.php
index 9a0d6c675..c61de2136 100644
--- a/src/Forms/FieldGroup.php
+++ b/src/Forms/FieldGroup.php
@@ -154,7 +154,7 @@ class FieldGroup extends CompositeField
/** @var FormField $subfield */
$messages = [];
foreach ($dataFields as $subfield) {
- $message = $subfield->obj('Message')->forTemplate();
+ $message = $subfield->obj('Message')?->forTemplate();
if ($message) {
$messages[] = rtrim($message ?? '', ".");
}
diff --git a/src/Forms/Form.php b/src/Forms/Form.php
index 7ce206f8d..a0483b68c 100644
--- a/src/Forms/Form.php
+++ b/src/Forms/Form.php
@@ -82,7 +82,7 @@ class Form extends ModelData implements HasRequestHandler
const ENC_TYPE_MULTIPART = 'multipart/form-data';
/**
- * Accessed by Form.ss.
+ * Accessed by Form template.
* A performance enhancement over the generate-the-form-tag-and-then-remove-it code that was there previously
*
* @var bool
@@ -159,7 +159,7 @@ class Form extends ModelData implements HasRequestHandler
/**
* Legend value, to be inserted into the
*