Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / modules / contrib / metatag / metatag.module
index 0cf71ec0e779b3d8affae1cd4a21302efd8ccb37..2732fcc068b42577fa5dff2743882dcbf825f495 100644 (file)
@@ -7,11 +7,14 @@
 
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
+use Drupal\taxonomy\TermInterface;
+use Drupal\Component\Utility\Html;
 
 /**
  * Implements hook_help().
@@ -33,7 +36,6 @@ function metatag_help($route_name, RouteMatchInterface $route_match) {
       $output .= '<li>' . t('To override the meta tags for individual entities, e.g. for individual nodes, add the "Metatag" field via the field settings for that entity or bundle type.') . '</li>';
       $output .= '</ol>';
       return $output;
-      break;
 
     // The main configuration page.
     case 'entity.metatag_defaults.collection':
@@ -42,14 +44,12 @@ function metatag_help($route_name, RouteMatchInterface $route_match) {
       $output .= '<p>' . t('If the top-level configuration is not specific enough, additional default meta tag configurations can be added for a specific entity type or entity bundle, e.g. for a specific content type.') . '</p>';
       $output .= '<p>' . t('Meta tags can be further refined on a per-entity basis, e.g. for individual nodes, by adding the "Metatag" field to that entity type through its normal field settings pages.') . '</p>';
       return $output;
-      break;
 
     // The 'add default meta tags' configuration page.
     case 'entity.metatag_defaults.add_form':
       $output = '<p>' . t('Use the following form to override the global default meta tags for a specific entity type or entity bundle. In practical terms, this allows the meta tags to be customized for a specific content type or taxonomy vocabulary, so that its content will have different meta tags <em>default values</em> than others.') . '</p>';
       $output .= '<p>' . t('As a reminder, if the "Metatag" field is added to the entity type through its normal field settings, the meta tags can be further refined on a per entity basis; this allows each node to have its meta tags  customized on an individual basis.') . '</p>';
       return $output;
-      break;
   }
 }
 
@@ -112,22 +112,73 @@ function metatag_page_attachments(array &$attachments) {
   if (is_null($metatag_attachments)) {
     // Load the meta tags from the route.
     $metatag_attachments = metatag_get_tags_from_route();
-    if (!$metatag_attachments) {
-      return NULL;
+  }
+  if (!$metatag_attachments) {
+    return NULL;
+  }
+
+  // If any Metatag items were found, append them.
+  if (!empty($metatag_attachments['#attached']['html_head'])) {
+    if (empty($attachments['#attached'])) {
+      $attachments['#attached'] = [];
+    }
+    if (empty($attachments['#attached']['html_head'])) {
+      $attachments['#attached']['html_head'] = [];
     }
 
-    // If any Metatag items were found, append them.
-    if (!empty($metatag_attachments['#attached']['html_head'])) {
-      if (empty($attachments['#attached'])) {
-        $attachments['#attached'] = [];
-      }
-      if (empty($attachments['#attached']['html_head'])) {
-        $attachments['#attached']['html_head'] = [];
-      }
-      foreach ($metatag_attachments['#attached']['html_head'] as $item) {
-        $attachments['#attached']['html_head'][] = $item;
+    $head_links = [];
+    foreach ($metatag_attachments['#attached']['html_head'] as $item) {
+      $attachments['#attached']['html_head'][] = $item;
+
+      // Also add a HTTP header "Link:" for canonical URLs and shortlinks.
+      // See HtmlResponseAttachmentsProcessor::processHtmlHeadLink() for the
+      // implementation of the functionality in core.
+      if (in_array($item[1], ['canonical_url', 'shortlink'])) {
+        $attributes = $item[0]['#attributes'];
+
+        $href = '<' . Html::escape($attributes['href']) . '>';
+        unset($attributes['href']);
+        if ($param = drupal_http_header_attributes($attributes)) {
+          $href .= ';' . $param;
+        }
+        $head_links[] = $href;
       }
     }
+
+    // If any HTTP Header items were found, add them too.
+    if (!empty($head_links)) {
+      $attachments['#attached']['http_header'][] = [
+        'Link',
+        implode(', ', $head_links),
+        FALSE,
+      ];
+    }
+  }
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ */
+function metatag_module_implements_alter(&$implementations, $hook) {
+  if ($hook == 'page_attachments_alter') {
+    // Move metatag_page_attachments_alter() to the end of the list. This is so
+    // the canonical and shortlink tags can be removed that are added by
+    // taxonomy_page_attachments_alter().
+    // @todo Remove once https://www.drupal.org/node/2282029 is fixed.
+    $group = $implementations['metatag'];
+    unset($implementations['metatag']);
+    $implementations['metatag'] = $group;
+  }
+}
+
+/**
+ * Implements hook_page_attachments_alter().
+ */
+function metatag_page_attachments_alter(array &$attachments) {
+  $route_match = \Drupal::routeMatch();
+  // Can be removed once https://www.drupal.org/node/2282029 is fixed.
+  if ($route_match->getRouteName() == 'entity.taxonomy_term.canonical' && ($term = $route_match->getParameter('taxonomy_term')) && $term instanceof TermInterface) {
+    _metatag_remove_duplicate_entity_tags($attachments);
   }
 }
 
