Skip to content

Commit 598e4cc

Browse files
authored
Merge pull request #6372 from WoltLab/6.2-fancyapps-v6-consent
Only display external content in Fancybox after confirmation by the user
2 parents 673d6ec + 13922a4 commit 598e4cc

File tree

7 files changed

+243
-12
lines changed

7 files changed

+243
-12
lines changed

com.woltlab.wcf/templates/shared_imageViewer.tpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444
}
4545
}
4646
</script>
47+
{if MESSAGE_ENABLE_USER_CONSENT}
48+
<template id="consentImageViewer" data-show-all-media="{if $__wcf->getUser()->userID && $__wcf->getUser()->getUserOption('enableEmbeddedMedia')}1{else}0{/if}">
49+
{include file="messageUserConsent" target='' host='' url='' sandbox=true}
50+
</template>
51+
{/if}
4752

4853
{assign var=__imageViewerLoaded value=true}
4954
{/if}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* @author Olaf Braun
3+
* @copyright 2001-2025 WoltLab GmbH
4+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
5+
*/
6+
7+
import { CarouselSlide, FancyboxInstance } from "@fancyapps/ui";
8+
import { CarouselInstance } from "@fancyapps/ui/dist/carousel/carousel";
9+
import { allExternalMediaEnabled } from "WoltLabSuite/Core/Ui/Message/UserConsent";
10+
import { stringToBool } from "WoltLabSuite/Core/Core";
11+
12+
export class ConsentPlugin {
13+
#api: FancyboxInstance | undefined = undefined;
14+
#sliderToType = new Map<CarouselSlide, string | undefined>();
15+
#templateNode: HTMLTemplateElement | undefined;
16+
#onAddSlideEvent = this.#onAddSlide.bind(this);
17+
#onAttachSlideElEvent = this.#onAttachSlideEl.bind(this);
18+
19+
constructor() {
20+
this.#templateNode = document.getElementById("consentImageViewer") as HTMLTemplateElement;
21+
}
22+
23+
init(api: FancyboxInstance): void {
24+
if (this.#showAllMedia()) {
25+
return;
26+
}
27+
28+
this.#api = api;
29+
30+
api.on("Carousel.addSlide", this.#onAddSlideEvent).on("Carousel.attachSlideEl", this.#onAttachSlideElEvent);
31+
}
32+
33+
destroy(): void {
34+
this.#api
35+
?.off("Carousel.addSlide", this.#onAddSlideEvent)
36+
?.off("Carousel.attachSlideEl", this.#onAttachSlideElEvent);
37+
}
38+
39+
#onAddSlide(_: FancyboxInstance, __: CarouselInstance, slide: CarouselSlide): void {
40+
if (!slide.src) {
41+
return;
42+
}
43+
44+
if (this.#isExternalURL(slide.src)) {
45+
this.#sliderToType.set(slide, slide.type);
46+
slide.type = "consent";
47+
}
48+
}
49+
50+
#onAttachSlideEl(_: FancyboxInstance, carousel: CarouselInstance, slide: CarouselSlide): void {
51+
if (slide.type !== "consent") {
52+
return;
53+
}
54+
55+
slide.el!.innerHTML = "";
56+
slide.el!.append(this.getConsentHTML(carousel, slide));
57+
}
58+
59+
private getConsentHTML(carousel: CarouselInstance, slide: CarouselSlide): DocumentFragment {
60+
const clone = this.#templateNode!.content.cloneNode(true) as DocumentFragment;
61+
62+
const messageUserConsent = clone.querySelector(".messageUserConsent") as HTMLElement;
63+
messageUserConsent.dataset.payload = "";
64+
65+
const externalURL = clone.querySelector(".externalURL") as HTMLAnchorElement;
66+
const url = new URL(slide.src!);
67+
externalURL.href = slide.src!;
68+
externalURL.innerText = url.host;
69+
70+
const button = clone.querySelector(".jsButtonMessageUserConsentEnable") as HTMLButtonElement;
71+
button.addEventListener("click", () => {
72+
this.destroy();
73+
74+
this.#templateNode!.dataset.showAllMedia = "1";
75+
76+
carousel
77+
.getSlides()
78+
.filter((slide) => slide.type === "consent")
79+
.forEach((slide) => {
80+
slide.type = this.#sliderToType.get(slide);
81+
slide.el!.innerHTML = "";
82+
});
83+
84+
// refresh current slide content
85+
carousel.emit("detachSlideEl", slide);
86+
carousel.emit("attachSlideEl", slide);
87+
});
88+
89+
return clone;
90+
}
91+
92+
#showAllMedia(): boolean {
93+
if (!this.#templateNode) {
94+
return true;
95+
}
96+
97+
if (stringToBool(this.#templateNode.dataset.showAllMedia!)) {
98+
return true;
99+
}
100+
101+
return allExternalMediaEnabled();
102+
}
103+
104+
#isExternalURL(url: string): boolean {
105+
return new URL(url).origin !== window.location.origin;
106+
}
107+
}

