You are here

oembed.module in oEmbed 8

Same filename and directory in other branches
  1. 6.0 oembed.module
  2. 7 oembed.module
  3. 7.0 oembed.module

Core functionality for oEmbed

File

oembed.module
View source
<?php

/**
 * @file
 * Core functionality for oEmbed
 */

/**
 * Implements hook_hook_info().
 */
function oembed_hook_info() {
  $hooks = array(
    'oembed_request_alter',
    'oembed_response_alter',
  );
  return array_fill_keys($hooks, array(
    'group' => 'oembed',
  ));
}

/**
 * Implements hook_help().
 */
function oembed_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.oembed':
      $output = '<p>' . t('oEmbed module will allow your Drupal site to embed content from <a href="@oembed">oEmbed</a>-providers as well as for the site to become an oEmbed-provider itself so that other oEmbed-enabled websites can easily embed your content.', array(
        '@oembed' => 'http://www.oembed.com/',
      )) . '</p>';
      $output .= '<p>' . t('Add or enable <a href="@provider">providers</a> to embed content from other sites.', array(
        '@provider' => url('admin/build/oembed/provider'),
      )) . '</p>';
      $output .= '<p>' . t('Adds an input filter for replacing oEmbed enabled URLs with embedded content') . '</p>';
      return $output;
    case 'oembed.settings':
      $output = '<p>' . t('These settings affect how your site behaves when it makes requests as an oEmbed consumer.') . '</p>';
      return $output;
    case 'entity.oembed_provider.collection':
      $output = '<p>' . t('Providers are other web sites with oEmbed endpoints whose content you can embed on your site.') . '</p>';
      return $output;
    case 'oembed.sandbox.form':
      $output = '<p>' . t('Use this form to test your configuration of provider plugins and endpoints.') . '</p>';
      return $output;
  }
}

/**
 * Implements hook_cache_flush().
 */
function oembed_cache_flush() {

  // Because some oEmbed providers (e.g., http://embed.ly) charge per request,
  // allow cache_oembed to opt out of drupal_flush_all_caches() clearing.
  if (\Drupal::config('oembed.settings')
    ->get('cache.flush')) {
    \Drupal::cache('oembed')
      ->deleteAll();
  }
}

/**
 * Implements hook_cron().
 */
function oembed_cron() {

  // If cache_oembed opts out of oembed_flush_caches(), then system_cron()
  // doesn't clear its expired records, so do so here.
  if (!\Drupal::config('oembed.settings')
    ->get('cache.flush')) {
    \Drupal::cache('oembed')
      ->deleteAll();
  }
}

/**
 * Implements of hook_theme().
 */
function oembed_theme($existing, $type, $theme, $path) {
  return array(
    'oembed' => array(
      'file' => 'oembed.theme.inc',
      'path' => $path . '/theme',
      'variables' => array(
        'embed' => NULL,
      ),
    ),
    'oembed__photo' => array(
      'variables' => array(
        'embed' => NULL,
      ),
      'base hook' => 'oembed',
    ),
    'oembed__rich' => array(
      'variables' => array(
        'embed' => NULL,
      ),
      'base hook' => 'oembed',
    ),
    'oembed__video' => array(
      'variables' => array(
        'embed' => NULL,
      ),
      'base hook' => 'oembed',
    ),
  );
}

/**
 * Fetch data for an embeddable URL.
 *
 * @param string $url
 *   An external URL for the content to embed.
 * @param array $parameters
 *   An associative array of request parameters, with the following keys:
 *   - 'maxwidth'
 *       The maximum width of the embed, in pixels.
 *   - 'maxheight'
 *       The maximum height of the embed, in pixels.
 *   Other keys may be supported by some providers (twitter, youtube, wordpress).
 * @return bool|array
 *   False or an array representing the embeddable data of the URL.
 */
function oembed_get_data($url, $parameters = array()) {
  $parameters = array_filter($parameters);

  /** @var \Bangpound\oEmbed\Consumer $consumer */
  $consumer = \Drupal::service('oembed.consumer');
  try {
    $data = $consumer
      ->get($url, $parameters);
    return $data;
  } catch (\RuntimeException $e) {
    return false;
  }
}

/**
 * Prepare an element based on a oEmbed request.
 *
 * @param $type
 *   Element type.
 * @param $url
 *   URL to embed.
 * @param $parameters
 *   oEmbed request parameters.
 *
 * @return array
 *   A renderable array with the following keys and values:
 *   - #type: The passed-in element $type.
 *   - #url: The passed-in $url.
 *   - #parameters: The passed-in $parameters.
 */
