This commit is contained in:
Jürgen Mummert
2026-03-06 21:25:18 +01:00
commit d10c160ae9
25 changed files with 903 additions and 0 deletions

3
contao/config/config.php Normal file
View File

@@ -0,0 +1,3 @@
<?php
declare(strict_types=1);

48
contao/dca/tl_form.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
use Contao\CoreBundle\DataContainer\PaletteManipulator;
PaletteManipulator::create()
->addLegend('timed_download_legend', 'store_legend', PaletteManipulator::POSITION_AFTER)
->addField('timedDownloadEnabled', 'timed_download_legend', PaletteManipulator::POSITION_APPEND)
->applyToPalette('default', 'tl_form')
;
$GLOBALS['TL_DCA']['tl_form']['palettes']['__selector__'][] = 'timedDownloadEnabled';
$GLOBALS['TL_DCA']['tl_form']['subpalettes']['timedDownloadEnabled'] = 'timedDownloadFile,timedDownloadDuration,timedDownloadUnit';
$GLOBALS['TL_DCA']['tl_form']['fields']['timedDownloadEnabled'] = [
'label' => &$GLOBALS['TL_LANG']['tl_form']['timedDownloadEnabled'],
'exclude' => true,
'inputType' => 'checkbox',
'eval' => ['submitOnChange' => true, 'tl_class' => 'w50 m12'],
'sql' => ['type' => 'boolean', 'default' => false],
];
$GLOBALS['TL_DCA']['tl_form']['fields']['timedDownloadFile'] = [
'label' => &$GLOBALS['TL_LANG']['tl_form']['timedDownloadFile'],
'exclude' => true,
'inputType' => 'fileTree',
'eval' => ['fieldType' => 'radio', 'files' => true, 'mandatory' => true, 'tl_class' => 'w50'],
'sql' => ['type' => 'binary', 'length' => 16, 'notnull' => false],
];
$GLOBALS['TL_DCA']['tl_form']['fields']['timedDownloadDuration'] = [
'label' => &$GLOBALS['TL_LANG']['tl_form']['timedDownloadDuration'],
'exclude' => true,
'inputType' => 'text',
'eval' => ['mandatory' => true, 'rgxp' => 'digit', 'maxlength' => 6, 'tl_class' => 'w50'],
'sql' => ['type' => 'integer', 'unsigned' => true, 'default' => 7],
];
$GLOBALS['TL_DCA']['tl_form']['fields']['timedDownloadUnit'] = [
'label' => &$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnit'],
'exclude' => true,
'inputType' => 'select',
'options' => ['hours', 'days', 'weeks', 'months'],
'reference' => &$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnitOptions'],
'eval' => ['mandatory' => true, 'chosen' => true, 'tl_class' => 'w50'],
'sql' => "varchar(16) NOT NULL default 'days'",
];

13
contao/dca/tl_module.php Normal file
View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_DCA']['tl_module']['palettes']['timed_download_link'] = '{title_legend},name,headline,type;{timed_download_legend},timedDownloadText;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID';
$GLOBALS['TL_DCA']['tl_module']['fields']['timedDownloadText'] = [
'label' => &$GLOBALS['TL_LANG']['tl_module']['timedDownloadText'],
'exclude' => true,
'inputType' => 'textarea',
'eval' => ['rte' => 'tinyMCE', 'tl_class' => 'clr'],
'sql' => 'text NULL',
];

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
use Contao\DC_Table;
$GLOBALS['TL_DCA']['tl_timed_download'] = [
'config' => [
'dataContainer' => DC_Table::class,
'sql' => [
'keys' => [
'id' => 'primary',
'token' => 'unique',
'expires_at' => 'index',
'form_id' => 'index',
],
],
],
'fields' => [
'id' => [
'sql' => 'int(10) unsigned NOT NULL auto_increment',
],
'tstamp' => [
'sql' => 'int(10) unsigned NOT NULL default 0',
],
'token' => [
'sql' => "varchar(64) NOT NULL default ''",
],
'file_uuid' => [
'sql' => 'binary(16) NOT NULL',
],
'expires_at' => [
'sql' => 'int(10) unsigned NOT NULL default 0',
],
'form_id' => [
'sql' => 'int(10) unsigned NOT NULL default 0',
],
'last_download_at' => [
'sql' => 'int(10) unsigned NOT NULL default 0',
],
'download_count' => [
'sql' => 'int(10) unsigned NOT NULL default 0',
],
],
];

View File

@@ -0,0 +1,5 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_LANG']['FMD']['timed_download_link'] = ['Befristeter Downloadlink', 'Zeigt einen zeitlich begrenzten Downloadlink inklusive Countdown an.'];

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_LANG']['tl_form']['timed_download_legend'] = 'Befristeter Download';
$GLOBALS['TL_LANG']['tl_form']['timedDownloadEnabled'] = ['Befristeten Download aktivieren', 'Erzeugt nach erfolgreichem Versand einen zeitlich begrenzten Downloadlink.'];
$GLOBALS['TL_LANG']['tl_form']['timedDownloadFile'] = ['Download-Datei', 'Bitte nur geschuetzte Dateiordner verwenden (Datei muss in tl_files als geschuetzt markiert sein).'];
$GLOBALS['TL_LANG']['tl_form']['timedDownloadDuration'] = ['Gueltigkeitsdauer', 'Numerischer Wert der Gueltigkeitsdauer.'];
$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnit'] = ['Zeiteinheit', 'Zeiteinheit fuer die Gueltigkeitsdauer.'];
$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnitOptions']['hours'] = 'Stunden';
$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnitOptions']['days'] = 'Tage';
$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnitOptions']['weeks'] = 'Wochen';
$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnitOptions']['months'] = 'Monate';

