-
-
Notifications
You must be signed in to change notification settings - Fork 5
Dev 1111 video blocking feature #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
ea0940d
Add video block mixins, DEV-1111
iranimij e3c91b4
Add video block mixins for , DEV-1111
iranimij 193be9e
Improve code, DEV-1111
iranimij 33c1998
Add video blocker for page-builder, DEV-1111
iranimij 4b4c10d
Add video blocker config, DEV-1111
iranimij 5d8a8bc
Add video blocker config, DEV-1111
iranimij 5c55e3b
Add video blocker to cms, DEV-1111
iranimij 21ff2dc
Add video blocker to cms, DEV-1111
iranimij ce24408
Optimize import, DEV-1111
iranimij 918d103
Reformat code, DEV-1111
iranimij e262dda
Improve code, DEV-1111
iranimij cb4c893
Improve code, DEV-1111
iranimij 1336879
Improve code, DEV-1111
iranimij 06035f5
Improve code, DEV-1111
iranimij 2409444
Improve code, DEV-1111
iranimij 511d7c0
Auto play video, DEV-1111
iranimij 1c9e1d8
Sort dependencies, DEV-1111
iranimij f66bb13
Improve code, DEV-1111
iranimij 895652e
Improve code, DEV-1111
iranimij a4e1fd0
Add plugin, DEV-1111
iranimij 0569c76
Add observer, DEV-1111
iranimij 0ddc4dc
Add observer, DEV-1111
iranimij 3660533
Improve code, DEV-1111
iranimij ed91125
Sort dependencies, DEV-1111
sprankhub 5ac6c42
Improve translations, DEV-1111
sprankhub 019e9a4
Refactor, DEV-1111
sprankhub d5334ce
Fix PHP 7.4 support, DEV-1111
sprankhub 27e60ef
Fix JS error, DEV-1111
sprankhub 2084320
Fix the translation, DEV-1111
iranimij 710cc91
Improve code, DEV-1111
iranimij 0c1911e
Refactor, DEV-1111
sprankhub 944dc13
Fix translations, DEV-1111
sprankhub 655241d
Remove page builder dep, DEV-1111
sprankhub bb26bfa
Update translations, DEV-1111
sprankhub d7872fe
Update dependencies, DEV-1111
sprankhub 8adbb4a
Update dependencies, DEV-1111
sprankhub File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace CustomGento\Cookiebot\Model; | ||
|
|
||
| class ExternalVideoReplacer | ||
| { | ||
| public function replaceIframeSources(string $content): string | ||
| { | ||
| $iframePatterns = [ | ||
| // YouTube patterns | ||
| '/<iframe([^>]*)\s+src=["\'](https?:\/\/(?:www\.)?(?:youtube\.com|youtube-nocookie\.com)\/embed\/[^"\']+)["\']([^>]*)>/i', | ||
| '/<iframe([^>]*)\s+src=["\'](https?:\/\/(?:www\.)?youtu\.be\/[^"\']+)["\']([^>]*)>/i', | ||
| // Vimeo patterns | ||
| '/<iframe([^>]*)\s+src=["\'](https?:\/\/(?:www\.)?vimeo\.com\/[^"\']+)["\']([^>]*)>/i', | ||
| '/<iframe([^>]*)\s+src=["\'](https?:\/\/(?:www\.)?player\.vimeo\.com\/[^"\']+)["\']([^>]*)>/i' | ||
| ]; | ||
|
|
||
| foreach ($iframePatterns as $pattern) { | ||
| $content = preg_replace_callback($pattern, function (array $matches) { | ||
| $beforeSrc = $matches[1]; | ||
| $iframeUrl = $matches[2]; | ||
| $afterSrc = $matches[3]; | ||
|
|
||
| // Check if data-cookieconsent already exists | ||
| if (preg_match('/data-cookieconsent=["\'][^"\']*["\']/', $beforeSrc . $afterSrc)) { | ||
| // If data-cookieconsent already exists, just change src to data-cookieblock-src | ||
| return '<iframe' . $beforeSrc . ' data-cookieblock-src="' . $iframeUrl . '"' . $afterSrc . '>'; | ||
| } | ||
|
|
||
| return '<iframe' . $beforeSrc . ' data-cookieblock-src="' . $iframeUrl | ||
| . '" data-cookieconsent="marketing"' . $afterSrc . '>'; | ||
| }, $content); | ||
| } | ||
|
|
||
| return $content; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace CustomGento\Cookiebot\Observer; | ||
|
|
||
| use CustomGento\Cookiebot\Model\Config; | ||
| use CustomGento\Cookiebot\Model\ExternalVideoReplacer; | ||
| use Exception; | ||
| use Magento\Framework\App\Response\Http; | ||
| use Magento\Framework\Event\Observer; | ||
| use Magento\Framework\Event\ObserverInterface; | ||
| use Psr\Log\LoggerInterface; | ||
|
|
||
| class HtmlContentFilterObserver implements ObserverInterface | ||
| { | ||
| private LoggerInterface $logger; | ||
|
|
||
| private Config $config; | ||
|
|
||
| private ExternalVideoReplacer $externalVideoReplacer; | ||
|
|
||
| public function __construct( | ||
| LoggerInterface $logger, | ||
| Config $config, | ||
| ExternalVideoReplacer $externalVideoReplacer | ||
| ) { | ||
| $this->externalVideoReplacer = $externalVideoReplacer; | ||
| $this->config = $config; | ||
| $this->logger = $logger; | ||
| } | ||
|
|
||
| public function execute(Observer $observer): void | ||
| { | ||
| if (!$this->config->isBlockVideosUntilConsentEnabled()) { | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| $response = $observer->getData('response'); | ||
|
|
||
| if (!$response instanceof Http) { | ||
| return; | ||
| } | ||
|
|
||
| $content = $response->getBody(); | ||
|
|
||
| if (empty($content) || !is_string($content)) { | ||
| return; | ||
| } | ||
|
|
||
| $modifiedContent = $this->externalVideoReplacer->replaceIframeSources($content); | ||
|
|
||
| $response->setBody($modifiedContent); | ||
| } catch (Exception $e) { | ||
| $this->logger->error('Error in HtmlContentFilterObserver: ' . $e->getMessage()); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,37 @@ | ||
| # CustomGento_Cookiebot | ||
| 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. | ||
|
|
||
| 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. | ||
|
|
||
| ## Installation | ||
|
|
||
| - `composer require customgento/module-cookiebot-m2` | ||
| - `bin/magento module:enable CustomGento_Cookiebot` | ||
| - `bin/magento setup:upgrade` | ||
| - `bin/magento setup:di:compile` | ||
| - `bin/magento cache:flush` | ||
|
|
||
| ## Configuration | ||
| 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. | ||
|
|
||
| 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/). | ||
| 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. | ||
|
|
||
| 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/). | ||
|
|
||
| ## Manual vs. Automatic Cookie Blocking | ||
| 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 :-( | ||
|
|
||
| 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 :-( | ||
|
|
||
| ## Disclaimer | ||
|
|
||
| You need a [Cookiebot](https://www.cookiebot.com/) account, so that this extension does anything useful for you. | ||
|
|
||
| ## Copyright | ||
|
|
||
| © 2020 - present CustomGento GmbH |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace CustomGento\Cookiebot\Test\Unit\Model; | ||
|
|
||
| use CustomGento\Cookiebot\Model\ExternalVideoReplacer; | ||
| use PHPUnit\Framework\TestCase; | ||
|
|
||
| class ExternalVideoReplacerTest extends TestCase | ||
| { | ||
| /** | ||
| * @var ExternalVideoReplacer | ||
| */ | ||
| private $externalVideoReplacer; | ||
|
|
||
| protected function setUp(): void | ||
| { | ||
| $this->externalVideoReplacer = new ExternalVideoReplacer(); | ||
| } | ||
|
|
||
| /** | ||
| * @dataProvider iframeDataProvider | ||
| */ | ||
| public function testReplaceIframeSources(string $input, string $expected): void | ||
| { | ||
| $result = $this->externalVideoReplacer->replaceIframeSources($input); | ||
| $this->assertEquals($expected, $result); | ||
| } | ||
|
|
||
| public function iframeDataProvider(): array | ||
| { | ||
| return [ | ||
| // YouTube test cases | ||
| 'youtube.com embed' => [ | ||
| '<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" width="560" height="315"></iframe>', | ||
| '<iframe data-cookieblock-src="https://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>' | ||
| ], | ||
| 'youtube-nocookie.com embed' => [ | ||
| '<iframe src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" width="560" height="315"></iframe>', | ||
| '<iframe data-cookieblock-src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>' | ||
| ], | ||
| 'youtu.be URL' => [ | ||
| '<iframe src="https://youtu.be/dQw4w9WgXcQ" width="560" height="315"></iframe>', | ||
| '<iframe data-cookieblock-src="https://youtu.be/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>' | ||
| ], | ||
| 'youtube.com without www' => [ | ||
| '<iframe src="https://youtube.com/embed/dQw4w9WgXcQ" width="560" height="315"></iframe>', | ||
| '<iframe data-cookieblock-src="https://youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>' | ||
| ], | ||
| 'youtube-nocookie.com without www' => [ | ||
| '<iframe src="https://youtube-nocookie.com/embed/dQw4w9WgXcQ" width="560" height="315"></iframe>', | ||
| '<iframe data-cookieblock-src="https://youtube-nocookie.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>' | ||
| ], | ||
| 'youtu.be without www' => [ | ||
| '<iframe src="https://youtu.be/dQw4w9WgXcQ" width="560" height="315"></iframe>', | ||
| '<iframe data-cookieblock-src="https://youtu.be/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>' | ||
| ], | ||
| 'http instead of https for youtube' => [ | ||
| '<iframe src="http://www.youtube.com/embed/dQw4w9WgXcQ" width="560" height="315"></iframe>', | ||
| '<iframe data-cookieblock-src="http://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>' | ||
| ], | ||
|
|
||
| // Vimeo test cases | ||
| 'vimeo.com embed' => [ | ||
| '<iframe src="https://vimeo.com/123456789" width="640" height="360"></iframe>', | ||
| '<iframe data-cookieblock-src="https://vimeo.com/123456789" data-cookieconsent="marketing" width="640" height="360"></iframe>' | ||
| ], | ||
| 'player.vimeo.com embed' => [ | ||
| '<iframe src="https://player.vimeo.com/video/123456789" width="640" height="360"></iframe>', | ||
| '<iframe data-cookieblock-src="https://player.vimeo.com/video/123456789" data-cookieconsent="marketing" width="640" height="360"></iframe>' | ||
| ], | ||
| 'www.vimeo.com embed' => [ | ||
| '<iframe src="https://www.vimeo.com/123456789" width="640" height="360"></iframe>', | ||
| '<iframe data-cookieblock-src="https://www.vimeo.com/123456789" data-cookieconsent="marketing" width="640" height="360"></iframe>' | ||
| ], | ||
| 'www.player.vimeo.com embed' => [ | ||
| '<iframe src="https://www.player.vimeo.com/video/123456789" width="640" height="360"></iframe>', | ||
| '<iframe data-cookieblock-src="https://www.player.vimeo.com/video/123456789" data-cookieconsent="marketing" width="640" height="360"></iframe>' | ||
| ], | ||
| 'http vimeo' => [ | ||
| '<iframe src="http://vimeo.com/123456789" width="640" height="360"></iframe>', | ||
| '<iframe data-cookieblock-src="http://vimeo.com/123456789" data-cookieconsent="marketing" width="640" height="360"></iframe>' | ||
| ], | ||
| // General test cases | ||
| 'single quotes' => [ | ||
| '<iframe src=\'https://www.youtube.com/embed/dQw4w9WgXcQ\' width="560" height="315"></iframe>', | ||
| '<iframe data-cookieblock-src="https://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" width="560" height="315"></iframe>' | ||
| ], | ||
| 'multiple iframes different services' => [ | ||
| '<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>', | ||
| '<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>' | ||
| ], | ||
| 'mixed content' => [ | ||
| '<p>Some text</p><iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ"></iframe><p>More text</p><iframe src="https://vimeo.com/123456789"></iframe>', | ||
| '<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>' | ||
| ], | ||
| 'iframe with frameborder and allowfullscreen' => [ | ||
| '<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" frameborder="0" allowfullscreen=""></iframe>', | ||
| '<iframe data-cookieblock-src="https://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing" frameborder="0" allowfullscreen=""></iframe>' | ||
| ], | ||
| 'iframe with existing data-cookieconsent' => [ | ||
| '<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing"></iframe>', | ||
| '<iframe data-cookieblock-src="https://www.youtube.com/embed/dQw4w9WgXcQ" data-cookieconsent="marketing"></iframe>' | ||
| ], | ||
| 'iframe with different data-cookieconsent value' => [ | ||
| '<iframe src="https://vimeo.com/123456789" data-cookieconsent="statistics"></iframe>', | ||
| '<iframe data-cookieblock-src="https://vimeo.com/123456789" data-cookieconsent="statistics"></iframe>' | ||
| ], | ||
| 'no matching iframe' => [ | ||
| '<iframe src="https://example.com/video"></iframe>', | ||
| '<iframe src="https://example.com/video"></iframe>' | ||
| ], | ||
| 'no iframe at all' => [ | ||
| '<p>Just some text content</p>', | ||
| '<p>Just some text content</p>' | ||
| ], | ||
| 'empty content' => [ | ||
| '', | ||
| '' | ||
| ] | ||
| ]; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <?xml version="1.0"?> | ||
| <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> | ||
| <event name="controller_front_send_response_before"> | ||
| <observer name="customgento_cookiebot_html_content_filter" instance="CustomGento\Cookiebot\Observer\HtmlContentFilterObserver"/> | ||
| </event> | ||
| </config> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,9 @@ | ||
| "Please <a href=""javascript:Cookiebot.renew()"" class=""cookiebot-iframe-consent-link"">accept %1 cookies</a> to view this %2 content.","Bitte <a href=""javascript:Cookiebot.renew()"" class=""cookiebot-iframe-consent-link"">akzeptieren Sie %1 Cookies</a>, um diesen %2 Inhalt anzuzeigen." | ||
| "Please <a href=""javascript:Cookiebot.renew()"">accept marketing cookies</a> to view this content.","Bitte <a href=""javascript:Cookiebot.renew()"">akzeptieren Sie Marketing-Cookies</a>, um diesen Inhalt anzuzeigen." | ||
| "Cookiebot Settings","Cookiebot Einstellungen" | ||
| "Enable Cookiebot Integration","Cookiebot-Integration aktivieren" | ||
| "Cookiebot ID","Cookiebot ID" | ||
| "Data Culture","Data Culture" | ||
| "Use European CDN","Europäisches CDN verwenden" | ||
| "Google Consent Mode","Google Consent Mode" | ||
| "Block YouTube / Vimeo Videos Until Marketing Consent","Blockiere YouTube / Vimeo Videos bis zur Marketingeinwilligung" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,9 @@ | ||
| "Please <a href=""javascript:Cookiebot.renew()"" class=""cookiebot-iframe-consent-link"">accept %1 cookies</a> to view this %2 content.","Please <a href=""javascript:Cookiebot.renew()"" class=""cookiebot-iframe-consent-link"">accept %1 cookies</a> to view this %2 content." | ||
| "Please <a href=""javascript:Cookiebot.renew()"">accept marketing cookies</a> to view this content.","Please <a href=""javascript:Cookiebot.renew()"">accept marketing cookies</a> to view this content." | ||
| "Cookiebot Settings","Cookiebot Settings" | ||
| "Enable Cookiebot Integration","Enable Cookiebot Integration" | ||
| "Cookiebot ID","Cookiebot ID" | ||
| "Data Culture","Data Culture" | ||
| "Use European CDN","Use European CDN" | ||
| "Google Consent Mode","Google Consent Mode" | ||
| "Block YouTube / Vimeo Videos Until Marketing Consent","Block YouTube / Vimeo Videos Until Marketing Consent" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.