function oembed_render_element($type, $url, $parameters = array()) {
  return array(
    '#type' => $type,
    '#url' => $url,
    '#parameters' => $parameters,
  );
}

/**
 * Generate a string for use as ALT attribute.
 */
function oembed_alt_attr(\Bangpound\oEmbed\Response\Response $embed) {
  $options = array(
    '@type' => $embed
      ->getType(),
  );

  // alt attribute using hopefully available title and provider name.
  if (!empty($embed
    ->getTitle())) {
    $string = '@title';
    $options['@title'] = $embed
      ->getTitle();
  }
  else {
    $string = 'Embedded @type';
  }
  if (!empty($embed
    ->getProviderName())) {
    $string .= ' on @provider_name';
    $options['@provider_name'] = $embed
      ->getProviderName();
  }
  return t($string, $options);
}

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

/**
 * Implement hook_preprocess_file_entity().
 */
function oembed_preprocess_file_entity(&$vars, $hook) {
  if (isset($vars['file']->metadata['oembed'])) {
    $vars['oembed_response'] = $embed = $vars['file']->metadata['oembed'];
    $vars['classes_array'][] = 'oembed-' . $embed['type'];
    if (strpos($embed['provider'], ':')) {
      list($parent, $child) = explode(':', $embed['provider'], 2);
      $vars['classes_array'][] = 'oembed-' . $parent;
      $vars['classes_array'][] = 'oembed-' . $child;
    }
    else {
      $vars['classes_array'][] = 'oembed-' . $embed['provider'];
    }
    $vars['title_attributes_array']['class'][] = 'oembed-title';

    // This conflicts with default file_entity.tpl.php which hardcodes a class attribute.
    $vars['content_attributes_array']['class'][] = 'oembed-content';
  }
}

/**
 * Implements hook_file_formatter_FORMATTER_view().
 */
function oembed_file_formatter_view($file, $display, $langcode) {
  $scheme = file_uri_scheme($file->uri);
  if ($scheme == 'oembed') {
    $wrapper = file_stream_wrapper_get_instance_by_uri($file->uri);

    // Build render attributes array. Prefer file-specific overrides to display settings.
    $attributes = (isset($file->override) ? $file->override : array()) + $display['settings'];
    unset($attributes['attributes']);
    unset($attributes['wmode']);
    $parameters = array();
    if (!empty($display['settings']['wmode'])) {
      $parameters['mode'] = $display['settings']['wmode'];
    }

    // The oEmbed spec defines `maxwidth` and `maxheight` parameters, but some providers
    // support `width` and `height`. Precise dimensions supercede maximums.
    if ($file->type != 'image' && $display['type'] != 'oembed_thumbnail') {
      if (isset($attributes['width'])) {
        $parameters['maxwidth'] = $parameters['width'] = $attributes['width'];
      }
      if (isset($attributes['height'])) {
        $parameters['maxheight'] = $parameters['height'] = $attributes['height'];
      }
    }
    $element = oembed_render_element($display['type'], $wrapper
      ->getExternalUrl(), $parameters);
    $element['#attributes'] = $attributes;

    // Unfortunately, it's necessary to validate the oEmbed response before rendering
    // so that file_view_file() can continue to the next formatter.
    $output = drupal_render($element);
    if ($output) {
      return show($element);
    }
  }
}

/**
 * Implements hook_file_formatter_FORMATTER_view().
 */
