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