More tidying.
[yaffs-website] / web / modules / contrib / metatag / metatag.module
1 <?php
2
3 /**
4  * @file
5  * Contains metatag.module.
6  */
7
8 use Drupal\Core\Entity\ContentEntityInterface;
9 use Drupal\Core\Entity\EntityInterface;
10 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
11 use Drupal\Core\Form\FormStateInterface;
12 use Drupal\Core\Language\LanguageInterface;
13 use Drupal\Core\Routing\RouteMatchInterface;
14 use Drupal\Core\Url;
15
16 /**
17  * Implements hook_help().
18  */
19 function metatag_help($route_name, RouteMatchInterface $route_match) {
20   switch ($route_name) {
21     // Main module help for the Metatag module.
22     case 'help.page.metatag':
23       $output = '<h2>' . t('About') . '</h2>';
24       $output .= '<p>' . t('This module allows a site to automatically provide structured metadata, aka "meta tags", about the site and individual pages.');
25       $output .= '<p>' . t('In the context of search engine optimization, providing an extensive set of meta tags may help improve the site\'s and pages\' rankings, thus may aid with achieving a more prominent display of the content within search engine results. They can also be used to tailor how content is displayed when shared on social networks. For additional information, see the <a href=":online">online documentation for Metatag</a>.', [':online' => 'https://www.drupal.org/node/1774342']) . '</p>';
26       $output .= '<h3>' . t('Intended worflow') . '</h3>';
27       $output .= '<p>' . t('The module uses <a href=":tokens">"tokens"</a> to automatically fill in values for different meta tags. Specific values may also be filled in.', [':tokens' => Url::fromRoute('help.page', ['name' => 'token'])->toString()]) . '</p>';
28       $output .= '<p>' . t('The best way of using Metatag is as follows:') . '</p>';
29       $output .= '<ol>';
30       $output .= '<li>' . t('Customize the <a href=":defaults">global defaults</a>, fill in the specific values and tokens that every page should have.', [':defaults' => Url::fromRoute('entity.metatag_defaults.edit_form', ['metatag_defaults' => 'global'])->toString()]) . '</li>';
31       $output .= '<li>' . t('Override each of the <a href=":defaults">other defaults</a>, fill in specific values and tokens that each item should have by default. This allows e.g. for all nodes to have different values than taxonomy terms.', [':defaults' => Url::fromRoute('entity.metatag_defaults.collection')->toString()]) . '</li>';
32       $output .= '<li>' . t('<a href=":add">Add more default configurations</a> as necessary for different entity types and entity bundles, e.g. for different content types or different vocabularies.', [':add' => Url::fromRoute('entity.metatag_defaults.add_form')->toString()]) . '</li>';
33       $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>';
34       $output .= '</ol>';
35       return $output;
36       break;
37
38     // The main configuration page.
39     case 'entity.metatag_defaults.collection':
40       $output = '<p>' . t('Configure global meta tag default values below. Meta tags may be left as the default.') . '</p>';
41       $output .= '<p>' . t('Meta tag patterns are passed down from one level to the next unless they are overridden. To view a summary of the individual meta tags and the pattern for a specific configuration, click on its name below.') . '</p>';
42       $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>';
43       $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>';
44       return $output;
45       break;
46
47     // The 'add default meta tags' configuration page.
48     case 'entity.metatag_defaults.add_form':
49       $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>';
50       $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>';
51       return $output;
52       break;
53   }
54 }
55
56 /**
57  * Implements hook_form_FORM_ID_alter() for 'field_storage_config_edit_form'.
58  */
59 function metatag_form_field_storage_config_edit_form_alter(&$form, FormStateInterface $form_state) {
60   if ($form_state->getFormObject()->getEntity()->getType() == 'metatag') {
61     // Hide the cardinality field.
62     $form['cardinality_container']['#access'] = FALSE;
63     $form['cardinality_container']['#disabled'] = TRUE;
64   }
65 }
66
67 /**
68  * Implements hook_form_FORM_ID_alter() for 'field_config_edit_form'.
69  *
70  * Configuration defaults are handled via a different mechanism, so do not allow
71  * any values to be saved.
72  */
73 function metatag_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state) {
74   if ($form_state->getFormObject()->getEntity()->getType() == 'metatag') {
75     // Hide the required and default value fields.
76     $form['required']['#access'] = FALSE;
77     $form['required']['#disabled'] = TRUE;
78     $form['default_value']['#access'] = FALSE;
79     $form['default_value']['#disabled'] = TRUE;
80
81     // Step through the default value structure and erase any '#default_value'
82     // items that are found.
83     foreach ($form['default_value']['widget'][0] as $key => &$outer) {
84       if (is_array($outer)) {
85         foreach ($outer as $key => &$inner) {
86           if (is_array($inner) && isset($inner['#default_value'])) {
87             if (is_array($inner['#default_value'])) {
88               $inner['#default_value'] = [];
89             }
90             else {
91               $inner['#default_value'] = NULL;
92             }
93           }
94         }
95       }
96     }
97   }
98 }
99
100 /**
101  * Implements hook_page_attachments().
102  *
103  * Load all meta tags for this page.
104  */
105 function metatag_page_attachments(array &$attachments) {
106   if (!metatag_is_current_route_supported()) {
107     return NULL;
108   }
109
110   $metatag_attachments = &drupal_static('metatag_attachments');
111
112   if (is_null($metatag_attachments)) {
113     // Load the meta tags from the route.
114     $metatag_attachments = metatag_get_tags_from_route();
115     if (!$metatag_attachments) {
116       return NULL;
117     }
118
119     // If any Metatag items were found, append them.
120     if (!empty($metatag_attachments['#attached']['html_head'])) {
121       if (empty($attachments['#attached'])) {
122         $attachments['#attached'] = [];
123       }
124       if (empty($attachments['#attached']['html_head'])) {
125         $attachments['#attached']['html_head'] = [];
126       }
127       foreach ($metatag_attachments['#attached']['html_head'] as $item) {
128         $attachments['#attached']['html_head'][] = $item;
129       }
130     }
131   }
132 }
133
134 /**
135  * Implements hook_entity_view_alter().
136  */
137 function metatag_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
138   // Some entities are built with a link rel="canonical" and/or link rel="shortlink" tag attached.
139   // If metatag provides them, remove the ones built with the entity.
140   if (isset($build['#attached']['html_head_link'])) {
141     $metatag_attachments = drupal_static('metatag_attachments');
142     if (is_null($metatag_attachments)) {
143       // Load the meta tags from the route.
144       $metatag_attachments = metatag_get_tags_from_route();
145     }
146
147     // Check to see if the page currently outputs a canonical and/or shortlink tag.
148     if (isset($metatag_attachments['#attached']['html_head'])) {
149       foreach ($metatag_attachments['#attached']['html_head'] as $metatag_item) {
150         if (in_array($metatag_item[1], array('canonical_url', 'shortlink'))) {
151           // Metatag provides rel="canonical" and/or rel="shortlink" tags.
152           foreach ($build['#attached']['html_head_link'] as $key => $item) {
153             if (isset($item[0]['rel']) && in_array($item[0]['rel'], array('canonical', 'shortlink'))) {
154               // Remove the link rel="canonical" or link rel="shortlink" tag
155               // from the entity's build array.
156               unset($build['#attached']['html_head_link'][$key]);
157             }
158           }
159         }
160       }
161     }
162   }
163 }
164
165 /**
166  * Identify whether the current route is supported by the module.
167  *
168  * @return bool
169  *   TRUE if the current route is supported.
170  */
171 function metatag_is_current_route_supported() {
172   // If upgrading, we need to wait for database updates to complete.
173   $is_ready = \Drupal::service('entity_type.manager')
174     ->getDefinition('metatag_defaults', FALSE);
175   if (!$is_ready) {
176     return FALSE;
177   }
178
179   // Ignore admin paths.
180   if (\Drupal::service('router.admin_context')->isAdminRoute()) {
181     return FALSE;
182   }
183
184   return TRUE;
185 }
186
187 /**
188  * Returns the entity of the current route.
189  *
190  * @return EntityInterface
191  *   The entity or NULL if this is not an entity route.
192  */
193 function metatag_get_route_entity() {
194   $route_match = \Drupal::routeMatch();
195   $route_name = $route_match->getRouteName();
196
197   // Look for a canonical entity view page, e.g. node/{nid}, user/{uid}, etc.
198   $matches = [];
199   preg_match('/entity\.(.*)\.canonical/', $route_name, $matches);
200   if (!empty($matches[1])) {
201     $entity_type = $matches[1];
202     return $route_match->getParameter($entity_type);
203   }
204
205   // Look for a rest entity view page, e.g. node/{nid}?_format=json, etc.
206   $matches = [];
207   // ie: rest.entity.node.GET.json
208   preg_match('/rest\.entity\.(.*)\.(.*)\.(.*)/', $route_name, $matches);
209   if (!empty($matches[1])) {
210     $entity_type = $matches[1];
211     return $route_match->getParameter($entity_type);
212   }
213
214   // Look for entity object 'add' pages, e.g. node/add/{bundle}.
215   $route_name_matches = [];
216   preg_match('/(entity\.)?(.*)\.add(_form)?/', $route_name, $route_name_matches);
217   if (!empty($route_name_matches[2])) {
218     $entity_type = $route_name_matches[2];
219     $definition = Drupal::entityTypeManager()->getDefinition($entity_type, FALSE);
220     if (!empty($definition)) {
221       $type = $route_match->getRawParameter($definition->get('bundle_entity_type'));
222       if (!empty($type)) {
223         return \Drupal::entityTypeManager()
224           ->getStorage($entity_type)
225           ->create([
226             $definition->get('entity_keys')['bundle'] => $type,
227           ]);
228       }
229     }
230   }
231
232   // Look for entity object 'edit' pages, e.g. node/{entity_id}/edit.
233   $route_name_matches = [];
234   preg_match('/entity\.(.*)\.edit_form/', $route_name, $route_name_matches);
235   if (!empty($route_name_matches[1])) {
236     $entity_type = $route_name_matches[1];
237     $entity_id = $route_match->getRawParameter($entity_type);
238
239     if (!empty($entity_id)) {
240       return \Drupal::entityTypeManager()
241         ->getStorage($entity_type)
242         ->load($entity_id);
243     }
244   }
245
246   // Look for entity object 'add content translation' pages, e.g.
247   // node/{nid}/translations/add/{source_lang}/{translation_lang}
248   $route_name_matches = [];
249   preg_match('/(entity\.)?(.*)\.content_translation_add/', $route_name, $route_name_matches);
250   if (!empty($route_name_matches[2])) {
251     $entity_type = $route_name_matches[2];
252     $definition = Drupal::entityTypeManager()->getDefinition($entity_type, FALSE);
253     if (!empty($definition)) {
254       $node = $route_match->getParameter($entity_type);
255       $type = $node->bundle();
256       if (!empty($type)) {
257         return \Drupal::entityTypeManager()
258           ->getStorage($entity_type)
259           ->create([
260             $definition->get('entity_keys')['bundle'] => $type,
261           ]);
262       }
263     }
264   }
265
266   // Special handling for the admin user_create page. In this case, there's only
267   // one bundle and it's named the same as the entity type, so some shortcuts
268   // can be used.
269   if ($route_name == 'user.admin_create') {
270     $entity_type = $type = 'user';
271     $definition = Drupal::entityTypeManager()->getDefinition($entity_type);
272     if (!empty($type)) {
273       return \Drupal::entityTypeManager()
274         ->getStorage($entity_type)
275         ->create([
276           $definition->get('entity_keys')['bundle'] => $type,
277         ]);
278     }
279   }
280
281   // Trigger hook_metatag_route_entity().
282   if ($entities = \Drupal::moduleHandler()->invokeAll('metatag_route_entity', [$route_match])) {
283     return reset($entities);
284   }
285
286   return NULL;
287 }
288
289 /**
290  * Implements template_preprocess_html().
291  */
292 function metatag_preprocess_html(&$variables) {
293   if (!metatag_is_current_route_supported()) {
294     return NULL;
295   }
296
297   $attachments = drupal_static('metatag_attachments');
298   if (is_null($attachments)) {
299     $attachments = metatag_get_tags_from_route();
300   }
301
302   if (!$attachments) {
303     return NULL;
304   }
305
306   // Load the page title.
307   if (!empty($attachments['#attached']['html_head'])) {
308     foreach ($attachments['#attached']['html_head'] as $key => $attachment) {
309       if (!empty($attachment[1]) && $attachment[1] == 'title') {
310         // It's safe to access the value directly because it was already
311         // processed in MetatagManager::generateElements().
312         $variables['head_title_array'] = [];
313         // Empty head_title to avoid the site name and slogan to be appended to
314         // the meta title.
315         $variables['head_title'] = [];
316         $variables['head_title']['title'] = html_entity_decode($attachment[0]['#attributes']['content'], ENT_QUOTES);
317         // Original:
318         // $variables['head_title_array']['title'] = $attachment[0]['#attributes']['content'];
319         // $variables['head_title'] = implode(' | ', $variables['head_title_array']);
320         break;
321       }
322     }
323   }
324 }
325
326 /**
327  * Load the meta tags by processing the route parameters.
328  *
329  * @return mixed
330  *   Array of metatags or NULL.
331  */
332 function metatag_get_tags_from_route() {
333   $metatag_manager = \Drupal::service('metatag.manager');
334
335   // First, get defaults.
336   $metatags = metatag_get_default_tags();
337   if (!$metatags) {
338     return NULL;
339   }
340
341   // Then, set tag overrides for this particular entity.
342   $entity = metatag_get_route_entity();
343   if (!empty($entity) && $entity instanceof ContentEntityInterface) {
344     foreach ($metatag_manager->tagsFromEntity($entity) as $tag => $data) {
345       $metatags[$tag] = $data;
346     }
347   }
348
349   // Trigger hook_metatags_alter().
350   // Allow modules to override tags or the entity used for token replacements.
351   $context = [
352     'entity' => $entity,
353   ];
354   \Drupal::service('module_handler')->alter('metatags', $metatags, $context);
355
356   return $metatag_manager->generateElements($metatags, $entity);
357 }
358
359 /**
360  * Returns default tags for the current route.
361  *
362  * @return mixed
363  *   Array of tags or NULL;
364  */
365 function metatag_get_default_tags() {
366   /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $global_metatag_manager */
367   $global_metatag_manager = \Drupal::entityTypeManager()->getStorage('metatag_defaults');
368   // First we load global defaults.
369   $metatags = $global_metatag_manager->load('global');
370   if (!$metatags) {
371     return NULL;
372   }
373
374   // Check if this is a special page.
375   $special_metatags = \Drupal::service('metatag.manager')->getSpecialMetatags();
376   if (isset($special_metatags)) {
377     $metatags->overwriteTags($special_metatags->get('tags'));
378   }
379
380   // Next check if there is this page is an entity that has meta tags.
381   else {
382     $entity = metatag_get_route_entity();
383
384     if (!empty($entity)) {
385       $entity_metatags = $global_metatag_manager->load($entity->getEntityTypeId());
386       if ($entity_metatags != NULL) {
387         // Merge with global defaults.
388         $metatags->overwriteTags($entity_metatags->get('tags'));
389       }
390
391       // Finally, check if bundle overrides should be added.
392       $bundle_metatags = $global_metatag_manager->load($entity->getEntityTypeId() . '__' . $entity->bundle());
393       if ($bundle_metatags != NULL) {
394         // Merge with existing defaults.
395         $metatags->overwriteTags($bundle_metatags->get('tags'));
396       }
397     }
398   }
399
400   return $metatags->get('tags');
401 }