Prompt the user for their approval before displaying external media
authorAlexander Ebert <ebert@woltlab.com>
Fri, 12 Jun 2020 18:14:39 +0000 (20:14 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Fri, 12 Jun 2020 18:14:39 +0000 (20:14 +0200)
12 files changed:
com.woltlab.wcf/option.xml
com.woltlab.wcf/templates/messageUserConsent.tpl [new file with mode: 0644]
com.woltlab.wcf/userOption.xml
constants.php
wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/UserConsent.js [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/FirstTimeSetupForm.class.php
wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProvider.class.php
wcfsetup/install/files/lib/data/user/UserAction.class.php
wcfsetup/install/files/style/ui/messageUserConsent.scss [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 7e5bea9e463f8e3b17816738fb12228050a7add7..e12cbd2b8086660c82ea351572f2c7ac24df7ef3 100644 (file)
@@ -1544,6 +1544,11 @@ ruby
 rust
 go</defaultvalue>
                        </option>
+                       <option name="message_enable_user_consent">
+                               <categoryname>message.general</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
                        <!-- /message.general -->
                        <option name="search_results_per_page">
                                <categoryname>message.search</categoryname>
diff --git a/com.woltlab.wcf/templates/messageUserConsent.tpl b/com.woltlab.wcf/templates/messageUserConsent.tpl
new file mode 100644 (file)
index 0000000..0cd19d9
--- /dev/null
@@ -0,0 +1,11 @@
+<div class="messageUserConsent" data-payload="{$payload}">
+       <div class="messageUserConsentHeader">
+               <span class="messageUserConsentTitle">{lang}wcf.message.user.consent.title{/lang}</span>
+               <a href="{$url}" class="messageUserConsentHost externalURL">{$host}</a>
+       </div>
+       <div class="messageUserConsentDescription">{lang}wcf.message.user.consent.description{/lang}</div>
+       <div class="messageUserConsentButtonContainer">
+               <button class="small jsButtonMessageUserConsentEnable">{lang}wcf.message.user.consent.button.enable{/lang}</button>
+       </div>
+       <div class="messageUserConsentNotice">{lang}wcf.message.user.consent.notice{/lang}</div>
+</div>
index 769aade42c8018b5e6ee95a3622ba71a49dcc62e..4decf051fa487d802245aae30d96a43e293de8fb 100644 (file)
 3:wcf.user.access.nobody</selectoptions>
                                <editable>3</editable>
                        </option>
+                       <option name="enableEmbeddedMedia">
+                               <categoryname>settings.privacy.content</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <editable>3</editable>
+                               <options>message_enable_user_consent</options>
+                       </option>
                        <!-- settings.privacy.messaging -->
                        <option name="canViewEmailAddress">
                                <categoryname>settings.privacy.messaging</categoryname>
index 61605901e2b7a3f7c822342e62379916dc90b705..a960771b6a480608e9cd7110375afa91685a82d2 100644 (file)
@@ -263,3 +263,4 @@ define('BLACKLIST_SFS_EMAIL_ADDRESS', 'moreThanOnce');
 define('BLACKLIST_SFS_IP_ADDRESS', '90percentile');
 define('BLACKLIST_SFS_ACTION', 'disable');
 define('ENABLE_ENTERPRISE_MODE', 0);
+define('MESSAGE_ENABLE_USER_CONSENT', 1);
index 0742b7b676cb5b1153c8cf0e4af14c74c2ac5c48..6a0b64e4d694e108d3d81f39034fe0c47ca0ad2d 100644 (file)
@@ -9,11 +9,13 @@
 define(
        [
                'WoltLabSuite/Core/BackgroundQueue', 'WoltLabSuite/Core/Bootstrap', 'WoltLabSuite/Core/Controller/Style/Changer',
-               'WoltLabSuite/Core/Controller/Popover', 'WoltLabSuite/Core/Ui/User/Ignore', 'WoltLabSuite/Core/Ui/Page/Header/Menu'
+               'WoltLabSuite/Core/Controller/Popover', 'WoltLabSuite/Core/Ui/User/Ignore', 'WoltLabSuite/Core/Ui/Page/Header/Menu',
+               'WoltLabSuite/Core/Ui/Message/UserConsent'
        ],
        function(
                BackgroundQueue, Bootstrap, ControllerStyleChanger,
-               ControllerPopover, UiUserIgnore, UiPageHeaderMenu
+               ControllerPopover, UiUserIgnore, UiPageHeaderMenu,
+               UiMessageUserConsent
        )
 {
        "use strict";
@@ -52,6 +54,8 @@ define(
                        if (COMPILER_TARGET_DEFAULT) {
                                UiUserIgnore.init();
                        }
+                       
+                       UiMessageUserConsent.init();
                },
                
                /**
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/UserConsent.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/UserConsent.js
new file mode 100644 (file)
index 0000000..348e99d
--- /dev/null
@@ -0,0 +1,65 @@
+define(['Ajax', 'Core', 'User', 'Dom/ChangeListener', 'Dom/Util'], function (Ajax, Core, User, DomChangeListener, DomUtil) {
+       var _enableAll = false;
+       var _knownButtons = (typeof window.WeakSet === 'function') ? new window.WeakSet() : new window.Set();
+       
+       return {
+               init: function () {
+                       if (window.sessionStorage.getItem(Core.getStoragePrefix() + 'user-consent') === 'all') {
+                               _enableAll = true;
+                       }
+                       
+                       this._registerEventListeners();
+                       
+                       DomChangeListener.add(
+                               'WoltLabSuite/Core/Ui/Message/UserConsent',
+                               this._registerEventListeners.bind(this)
+                       );
+               },
+               
+               _registerEventListeners: function () {
+                       if (_enableAll) {
+                               this._enableAll();
+                       }
+                       else {
+                               elBySelAll('.jsButtonMessageUserConsentEnable', undefined, (function (button) {
+                                       if (!_knownButtons.has(button)) {
+                                               button.addEventListener('click', this._click.bind(this));
+                                               _knownButtons.add(button);
+                                       }
+                               }).bind(this));
+                       }
+               },
+               
+               _click: function (event) {
+                       event.preventDefault();
+                       
+                       _enableAll = true;
+                       
+                       this._enableAll();
+                       
+                       if (User.userId) {
+                               Ajax.apiOnce({
+                                       data: {
+                                               actionName: 'saveUserConsent',
+                                               className: 'wcf\\data\\user\\UserAction'
+                                       },
+                                       silent: true
+                               });
+                       }
+                       else {
+                               window.sessionStorage.setItem(Core.getStoragePrefix() + 'user-consent', 'all');
+                       }
+               },
+               
+               _enableExternalMedia: function (container) {
+                       var payload = atob(elData(container, 'payload'));
+                       
+                       DomUtil.insertHtml(payload, container, 'before');
+                       elRemove(container);
+               },
+               
+               _enableAll: function () {
+                       elBySelAll('.messageUserConsent', undefined, this._enableExternalMedia.bind(this));
+               }
+       };
+});
index 28b23f667e208b028e0d2153643a2642585559c4..fe23827af25a0bd44af32ebf53a560a04dbfe133 100644 (file)
@@ -41,6 +41,7 @@ class FirstTimeSetupForm extends AbstractOptionListForm {
                'mail_from_address',
                'mail_admin_address',
                'image_allow_external_source',
+               'message_enable_user_consent',
                'module_contact_form',
                'log_ip_address',
                'package_server_auth_code',
index f920be955b55fa49ac2dfe2e47863ba292023857..b3e9d91d3b8d58fd98e9493ceb30a4b01550252d 100644 (file)
@@ -5,13 +5,15 @@ use wcf\system\bbcode\media\provider\IBBCodeMediaProvider;
 use wcf\system\cache\builder\BBCodeMediaProviderCacheBuilder;
 use wcf\system\request\IRouteController;
 use wcf\system\Regex;
+use wcf\system\WCF;
 use wcf\util\StringUtil;
+use wcf\util\Url;
 
 /**
  * Represents a BBCode media provider.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Bbcode\Media\Provider
  *
@@ -98,14 +100,14 @@ class BBCodeMediaProvider extends DatabaseObject implements IRouteController {
                        if (!$regex->match($url)) continue;
                        
                        if ($this->getCallback() !== null) {
-                               return $this->getCallback()->parse($url, $regex->getMatches());
+                               return $this->getOutputForUserConsent($url, $this->getCallback()->parse($url, $regex->getMatches()) );
                        }
                        else {
                                $output = $this->html;
                                foreach ($regex->getMatches() as $name => $value) {
                                        $output = str_replace('{$' . $name . '}', $value, $output);
                                }
-                               return $output;
+                               return $this->getOutputForUserConsent($url, $output);
                        }
                }
                
@@ -133,4 +135,27 @@ class BBCodeMediaProvider extends DatabaseObject implements IRouteController {
                
                return $this->callback;
        }
+       
+       /**
+        * Replaces embedded media with an approval dialog.
+        * 
+        * @param string $url
+        * @param string $html
+        * @return string
+        */
+       protected function getOutputForUserConsent($url, $html) {
+               if (!MESSAGE_ENABLE_USER_CONSENT) {
+                       return $html;
+               }
+               
+               if (WCF::getUser()->userID && WCF::getUser()->getUserOption('enableEmbeddedMedia')) {
+                       return $html;
+               }
+               
+               return WCF::getTPL()->fetch('messageUserConsent', 'wcf', [
+                       'host' => Url::parse($url)['host'],
+                       'payload' => base64_encode($html),
+                       'url' => $url,
+               ]);
+       }
 }
index f8cea74a7f0987af8821d6d8f4e1cdd81d0ef5ab..cb6d86fb986d1c553e3705adbc9fd4fb14ddbffd 100644 (file)
@@ -989,6 +989,21 @@ class UserAction extends AbstractDatabaseObjectAction implements IClipboardActio
                // does nothing
        }
        
+       /**
+        * @since 5.3
+        */
+       public function validateSaveUserConsent() {}
+       
+       /**
+        * @since 5.3
+        */
+       public function saveUserConsent() {
+               $userEditor = new UserEditor(WCF::getUser());
+               $userEditor->updateUserOptions([
+                       User::getUserOptionID('enableEmbeddedMedia') => 1,
+               ]);
+       }
+       
        /**
         * Validates the 'resendActivationMail' action.
         * 
diff --git a/wcfsetup/install/files/style/ui/messageUserConsent.scss b/wcfsetup/install/files/style/ui/messageUserConsent.scss
new file mode 100644 (file)
index 0000000..a51d266
--- /dev/null
@@ -0,0 +1,34 @@
+.messageUserConsent {
+       background-color: $wcfContentBackground;
+       border-radius: 2px;
+       box-shadow: 0 0 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24);
+       margin: 10px 0;
+       max-width: 600px;
+       padding: 20px;
+       
+       @include screen-sm-down {
+               padding: 10px;
+       }
+}
+
+.messageUserConsentHeader {
+       display:flex;
+       justify-content: space-between;
+}
+
+.messageUserConsentTitle {
+       font-size: 18px;
+       font-weight: 600;
+       line-height: 1.05;
+       margin-bottom: 10px;
+}
+
+.messageUserConsentButtonContainer {
+       margin: 10px 0;
+       text-align: center;
+}
+
+.messageUserConsentNotice {
+       color: $wcfContentDimmedText;
+       font-size: 12px;
+}
index 714e010d033f6486a59fc74dbf6374950260811d..cfe0d5edaa43caf06cd39caa18fa119cf45faa1a 100644 (file)
@@ -1762,6 +1762,7 @@ Die Datenbestände werden sorgfältig gepflegt, aber es ist nicht ausgeschlossen
                <item name="wcf.acp.option.blacklist_sfs_action.description"><![CDATA[Es besteht immer das Risiko eines fehlerhaften Eintrages, daher wird die Einstellung <strong>Deaktivierung, erfordert manuelle Freischaltung</strong> ausdrücklich empfohlen.]]></item>
                <item name="wcf.acp.option.blacklist_sfs_action.disable"><![CDATA[Deaktivierung, erfordert manuelle Freischaltung]]></item>
                <item name="wcf.acp.option.module_amp"><![CDATA[<abbr title="Accelerated Mobile Pages">AMP</abbr>]]></item>
+               <item name="wcf.acp.option.message_enable_user_consent"><![CDATA[Inhalte von externen Anbietern erst nach Zustimmung anzuzeigen]]></item>
        </category>
        <category name="wcf.acp.customOption">
                <item name="wcf.acp.customOption.list"><![CDATA[Eingabefelder]]></item>
@@ -4144,6 +4145,10 @@ Dateianhänge:
                <item name="wcf.message.toc"><![CDATA[Inhaltsverzeichnis]]></item>
                <item name="wcf.message.toc.hide"><![CDATA[Verbergen]]></item>
                <item name="wcf.message.toc.show"><![CDATA[Anzeigen]]></item>
+               <item name="wcf.message.user.consent.button.enable"><![CDATA[Alle externen Inhalte anzeigen]]></item>
+               <item name="wcf.message.user.consent.description"><![CDATA[Inhalte von externen Seiten werden ohne {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Zustimmung nicht automatisch geladen und angezeigt.]]></item>
+               <item name="wcf.message.user.consent.notice"><![CDATA[Durch die Aktivierung der externen Inhalte {if LANGUAGE_USE_INFORMAL_VARIANT}erklärst du dich{else}erklären Sie sich{/if} damit einverstanden, dass personenbezogene Daten an Drittplattformen übermittelt werden. Mehr Informationen dazu haben wir in unserer Datenschutzerklärung zur Verfügung gestellt.]]></item>
+               <item name="wcf.message.user.consent.title"><![CDATA[Externer Inhalt]]></item>
        </category>
        <category name="wcf.menu">
                <!-- category for menus and menu items -->
@@ -5231,6 +5236,7 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|lang
                <item name="wcf.user.option.searchRadioButtonOption"><![CDATA[Auswahl des Benutzers bei „{$option->getTitle()}“:]]></item>
                <item name="wcf.user.option.searchTextOption"><![CDATA[„{$option->getTitle()}“ enthält:]]></item>
                <item name="wcf.user.option.searchBooleanOption"><![CDATA[Auswahl des Benutzers bei „{$option->getTitle()}“:]]></item>
+               <item name="wcf.user.option.enableEmbeddedMedia"><![CDATA[Alle externen Inhalte anzeigen]]></item>
        </category>
        <category name="wcf.user.mail">
                <item name="wcf.user.mail.mail.plaintext"><![CDATA[Hallo {@$mailbox->getUser()->username},
index 9c7e207d20a7f8f161f5216be338d0aed6fdd1c4..abc2804f204b71ec391f7f8a4972d60d30efe779 100644 (file)
@@ -1746,6 +1746,7 @@ The database is carefully maintained, but there will be always be a margin of er
                <item name="wcf.acp.option.blacklist_sfs_action.description"><![CDATA[There is always the risk of a false-positive match, therefore it is highly recommended to set it to <strong>Disable and require manual approval</strong>.]]></item>
                <item name="wcf.acp.option.blacklist_sfs_action.disable"><![CDATA[Disable and require manual approval]]></item>
                <item name="wcf.acp.option.module_amp"><![CDATA[<abbr title="Accelerated Mobile Pages">AMP</abbr>]]></item>
+               <item name="wcf.acp.option.message_enable_user_consent"><![CDATA[Prompt users before displaying content from external sources]]></item>
        </category>
        <category name="wcf.acp.customOption">
                <item name="wcf.acp.customOption.list"><![CDATA[Option Fields]]></item>
@@ -4089,6 +4090,10 @@ Attachments:
                <item name="wcf.message.toc"><![CDATA[Contents]]></item>
                <item name="wcf.message.toc.hide"><![CDATA[hide]]></item>
                <item name="wcf.message.toc.show"><![CDATA[show]]></item>
+               <item name="wcf.message.user.consent.button.enable"><![CDATA[Display all external content]]></item>
+               <item name="wcf.message.user.consent.description"><![CDATA[Content embedded from external sources will not be displayed without your consent.]]></item>
+               <item name="wcf.message.user.consent.notice"><![CDATA[Through the activation of external content, you agree that personal data may be transferred to third party platforms. We have provided more information on this in our privacy policy.]]></item>
+               <item name="wcf.message.user.consent.title"><![CDATA[External Content]]></item>
        </category>
        <category name="wcf.menu">
                <!-- category for menus and menu items -->
@@ -5229,6 +5234,7 @@ your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|langu
                <item name="wcf.user.option.searchRadioButtonOption"><![CDATA[User’s selection for “{$option->getTitle()}”:]]></item>
                <item name="wcf.user.option.searchTextOption"><![CDATA[“{$option->getTitle()}” contains:]]></item>
                <item name="wcf.user.option.searchBooleanOption"><![CDATA[User’s selection for “{$option->getTitle()}”:]]></item>
+               <item name="wcf.user.option.enableEmbeddedMedia"><![CDATA[Display all external content]]></item>
        </category>
        <category name="wcf.user.mail">
                <item name="wcf.user.mail.mail.plaintext"><![CDATA[Dear {@$mailbox->getUser()->username},