@@ -135,22 +186,41 @@ function metatag_page_attachments(array &$attachments) {
  * Implements hook_entity_view_alter().
  */
 function metatag_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
-  // Some entities are built with a link rel="canonical" and/or link rel="shortlink" tag attached.
+  // If this is a 403 or 404 page then don't output these meta tags.
+  // @todo Make the default meta tags load properly so this is unnecessary.
+  if ($display->getOriginalId() == 'node.403.default' || $display->getOriginalId() == 'node.404.default') {
+    $build['#attached']['html_head_link'] = [];
+    return;
+  }
+
+  _metatag_remove_duplicate_entity_tags($build);
+}
+
+/**
+ * Remove duplicate entity tags from a build.
+ *
+ * @param array $build
+ *   The build.
+ */
+function _metatag_remove_duplicate_entity_tags(array &$build) {
+  // Some entities are built with a link rel="canonical" and/or link
+  // rel="shortlink" tag attached.
   // If metatag provides them, remove the ones built with the entity.
   if (isset($build['#attached']['html_head_link'])) {
-    $metatag_attachments = drupal_static('metatag_attachments');
+    $metatag_attachments = &drupal_static('metatag_attachments');
     if (is_null($metatag_attachments)) {
       // Load the meta tags from the route.
       $metatag_attachments = metatag_get_tags_from_route();
     }
 
-    // Check to see if the page currently outputs a canonical and/or shortlink tag.
+    // Check to see if the page currently outputs a canonical and/or shortlink
+    // tag.
     if (isset($metatag_attachments['#attached']['html_head'])) {
       foreach ($metatag_attachments['#attached']['html_head'] as $metatag_item) {
-        if (in_array($metatag_item[1], array('canonical_url', 'shortlink'))) {
+        if (in_array($metatag_item[1], ['canonical_url', 'shortlink'])) {
           // Metatag provides rel="canonical" and/or rel="shortlink" tags.
           foreach ($build['#attached']['html_head_link'] as $key => $item) {
-            if (isset($item[0]['rel']) && in_array($item[0]['rel'], array('canonical', 'shortlink'))) {
+            if (isset($item[0]['rel']) && in_array($item[0]['rel'], ['canonical', 'shortlink'])) {
               // Remove the link rel="canonical" or link rel="shortlink" tag
               // from the entity's build array.
               unset($build['#attached']['html_head_link'][$key]);
@@ -187,7 +257,7 @@ function metatag_is_current_route_supported() {
 /**
  * Returns the entity of the current route.
  *
- * @return EntityInterface
+ * @return Drupal\Core\Entity\EntityInterface
  *   The entity or NULL if this is not an entity route.
  */
 function metatag_get_route_entity() {
@@ -196,22 +266,22 @@ function metatag_get_route_entity() {
 
   // Look for a canonical entity view page, e.g. node/{nid}, user/{uid}, etc.
   $matches = [];
-  preg_match('/entity\.(.*)\.canonical/', $route_name, $matches);
+  preg_match('/entity\.(.*)\.(latest[_-]version|canonical)/', $route_name, $matches);
   if (!empty($matches[1])) {
     $entity_type = $matches[1];
     return $route_match->getParameter($entity_type);
   }
 
-  // Look for a rest entity view page, e.g. node/{nid}?_format=json, etc.
+  // Look for a rest entity view page, e.g. "node/{nid}?_format=json", etc.
   $matches = [];
-  // ie: rest.entity.node.GET.json
+  // Matches e.g. "rest.entity.node.GET.json".
   preg_match('/rest\.entity\.(.*)\.(.*)\.(.*)/', $route_name, $matches);
   if (!empty($matches[1])) {
     $entity_type = $matches[1];
     return $route_match->getParameter($entity_type);
   }
 
-  // Look for entity object 'add' pages, e.g. node/add/{bundle}.
+  // Look for entity object 'add' pages, e.g. "node/add/{bundle}".
   $route_name_matches = [];
   preg_match('/(entity\.)?(.*)\.add(_form)?/', $route_name, $route_name_matches);
   if (!empty($route_name_matches[2])) {
@@ -229,7 +299,7 @@ function metatag_get_route_entity() {
     }
   }
 
-  // Look for entity object 'edit' pages, e.g. node/{entity_id}/edit.
+  // Look for entity object 'edit' pages, e.g. "node/{entity_id}/edit".
   $route_name_matches = [];
   preg_match('/entity\.(.*)\.edit_form/', $route_name, $route_name_matches);
   if (!empty($route_name_matches[1])) {
@@ -244,7 +314,7 @@ function metatag_get_route_entity() {
   }
 
   // Look for entity object 'add content translation' pages, e.g.
-  // node/{nid}/translations/add/{source_lang}/{translation_lang}
+  // "node/{nid}/translations/add/{source_lang}/{translation_lang}".
   $route_name_matches = [];
   preg_match('/(entity\.)?(.*)\.content_translation_add/', $route_name, $route_name_matches);
   if (!empty($route_name_matches[2])) {
@@ -294,7 +364,7 @@ function metatag_preprocess_html(&$variables) {
     return NULL;
   }
 
-  $attachments = drupal_static('metatag_attachments');
+  $attachments = &drupal_static('metatag_attachments');
   if (is_null($attachments)) {
     $attachments = metatag_get_tags_from_route();
   }
@@ -315,8 +385,10 @@ function metatag_preprocess_html(&$variables) {
         $variables['head_title'] = [];
         $variables['head_title']['title'] = html_entity_decode($attachment[0]['#attributes']['content'], ENT_QUOTES);
         // Original:
-        // $variables['head_title_array']['title'] = $attachment[0]['#attributes']['content'];
-        // $variables['head_title'] = implode(' | ', $variables['head_title_array']);
+        // $variables['head_title_array']['title'] =
+        //   $attachment[0]['#attributes']['content'];
+        // $variables['head_title'] = implode(' | ',
+        //   $variables['head_title_array']);
         break;
       }
     }
@@ -327,20 +399,29 @@ function metatag_preprocess_html(&$variables) {
  * Load the meta tags by processing the route parameters.
  *
  * @return mixed
- *   Array of metatags or NULL.
+ *   Array of meta tags or NULL.
  */
-function metatag_get_tags_from_route() {
+function metatag_get_tags_from_route($entity = NULL) {
   $metatag_manager = \Drupal::service('metatag.manager');
 
   // First, get defaults.
-  $metatags = metatag_get_default_tags();
+  $metatags = metatag_get_default_tags($entity);
   if (!$metatags) {
     return NULL;
   }
 
   // Then, set tag overrides for this particular entity.
-  $entity = metatag_get_route_entity();
+  if (!$entity) {
+    $entity = metatag_get_route_entity();
+  }
+
   if (!empty($entity) && $entity instanceof ContentEntityInterface) {
+    // If content entity does not have an ID the page is likely an "Add" page,
+    // so do not generate meta tags for entity which has not been created yet.
+    if (!$entity->id()) {
+      return NULL;
+    }
+
     foreach ($metatag_manager->tagsFromEntity($entity) as $tag => $data) {
       $metatags[$tag] = $data;
     }
@@ -362,7 +443,7 @@ function metatag_get_tags_from_route() {
  * @return mixed
  *   Array of tags or NULL;
  */
-function metatag_get_default_tags() {
+function metatag_get_default_tags($entity = NULL) {
   /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $global_metatag_manager */
   $global_metatag_manager = \Drupal::entityTypeManager()->getStorage('metatag_defaults');
   // First we load global defaults.
@@ -379,9 +460,11 @@ function metatag_get_default_tags() {
 
   // Next check if there is this page is an entity that has meta tags.
   else {
-    $entity = metatag_get_route_entity();
+    if (!$entity) {
+      $entity = metatag_get_route_entity();
+    }
 
-    if (!empty($entity)) {
+    if (!empty($entity) && $entity instanceof ContentEntityInterface) {
       $entity_metatags = $global_metatag_manager->load($entity->getEntityTypeId());
       if ($entity_metatags != NULL) {
         // Merge with global defaults.
@@ -399,3 +482,100 @@ function metatag_get_default_tags() {
 
   return $metatags->get('tags');
 }
+
+/**
+ * Implements hook_entity_base_field_info().
+ */
+function metatag_entity_base_field_info(EntityTypeInterface $entity_type) {
+  $fields = [];
+  $base_table = $entity_type->getBaseTable();
+  $canonical_template_exists = $entity_type->hasLinkTemplate('canonical');
+  // Certain classes are just not supported.
+  $original_class = $entity_type->getOriginalClass();
+  $classes_to_skip = [
+    'Drupal\comment\Entity\Comment',
+  ];
+
+  // If the entity type doesn't have a base table, has no link template then
+  // there's no point in supporting it.
+  if (!empty($base_table) && $canonical_template_exists && !in_array($original_class, $classes_to_skip)) {
+    $fields['metatag'] = BaseFieldDefinition::create('map')
+      ->setLabel(t('Metatags'))
+      ->setDescription(t('The meta tags for the entity.'))
+      ->setClass('\Drupal\metatag\Plugin\Field\MetatagEntityFieldItemList')
+      ->setQueryable(FALSE)
+      ->setComputed(TRUE)
+      ->setTargetEntityTypeId($entity_type->id());
+  }
+
+  return $fields;
+}
+
+/**
+ * Implements hook_entity_diff_options().
+ */
+function metatag_entity_diff_options($entity_type) {
+  if (metatag_entity_supports_metatags($entity_type)) {
+    $options = [
+      'metatag' => t('Metatags'),
+    ];
+    return $options;
+  }
+}
+
+/**
+ * Implements hook_entity_diff().
+ */
+function metatag_entity_diff($old_entity, $new_entity, $context) {
+  $result = [];
+  $entity_type = $context['entity_type'];
+  $options = variable_get('diff_additional_options_' . $entity_type, []);
+  if (!empty($options['metatag']) && metatag_entity_supports_metatags($entity_type)) {
+    // Find meta tags that are set on either the new or old entity.
+    $tags = [];
+    foreach (['old' => $old_entity, 'new' => $new_entity] as $entity_key => $entity) {
+      $language = metatag_entity_get_language($entity_type, $entity);
+      if (isset($entity->metatags[$language])) {
+        foreach ($entity->metatags[$language] as $key => $value) {
+          $tags[$key][$entity_key] = $value['value'];
+        }
+      }
+    }
+
+    $init_weight = 100;
+    foreach ($tags as $key => $values) {
+      $id = ucwords('Meta ' . $key);
+      // @todo Find the default values and show these if not set.
+      $result[$id] = [
+        '#name' => $id,
+        '#old' => [empty($values['old']) ? '' : $values['old']],
+        '#new' => [empty($values['new']) ? '' : $values['new']],
+        '#weight' => $init_weight++,
+        '#settings' => [
+          'show_header' => TRUE,
+        ],
+      ];
+    }
+  }
+  return $result;
+}
+
+/**
+ * Turn the meta tags for an entity into a human readable structure.
+ *
+ * @param object $entity
+ *   The entity object.
+ *
+ * @return array
+ *   All of the meta tags in a nested structure.
+ */
+function metatag_generate_entity_metatags($entity) {
+  $values = [];
+  $raw = metatag_get_tags_from_route($entity);
+  if (!empty($raw['#attached']['html_head'])) {
+    foreach ($raw['#attached']['html_head'] as $tag) {
+      $values[$tag[1]] = $tag[0];
+    }
+  }
+  return $values;
+}