This commit is contained in:
Christopher Joe 2017-11-21 15:08:30 +13:00
commit 2b6b877327
9 changed files with 122 additions and 54 deletions

View File

@ -165,21 +165,22 @@ around 2000px on the longest edge.
#### Forced resampling
Since the 'master' images in your asset store may have a large file size, by
default SilverStripe will always apply compression to your images to save
bandwidth - even if no other manipulation (such as a crop or resize) is taking
place. If you expect the images in your asset store to already have
compression applied and want to serve up the original when no resampling is
necessary, you can add this to your mysite/config/config.yml file:
Since the 'master' images in your asset store may have a large file size, SilverStripe
can apply compression to your images to save bandwidth - even if no other manipulation
(such as a crop or resize) is taking place. In many cases this can result in a smaller
overall file size, which may be appropriate for streaming to web users.
Please note that turning this feature on can increase the server memory requirements,
and is off by default to conserve resources.
You can turn this on with the below config:
```yml
# Configure resampling for File dataobject
---
Name: resamplefiles
---
SilverStripe\Assets\File:
force_resample: false
# DBFile can be configured independently
SilverStripe\Assets\Storage\DBFile:
force_resample: false
```
#### Resampled image quality

View File

@ -1350,18 +1350,26 @@ These methods are deprecated:
### New Ownership API {#ownership}
In order to support the recursive publishing of dataobjects, a new API has been developed to allow
developers to declare dependencies between objects. This is done to ensure that the published state
of linked components are consistent with their "owner." Without the concept of ownership, these linked
components could be implicitly exposed on the frontend, which may not align with the intent of the
content author.
developers to declare dependencies between versioned objects.
For instance, on a products page which has a list of products, the products should not be published unless the products page is, too. The ownership API solves this by allowing you to declare
a two-way relationship between objects, typically, but not necessarily, linked by a database relationship
(`has_many`, `many_many`, etc.).
```php
private static $owns = [
'Banners', // This page depends on banners, declared as a separate 'has_many'
'FooterImage', // This page depends on the footer image, declared as a separate 'has_one'
];
```
This is done to ensure that the published state of linked components are consistent with their "owner."
Without the concept of ownership, these linked components could be implicitly exposed on the frontend,
which may not align with the intent of the content author.
For instance, on a products page which has a list of products, the products should not be published unless
the products page is, too. The ownership API solves this by allowing you to declare a two-way relationship
between objects, typically, but not necessarily, linked by a database relationship
(`has_many`, `many_many`, etc.).
```php
use SilverStripe\Versioned\Versioned;
use Page;
use SilverStripe\ORM\DataObject;
class ProductPage extends Page
@ -1386,6 +1394,7 @@ class Product extends DataObject
];
}
```
If your objects are linked by something other than a database relationship, for instance, a custom
getter that is computed at runtime, the same rules can be applied, as long as you provide an `$owned_by`
setting on the child object.

View File

