3 namespace Drupal\metatag;
5 use Drupal\Component\Render\PlainTextOutput;
6 use Drupal\Core\Entity\ContentEntityInterface;
7 use Drupal\Core\Language\LanguageInterface;
8 use Drupal\Core\Logger\LoggerChannelFactoryInterface;
9 use Drupal\field\Entity\FieldConfig;
12 * Class MetatagManager.
14 * @package Drupal\metatag
16 class MetatagManager implements MetatagManagerInterface {
18 protected $groupPluginManager;
19 protected $tagPluginManager;
21 protected $tokenService;
24 * Metatag logging channel.
26 * @var \Drupal\Core\Logger\LoggerChannelInterface
31 * Constructor for MetatagManager.
33 * @param MetatagGroupPluginManager $groupPluginManager
34 * @param MetatagTagPluginManager $tagPluginManager
35 * @param MetatagToken $token
36 * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $channelFactory
38 public function __construct(MetatagGroupPluginManager $groupPluginManager,
39 MetatagTagPluginManager $tagPluginManager,
41 LoggerChannelFactoryInterface $channelFactory) {
42 $this->groupPluginManager = $groupPluginManager;
43 $this->tagPluginManager = $tagPluginManager;
44 $this->tokenService = $token;
45 $this->logger = $channelFactory->get('metatag');
51 public function tagsFromEntity(ContentEntityInterface $entity) {
54 $fields = $this->getFields($entity);
56 /* @var FieldConfig $field_info */
57 foreach ($fields as $field_name => $field_info) {
58 // Get the tags from this field.
59 $tags = $this->getFieldTags($entity, $field_name);
66 * Gets the group plugin definitions.
71 protected function groupDefinitions() {
72 return $this->groupPluginManager->getDefinitions();
76 * Gets the tag plugin definitions.
81 protected function tagDefinitions() {
82 return $this->tagPluginManager->getDefinitions();
88 public function sortedGroups() {
89 $metatag_groups = $this->groupDefinitions();
91 // Pull the data from the definitions into a new array.
93 foreach ($metatag_groups as $group_name => $group_info) {
94 $id = $group_info['id'];
95 $groups[$id]['label'] = $group_info['label']->render();
96 $groups[$id]['description'] = $group_info['description'];
97 $groups[$id]['weight'] = $group_info['weight'];
100 // Create the 'sort by' array.
102 foreach ($groups as $group) {
103 $sort_by[] = $group['weight'];
106 // Sort the groups by weight.
107 array_multisort($sort_by, SORT_ASC, $groups);
115 public function sortedTags() {
116 $metatag_tags = $this->tagDefinitions();
118 // Pull the data from the definitions into a new array.
120 foreach ($metatag_tags as $tag_name => $tag_info) {
121 $id = $tag_info['id'];
122 $tags[$id]['label'] = $tag_info['label']->render();
123 $tags[$id]['group'] = $tag_info['group'];
124 $tags[$id]['weight'] = $tag_info['weight'];
127 // Create the 'sort by' array.
129 foreach ($tags as $key => $tag) {
130 $sort_by['group'][$key] = $tag['group'];
131 $sort_by['weight'][$key] = $tag['weight'];
134 // Sort the tags by weight.
135 array_multisort($sort_by['group'], SORT_ASC, $sort_by['weight'], SORT_ASC, $tags);
143 public function sortedGroupsWithTags() {
144 $groups = $this->sortedGroups();
145 $tags = $this->sortedTags();
147 foreach ($tags as $tag_id => $tag) {
148 $tag_group = $tag['group'];
150 if (!isset($groups[$tag_group])) {
151 // If the tag is claiming a group that has no matching plugin, log an
152 // error and force it to the basic group.
153 $this->logger->error("Undefined group '%group' on tag '%tag'", ['%group' => $tag_group, '%tag' => $tag_id]);
154 $tag['group'] = 'basic';
155 $tag_group = 'basic';
158 $groups[$tag_group]['tags'][$tag_id] = $tag;
167 public function form(array $values, array $element, array $token_types = [], array $included_groups = NULL, array $included_tags = NULL) {
169 // Add the outer fieldset.
171 '#type' => 'details',
174 $element += $this->tokenService->tokenBrowser($token_types);
176 $groups_and_tags = $this->sortedGroupsWithTags();
179 foreach ($groups_and_tags as $group_id => $group) {
180 // Only act on groups that have tags and are in the list of included
181 // groups (unless that list is null).
182 if (isset($group['tags']) && (is_null($included_groups) || in_array($group_id, $included_groups))) {
183 // Create the fieldset.
184 $element[$group_id]['#type'] = 'details';
185 $element[$group_id]['#title'] = $group['label'];
186 $element[$group_id]['#description'] = $group['description'];
187 $element[$group_id]['#open'] = $first;
190 foreach ($group['tags'] as $tag_id => $tag) {
191 // Only act on tags in the included tags list, unless that is null.
192 if (is_null($included_tags) || in_array($tag_id, $included_tags)) {
193 // Make an instance of the tag.
194 $tag = $this->tagPluginManager->createInstance($tag_id);
196 // Set the value to the stored value, if any.
197 $tag_value = isset($values[$tag_id]) ? $values[$tag_id] : NULL;
198 $tag->setValue($tag_value);
200 // Create the bit of form for this tag.
201 $element[$group_id][$tag_id] = $tag->form($element);
211 * Returns a list of the metatag fields on an entity.
213 protected function getFields(ContentEntityInterface $entity) {
216 if ($entity instanceof ContentEntityInterface) {
217 // Get a list of the metatag field types.
218 $field_types = $this->fieldTypes();
220 // Get a list of the field definitions on this entity.
221 $definitions = $entity->getFieldDefinitions();
223 // Iterate through all the fields looking for ones in our list.
224 foreach ($definitions as $field_name => $definition) {
225 // Get the field type, ie: metatag.
226 $field_type = $definition->getType();
228 // Check the field type against our list of fields.
229 if (isset($field_type) && in_array($field_type, $field_types)) {
230 $field_list[$field_name] = $definition;
239 * Returns a list of the metatags with values from a field.
244 protected function getFieldTags(ContentEntityInterface $entity, $field_name) {
246 foreach ($entity->{$field_name} as $item) {
247 // Get serialized value and break it into an array of tags with values.
248 $serialized_value = $item->get('value')->getValue();
249 if (!empty($serialized_value)) {
250 $tags += unserialize($serialized_value);
258 * Generate the elements that go in the attached array in
259 * hook_page_attachments.
262 * The array of tags as plugin_id => value.
263 * @param object $entity
264 * Optional entity object to use for token replacements.
266 * Render array with tag elements.
268 public function generateElements($tags, $entity = NULL) {
269 $metatag_tags = $this->tagPluginManager->getDefinitions();
272 // Each element of the $values array is a tag with the tag plugin name
274 foreach ($tags as $tag_name => $value) {
275 // Check to ensure there is a matching plugin.
276 if (isset($metatag_tags[$tag_name])) {
277 // Get an instance of the plugin.
278 $tag = $this->tagPluginManager->createInstance($tag_name);
280 // Render any tokens in the value.
281 $token_replacements = [];
283 $token_replacements = [$entity->getEntityTypeId() => $entity];
286 // Set the value as sometimes the data needs massaging, such as when
287 // field defaults are used for the Robots field, which come as an array
288 // that needs to be filtered and converted to a string.
289 // @see @Robots::setValue().
290 $tag->setValue($value);
291 $langcode = \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
292 if ($tag->type() === 'image') {
293 $processed_value = $this->tokenService->replace($tag->value(), $token_replacements, ['langcode' => $langcode]);
296 $processed_value = PlainTextOutput::renderFromHtml(htmlspecialchars_decode($this->tokenService->replace($tag->value(), $token_replacements, ['langcode' => $langcode])));
299 // Now store the value with processed tokens back into the plugin.
300 $tag->setValue($processed_value);
302 // Have the tag generate the output based on the value we gave it.
303 $output = $tag->output();
305 if (!empty($output)) {
306 $elements['#attached']['html_head'][] = [
318 * Returns a list of fields handled by Metatag.
322 protected function fieldTypes() {
323 //@TODO: Either get this dynamically from field plugins or forget it and just hardcode metatag where this is called.