Skip to content

Commit

Permalink
add video content elements
Browse files Browse the repository at this point in the history
  • Loading branch information
m-vo committed Jun 28, 2022
1 parent 9404ec1 commit 4f97cb9
Show file tree
Hide file tree
Showing 11 changed files with 406 additions and 14 deletions.
24 changes: 14 additions & 10 deletions UPGRADE.md
Expand Up @@ -127,16 +127,18 @@ documentation for more details.
The following content element types have been rewritten as fragment controllers with
Twig-only templates:

- `code` (`ce_code``content_element/code`)
- `headline` (`ce_headline``content_element/headline`)
- `html` (`ce_html``content_element/html`)
- `list` (`ce_list``content_element/list`)
- `text` (`ce_text``content_element/text`)
- `table` (`ce_table``content_element/table`)
- `hyperlink` (`ce_hyperlink``content_element/hyperlink`)
- `toplink` (`ce_toplink``content_element/toplink`)
- `image` (`ce_image``content_element/image`)
- `gallery` (`ce_gallery``content_element/gallery`)
- `code` (`ce_code``content_element/code`)
- `headline` (`ce_headline``content_element/headline`)
- `html` (`ce_html``content_element/html`)
- `list` (`ce_list``content_element/list`)
- `text` (`ce_text``content_element/text`)
- `table` (`ce_table``content_element/table`)
- `hyperlink` (`ce_hyperlink``content_element/hyperlink`)
- `toplink` (`ce_toplink``content_element/toplink`)
- `image` (`ce_image``content_element/image`)
- `gallery` (`ce_gallery``content_element/gallery`)
- `youtube` (`ce_youtube``content_element/youtube`)
- `vimeo` (`ce_vimeo``content_element/vimeo`)