function oembed_remote_file_formatter_view($file, $display, $langcode) {
  $scheme = file_uri_scheme($file->uri);
  if ($scheme == 'oembed') {

    // URI of local copy of remote file must be stored because it may be
    // different from the oEmbed URLs. If the URL does not have a valid
    // extension, it will redirect to a URL that does.
    if (!isset($file->metadata['oembed_remote_file_image']) || !file_exists($file->metadata['oembed_remote_file_image'])) {
      $embed = $file->metadata['oembed'];
      if ($embed['type'] == 'photo' && !empty($embed['url'])) {
        $url = $embed['url'];
      }
      else {
        if (isset($embed['thumbnail_url'])) {
          $url = $embed['thumbnail_url'];
        }
      }
      if (isset($url)) {
        $result = drupal_http_request($url);

        // Using the redirect URL's basename might guarantee a path with an
        // appropriate file extension.
        if (isset($result->redirect_url)) {

          // If the redirect and original basenames are identical, do nothing.
          if (drupal_basename($result->redirect_url) != drupal_basename($url)) {
            $url .= '/' . drupal_basename($result->redirect_url);
          }
        }
        $parsed_url = parse_url($url);

        // Store local copies of images using hostname, path and filename of source.
        $path = $parsed_url['host'];
        $path .= drupal_dirname($parsed_url['path']);
        if (substr($path, -1) != '/') {
          $path .= '/';
        }
        $filename = drupal_basename($parsed_url['path']);
        if (strpos($filename, '.') !== FALSE) {
          $filename = file_munge_filename($filename, 'jpg jpeg gif png', FALSE);
        }
        $path .= $filename;
        $local_uri = file_stream_wrapper_uri_normalize(file_default_scheme() . '://oembed/' . $path);
        if (!file_exists($local_uri)) {

          // Drupal dislikes protocol relative URL schemes. Everything should
          // be accessible without HTTPS.
          if (strpos($url, '//') === 0) {
            $url = 'http:' . $url;
          }

          /// Ensure filesystem has directories for new file.
          $dirname = drupal_dirname($local_uri);
          file_prepare_directory($dirname, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);

          // Save the file data to the local directory.
          $files = entity_load('file', FALSE, array(
            'uri' => $local_uri,
          ));
          if (!empty($files)) {
            file_unmanaged_save_data($result->data, $local_uri);
          }
          else {
            file_save_data($result->data, $local_uri);
          }
        }
        $file->metadata['oembed_remote_file_image'] = $local_uri;

        // Redundantish. See file_entity_file_insert and related hooks.
        foreach (array(
          'oembed_remote_file_image',
        ) as $name) {
          if (!empty($file->metadata[$name])) {
            $value = $file->metadata[$name];
            db_merge('file_metadata')
              ->fields(array(
              'value' => serialize($value),
            ))
              ->key(array(
              'fid' => $file->fid,
              'name' => $name,
            ))
              ->execute();
          }
        }
      }
    }
    if (isset($file->metadata['oembed_remote_file_image'])) {
      $local_uri = $file->metadata['oembed_remote_file_image'];
      $image_file = file_uri_to_object($local_uri);
      $image_file->metadata = array();

      // Forcing the image file's type is perhaps no longer necessary.
      if (!isset($image_file->type) || $image_file->type === FILE_TYPE_NONE) {
        $image_file->type = 'image';
      }
      if ($image_file->filesize) {
        $image_file->metadata = image_get_info($image_file->uri);
        $image_file->filemime = $image_file->metadata['mime_type'];
        return file_entity_file_formatter_file_image_view($image_file, $display, $langcode);
      }
    }
  }
}

/**
 * Implements hook_file_formatter_FORMATTER_settings().
 */
function oembed_file_formatter_oembed_settings($form, &$form_state, $settings) {
  $element = array();
  $element['width'] = array(
    '#title' => t('Width'),
    '#type' => 'textfield',
    '#default_value' => $settings['width'],
  );
  $element['height'] = array(
    '#title' => t('Height'),
    '#type' => 'textfield',
    '#default_value' => $settings['height'],
  );
  $element['wmode'] = array(
    '#title' => t('Flash window mode (wmode)'),
    '#type' => 'select',
    '#empty_option' => t('None (do not request a specific wmode from the provider)'),
    '#options' => drupal_map_assoc(array(
      'window',
      'transparent',
      'opaque',
      'direct',
      'gpu',
    )),
    '#description' => t('Controls layering, transparency, and playback performance of content rendered by the Flash player. For more information, view <a href="http://kb2.adobe.com/cps/127/tn_12701.html#main_Using_Window_Mode__wmode__values_">Adobe\'s documentation</a>.'),
    '#default_value' => $settings['wmode'],
  );
  return $element;
}

/**
 * Implements hook_file_formatter_FORMATTER_settings().
 */
function oembed_file_formatter_oembed_thumbnail_settings($form, &$form_state, $settings) {
  $element = array();
  $element['width'] = array(
    '#title' => t('Width'),
    '#type' => 'textfield',
    '#default_value' => $settings['width'],
  );
  $element['height'] = array(
    '#title' => t('Height'),
    '#type' => 'textfield',
    '#default_value' => $settings['height'],
  );
  return $element;
}

/**
 * Clear the cached oEmbed content for the selected files.
 */
