29
Feb
2012

Parsing a Youtube URL

More than a few times in the past few years I've been tasked with enabling a user to add a youtube video to a piece of content in Drupal.

Sure there are specific modules out there that try to address this, but more often than not they do way more than what is needed, which generally ends up being simply that -- I want a youtube video added to this content type.

Well, I've once again had to deal with this situation, and this time around I think I found a fairly elegant solution.

For starters, I have a content type called Gallery Video, the idea being that there will be multiple galleries on this client's website, with a mixture of images and video within.  

The determination had already been made to leverage youtube for hosting the videos.

In the past, I've simply had users paste just the youtube video ID into a field, and that's worked out well, except that almost certainly will require some attention during training to be sure they know how to find the video id. If you've read my Make it Mom Friendly post, I'm sure you'll agree that this probably isn't the best solution.

So, I wanted to find as fool proof, or "mom friendly" if you will, way to implement this functionality; and, since this will be a gallery I wanted to pull in the poster image of the video to display as a thumbnail when the video isn't actually playing.

This post isn't going to go in depth with how I'm actually going to format the display and whatnot, rather focus on getting the data we need added to our content.

Ok, lets get on with it then.

So, my Gallery Video content type has a few fields:

  • Title (this is of course the built in node title, and I'm actually hiding and auto populating it with auto_nodetitle)
  • Youtube (text field)
  • Caption (renamed default body field)
  • Gallery Reference (node reference field, it too is being autopopulated by leveraging the awesomeness of context_admin)
  • Poster Image (image field)
  • Youtube Video ID (text field)

Ok, so the important ones are in bold.  I'm actually hiding the poster image and youtube video id fields with a form alter, which looks like this:

/**
 * Implements hook_form_alter().
 */
function content_type_gallery_video_form_alter(&$form, &$form_state, $form_id) {
  if (isset($form['#node_edit_form']) && $form['#node_edit_form'] === TRUE) {
    if ($form['#node']->type == 'gallery_video') {
      array_unshift($form['#validate'], 'content_type_gallery_video_validate');
      $form['field_youtube_id']['#access'] = FALSE;
      $form['field_gallery_video_image']['#access'] = FALSE;
    }
  }
}

If you're familiar with form_alter hooks, you're probably wondering why I'm not using a form_FORM_ID_alter(). As it turns out, context_admin actually uses a completely different form_id than what Drupal normally generates for node forms, and while I could use that alternate form_id this works just fine.

So what it's doing is making sure the #node_edit_form attribute is set, and is true (which will be the case on ANY node_edit form, regardless of the form_id, if so, checks to be sure we've got the right node form, in this case gallery_video. Then we add a custom validator function as the first item in the #validate array. Finally, we hide our two fields that will be auto-populated by setting #access to false.

Next up, we need to implement the validate function. That looks like this:

/**
 * Custom node validation function
 * Adds Youtube poster image, and extracts youtube video id from provided URL.
 */
function content_type_gallery_video_validate($form, &$form_state) {
  // Extract Youtube ID from URL.
  $video = parse_url($form_state['values']['field_gallery_video_url'][$form['language']['#value']][0]['value']);
  parse_str($video['query'], $vid);
 
  // Set the value to the youtube id field.
  $form_state['values']['field_youtube_id'][$form['language']['#value']][0]['value'] = $vid['v'];
  $form_state['input']['field_youtube_id'][$form['language']['#value']][0]['value'] = $vid['v'];
 
  // Retrieve Youtube poster image file.
  $youtube_img = file_get_contents('http://img.youtube.com/vi/'. $vid['v'] .'/0.jpg');
  $filename  = 'public://'. $form_state['field']['field_gallery_video_image'][$form['language']['#value']]['instance']['settings']['file_directory'];
  if (!file_exists($filename)) {
    mkdir($filename, 0, TRUE);
  }
  $filename .= '/'. $vid['v'] .'.jpg';
 
  // Create a drupal file record for the newly acquired image
  $youtube_img = file_save_data($youtube_img, $filename, FILE_EXISTS_RENAME);
 
  global $user;
  if (file_exists($youtube_img->uri)) {
    $image_info = image_get_info($youtube_img->uri);
 
    $name_arr = explode('/', $youtube_img->uri);
    $name = end($name_arr);
 
    $file = array(
      'fid'       => $youtube_img->fid,
      'uid'       => $user->uid,
      'filename'  => $name,
      'filepath'  => $youtube_img->uri,
      'filemime'  => $image_info['mime_type'],
      'filesize'  => $image_info['file_size'],
      'status'    => FILE_STATUS_PERMANENT,
      'timestamp' => REQUEST_TIME,
    );
 
    drupal_write_record('files', $file);
 
    // Set the value in our field
    $form_state['values']['field_gallery_video_image'][$form['language']['#value']][0]['fid'] = $youtube_img->fid;
    $form_state['input']['field_gallery_video_image'][$form['language']['#value']][0]['fid'] = $youtube_img->fid;
  }
}

Ok, ok.. this might not be the most ideal way to add data to the node, but it works, and I'm ok with it. Basically we're injecting values into the form_state to make it appear as though they were submit along with the rest of the node form, so upon node save, Drupal will happily plug those values into the proper field tables in the database.

Lets break this one down a bit.  

  1. Using PHPs parse_url() and parse_str() functions, we're actually reaching into the URL our user supplied in the field, and extracting just the youtube video id (the v parameter).
  2. We then assign that value to the aforementioned Youtube Video ID field in both the form state values and input arrays
  3. Next, we use that same video id value, and pull the data for that video's poster image from youtube.
    Try it yourself.. http://img.youtube.com/vi/W-v5u0rUvoA/0.jpg ... where W-v5u0rUvoA is the video id.
  4. Now that we've got our image data, we start building up the path, but grabbing the configured file directory from the field instance settings.
  5. Before tacking the filename onto the end, we check if that file directory exists, and if not create it (recursively)
  6. Then we tack on the filename, which will simply be the youtube video id with .jpg appended to the end.
  7. Then we call Drupal's file_save_data() function to actually write out the file to disk.
  8. Next we gather up the file metadata, build up an array with the pertinent info, and call drupal_write_record to add that file to the DB.
  9. Finally, we set the file ID value in our field, in both the form state values and input arrays.

Assuming any other validation succeeds, the node should be created, and those fields populated.  Now you can theme it out however you'd like.

Note: In order for auto_nodetitle to fully work with context admin, it requires a patch.