Skip to content

Commit e3ad6f2

Browse files
authored
Add support for custom ide url (#956)
1 parent 12478ca commit e3ad6f2

File tree

14 files changed

+199
-26
lines changed

14 files changed

+199
-26
lines changed

assets/styles/app.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ ul {
3939
display: none;
4040
}
4141

42+
.form-text-visible {
43+
display: block;
44+
}
45+
4246
.dr-btn-orange {
4347
--bs-btn-color: #e24329;
4448
--bs-btn-border-color: #e24329;

config/services.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use DR\Review\Entity\User\User;
1414
use DR\Review\EventSubscriber\ContentSecurityPolicyResponseSubscriber;
1515
use DR\Review\ExternalTool\Gitlab\GitlabService;
16+
use DR\Review\Form\User\UserSettingType;
1617
use DR\Review\MessageHandler\Mail\CommentAddedMailNotificationHandler;
1718
use DR\Review\MessageHandler\Mail\CommentReplyAddedMailNotificationHandler;
1819
use DR\Review\MessageHandler\Mail\CommentReplyUpdatedMailNotificationHandler;
@@ -62,6 +63,7 @@
6263
use DR\Review\Service\Report\Coverage\CodeCoverageParserProvider;
6364
use DR\Review\Service\Report\Coverage\Parser\CloverParser;
6465
use DR\Review\Service\Revision\RevisionPatternMatcher;
66+
use DR\Review\Service\User\IdeUrlPatternProvider;
6567
use DR\Review\Service\Webhook\WebhookExecutionService;
6668
use DR\Review\Twig\IdeButtonExtension;
6769
use DR\Review\Twig\InlineCss\CssToInlineStyles;
@@ -128,8 +130,7 @@
128130
$services->set(User::class)->public()->factory([service(Security::class), 'getUser']);
129131
$services->set(ContentSecurityPolicyResponseSubscriber::class)
130132
->arg('$hostname', '%env(APP_HOSTNAME)%')
131-
->arg('$ideUrlEnabled', '%env(bool:IDE_URL_ENABLED)%')
132-
->arg('$ideUrlPattern', '%env(IDE_URL_PATTERN)%');
133+
->arg('$ideUrlEnabled', '%env(bool:IDE_URL_ENABLED)%');
133134
$services->set(ProblemJsonResponseFactory::class)->arg('$debug', '%env(APP_DEBUG)%');
134135

135136
// Configure Api
@@ -161,12 +162,14 @@
161162
$services->set(DiffFileParser::class);
162163
$services->set(JBDiff::class);
163164
$services->set(CssToInlineStyles::class);
164-
$services->set(IdeButtonExtension::class)->args(['%env(bool:IDE_URL_ENABLED)%', '%env(IDE_URL_PATTERN)%', '%env(IDE_URL_TITLE)%']);
165+
$services->set(IdeButtonExtension::class)->args(['%env(bool:IDE_URL_ENABLED)%', '%env(IDE_URL_TITLE)%']);
165166
$services->set(Highlighter::class);
166167
$services->set(MarkdownConverter::class, CommonMarkdownConverter::class);
167168
$services->set(GitCommandBuilderFactory::class)->arg('$git', '%env(GIT_BINARY)%');
168169
$services->set(ParserHasFailedFormatter::class);
169170
$services->set(RuleNotificationTokenGenerator::class)->arg('$appSecret', '%env(APP_SECRET)%');
171+
$services->set(UserSettingType::class)->arg('$ideUrlPattern', '%env(IDE_URL_PATTERN)%');
172+
$services->set(IdeUrlPatternProvider::class)->arg('$ideUrlPattern', '%env(IDE_URL_PATTERN)%');
170173

171174
// custom register cache dir
172175
$services->set(CacheableGitRepositoryService::class)->arg('$cacheDirectory', "%kernel.project_dir%/var/git/");
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20241222181305 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return '';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
$this->addSql('ALTER TABLE user_setting ADD ide_url VARCHAR(500) DEFAULT NULL');
23+
}
24+
25+
public function down(Schema $schema): void
26+
{
27+
$this->addSql('ALTER TABLE user_setting DROP ide_url');
28+
}
29+
}

src/Entity/User/UserSetting.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class UserSetting
3030
#[ORM\Column(length: 10000, options: ['default' => ''])]
3131
private string $browserNotificationEvents = '';
3232

33+
#[ORM\Column(length: 500, nullable: true)]
34+
private ?string $ideUrl = null;
35+
3336
#[ORM\OneToOne(targetEntity: User::class, inversedBy: 'setting')]
3437
private ?User $user = null;
3538

