Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

All notable changes to this project will be documented in this file.

## [4.0.0-beta2.0] - 2026-05-28


## [4.0.0-beta1.2] - 2026-05-28
- Fixed: download links in shared watchlist not working

Expand Down
135 changes: 74 additions & 61 deletions contao/templates/twig/frontend_module/watchlist_share_list.html.twig
Original file line number Diff line number Diff line change
@@ -1,74 +1,87 @@
{% extends "@Contao/block_unsearchable" %}
{% extends "@Contao/content_element/_base.html.twig" %}
{% import "@ContaoCore/Image/Studio/_macros.html.twig" as studio %}

{% block content %}
{% if watchlistNotFound %}
<p class="watchlist-not-found">
{{ 'huh.watchlist.misc.watchlistNotFound'|trans }}
</p>
{% if watchlistNotFound|default %}
{{ block('not_found') }}
{% else %}
{% if title %}
<h2>
{{ title }}
</h2>
{% if items is empty %}
{{ block('no_items') }}
{% else %}
{% block items %}
<div{{ attrs().addClass('items').mergeWith(items_wrapper_attributes|default) }}>
{% for item in items %}
{{ block('item') }}
{% endfor %}
</div>
{% endblock %}
{% endif %}

<div class="items">
{% if items is empty %}
<p class="no-items">
{{ 'huh.watchlist.misc.noItemsInCurrentWatchlist'|trans }}
</p>
{% else %}
{% for item in items %}
<div class="item">
{% if item.type == constant('HeimrichHannot\\WatchlistBundle\\DataContainer\\WatchlistItemContainer::TYPE_FILE') and item.imageData_file is defined %}
<div class="image">
{{ include('@Contao/image.html.twig', item.imageData_file) }}
</div>
{% elseif item.type == constant('HeimrichHannot\\WatchlistBundle\\DataContainer\\WatchlistItemContainer::TYPE_ENTITY') and item.imageData_entityFile is defined %}
<div class="image">
{{ include('@Contao/image.html.twig', item.imageData_entityFile) }}
</div>
{% endif %}

<h6 class="title">
{{ item.title }}
</h6>

{% if not item.existing %}
<div class="non-existing">
<small>
{{ ('huh.watchlist.misc.linked' ~ item.type|capitalize ~ 'Missing')|trans }}
</small>
</div>
{% else %}
{% if item.downloadUrl %}
<div class="download-wrapper">
<a href="{{ item.downloadUrl }}" class="download">
{{ 'huh.watchlist.misc.download'|trans }}
</a>
</div>
{% elseif item.entityUrl %}
<div class="show-wrapper">
<a href="{{ item.entityUrl }}" class="show">
{{ 'huh.watchlist.misc.show'|trans }}
</a>
</div>
{% endif %}
{% endif %}
</div>
{% endfor %}
{% endif %}
</div>

{% if items is not empty %}
<div class="actions">
{% if hasDownloadableFiles %}
<a href="{{ watchlistDownloadAllUrl }}" class="download-all">
{{ 'huh.watchlist.misc.downloadAll'|trans }}
</a>
{% if has_downloads|default %}
{{ block('download_all') }}
{% endif %}
</div>
{% endif %}
{% endif %}
{% endblock %}

{% block not_found %}
<p{{ attrs().addClass("watchlist-not-found").mergeWith(watchlist_not_found_attributes|default) }}>
{{ 'huh.watchlist.misc.watchlistNotFound'|trans }}
</p>
{% endblock %}

{% block no_items %}
<p{{ attrs().addClass('no-items').mergeWith(no_items_attributes|default) }}>
{{ 'huh.watchlist.misc.noItemsInCurrentWatchlist'|trans }}
</p>
{% endblock %}

