Youtube is one of those essential platforms for any digital marketing strategy carried out today. The number of people who constantly consume material from there is overwhelming and brands will always want to take advantage of these channels. That is why it is common for them to also want to show on their websites the videos they have published on their channels to engage their users with the content they have created.
In terms of development, the recommended and practical way to obtain information about a YouTube channel in order to put together a block with the most recent uploaded videos is by using the API, this API is on Google's cloud services in Google Cloud and Although the procedure is easy to understand for a developer, it is not easy for a user who usually only publishes content. Let's look at the steps indicated by Google on its website.
Note that to use the API you must have a Google Cloud account and usually that may also involve having to register a credit card, it is not so difficult if this is done by a developer and only once.
Definition of the requirement
In a project we found a more specific requirement, a multi-domain installation separated into multisites by means of a group required that in the configuration of each mysite, the webmaster or administrator of the site could configure their YouTube channel to show the latest published videos, the problem was that these administrators had very little or no technical training to successfully follow the steps to register an API Key for query with Google and additionally that would be a task for about 300 minisites. We needed to find a practical solution for this requirement.
Solution case
Investigating a little we found that YouTube still keeps the RSS channels running for each channel in such a way that thanks to the XML structure that the RSS has we can use an RSS interpreter to extract the data from the latest videos and it saves us the entire process. registration in Google Cloud to obtain the information.
Nowadays RSS is not so popular but it is still an open feed channel used by those who still like aggregators and in this particular case it makes it easier for us to create a small RSS aggregator that displays in Drupal the latest videos uploaded to the channel.
To solve this case in Drupal we decided to create a formatter for a text field that receives the channel ID, this ID is obtained from the channel URL
The created field is displayed like this:
When the user enters the Channel ID we use it to build the RSS URL and read the structure to extract the information from the last three videos uploaded.
To read the HTML structure we use the Laminas Feed Laminas\Feed library that already comes among the base packages of any Drupal 8 and 9 installation, according to the library description:
“Sheets\Feed provides functionality to consume RSS and Atom feeds. Provides a natural syntax for accessing feed elements, feed attributes, and input attributes.”
Although Láminas already offers us a way to read an RSS structure, the YouTube structure has some structures that are not part of a standard RSS as we can see in the following image.
In order to tell Laminas how to read this data structure, we must create an extension that we can include within the Custom Module that we are creating for our solution. We create an Entry.php file in the code structure that you can see in the following image.
In this file we do the following:
- We create a class called Entry in the Namespace of the YoutubeRSS extension, this class extends AbstractEntry
- We created four methods to define how to read each field, the methods are:
- getMediaThumbnail
- getMediaContent
- getMediaDescription
- getMediaTitle
- We register a namespace called youtube using the registerNamespaces method
Observe in the code how it was done:
data['media_thumbnail'])) {
return $this->data['media_thumbnail'];
}
$media_thumbnail = $this->xpath->evaluate(
'string(' . $this->getXpathPrefix() . '/media:group/media:thumbnail[@url]/@url)'
);
if (! $media_thumbnail) {
$media_thumbnail = null;
}
$this->data['media_thumbnail'] = $media_thumbnail;
return $this->data['media_thumbnail'];
}
public function getMediaContent()
{
if (isset($this->data['media_content'])) {
return $this->data['media_content'];
}
$media_content = $this->xpath->evaluate(
'string(' . $this->getXpathPrefix() . '/media:group/media:content[@url]/@url)'
);
if (! $media_content) {
$media_content = null;
}
$this->data['media_content'] = $media_content;
return $this->data['media_content'];
}
public function getMediaDescription()
{
if (isset($this->data['media_description'])) {
return $this->data['media_description'];
}
$media_description = $this->xpath->evaluate(
'string(' . $this->getXpathPrefix() . '/media:group/media:description)'
);
if (! $media_description) {
$media_description = null;
}
$this->data['media_description'] = $media_description;
return $this->data['media_description'];
}
public function getMediaTitle()
{
if (isset($this->data['media_title'])) {
return $this->data['media_title'];
}
$media_title = $this->xpath->evaluate(
'string(' . $this->getXpathPrefix() . '/media:group/media:title)'
);
if (! $media_title) {
$media_title = null;
}
$this->data['media_title'] = $media_title;
return $this->data['media_title'];
}
protected function registerNamespaces()
{
$this->xpath->registerNamespace(
'youtube',
'https://www.youtube.com/feeds/videos.xml'
);
}
}
Now that we have created the necessary methods to indicate how to read those structures, we can create the formatter for the field and make use of foils to read the RSS and process the structure as necessary.
We created a PHP File called YoutubeRSSParser.php that you can see how it looks in the folder structure of the following image:
In the file we do the following:
- A class called YoutubeRSSParser is created that extends FormatterBase, in the annotations the id, label and field_types properties are defined, the latter indicating that it is a formatter only for string type fields.
- The viewValue method invokes the RSSParser method in which the YoutubeRss Class that we had created is invoked and registers it so that it is taken into account in the processing of the structure.
- The URL is constructed with the channel ID received from the field and that URL is passed through a method called process.
- The process method imports the feed and extracts the data using the previously defined getTitle, getDescription, getLink and getMediaThumbnail methods in addition to other existing methods. The result is stored in a data array that is finally passed to a branch template to be structured in HTML
The code is the following:
$item) {
$elements[$delta] = ['#markup' => $this->viewValue($item)];
}
return $elements;
}
/**
* Generate the output appropriate for one field item.
*
* @param \Drupal\Core\Field\FieldItemInterface $item
* One field item.
*
* @return string
* The textual output generated.
*/
protected function viewValue(FieldItemInterface $item) {
// The text value has no text format assigned to it, so the user input
// should equal the output, including newlines.
return $this->RSSParser($item->value);
}
private function RSSParser($value){
$extensions = new ExtensionPluginManager();
$extensions->setInvokableClass('YoutubeRss\Entry', YoutubeRss\Entry::class);
Reader::setExtensionManager(new ExtensionManager($extensions));
Reader::registerExtension('YoutubeRss');
$urlFeed = "https://www.youtube.com/feeds/videos.xml?channel_id=".$value;
return $this->process($urlFeed);
}
private function process($feed){
try {
$channel = Reader::import($feed);
$link = $channel->getLink();
$data = [];
foreach ($channel as $item) {
// Reset the parsed item.
$parsed_item = [];
// Move the values to an array as expected by processors.
$parsed_item['title'] = $item->getTitle();
$parsed_item['description'] = $item->getDescription();
$parsed_item['link'] = $item->getLink();
$parsed_item['media_thumbnail'] = $item->getMediaThumbnail();
// Store on $feed object. This is where processors will look for parsed items.
$data[] = $parsed_item;
}
$build_content = [
'#theme' => 'youtube_feed_block',
'#title' => $channel->getTitle(),
'#items' => $data,
];
$rendered = \Drupal::service('renderer')->renderPlain($build_content);
return $rendered;
}
catch (ExceptionInterface $e) {
watchdog_exception('my_module', $e);
return FALSE;
}
}
}
As we could see in the previous code, practically all the processing was done in a few lines of code and its result is sent in an array to a Twig template.
The template was defined in the .module file of the module, that is, in my_module.module. In the following code we can see how the hook_theme has been implemented to define the youtube_feed_block theme, which in turn defines that the name of the branch file to be used is block- -youtube-feed-block
/**
* Implements hook_theme().
*/
function my_module_theme() {
return [
'youtube_feed_block' => [
'variables' => [
'title' => NULL,
'items' => NULL,
],
'template' => 'block--youtube-feed-block',
],
];
}
Finally we create the Twig file to structure the resulting HTML which will be seen when rendering the field, the file must be created in the templates folder of the module as we see in the following image.
In our case the content of the HTML is the following, you will notice that what it does is go through the items and print them in a list.
<div class="layout--threecol-section--33-34-33 flex">
<div class="social-media--container">
<h3 class="title-social-network">YouTube</h3>
</div>
<div class="block-youtubechannel-block block-youtube--33">
<h2 class="panel-heading">{{ title }}</h2>
<div class="youtube-block-channel">
<ul>
{% for item in items %}
<li>
<a href={{ item.link }}">
<img src="{{ item.media_thumbnail }}" alt="{{ item.title }}"/>
<span class="title-video-youtube">{{ item.title }}</span>
</a>
</li>
{% endfor %}
<ul>
</div>
</div>
</div>
Conclusions
The solution method we implemented is practical for a very specific solution that requires displaying the latest videos published on a channel, however, for more advanced solutions, the use of the API is necessary.
I hope it will be useful to you to know how to extend the sheets library, how to parse an RSS with Drupal, create a new formatter for a field and render in a template.