@@ -127,4 +130,16 @@ public function setBrowserNotificationEvents(array $browserNotificationEvents):
127130

128131
return $this;
129132
}
133+
134+
public function getIdeUrl(): ?string
135+
{
136+
return $this->ideUrl;
137+
}
138+
139+
public function setIdeUrl(?string $ideUrl): UserSetting
140+
{
141+
$this->ideUrl = $ideUrl;
142+
143+
return $this;
144+
}
130145
}

src/EventSubscriber/ContentSecurityPolicyResponseSubscriber.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33

44
namespace DR\Review\EventSubscriber;
55

6+
use DR\Review\Service\User\IdeUrlPatternProvider;
67
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
78
use Symfony\Component\HttpKernel\Event\ResponseEvent;
89
use Symfony\Component\HttpKernel\KernelEvents;
910

1011
class ContentSecurityPolicyResponseSubscriber implements EventSubscriberInterface
1112
{
12-
public function __construct(private readonly string $hostname, private readonly bool $ideUrlEnabled, private readonly string $ideUrlPattern)
13-
{
13+
public function __construct(
14+
private readonly string $hostname,
15+
private readonly bool $ideUrlEnabled,
16+
private readonly IdeUrlPatternProvider $ideUrlPatternProvider
17+
) {
1418
}
1519

1620
public function onResponse(ResponseEvent $event): void
@@ -32,7 +36,7 @@ public function onResponse(ResponseEvent $event): void
3236
];
3337

3438
// if IDE url is allowed, allow iframe host from http or https url
35-
if ($this->ideUrlEnabled && preg_match('#^(https?://[^:/]+)#', $this->ideUrlPattern, $matches) === 1) {
39+
if ($this->ideUrlEnabled && preg_match('#^(https?://[^:/]+)#', $this->ideUrlPatternProvider->getUrl(), $matches) === 1) {
3640
$policy[] = sprintf("frame-src %s:*", $matches[1]);
3741
}
3842

src/Form/User/UserSettingType.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,16 @@
1717
use Symfony\Component\Form\AbstractType;
1818
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
1919
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
20+
use Symfony\Component\Form\Extension\Core\Type\TextType;
2021
use Symfony\Component\Form\FormBuilderInterface;
2122
use Symfony\Component\OptionsResolver\OptionsResolver;
2223

2324
class UserSettingType extends AbstractType
2425
{
26+
public function __construct(private readonly string $ideUrlPattern)
27+
{
28+
}
29+
2530
/**
2631
* @inheritDoc
2732
*/
@@ -65,6 +70,18 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
6570
'multiple' => true,
6671
]
6772
);
73+
$builder->add(
74+
'ideUrl',
75+
TextType::class,
76+
[
77+
'required' => false,
78+
'label' => 'form.label.ide.url',
79+
'help' => 'form.help.ide.url',
80+
'help_html' => true,
81+
'help_attr' => ['class' => 'form-text-visible'],
82+
'attr' => ['placeholder' => $this->ideUrlPattern]
83+
]
84+
);
6885
}
6986

7087
public function configureOptions(OptionsResolver $resolver): void
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DR\Review\Service\User;
5+
6+
use DR\Review\Entity\User\User;
7+
use Symfony\Bundle\SecurityBundle\Security;
8+
9+
class IdeUrlPatternProvider
10+
{
11+
public function __construct(private readonly string $ideUrlPattern, private readonly Security $security)
12+
{
13+
}
14+
15+
public function getUrl(): string
16+
{
17+
$user = $this->security->getUser();
18+
if ($user instanceof User && $user->getSetting()->getIdeUrl() !== null) {
19+
return $user->getSetting()->getIdeUrl();
20+
}
21+
22+
return $this->ideUrlPattern;
23+
}
24+
}

src/Twig/IdeButtonExtension.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace DR\Review\Twig;
55

