You are here

media_youtube.module in Media: YouTube 7.2

Same filename and directory in other branches
  1. 6 media_youtube.module
  2. 7.3 media_youtube.module
  3. 7 media_youtube.module

Provides a stream wrapper and formatters appropriate for accessing and displaying YouTube videos.

File

media_youtube.module
View source
<?php

/**
 * @file
 * Provides a stream wrapper and formatters appropriate for accessing and
 * displaying YouTube videos.
 */

/**
 * This is the rest point for the YouTube api.
 *
 * Avoid using the gdata api url when possible. Too many calls will result in
 * throttling and 403 errors.
 */
define('MEDIA_YOUTUBE_REST_API', variable_get('media_youtube_api_url', ''));

// Load all YouTube file formatters.
require_once dirname(__FILE__) . '/includes/media_youtube.formatters.inc';

/**
 * Implements hook_media_internet_providers().
 */
function media_youtube_media_internet_providers() {
  return array(
    'MediaInternetYouTubeHandler' => array(
      'title' => t('YouTube'),
    ),
  );
}

/**
 * Implements hook_stream_wrappers().
 */
function media_youtube_stream_wrappers() {
  return array(
    'youtube' => array(
      'name' => t('YouTube videos'),
      'class' => 'MediaYouTubeStreamWrapper',
      'description' => t('Remote videos hosted on the YouTube video-sharing website.'),
      'type' => STREAM_WRAPPERS_READ_VISIBLE,
    ),
  );
}

/**
 * Implements hook_theme().
 */
function media_youtube_theme($existing, $type, $theme, $path) {
  return array(
    'media_youtube_video' => array(
      'variables' => array(
        'uri' => NULL,
        'options' => array(),
      ),
      'file' => 'media_youtube.theme.inc',
      'path' => $path . '/themes',
      'template' => 'media-youtube-video',
    ),
  );
}

/**
 * Implements hook_media_parse().
 *
 * @todo This hook should be deprecated. Refactor Media module to not call it
 * any more, since media_internet should be able to automatically route to the
 * appropriate handler.
 */
function media_youtube_media_parse($embed_code) {
  $handler = new MediaInternetYouTubeHandler($embed_code);
  return $handler
    ->parse($embed_code);
}

/**
 * Implements hook_file_mimetype_mapping_alter().
 */
function media_youtube_file_mimetype_mapping_alter(&$mapping) {
  $mapping['mimetypes'][] = 'video/youtube';
}

/**
 * Implements hook_ctools_plugin_api().
 */
function media_youtube_ctools_plugin_api($module, $api) {
  if ($module == 'file_entity' && $api == 'file_default_displays') {
    return array(
      'version' => 1,
    );
  }
}

/**
 * YouTube search tab for the Media browser.
 */

/**
 * Implements hook_media_browser_plugin_info().
 *
 * Commented out for release versions, active in dev versions. To enable the
 * YouTube media browser tab, uncomment this function.
 */
function media_youtube_media_browser_plugin_info() {
  $info['youtube'] = array(
    'title' => t('YouTube'),
    'class' => 'MediaYouTubeBrowser',
  );
  return $info;
}

/**
 * Provides a form for adding media items from YouTube search.
 */
