diff --git a/core/modules/hal/hal.info b/core/modules/hal/hal.info new file mode 100644 index 0000000..17c302e --- /dev/null +++ b/core/modules/hal/hal.info @@ -0,0 +1,5 @@ +name = HAL (Hypertext Application Language) +description = Serializes entities using HAL. +package = Core +core = 8.x +dependencies[] = serialization diff --git a/core/modules/hal/hal.module b/core/modules/hal/hal.module new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/core/modules/hal/hal.module @@ -0,0 +1 @@ +format; + } + +} diff --git a/core/modules/hal/lib/Drupal/hal/HalBundle.php b/core/modules/hal/lib/Drupal/hal/HalBundle.php new file mode 100644 index 0000000..ad817dd --- /dev/null +++ b/core/modules/hal/lib/Drupal/hal/HalBundle.php @@ -0,0 +1,44 @@ +register('serializer.normalizer.entity_reference_item.hal', 'Drupal\hal\Normalizer\EntityReferenceItemNormalizer') + ->addTag('normalizer', array('priority' => $priority)); + $container->register('serializer.normalizer.field_item.hal', 'Drupal\hal\Normalizer\FieldItemNormalizer') + ->addTag('normalizer', array('priority' => $priority)); + $container->register('serializer.normalizer.field.hal', 'Drupal\hal\Normalizer\FieldNormalizer') + ->addTag('normalizer', array('priority' => $priority)); + $container->register('serializer.normalizer.entity.hal', 'Drupal\hal\Normalizer\EntityNormalizer') + ->addTag('normalizer', array('priority' => $priority)); + + $container->register('serializer.encoder.hal', 'Drupal\hal\Encoder\JsonEncoder') + ->addTag('encoder', array( + 'priority' => $priority, + 'format' => array( + 'hal_json' => 'HAL (JSON)', + ), + )); + + $container->register('hal.subscriber', 'Drupal\hal\HalSubscriber') + ->addTag('event_subscriber'); + } +} diff --git a/core/modules/hal/lib/Drupal/hal/HalSubscriber.php b/core/modules/hal/lib/Drupal/hal/HalSubscriber.php new file mode 100644 index 0000000..93a70bb --- /dev/null +++ b/core/modules/hal/lib/Drupal/hal/HalSubscriber.php @@ -0,0 +1,41 @@ +getRequest(); + $request->setFormat('hal_json', 'application/hal+json'); + } + + /** + * Registers the methods in this class that should be listeners. + * + * @return array + * An array of event listener definitions. + */ + static function getSubscribedEvents() { + $events[KernelEvents::REQUEST][] = array('onKernelRequest', 40); + return $events; + } + +} diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php new file mode 100644 index 0000000..34c769f --- /dev/null +++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php @@ -0,0 +1,60 @@ + array( + 'curies' => array( + array( + // @todo Make this configurable. + 'href' => url('relations'), + 'name' => 'site', + 'templated' => TRUE, + ), + ), + 'self' => array( + 'href' => $entity_wrapper->getUri(), + ), + 'site:type' => array( + 'href' => $entity_wrapper->getTypeUri(), + ), + ), + ); + + $properties = $entity->getProperties(); + foreach ($properties as $property) { + $normalized_property = $this->serializer->normalize($property, $format, $context); + $normalized = NestedArray::mergeDeep($normalized, $normalized_property); + } + + return $normalized; + } + +} diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/EntityReferenceItemNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityReferenceItemNormalizer.php new file mode 100644 index 0000000..954322f --- /dev/null +++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityReferenceItemNormalizer.php @@ -0,0 +1,52 @@ +get('entity')->getValue(); + $field_name = $field_item->getParent()->getName(); + $entity_wrapper = new EntityWrapper($target_entity); + + $values = array( + 'uuid' => $entity_wrapper->getUuid(), + ); + $link = array( + 'href' => $entity_wrapper->getUri(), + ); + if (isset($context['langcode'])) { + $values['lang'] = $link['lang'] = $context['langcode']; + } + + // This structure will be recursively merged into the normalized entity so + // that the links are properly added to the _links object. + return array( + '_links' => array( + "site:$field_name" => array($link), + ), + $field_name => array($values), + ); + } + +} diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/EntityWrapper.php b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityWrapper.php new file mode 100644 index 0000000..841b443 --- /dev/null +++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityWrapper.php @@ -0,0 +1,58 @@ +entity = $entity; + } + + public function getUri() { + // @todo Remove this conditional once entities are converted to EntityNG. + if ($this->entity instanceof EntityNG) { + $uri_info = $this->entity->uri(); + return url($uri_info['path']); + } + } + + public function getTypeUri() { + $entity_type = $this->entity->entityType(); + $bundle = $this->entity->bundle(); + return url("types/$entity_type/$bundle"); + } + + public function getUuid() { + // @todo Remove this conditional once entities are converted to EntityNG. + if ($this->entity instanceof EntityNG) { + return $this->entity->uuid(); + } + } + +} diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php new file mode 100644 index 0000000..b06a90f --- /dev/null +++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php @@ -0,0 +1,41 @@ +getPropertyValues(); + if (isset($context['langcode'])) { + $values['lang'] = $context['langcode']; + } + + // The values are wrapped in an array, and then wrapped in another array + // keyed by field name so that field items can be merged by the + // FieldNormalizer. This is necessary for the EntityReferenceItemNormalizer + // to be able to place values in the '_links' array. + $field = $field_item->getParent(); + return array( + $field->getName() => array($values), + ); + } + +} diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php new file mode 100644 index 0000000..e6e3777 --- /dev/null +++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php @@ -0,0 +1,66 @@ +getParent(); + $field_name = $field->getName(); + $field_definition = $entity->getPropertyDefinition($field_name); + + // If this field is not translatable, it can simply be normalized without + // separating it into different translations. + if (empty($field_definition['translatable'])) { + $normalized_field_items = $this->normalizeField($field, $format, $context); + } + // Otherwise, the languages have to be extracted from the entity and passed + // in to the field item normalizer in the context. The langcode is appended + // to the field item values. + else { + foreach ($entity->getTranslationLanguages() as $lang) { + $context['langcode'] = $lang->langcode == 'und' ? LANGUAGE_DEFAULT : $lang->langcode; + $translation = $entity->getTranslation($lang->langcode); + $translated_field = $translation->get($field_name); + $normalized_field_items = array_merge($normalized_field_items, $this->normalizeField($translated_field, $format, $context)); + } + } + + // Merge deep so that links set in entity reference normalizers are merged + // into the links property. + $normalized = NestedArray::mergeDeepArray($normalized_field_items); + return $normalized; + } + + protected function normalizeField($field, $format, $context) { + $normalized_field_items = array(); + if (!$field->isEmpty()) { + foreach ($field as $field_item) { + $normalized_field_items[] = $this->serializer->normalize($field_item, $format, $context); + } + } + return $normalized_field_items; + } + +} diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/NormalizerBase.php b/core/modules/hal/lib/Drupal/hal/Normalizer/NormalizerBase.php new file mode 100644 index 0000000..8a42ff3 --- /dev/null +++ b/core/modules/hal/lib/Drupal/hal/Normalizer/NormalizerBase.php @@ -0,0 +1,31 @@ +formats) && parent::supportsNormalization($data, $format); + } + +} diff --git a/core/modules/hal/lib/Drupal/hal/Tests/NormalizeTest.php b/core/modules/hal/lib/Drupal/hal/Tests/NormalizeTest.php new file mode 100644 index 0000000..61819f5 --- /dev/null +++ b/core/modules/hal/lib/Drupal/hal/Tests/NormalizeTest.php @@ -0,0 +1,153 @@ + 'Normalize Test', + 'description' => 'Test that entities can be normalized in HAL.', + 'group' => 'HAL', + ); + } + + /** + * Tests the normalize function. + */ + public function testNormalize() { + $target_entity_de = entity_create('entity_test', (array('field_test_entity_reference' => NULL))); + $target_entity_de->save(); + $target_entity_en = entity_create('entity_test', (array('field_test_entity_reference' => NULL))); + $target_entity_en->save(); + + // Create a German entity. + $values = array( + 'langcode' => 'de', + 'name' => $this->randomName(), + 'user_id' => 1, + 'field_test_text' => array( + 'value' => $this->randomName(), + 'format' => 'full_html', + ), + 'field_test_entity_reference' => array( + 'target_id' => $target_entity_de->id(), + ), + ); + // Array of translated values. + $translation_values = array( + 'name' => $this->randomName(), + 'field_test_entity_reference' => array( + 'target_id' => $target_entity_en->id(), + ) + ); + + $entity = entity_create('entity_test', $values); + $entity->save(); + // Add an English value for name and entity reference properties. + $entity->getTranslation('en')->set('name', array(0 => array('value' => $translation_values['name']))); + $entity->getTranslation('en')->set('field_test_entity_reference', array(0 => $translation_values['field_test_entity_reference'])); + $entity->save(); + + $expected_array = array( + '_links' => array( + 'curies' => array( + array( + 'href' => '/relations', + 'name' => 'site', + 'templated' => true, + ), + ), + 'self' => array( + 'href' => $this->getUri($entity), + ), + 'site:type' => array( + 'href' => '/types/entity_test/entity_test', + ), + 'site:user_id' => array( + array( + 'href' => NULL, + 'lang' => 'de', + ), + ), + 'site:field_test_entity_reference' => array( + array( + 'href' => $this->getUri($target_entity_de), + 'lang' => 'de', + ), + array( + 'href' => $this->getUri($target_entity_en), + 'lang' => 'en', + ), + ), + ), + 'uuid' => array( + array( + 'value' => $entity->uuid(), + ), + ), + 'langcode' => array( + array( + 'value' => 'de', + ), + ), + 'name' => array( + array( + 'value' => $values['name'], + 'lang' => 'de', + ), + array( + 'value' => $translation_values['name'], + 'lang' => 'en', + ), + ), + 'user_id' => array( + array( + 'uuid' => NULL, + 'lang' => 'de', + ), + ), + 'field_test_text' => array( + array( + 'value' => $values['field_test_text']['value'], + 'format' => $values['field_test_text']['format'], + ), + ), + 'field_test_entity_reference' => array( + array( + 'uuid' => $target_entity_de->uuid(), + 'lang' => 'de', + ), + array( + 'uuid' => $target_entity_en->uuid(), + 'lang' => 'en', + ), + ), + ); + + $normalized = $this->container->get('serializer')->normalize($entity, $this->format); + $this->assertEqual($normalized['_links']['self'], $expected_array['_links']['self'], 'self link placed correctly.'); + // @todo Test curies. + // @todo Test type. + $this->assertFalse(isset($expected_array['id']), 'Internal id is not exposed.'); + $this->assertEqual($normalized['uuid'], $expected_array['uuid'], 'Non-translatable fields is normalized.'); + $this->assertEqual($normalized['name'], $expected_array['name'], 'Translatable field with multiple language values is normalized.'); + $this->assertEqual($normalized['field_test_text'], $expected_array['field_test_text'], 'Field with properties is normalized.'); + $this->assertEqual($normalized['field_test_entity_reference'], $expected_array['field_test_entity_reference'], 'Entity reference field is normalized.'); + $this->assertEqual($normalized['_links']['site:field_test_entity_reference'], $expected_array['_links']['site:field_test_entity_reference'], 'Links are added for entity reference field.'); + } + + protected function getUri($entity) { + $entity_uri_info = $entity->uri(); + return url($entity_uri_info['path']); + } + +} diff --git a/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php new file mode 100644 index 0000000..691a05c --- /dev/null +++ b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php @@ -0,0 +1,80 @@ +installSchema('system', 'variable'); + $this->installSchema('system', 'url_alias'); + $this->installSchema('field', 'field_config'); + $this->installSchema('field', 'field_config_instance'); + + // English must be added before entity_test is enabled. + $this->enableModules(array('language')); + $english = new Language(array( + 'langcode' => 'en', + 'name' => 'English', + )); + language_save($english); + // Add German as a language. + $german = new Language(array( + 'langcode' => 'de', + 'name' => 'Deutsch', + )); + language_save($german); + + $this->enableModules(array('entity_test', 'hal', 'user')); + + // Create the test field. + $field = array( + 'translatable' => TRUE, + 'settings' => array( + 'target_type' => 'entity_test', + ), + 'field_name' => 'field_test_entity_reference', + 'type' => 'entity_reference', + ); + field_create_field($field); + + // Create the test field instance. + $instance = array( + 'entity_type' => 'entity_test', + 'field_name' => 'field_test_entity_reference', + 'bundle' => 'entity_test', + ); + field_create_instance($instance); + } + +} diff --git a/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityNormalizer.php b/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityNormalizer.php index ab662b3..14e7e00 100644 --- a/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityNormalizer.php +++ b/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityNormalizer.php @@ -22,7 +22,7 @@ class JsonldEntityNormalizer extends JsonldNormalizerBase implements Denormalize * * @var string */ - protected static $supportedInterfaceOrClass = 'Drupal\Core\Entity\EntityInterface'; + protected $supportedInterfaceOrClass = 'Drupal\Core\Entity\EntityInterface'; /** * Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize() diff --git a/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityReferenceNormalizer.php b/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityReferenceNormalizer.php index f57066d..28f47ef 100644 --- a/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityReferenceNormalizer.php +++ b/core/modules/jsonld/lib/Drupal/jsonld/JsonldEntityReferenceNormalizer.php @@ -25,7 +25,7 @@ class JsonldEntityReferenceNormalizer extends JsonldNormalizerBase implements De * * @var string */ - protected static $supportedInterfaceOrClass = 'Drupal\Core\Entity\Field\Type\EntityReferenceItem'; + protected $supportedInterfaceOrClass = 'Drupal\Core\Entity\Field\Type\EntityReferenceItem'; /** * Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize() @@ -55,7 +55,7 @@ public function denormalize($data, $class, $format = null, array $context = arra */ public function supportsDenormalization($data, $type, $format = NULL) { $reflection = new ReflectionClass($type); - return in_array($format, static::$format) && ($reflection->getName() == static::$supportedInterfaceOrClass || $reflection->isSubclassOf(static::$supportedInterfaceOrClass)); + return in_array($format, static::$format) && ($reflection->getName() == $this->supportedInterfaceOrClass || $reflection->isSubclassOf($this->supportedInterfaceOrClass)); } } diff --git a/core/modules/jsonld/lib/Drupal/jsonld/JsonldFieldItemNormalizer.php b/core/modules/jsonld/lib/Drupal/jsonld/JsonldFieldItemNormalizer.php index 7e3d124..1f1a1bb 100644 --- a/core/modules/jsonld/lib/Drupal/jsonld/JsonldFieldItemNormalizer.php +++ b/core/modules/jsonld/lib/Drupal/jsonld/JsonldFieldItemNormalizer.php @@ -22,7 +22,7 @@ class JsonldFieldItemNormalizer extends JsonldNormalizerBase implements Denormal * * @var string */ - protected static $supportedInterfaceOrClass = 'Drupal\Core\Entity\Field\FieldItemInterface'; + protected $supportedInterfaceOrClass = 'Drupal\Core\Entity\Field\FieldItemInterface'; /** * Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize() diff --git a/core/modules/jsonld/lib/Drupal/jsonld/JsonldNormalizerBase.php b/core/modules/jsonld/lib/Drupal/jsonld/JsonldNormalizerBase.php index e898da8..61741ee 100644 --- a/core/modules/jsonld/lib/Drupal/jsonld/JsonldNormalizerBase.php +++ b/core/modules/jsonld/lib/Drupal/jsonld/JsonldNormalizerBase.php @@ -23,7 +23,7 @@ * * @var string */ - protected static $supportedInterfaceOrClass; + protected $supportedInterfaceOrClass; /** * The formats that this Normalizer supports. @@ -63,7 +63,7 @@ public function __construct(SiteSchemaManager $site_schema_manager, RdfMappingMa * Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize() */ public function supportsNormalization($data, $format = NULL) { - return is_object($data) && in_array($format, static::$format) && ($data instanceof static::$supportedInterfaceOrClass); + return is_object($data) && in_array($format, static::$format) && ($data instanceof $this->supportedInterfaceOrClass); } /** @@ -75,6 +75,6 @@ public function supportsNormalization($data, $format = NULL) { */ public function supportsDenormalization($data, $type, $format = NULL) { $reflection = new ReflectionClass($type); - return in_array($format, static::$format) && $reflection->implementsInterface(static::$supportedInterfaceOrClass); + return in_array($format, static::$format) && $reflection->implementsInterface($this->supportedInterfaceOrClass); } } diff --git a/core/modules/jsonld/lib/Drupal/jsonld/JsonldRdfSchemaNormalizer.php b/core/modules/jsonld/lib/Drupal/jsonld/JsonldRdfSchemaNormalizer.php index 9c63332..9315a58 100644 --- a/core/modules/jsonld/lib/Drupal/jsonld/JsonldRdfSchemaNormalizer.php +++ b/core/modules/jsonld/lib/Drupal/jsonld/JsonldRdfSchemaNormalizer.php @@ -20,7 +20,7 @@ class JsonldRdfSchemaNormalizer extends JsonldNormalizerBase { * * @var string */ - protected static $supportedInterfaceOrClass = 'Drupal\rdf\SiteSchema\SchemaTermBase'; + protected $supportedInterfaceOrClass = 'Drupal\rdf\SiteSchema\SchemaTermBase'; /** * Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize() diff --git a/core/modules/serialization/lib/Drupal/serialization/Normalizer/ComplexDataNormalizer.php b/core/modules/serialization/lib/Drupal/serialization/Normalizer/ComplexDataNormalizer.php index 456cc0e..9d99d5d 100644 --- a/core/modules/serialization/lib/Drupal/serialization/Normalizer/ComplexDataNormalizer.php +++ b/core/modules/serialization/lib/Drupal/serialization/Normalizer/ComplexDataNormalizer.php @@ -26,7 +26,7 @@ class ComplexDataNormalizer extends NormalizerBase { * * @var string */ - protected static $supportedInterfaceOrClass = 'Drupal\Core\TypedData\ComplexDataInterface'; + protected $supportedInterfaceOrClass = 'Drupal\Core\TypedData\ComplexDataInterface'; /** * Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize(). diff --git a/core/modules/serialization/lib/Drupal/serialization/Normalizer/ListNormalizer.php b/core/modules/serialization/lib/Drupal/serialization/Normalizer/ListNormalizer.php index 32c3f68..4fc2d00 100644 --- a/core/modules/serialization/lib/Drupal/serialization/Normalizer/ListNormalizer.php +++ b/core/modules/serialization/lib/Drupal/serialization/Normalizer/ListNormalizer.php @@ -25,7 +25,7 @@ class ListNormalizer extends NormalizerBase { * * @var string */ - protected static $supportedInterfaceOrClass = 'Drupal\Core\TypedData\ListInterface'; + protected $supportedInterfaceOrClass = 'Drupal\Core\TypedData\ListInterface'; /** * Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize(). diff --git a/core/modules/serialization/lib/Drupal/serialization/Normalizer/NormalizerBase.php b/core/modules/serialization/lib/Drupal/serialization/Normalizer/NormalizerBase.php index c30cd3e..cb50949 100644 --- a/core/modules/serialization/lib/Drupal/serialization/Normalizer/NormalizerBase.php +++ b/core/modules/serialization/lib/Drupal/serialization/Normalizer/NormalizerBase.php @@ -21,13 +21,13 @@ * * @var string */ - protected static $supportedInterfaceOrClass; + protected $supportedInterfaceOrClass; /** * Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::supportsNormalization(). */ public function supportsNormalization($data, $format = NULL) { - return is_object($data) && ($data instanceof static::$supportedInterfaceOrClass); + return is_object($data) && ($data instanceof $this->supportedInterfaceOrClass); } } diff --git a/core/modules/serialization/lib/Drupal/serialization/Normalizer/TypedDataNormalizer.php b/core/modules/serialization/lib/Drupal/serialization/Normalizer/TypedDataNormalizer.php index 38e9505..69f9aeb 100644 --- a/core/modules/serialization/lib/Drupal/serialization/Normalizer/TypedDataNormalizer.php +++ b/core/modules/serialization/lib/Drupal/serialization/Normalizer/TypedDataNormalizer.php @@ -19,7 +19,7 @@ class TypedDataNormalizer extends NormalizerBase { * * @var string */ - protected static $supportedInterfaceOrClass = 'Drupal\Core\TypedData\TypedDataInterface'; + protected $supportedInterfaceOrClass = 'Drupal\Core\TypedData\TypedDataInterface'; /** * Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize().