diff --git a/metatag.module b/metatag.module index 78e9f5e..229e6bb 100644 --- a/metatag.module +++ b/metatag.module @@ -12,6 +12,7 @@ use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; @@ -575,6 +576,13 @@ function metatag_entity_base_field_info(EntityTypeInterface $entity_type) { ->setComputed(TRUE) ->setTranslatable(TRUE) ->setTargetEntityTypeId($entity_type->id()); + + $fields['metatag_normalized'] = BaseFieldDefinition::create('metatag_normalized') + ->setLabel(t('Metatags normalized')) + ->setDescription(t('The meta tags for the entity.')) + ->setComputed(TRUE) + ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) + ->setTranslatable(TRUE); } return $fields; diff --git a/src/Plugin/Field/FieldType/MetatagNormalizedFieldItem.php b/src/Plugin/Field/FieldType/MetatagNormalizedFieldItem.php new file mode 100644 index 0000000..75ac950 --- /dev/null +++ b/src/Plugin/Field/FieldType/MetatagNormalizedFieldItem.php @@ -0,0 +1,50 @@ +setLabel(t('Tag')) + ->setRequired(TRUE); + $properties['attributes'] = DataDefinition::create('any') + ->setLabel(t('Name')) + ->setRequired(TRUE); + return $properties; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $value = $this->get('attributes')->getValue(); + return $value === NULL || $value === serialize([]); + } + +} diff --git a/src/Plugin/Field/FieldType/MetatagNormalizedFieldItemList.php b/src/Plugin/Field/FieldType/MetatagNormalizedFieldItemList.php new file mode 100644 index 0000000..827a75b --- /dev/null +++ b/src/Plugin/Field/FieldType/MetatagNormalizedFieldItemList.php @@ -0,0 +1,57 @@ +getEntity()->isNew() && !$this->metatagsGenerated; + } + + /** + * {@inheritdoc} + */ + protected function computeValue() { + $entity = $this->getEntity(); + if ($entity->isNew()) { + return; + } + /** @var \Drupal\metatag\MetatagManagerInterface $metatag_manager */ + $metatag_manager = \Drupal::service('metatag.manager'); + $metatags_for_entity = $metatag_manager->tagsFromEntityWithDefaults($entity); + $tags = $metatag_manager->generateRawElements($metatags_for_entity, $entity); + $this->list = []; + $offset = 0; + foreach ($tags as $tag) { + $item = [ + 'tag' => $tag['#tag'], + 'attributes' => $tag['#attributes'], + ]; + $this->list[] = $this->createItem($offset, $item); + $offset++; + } + + $this->metatagsGenerated = TRUE; + } + +} diff --git a/src/TypedData/ComputedItemListTrait.php b/src/TypedData/ComputedItemListTrait.php new file mode 100644 index 0000000..21cf66f --- /dev/null +++ b/src/TypedData/ComputedItemListTrait.php @@ -0,0 +1,46 @@ +valueComputed === FALSE || $this->valueNeedsRecomputing()) { + $this->computeValue(); + $this->valueComputed = TRUE; + } + } + + /** + * Returns whether the value should be recomputed. + * + * This is run after the value has been computed at least once. + * + * @return bool + */ + abstract protected function valueNeedsRecomputing(); + +} diff --git a/tests/src/Functional/EntityTestMetatagTest.php b/tests/src/Functional/EntityTestMetatagTest.php new file mode 100644 index 0000000..b470458 --- /dev/null +++ b/tests/src/Functional/EntityTestMetatagTest.php @@ -0,0 +1,175 @@ +container->get('config.factory'); + $config_factory + ->getEditable('metatag.metatag_defaults.global') + ->set('tags.title', 'Global Title') + ->set('tags.description', 'Global description') + ->set('tags.keywords', 'drupal8, testing, jsonapi, metatag') + ->save(); + + // The global default canonical URL is [current-page:url] which returns the + // endpoint URL on a REST request, so be sure to set a default canonical URL + // for the entity_test entity type. + MetatagDefaults::create([ + 'id' => 'entity_test', + 'tags' => [ + 'title' => '[entity_test:name] | [site:name]', + 'canonical_url' => '[entity_test:url]', + ], + ])->save(); + } + + /** + * {@inheritdoc} + */ + protected function createEntity() { + // Add the fields here rather than in ::setUp() because they need to be + // created before the entity is, and this method is called from + // parent::setUp(). + if (!$this->addedFields) { + $this->addedFields = TRUE; + + FieldStorageConfig::create([ + 'entity_type' => 'entity_test', + 'field_name' => 'field_metatag', + 'type' => 'metatag', + ])->save(); + + FieldConfig::create([ + 'entity_type' => 'entity_test', + 'field_name' => 'field_metatag', + 'bundle' => 'entity_test', + ])->save(); + } + + $entity_test = EntityTest::create([ + 'name' => 'Llama', + 'type' => 'entity_test', + 'field_metatag' => [ + 'value' => [ + 'description' => 'This is a description for use in Search Engines' + ], + ], + ]); + $entity_test->setOwnerId(0); + $entity_test->save(); + + return $entity_test; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedNormalizedEntity() { + $metatags = [ + 'description' => 'This is a description for use in Search Engines', + ]; + // When bc_primitives_as_strings is 1 the field of type metatag is + // normalized by \Drupal\serialization\Normalizer\TypedDataNormalizer which + // just outputs the serialized string. Otherwise it will use + // \Drupal\serialization\Normalizer\PrimitiveDataNormalizer which + // unserializes the values on normalization. + if ($this->config('serialization.settings')->get('bc_primitives_as_strings')) { + $metatags = serialize($metatags); + } + $canonical_url = base_path() . 'entity_test/' . $this->entity->id(); + return parent::getExpectedNormalizedEntity() + [ + 'field_metatag' => [ + [ + 'value' => $metatags, + ], + ], + 'metatag' => [ + 'value' => [ + 'canonical_url' => $canonical_url, + 'keywords' => 'drupal8, testing, jsonapi, metatag', + 'description' => 'This is a description for use in Search Engines', + 'title' => 'Llama | Drupal', + ], + ], + 'metatag_normalized' => [ + [ + 'tag' => 'meta', + 'attributes' => [ + 'name' => 'title', + 'content' => 'Llama | Drupal', + ], + ], + [ + 'tag' => 'link', + 'attributes' => [ + 'rel' => 'canonical', + 'href' => $canonical_url, + ], + ], + [ + 'tag' => 'meta', + 'attributes' => [ + 'name' => 'description', + 'content' => 'This is a description for use in Search Engines', + ], + ], + [ + 'tag' => 'meta', + 'attributes' => [ + 'name' => 'keywords', + 'content' => 'drupal8, testing, jsonapi, metatag', + ], + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedCacheTags() { + return Cache::mergeTags(parent::getExpectedCacheTags(), ['config:system.site']); + } + +}