diff --git a/.upgrade.yml b/.upgrade.yml
index a25ba0b11..e4e2c392a 100644
--- a/.upgrade.yml
+++ b/.upgrade.yml
@@ -1171,8 +1171,6 @@ warnings:
message: 'moved to SilverStripe\View\HTML->createTag()'
'SilverStripe\Forms\Form->transformTo()':
message: 'Removed'
- 'SilverStripe\Forms\Form->callfieldmethod()':
- message: 'Removed'
'SilverStripe\Forms\Form::single_field_required()':
message: 'Removed'
'SilverStripe\Forms\Form::current_action()':
@@ -1183,8 +1181,6 @@ warnings:
message: 'Renamed to restoreFormState()'
'SilverStripe\Forms\Form->resetValidation()':
message: 'Renamed to clearFormState()'
- 'SilverStripe\Forms\Form->transformTo()':
- message: 'Removed'
'SilverStripe\Forms\Form->callfieldmethod()':
message: 'Removed'
'SilverStripe\Forms\Form->addErrorMessage()':
@@ -1323,8 +1319,6 @@ warnings:
message: 'Use new asset abstraction'
url: 'https://docs.silverstripe.org/en/4/changelogs/4.0.0#asset-storage'
constants:
- 'SS_TRUSTED_PROXY_HOST_HEADER':
- message: 'See TrustedProxyMiddleware'
'SS_TRUSTED_PROXY_PROTOCOL_HEADER':
message: 'See TrustedProxyMiddleware'
'SS_TRUSTED_PROXY_IP_HEADER':
diff --git a/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md b/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md
index 71b5747db..b9dafb44d 100644
--- a/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md
+++ b/docs/en/00_Getting_Started/01_Installation/How_To/Configure_Nginx.md
@@ -16,15 +16,19 @@ If you don't fully understand the configuration presented here, consult the
Especially be aware of [accidental php-execution](https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/ "Don't trust the tutorials") when extending the configuration.
-But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`:
+## Caveats about the sample configuration below
+
+* It does not cover serving securely over HTTPS.
+* It uses the new filesystem layout (with `public` directory) introduced in version 4.1.0. If your installation has been upgraded to 4.1+ from an older version and you have not [upgraded to the public folder](/changelogs/4.1.0.md), see the version of this documentation for version 4.0.
+* The error pages for 502 (Bad Gateway) and 503 (Service Unavailable) need to be manually created and published in the CMS (assuming use of the silverstripe/errorpage module).
```nginx
server {
include mime.types;
default_type application/octet-stream;
- client_max_body_size 0; # Manage this in php.ini
+ client_max_body_size 0; # Manage this in php.ini (upload_max_filesize & post_max_size)
listen 80;
- root /path/to/ss/folder;
+ root /path/to/ss/folder/public;
server_name example.com www.example.com;
# Defend against SS-2015-013 -- http://www.silverstripe.org/software/download/security-releases/ss-2015-013
@@ -39,6 +43,10 @@ server {
error_page 404 /assets/error-404.html;
error_page 500 /assets/error-500.html;
+ # See caveats
+ error_page 502 /assets/error-500.html;
+ error_page 503 /assets/error-500.html;
+
location ^~ /assets/ {
sendfile on;
try_files $uri =404;
@@ -54,37 +62,6 @@ server {
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
-
- # Denials
- location ~ /\.. {
- deny all;
- }
- location ~ \.ss$ {
- satisfy any;
- allow 127.0.0.1;
- deny all;
- }
- location ~ web\.config$ {
- deny all;
- }
- location ~ \.ya?ml$ {
- deny all;
- }
- location ~* README.*$ {
- deny all;
- }
- location ^~ /vendor/ {
- deny all;
- }
- location ~* /silverstripe-cache/ {
- deny all;
- }
- location ~* composer\.(json|lock)$ {
- deny all;
- }
- location ~* /(cms|framework)/silverstripe_version$ {
- deny all;
- }
}
```
diff --git a/src/Control/Middleware/CanonicalURLMiddleware.php b/src/Control/Middleware/CanonicalURLMiddleware.php
index b108a479e..c007f0742 100644
--- a/src/Control/Middleware/CanonicalURLMiddleware.php
+++ b/src/Control/Middleware/CanonicalURLMiddleware.php
@@ -51,7 +51,8 @@ class CanonicalURLMiddleware implements HTTPMiddleware
/**
* Environment variables this middleware is enabled in, or a fixed boolean flag to
- * apply to all environments
+ * apply to all environments. cli is disabled unless present here as `cli`, or set to true
+ * to force enabled.
*
* @var array|bool
*/
@@ -325,7 +326,7 @@ class CanonicalURLMiddleware implements HTTPMiddleware
}
/**
- * Get enabled flag, or list of environments to enable in
+ * Get enabled flag, or list of environments to enable in.
*
* @return array|bool
*/
@@ -335,6 +336,10 @@ class CanonicalURLMiddleware implements HTTPMiddleware
}
/**
+ * Set enabled flag, or list of environments to enable in.
+ * Note: CLI is disabled by default, so `"cli"(string)` or `true(bool)` should be specified if you wish to
+ * enable for testing.
+ *
* @param array|bool $enabledEnvs
* @return $this
*/
@@ -359,6 +364,13 @@ class CanonicalURLMiddleware implements HTTPMiddleware
if (is_bool($enabledEnvs)) {
return $enabledEnvs;
}
+
+ // If CLI, EnabledEnvs must contain CLI
+ if (Director::is_cli() && !in_array('cli', $enabledEnvs)) {
+ return false;
+ }
+
+ // Check other envs
return empty($enabledEnvs) || in_array(Director::get_environment_type(), $enabledEnvs);
}
diff --git a/src/Core/Injector/Injector.php b/src/Core/Injector/Injector.php
index bf5fc8b54..6b0885132 100644
--- a/src/Core/Injector/Injector.php
+++ b/src/Core/Injector/Injector.php
@@ -584,6 +584,11 @@ class Injector implements ContainerInterface
$factory = isset($spec['factory']) ? $this->get($spec['factory']) : $this->getObjectCreator();
$object = $factory->create($class, $constructorParams);
+ // Handle empty factory responses
+ if (!$object) {
+ return null;
+ }
+
// figure out if we have a specific id set or not. In some cases, we might be instantiating objects
// that we don't manage directly; we don't want to store these in the service cache below
if (!$id) {
diff --git a/src/Forms/DateField.php b/src/Forms/DateField.php
index 9122aa0b9..d54aa5433 100644
--- a/src/Forms/DateField.php
+++ b/src/Forms/DateField.php
@@ -353,9 +353,12 @@ class DateField extends TextField
public function performReadonlyTransformation()
{
- $field = $this->castedCopy(DateField_Disabled::class);
- $field->setValue($this->dataValue());
- $field->setReadonly(true);
+ $field = $this
+ ->castedCopy(DateField_Disabled::class)
+ ->setValue($this->dataValue())
+ ->setLocale($this->getLocale())
+ ->setReadonly(true);
+
return $field;
}
diff --git a/src/Forms/DateField_Disabled.php b/src/Forms/DateField_Disabled.php
index e0656f99a..22cd201c3 100644
--- a/src/Forms/DateField_Disabled.php
+++ b/src/Forms/DateField_Disabled.php
@@ -14,27 +14,51 @@ class DateField_Disabled extends DateField
protected $disabled = true;
- public function Field($properties = array())
+ public function Field($properties = [])
{
- if ($this->valueObj) {
- if ($this->valueObj->isToday()) {
- $val = Convert::raw2xml($this->valueObj->toString($this->getConfig('dateformat'))
- . ' (' . _t('SilverStripe\\Forms\\DateField.TODAY', 'today') . ')');
+ // Default display value
+ $displayValue = '(' . _t(DateField::class . '.NOTSET', 'not set') . ')';
+
+ $value = $this->dataValue();
+
+ if ($value) {
+ $value = $this->tidyInternal($value);
+ $df = new DBDate($this->name);
+ $df->setValue($value);
+
+ if ($df->IsToday()) {
+ // e.g. 2018-06-01 (today)
+ $format = '%s (%s)';
+ $infoComplement = _t(DateField::class . '.TODAY', 'today');
} else {
- $df = new DBDate($this->name);
- $df->setValue($this->dataValue());
- $val = Convert::raw2xml($this->valueObj->toString($this->getConfig('dateformat'))
- . ', ' . $df->Ago());
+ // e.g. 2018-06-01, 5 days ago
+ $format = '%s, %s';
+ $infoComplement = $df->Ago();
}
- } else {
- $val = '(' . _t('SilverStripe\\Forms\\DateField.NOTSET', 'not set') . ')';
+
+ // Render the display value with some complement of info
+ $displayValue = Convert::raw2xml(sprintf(
+ $format,
+ $this->Value(),
+ $infoComplement
+ ));
}
- return "ID() . "\">$val";
+ return sprintf(
+ "%s",
+ $this->ID(),
+ $displayValue
+ );
}
public function Type()
{
- return "date_disabled readonly";
+ return "date_disabled readonly " . parent::Type();
+ }
+
+ public function getHTML5()
+ {
+ // Always disable HTML5 feature when using the readonly field.
+ return false;
}
}
diff --git a/tests/php/Core/Injector/InjectorTest.php b/tests/php/Core/Injector/InjectorTest.php
index d2a42af76..1e54cc1de 100644
--- a/tests/php/Core/Injector/InjectorTest.php
+++ b/tests/php/Core/Injector/InjectorTest.php
@@ -14,6 +14,7 @@ use SilverStripe\Core\Tests\Injector\InjectorTest\CircularOne;
use SilverStripe\Core\Tests\Injector\InjectorTest\CircularTwo;
use SilverStripe\Core\Tests\Injector\InjectorTest\ConstructableObject;
use SilverStripe\Core\Tests\Injector\InjectorTest\DummyRequirements;
+use SilverStripe\Core\Tests\Injector\InjectorTest\EmptyFactory;
use SilverStripe\Core\Tests\Injector\InjectorTest\MyChildClass;
use SilverStripe\Core\Tests\Injector\InjectorTest\MyParentClass;
use SilverStripe\Core\Tests\Injector\InjectorTest\NeedsBothCirculars;
@@ -102,6 +103,21 @@ class InjectorTest extends SapphireTest
);
}
+ public function testEmptyFactory()
+ {
+ $this->expectException(InjectorNotFoundException::class);
+ $injector = new Injector();
+ $services = array(
+ 'SomeClass' => array(
+ 'class' => AnotherService::class,
+ 'factory' => EmptyFactory::class,
+ )
+ );
+
+ $injector->load($services);
+ $injector->create('SomeClass');
+ }
+
public function testConfiguredInjector()
{
$injector = new Injector();
diff --git a/tests/php/Core/Injector/InjectorTest/EmptyFactory.php b/tests/php/Core/Injector/InjectorTest/EmptyFactory.php
new file mode 100644
index 000000000..3354d6b3f
--- /dev/null
+++ b/tests/php/Core/Injector/InjectorTest/EmptyFactory.php
@@ -0,0 +1,13 @@
+setValue('2011-02-01')
+ ->Field();
+ $expected = '1/02/2011 (today)';
+ $this->assertEquals($expected, $actual);
+
+ // Test today's date with time
+ $actual = DateField_Disabled::create('Test')
+ ->setValue('2011-02-01 10:34:00')
+ ->Field();
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testFieldWithDifferentDay()
+ {
+ // Test past
+ $actual = DateField_Disabled::create('Test')
+ ->setValue('2011-01-27')
+ ->Field();
+ $expected = '27/01/2011, 5 days ago';
+ $this->assertEquals($expected, $actual);
+
+ // Test future
+ $actual = DateField_Disabled::create('Test')
+ ->setValue('2011-02-06')
+ ->Field();
+ $expected = '6/02/2011, in 5 days';
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testFieldWithDifferentLocal()
+ {
+ // Test different local
+ $actual = DateField_Disabled::create('Test')
+ ->setValue('2011-02-06')
+ ->setHTML5(false)
+ ->setLocale('de_DE')
+ ->Field();
+ $expected = '06.02.2011, in 5 days';
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function testFieldWithNonValue()
+ {
+ // Test none value
+ $actual = DateField_Disabled::create('Test')->Field();
+ $expected = '(not set)';
+ $this->assertEquals($expected, $actual);
+
+ $actual = DateField_Disabled::create('Test')->setValue('This is not a date')->Field();
+ $this->assertEquals($expected, $actual);
+ }
+}