DNotes LLC

DNotes LLC

Drupal development, hosting, and consulting

Drupal 7 metatag tokens for external YouTube or Vimeo video thumbnails in a media field

Facebook post with embedded media player
Facebook post with embedded media player

Media module is a popular tool for embedding video from providers like YouTube and Vimeo on a Drupal 7 site, and it works like a charm when people come to your site to view those videos. However, if you want your visitors to be able to share your pages on social networks with the appropriate images and video players, it's a little tricky. That's because the Metatag module, which provides the header tags needed by sites like Facebook and Twitter, doesn't come with an easy way to get the preview image for a media field or properties like the height and width for a media player.

Metatag configuration : you'll need a token

Enabling the requisite Metatag modules, including the submodules for things like opengraph and twitter cards, will give you configuration screens at admin/config/search/metatags (Configuration > Search and metadata > Metatags). You can create a new "Metatag default" (is it really a default if you create it?) for a node type or taxonomy vocabulary, or edit one of the existing ones, and you'll see a lot of tags for which you can specify a value. For something like og:image, you need a url to an image, and this is where you need a token.

Unfortunately, if you are using a media field to embed videos from providers like YouTube and Vimeo, there is no token for getting the preview image - at least not without some complicated setup - and none at all for height and width. You could do this by implementing hook_metatag_metatags_view_alter() and changing the values when the metatags are being created, but that would potentially override later configuration by site administrators. So in some cases a better option may be to implement your own tokens.

Implementing new tokens and token types

To implement your own tokens in Drupal 7, you'll need to implement two hooks: hook_token_info and hook_tokens. These two hooks should go in a separate include file named module.tokens.inc, and you do not need to mention this file in your module's .info file. The documentation on api.drupal.org is a good place to start in understanding them, so I'll just be explaining some background information and undocumented features below.

hook_token_info()

My plan for tackling this problem was to provide tokens for "field_media" (my field with the external videos) that could give me a video link, an image link, a height or a width. In practical terms, I needed to provide the token implementation for field_media of a custom type that would allow me to target any of those four properties.

My first question was: How do other fields get their tokens? It turns out that functionality is provided by the Token module itself in the field_token_info_alter() function; it is not a part of the token implementation in core. It's done in an alter hook so that token module can refrain from providing token implementations for fields that other modules have covered. How thoughtful. I decided to implement tokens for field_media on every entity type (nodes, taxonomy terms, etc.) that has it on one of its bundles (node types, vocabularies, etc.).

<?php
  // Get a quick listing of where all the fields are used.
  $all_fields = field_info_field_map();

  // Only continue if the desired field is in use.
  if (!empty($all_fields['field_media'])) {

    // Extract the entity types wherein any bundles use the desired field.
    $entity_types = array_keys($all_fields['field_media']['bundles']);
    foreach ($entity_types as $type) {

      // Implement tokens for field_media on each of the entity types, using the custom token type 'emvideo'.
      $return['tokens'][$type]['field_media'] = array(
        'name' => 'Media',
        'description' => t('A video from YouTube or Vimeo.'),
        'type' => 'emvideo',
      );
    }
  }
?>

That should work - except I anticipated having a token type called 'emvideo' with all the properties I want, so I needed to create it. This actually requires two things: you need to declare the token type and return it under the 'types' key, and you need to declare its properties under the 'tokens' key. Helpful hint: add 'hidden' => TRUE to your type definition, and you won't get unusable global tokens of type Video showing up in your token browser.

<?php
  $return['types']['emvideo'] = array(
    'name' => t('Video'),
    'description' => t('Tokens related to embedded video from YouTube or Vimeo.'),
    'hidden' => TRUE,          // These aren't global, after all.
  );

  $return['tokens']['emvideo'] = array(
    'video' => array(
      'name' => t('Video'),
      'description' => t('The playable url of the video.'),
    ),
    'image' => array(
      'name' => t('Image'),
      'description' => t('The url of the thumbnail for the video.'),
    ),
    'height' => array(
      'name' => t('Height'),
      'description' => t('The height of the video.'),
    ),
    'width' => array(
      'name' => t('Width'),
      'description' => t('The width of the video.'),
    ),
  );