function media_youtube_add($form, &$form_state = array()) {
  if (strlen(variable_get('media_youtube_api_key', '')) == 0) {
    $form = array();
    $form['message'] = array(
      '#markup' => t('YouTube Data API v3 is required. You can obtain your key ' . '<a href="https://developers.google.com/youtube/v3/getting-started">' . 'here</a>. Then you need to put it in the configurations page click <a href="@url"> here ' . '</a> to go the configuration page.', array(
        '@url' => url('admin/config/media/media-youtube'),
      )),
    );
    $form['actions'] = array(
      '#type' => 'actions',
    );
    return $form;
  }
  module_load_include('inc', 'media', 'includes/media.browser');

  // Our search term can come from the form, or from the pager.
  $term = isset($form_state['input']['search']) ? $form_state['input']['search'] : (isset($_GET['search']) ? $_GET['search'] : '');
  $form['search'] = array(
    '#type' => 'textfield',
    '#title' => t('Search'),
    '#description' => t('Input a phrase or tags to search.'),
    '#default_value' => $term,
  );
  $form['apply'] = array(
    '#type' => 'button',
    '#value' => t('Apply'),
  );

  // This is our half-assed pager.
  $page = isset($_GET['page-yt']) ? $_GET['page-yt'] : 0;
  if (isset($form_state['input']['search'])) {

    // Reset the pager when we press apply.
    $page = 0;
  }
  if (!empty($term)) {
    $search = media_youtube_video_search(array(
      'q' => $term,
      'max-results' => 12,
      'start-index' => $page * 12 + 1,
    ));
  }
  $form['videos']['#prefix'] = '<div id="container"><div id="scrollbox"><ul id="media-browser-library-list" class="media-list-thumbnails">';
  $form['videos']['#suffix'] = '</ul><div id="status"></div></div></div>';
  $empty = FALSE;
  $files = array();
  if (!isset($search->items)) {
    $empty = TRUE;
  }
  else {

    // $search['entry'] is different depending on whether there is a single
    // result or multiple results. So normalise it.
    $videos = $search->items;
    foreach ($videos as $video) {
      if ($video->id->kind == "youtube#video") {
        try {
          $uri = 'youtube://v/' . $video->id->videoId;
        } catch (Exception $e) {

          // Ignore invalid videos.
          continue;
        }

        // Create a temporary file object for our retrieved video.
        $file = file_uri_to_object($uri);
        $file->type = 'video';
        $file->filemime = 'video/youtube';
        if (!isset($file->fid)) {
          $file->fid = 0;
        }
        media_browser_build_media_item($file);
        $attributes = array(
          'data-uri' => $uri,
          'class' => array(
            'media-youtube-wrapper',
          ),
        );
        $form['videos'][$uri] = array(
          '#markup' => $file->preview,
          '#prefix' => '<li' . drupal_attributes($attributes) . '>',
          '#suffix' => '</li>',
        );
        $files[$uri] = $file;
      }
    }
  }
  if (!count($files)) {
    $empty = TRUE;
  }
  if ($empty) {
    $form['empty'] = array(
      '#markup' => '<div class="empty-message">' . t('No videos match your search criteria. Please try again.') . '</div>',
    );
  }
  $query = $_GET;
  if ($term !== '') {
    $query['search'] = $term;
  }
  $dest = $query['q'];
  unset($query['q']);
  $prev = $next = '';
  if ($page) {
    $query['page-yt'] = $page - 1;
    $prev = l(t('previous'), $dest, array(
      'query' => $query,
    ));
  }
  $query['page-yt'] = $page + 1;
  if (!$empty) {
    $next = l(t('next'), $dest, array(
      'query' => $query,
    ));
  }
  $form['pager'] = array(
    '#markup' => $prev . ' ' . $next,
  );
  $form['submitted-video'] = array(
    '#type' => 'hidden',
    '#default_value' => FALSE,
  );

  // Add the files to JS so that they are accessible inside the browser
  drupal_add_js(array(
    'media' => array(
      'files' => $files,
    ),
  ), 'setting');

  // Add media browser javascript and CSS.
  drupal_add_js(drupal_get_path('module', 'media_youtube') . '/js/media-youtube.browser.js');

  // @TODO: Remove deprecated library js and css. They're removed in Media,
  // so let's comment out for now.
  // drupal_add_js(drupal_get_path('module', 'media') . '/js/plugins/media.library.js');
  // drupal_add_css(drupal_get_path('module', 'media') . '/js/plugins/media.library.css');
  $form['actions'] = array(
    '#type' => 'actions',
  );
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

/**
 * Allow stream wrappers to have their chance at validation.
 *
 * Any module that implements hook_media_parse will have an
 * opportunity to validate this.
 *
 * @see media_parse_to_uri()
 */
function media_youtube_add_validate($form, &$form_state) {
  if ($form_state['values']['op'] == t('Apply')) {
    return;
  }
  $uri = $form_state['values']['submitted-video'];
  try {
    $file = file_uri_to_object($uri, TRUE);
  } catch (Exception $e) {
    form_set_error('url', $e
      ->getMessage());
    return;
  }
  if (!$file->uri) {
    form_set_error('url', t('Please select a video.'));
    return;
  }
  $validators = isset($form['#validators']) ? $form['#validators'] : array();
  if ($validators) {

    // Check for errors. @see media_add_upload_validate calls file_save_upload().
    // this code is ripped from file_save_upload because we just want the validation part.
    // Call the validation functions specified by this function's caller.
    $errors = file_validate($file, $validators);
    if (!empty($errors)) {
      $message = t('%uri could not be added.', array(
        '%uri' => $uri,
      ));
      if (count($errors) > 1) {
        $message .= theme('item_list', array(
          'items' => $errors,
        ));
      }
      else {
        $message .= ' ' . array_pop($errors);
      }
      form_set_error('url', $message);
      return FALSE;
    }
  }

  // @TODO: Validate that if we have no $uri that this is a valid file to
  // save. For instance, we may only be interested in images, and it would
  // be helpful to let the user know they passed the HTML page containing
  // the image accidentally. That would also save us from saving the file
  // in the submit step.
  // This is kinda a hack of the same.
  // This should use the file_validate routines that the upload form users.
  // We need to fix the media_parse_to_file routine to allow for a validation.
}

/**
 * @TODO: Document this function.
 */
function media_youtube_add_submit($form, &$form_state) {
  $uri = $form_state['values']['submitted-video'];
  try {

    // Save the remote file
    $file = file_uri_to_object($uri, TRUE);
    file_save($file);
  } catch (Exception $e) {
    form_set_error('url', $e
      ->getMessage());
    return;
  }
  if (!$file->fid) {
    form_set_error('url', t('The file %file could not be saved. An unknown error has occurred.', array(
      '%file' => $uri,
    )));
    return;
  }
  else {
    $form_state['file'] = $file;
  }

  // Redirect to the file edit page after submission.
  if (media_youtube_access('update', $file)) {
    $destination = array(
      'destination' => 'admin/content/file',
    );
    if (isset($_GET['destination'])) {
      $destination = drupal_get_destination();
      unset($_GET['destination']);
    }
    $form_state['redirect'] = array(
      'file/' . $file->fid . '/edit',
      array(
        'query' => $destination,
      ),
    );
  }
  else {
    $form_state['redirect'] = 'admin/content/file';
  }
}

/**
 * Determine if a user may perform the given operation on the specified file.
 *
 * Enables compatibility with Media 1.x and 2.x by providing a wrapper around
 * both media_access() and file_entity_access().
 *
 * @return boolean
 *   TRUE if the operation may be performed, FALSE otherwise.
 *
 * @see media_access()
 * @see file_entity_access()
 */
function media_youtube_access($op, $file = NULL, $account = NULL) {
  if (function_exists('file_entity_access')) {
    $access = file_entity_access($op, $file, $account);
  }
  elseif (function_exists('media_access')) {
    $access = media_access($op, $account);
  }
  else {
    $access = FALSE;
  }
  return $access;
}

/**
 * @TODO: Document this function.
 */
function media_youtube_video_search($options = array()) {
  $options['v'] = 3;
  $options['key'] = variable_get('media_youtube_api_key', '');
  $options['part'] = 'snippet';
  $options['maxResults'] = variable_get('media_youtube_max_results', '5');
  $request = drupal_http_request(url(MEDIA_YOUTUBE_REST_API, array(
    'query' => $options,
  )));
  if (!isset($request->error)) {
    $json = json_decode($request->data);
  }
  else {
    throw new Exception("Error Processing Request. (Error: {$request->code}, {$request->error}, {$request->data})");

    //if request wasn't successful, create object for return to avoid errors

    //$entry = new SimpleXMLElement();
  }

  // return media_youtube_unserialize_xml($entry);
  return $json;
}

/**
 * Recursively converts a SimpleXMLElement object into an array.
 *
 * @param object $xml
 *   The original XML object.
 */
function media_youtube_unserialize_xml($xml) {
  if ($xml instanceof SimpleXMLElement) {
    $xml = (array) $xml;
  }
  if (is_array($xml)) {
    foreach ($xml as $key => $item) {
      $xml[$key] = media_youtube_unserialize_xml($item);
    }
  }
  return $xml;
}

/**
 * Check to ensure that a given id is valid.
 *
 * @param string $id
 *   The YouTube video id.
 * @param boolean $refresh
 *   (Defaults to FALSE) If TRUE, then reset the value from the cache.
 * @return boolean
 *   Returns TRUE if the video is valid.
 *
 * @TODO: How does this compare to MediaInternetYouTubeHandler's validId
 * method, and can we refactor the code to rely on only one of them?
 */
function media_youtube_valid_id($id, $refresh = FALSE) {
  $ids =& drupal_static(__FUNCTION__, array());

  // Return our cached id if allowed, and it exists.
  if (!$refresh && isset($ids[$id])) {
    return $ids[$id];
  }
  elseif (!$refresh && !isset($ids[$id])) {
    return $id;
  }
  elseif (!$refresh && ($cache = cache_get('media_youtube:id:' . $id, 'cache_media_xml'))) {
    $ids[$id] = $cache->data;
    return $ids[$id];
  }
  $url = url(MEDIA_YOUTUBE_REST_API . '/' . $id);
  $response = drupal_http_request($url, array(
    'method' => 'HEAD',
  ));
  $ids[$id] = $response->code == 200;
  cache_set('media_youtube:id:' . $id, $ids[$id], 'cache_media_xml', media_variable_get('xml_cache_expire', 3600));
  return $ids[$id];
}

/**
 * Implements hook_menu().
 *
 * Added to configure YouTube API KEY
 */
function media_youtube_menu() {
  $items['admin/config/media/media-youtube'] = array(
    'title' => "Media: YouTube",
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'media_youtube_settings_form',
    ),
    'access arguments' => array(
      'administer media browser',
    ),
    // TODO: is this permission ok here ? (come from media module)
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Settings form.
 *
 * Added to configure YouTube API KEY
 */
function media_youtube_settings_form($form, &$form_state) {
  $form['media_youtube_api_url'] = array(
    '#type' => 'textfield',
    '#title' => t('YouTube API Url'),
    '#description' => t('YouTube API Url'),
    '#default_value' => variable_get('media_youtube_api_url', ''),
  );
  $form['media_youtube_api_key'] = array(
    '#type' => 'textfield',
    '#title' => t('API key'),
    '#description' => t('The Google API key has to be requested from the Google website. A Google account is needed too. See https://developers.google.com/youtube/v3/getting-started'),
    '#default_value' => variable_get('media_youtube_api_key', ''),
  );
  $form['media_youtube_max_results'] = array(
    '#type' => 'textfield',
    '#title' => t('Maximum search results to display in media browser'),
    '#description' => t('The <em>maxResults</em> parameter specifies the maximum number of items that should be returned in the result set. Acceptable values are 1 to 50, inclusive.'),
    '#default_value' => variable_get('media_youtube_max_results', 5),
  );
  $form = system_settings_form($form);
  return $form;
}

Functions

Namesort descending Description
media_youtube_access Determine if a user may perform the given operation on the specified file.
media_youtube_add Provides a form for adding media items from YouTube search.
media_youtube_add_submit @TODO: Document this function.
media_youtube_add_validate Allow stream wrappers to have their chance at validation.
media_youtube_ctools_plugin_api Implements hook_ctools_plugin_api().
media_youtube_file_mimetype_mapping_alter Implements hook_file_mimetype_mapping_alter().
media_youtube_media_browser_plugin_info Implements hook_media_browser_plugin_info().
media_youtube_media_internet_providers Implements hook_media_internet_providers().
media_youtube_media_parse Implements hook_media_parse().
media_youtube_menu Implements hook_menu().
media_youtube_settings_form Settings form.
media_youtube_stream_wrappers Implements hook_stream_wrappers().
media_youtube_theme Implements hook_theme().
media_youtube_unserialize_xml Recursively converts a SimpleXMLElement object into an array.
media_youtube_valid_id Check to ensure that a given id is valid.
media_youtube_video_search @TODO: Document this function.

Constants

Namesort descending Description
MEDIA_YOUTUBE_REST_API This is the rest point for the YouTube api.