ts/WoltLabSuite/Core/Component/Image/Viewer.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
/**
2+
* @author Olaf Braun
3+
* @copyright 2001-2025 WoltLab GmbH
4+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
5+
*/
6+
17
import { Fancybox, CarouselSlide, FancyboxInstance } from "@fancyapps/ui";
28
import { getPageOverlayContainer } from "WoltLabSuite/Core/Helper/PageOverlay";
39
import { getPhrase } from "WoltLabSuite/Core/Language";
10+
import { ConsentPlugin } from "./Fancybox/ConsentPlugin";
411

512
setDefaultConfig();
613

714
export function setup() {
8-
Fancybox.bind('[data-fancybox]:is([data-type="image"],[data-type="video"])');
15+
Fancybox.bind(
16+
'[data-fancybox]:is([data-type="image"],[data-type="youtube"],[data-type="vimeo"],[data-type="video"])',
17+
);
918
}
1019

1120
export function setupLegacy() {
@@ -27,6 +36,12 @@ function setDefaultConfig(): void {
2736
autoplay: false,
2837
},
2938
};
39+
if (!defaultConfig.plugins) {
40+
defaultConfig.plugins = {};
41+
}
42+
defaultConfig.plugins.consent = () => {
43+
return new ConsentPlugin();
44+
};
3045
}
3146

3247
export function getLocalization(): Record<string, string> {

ts/WoltLabSuite/Core/Ui/Message/UserConsent.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@ import DomUtil from "../../Dom/Util";
1414
import User from "../../User";
1515

1616
class UserConsent {
17-
private enableAll = false;
18-
1917
constructor() {
2018
if (window.sessionStorage.getItem(`${Core.getStoragePrefix()}user-consent`) === "all") {
21-
this.enableAll = true;
19+
enableAll = true;
2220
}
2321

2422
this.registerEventListeners();
@@ -27,7 +25,7 @@ class UserConsent {
2725
}
2826

2927
private registerEventListeners(): void {
30-
if (this.enableAll) {
28+
if (enableAll) {
3129
this.enableAllExternalMedia();
3230
} else {
3331
wheneverFirstSeen(".jsButtonMessageUserConsentEnable", (button) => {
@@ -39,7 +37,7 @@ class UserConsent {
3937
private click(event: MouseEvent): void {
4038
event.preventDefault();
4139

42-
this.enableAll = true;
40+
enableAll = true;
4341

4442
this.enableAllExternalMedia();
4543

@@ -72,10 +70,15 @@ class UserConsent {
7270
}
7371
}
7472

73+
let enableAll = false;
7574
let userConsent: UserConsent;
7675

7776
export function init(): void {
7877
if (!userConsent) {
7978
userConsent = new UserConsent();
8079
}
8180
}
81+
82+
export function allExternalMediaEnabled(): boolean {
83+
return enableAll;
84+
}

wcfsetup/install/files/js/WoltLabSuite/Core/Component/Image/Fancybox/ConsentPlugin.js

Lines changed: 86 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wcfsetup/install/files/js/WoltLabSuite/Core/Component/Image/Viewer.js

Lines changed: 13 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/UserConsent.js

Lines changed: 8 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)