diff --git a/_config/view.yml b/_config/view.yml
new file mode 100644
index 00000000000..fd8293c9f3d
--- /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 aeb3b9f54a8..4bb9b46c1c0 100644
--- a/src/Control/ContentNegotiator.php
+++ b/src/Control/ContentNegotiator.php
@@ -225,7 +225,7 @@ public function html(HTTPResponse $response)
// Fix base tag
$content = preg_replace(
'//',
- '',
+ '',
$content ?? ''
);
diff --git a/src/Control/Controller.php b/src/Control/Controller.php
index b66ae94424d..2ebbe9c9479 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;
/**
@@ -87,6 +90,8 @@ class Controller extends RequestHandler implements TemplateGlobalProvider
'handleIndex',
];
+ protected ?TemplateEngine $templateEngine = null;
+
public function __construct()
{
parent::__construct();
@@ -400,7 +405,7 @@ public function getViewer($action)
$templates = array_unique(array_merge($actionTemplates, $classTemplates));
}
- return SSViewer::create($templates);
+ return SSViewer::create($templates, $this->getTemplateEngine());
}
/**
@@ -452,9 +457,10 @@ protected function definingClassForAction($action)
}
$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;
}
@@ -486,17 +492,25 @@ public function hasActionTemplate($action)
$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());
@@ -735,4 +749,12 @@ public static function get_template_global_variables()
'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 aa8bddd5c82..8ee6040583e 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 @@ public function removeData(string $name)
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 @@ public function getPlainTemplate(): string
}
/**
- * 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 3cb4a498bbc..5e657ef6b36 100644
--- a/src/Control/HTTPResponse.php
+++ b/src/Control/HTTPResponse.php
@@ -444,8 +444,6 @@ public function isRedirect()
/**
* The HTTP response represented as a raw string
- *
- * @return string
*/
public function __toString()
{
diff --git a/src/Control/RSS/RSSFeed_Entry.php b/src/Control/RSS/RSSFeed_Entry.php
index 1ebaae7e7de..66034d711ec 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 @@ public function __construct($entry, $titleField, $descriptionField, $authorField
/**
* 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 @@ public function Title()
/**
* 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 @@ public function Description()
/**
* 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 @@ public function Author()
* 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 e89b90ac547..54756184bc1 100644
--- a/src/Core/Manifest/ModuleResource.php
+++ b/src/Core/Manifest/ModuleResource.php
@@ -114,8 +114,6 @@ public function exists()
/**
* Get relative path
- *
- * @return string
*/
public function __toString()
{
diff --git a/src/Dev/Backtrace.php b/src/Dev/Backtrace.php
index 62d402efc51..9aa7b85ad81 100644
--- a/src/Dev/Backtrace.php
+++ b/src/Dev/Backtrace.php
@@ -149,11 +149,11 @@ public static function full_func_name($item, $showArgs = false, $argCharLimit =
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 2c1ff07a659..ae61630e4ba 100644
--- a/src/Dev/TestSession.php
+++ b/src/Dev/TestSession.php
@@ -4,6 +4,7 @@
use Exception;
use InvalidArgumentException;
+use LogicException;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Cookie_Backend;
use SilverStripe\Control\Director;
@@ -214,7 +215,7 @@ public function submitForm(string $formID, string $button = null, array $data =
$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 @@ public function submitForm(string $formID, string $button = null, array $data =
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 ed5da300034..9e31245250f 100644
--- a/src/Forms/DropdownField.php
+++ b/src/Forms/DropdownField.php
@@ -68,7 +68,7 @@
* 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 9a0d6c67588..c61de2136b9 100644
--- a/src/Forms/FieldGroup.php
+++ b/src/Forms/FieldGroup.php
@@ -154,7 +154,7 @@ public function getMessage()
/** @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 7ce206f8d57..a0483b68cc6 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
*