Yaffs site version 1.1
[yaffs-website] / web / modules / contrib / metatag / src / MetatagManager.php
1 <?php
2
3 namespace Drupal\metatag;
4
5 use Drupal\Component\Render\PlainTextOutput;
6 use Drupal\Core\Entity\ContentEntityInterface;
7 use Drupal\Core\Entity\EntityTypeManager;
8 use Drupal\Core\Language\LanguageInterface;
9 use Drupal\Core\Logger\LoggerChannelFactoryInterface;
10 use Drupal\field\Entity\FieldConfig;
11 use Drupal\metatag\Entity\MetatagDefaults;
12 use Drupal\views\ViewEntityInterface;
13
14 /**
15  * Class MetatagManager.
16  *
17  * @package Drupal\metatag
18  */
19 class MetatagManager implements MetatagManagerInterface {
20
21   protected $groupPluginManager;
22   protected $tagPluginManager;
23   protected $metatagDefaults;
24   protected $tokenService;
25
26   /**
27    * Metatag logging channel.
28    *
29    * @var \Drupal\Core\Logger\LoggerChannelInterface
30    */
31   protected $logger;
32
33   /**
34    * Constructor for MetatagManager.
35    *
36    * @param MetatagGroupPluginManager $groupPluginManager
37    * @param MetatagTagPluginManager $tagPluginManager
38    * @param MetatagToken $token
39    * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $channelFactory
40    * @param EntityTypeManager $entityTypeManager
41    */
42   public function __construct(MetatagGroupPluginManager $groupPluginManager,
43                               MetatagTagPluginManager $tagPluginManager,
44                               MetatagToken $token,
45                               LoggerChannelFactoryInterface $channelFactory,
46                               EntityTypeManager $entityTypeManager) {
47     $this->groupPluginManager = $groupPluginManager;
48     $this->tagPluginManager = $tagPluginManager;
49     $this->tokenService = $token;
50     $this->logger = $channelFactory->get('metatag');
51     $this->metatagDefaults = $entityTypeManager->getStorage('metatag_defaults');
52   }
53
54   /**
55    * {@inheritdoc}
56    */
57   public function tagsFromEntity(ContentEntityInterface $entity) {
58     $tags = [];
59
60     $fields = $this->getFields($entity);
61
62     /* @var FieldConfig $field_info */
63     foreach ($fields as $field_name => $field_info) {
64       // Get the tags from this field.
65       $tags = $this->getFieldTags($entity, $field_name);
66     }
67
68     return $tags;
69   }
70
71   /**
72    * {@inheritdoc}
73    */
74   public function tagsFromEntityWithDefaults(ContentEntityInterface $entity) {
75     return $this->tagsFromEntity($entity) + $this->defaultTagsFromEntity($entity);
76   }
77
78   /**
79    * {@inheritdoc}
80    */
81   public function defaultTagsFromEntity(ContentEntityInterface $entity) {
82     /** @var MetatagDefaults $metatags */
83     $metatags = $this->metatagDefaults->load('global');
84     if (!$metatags) {
85       return NULL;
86     }
87     // Add/overwrite with tags set on the entity type.
88     $entity_type_tags = $this->metatagDefaults->load($entity->getEntityTypeId());
89     if (!is_null($entity_type_tags)) {
90       $metatags->overwriteTags($entity_type_tags->get('tags'));
91     }
92     // Add/overwrite with tags set on the entity bundle.
93     $bundle_metatags = $this->metatagDefaults->load($entity->getEntityTypeId() . '__' . $entity->bundle());
94     if (!is_null($bundle_metatags)) {
95       $metatags->overwriteTags($bundle_metatags->get('tags'));
96     }
97     return $metatags->get('tags');
98   }
99
100   /**
101    * Gets the group plugin definitions.
102    *
103    * @return array
104    *   Group definitions.
105    */
106   protected function groupDefinitions() {
107     return $this->groupPluginManager->getDefinitions();
108   }
109
110   /**
111    * Gets the tag plugin definitions.
112    *
113    * @return array
114    *   Tag definitions
115    */
116   protected function tagDefinitions() {
117     return $this->tagPluginManager->getDefinitions();
118   }
119
120   /**
121    * {@inheritdoc}
122    */
123   public function sortedGroups() {
124     $metatag_groups = $this->groupDefinitions();
125
126     // Pull the data from the definitions into a new array.
127     $groups = [];
128     foreach ($metatag_groups as $group_name => $group_info) {
129       $groups[$group_name]['id'] = $group_info['id'];
130       $groups[$group_name]['label'] = $group_info['label']->render();
131       $groups[$group_name]['description'] = $group_info['description'];
132       $groups[$group_name]['weight'] = $group_info['weight'];
133     }
134
135     // Create the 'sort by' array.
136     $sort_by = [];
137     foreach ($groups as $group) {
138       $sort_by[] = $group['weight'];
139     }
140
141     // Sort the groups by weight.
142     array_multisort($sort_by, SORT_ASC, $groups);
143
144     return $groups;
145   }
146
147   /**
148    * {@inheritdoc}
149    */
150   public function sortedTags() {
151     $metatag_tags = $this->tagDefinitions();
152
153     // Pull the data from the definitions into a new array.
154     $tags = [];
155     foreach ($metatag_tags as $tag_name => $tag_info) {
156       $tags[$tag_name]['id'] = $tag_info['id'];
157       $tags[$tag_name]['label'] = $tag_info['label']->render();
158       $tags[$tag_name]['group'] = $tag_info['group'];
159       $tags[$tag_name]['weight'] = $tag_info['weight'];
160     }
161
162     // Create the 'sort by' array.
163     $sort_by = [];
164     foreach ($tags as $key => $tag) {
165       $sort_by['group'][$key] = $tag['group'];
166       $sort_by['weight'][$key] = $tag['weight'];
167     }
168
169     // Sort the tags by weight.
170     array_multisort($sort_by['group'], SORT_ASC, $sort_by['weight'], SORT_ASC, $tags);
171
172     return $tags;
173   }
174
175   /**
176    * {@inheritdoc}
177    */
178   public function sortedGroupsWithTags() {
179     $groups = $this->sortedGroups();
180     $tags = $this->sortedTags();
181
182     foreach ($tags as $tag_name => $tag) {
183       $tag_group = $tag['group'];
184
185       if (!isset($groups[$tag_group])) {
186         // If the tag is claiming a group that has no matching plugin, log an
187         // error and force it to the basic group.
188         $this->logger->error("Undefined group '%group' on tag '%tag'", ['%group' => $tag_group, '%tag' => $tag_id]);
189         $tag['group'] = 'basic';
190         $tag_group = 'basic';
191       }
192
193       $groups[$tag_group]['tags'][$tag_name] = $tag;
194     }
195
196     return $groups;
197   }
198
199   /**
200    * {@inheritdoc}
201    */
202   public function form(array $values, array $element, array $token_types = [], array $included_groups = NULL, array $included_tags = NULL) {
203     // Add the outer fieldset.
204     $element += [
205       '#type' => 'details',
206     ];
207
208     $element += $this->tokenService->tokenBrowser($token_types);
209
210     $groups_and_tags = $this->sortedGroupsWithTags();
211
212     $first = TRUE;
213     foreach ($groups_and_tags as $group_name => $group) {
214       // Only act on groups that have tags and are in the list of included
215       // groups (unless that list is null).
216       if (isset($group['tags']) && (is_null($included_groups) || in_array($group_name, $included_groups) || in_array($group['id'], $included_groups))) {
217         // Create the fieldset.
218         $element[$group_name]['#type'] = 'details';
219         $element[$group_name]['#title'] = $group['label'];
220         $element[$group_name]['#description'] = $group['description'];
221         $element[$group_name]['#open'] = $first;
222         $first = FALSE;
223
224         foreach ($group['tags'] as $tag_name => $tag) {
225           // Only act on tags in the included tags list, unless that is null.
226           if (is_null($included_tags) || in_array($tag_name, $included_tags) || in_array($tag['id'], $included_tags)) {
227             // Make an instance of the tag.
228             $tag = $this->tagPluginManager->createInstance($tag_name);
229
230             // Set the value to the stored value, if any.
231             $tag_value = isset($values[$tag_name]) ? $values[$tag_name] : NULL;
232             $tag->setValue($tag_value);
233
234             // Create the bit of form for this tag.
235             $element[$group_name][$tag_name] = $tag->form($element);
236           }
237         }
238       }
239     }
240
241     return $element;
242   }
243
244   /**
245    * Returns a list of the metatag fields on an entity.
246    */
247   protected function getFields(ContentEntityInterface $entity) {
248     $field_list = [];
249
250     if ($entity instanceof ContentEntityInterface) {
251       // Get a list of the metatag field types.
252       $field_types = $this->fieldTypes();
253
254       // Get a list of the field definitions on this entity.
255       $definitions = $entity->getFieldDefinitions();
256
257       // Iterate through all the fields looking for ones in our list.
258       foreach ($definitions as $field_name => $definition) {
259         // Get the field type, ie: metatag.
260         $field_type = $definition->getType();
261
262         // Check the field type against our list of fields.
263         if (isset($field_type) && in_array($field_type, $field_types)) {
264           $field_list[$field_name] = $definition;
265         }
266       }
267     }
268
269     return $field_list;
270   }
271
272   /**
273    * Returns a list of the metatags with values from a field.
274    *
275    * @param ContentEntityInterface $entity
276    * @param string $field_name
277    */
278   protected function getFieldTags(ContentEntityInterface $entity, $field_name) {
279     $tags = [];
280     foreach ($entity->{$field_name} as $item) {
281       // Get serialized value and break it into an array of tags with values.
282       $serialized_value = $item->get('value')->getValue();
283       if (!empty($serialized_value)) {
284         $tags += unserialize($serialized_value);
285       }
286     }
287
288     return $tags;
289   }
290
291   /**
292    *
293    *
294    * @param ContentEntityInterface $entity
295    */
296   public function getDefaultMetatags(ContentEntityInterface $entity = NULL) {
297     // Get general global metatags
298     $metatags = $this->getGlobalMetatags();
299     // If that is empty something went wrong.
300     if (!$metatags) {
301       return;
302     }
303
304     // Check if this is a special page.
305     $special_metatags = $this->getSpecialMetatags();
306
307     // Merge with all globals defaults.
308     if ($special_metatags) {
309       $metatags->set('tags', array_merge($metatags->get('tags'), $special_metatags->get('tags')));
310     }
311
312     // Next check if there is this page is an entity that has meta tags.
313     // @TODO: Think about using other defaults, e.g. views. Maybe use plugins?
314     else {
315       if (is_null($entity)) {
316         $entity = metatag_get_route_entity();
317       }
318
319       if (!empty($entity)) {
320         // Get default metatags for a given entity.
321         $entity_defaults = $this->getEntityDefaultMetatags($entity);
322         if ($entity_defaults != NULL) {
323           $metatags->set('tags', array_merge($metatags->get('tags'), $entity_defaults));
324         }
325       }
326     }
327
328     return $metatags->get('tags');
329   }
330
331   /**
332    *
333    */
334   public function getGlobalMetatags() {
335     return $this->metatagDefaults->load('global');
336   }
337
338   /**
339    *
340    */
341   public function getSpecialMetatags() {
342     $metatags = NULL;
343
344     if (\Drupal::service('path.matcher')->isFrontPage()) {
345       $metatags = $this->metatagDefaults->load('front');
346     }
347     elseif (\Drupal::service('current_route_match')->getRouteName() == 'system.403') {
348       $metatags = $this->metatagDefaults->load('403');
349     }
350     elseif (\Drupal::service('current_route_match')->getRouteName() == 'system.404') {
351       $metatags = $this->metatagDefaults->load('404');
352     }
353
354     return $metatags;
355   }
356
357   /**
358    *
359    */
360   public function getEntityDefaultMetatags(ContentEntityInterface $entity) {
361     $entity_metatags = $this->metatagDefaults->load($entity->getEntityTypeId());
362     $metatags = [];
363     if ($entity_metatags != NULL) {
364       // Merge with global defaults.
365       $metatags = array_merge($metatags, $entity_metatags->get('tags'));
366     }
367
368     // Finally, check if we should apply bundle overrides.
369     $bundle_metatags = $this->metatagDefaults->load($entity->getEntityTypeId() . '__' . $entity->bundle());
370     if ($bundle_metatags != NULL) {
371       // Merge with existing defaults.
372       $metatags = array_merge($metatags, $bundle_metatags->get('tags'));
373     }
374
375     return $metatags;
376   }
377
378   /**
379    * Generate the elements that go in the attached array in
380    * hook_page_attachments.
381    *
382    * @param array $tags
383    *   The array of tags as plugin_id => value.
384    * @param object $entity
385    *   Optional entity object to use for token replacements.
386    *
387    * @return array
388    *   Render array with tag elements.
389    */
390   public function generateElements($tags, $entity = NULL) {
391     $elements = [];
392     $tags = $this->generateRawElements($tags, $entity);
393
394     foreach ($tags as $name => $tag) {
395       if (!empty($tag)) {
396         $elements['#attached']['html_head'][] = [
397           $tag,
398           $name,
399         ];
400       }
401     }
402
403     return $elements;
404   }
405
406   /**
407    * Generate the actual meta tag values.
408    *
409    * @param array $tags
410    *   The array of tags as plugin_id => value.
411    * @param object $entity
412    *   Optional entity object to use for token replacements.
413    *
414    * @return array
415    *   Render array with tag elements.
416    */
417   public function generateRawElements($tags, $entity = NULL) {
418     $rawTags = [];
419
420     $metatag_tags = $this->tagPluginManager->getDefinitions();
421
422     // Order the elements by weight first, as some systems like Facebook care.
423     uksort($tags, function ($tag_name_a, $tag_name_b) use ($metatag_tags) {
424       $weight_a = isset($metatag_tags[$tag_name_a]['weight']) ? $metatag_tags[$tag_name_a]['weight'] : 0;
425       $weight_b = isset($metatag_tags[$tag_name_b]['weight']) ? $metatag_tags[$tag_name_b]['weight'] : 0;
426
427       return ($weight_a < $weight_b) ? -1 : 1;
428     });
429
430     // Each element of the $values array is a tag with the tag plugin name
431     // as the key.
432     foreach ($tags as $tag_name => $value) {
433       // Check to ensure there is a matching plugin.
434       if (isset($metatag_tags[$tag_name])) {
435         // Get an instance of the plugin.
436         $tag = $this->tagPluginManager->createInstance($tag_name);
437
438         // Render any tokens in the value.
439         $token_replacements = [];
440         if ($entity) {
441           // @TODO: This needs a better way of discovering the context.
442           if ($entity instanceof ViewEntityInterface) {
443             // Views tokens require the ViewExecutable, not the config entity.
444             // @todo Can we move this into metatag_views somehow?
445             $token_replacements = ['view' => $entity->getExecutable()];
446           }
447           else {
448             $token_replacements = [$entity->getEntityTypeId() => $entity];
449           }
450         }
451
452         // Set the value as sometimes the data needs massaging, such as when
453         // field defaults are used for the Robots field, which come as an array
454         // that needs to be filtered and converted to a string.
455         // @see @Robots::setValue().
456         $tag->setValue($value);
457         $langcode = \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
458
459         if ($tag->type() === 'image') {
460           $processed_value = $this->tokenService->replace($tag->value(), $token_replacements, ['langcode' => $langcode]);
461         }
462         else {
463           $processed_value = PlainTextOutput::renderFromHtml(htmlspecialchars_decode($this->tokenService->replace($tag->value(), $token_replacements, ['langcode' => $langcode])));
464         }
465
466         // Now store the value with processed tokens back into the plugin.
467         $tag->setValue($processed_value);
468
469         // Have the tag generate the output based on the value we gave it.
470         $output = $tag->output();
471
472         if (!empty($output)) {
473           $rawTags[$tag_name] = $output;
474         }
475       }
476     }
477
478     return $rawTags;
479   }
480
481   /**
482    * Returns a list of fields handled by Metatag.
483    *
484    * @return array
485    */
486   protected function fieldTypes() {
487     //@TODO: Either get this dynamically from field plugins or forget it and just hardcode metatag where this is called.
488     return ['metatag'];
489   }
490
491 }