diff -u b/core/misc/message.js b/core/misc/message.js --- b/core/misc/message.js +++ b/core/misc/message.js @@ -12,7 +12,8 @@ var messageWrapperSelector = '[data-drupal-messages]'; /** - * Builds a div element with the aria-live attribute and attaches it to the DOM. + * Builds a div element with the aria-live attribute and attaches it to the + * DOM. */ Drupal.behaviors.drupalMessage = { attach: function () { @@ -62,8 +63,8 @@ */ Drupal.message.add = function (message, type, context) { if (typeof message === 'string') { - // Save the text and priority into a closure variable. Multiple simultaneous - // announcements will be concatenated and read in sequence. + // Save the text and priority into a closure variable. Multiple + // simultaneous announcements will be concatenated and read in sequence. messages.push({ context: context || 'default', text: message, only in patch2: unchanged: --- /dev/null +++ b/core/modules/hal/src/Normalizer/TimestampItemNormalizer.php @@ -0,0 +1,31 @@ +processNormalizedValues($normalized); + } + +} only in patch2: unchanged: --- /dev/null +++ b/core/modules/rest/tests/src/Functional/BcTimestampNormalizerUnixTestTrait.php @@ -0,0 +1,44 @@ +config('serialization.settings')->get('bc_timestamp_normalizer_unix')) { + return ['value' => $timestamp]; + } + + // Otherwise, format the date string to the same that + // \Drupal\serialization\Normalizer\TimestampItemNormalizer will produce. + $date = new \DateTime(); + $date->setTimestamp($timestamp); + $date->setTimezone(new \DateTimeZone('UTC')); + + // Format is also added to the expected return values. + return [ + 'value' => $date->format(\DateTime::RFC3339), + 'format' => \DateTime::RFC3339, + ]; + } + +} only in patch2: unchanged: --- /dev/null +++ b/core/modules/serialization/src/Normalizer/TimeStampItemNormalizerTrait.php @@ -0,0 +1,85 @@ + 'U', + 'ISO 8601' => \DateTime::ISO8601, + 'RFC 3339' => \DateTime::RFC3339, + ]; + + /** + * Processes normalized timestamp values to add a formatted date and format. + * + * @param array $data + * @return array + */ + protected function processNormalizedValues(array $data) { + // Use a RFC 3339 timestamp with the time zone set to UTC to replace the + // timestamp value. + $date = new \DateTime(); + $date->setTimestamp($data['value']); + $date->setTimezone(new \DateTimeZone('UTC')); + $data['value'] = $date->format(\DateTime::RFC3339); + // 'format' is not a property on TimestampItem fields. This is present to + // assist consumers of this data. + $data['format'] = \DateTime::RFC3339; + + return $data; + } + + /** + * {@inheritdoc} + */ + protected function constructValue($data, $context) { + // Loop through the allowed formats and create a TimestampItem from the + // input data if it matches the defined pattern. Since the formats are + // unambiguous (i.e., they reference an absolute time with a defined time + // zone), only one will ever match. + $timezone = new \DateTimeZone('UTC'); + + // First check for a provided format. + if (!empty($data['format']) && in_array($data['format'], $this->allowedFormats)) { + $date = \DateTime::createFromFormat($data['format'], $data['value'], $timezone); + return ['value' => $date->getTimestamp()]; + } + // Otherwise, loop through formats. + else { + foreach ($this->allowedFormats as $format) { + if (($date = \DateTime::createFromFormat($format, $data['value'], $timezone)) !== FALSE) { + return ['value' => $date->getTimestamp()]; + } + } + } + + $format_strings = []; + + foreach ($this->allowedFormats as $label => $format) { + $format_strings[] = "\"$format\" ($label)"; + } + + $formats = implode(', ', $format_strings); + throw new UnexpectedValueException(sprintf('The specified date "%s" is not in an accepted format: %s.', $data['value'], $formats)); + } + +} only in patch2: unchanged: --- /dev/null +++ b/core/modules/serialization/src/Normalizer/TimestampItemNormalizer.php @@ -0,0 +1,44 @@ +processNormalizedValues($data); + } + + /** + * {@inheritdoc} + */ + public function denormalize($data, $class, $format = NULL, array $context = []) { + if (empty($data['value'])) { + throw new InvalidArgumentException('No "value" attribute present'); + } + + return parent::denormalize($data, $class, $format, $context); + } + + +} only in patch2: unchanged: --- /dev/null +++ b/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php @@ -0,0 +1,146 @@ +normalizer = new TimestampItemNormalizer(); + } + + /** + * @covers ::supportsNormalization + */ + public function testSupportsNormalization() { + $timestamp_item = $this->createTimestampItemProphecy(); + $this->assertTrue($this->normalizer->supportsNormalization($timestamp_item->reveal())); + + $entity_ref_item = $this->prophesize(EntityReferenceItem::class); + $this->assertFalse($this->normalizer->supportsNormalization($entity_ref_item->reveal())); + } + + /** + * @covers ::supportsDenormalization + */ + public function testSupportsDenormalization() { + $timestamp_item = $this->createTimestampItemProphecy(); + $this->assertTrue($this->normalizer->supportsDenormalization($timestamp_item->reveal(), TimestampItem::class)); + + $entity_ref_item = $this->prophesize(EntityReferenceItem::class); + $this->assertFalse($this->normalizer->supportsNormalization($entity_ref_item->reveal(), TimestampItem::class)); + } + + /** + * Tests the normalize function. + * + * @covers ::normalize + */ + public function testNormalize() { + $expected = ['value' => '2016-11-06T09:02:00+00:00', 'format' => \DateTime::RFC3339]; + + $timestamp_item = $this->createTimestampItemProphecy(); + $timestamp_item->getIterator() + ->willReturn(new \ArrayIterator(['value' => 1478422920])); + + $serializer = new Serializer(); + $this->normalizer->setSerializer($serializer); + + $normalized = $this->normalizer->normalize($timestamp_item->reveal()); + $this->assertSame($expected, $normalized); + } + + /** + * Tests the denormalize function with good data. + * + * @covers ::denormalize + * @dataProvider providerTestDenormalizeValidFormats + */ + public function testDenormalizeValidFormats($value, $expected) { + $normalized = ['value' => $value]; + + $timestamp_item = $this->createTimestampItemProphecy(); + // The field item should be set with the expected timestamp. + $timestamp_item->setValue(['value' => $expected]) + ->shouldBeCalled(); + + $context = ['target_instance' => $timestamp_item->reveal()]; + + $denormalized = $this->normalizer->denormalize($normalized, TimestampItem::class, NULL, $context); + $this->assertTrue($denormalized instanceof TimestampItem); + } + + /** + * Data provider for testDenormalizeValidFormats. + * + * @return array + */ + public function providerTestDenormalizeValidFormats() { + $expected_stamp = 1478422920; + + $data = []; + + $data['U'] = [$expected_stamp, $expected_stamp]; + $data['RFC3339'] = ['2016-11-06T09:02:00+00:00', $expected_stamp]; + $data['ISO8601'] = ['2016-11-06T09:02:00+0000', $expected_stamp]; + + return $data; + } + + /** + * Tests the denormalize function with bad data. + * + * @covers ::denormalize + */ + public function testDenormalizeException() { + $this->setExpectedException(UnexpectedValueException::class, 'The specified date "2016/11/06 09:02am GMT" is not in an accepted format: "U" (UNIX timestamp), "Y-m-d\TH:i:sO" (ISO 8601), "Y-m-d\TH:i:sP" (RFC 3339).'); + + $context = ['target_instance' => $this->createTimestampItemProphecy()->reveal()]; + + $normalized = ['value' => '2016/11/06 09:02am GMT']; + $this->normalizer->denormalize($normalized, TimestampItem::class, NULL, $context); + } + + /** + * Creates a Timestamp Item prophecy. + * + * @return \Prophecy\Prophecy\ObjectProphecy + */ + protected function createTimestampItemProphecy() { + $timestamp_item = $this->prophesize(TimestampItem::class); + $timestamp_item->getParent() + ->willReturn(TRUE); + + return $timestamp_item; + } + +}