{% block item %}
{# @var HeimrichHannot\WatchlistBundle\Item\WatchlistItem item #}
<div{{ attrs().addClass('item').mergeWith(item_wrapper_attributes|default) }}>
{# Image rendering with fallback chain #}
{% block item_figure %}
{% if item.getImage()|default %}
{{ studio.figure(item.getImage()) }}
{% endif %}
{% endblock %}

{% block item_title %}
<h6 class="title">
{{ item.title }}
</h6>
{% endblock %}

{% if not item.fileExist %}
<div class="non-existing">
<small>
{{ ('huh.watchlist.misc.linked' ~ item.type|capitalize ~ 'Missing')|trans }}
</small>
</div>
{% else %}
{% if item.downloadUrl %}
<div class="download-wrapper">
<a{{ attrs().addClass('download').set('href', item.downloadUrl).mergeWith(link_attributes|default) }}>
{{ 'huh.watchlist.misc.download'|trans }}
</a>
</div>
{% elseif item.entityUrl %}
<div class="show-wrapper">
<a{{ attrs().addClass('show').set('href', item.model.entityUrl).mergeWith(link_attributes|default) }}>
{{ 'huh.watchlist.misc.show'|trans }}
</a>
</div>
{% endif %}
{% endif %}
</div>
{% endblock %}

{% block download_all %}
<a{{ attrs().addClass('download-all').set('href', download_all).mergeWith(link_attributes|default) }}>
{{ 'huh.watchlist.misc.downloadAll'|trans }}
</a>
{% endblock %}

Original file line number Diff line number Diff line change
@@ -1,80 +1,11 @@
{% extends "@Contao/frontend_module/watchlist_share_list.html.twig" %}

{% block content %}
{% if watchlistNotFound %}
<p class="watchlist-not-found alert alert-danger">
{{ 'huh.watchlist.misc.watchlistNotFound'|trans }}
</p>
{% else %}
{% if title %}
<h2>
{{ title }}
</h2>
{% endif %}

<div class="items row">
{% if items is empty %}
<p class="no-items alert alert-warning">
{{ 'huh.watchlist.misc.noItemsInCurrentWatchlist'|trans }}
</p>
{% else %}
{% for item in items %}
<div class="item col-md-6 col-lg-4 mb-5">
{% if item.figure ?? false %}
<div class="figure mb-3">
{{ item.figure|raw }}
</div>
{% endif %}

{% if item.type == constant('HeimrichHannot\\WatchlistBundle\\DataContainer\\WatchlistItemContainer::TYPE_FILE') and item.imageData_file is defined %}
<div class="image mb-3">
{{ include('@Contao/image.html.twig', item.imageData_file) }}
</div>
{% elseif item.type == constant('HeimrichHannot\\WatchlistBundle\\DataContainer\\WatchlistItemContainer::TYPE_ENTITY') and item.imageData_entityFile is defined %}
<div class="image mb-3">
{{ include('@Contao/image.html.twig', item.imageData_entityFile) }}
</div>
{% endif %}

<h6 class="title">
{{ item.title }}
</h6>

{% if not item.existing %}
<div class="non-existing">
<small>
{{ ('huh.watchlist.misc.linked' ~ item.type|capitalize ~ 'Missing')|trans }}
</small>
</div>
{% else %}
{% if item.downloadUrl %}
<div class="download-wrapper col-md-4">
<a href="{{ item.downloadUrl }}" class="download btn btn-primary">
{{ 'huh.watchlist.misc.download'|trans }}
</a>
</div>
{% elseif item.entityUrl %}
<div class="show-wrapper col-md-4">
<a href="{{ item.entityUrl }}" class="show btn btn-primary">
{{ 'huh.watchlist.misc.show'|trans }}
</a>
</div>
{% endif %}
{% endif %}
</div>
{% endfor %}
{% endif %}
</div>

{% if items is not empty %}
<div class="actions">
{% if hasDownloadableFiles %}
<a href="{{ watchlistDownloadAllUrl }}" class="download-all btn btn-primary">
{{ 'huh.watchlist.misc.downloadAll'|trans }}
</a>
{% endif %}
</div>
{% endif %}
{% endif %}
{% set watchlist_not_found_attributes = attrs().addClass('alert alert-danger') %}
{% set no_items_attributes = attrs().addClass('alert alert-warning') %}
{% set items_wrapper_attributes = attrs().addClass('row') %}
{% set item_wrapper_attributes = attrs().addClass('col-md-6 col-lg-4 mb-5') %}
{% set link_attributes = attrs().addClass('btn btn-primary') %}
{{ parent() }}
{% endblock %}

32 changes: 15 additions & 17 deletions src/Controller/FrontendModule/ShareListModuleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Contao\CoreBundle\Filesystem\FilesystemItemIterator;
use Contao\CoreBundle\Filesystem\VirtualFilesystemInterface;
use Contao\CoreBundle\Framework\ContaoFramework;
use Contao\CoreBundle\Twig\FragmentTemplate;
use Contao\ModuleModel;
use Contao\Template;
use HeimrichHannot\WatchlistBundle\Item\WatchlistItem;
Expand All @@ -30,7 +31,7 @@
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Routing\RouterInterface;

#[AsFrontendModule(ShareListModuleController::TYPE, category: 'miscellaneous', template: 'frontend_modules/watchlist_share_list')]
#[AsFrontendModule(ShareListModuleController::TYPE, category: 'miscellaneous', template: 'frontend_module/watchlist_share_list')]
class ShareListModuleController extends AbstractFrontendModuleController
{
const TYPE = 'watchlist_share_list';
Expand All @@ -53,7 +54,7 @@ public function __invoke(Request $request, ModuleModel $model, string $section,
return parent::__invoke($request, $model, $section, $classes);
}

protected function getResponse(Template $template, ModuleModel $module, Request $request): Response
protected function getResponse(FragmentTemplate $template, ModuleModel $model, Request $request): Response
{
if (!($watchlistUuid = $request->get('watchlist'))) {
$template->watchlistNotFound = true;
Expand All @@ -68,29 +69,26 @@ protected function getResponse(Template $template, ModuleModel $module, Request
return $template->getResponse();
}

$items = [];

$watchlistItemModels = $this->watchlistUtil->getWatchlistItems(
$watchlist->id,
['modelOptions' => ['order' => 'title ASC'],],
);
foreach ($watchlistItemModels as $model) {
$item = $model->row();
$wlItem = $this->watchlistItemFactory->build($model);
$item = $wlItem->applyToTemplateData($item);

if (WatchlistItemType::FILE === $wlItem->getType() && $wlItem->fileExist()) {
$template->hasDownloadableFiles = true;
}
$items = $this->watchlistItemFactory->buildForCollection($watchlistItemModels);
$template->set('items', $items);

$item['watchlistConfig'] = $watchlist->config;

$items[] = $item;
}

$template->items = $items;
foreach ($items as $item) {
if (WatchlistItemType::FILE === $item->getType() && $item->fileExist()) {
$template->hasDownloadableFiles = true;
$template->set('has_downloads', true);

$template->watchlistDownloadAllUrl = $this->router->generate('huh_watchlist_downlad_all', ['watchlist' => $watchlist->id]);
$downloadAll = $this->router->generate('huh_watchlist_downlad_all', ['watchlist' => $watchlist->id]);
$template->watchlistDownloadAllUrl = $downloadAll;
$template->set('download_all', $downloadAll);
break;
}
}

return $template->getResponse();
}
Expand Down
34 changes: 13 additions & 21 deletions src/Item/WatchlistItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Contao\CoreBundle\Image\Studio\Figure;
use Contao\CoreBundle\Image\Studio\Studio;
use Contao\Image;
use Contao\Image\PictureConfiguration;
use Contao\System;
use HeimrichHannot\WatchlistBundle\Model\WatchlistConfigModel;
use HeimrichHannot\WatchlistBundle\Model\WatchlistItemModel;
Expand All @@ -18,7 +19,6 @@ class WatchlistItem
private readonly WatchlistItemType $type;
private ?FilesystemItem $file;
private ?WatchlistConfigModel $config;
private ?Figure $figure;
private string $downloadUrl;

/**
Expand Down Expand Up @@ -64,27 +64,32 @@ public function applyToTemplateData(array $data): array
};
}

public function getImage(): ?Figure
public function getTitle(): string
{
if (isset($this->figure)) {
return $this->figure;
if ($this->model->title) {
return $this->model->title;
}
return $this->file?->getName() ?: '';
}

public function getImage(PictureConfiguration|array|int|string|null $size = null): ?Figure
{
$this->resolveFile();
if (!$this->fileExist()) {
return null;
}

$figureBuilder = $this->studio->createFigureBuilder()
->fromUuid($this->file->getUuid())
->enableLightbox();;
->enableLightbox();

if ($this->getConfig()?->imgSize) {
if ($size) {
$figureBuilder->setSize($size);
} elseif ($this->getConfig()?->imgSize) {
$figureBuilder->setSize($this->getConfig()->imgSize);
}

$this->figure = $figureBuilder->buildIfResourceExists();
return $this->figure;
return $figureBuilder->buildIfResourceExists();
}

public function getFile(): ?FilesystemItem
Expand Down Expand Up @@ -142,14 +147,6 @@ private function fileTemplateData(array $data): array
$data['existing'] = true;
$data['fileItem'] = $this->file;
$data['file'] = (string)$this->file->getUuid();

// create the url with file-GET-parameter so that also nonpublic files can be accessed safely
// $url = $this->insertTagParser->replace('{{download_link::' . $file->path . '}}');
// $query = parse_url((string)$url, \PHP_URL_QUERY);
// $url = $this->utils->url()->addQueryStringParameterToUrl($query, $currentUrl);
//
// $cleanedItem['downloadUrl'] = $this->utils->url()->removeQueryStringParameterFromUrl(['wl_root_page', 'wl_url'], $url);

$data['downloadUrl'] = $this->getDownloadUrl();

if (empty($data['title'])) {
Expand All @@ -169,14 +166,9 @@ private function entityTemplateData(array $data): array
$data['entityTable'] = $this->model->entityTable;
$data['entity'] = $this->model->entity;
$data['entityUrl'] = $this->model->entityUrl;


$data['entityFile'] = (string)$this->getFile()?->getUuid() ?: '';
$data['existing'] = $this->fileExist();
$data['hash'] = md5(implode('_', [$this->getType()->value, $this->model->pid, $this->model->entityTable, $this->model->entity]));

// $cleanedItem['postData'] = htmlspecialchars(json_encode($cleanedItem), \ENT_QUOTES, 'UTF-8');

$data['figure'] = $this->getImage();

return $data;
Expand Down
Loading