6+
use DR\Review\Service\User\IdeUrlPatternProvider;
67
use Twig\Environment;
78
use Twig\Error\LoaderError;
89
use Twig\Error\RuntimeError;
@@ -14,8 +15,8 @@ class IdeButtonExtension extends AbstractExtension
1415
{
1516
public function __construct(
1617
private readonly bool $enabled,
17-
private readonly string $url,
1818
private readonly string $title,
19+
private readonly IdeUrlPatternProvider $ideUrlPatternProvider,
1920
private readonly Environment $twig,
2021
) {
2122
}
@@ -43,7 +44,7 @@ public function createLink(string $file, int $line = 1): string
4344
return $this->twig->render(
4445
'/extension/ide-button.widget.html.twig',
4546
[
46-
'url' => str_ireplace($search, $replace, $this->url),
47+
'url' => str_ireplace($search, $replace, $this->ideUrlPatternProvider->getUrl()),
4748
'title' => $this->title
4849
]
4950
);

templates/app/user/user.setting.html.twig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,15 @@
3838
</small>
3939
</div>
4040

41+
<h3 class="mb-4 mt-4">{{ 'form.header.ide.url'|trans }}</h3>
42+
{{ form_widget(settingViewModel.settingForm.setting.ideUrl) }}
43+
{{ form_help(settingViewModel.settingForm.setting.ideUrl) }}
44+
4145
<h3 class="mb-4 mt-4">{{ 'mail.settings'|trans }}</h3>
4246

4347
{{ form_rest(settingViewModel.settingForm) }}
4448
{{ form_end(settingViewModel.settingForm) }}
4549

50+
51+
4652
{% endblock %}

tests/Unit/EventSubscriber/ContentSecurityPolicyResponseSubscriberTest.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
namespace DR\Review\Tests\Unit\EventSubscriber;
55

66
use DR\Review\EventSubscriber\ContentSecurityPolicyResponseSubscriber;
7+
use DR\Review\Service\User\IdeUrlPatternProvider;
78
use DR\Review\Tests\AbstractTestCase;
89
use PHPUnit\Framework\Attributes\CoversClass;
10+
use PHPUnit\Framework\MockObject\MockObject;
911
use Symfony\Component\HttpFoundation\Request;
1012
use Symfony\Component\HttpFoundation\Response;
1113
use Symfony\Component\HttpKernel\Event\ResponseEvent;
@@ -15,6 +17,14 @@
1517
#[CoversClass(ContentSecurityPolicyResponseSubscriber::class)]
1618
class ContentSecurityPolicyResponseSubscriberTest extends AbstractTestCase
1719
{
20+
private IdeUrlPatternProvider&MockObject $ideUrlPatternProvider;
21+
22+
protected function setUp(): void
23+
{
24+
parent::setUp();
25+
$this->ideUrlPatternProvider = $this->createMock(IdeUrlPatternProvider::class);
26+
}
27+
1828
public function testGetSubscribedEvents(): void
1929
{
2030
static::assertSame([KernelEvents::RESPONSE => 'onResponse'], ContentSecurityPolicyResponseSubscriber::getSubscribedEvents());
@@ -25,7 +35,9 @@ public function testOnResponseShouldNotOverrideExisting(): void
2535
$response = new Response();
2636
$response->headers->set('Content-Security-Policy', '');
2737
$event = new ResponseEvent($this->createMock(HttpKernelInterface::class), new Request(), 1, $response);
28-
$subscriber = new ContentSecurityPolicyResponseSubscriber('host', true, 'url');
38+
$subscriber = new ContentSecurityPolicyResponseSubscriber('host', true, $this->ideUrlPatternProvider);
39+
40+
$this->ideUrlPatternProvider->expects(self::never())->method('getUrl');
2941

3042
$subscriber->onResponse($event);
3143

@@ -36,7 +48,10 @@ public function testOnResponseWithIdeUrl(): void
3648
{
3749
$response = new Response();
3850
$event = new ResponseEvent($this->createMock(HttpKernelInterface::class), new Request(), 1, $response);
39-
$subscriber = new ContentSecurityPolicyResponseSubscriber('host', true, 'http://localhost:8080/file');
51+
$subscriber = new ContentSecurityPolicyResponseSubscriber('host', true, $this->ideUrlPatternProvider,);
52+
53+
$this->ideUrlPatternProvider->expects(self::once())->method('getUrl')->willReturn('http://localhost:8080/file');
54+
4055
$subscriber->onResponse($event);
4156

4257
static::assertSame(
@@ -50,7 +65,10 @@ public function testOnResponseWithoutIdeUrl(): void
5065
{
5166
$response = new Response();
5267
$event = new ResponseEvent($this->createMock(HttpKernelInterface::class), new Request(), 1, $response);
53-
$subscriber = new ContentSecurityPolicyResponseSubscriber('host', false, 'http://localhost:8080/file');
68+
$subscriber = new ContentSecurityPolicyResponseSubscriber('host', false, $this->ideUrlPatternProvider);
69+
70+
$this->ideUrlPatternProvider->expects(self::never())->method('getUrl');
71+
5472
$subscriber->onResponse($event);
5573

5674
static::assertSame(

0 commit comments

Comments
 (0)