?>

Oh, and don't forget to

return $return;.

If you put this code in your hook_token_info implementation and clear the cache, the next time you go to configure your Metatag defaults you'll see a new set of tokens for field_media, all decked out and waiting to give you the data you need. Except you haven't yet told it how.

hook_tokens($type, $tokens, array $data = array(), array $options = array(), $etc = 'Maybe this is why D8 is OO now.')

Hook tokens comes with your standard confusing array of arrayed arrays of tokens and data and options. To get a handle on this I had to check out a couple of other implementations, including Token module's field_tokens. The basic structure here is as follows:

<?php
  $replacements = array();

  // You must check to make sure that you are able to deal with these tokens
  if ($type = 'entity' && !empty($data['entity']) && !empty($data['entity']->field_media)) {

    foreach ($tokens as $name => $original) {

      // You'll probably need some code here to arrive at a $value.
      $replacements[$original] = $value;

    }
  }
  return $replacements;
?>

In this specific case, I want to get information about the video in field_media and return that in $replacements[$original]. There are probably several ways to do this, and I'm not yet confident that I've chosen the best solution, or thought of all the possible ways this could go wrong, so definitely take this next bit with the proverbial grain of salt.

First of all, your video is hidden in $data['entity']->field_media[$language_code][$index], so to avoid dealing with translations and multiple fields in this post, let's say that
$video = $data['entity']->field_media['und'][0];
This will give you a file array with all the data from file_managed about your video file.

Your $video['uri'] is the most important part. It will be something like vimeo://v/97200943. Media module also saves the image for that video in your files folder at the uri public://files/media-vimeo/97200943.jpg. Let's call that $image_uri. I wanted four properties about the video - a url to the video, a url to the image, a height, and a width - and for now I have decided to get the height and width from that image file:

$image_info = image_get_info($image_uri);

  • 'video': $replacements['original'] = file_create_url($video['uri']);
  • 'image': $replacements['original'] = file_create_url($image_uri);
  • 'height': $replacements['original'] = $image_info['height']
  • 'width': $replacements['original'] = $image_info['width']

Configuring Metatag defaults, take two

Now that you have the tokens and they work, go back to the Metatag configuration screens and enter your configuration for the Video node type. For og:image, use [node:field_media:image]. I think you should also be able to use og:video with [node:field_media:video]. If you're wanting to use a "player" Twitter card, you'll need all four of the tokens you created.

Exporting Metatag defaults

Want to put your Metatag defaults in code? Of course you do! After you save the configuration, export it. You should get a bunch of text with all kinds of data about a $config object. Copy that text and put it in a new file named .metatag.inc, in your module's base directory. Again, there is no need to mention that file in .info.

Set up .metatag.inc like this:

<?php
function yourmodule_metatag_config_default() {

  // paste the export text in here

  $configs[$config->instance] = $config;
  return $configs;
}
?>

Since Metatag uses ctools exportables, you also need to let ctools know that your module is using them. The following goes in your .module file:

<?php
function yourmodule_ctools_plugin_api($owner, $api) {
  if ($owner == 'metatag' && $api == 'metatag') {
    return array('version' => 1);
  }
}
?>

That's it - you can actually copy that code above and paste it into your .module file. Just don't forget to change the "yourmodule" to the actual name of your module. After that's done, clear the cache and you'll see you can revert the Metatag configuration for the Video nodes to the version in code.

Want another example?

My second task along these lines was setting up metatags for photo galleries, a node type with a multiple-entry field for gallery images. I used the same technique, except this time I didn't need an extra token type; instead, I just needed to implement the tokens for field_gallery_images and set the 'type' => 'array', which is a standard token type provided by Token module. That gave me the ability to have tokens like [node:field_gallery_images:first] and [node:field_gallery_images:value:2]. I'm hoping that these will make it possible to implement gallery-type twitter cards.