mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-09-28 20:29:15 +02:00
Merge branch '4.0' into 4
This commit is contained in:
commit
e4b936c7c7
@ -45,8 +45,8 @@ Our web-based [PHP installer](installation/) can check if you meet the requireme
|
|||||||
* We recommend enabling content compression (for example with mod_deflate) to speed up the delivery of HTML, CSS, and JavaScript.
|
* We recommend enabling content compression (for example with mod_deflate) to speed up the delivery of HTML, CSS, and JavaScript.
|
||||||
* One of the following operating systems:
|
* One of the following operating systems:
|
||||||
* Linux/Unix/BSD
|
* Linux/Unix/BSD
|
||||||
* Microsoft Windows XP SP3, Vista, Windows 7, Server 2008, Server 2008 R2
|
* Windows
|
||||||
* Mac OS X 10.4+
|
* Mac OS X
|
||||||
|
|
||||||
## Web server hardware requirements
|
## Web server hardware requirements
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ you are running your site configuration with a separate `public/` webroot folder
|
|||||||
deny all;
|
deny all;
|
||||||
}
|
}
|
||||||
sendfile on;
|
sendfile on;
|
||||||
try_files $uri index.php?$query_string;
|
try_files $uri /index.php?$query_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ /\.. {
|
location ~ /\.. {
|
||||||
|
@ -69,7 +69,7 @@ This is useful where there could be many use cases for a particular data structu
|
|||||||
An additional column is created called "`<relationship-name>`Class", which along
|
An additional column is created called "`<relationship-name>`Class", which along
|
||||||
with the ID column identifies the object.
|
with the ID column identifies the object.
|
||||||
|
|
||||||
To specify that a has_one relation is polymorphic set the type to 'DataObject'.
|
To specify that a has_one relation is polymorphic set the type to [api:SilverStripe\ORM\DataObject]
|
||||||
Ideally, the associated has_many (or belongs_to) should be specified with dot notation.
|
Ideally, the associated has_many (or belongs_to) should be specified with dot notation.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
@ -94,7 +94,7 @@ class Fan extends DataObject
|
|||||||
|
|
||||||
// Generates columns FanOfID and FanOfClass
|
// Generates columns FanOfID and FanOfClass
|
||||||
private static $has_one = [
|
private static $has_one = [
|
||||||
"FanOf" => "DataObject"
|
"FanOf" => DataObject::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -103,19 +103,23 @@ class HTTPRequestBuilder
|
|||||||
$headers['Content-Length'] = $server['CONTENT_LENGTH'];
|
$headers['Content-Length'] = $server['CONTENT_LENGTH'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable HTTP Basic authentication workaround for PHP running in CGI mode with Apache
|
||||||
|
// Depending on server configuration the auth header may be in HTTP_AUTHORIZATION or
|
||||||
|
// REDIRECT_HTTP_AUTHORIZATION
|
||||||
|
$authHeader = null;
|
||||||
|
if (isset($headers['Authorization'])) {
|
||||||
|
$authHeader = $headers['Authorization'];
|
||||||
|
} elseif (isset($server['REDIRECT_HTTP_AUTHORIZATION'])) {
|
||||||
|
$authHeader = $server['REDIRECT_HTTP_AUTHORIZATION'];
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure basic auth is available via headers
|
// Ensure basic auth is available via headers
|
||||||
if (isset($server['PHP_AUTH_USER']) && isset($server['PHP_AUTH_PW'])) {
|
if (isset($server['PHP_AUTH_USER']) && isset($server['PHP_AUTH_PW'])) {
|
||||||
// Shift PHP_AUTH_* into headers so they are available via request
|
// Shift PHP_AUTH_* into headers so they are available via request
|
||||||
$headers['PHP_AUTH_USER'] = $server['PHP_AUTH_USER'];
|
$headers['PHP_AUTH_USER'] = $server['PHP_AUTH_USER'];
|
||||||
$headers['PHP_AUTH_PW'] = $server['PHP_AUTH_PW'];
|
$headers['PHP_AUTH_PW'] = $server['PHP_AUTH_PW'];
|
||||||
} elseif (!empty($headers['Authorization']) && preg_match('/Basic\s+(.*)$/i', $headers['Authorization'], $matches)) {
|
} elseif ($authHeader && preg_match('/Basic\s+(?<token>.*)$/i', $authHeader, $matches)) {
|
||||||
// Enable HTTP Basic authentication workaround for PHP running in CGI mode with Apache
|
list($name, $password) = explode(':', base64_decode($matches['token']));
|
||||||
// Depending on server configuration the auth header may be in HTTP_AUTHORIZATION or
|
|
||||||
// REDIRECT_HTTP_AUTHORIZATION
|
|
||||||
//
|
|
||||||
// The follow rewrite rule must be in the sites .htaccess file to enable this workaround
|
|
||||||
// RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
|
||||||
list($name, $password) = explode(':', base64_decode($matches[1]));
|
|
||||||
$headers['PHP_AUTH_USER'] = $name;
|
$headers['PHP_AUTH_USER'] = $name;
|
||||||
$headers['PHP_AUTH_PW'] = $password;
|
$headers['PHP_AUTH_PW'] = $password;
|
||||||
}
|
}
|
||||||
|
@ -208,6 +208,7 @@ class ClassInfo
|
|||||||
* eg: self::class_name('dataobJEct'); //returns 'DataObject'
|
* eg: self::class_name('dataobJEct'); //returns 'DataObject'
|
||||||
*
|
*
|
||||||
* @param string|object $nameOrObject The classname or object you want to normalise
|
* @param string|object $nameOrObject The classname or object you want to normalise
|
||||||
|
* @throws \ReflectionException
|
||||||
* @return string The normalised class name
|
* @return string The normalised class name
|
||||||
*/
|
*/
|
||||||
public static function class_name($nameOrObject)
|
public static function class_name($nameOrObject)
|
||||||
|
@ -495,17 +495,22 @@ trait Extensible
|
|||||||
/**
|
/**
|
||||||
* Get an extension instance attached to this object by name.
|
* Get an extension instance attached to this object by name.
|
||||||
*
|
*
|
||||||
* @uses hasExtension()
|
|
||||||
*
|
|
||||||
* @param string $extension
|
* @param string $extension
|
||||||
* @return Extension
|
* @return Extension|null
|
||||||
*/
|
*/
|
||||||
public function getExtensionInstance($extension)
|
public function getExtensionInstance($extension)
|
||||||
{
|
{
|
||||||
$instances = $this->getExtensionInstances();
|
$instances = $this->getExtensionInstances();
|
||||||
return isset($instances[$extension])
|
if (array_key_exists($extension, $instances)) {
|
||||||
? $instances[$extension]
|
return $instances[$extension];
|
||||||
: null;
|
}
|
||||||
|
// in case Injector has been used to replace an extension
|
||||||
|
foreach ($instances as $instance) {
|
||||||
|
if (is_a($instance, $extension)) {
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -524,8 +529,7 @@ trait Extensible
|
|||||||
*/
|
*/
|
||||||
public function hasExtension($extension)
|
public function hasExtension($extension)
|
||||||
{
|
{
|
||||||
$instances = $this->getExtensionInstances();
|
return (bool) $this->getExtensionInstance($extension);
|
||||||
return isset($instances[$extension]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -553,8 +557,16 @@ trait Extensible
|
|||||||
|
|
||||||
if ($extensions) {
|
if ($extensions) {
|
||||||
foreach ($extensions as $extension) {
|
foreach ($extensions as $extension) {
|
||||||
$instance = Injector::inst()->get($extension);
|
$name = $extension;
|
||||||
$this->extension_instances[get_class($instance)] = $instance;
|
// Allow service names of the form "%$ServiceName"
|
||||||
|
if (substr($name, 0, 2) == '%$') {
|
||||||
|
$name = substr($name, 2);
|
||||||
|
}
|
||||||
|
$name = trim(strtok($name, '('));
|
||||||
|
if (class_exists($name)) {
|
||||||
|
$name = ClassInfo::class_name($name);
|
||||||
|
}
|
||||||
|
$this->extension_instances[$name] = Injector::inst()->get($extension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ class FileField extends FormField implements FileHandleField
|
|||||||
*/
|
*/
|
||||||
public function saveInto(DataObjectInterface $record)
|
public function saveInto(DataObjectInterface $record)
|
||||||
{
|
{
|
||||||
if (!isset($_FILES[$this->name])) {
|
if (!isset($_FILES[$this->name]['error']) || $_FILES[$this->name]['error'] == UPLOAD_ERR_NO_FILE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM
|
|||||||
'<button type="button" name="showFilter" title="Open search and filter" class="btn btn-secondary font-icon-search btn--no-text btn--icon-large grid-field__filter-open"></button>'
|
'<button type="button" name="showFilter" title="Open search and filter" class="btn btn-secondary font-icon-search btn--no-text btn--icon-large grid-field__filter-open"></button>'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$field = new LiteralField($fieldName, '<span class="non-sortable"></span>');
|
$field = new LiteralField($fieldName, '<span class="non-sortable">' . $title . '</span>');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$field = new LiteralField($fieldName, '<span class="non-sortable">' . $title . '</span>');
|
$field = new LiteralField($fieldName, '<span class="non-sortable">' . $title . '</span>');
|
||||||
|
@ -318,22 +318,6 @@ class DatabaseAdmin extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($dataClasses as $dataClass) {
|
|
||||||
// Check if class exists before trying to instantiate - this sidesteps any manifest weirdness
|
|
||||||
// Test_ indicates that it's the data class is part of testing system
|
|
||||||
if (strpos($dataClass, 'Test_') === false && class_exists($dataClass)) {
|
|
||||||
if (!$quiet) {
|
|
||||||
if (Director::is_cli()) {
|
|
||||||
echo " * $dataClass\n";
|
|
||||||
} else {
|
|
||||||
echo "<li>$dataClass</li>\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
singleton($dataClass)->requireDefaultRecords();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remap obsolete class names
|
// Remap obsolete class names
|
||||||
$schema = DataObject::getSchema();
|
$schema = DataObject::getSchema();
|
||||||
foreach ($this->config()->classname_value_remapping as $oldClassName => $newClassName) {
|
foreach ($this->config()->classname_value_remapping as $oldClassName => $newClassName) {
|
||||||
@ -353,7 +337,7 @@ class DatabaseAdmin extends Controller
|
|||||||
$updateQueries = [sprintf($updateQuery, '')];
|
$updateQueries = [sprintf($updateQuery, '')];
|
||||||
|
|
||||||
// Remap versioned table ClassName values as well
|
// Remap versioned table ClassName values as well
|
||||||
$class = singleton($newClassName);
|
$class = DataObject::singleton($newClassName);
|
||||||
if ($class->has_extension(Versioned::class)) {
|
if ($class->has_extension(Versioned::class)) {
|
||||||
if ($class->hasStages()) {
|
if ($class->hasStages()) {
|
||||||
$updateQueries[] = sprintf($updateQuery, '_Live');
|
$updateQueries[] = sprintf($updateQuery, '_Live');
|
||||||
@ -367,6 +351,23 @@ class DatabaseAdmin extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Require all default records
|
||||||
|
foreach ($dataClasses as $dataClass) {
|
||||||
|
// Check if class exists before trying to instantiate - this sidesteps any manifest weirdness
|
||||||
|
// Test_ indicates that it's the data class is part of testing system
|
||||||
|
if (strpos($dataClass, 'Test_') === false && class_exists($dataClass)) {
|
||||||
|
if (!$quiet) {
|
||||||
|
if (Director::is_cli()) {
|
||||||
|
echo " * $dataClass\n";
|
||||||
|
} else {
|
||||||
|
echo "<li>$dataClass</li>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DataObject::singleton($dataClass)->requireDefaultRecords();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!$quiet && !Director::is_cli()) {
|
if (!$quiet && !Director::is_cli()) {
|
||||||
echo "</ul>";
|
echo "</ul>";
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,6 @@ class HTTPRequestBuilderTest extends SapphireTest
|
|||||||
];
|
];
|
||||||
$this->assertEquals($headers, HTTPRequestBuilder::extractRequestHeaders($request));
|
$this->assertEquals($headers, HTTPRequestBuilder::extractRequestHeaders($request));
|
||||||
|
|
||||||
|
|
||||||
$request = [
|
$request = [
|
||||||
'PHP_AUTH_USER' => 'admin',
|
'PHP_AUTH_USER' => 'admin',
|
||||||
'PHP_AUTH_PW' => 'password',
|
'PHP_AUTH_PW' => 'password',
|
||||||
@ -62,5 +61,29 @@ class HTTPRequestBuilderTest extends SapphireTest
|
|||||||
'PHP_AUTH_PW' => 'password',
|
'PHP_AUTH_PW' => 'password',
|
||||||
];
|
];
|
||||||
$this->assertEquals($headers, HTTPRequestBuilder::extractRequestHeaders($request));
|
$this->assertEquals($headers, HTTPRequestBuilder::extractRequestHeaders($request));
|
||||||
|
|
||||||
|
$request = [
|
||||||
|
'REDIRECT_HTTP_AUTHORIZATION' => 'Basic YWRtaW46cGFzc3dvcmQ=',
|
||||||
|
];
|
||||||
|
$headers = [
|
||||||
|
'PHP_AUTH_USER' => 'admin',
|
||||||
|
'PHP_AUTH_PW' => 'password',
|
||||||
|
];
|
||||||
|
$this->assertEquals($headers, HTTPRequestBuilder::extractRequestHeaders($request));
|
||||||
|
|
||||||
|
$request = [
|
||||||
|
'HTTP_AUTHORIZATION' => 'Basic YWRtaW46cGFzc3dvcmQ=',
|
||||||
|
'REDIRECT_HTTP_AUTHORIZATION' => 'Basic dXNlcjphdXRo=',
|
||||||
|
];
|
||||||
|
$headers = [
|
||||||
|
'PHP_AUTH_USER' => 'admin',
|
||||||
|
'PHP_AUTH_PW' => 'password',
|
||||||
|
'Authorization' => 'Basic YWRtaW46cGFzc3dvcmQ=',
|
||||||
|
];
|
||||||
|
$this->assertEquals(
|
||||||
|
$headers,
|
||||||
|
HTTPRequestBuilder::extractRequestHeaders($request),
|
||||||
|
'Prefer HTTP_AUTHORIZATION over REDIRECT_HTTP_AUTHORIZATION'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,11 +195,13 @@ class ObjectTest extends SapphireTest
|
|||||||
);
|
);
|
||||||
$inst = new ExtensionTest();
|
$inst = new ExtensionTest();
|
||||||
$extensions = $inst->getExtensionInstances();
|
$extensions = $inst->getExtensionInstances();
|
||||||
$this->assertEquals(count($extensions), 2);
|
$this->assertCount(2, $extensions);
|
||||||
|
$this->assertArrayHasKey(ExtendTest1::class, $extensions);
|
||||||
$this->assertInstanceOf(
|
$this->assertInstanceOf(
|
||||||
ExtendTest1::class,
|
ExtendTest1::class,
|
||||||
$extensions[ExtendTest1::class]
|
$extensions[ExtendTest1::class]
|
||||||
);
|
);
|
||||||
|
$this->assertArrayHasKey(ExtendTest2::class, $extensions);
|
||||||
$this->assertInstanceOf(
|
$this->assertInstanceOf(
|
||||||
ExtendTest2::class,
|
ExtendTest2::class,
|
||||||
$extensions[ExtendTest2::class]
|
$extensions[ExtendTest2::class]
|
||||||
@ -507,4 +509,33 @@ class ObjectTest extends SapphireTest
|
|||||||
ClassInfo::parse_class_spec('\Test\MyClass')
|
ClassInfo::parse_class_spec('\Test\MyClass')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testInjectedExtensions()
|
||||||
|
{
|
||||||
|
$mockExtension = $this->createMock(TestExtension::class);
|
||||||
|
$mockClass = get_class($mockExtension);
|
||||||
|
|
||||||
|
$object = new ExtensionTest2();
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
$this->assertNotEquals(TestExtension::class, $mockClass);
|
||||||
|
|
||||||
|
$this->assertTrue($object->hasExtension(TestExtension::class));
|
||||||
|
$this->assertFalse($object->hasExtension($mockClass));
|
||||||
|
$this->assertCount(1, $object->getExtensionInstances());
|
||||||
|
$this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance(TestExtension::class));
|
||||||
|
$this->assertNotInstanceOf($mockClass, $object->getExtensionInstance(TestExtension::class));
|
||||||
|
|
||||||
|
Injector::inst()->registerService($mockExtension, TestExtension::class);
|
||||||
|
|
||||||
|
$object = new ExtensionTest2();
|
||||||
|
|
||||||
|
$this->assertTrue($object->hasExtension(TestExtension::class));
|
||||||
|
$this->assertTrue($object->hasExtension($mockClass));
|
||||||
|
$this->assertCount(1, $object->getExtensionInstances());
|
||||||
|
$this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance(TestExtension::class));
|
||||||
|
$this->assertInstanceOf($mockClass, $object->getExtensionInstance(TestExtension::class));
|
||||||
|
$this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance($mockClass));
|
||||||
|
$this->assertInstanceOf($mockClass, $object->getExtensionInstance($mockClass));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user