View File

@@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_LANG']['tl_module']['timed_download_legend'] = 'Befristeter Download';
$GLOBALS['TL_LANG']['tl_module']['timedDownloadText'] = ['Text', 'Text oberhalb des Downloadlinks.'];

View File

@@ -0,0 +1,5 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_LANG']['FMD']['timed_download_link'] = ['Timed download link', 'Shows a time-limited download link including a countdown.'];

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_LANG']['tl_form']['timed_download_legend'] = 'Timed download';
$GLOBALS['TL_LANG']['tl_form']['timedDownloadEnabled'] = ['Enable timed download', 'Creates a time-limited download link after successful form submission.'];
$GLOBALS['TL_LANG']['tl_form']['timedDownloadFile'] = ['Download file', 'Please use protected folders only (file must be marked as protected in tl_files).'];
$GLOBALS['TL_LANG']['tl_form']['timedDownloadDuration'] = ['Validity duration', 'Numeric value for the validity period.'];
$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnit'] = ['Time unit', 'Time unit for the validity duration.'];
$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnitOptions']['hours'] = 'Hours';
$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnitOptions']['days'] = 'Days';
$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnitOptions']['weeks'] = 'Weeks';
$GLOBALS['TL_LANG']['tl_form']['timedDownloadUnitOptions']['months'] = 'Months';

View File

@@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
$GLOBALS['TL_LANG']['tl_module']['timed_download_legend'] = 'Timed download';
$GLOBALS['TL_LANG']['tl_module']['timedDownloadText'] = ['Text', 'Text shown above the download link.'];

View File

View File

@@ -0,0 +1,118 @@
<div class="timed-download" data-timed-download>
{% if timedDownloadText %}
<div class="timed-download__text">
{{ timedDownloadText|raw }}
</div>
{% endif %}
{% if isValid %}
<p class="timed-download__countdown" data-expires-at="{{ expiresAt }}">
Verbleibende Zeit: --:--:--
</p>
<p class="timed-download__valid-until">
Gueltig bis:
<time datetime="{{ expiresAt|date('c') }}">{{ expiresAt|date('d.m.Y H:i') }} Uhr</time>
</p>
<p class="timed-download__link">
<a href="{{ downloadUrl }}">Download starten</a>
</p>
<p class="timed-download__share-link">
<a href="{{ shareUrl|default('#') }}">Diesen Link speichern</a>
</p>
<script>
(function () {
var root = document.currentScript ? document.currentScript.closest('[data-timed-download]') : null;
if (!root) {
return;
}
var token = '{{ token|e('js') }}';
if (token) {
var currentUrl = new URL(window.location.href);
if (currentUrl.searchParams.get('tdl') !== token) {
currentUrl.searchParams.set('tdl', token);
window.history.replaceState({}, '', currentUrl.toString());
var shareLinkElement = root.querySelector('.timed-download__share-link a');
if (shareLinkElement) {
shareLinkElement.href = currentUrl.toString();
}
}
}
var fallbackShareLinkElement = root.querySelector('.timed-download__share-link a');
if (fallbackShareLinkElement && '#' === fallbackShareLinkElement.getAttribute('href')) {
fallbackShareLinkElement.href = window.location.href;
}
var element = root.querySelector('.timed-download__countdown[data-expires-at]');
if (!element) {
return;
}
var expiresAt = parseInt(element.getAttribute('data-expires-at'), 10);
if (Number.isNaN(expiresAt)) {
return;
}
function formatCountdown(secondsTotal) {
var seconds = Math.max(0, secondsTotal);
var days = Math.floor(seconds / 86400);
var hours = Math.floor((seconds % 86400) / 3600);
var minutes = Math.floor((seconds % 3600) / 60);
var secs = seconds % 60;
var hh = String(hours).padStart(2, '0');
var mm = String(minutes).padStart(2, '0');
var ss = String(secs).padStart(2, '0');
if (days > 0) {
return days + 'd ' + hh + ':' + mm + ':' + ss;
}
return hh + ':' + mm + ':' + ss;
}
function render() {
var now = Math.floor(Date.now() / 1000);
var remaining = expiresAt - now;
if (remaining <= 0) {
element.textContent = 'Der Download-Link ist abgelaufen.';
return false;
}
element.textContent = 'Verbleibende Zeit: ' + formatCountdown(remaining);
return true;
}
if (!render()) {
return;
}
var timer = window.setInterval(function () {
if (!render()) {
window.clearInterval(timer);
window.location.reload();
}
}, 1000);
})();
</script>
{% elseif isExpired %}
<p class="timed-download__expired">Der Download-Link ist abgelaufen.</p>
{% else %}
<p class="timed-download__missing">Kein gueltiger Download-Link vorhanden.</p>
{% endif %}
</div>