diff --git a/core/lib/Drupal/Core/TypedData/Primitive.php b/core/lib/Drupal/Core/TypedData/Primitive.php deleted file mode 100644 index 02fc6b4..0000000 --- a/core/lib/Drupal/Core/TypedData/Primitive.php +++ /dev/null @@ -1,36 +0,0 @@ -value; - } - - /** - * {@inheritdoc} - */ - public function setValue($value, $notify = TRUE) { - // Notify the parent of any changes to be made. - if ($notify && isset($this->parent)) { - $this->parent->onChange($this->name); - } - $this->value = $value; - } -} diff --git a/core/lib/Drupal/Core/TypedData/PrimitiveBase.php b/core/lib/Drupal/Core/TypedData/PrimitiveBase.php new file mode 100644 index 0000000..1ba9319 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/PrimitiveBase.php @@ -0,0 +1,39 @@ +value; + } + + /** + * {@inheritdoc} + */ + public function setValue($value, $notify = TRUE) { + // Notify the parent of any changes to be made. + if ($notify && isset($this->parent)) { + $this->parent->onChange($this->name); + } + $this->value = $value; + } +} diff --git a/core/lib/Drupal/Core/TypedData/PrimitiveInterface.php b/core/lib/Drupal/Core/TypedData/PrimitiveInterface.php index 72ebbc6..b2293e2 100644 --- a/core/lib/Drupal/Core/TypedData/PrimitiveInterface.php +++ b/core/lib/Drupal/Core/TypedData/PrimitiveInterface.php @@ -1,18 +1,31 @@ value; - } - - /** - * Overrides TypedData::setValue(). - */ - public function setValue($value, $notify = TRUE) { - // Notify the parent of any changes to be made. - if ($notify && isset($this->parent)) { - $this->parent->onChange($this->name); - } - // Catch any exceptions thrown due to invalid values being passed. - try { - if ($value instanceof DateInterval || !isset($value)) { - $this->value = $value; - } - // Treat integer values as time spans in seconds, even if supplied as PHP - // string. - elseif ((string) (int) $value === (string) $value) { - $this->value = new DateInterval('PT' . $value . 'S'); - } - elseif (is_string($value)) { - // @todo: Add support for negative intervals on top of the DateInterval - // constructor. - $this->value = new DateInterval($value); - } - else { - // Unknown value given. - $this->value = $value; - } - } - catch (\Exception $e) { - // An invalid value has been given. Setting any invalid value will let - // validation fail. - $this->value = $e; - } - } - - /** - * Overrides TypedData::getString(). - */ - public function getString() { - // Generate an ISO 8601 formatted string as supported by - // DateInterval::__construct() and setValue(). - return (string) $this->getValue()->format('%rP%yY%mM%dDT%hH%mM%sS'); - } -} diff --git a/core/lib/Drupal/Core/TypedData/Type/DurationInterface.php b/core/lib/Drupal/Core/TypedData/Type/DurationInterface.php index 83e9250..a39c0d5 100644 --- a/core/lib/Drupal/Core/TypedData/Type/DurationInterface.php +++ b/core/lib/Drupal/Core/TypedData/Type/DurationInterface.php @@ -1,9 +1,33 @@ value) { + // @todo: Add support for negative intervals on top of the DateInterval + // constructor. + return new \DateInterval($this->value); + } + } + + /** + * {@inheritdoc} + */ + public function setDuration(\DateInterval $duration) { + // Generate an ISO 8601 formatted string as supported by + // DateInterval::__construct() and setValue(). + $this->value = $duration->format('%rP%yY%mM%dDT%hH%mM%sS'); + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Type/Float.php b/core/lib/Drupal/Core/TypedData/Type/Float.php index 8ccc4d4..9f21aa2 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Float.php +++ b/core/lib/Drupal/Core/TypedData/Type/Float.php @@ -7,15 +7,12 @@ namespace Drupal\Core\TypedData\Type; -use Drupal\Core\TypedData\Primitive; +use Drupal\Core\TypedData\PrimitiveBase; use Drupal\Core\TypedData\Type\FloatInterface; /** * The float data type. - * - * The plain value of a float is a regular PHP float. For setting the value - * any PHP variable that casts to a float may be passed. */ -class Float extends Primitive implements FloatInterface { +class Float extends PrimitiveBase implements FloatInterface { } diff --git a/core/lib/Drupal/Core/TypedData/Type/FloatInterface.php b/core/lib/Drupal/Core/TypedData/Type/FloatInterface.php index 38724da..643fcbe 100644 --- a/core/lib/Drupal/Core/TypedData/Type/FloatInterface.php +++ b/core/lib/Drupal/Core/TypedData/Type/FloatInterface.php @@ -1,9 +1,20 @@ value) { + return new DateInterval('PT' . $this->value . 'S'); + } + } + + /** + * {@inheritdoc} + */ + public function setDuration(\DateInterval $duration) { + // Note that this applies the assumption of 12 month's a 30 days and + // each year having 365 days. There is no accurate conversion for time spans + // exceeding a day. + $this->value = ($duration->y * 365 * 24 * 60 * 60) + + ($duration->m * 30 * 24 * 60 * 60) + + ($duration->d * 24 * 60 * 60) + + ($duration->h * 60 * 60) + + ($duration->i * 60) + + $duration->s; + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Type/Timestamp.php b/core/lib/Drupal/Core/TypedData/Type/Timestamp.php index 584ee65..86ac13c 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Timestamp.php +++ b/core/lib/Drupal/Core/TypedData/Type/Timestamp.php @@ -8,7 +8,6 @@ namespace Drupal\Core\TypedData\Type; use Drupal\Core\Datetime\DrupalDateTime; -use Drupal\Core\TypedData\Primitive; use Drupal\Core\TypedData\Type\DateTimeInterface; /** diff --git a/core/lib/Drupal/Core/TypedData/Type/Uri.php b/core/lib/Drupal/Core/TypedData/Type/Uri.php index 1d42a1c..e10f23c 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Uri.php +++ b/core/lib/Drupal/Core/TypedData/Type/Uri.php @@ -7,14 +7,12 @@ namespace Drupal\Core\TypedData\Type; -use Drupal\Core\TypedData\Primitive; +use Drupal\Core\TypedData\PrimitiveBase; use Drupal\Core\TypedData\Type\UriInterface; /** * The URI data type. - * - * The plain value of a URI is an absolute URI represented as PHP string. */ -class Uri extends Primitive implements UriInterface { +class Uri extends PrimitiveBase implements UriInterface { } diff --git a/core/lib/Drupal/Core/TypedData/Type/UriInterface.php b/core/lib/Drupal/Core/TypedData/Type/UriInterface.php index 33af1dc..1034061 100644 --- a/core/lib/Drupal/Core/TypedData/Type/UriInterface.php +++ b/core/lib/Drupal/Core/TypedData/Type/UriInterface.php @@ -1,9 +1,19 @@ getConstraints($this); + return \Drupal::typedData()->getConstraints($this->definition); } /** diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php index e14ca46..0d9fce6 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php @@ -341,15 +341,14 @@ public function getValidationConstraintManager() { * Array of constraints, each being an instance of * \Symfony\Component\Validator\Constraint. */ - public function getConstraints($typed_data) { - $definition = $typed_data->getDefinition(); + public function getConstraints($definition) { $constraints = array(); $validation_manager = $this->getValidationConstraintManager(); $type_definition = $this->getDefinition($definition['type']); - // Auto-generate a constraint for the primitive type if we have a mapping. - if ($typed_data instanceof PrimitiveInterface) { - $constraints[] = $validation_manager->create('PrimitiveType', array('type' => $definition['type'], 'typed_data' => $typed_data)); + // Auto-generate a constraint data types implementing a primitive interface. + if (is_subclass_of($type_definition['class'], '\Drupal\Core\TypedData\PrimitiveInterface')) { + $constraints[] = $validation_manager->create('PrimitiveType', array()); } // Add in constraints specified by the data type. if (isset($type_definition['constraints'])) { diff --git a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php b/core/lib/Drupal/Core/TypedData/Validation/Metadata.php index 106f2d0..73bfc8b 100644 --- a/core/lib/Drupal/Core/TypedData/Validation/Metadata.php +++ b/core/lib/Drupal/Core/TypedData/Validation/Metadata.php @@ -91,4 +91,14 @@ public function getPropertyName() { public function getPropertyValue($container) { return $this->typedData->getValue(); } + + /** + * Returns the typed data object. + * + * @return \Drupal\Core\TypedData\TypedDataInterface + * The typed data object. + */ + public function getTypedData() { + return $this->typedData; + } } diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraint.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraint.php index e71e04a..41c7fee 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraint.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraint.php @@ -9,20 +9,17 @@ use Drupal\Component\Annotation\Plugin; use Drupal\Core\Annotation\Translation; -use Symfony\Component\Validator\Constraints\Type as SymfonyConstraint; +use Symfony\Component\Validator\Constraint; /** * Supports validating all primitive types. * - * @todo: Move this below the TypedData core component. - * * @Plugin( * id = "PrimitiveType", * label = @Translation("Primitive type", context = "Validation") * ) */ -class PrimitiveTypeConstraint extends SymfonyConstraint { +class PrimitiveTypeConstraint extends Constraint { - public $message = 'This value should be of type %type.'; - public $typed_data; + public $message = 'This value should be of the correct primitive type.'; } diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php index a1a2c0e..0547991 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php @@ -16,7 +16,6 @@ use Drupal\Core\TypedData\Type\IntegerInterface; use Drupal\Core\TypedData\Type\StringInterface; use Drupal\Core\TypedData\Type\UriInterface; -use Drupal\Core\Datetime\DrupalDateTime; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -34,49 +33,38 @@ public function validate($value, Constraint $constraint) { return; } - switch ($constraint->type) { - case 'binary': - $valid = is_resource($value); - break; - case 'boolean': - $valid = is_bool($value) || $value === 0 || $value === '0' || $value === 1 || $value == '1'; - break; - case 'datetime': - case 'timestamp': - $valid = FALSE; - debug($constraint->typed_data); - if ($date_time = $constraint->typed_data->getDateTime()) { - $valid = !$date_time->hasErrors(); - } - break; - case 'duration': - $valid = $value instanceof DateInterval; - break; - case 'float': - $valid = filter_var($value, FILTER_VALIDATE_FLOAT) !== FALSE; - break; - case 'integer': - $valid = filter_var($value, FILTER_VALIDATE_INT) !== FALSE; - break; - case 'string': - // @todo: What to do this with one? Remove default FALSE? - case 'email': - // PHP integers, floats or booleans are valid strings also, so we - // cannot use is_string() here. - $valid = is_scalar($value); - break; - case 'uri': - $valid = filter_var($value, FILTER_VALIDATE_URL) ; - break; - default: - $valid = FALSE; - break; + $typed_data = $this->context->getMetadata()->getTypedData(); + $valid = TRUE; + if ($typed_data instanceof BinaryInterface && !is_resource($value)) { + $valid = FALSE; + } + if ($typed_data instanceof BooleanInterface && !(is_bool($value) || $value === 0 || $value === '0' || $value === 1 || $value == '1')) { + $valid = FALSE; + } + if ($typed_data instanceof FloatInterface && filter_var($value, FILTER_VALIDATE_FLOAT) === FALSE) { + $valid = FALSE; + } + if ($typed_data instanceof IntegerInterface && filter_var($value, FILTER_VALIDATE_INT) === FALSE) { + $valid = FALSE; + } + if ($typed_data instanceof StringInterface && !is_scalar($value)) { + $valid = FALSE; + } + if ($typed_data instanceof UriInterface && filter_var($value, FILTER_VALIDATE_URL) === FALSE) { + $valid = FALSE; + } + // @todo: Move those to separate constraint validators. + if ($typed_data instanceof DateTimeInterface && $typed_data->getDateTime()->hasErrors()) { + $valid = FALSE; + } + if ($typed_data instanceof DurationInterface && !($typed_data->getDuration() instanceof DateInterval)) { + $valid = FALSE; } if (!$valid) { + // @todo: Provide a good violation message for each problem. $this->context->addViolation($constraint->message, array( - '%value' => is_object($value) ? get_class($value) : (is_array($value) ? 'Array' : (string) $value), - '%type' => $constraint->type, + '%value' => is_object($value) ? get_class($value) : (is_array($value) ? 'Array' : (string) $value) )); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php index c08eaff..851a476 100644 --- a/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/TypedData/TypedDataTest.php @@ -134,13 +134,13 @@ public function testGetAndSet() { // Date Time type. $value = '2014-01-01T20:00:00+00:00'; - $typed_data = $this->createTypedData(array('type' => 'datetime'), $value); + $typed_data = $this->createTypedData(array('type' => 'datetime_iso8601'), $value); $this->assertTrue($typed_data->getValue() == $value, 'Date value was fetched.'); - $this->assertEqual($typed_data->getValue(), $typed_data->getDateTime()->format('c'), 'Value representation of a date is ISO 8061'); + $this->assertEqual($typed_data->getValue(), $typed_data->getDateTime()->format('c'), 'Value representation of a date is ISO 8601'); $this->assertEqual($typed_data->validate()->count(), 0); $new_value = '2014-01-02T20:00:00+00:00'; $typed_data->setValue($new_value); - $this->assertTrue($typed_data->getDateTime()->format('c') === $new_value, 'Date value was changed and set by timestamp.'); + $this->assertTrue($typed_data->getDateTime()->format('c') === $new_value, 'Date value was changed and set by an ISO8601 date.'); $this->assertEqual($typed_data->validate()->count(), 0); $this->assertTrue($typed_data->getDateTime()->format('Y-m-d') == '2014-01-02', 'Date value was changed and set by date string.'); $this->assertEqual($typed_data->validate()->count(), 0); @@ -149,6 +149,13 @@ public function testGetAndSet() { $this->assertEqual($typed_data->validate()->count(), 0); $typed_data->setValue('invalid'); $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.'); + // Check implementation of DateTimeInterface. + $typed_data = $this->createTypedData(array('type' => 'datetime_iso8601'), '2014-01-01T20:00:00+00:00'); + $this->assertTrue($typed_data->getDateTime() instanceof DrupalDateTime); + $typed_data->setDateTime(new DrupalDateTime('2014-01-02T20:00:00+00:00')); + $this->assertEqual($typed_data->getValue(), '2014-01-02T20:00:00+00:00'); + $typed_data->setValue(NULL); + $this->assertNull($typed_data->getDateTime()); // Timestamp type. $value = REQUEST_TIME; @@ -164,29 +171,57 @@ public function testGetAndSet() { $this->assertEqual($typed_data->validate()->count(), 0); $typed_data->setValue('invalid'); $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.'); + // Check implementation of DateTimeInterface. + $typed_data = $this->createTypedData(array('type' => 'timestamp'), REQUEST_TIME); + $this->assertTrue($typed_data->getDateTime() instanceof DrupalDateTime); + $typed_data->setDateTime(new DrupalDateTime(REQUEST_TIME + 1)); + $this->assertEqual($typed_data->getValue(), REQUEST_TIME + 1); + $typed_data->setValue(NULL); + $this->assertNull($typed_data->getDateTime()); - // Duration type. - $value = new DateInterval('PT20S'); - $typed_data = $this->createTypedData(array('type' => 'duration'), $value); - $this->assertTrue($typed_data->getValue() === $value, 'Duration value was fetched.'); - $this->assertEqual($typed_data->validate()->count(), 0); - $typed_data->setValue(10); - $this->assertTrue($typed_data->getValue()->s == 10, 'Duration value was changed and set by time span in seconds.'); + // DurationIso8601 type. + $value = 'PT20S'; + $typed_data = $this->createTypedData(array('type' => 'duration_iso8601'), $value); + $this->assertTrue($typed_data->getValue() === $value, 'DurationIso8601 value was fetched.'); $this->assertEqual($typed_data->validate()->count(), 0); $typed_data->setValue('P40D'); - $this->assertTrue($typed_data->getValue()->d == 40, 'Duration value was changed and set by duration string.'); - $this->assertTrue(is_string($typed_data->getString()), 'Duration value was converted to string'); + $this->assertTrue($typed_data->getValue()->d == 40, 'DurationIso8601 value was changed and set by duration string.'); + $this->assertTrue(is_string($typed_data->getString()), 'DurationIso8601 value was converted to string'); $this->assertEqual($typed_data->validate()->count(), 0); - // Test getting the string and passing it back as value. - $duration = $typed_data->getString(); - $typed_data->setValue($duration); - $this->assertEqual($typed_data->getString(), $duration, 'Duration formatted as string can be used to set the duration value.'); + $typed_data->setValue(NULL); + $this->assertNull($typed_data->getValue(), 'DurationIso8601 wrapper is null-able.'); $this->assertEqual($typed_data->validate()->count(), 0); + $typed_data->setValue('invalid'); + $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.'); + // Check implementation of DurationInterface. + $typed_data = $this->createTypedData(array('type' => 'duration_iso8601'), 'PT20S'); + $this->assertTrue($typed_data->getDuration() instanceof DateInterval); + $typed_data->setDuration(new DateInterval('P40D')); + $this->assertEqual($typed_data->getValue(), 'P40D'); $typed_data->setValue(NULL); - $this->assertNull($typed_data->getValue(), 'Duration wrapper is null-able.'); + $this->assertNull($typed_data->getDuration()); + + // Time span type. + $value = new DateInterval(20); + $typed_data = $this->createTypedData(array('type' => 'timespan'), $value); + $this->assertTrue($typed_data->getValue() === $value, 'Time span value was fetched.'); + $this->assertEqual($typed_data->validate()->count(), 0); + $typed_data->setValue(60 * 60 * 4); + $this->assertTrue($typed_data->getDuration()->h == 4, 'Time span was changed'); + $this->assertTrue(is_string($typed_data->getString()), 'Time span value was converted to string'); + $this->assertEqual($typed_data->validate()->count(), 0); + $typed_data->setValue(NULL); + $this->assertNull($typed_data->getValue(), 'Time span wrapper is null-able.'); $this->assertEqual($typed_data->validate()->count(), 0); $typed_data->setValue('invalid'); $this->assertEqual($typed_data->validate()->count(), 1, 'Validation detected invalid value.'); + // Check implementation of DurationInterface. + $typed_data = $this->createTypedData(array('type' => 'timespan'), 20); + $this->assertTrue($typed_data->getDuration() instanceof DateInterval); + $typed_data->setDuration(new DateInterval('P40H')); + $this->assertEqual($typed_data->getValue(), 60 * 60 * 4); + $typed_data->setValue(NULL); + $this->assertNull($typed_data->getDuration()); // URI type. $uri = 'http://example.com/foo/'; diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 4bcc4d9..3eea1f2 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -9,7 +9,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Language\Language; use Drupal\Core\Utility\ModuleInfo; -use Drupal\Core\TypedData\Primitive; +use Drupal\Core\TypedData\PrimitiveBase; use Drupal\system\Plugin\Block\SystemMenuBlock; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -2182,7 +2182,7 @@ function system_data_type_info() { 'label' => t('Float'), 'class' => '\Drupal\Core\TypedData\Type\Float', ), - 'datetime' => array( + 'datetime_iso8601' => array( 'label' => t('Date'), 'class' => '\Drupal\Core\TypedData\Type\DateTimeIso8601', ), @@ -2190,9 +2190,13 @@ function system_data_type_info() { 'label' => t('Timestamp'), 'class' => '\Drupal\Core\TypedData\Type\Timestamp', ), - 'duration' => array( - 'label' => t('Duration'), - 'class' => '\Drupal\Core\TypedData\Type\Duration', + 'duration_iso8601' => array( + 'label' => t('Duration Iso8601'), + 'class' => '\Drupal\Core\TypedData\Type\DurationIso8601', + ), + 'timespan' => array( + 'label' => t('Time span in seconds'), + 'class' => '\Drupal\Core\TypedData\Type\TimeSpan', ), 'uri' => array( 'label' => t('URI'),