Commit 54762856 authored by Damien McKenna's avatar Damien McKenna
Browse files

Issue #2901039 by rcodina, tsplash, DamienMcKenna, Wannes DR, mErilainen,...

Issue #2901039 by rcodina, tsplash, DamienMcKenna, Wannes DR, mErilainen, weekbeforenext, natemow, mpp, borisson_, AMDandy, jenniferaube, klmd302: Add a TypedData plugin to support Search API.
parent 6f313092
Loading
Loading
Loading
Loading
+3 −0
Changes for CHANGELOG.txt: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -27,6 +27,9 @@ Metatag 8.x-1.x-dev, 2022-xx-xx
#2767107 by DamienMcKenna, Antares89: Add support for the Diff module.
#3311343 by Anybody, DamienMcKenna: Metatag routes misses package name SEO in
  info.yml.
#2901039 by rcodina, tsplash, DamienMcKenna, Wannes DR, mErilainen,
  weekbeforenext, natemow, mpp, borisson_, AMDandy, jenniferaube, klmd302: Add
  a TypedData plugin to support Search API.


Metatag 8.x-1.21, 2022-07-16
+42 −0
Changes for README.md: 42 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -80,6 +80,10 @@ The primary features include:
* Meta tags specific to Facebook are included in the "Metatag: Facebook"
  submodule.

* Individual meta tags can be added to the [Search
  API](https://www.drupal.org/project/search_api) index if a Metatag field is
  added to the entity bundles; see below for further details.

* A plugin interface allowing for additional meta tags to be easily added via
  custom modules.

@@ -131,6 +135,44 @@ Metatag field entirely, then use tokens for those fields in the defaults
display, or just left hidden.


## Indexing meta tags with Search API

The Search API integration allows indexing the meta tag values from a meta tag
field on an entity bundle.

### Limitations

* Each meta tag must be specified individually, there isn't (currently) a way to
  add all of them at once.
* This may not work with values stored in Schema.org data structures from the
  Schema.org Metatag module.
* Changing the global Metatag defaults will not trigger a reindex of the content
  so this will need to be done separately.
* By default meta tags will be added as "string" fields, which will not include
  them in the keyword search index; their "Type" selection must be specifically
  set to "Fulltext" in order for the meta tags to be included in the keyword
  search index, otherwise they will only be useful for doing facets or filters.

### Setup

To add meta tags to the Search API:

* Make sure that a Metatag field is present on the entity bundle(s) that are
  being indexed.
* In the index configuration, modify the Fields section.
* Click the "Add fields" button.
* In the "Add fields to index [indexname]" popup, scroll down to find the
  Metatag field; it will have the name defined in the field's settings.
* Click the plus `(+)` button beside the field to see which meta tags are
  available.
* Click the "Add" button for the meta tags that are needed.
* Click "Done" when all of the meta tags are added.
* Look for the meta tag fields added to the index, change their "type" selector
  to "Fulltext"; adjust the "Boost" value if desired.
* Click "Save changes" at the bottom of the Fields admin page.
* Reindex the content with the new settings.


## Alternative option to simplify the content administration experience

On the settings page (/admin/config/search/metatag/settings) are options to
+14 −0
Changes for src/Plugin/Field/FieldType/MetatagFieldItem.php: 14 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -46,6 +46,20 @@ class MetatagFieldItem extends FieldItemBase {
      ->setLabel(t('Metatag'))
      ->setRequired(TRUE);

    $sorted_tags = \Drupal::service('metatag.manager')->sortedGroupsWithTags();

    foreach ($sorted_tags as $group_id => $group) {
      if (isset($group['tags'])) {
        foreach ($group['tags'] as $tag_id => $tag) {
          $properties[$tag_id] = DataDefinition::create('string')
            ->setLabel(t('@label', ['@label' => $tag['label']]))
            ->setComputed(TRUE)
            ->setClass('\Drupal\metatag\TypedData\IndividualTag')
            ->setSetting('tag_name', $tag_id);
        }
      }
    }

    return $properties;
  }

+10 −0
Changes for src/Plugin/metatag/Tag/MetaNameBase.php: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -316,6 +316,16 @@ abstract class MetaNameBase extends PluginBase {
    return FALSE;
  }

  /**
   * Get the HTML attribute used to store this meta tag's value.
   *
   * @return string
   *   The HTML attribute used to store this meta tag's value.
   */
  public function getHtmlValueAttribute() {
    return $this->htmlValueAttribute;
  }

  /**
   * Whether or not this meta tag must output required absolute URLs.
   *
+99 −0
Changes for src/TypedData/IndividualTag.php: 99 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\metatag\TypedData;

use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedData;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;

/**
 * A computed property for each meta tag.
 *
 * Required settings (below the definition's 'settings' key) are:
 *  - tag_name: The tag to be processed.
 *    Examples: "title", "description".
 */
class IndividualTag extends TypedData {

  use DependencySerializationTrait;

  /**
   * Cached processed value.
   */
  protected $value;

  /**
   * {@inheritdoc}
   */
  public function __construct(DataDefinitionInterface $definition, $name = NULL, TypedDataInterface $parent = NULL) {
    parent::__construct($definition, $name, $parent);

    if ($definition->getSetting('tag_name') === NULL) {
      throw new \InvalidArgumentException("The definition's 'tag_name' key has to specify the name of the meta tag to be processed.");
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getValue() {
    if (isset($this->value)) {
      return $this->value;
    }

    // The Metatag plugin ID.
    $property_name = $this->definition->getSetting('tag_name');

    // The item is the parent.
    $item = $this->getParent();
    $entity = $item->getEntity();

    // Rendered values.
    $metatagManager = \Drupal::service('metatag.manager');
    $defaultTags = $metatagManager->tagsFromEntityWithDefaults($entity);
    if (!isset($defaultTags[$property_name])) {
      \Drupal::service('logger.factory')
        ->get('metatag')
        ->notice('No default for "%tag_name" - entity_type: %type, entity_bundle: %bundle, id: %id. See src/TypedData/Metatags.php.', [
          '%tag_name' => $property_name,
          '%type' => $entity->getEntityTypeId(),
          '%bundle' => $entity->bundle(),
          '%id' => $entity->id(),
        ]);
      return FALSE;
    }
    $tags = [$property_name => $defaultTags[$property_name]];
    $values = $metatagManager->generateRawElements($tags, $entity);

    $all_tags = [];
    foreach (\Drupal::service('plugin.manager.metatag.tag') as $tag_name => $tag_spec) {
      $all_tags[$tag_name] = new $tag_spec['class']([], $tag_name, $tag_spec);
    }

    // If this tag has a value set the property value.
    if (isset($values[$property_name])) {
      $attribute_name = $tag->getHtmlValueAttribute();

      // It should be possible to extract the HTML attribute that stores the
      // value, but in some cases it might not be possible.
      if (isset($values[$property_name]['#attributes'][$attribute_name])) {
        $this->value = $values[$property_name]['#attributes'][$attribute_name];
      }
      else {
        \Drupal::service('logger.factory')
          ->get('metatag')
          ->notice('Attribute value not mapped for "%property_name" - entity_type: %type, entity_bundle: %bundle, id: %id. See src/TypedData/Metatags.php.', [
            '%property_name' => $property_name,
            '%type' => $entity->getEntityTypeId(),
            '%bundle' => $entity->bundle(),
            '%id' => $entity->id(),
          ]);
        return FALSE;
      }
    }

    return $this->value;
  }

}