function oembed_cache_clear($fids) {
  $fids = array_keys($fids);
  $query = new EntityFieldQuery();
  $results = $query
    ->entityCondition('entity_type', 'file')
    ->propertyCondition('uri', 'oembed:', 'STARTS_WITH')
    ->propertyCondition('fid', $fids)
    ->execute();
  $files = file_load_multiple(array_keys($results['file']));
  foreach ($files as $file) {
    $wrapper = file_stream_wrapper_get_instance_by_uri($file->uri);
    $url = $wrapper
      ->getExternalUrl();
    $cid = hash('sha256', $url);
    cache_clear_all($cid, 'cache_oembed', TRUE);
  }
}

/**
 * Checks that the oEmbed response has required standard properties for its type.
 *
 * @param \stdClass $file
 *   A Drupal file object.
 *
 * @return array
 *   If the oEmbed response is invalid, the array will contain at least one error message.
 */
function oembed_file_validator_type(stdClass $file) {
  return oembed_validate_response($file->metadata['oembed']);
}

/**
 * Validates oEmbed responses.
 */
function oembed_validate_response($embed) {
  $errors = array();
  if (!$embed) {
    $errors[] = t('Unable to fetch oEmbed data or it is not a valid URL.');
  }
  else {
    if (empty($embed['version']) || empty($embed['type']) || intval($embed['version']) != 1) {
      $errors[] = t('oEmbed data for is invalid.');
    }
  }

  // Validate that response has required properties for its type.
  $message = t('oEmbed response is missing required properties for @type.', array(
    '@type' => $embed['type'],
  ));

  // Video, rich and photo all must have width and height.
  // This validation causes lots of legitimate responses to be rejected. To retain access
  // to Twitter, Scribd and others, we allow responses that do not have height and width.
  if (in_array($embed['type'], array(
    'video',
    'rich',
    'photo',
  ))) {
    if (!isset($embed['width']) || empty($embed['width']) || (!isset($embed['height']) || empty($embed['height']))) {

      //$errors[] = $message;
    }
  }

  // Video and rich type must have html content.
  if (in_array($embed['type'], array(
    'video',
    'rich',
  ))) {
    if (!isset($embed['html']) || empty($embed['html'])) {
      $errors[] = $message;
    }
  }

  // Image type must have a URL.
  if ($embed['type'] == 'photo') {
    if (!isset($embed['url']) || empty($embed['url'])) {
      $errors[] = $message;
    }
  }
  return $errors;
}

/**
 * Return a file entity for a URL. Create the file if necessary.
 *
 * @param string $url
 *   URL of oEmbed request.
 * @param boolean $create
 *   Flag to create the file entity if it does not already exist.
 *
 * @return \stdClass
 */
function oembed_url_to_file($url, $create = FALSE) {
  $uri = 'oembed://' . drupal_encode_path($url);
  $file = file_uri_to_object($uri);
  $file->metadata = array();
  if (!isset($file->metadata['oembed'])) {
    $file->metadata['oembed'] = oembed_get_data($url);
  }

  // New URLs need to be validated before being saved.
  if ($create && !isset($file->fid)) {

    // Save the new file.
    file_save($file);
  }
  return $file;
}

Functions

Namesort descending Description
oembed_alt_attr Generate a string for use as ALT attribute.
oembed_cache_clear Clear the cached oEmbed content for the selected files.
oembed_cache_flush Implements hook_cache_flush().
oembed_cron Implements hook_cron().
oembed_ctools_plugin_api Implements hook_ctools_plugin_api().
oembed_file_formatter_oembed_settings Implements hook_file_formatter_FORMATTER_settings().
oembed_file_formatter_oembed_thumbnail_settings Implements hook_file_formatter_FORMATTER_settings().
oembed_file_formatter_view Implements hook_file_formatter_FORMATTER_view().
oembed_file_validator_type Checks that the oEmbed response has required standard properties for its type.
oembed_get_data Fetch data for an embeddable URL.
oembed_help Implements hook_help().
oembed_hook_info Implements hook_hook_info().
oembed_preprocess_file_entity Implement hook_preprocess_file_entity().
oembed_remote_file_formatter_view Implements hook_file_formatter_FORMATTER_view().
oembed_render_element Prepare an element based on a oEmbed request.
oembed_theme Implements of hook_theme().
oembed_url_to_file Return a file entity for a URL. Create the file if necessary.
oembed_validate_response Validates oEmbed responses.