youtube.module in YouTube Field 8
Same filename and directory in other branches
Youtube field module adds a field for YouTube videos.
File
youtube.moduleView source
<?php
/**
* @file
* Youtube field module adds a field for YouTube videos.
*/
use GuzzleHttp\Exception\RequestException;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\UrlHelper;
use Drupal\field\Entity\FieldConfig;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Drupal\image\Entity\ImageStyle;
/**
* Implements hook_menu_link_defaults().
*/
function youtube_menu_link_defaults() {
$links = [];
$links['youtube.settings'] = [
'link_title' => 'YouTube Field settings',
'description' => 'Configure global settings for YouTube fields.',
'route_name' => 'youtube.settings',
'parent' => 'system.admin.config.media',
];
return $links;
}
/**
* Extracts the video_id from the submitted field value.
*
* @param string $input
* The input submitted to the field.
*
* @return string|bool
* Returns the video_id if available, or FALSE if not.
*/
function youtube_get_video_id($input) {
// See README.txt for accepted URL formats.
preg_match("/^(?:http(?:s)?:\\/\\/)?(?:www\\.)?(?:youtu\\.be\\/|youtube\\.com\\/(?:(?:watch)?\\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|user)\\/))([^\\?&\"'<> ]+)/", $input, $matches);
if (!empty($matches[1])) {
$video_id = $matches[1];
return $video_id;
}
return FALSE;
}
/**
* Returns a list of standard YouTube video sizes.
*/
function youtube_size_options() {
return [
'450x315' => '450px by 315px',
'480x360' => '480px by 360px',
'640x480' => '640px by 480px',
'960x720' => '960px by 720px',
'responsive' => 'responsive (full-width of container)',
'custom' => 'custom',
];
}
/**
* Implements hook_theme().
*/
function youtube_theme($existing, $type, $theme, $path) {
return [
'youtube_thumbnail' => [
'variables' => [
'video_id' => NULL,
'entity_title' => NULL,
'image_style' => NULL,
'image_link' => NULL,
'image' => NULL,
],
],
'youtube_video' => [
'variables' => [
'input' => NULL,
'video_id' => NULL,
'entity_title' => NULL,
'settings' => [],
'alternative_content' => NULL,
],
],
];
}
/**
* Prepares variables for the YouTube Video template.
*
* Default template: youtube-video.html.twig.
*/
function template_preprocess_youtube_video(&$variables) {
$config = \Drupal::config('youtube.settings');
// Add global YouTube module configuration to the settings array.
$variables['settings'] += [
'youtube_suggest' => $config
->get('youtube_suggest'),
'youtube_privacy' => $config
->get('youtube_privacy'),
'youtube_player_class' => $config
->get('youtube_player_class'),
'youtube_modestbranding' => $config
->get('youtube_modestbranding'),
'youtube_theme' => $config
->get('youtube_theme'),
'youtube_color' => $config
->get('youtube_color'),
'youtube_enablejsapi' => $config
->get('youtube_enablejsapi'),
'youtube_wmode' => $config
->get('youtube_wmode'),
];
// Build the query for the embedded video using module and field settings.
$query = [];
if (!$variables['settings']['youtube_suggest']) {
$query['rel'] = '0';
}
if ($variables['settings']['youtube_modestbranding']) {
$query['modestbranding'] = '1';
}
if ($variables['settings']['youtube_theme']) {
$query['theme'] = 'light';
}
if ($variables['settings']['youtube_color']) {
$query['color'] = 'white';
}
if ($variables['settings']['youtube_enablejsapi']) {
global $base_root;
$query['enablejsapi'] = '1';
$query['origin'] = $base_root;
}
if ($variables['settings']['youtube_wmode']) {
$query['wmode'] = 'opaque';
}
if ($variables['settings']['youtube_autoplay']) {
$query['autoplay'] = '1';
}
if ($variables['settings']['youtube_mute']) {
$query['mute'] = '1';
}
if ($variables['settings']['youtube_loop']) {
$query['loop'] = '1';
$query['playlist'] = $variables['video_id'];
}
if ($variables['settings']['youtube_controls']) {
$query['controls'] = '0';
}
if ($variables['settings']['youtube_autohide']) {
$query['autohide'] = '1';
}
if ($variables['settings']['youtube_iv_load_policy']) {
$query['iv_load_policy'] = '3';
}
// If the override setting is enabled, add any additional parameters provided
// in the initial field value to the query of the embedded video.
if ($config
->get('youtube_override')) {
if ($url_parts = UrlHelper::parse($variables['input'])) {
foreach ($url_parts['query'] as $key => $value) {
if ($key == 'v') {
continue;
}
$query[$key] = $value;
}
}
}
// Use the module's privacy configuration to determine the domain.
$domain = !$variables['settings']['youtube_privacy'] ? 'youtube.com' : 'youtube-nocookie.com';
$path = 'https://www.' . $domain . '/embed/' . $variables['video_id'];
// Build the src attribute with the path and query array constructed above.
$url = Url::fromUri($path, [
'query' => $query,
]);
$variables['content_attributes']['src'] = $url
->toString();
// Use the field's display settings to retrieve the video's dimensions.
$size = $variables['settings']['youtube_size'];
$width = $variables['settings']['youtube_width'];
$height = $variables['settings']['youtube_height'];
if ($size != 'responsive') {
$dimensions = youtube_get_dimensions($size, $width, $height);
// Assign the retrieved dimensions as attributes on the iframe element.
$variables['content_attributes']['width'] = $dimensions['width'];
$variables['content_attributes']['height'] = $dimensions['height'];
}
// Build the iframe element's id and class attribute values.
$class = $variables['settings']['youtube_player_class'];
$id = Html::getUniqueId($class);
$variables['content_attributes']['id'] = $id;
$variables['content_attributes']['class'][] = Html::getClass($class);
// Build the iframe element's title attribute value (for accessibility).
$variables['content_attributes']['title'] = t('Embedded video');
if (!empty($variables['entity_title'])) {
$variables['content_attributes']['title'] = t('Embedded video for @entity_title', [
'@entity_title' => $variables['entity_title'],
]);
}
// Alternative content for browsers that don't understand iframes (WCAG).
$variables['content_attributes']['aria-label'] = $variables['content_attributes']['title'] . ': ' . $url
->toString();
// Remove iframe border.
$variables['content_attributes']['frameborder'] = 0;
// Add classes to the wrapping element.
$variables['attributes']['class'][] = 'youtube-container';
if ($size == 'responsive') {
// When the "responsive" size is chosen in the field's display settings,
// this class is used by the module's CSS to make the player responsive.
$variables['attributes']['class'][] = 'youtube-container--responsive';
}
}
/**
* Prepares variables for the YouTube Thumbnail template.
*
* Default template: youtube-thumbnail.html.twig.
*/
function template_preprocess_youtube_thumbnail(&$variables) {
$video_id = $variables['video_id'];
$image_style = $variables['image_style'];
// Build the image element's alt attribute value (for accessibility).
$alt = t('Embedded thumbnail');
if (!empty($variables['entity_title'])) {
$alt .= ' ' . t('for @entity_title', [
'@entity_title' => $variables['entity_title'],
]);
}
// Check to see if a thumbnail exists locally.
$uri = youtube_build_thumbnail_uri($video_id);
if (!file_exists($uri)) {
// Retrieve the image from YouTube.
if (!youtube_get_remote_image($video_id)) {
// Use the remote source if local copy fails.
$uri = youtube_build_remote_image_path($video_id);
}
}
// Build the initial image render array.
$variables['image'] = [
'#theme' => 'image',
'#uri' => $uri,
'#alt' => $alt,
];
// If an image style has been chosen in the field's display settings, alter
// the render array to use that image style. Remote images cannot be rendered
// through an image style.
if ($image_style && empty($remote_uri)) {
$variables['image']['#theme'] = 'image_style';
$variables['image']['#style_name'] = $image_style;
}
// If a URL path is provided, create a link with the image and that path.
if ($variables['image_link'] != NULL) {
$variables['image'] = Link::fromTextAndUrl($variables['image'], $variables['image_link']);
}
}
/**
* Splits height and width when given size, as from youtube_size_options.
*
* @param string $size
* Image size.
* @param string $width
* Image width.
* @param string $height
* Image height.
*
* @return array
* An array containing the dimensions.
*/
function youtube_get_dimensions($size = NULL, $width = NULL, $height = NULL) {
$dimensions = [];
if ($size == 'custom') {
$dimensions['width'] = (int) $width;
$dimensions['height'] = (int) $height;
}
else {
// Locate the 'x'.
$strpos = strpos($size, 'x');
// Width is the first dimension.
$dimensions['width'] = substr($size, 0, $strpos);
// Height is the second dimension.
$dimensions['height'] = substr($size, $strpos + 1, strlen($size));
}
return $dimensions;
}
/**
* Retrieves the thumbnail image for a given video from YouTube.
*
* @param int $video_id
* The video ID of the particular YouTube video.
* @param bool $force_small
* (optional) When TRUE, this function should return the standard size image
* regardless of what the youtube_thumb_hires variable is set to. This is used
* should the high resolution image be found to not exist for a particular
* video.
*
* @return bool|object
* Either the Drupal $file object of saved image, or FALSE if the save failed.
*/
function youtube_get_remote_image($video_id = NULL, $force_small = FALSE) {
// This value is TRUE when higher resolution thumbnails should be saved.
// This resolution is not guaranteed to exist and if it doesn't, the smaller
// resolution image will be saved in its place.
$youtube_thumb_hires = \Drupal::config('youtube.settings')
->get('youtube_thumb_hires');
// This boolean is TRUE if we're obtaining a hi-res thumbnail.
$get_hires = $youtube_thumb_hires && !$force_small;
// Build the image url.
if ($get_hires) {
$src = youtube_build_remote_image_path($video_id, 'maxresdefault');
}
else {
$src = youtube_build_remote_image_path($video_id);
}
// Make the request for the file.
try {
$image_request = Drupal::httpClient()
->get($src);
} catch (RequestException $e) {
// The high resolution image didn't exist and the request was a 404. Force
// it to try again, but look for the smaller resolution image.
if ($get_hires) {
return youtube_get_remote_image($video_id, TRUE);
}
watchdog_exception('youtube', $e);
return FALSE;
}
$youtube_thumb_uri = youtube_build_thumbnail_uri();
if (!\Drupal::service('file_system')
->prepareDirectory($youtube_thumb_uri, FileSystemInterface::CREATE_DIRECTORY) && !mkdir($youtube_thumb_uri, 0775, TRUE)) {
\Drupal::service('logger.factory')
->get('youtube')
->error('Failed to create YouTube thumbnail directory: %dir', [
'%dir' => $youtube_thumb_uri,
]);
return FALSE;
}
$data = $image_request
->getBody(TRUE);
$destination = youtube_build_thumbnail_uri($video_id);
// Save the thumbnail and add to Drupal managed files.
$file = file_save_data($data, $destination, FileSystemInterface::EXISTS_REPLACE);
if (!$file) {
\Drupal::service('logger.factory')
->get('youtube')
->error('Unable to save youtube thumbnail to filesystem for video %id', [
'%id' => $video_id,
]);
return FALSE;
}
return $file;
}
/**
* Deletes all existing thumbnail image files.
*
* This is a submit callback for the "Refresh" option in the configuration form.
*/
function youtube_thumb_delete_all($form, &$form_state) {
$youtube_thumb_uri = youtube_build_thumbnail_uri();
$file_system = \Drupal::service('file_system');
if (!$file_system
->prepareDirectory($youtube_thumb_uri)) {
return \Drupal::messenger()
->addMessage(t('No files deleted.'));
}
$files = $file_system
->scanDirectory($youtube_thumb_uri, '/^.*\\.(jpg|png)$/');
foreach ($files as $raw_file) {
$fid = \Drupal::database()
->select('file_managed')
->fields('file_managed', [
'fid',
])
->condition('uri', '%' . $raw_file->uri, 'LIKE')
->execute()
->fetchField();
if ($fid) {
$managed_file = File::load($fid);
}
if (!$managed_file) {
$file_system
->delete($raw_file->uri);
}
else {
$managed_file
->delete();
}
}
drupal_flush_all_caches();
}
/**
* Builds the URI to a given thumbnail or the module's thumbnail directory.
*
* @param string $video_id
* (optional) The video ID to build the thumbnail URI for.
*
* @return string
* If a $video_id was supplied, the URI to that video's thumbnail. Otherwise
* the URI to the module's thumbnail directory.
*/
function youtube_build_thumbnail_uri($video_id = NULL) {
$youtube_thumb_dir = \Drupal::config('youtube.settings')
->get('youtube_thumb_dir');
$youtube_thumb_dir = empty($youtube_thumb_dir) ? 'youtube' : $youtube_thumb_dir;
$youtube_thumb_uri = file_build_uri($youtube_thumb_dir);
if ($video_id) {
return $youtube_thumb_uri . '/' . $video_id . '.jpg';
}
return $youtube_thumb_uri;
}
/**
* Get YouTube image path by building correctly formed URL.
*
* @param string $video_id
* The ID of the video to grab the thumbnail from.
* @param string $version
* Which version of the thumbnail to grab. See $versions for options.
*
* @return string|bool
* The youtube.com image path to the specified version/video. FALSE if the
* supplied version is not an option.
*/
function youtube_build_remote_image_path($video_id = NULL, $version = '0') {
// The different versions of the image made available by YouTube.
// http://stackoverflow.com/a/2068371
$versions = [
'0',
'hqdefault',
'mqdefault',
'maxresdefault',
'default',
'1',
'2',
'3',
];
if (!$video_id || !in_array($version, $versions)) {
return FALSE;
}
$version_path = 'http://img.youtube.com/vi/' . $video_id . '/' . $version . '.jpg';
return Url::fromUri($version_path)
->toString();
}
/**
* Implements hook_token_info_alter().
*
* Alters and adds tokens for each youtube field.
*/
function youtube_token_info_alter(&$data) {
// Get all youtube fields. Gather entity_type and bundle information.
$youtube_fields = [];
$entity_field_manager = \Drupal::service('entity_field.manager');
$field_map = $entity_field_manager
->getFieldMap();
foreach ($field_map as $entity_type_name => $fields) {
foreach ($fields as $field_name => $field) {
foreach ($field['bundles'] as $bundle) {
if ($field['type'] == 'youtube') {
$youtube_fields[] = [
'entity_type' => $entity_type_name,
'bundle' => $bundle,
'field_name' => $field_name,
];
}
}
}
}
foreach ($youtube_fields as $field) {
$field_info = FieldConfig::loadByName($field['entity_type'], $field['bundle'], $field['field_name']);
$field_label = $field_info ? $field_info
->getLabel() : '';
// Modify the default field token.
$data['tokens'][$field['entity_type']][$field['field_name']] = [
'name' => $field_label . t(": Default"),
'description' => t("The YouTube video field value's Default (or Token if exists) view mode output."),
];
// Add two new tokens.
$data['tokens'][$field['entity_type']][$field['field_name'] . '__youtube_video_url'] = [
'name' => $field_label . t(": Video URL"),
'description' => t("The YouTube video field value's youtube.com URL."),
];
$data['tokens'][$field['entity_type']][$field['field_name'] . '__youtube_image_url'] = [
'name' => $field_label . t(": Image URL"),
'description' => t("The YouTube video field value's local image URL."),
];
}
}
/**
* Implements hook_tokens().
*
* @see youtube_tokens_info_alter()
*/
function youtube_tokens($type, $tokens, array $data = [], array $options = []) {
global $base_url;
global $base_path;
$url_options = [
'absolute' => TRUE,
];
if (isset($options['langcode'])) {
$url_options['language'] = \Drupal::languageManager()
->getLanguage($options['langcode']);
$langcode = $options['langcode'];
}
else {
$langcode = LanguageInterface::LANGCODE_DEFAULT;
}
$sanitize = !empty($options['sanitize']);
$replacements = [];
if ($type == 'node' && !empty($data['node'])) {
$node = $data['node'];
foreach ($tokens as $name => $original) {
if (!strpos($name, '__youtube_')) {
// This isn't a youtube token!
continue;
}
$token_pieces = explode('__', $name);
if (count($token_pieces) != 2) {
continue;
}
$field_name = $token_pieces[0];
$token_name = $token_pieces[1];
switch ($token_name) {
case 'youtube_video_url':
$replacements[$original] = '';
$field = $node->{$field_name};
if ($video_id = $field->video_id) {
$replacements[$original] = 'http://www.youtube.com/watch?v=' . $video_id;
}
break;
case 'youtube_image_url':
$replacements[$original] = '';
$field = $node->{$field_name};
if ($video_id = $field->video_id) {
$file_uri = youtube_build_thumbnail_uri($video_id);
if (file_exists($file_uri) || youtube_get_remote_image($video_id)) {
$replacements[$original] = file_create_url($file_uri);
if ($style_name = \Drupal::config('youtube.settings')
->get('youtube_thumb_token_image_style')) {
$image_style = ImageStyle::load($style_name);
$derivative_uri = $image_style
->buildUri($file_uri);
if (!file_exists($derivative_uri)) {
$image_style
->createDerivative($file_uri, $derivative_uri);
}
$replacements[$original] = file_create_url($derivative_uri);
}
}
}
break;
}
}
}
return $replacements;
}
Functions
Name | Description |
---|---|
template_preprocess_youtube_thumbnail | Prepares variables for the YouTube Thumbnail template. |
template_preprocess_youtube_video | Prepares variables for the YouTube Video template. |
youtube_build_remote_image_path | Get YouTube image path by building correctly formed URL. |
youtube_build_thumbnail_uri | Builds the URI to a given thumbnail or the module's thumbnail directory. |
youtube_get_dimensions | Splits height and width when given size, as from youtube_size_options. |
youtube_get_remote_image | Retrieves the thumbnail image for a given video from YouTube. |
youtube_get_video_id | Extracts the video_id from the submitted field value. |
youtube_menu_link_defaults | Implements hook_menu_link_defaults(). |
youtube_size_options | Returns a list of standard YouTube video sizes. |
youtube_theme | Implements hook_theme(). |
youtube_thumb_delete_all | Deletes all existing thumbnail image files. |
youtube_tokens | Implements hook_tokens(). |
youtube_token_info_alter | Implements hook_token_info_alter(). |