Skip to content

Commit 8e13efa

Browse files
authored
Merge pull request #36 from customgento/DEV-1111-video-blocking-feature
Dev 1111 video blocking feature
2 parents 1d0fb6a + 8adbb4a commit 8e13efa

File tree

19 files changed

+466
-16
lines changed

19 files changed

+466
-16
lines changed

Model/Config.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Config
1414
public const XML_PATH_DATA_CULTURE = 'web/cookiebot/data_culture';
1515
public const XML_PATH_USE_EU_CDN = 'web/cookiebot/use_eu_cdn';
1616
public const XML_PATH_USE_GOOGLE_CONSENT_MODE = 'web/cookiebot/use_google_consent_mode';
17+
public const XML_PATH_BLOCK_VIDEOS_UNTIL_CONSENT = 'web/cookiebot/block_videos_until_consent';
1718

1819
/**
1920
* @var ScopeConfigInterface
@@ -49,4 +50,9 @@ public function isGoogleConsentModeEnabled(): bool
4950
{
5051
return $this->scopeConfig->isSetFlag(self::XML_PATH_USE_GOOGLE_CONSENT_MODE, ScopeInterface::SCOPE_STORE);
5152
}
53+
54+
public function isBlockVideosUntilConsentEnabled(): bool
55+
{
56+
return $this->scopeConfig->isSetFlag(self::XML_PATH_BLOCK_VIDEOS_UNTIL_CONSENT, ScopeInterface::SCOPE_STORE);
57+
}
5258
}

Model/ExternalVideoReplacer.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CustomGento\Cookiebot\Model;
6+
7+
class ExternalVideoReplacer
8+
{
9+
public function replaceIframeSources(string $content): string
10+
{
11+
$iframePatterns = [
12+
// YouTube patterns
13+
'/<iframe([^>]*)\s+src=["\'](https?:\/\/(?:www\.)?(?:youtube\.com|youtube-nocookie\.com)\/embed\/[^"\']+)["\']([^>]*)>/i',
14+
'/<iframe([^>]*)\s+src=["\'](https?:\/\/(?:www\.)?youtu\.be\/[^"\']+)["\']([^>]*)>/i',
15+
// Vimeo patterns
16+
'/<iframe([^>]*)\s+src=["\'](https?:\/\/(?:www\.)?vimeo\.com\/[^"\']+)["\']([^>]*)>/i',
17+
'/<iframe([^>]*)\s+src=["\'](https?:\/\/(?:www\.)?player\.vimeo\.com\/[^"\']+)["\']([^>]*)>/i'
18+
];
19+
20+
foreach ($iframePatterns as $pattern) {
21+
$content = preg_replace_callback($pattern, function (array $matches) {
22+
$beforeSrc = $matches[1];
23+
$iframeUrl = $matches[2];
24+
$afterSrc = $matches[3];
25+
26+
// Check if data-cookieconsent already exists
27+
if (preg_match('/data-cookieconsent=["\'][^"\']*["\']/', $beforeSrc . $afterSrc)) {
28+
// If data-cookieconsent already exists, just change src to data-cookieblock-src
29+
return '<iframe' . $beforeSrc . ' data-cookieblock-src="' . $iframeUrl . '"' . $afterSrc . '>';
30+
}
31+
32+
return '<iframe' . $beforeSrc . ' data-cookieblock-src="' . $iframeUrl
33+
. '" data-cookieconsent="marketing"' . $afterSrc . '>';
34+
}, $content);
35+
}
36+
37+
return $content;
38+
}
39+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CustomGento\Cookiebot\Observer;
6+
7+
use CustomGento\Cookiebot\Model\Config;
8+
use CustomGento\Cookiebot\Model\ExternalVideoReplacer;
9+
use Exception;
10+
use Magento\Framework\App\Response\Http;
11+
use Magento\Framework\Event\Observer;
12+
use Magento\Framework\Event\ObserverInterface;
13+
use Psr\Log\LoggerInterface;
14+
15+
class HtmlContentFilterObserver implements ObserverInterface
16+
{
17+
private LoggerInterface $logger;
18+
19+
private Config $config;
20+
21+
private ExternalVideoReplacer $externalVideoReplacer;
22+
23+
public function __construct(
24+
LoggerInterface $logger,
25+
Config $config,
26+
ExternalVideoReplacer $externalVideoReplacer
27+
) {
28+
$this->externalVideoReplacer = $externalVideoReplacer;
29+
$this->config = $config;
30+
$this->logger = $logger;
31+
}
32+
33+
public function execute(Observer $observer): void
34+
{
35+
if (!$this->config->isBlockVideosUntilConsentEnabled()) {
36+
return;
37+
}
38+
39+
try {
40+
$response = $observer->getData('response');
41+
42+
if (!$response instanceof Http) {
43+
return;
44+
}
45+
46+
$content = $response->getBody();
47+
48+
if (empty($content) || !is_string($content)) {
49+
return;
50+
}
51+
52+
$modifiedContent = $this->externalVideoReplacer->replaceIframeSources($content);
53+
54+
$response->setBody($modifiedContent);
55+
} catch (Exception $e) {
56+
$this->logger->error('Error in HtmlContentFilterObserver: ' . $e->getMessage());
57+
}
58+
}
59+
}

README.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
11
# CustomGento_Cookiebot
2-
This Magento 2 module integrates Cookiebot into your store. This is useful to give the customer the possibility to block unwanted cookies and only allow wanted cookies. It requires an active Cookiebot subscription and can be completely configured in your Cookiebot account.
2+
3+
This Magento 2 module integrates Cookiebot into your store. This is useful to give the customer the possibility to block
4+
unwanted cookies and only allow wanted cookies. It requires an active Cookiebot subscription and can be completely
5+
configured in your Cookiebot account.
36

47
## Installation
8+
59
- `composer require customgento/module-cookiebot-m2`
610
- `bin/magento module:enable CustomGento_Cookiebot`
711
- `bin/magento setup:upgrade`
812
- `bin/magento setup:di:compile`
913
- `bin/magento cache:flush`
1014

1115
## Configuration
12-
After installing the extension, just go to Stores > Configuration > Web > Cookiebot Settings to enable Cookiebot and enter your Cookiebot ID. You find the ID in your Cookiebot account under Settings > Your scripts. All other configuration regarding the cookie overlay is done in your Cookiebot account.
1316

14-
Please mind that this extension uses the manual cookie blocking mode. Hence, you still need to update all scripts, which set not-strictly-necessary cookies in your system as described in the [Cookiebot manual implementation guide](https://www.cookiebot.com/en/manual-implementation/).
17+
After installing the extension, just go to Stores > Configuration > Web > Cookiebot Settings to enable Cookiebot and
18+
enter your Cookiebot ID. You find the ID in your Cookiebot account under Settings > Your scripts. All other
19+
configuration regarding the cookie overlay is done in your Cookiebot account.
20+
21+
Please mind that this extension uses the manual cookie blocking mode. Hence, you still need to update all scripts, which
22+
set not-strictly-necessary cookies in your system as described in
23+
the [Cookiebot manual implementation guide](https://www.cookiebot.com/en/manual-implementation/).
1524

1625
## Manual vs. Automatic Cookie Blocking
17-
Unfortunately, Cookiebot currently does not support automatic blocking when RequireJS is used as stated in their [docs](https://support.cookiebot.com/hc/en-us/articles/360015039559-Installing-Cookiebot-in-Magento-2-3-4). Since Magento 2 does use RequireJS, we need to use the manual cookie blocking mode :-(
26+
27+
Unfortunately, Cookiebot currently does not support automatic blocking when RequireJS is used as stated in
28+
their [docs](https://support.cookiebot.com/hc/en-us/articles/360015039559-Installing-Cookiebot-in-Magento-2-3-4). Since
29+
Magento 2 does use RequireJS, we need to use the manual cookie blocking mode :-(
1830

1931
## Disclaimer
32+
2033
You need a [Cookiebot](https://www.cookiebot.com/) account, so that this extension does anything useful for you.
2134

2235
## Copyright
36+
2337
&copy; 2020 - present CustomGento GmbH
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CustomGento\Cookiebot\Test\Unit\Model;
6+
7+
use CustomGento\Cookiebot\Model\ExternalVideoReplacer;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class ExternalVideoReplacerTest extends TestCase
11+
{
12+
/**
13+
* @var ExternalVideoReplacer
14+
*/
15+
private $externalVideoReplacer;
16+
17+
protected function setUp(): void
18+
{
19+
$this->externalVideoReplacer = new ExternalVideoReplacer();
20+
}
21+
22+
/**
23+
* @dataProvider iframeDataProvider
24+
*/
25+
public function testReplaceIframeSources(string $input, string $expected): void
26+
{
27+
$result = $this->externalVideoReplacer->replaceIframeSources($input);
28+
$this->assertEquals($expected, $result);
29+
}
30+
31+
public function iframeDataProvider(): array
32+
{
33+
return [
34+
// YouTube test cases
35+
'youtube.com embed' => [
36+
'<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" width="560" height="315"></iframe>',
37+
'<iframe data-cookieblock-src="https://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>'
38+
],
39+
'youtube-nocookie.com embed' => [
40+
'<iframe src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" width="560" height="315"></iframe>',
41+
'<iframe data-cookieblock-src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>'
42+
],
43+
'youtu.be URL' => [
44+
'<iframe src="https://youtu.be/dQw4w9WgXcQ" width="560" height="315"></iframe>',
45+
'<iframe data-cookieblock-src="https://youtu.be/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>'
46+
],
47+
'youtube.com without www' => [
48+
'<iframe src="https://youtube.com/embed/dQw4w9WgXcQ" width="560" height="315"></iframe>',
49+
'<iframe data-cookieblock-src="https://youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>'
50+
],
51+
'youtube-nocookie.com without www' => [
52+
'<iframe src="https://youtube-nocookie.com/embed/dQw4w9WgXcQ" width="560" height="315"></iframe>',
53+
'<iframe data-cookieblock-src="https://youtube-nocookie.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>'
54+
],
55+
'youtu.be without www' => [
56+
'<iframe src="https://youtu.be/dQw4w9WgXcQ" width="560" height="315"></iframe>',
57+
'<iframe data-cookieblock-src="https://youtu.be/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>'
58+
],
59+
'http instead of https for youtube' => [
60+
'<iframe src="http://www.youtube.com/embed/dQw4w9WgXcQ" width="560" height="315"></iframe>',
61+
'<iframe data-cookieblock-src="http://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>'
62+
],
63+
64+
// Vimeo test cases
65+
'vimeo.com embed' => [
66+
'<iframe src="https://vimeo.com/123456789" width="640" height="360"></iframe>',
67+
'<iframe data-cookieblock-src="https://vimeo.com/123456789" data-cookieconsent="marketing" width="640" height="360"></iframe>'
68+
],
69+
'player.vimeo.com embed' => [
70+
'<iframe src="https://player.vimeo.com/video/123456789" width="640" height="360"></iframe>',
71+
'<iframe data-cookieblock-src="https://player.vimeo.com/video/123456789" data-cookieconsent="marketing" width="640" height="360"></iframe>'
72+
],
73+
'www.vimeo.com embed' => [
74+
'<iframe src="https://www.vimeo.com/123456789" width="640" height="360"></iframe>',
75+
'<iframe data-cookieblock-src="https://www.vimeo.com/123456789" data-cookieconsent="marketing" width="640" height="360"></iframe>'
76+
],
77+
'www.player.vimeo.com embed' => [
78+
'<iframe src="https://www.player.vimeo.com/video/123456789" width="640" height="360"></iframe>',
79+
'<iframe data-cookieblock-src="https://www.player.vimeo.com/video/123456789" data-cookieconsent="marketing" width="640" height="360"></iframe>'
80+
],
81+
'http vimeo' => [
82+
'<iframe src="http://vimeo.com/123456789" width="640" height="360"></iframe>',
83+
'<iframe data-cookieblock-src="http://vimeo.com/123456789" data-cookieconsent="marketing" width="640" height="360"></iframe>'
84+
],
85+
// General test cases
86+
'single quotes' => [
87+
'<iframe src=\'https://www.youtube.com/embed/dQw4w9WgXcQ\' width="560" height="315"></iframe>',
88+
'<iframe data-cookieblock-src="https://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>'
89+
],
90+
'multiple iframes different services' => [
91+
'<iframe src="https://www.youtube.com/embed/video1"></iframe><iframe src="https://vimeo.com/video2"></iframe><iframe src="https://www.google.com/maps/embed?pb=test"></iframe>',
92+
'<iframe data-cookieblock-src="https://www.youtube.com/embed/video1" data-cookieconsent="marketing"></iframe><iframe data-cookieblock-src="https://vimeo.com/video2" data-cookieconsent="marketing"></iframe><iframe src="https://www.google.com/maps/embed?pb=test"></iframe>'
93+
],
94+
'mixed content' => [
95+
'<p>Some text</p><iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ"></iframe><p>More text</p><iframe src="https://vimeo.com/123456789"></iframe>',
96+
'<p>Some text</p><iframe data-cookieblock-src="https://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing"></iframe><p>More text</p><iframe data-cookieblock-src="https://vimeo.com/123456789" data-cookieconsent="marketing"></iframe>'
97+
],
98+
'iframe with frameborder and allowfullscreen' => [
99+
'<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" frameborder="0" allowfullscreen=""></iframe>',
100+
'<iframe data-cookieblock-src="https://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" frameborder="0" allowfullscreen=""></iframe>'
101+
],
102+
'iframe with existing data-cookieconsent' => [
103+
'<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing"></iframe>',
104+
'<iframe data-cookieblock-src="https://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing"></iframe>'
105+
],
106+
'iframe with different data-cookieconsent value' => [
107+
'<iframe src="https://vimeo.com/123456789" data-cookieconsent="statistics"></iframe>',
108+
'<iframe data-cookieblock-src="https://vimeo.com/123456789" data-cookieconsent="statistics"></iframe>'
109+
],
110+
'no matching iframe' => [
111+
'<iframe src="https://example.com/video"></iframe>',
112+
'<iframe src="https://example.com/video"></iframe>'
113+
],
114+
'no iframe at all' => [
115+
'<p>Just some text content</p>',
116+
'<p>Just some text content</p>'
117+
],
118+
'empty content' => [
119+
'',
120+
''
121+
]
122+
];
123+
}
124+
}

