diff --git a/docs/en/00_Getting_Started/00_Server_Requirements.md b/docs/en/00_Getting_Started/00_Server_Requirements.md index c1ba6b938..710c62c7c 100644 --- a/docs/en/00_Getting_Started/00_Server_Requirements.md +++ b/docs/en/00_Getting_Started/00_Server_Requirements.md @@ -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. * One of the following operating systems: * Linux/Unix/BSD - * Microsoft Windows XP SP3, Vista, Windows 7, Server 2008, Server 2008 R2 - * Mac OS X 10.4+ + * Windows + * Mac OS X ## Web server hardware requirements 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 2eb4e4587..b1be3f83d 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 @@ -42,7 +42,7 @@ you are running your site configuration with a separate `public/` webroot folder deny all; } sendfile on; - try_files $uri index.php?$query_string; + try_files $uri /index.php?$query_string; } location ~ /\.. { diff --git a/docs/en/02_Developer_Guides/00_Model/02_Relations.md b/docs/en/02_Developer_Guides/00_Model/02_Relations.md index 36661aa98..c6933e2a7 100644 --- a/docs/en/02_Developer_Guides/00_Model/02_Relations.md +++ b/docs/en/02_Developer_Guides/00_Model/02_Relations.md @@ -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 "``Class", which along 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. ```php @@ -94,7 +94,7 @@ class Fan extends DataObject // Generates columns FanOfID and FanOfClass private static $has_one = [ - "FanOf" => "DataObject" + "FanOf" => DataObject::class, ]; } ``` diff --git a/src/Control/HTTPRequestBuilder.php b/src/Control/HTTPRequestBuilder.php index 0e81fc712..8ccd07aaa 100644 --- a/src/Control/HTTPRequestBuilder.php +++ b/src/Control/HTTPRequestBuilder.php @@ -103,19 +103,23 @@ class HTTPRequestBuilder $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 if (isset($server['PHP_AUTH_USER']) && isset($server['PHP_AUTH_PW'])) { // Shift PHP_AUTH_* into headers so they are available via request $headers['PHP_AUTH_USER'] = $server['PHP_AUTH_USER']; $headers['PHP_AUTH_PW'] = $server['PHP_AUTH_PW']; - } elseif (!empty($headers['Authorization']) && preg_match('/Basic\s+(.*)$/i', $headers['Authorization'], $matches)) { - // 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 - // - // 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])); + } elseif ($authHeader && preg_match('/Basic\s+(?.*)$/i', $authHeader, $matches)) { + list($name, $password) = explode(':', base64_decode($matches['token'])); $headers['PHP_AUTH_USER'] = $name; $headers['PHP_AUTH_PW'] = $password; } diff --git a/src/Core/ClassInfo.php b/src/Core/ClassInfo.php index 12e707d82..3029b200f 100644 --- a/src/Core/ClassInfo.php +++ b/src/Core/ClassInfo.php @@ -208,6 +208,7 @@ class ClassInfo * eg: self::class_name('dataobJEct'); //returns 'DataObject' * * @param string|object $nameOrObject The classname or object you want to normalise + * @throws \ReflectionException * @return string The normalised class name */ public static function class_name($nameOrObject) diff --git a/src/Core/Extensible.php b/src/Core/Extensible.php index e4bb1be1b..8dc523bf5 100644 --- a/src/Core/Extensible.php +++ b/src/Core/Extensible.php @@ -495,17 +495,22 @@ trait Extensible /** * Get an extension instance attached to this object by name. * - * @uses hasExtension() - * * @param string $extension - * @return Extension + * @return Extension|null */ public function getExtensionInstance($extension) { $instances = $this->getExtensionInstances(); - return isset($instances[$extension]) - ? $instances[$extension] - : null; + if (array_key_exists($extension, $instances)) { + return $instances[$extension]; + } + // 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) { - $instances = $this->getExtensionInstances(); - return isset($instances[$extension]); + return (bool) $this->getExtensionInstance($extension); } /** @@ -553,8 +557,16 @@ trait Extensible if ($extensions) { foreach ($extensions as $extension) { - $instance = Injector::inst()->get($extension); - $this->extension_instances[get_class($instance)] = $instance; + $name = $extension; + // 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); } } } diff --git a/src/Forms/FileField.php b/src/Forms/FileField.php index 44e104b85..41372d3af 100644 --- a/src/Forms/FileField.php +++ b/src/Forms/FileField.php @@ -128,7 +128,7 @@ class FileField extends FormField implements FileHandleField */ 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; } diff --git a/src/Forms/GridField/GridFieldSortableHeader.php b/src/Forms/GridField/GridFieldSortableHeader.php index 0dd66a64c..f4eb9344b 100644 --- a/src/Forms/GridField/GridFieldSortableHeader.php +++ b/src/Forms/GridField/GridFieldSortableHeader.php @@ -196,7 +196,7 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM '' ); } else { - $field = new LiteralField($fieldName, ''); + $field = new LiteralField($fieldName, '' . $title . ''); } } else { $field = new LiteralField($fieldName, '' . $title . ''); diff --git a/src/ORM/DatabaseAdmin.php b/src/ORM/DatabaseAdmin.php index 945b787e8..4d87bc98c 100644 --- a/src/ORM/DatabaseAdmin.php +++ b/src/ORM/DatabaseAdmin.php @@ -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 "
  • $dataClass
  • \n"; - } - } - - singleton($dataClass)->requireDefaultRecords(); - } - } - // Remap obsolete class names $schema = DataObject::getSchema(); foreach ($this->config()->classname_value_remapping as $oldClassName => $newClassName) { @@ -353,7 +337,7 @@ class DatabaseAdmin extends Controller $updateQueries = [sprintf($updateQuery, '')]; // Remap versioned table ClassName values as well - $class = singleton($newClassName); + $class = DataObject::singleton($newClassName); if ($class->has_extension(Versioned::class)) { if ($class->hasStages()) { $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 "
  • $dataClass
  • \n"; + } + } + + DataObject::singleton($dataClass)->requireDefaultRecords(); + } + } + if (!$quiet && !Director::is_cli()) { echo ""; } diff --git a/tests/php/Control/HTTPRequestBuilderTest.php b/tests/php/Control/HTTPRequestBuilderTest.php index 4d5063c54..23a6c0821 100644 --- a/tests/php/Control/HTTPRequestBuilderTest.php +++ b/tests/php/Control/HTTPRequestBuilderTest.php @@ -52,7 +52,6 @@ class HTTPRequestBuilderTest extends SapphireTest ]; $this->assertEquals($headers, HTTPRequestBuilder::extractRequestHeaders($request)); - $request = [ 'PHP_AUTH_USER' => 'admin', 'PHP_AUTH_PW' => 'password', @@ -62,5 +61,29 @@ class HTTPRequestBuilderTest extends SapphireTest 'PHP_AUTH_PW' => 'password', ]; $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' + ); } } diff --git a/tests/php/Core/ObjectTest.php b/tests/php/Core/ObjectTest.php index 46a4c4619..886a5cd0a 100644 --- a/tests/php/Core/ObjectTest.php +++ b/tests/php/Core/ObjectTest.php @@ -195,11 +195,13 @@ class ObjectTest extends SapphireTest ); $inst = new ExtensionTest(); $extensions = $inst->getExtensionInstances(); - $this->assertEquals(count($extensions), 2); + $this->assertCount(2, $extensions); + $this->assertArrayHasKey(ExtendTest1::class, $extensions); $this->assertInstanceOf( ExtendTest1::class, $extensions[ExtendTest1::class] ); + $this->assertArrayHasKey(ExtendTest2::class, $extensions); $this->assertInstanceOf( ExtendTest2::class, $extensions[ExtendTest2::class] @@ -507,4 +509,33 @@ class ObjectTest extends SapphireTest 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)); + } }