The legacy content elements and their templates are still around and will only be dropped in Contao 6.
If you want to use them instead of the new ones, you can opt in on a per-element basis by adding the
Expand All @@ -154,6 +156,8 @@ $GLOBALS['TL_CTE']['links']['hyperlink'] = \Contao\ContentHyperlink::class;
$GLOBALS['TL_CTE']['links']['toplink'] = \Contao\ContentToplink::class;
$GLOBALS['TL_CTE']['media']['image'] = \Contao\ContentImage::class;
$GLOBALS['TL_CTE']['media']['gallery'] = \Contao\ContentGallery::class;
$GLOBALS['TL_CTE']['media']['youtube'] = \Contao\ContentYouTube::class;
$GLOBALS['TL_CTE']['media']['vimeo'] = \Contao\ContentVimeo::class;
```

### Show to guests only
Expand Down
159 changes: 159 additions & 0 deletions core-bundle/src/Controller/ContentElement/VideoController.php
@@ -0,0 +1,159 @@
<?php

declare(strict_types=1);

/*
* This file is part of Contao.
*
* (c) Leo Feyer
*
* @license LGPL-3.0-or-later
*/

namespace Contao\CoreBundle\Controller\ContentElement;

use Contao\ContentModel;
use Contao\CoreBundle\Image\Studio\Studio;
use Contao\CoreBundle\ServiceAnnotation\ContentElement;
use Contao\CoreBundle\Twig\FragmentTemplate;
use Contao\StringUtil;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
* @ContentElement("vimeo", category="media")
* @ContentElement("youtube", category="media")
*
* @phpstan-type VideoSourceParameters array{
* provider: 'vimeo'|'youtube',
* video_id: string,
* options: array<string, string>,
* base_url: string,
* query: string,
* url: string
* }
*/
class VideoController extends AbstractContentElementController
{
public function __construct(private readonly Studio $studio)
{
}

protected function getResponse(FragmentTemplate $template, ContentModel $model, Request $request): Response
{
// Video source data, size and aspect ratio
$sourceParameters = match ($type = $template->get('type')) {
'vimeo' => $this->getVimeoSourceParameters($model),
'youtube' => $this->getYoutubeSourceParameters($model, $request->getLocale()),
default => throw new \InvalidArgumentException(sprintf('Unknown video provider "%s".', $type))
};

$template->set('source', $sourceParameters);

$size = StringUtil::deserialize($model->playerSize, true);

$template->set('width', $size[0] ?? 640);
$template->set('height', $size[1] ?? 360);

$template->set('aspect_ratio', $model->playerAspect);

// Meta data
$template->set('caption', $model->playerCaption);

// Splash image
$figure = !$model->splashImage ? null : $this->studio
->createFigureBuilder()
->fromUuid($model->singleSRC ?: '')
->setSize($model->size)
->buildIfResourceExists()
;

$template->set('splash_image', $figure);

return $template->getResponse();
}

/**
* @return array<string, string|array<string, string>>
*
* @phpstan-return VideoSourceParameters
*/
private function getVimeoSourceParameters(ContentModel $model): array
{
$options = [];

foreach (StringUtil::deserialize($model->vimeoOptions, true) as $option) {
[$option, $value] = match ($option) {
'vimeo_portrait', 'vimeo_title', 'vimeo_byline' => [substr($option, 6), '0'],
default => [substr($option, 6), '1'],
};

$options[$option] = $value;
}

if ($color = $model->playerColor) {
$options['color'] = $color;
}

$query = http_build_query($options);

if (($start = (int) $model->playerStart) > 0) {
$options['start'] = $start;
$query .= "#t={$start}s";
}

return [
'provider' => 'vimeo',
'video_id' => $videoId = $model->vimeo,
'options' => $options,
'base_url' => $baseUrl = "https://player.vimeo.com/video/$videoId",
'query' => $query,
'url' => empty($query) ? $baseUrl : "$baseUrl?$query",
];
}

/**
* @return array<string, string|array<string, string>>
*
* @phpstan-return VideoSourceParameters
*/
private function getYoutubeSourceParameters(ContentModel $model, string $locale): array
{
$options = [];
$domain = 'https://www.youtube.com';

foreach (StringUtil::deserialize($model->youtubeOptions, true) as $option) {
if ('youtube_nocookie' === $option) {
$domain = 'https://www.youtube-nocookie.com';

continue;
}

[$option, $value] = match ($option) {
'youtube_fs', 'youtube_rel', 'youtube_controls' => [substr($option, 8), '0'],
'youtube_hl' => [substr($option, 8), \Locale::parseLocale($locale)[\Locale::LANG_TAG] ?? ''],
'youtube_iv_load_policy' => [substr($option, 8), '3'],
default => [substr($option, 8), '1'],
};

$options[$option] = $value;
}

if (($start = (int) $model->playerStart) > 0) {
$options['start'] = $start;
}

if (($end = (int) $model->playerStop) > 0) {
$options['end'] = $end;
}

return [
'provider' => 'youtube',
'video_id' => $videoId = $model->youtube,
'options' => $options,
'base_url' => $baseUrl = "$domain/embed/$videoId",
'query' => $query = http_build_query($options),
'url' => empty($query) ? $baseUrl : "$baseUrl?$query",
];
}
}
4 changes: 4 additions & 0 deletions core-bundle/src/Resources/config/controller.yaml
Expand Up @@ -90,6 +90,10 @@ services:

Contao\CoreBundle\Controller\ContentElement\UnfilteredHtmlController: ~

Contao\CoreBundle\Controller\ContentElement\VideoController:
arguments:
- '@contao.image.studio'

Contao\CoreBundle\Controller\FaviconController:
arguments:
- '@contao.framework'
Expand Down
4 changes: 0 additions & 4 deletions core-bundle/src/Resources/contao/config/config.php
Expand Up @@ -26,8 +26,6 @@
use Contao\ContentSliderStart;
use Contao\ContentSliderStop;
use Contao\ContentTeaser;
use Contao\ContentVimeo;
use Contao\ContentYouTube;
use Contao\CoreBundle\Controller\BackendCsvImportController;
use Contao\Crawl;
use Contao\FilesModel;
Expand Down Expand Up @@ -280,8 +278,6 @@
'media' => array
(
'player' => ContentPlayer::class,
'youtube' => ContentYouTube::class,
'vimeo' => ContentVimeo::class
),
'files' => array
(
Expand Down
3 changes: 3 additions & 0 deletions core-bundle/src/Resources/contao/languages/en/default.xlf
Expand Up @@ -2066,6 +2066,9 @@
<trans-unit id="MSC.manual">
<source>Manual</source>
</trans-unit>
<trans-unit id="MSC.splashScreen">
<source>Please click here to load the content.</source>
</trans-unit>
<trans-unit id="UNITS.0">
<source>Byte</source>
</trans-unit>
Expand Down
@@ -0,0 +1,60 @@
{#
This component outputs a splash screen button. A client side script will
replace it with content from a template tag when it gets clicked.
<button data-splash>
<p>Click to load the iframe.</p>
<template>
<div class="my-content">
<iframe src="…"></iframe>
</div>
</template>
</button>
After activation the HTML of the above example will look like this:
<div class="my-content">
<iframe src="…"></iframe>
</div>
Optional variables:
@var bool randomize_order
@var int limit
@var int items_per_page
@var string tag_name
@var \Contao\CoreBundle\String\HtmlAttributes splash_button_attributes
#}

{% trans_default_domain "contao_default" %}

{% block splash_screen_component %}
{% set splash_button_attributes = attrs(splash_button_attributes|default)
.set('data-splash-screen')
%}
<button{{ splash_button_attributes }}>
{% block splash_screen_button_content %}
{# Render your splash screen's button content here. #}
{{ ('MSC.splashScreen')|trans }}
{% endblock %}
<template>
{% block splash_screen_content %}
{# Render the actual content (present after activation) here. #}
{% endblock %}
</template>
</button>

{% block splash_screen_script %}
{% if not as_editor_view %}
{% add "splash_screen_script" to body %}
<script>
document.querySelectorAll('*[data-splash-screen]').forEach(button => {
button.addEventListener('click', () => {
button.insertAdjacentHTML('afterend', button.querySelector('template').innerHTML);
button.remove();
})
})
</script>
{% endadd %}
{% endif %}
{% endblock %}
{% endblock %}
@@ -0,0 +1,44 @@
{% trans_default_domain "contao_default" %}
{% extends "@Contao/content_element/_base.html.twig" %}
{% use "@Contao/component/_splash_screen.html.twig" %}
{% use "@Contao/component/_picture.html.twig" %}

{% block content %}
{% set video_attributes = attrs(video_attributes|default)
.addClass('aspect aspect--' ~ aspect_ratio, aspect_ratio)
%}
<figure{{ video_attributes }}>
{% if splash_image %}
{{ block('splash_screen_component') }}
{% else %}
{% block iframe %}
{% set iframe_attrs = attrs({width, height, src: source.url, allowfullscreen: true})
.mergeWith(iframe_attrs|default)
%}
<iframe{{ iframe_attrs }}></iframe>
{% endblock %}
{% endif %}

{% block video_caption %}
{% if caption %}
<figcaption{{ attrs(video_caption_attributes|default) }}>
{{- caption|insert_tag_raw -}}
</figcaption>
{% endif %}
{% endblock %}
</figure>
{% endblock %}

{% block splash_screen_button_content %}
{# Preview image #}
{% with {figure: splash_image} %}{{ block('picture_component') }}{% endwith %}

{# Textual note #}
{% block splash_screen_text %}
<p>{{ ('MSC.splashScreen')|trans }}</p>
{% endblock %}
{% endblock %}

{% block splash_screen_content %}
{{ block('iframe') }}
{% endblock %}
@@ -0,0 +1 @@
{% extends "@Contao/content_element/_video.html.twig" %}
@@ -0,0 +1,5 @@
{% extends "@Contao/content_element/_video.html.twig" %}

{% set iframe_attrs = attrs(iframe_attrs|default)
.set('allow', 'autoplay; encrypted-media; picture-in-picture; fullscreen')
%}
Expand Up @@ -137,6 +137,7 @@ static function () use ($modelData): Metadata|null {

$controller->setFragmentOptions([
'template' => $template ?? "content_element/{$modelData['type']}",
'type' => $modelData['type'],
]);

$response = $controller(new Request(), $model, 'main');
Expand Down

0 comments on commit 4f97cb9

Please sign in to comment.