ViewModel/Script.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,9 @@ public function isGoogleConsentModeEnabled(): bool
3939
{
4040
return $this->config->isGoogleConsentModeEnabled();
4141
}
42+
43+
public function isBlockVideosUntilConsentEnabled(): bool
44+
{
45+
return $this->config->isBlockVideosUntilConsentEnabled();
46+
}
4247
}

composer.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010
}
1111
],
1212
"require": {
13-
"magento/framework": "~102.0||~103.0",
14-
"magento/module-catalog": "~103.0||~104.0",
15-
"magento/module-cms": "~103.0||~104.0",
16-
"php": "~7.4.0||~8.1.0||~8.2.0||~8.3.0||~8.4.0"
13+
"ext-pcre": "*",
14+
"magento/framework": "^103.0",
15+
"magento/module-catalog": "^104.0",
16+
"magento/module-cms": "^104.0",
17+
"magento/module-config": "^101.2",
18+
"magento/module-store": "^101.1",
19+
"php": "^7.4.0||^8.1.0||^8.2.0||^8.3.0||^8.4.0",
20+
"psr/log": "^1.1||^2||^3"
1721
},
1822
"require-dev": {
19-
"phpunit/phpunit": "~6.5||~9.5",
23+
"phpunit/phpunit": "^6.5||^9.5",
2024
"roave/security-advisories": "dev-latest"
2125
},
2226
"autoload": {

etc/adminhtml/system.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
<label>Google Consent Mode</label>
3030
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
3131
</field>
32+
<field id="block_videos_until_consent" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="1">
33+
<label>Block YouTube / Vimeo Videos Until Marketing Consent</label>
34+
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
35+
</field>
3236
</group>
3337
</section>
3438
</system>

etc/config.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<data_culture/>
1010
<use_eu_cdn>0</use_eu_cdn>
1111
<use_google_consent_mode>1</use_google_consent_mode>
12+
<block_videos_until_consent>0</block_videos_until_consent>
1213
</cookiebot>
1314
</web>
1415
</default>

etc/events.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
3+
<event name="controller_front_send_response_before">
4+
<observer name="customgento_cookiebot_html_content_filter" instance="CustomGento\Cookiebot\Observer\HtmlContentFilterObserver"/>
5+
</event>
6+
</config>

0 commit comments

Comments
 (0)