@ -163,7 +163,7 @@ class PDOConnector extends DBConnector
$connCollation = Config::inst()->get('SilverStripe\ORM\Connect\MySQLDatabase', 'connection_collation');
// Set charset if given and not null. Can explicitly set to empty string to omit
if ($parameters['driver'] !== 'sqlsrv') {
if (!in_array($parameters['driver'], ['sqlsrv', 'pgsql'])) {
$charset = isset($parameters['charset'])
? $parameters['charset']
: $connCharset;

View File

@ -67,6 +67,9 @@ class DBDate extends DBField
} else {
// Convert US date -> iso, fix y2k, etc
$value = $this->fixInputDate($value);
if (is_null($value)) {
return null;
}
$source = strtotime($value); // convert string to timestamp
}
if ($value === false) {
@ -529,6 +532,9 @@ class DBDate extends DBField
// split
list($year, $month, $day, $time) = $this->explodeDateString($value);
if ((int)$year === 0 && (int)$month === 0 && (int)$day === 0) {
return null;
}
// Validate date
if (!checkdate($month, $day, $year)) {
throw new InvalidArgumentException(
@ -568,7 +574,7 @@ class DBDate extends DBField
if ($parts[0] < 1000 && $parts[2] > 1000) {
$parts = array_reverse($parts);
}
if ($parts[0] < 1000) {
if ($parts[0] < 1000 && (int)$parts[0] !== 0) {
throw new InvalidArgumentException(
"Invalid date: '$value'. Use " . self::ISO_DATE . " to prevent this error."
);

View File

@ -32,45 +32,46 @@ class DBEnum extends DBString
private static $default_search_filter_class = 'ExactMatchFilter';
/**
* Create a new Enum field.
* Create a new Enum field, which is a value within a defined set, with an optional default.
*
* Example usage in {@link DataObject::$db} with comma-separated string
* notation ('Val1' is default)
* Example field specification strings:
*
* <code>
* "MyField" => "Enum('Val1, Val2, Val3', 'Val1')"
* </code>
*
* Example usage in in {@link DataObject::$db} with array notation
* ('Val1' is default)
*
* <code>
* "MyField" => "Enum(array('Val1', 'Val2', 'Val3'), 'Val1')"
* "MyField" => "Enum('Val1, Val2, Val3')" // First item 'Val1' is default implicitly
* "MyField" => "Enum('Val1, Val2, Val3', 'Val2')" // 'Val2' is default explicitly
* "MyField" => "Enum('Val1, Val2, Val3', null)" // Force empty (no) default
* "MyField" => "Enum(array('Val1', 'Val2', 'Val3'), 'Val1')" // Supports array notation as well
* </code>
*
* @param string $name
* @param string|array $enum A string containing a comma separated list of options or an array of Vals.
* @param string $default The default option, which is either NULL or one of the items in the enumeration.
* @param string|int|null $default The default option, which is either NULL or one of the items in the enumeration.
* If passing in an integer (non-string) it will default to the index of that item in the list.
* Set to null or empty string to allow empty values
* @param array $options Optional parameters for this DB field
*/
public function __construct($name = null, $enum = null, $default = null, $options = [])
public function __construct($name = null, $enum = null, $default = 0, $options = [])
{
if ($enum) {
$this->setEnum($enum);
$enum = $this->getEnum();
// If there's a default, then
if ($default) {
if (in_array($default, $this->getEnum())) {
// If there's a default, then use this
if ($default && !is_int($default)) {
if (in_array($default, $enum)) {
$this->setDefault($default);
} else {
user_error("Enum::__construct() The default value '$default' does not match any item in the"
. " enumeration", E_USER_ERROR);
user_error(
"Enum::__construct() The default value '$default' does not match any item in the enumeration",
E_USER_ERROR
);
}
// By default, set the default value to the first item
} elseif (is_int($default) && $default < count($enum)) {
// Set to specified index if given
$this->setDefault($enum[$default]);
} else {
$enum = $this->getEnum();
$this->setDefault(reset($enum));
// Set to null if specified
$this->setDefault(null);
}
}
@ -187,7 +188,7 @@ class DBEnum extends DBString
ltrim(preg_replace('/,\s*\n\s*$/', '', $enum))
);
}
$this->enum = $enum;
$this->enum = array_values($enum);
return $this;
}
@ -210,6 +211,7 @@ class DBEnum extends DBString
public function setDefault($default)
{
$this->default = $default;
$this->setDefaultValue($default);
return $this;
}
}

View File

@ -2,8 +2,9 @@
namespace SilverStripe\ORM\FieldType;
use SilverStripe\Core\Injector\Injector;
use InvalidArgumentException;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\FormField;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\DataObject;
@ -139,7 +140,7 @@ abstract class DBField extends ViewableData implements DBIndexable
if ($options) {
if (!is_array($options)) {
throw new \InvalidArgumentException("Invalid options $options");
throw new InvalidArgumentException("Invalid options $options");
}
$this->setOptions($options);
}
@ -152,16 +153,26 @@ abstract class DBField extends ViewableData implements DBIndexable
*
* Useful for accessing the classes behaviour for other parts of your code.
*
* @param string $className class of field to construct
* @param string $spec Class specification to construct. May include both service name and additional
* constructor arguments in the same format as DataObject.db config.
* @param mixed $value value of field
* @param string $name Name of field
* @param mixed $object Additional parameter to pass to field constructor
* @param mixed $args Additional arguments to pass to constructor if not using args in service $spec
* Note: Will raise a warning if using both
* @return static
*/
public static function create_field($className, $value, $name = null, $object = null)
public static function create_field($spec, $value, $name = null, ...$args)
{
// Raise warning if inconsistent with DataObject::dbObject() behaviour
// This will cause spec args to be shifted down by the number of provided $args
if ($args && strpos($spec, '(') !== false) {
trigger_error('Additional args provided in both $spec and $args', E_USER_WARNING);
}
// Ensure name is always first argument
array_unshift($args, $name);
/** @var DBField $dbField */
$dbField = Injector::inst()->create($className, $name, $object);
$dbField = Injector::inst()->createWithArgs($spec, $args);
$dbField->setValue($value, null, false);
return $dbField;
}
@ -633,11 +644,13 @@ DBG;
public function getIndexSpecs()
{
if ($type = $this->getIndexType()) {
$type = $this->getIndexType();
if ($type) {
return [
'type' => $type,
'columns' => [$this->getName()],
];
}
return null;
}
}

View File

@ -218,6 +218,12 @@ class DBDateTest extends SapphireTest
$date = DBField::create_field('Date', 0);
$this->assertEquals('1970-01-01', $date->getValue(), 'Zero is UNIX epoch date');
$date = DBField::create_field('Date', '0000-00-00 00:00:00');
$this->assertNull($date->getValue(), '0000-00-00 00:00:00 is set as NULL');
$date = DBField::create_field('Date', '00/00/0000');
$this->assertNull($date->getValue(), '00/00/0000 is set as NULL');
}
public function testDayOfMonth()

View File

@ -0,0 +1,31 @@
<?php
namespace SilverStripe\ORM\Tests;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\FieldType\DBEnum;
use SilverStripe\ORM\FieldType\DBField;
class DBEnumTest extends SapphireTest
{
public function testDefault()
{
/** @var DBEnum $enum1 */
$enum1 = DBField::create_field('Enum("A, B, C, D")', null);
/** @var DBEnum $enum2 */
$enum2 = DBField::create_field('Enum("A, B, C, D", "")', null);
/** @var DBEnum $enum3 */
$enum3 = DBField::create_field('Enum("A, B, C, D", null)', null);
/** @var DBEnum $enum4 */
$enum4 = DBField::create_field('Enum("A, B, C, D", 1)', null);
$this->assertEquals('A', $enum1->getDefaultValue());
$this->assertEquals('A', $enum1->getDefault());
$this->assertEquals(null, $enum2->getDefaultValue());
$this->assertEquals(null, $enum2->getDefault());
$this->assertEquals(null, $enum3->getDefaultValue());
$this->assertEquals(null, $enum3->getDefault());
$this->assertEquals('B', $enum4->getDefaultValue());
$this->assertEquals('B', $enum4->getDefault());
}
}

View File

@ -104,17 +104,17 @@ class PDODatabaseTest extends SapphireTest
$this->markTestSkipped('This test requires the current DB connector is PDO');
}
$query = new SQLUpdate('MySQLDatabaseTest_Data');
$query->setAssignments(array('Title' => 'New Title'));
$query = new SQLUpdate('"MySQLDatabaseTest_Data"');
$query->setAssignments(array('"Title"' => 'New Title'));
// Test update which affects no rows
$query->setWhere(array('Title' => 'Bob'));
$query->setWhere(array('"Title"' => 'Bob'));
$result = $query->execute();
$this->assertInstanceOf(PDOQuery::class, $result);
$this->assertEquals(0, DB::affected_rows());
// Test update which affects some rows
$query->setWhere(array('Title' => 'First Item'));
$query->setWhere(array('"Title"' => 'First Item'));
$result = $query->execute();
$this->assertInstanceOf(PDOQuery::class, $result);
$this->assertEquals(1, DB::affected_rows());