Skip to content

Commit 2daa66e

Browse files
[BUGFIX] Restore captcha validation in CaptchaValidator
The validation refactoring in c6c35a2 removed the property $validationSettings from AbstractValidator and replaced it with ValidationSettingsService, but CaptchaValidator was not adjusted and kept reading $this->validationSettings['captcha']['captcha']. CaptchaValidator now resolves the setting through ValidationSettingsService::isCaptchaEnabled(), the same way ServersideValidator already reads its validation settings. This restores the per-plugin lookup for new.validation, edit.validation and invitation.validationEdit. Relates: https://projekte.in2code.de/issues/83110 Resolves: /issues/722
1 parent 68dacef commit 2daa66e

4 files changed

Lines changed: 306 additions & 3 deletions

File tree

‎Classes/Domain/Service/ValidationSettingsService.php‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55
namespace In2code\Femanager\Domain\Service;
66

77
use In2code\Femanager\Utility\ConfigurationUtility;
8+
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
89

910
class ValidationSettingsService
1011
{
12+
/**
13+
* Extension providing the captcha implementation
14+
*/
15+
protected const EXTENSION_KEY_CAPTCHA_PROVIDER = 'sr_freecap';
16+
1117
/**
1218
* Validation names with simple configuration
1319
*/
@@ -72,6 +78,17 @@ public function isServersideValidationEnabled(): bool
7278
return $validationSetting === '1';
7379
}
7480

81+
/**
82+
* A captcha can only be validated if it is configured for the current plugin
83+
* and if the extension providing it is available.
84+
*/
85+
public function isCaptchaEnabled(): bool
86+
{
87+
$isCaptchaConfigured = (bool)(ConfigurationUtility::getConfiguration()[$this->controllerName][$this->validationName]['captcha']['captcha'] ?? false);
88+
89+
return $isCaptchaConfigured && ExtensionManagementUtility::isLoaded(self::EXTENSION_KEY_CAPTCHA_PROVIDER);
90+
}
91+
7592
/**
7693
* @param string $validation
7794
* @param string $configuration

‎Classes/Domain/Validator/CaptchaValidator.php‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
namespace In2code\Femanager\Domain\Validator;
66

77
use In2code\Femanager\Domain\Repository\UserRepository;
8+
use In2code\Femanager\Domain\Service\ValidationSettingsService;
89
use Psr\EventDispatcher\EventDispatcherInterface;
9-
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
1010
use TYPO3\CMS\Core\Utility\GeneralUtility;
1111
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
1212

@@ -59,7 +59,12 @@ protected function validCaptcha(string $captcha): bool
5959
*/
6060
protected function captchaEnabled(): bool
6161
{
62-
return ExtensionManagementUtility::isLoaded('sr_freecap')
63-
&& !empty($this->validationSettings['captcha']['captcha']);
62+
$validationSettingsService = GeneralUtility::makeInstance(
63+
ValidationSettingsService::class,
64+
$this->getControllerName(),
65+
$this->getValidationName()
66+
);
67+
68+
return $validationSettingsService->isCaptchaEnabled();
6469
}
6570
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace In2code\Femanager\Tests\Unit\Domain\Service;
6+
7+
use In2code\Femanager\Domain\Service\ValidationSettingsService;
8+
use PHPUnit\Framework\Attributes\CoversClass;
9+
use PHPUnit\Framework\Attributes\DataProvider;
10+
use PHPUnit\Framework\Attributes\Test;
11+
use ReflectionProperty;
12+
use TYPO3\CMS\Core\Package\PackageManager;
13+
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
14+
use TYPO3\CMS\Core\Utility\GeneralUtility;
15+
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
16+
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
17+
18+
#[CoversClass(ValidationSettingsService::class)]
19+
class ValidationSettingsServiceTest extends UnitTestCase
20+
{
21+
protected const SR_FREECAP_EXTENSION_KEY = 'sr_freecap';
22+
23+
protected bool $resetSingletonInstances = true;
24+
25+
protected PackageManager $originalPackageManager;
26+
27+
protected function setUp(): void
28+
{
29+
parent::setUp();
30+
$this->originalPackageManager = (new ReflectionProperty(
31+
ExtensionManagementUtility::class,
32+
'packageManager'
33+
))->getValue();
34+
}
35+
36+
protected function tearDown(): void
37+
{
38+
ExtensionManagementUtility::setPackageManager($this->originalPackageManager);
39+
parent::tearDown();
40+
}
41+
42+
public static function isCaptchaEnabledDataProvider(): array
43+
{
44+
return [
45+
'enabled for the given controller and validation name' => [
46+
['new' => ['validation' => ['captcha' => ['captcha' => '1']]]],
47+
true,
48+
],
49+
'switched off' => [
50+
['new' => ['validation' => ['captcha' => ['captcha' => '0']]]],
51+
false,
52+
],
53+
'not configured' => [
54+
['new' => ['validation' => []]],
55+
false,
56+
],
57+
'configured for a different controller' => [
58+
['edit' => ['validation' => ['captcha' => ['captcha' => '1']]]],
59+
false,
60+
],
61+
];
62+
}
63+
64+
#[DataProvider('isCaptchaEnabledDataProvider')]
65+
#[Test]
66+
public function isCaptchaEnabledEvaluatesTheValidationSettings(
67+
array $typoScriptSettings,
68+
bool $expectedResult
69+
): void {
70+
$this->setSrFreecapLoaded(true);
71+
$this->stubTypoScriptSettings($typoScriptSettings);
72+
$service = new ValidationSettingsService('new', 'validation');
73+
74+
self::assertSame($expectedResult, $service->isCaptchaEnabled());
75+
}
76+
77+
#[Test]
78+
public function isCaptchaEnabledIsFalseWithoutTheCaptchaProvidingExtension(): void
79+
{
80+
$this->setSrFreecapLoaded(false);
81+
$this->stubTypoScriptSettings(['new' => ['validation' => ['captcha' => ['captcha' => '1']]]]);
82+
$service = new ValidationSettingsService('new', 'validation');
83+
84+
self::assertFalse($service->isCaptchaEnabled());
85+
}
86+
87+
/**
88+
* ConfigurationUtility::getConfiguration() reads the settings through the extbase configuration manager.
89+
*/
90+
protected function stubTypoScriptSettings(array $typoScriptSettings): void
91+
{
92+
$configurationManager = $this->createMock(ConfigurationManagerInterface::class);
93+
$configurationManager->method('getConfiguration')->willReturn($typoScriptSettings);
94+
GeneralUtility::setSingletonInstance(ConfigurationManagerInterface::class, $configurationManager);
95+
}
96+
97+
protected function setSrFreecapLoaded(bool $isLoaded): void
98+
{
99+
$packageManager = $this->createMock(PackageManager::class);
100+
$packageManager->method('isPackageActive')->willReturnCallback(
101+
static fn (string $packageKey): bool => $packageKey === self::SR_FREECAP_EXTENSION_KEY && $isLoaded
102+
);
103+
ExtensionManagementUtility::setPackageManager($packageManager);
104+
}
105+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace In2code\Femanager\Tests\Unit\Domain\Validator;
6+
7+
use In2code\Femanager\Domain\Repository\UserRepository;
8+
use In2code\Femanager\Domain\Validator\CaptchaValidator;
9+
use PHPUnit\Framework\Attributes\CoversClass;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use PHPUnit\Framework\Attributes\Test;
12+
use PHPUnit\Framework\MockObject\MockObject;
13+
use Psr\EventDispatcher\ListenerProviderInterface;
14+
use ReflectionProperty;
15+
use TYPO3\CMS\Core\EventDispatcher\EventDispatcher;
16+
use TYPO3\CMS\Core\Package\PackageManager;
17+
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
18+
use TYPO3\CMS\Core\Utility\GeneralUtility;
19+
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
20+
use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
21+
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
22+
23+
#[CoversClass(CaptchaValidator::class)]
24+
class CaptchaValidatorTest extends UnitTestCase
25+
{
26+
protected const SR_FREECAP_EXTENSION_KEY = 'sr_freecap';
27+
28+
protected bool $resetSingletonInstances = true;
29+
30+
protected PackageManager $originalPackageManager;
31+
32+
protected function setUp(): void
33+
{
34+
parent::setUp();
35+
$this->originalPackageManager = (new ReflectionProperty(
36+
ExtensionManagementUtility::class,
37+
'packageManager'
38+
))->getValue();
39+
}
40+
41+
protected function tearDown(): void
42+
{
43+
ExtensionManagementUtility::setPackageManager($this->originalPackageManager);
44+
parent::tearDown();
45+
}
46+
47+
/**
48+
* Every case must be recognized as "captcha is required", otherwise the captcha is silently skipped.
49+
*/
50+
public static function captchaIsEnabledDataProvider(): array
51+
{
52+
return [
53+
'registration plugin reads new.validation' => [
54+
'tx_femanager_registration',
55+
'',
56+
['new' => ['validation' => ['captcha' => ['captcha' => '1']]]],
57+
],
58+
'edit plugin reads edit.validation' => [
59+
'tx_femanager_edit',
60+
'',
61+
['edit' => ['validation' => ['captcha' => ['captcha' => '1']]]],
62+
],
63+
'invitation plugin reads invitation.validation' => [
64+
'tx_femanager_invitation',
65+
'',
66+
['invitation' => ['validation' => ['captcha' => ['captcha' => '1']]]],
67+
],
68+
'invitation plugin reads invitation.validationEdit on the edit referrer' => [
69+
'tx_femanager_invitation',
70+
'edit',
71+
['invitation' => ['validationEdit' => ['captcha' => ['captcha' => '1']]]],
72+
],
73+
];
74+
}
75+
76+
#[DataProvider('captchaIsEnabledDataProvider')]
77+
#[Test]
78+
public function captchaIsEnabledWhenConfiguredForThePlugin(
79+
string $pluginNamespace,
80+
string $referrerActionName,
81+
array $typoScriptSettings
82+
): void {
83+
$this->setSrFreecapLoaded(true);
84+
$validator = $this->getValidator($pluginNamespace, $referrerActionName, $typoScriptSettings);
85+
86+
self::assertTrue($validator->_call('captchaEnabled'));
87+
}
88+
89+
public static function captchaIsDisabledDataProvider(): array
90+
{
91+
return [
92+
'no validation settings at all' => [
93+
'tx_femanager_registration',
94+
[],
95+
],
96+
'captcha explicitly switched off' => [
97+
'tx_femanager_registration',
98+
['new' => ['validation' => ['captcha' => ['captcha' => '0']]]],
99+
],
100+
'captcha configured for another controller' => [
101+
'tx_femanager_edit',
102+
['new' => ['validation' => ['captcha' => ['captcha' => '1']]]],
103+
],
104+
'captcha configured for another validation name' => [
105+
'tx_femanager_registration',
106+
['new' => ['validationEdit' => ['captcha' => ['captcha' => '1']]]],
107+
],
108+
];
109+
}
110+
111+
#[DataProvider('captchaIsDisabledDataProvider')]
112+
#[Test]
113+
public function captchaIsDisabledWhenNotConfiguredForThePlugin(
114+
string $pluginNamespace,
115+
array $typoScriptSettings
116+
): void {
117+
$this->setSrFreecapLoaded(true);
118+
$validator = $this->getValidator($pluginNamespace, '', $typoScriptSettings);
119+
120+
self::assertFalse($validator->_call('captchaEnabled'));
121+
}
122+
123+
#[Test]
124+
public function captchaIsDisabledWhenSrFreecapIsNotLoaded(): void
125+
{
126+
$this->setSrFreecapLoaded(false);
127+
$validator = $this->getValidator(
128+
'tx_femanager_registration',
129+
'',
130+
['new' => ['validation' => ['captcha' => ['captcha' => '1']]]]
131+
);
132+
133+
self::assertFalse($validator->_call('captchaEnabled'));
134+
}
135+
136+
protected function getValidator(
137+
string $pluginNamespace,
138+
string $referrerActionName,
139+
array $typoScriptSettings
140+
): CaptchaValidator&AccessibleObjectInterface&MockObject {
141+
$this->stubTypoScriptSettings($typoScriptSettings);
142+
143+
$validator = $this->getAccessibleMock(
144+
CaptchaValidator::class,
145+
null,
146+
[
147+
new UserRepository(),
148+
$this->createMock(ConfigurationManagerInterface::class),
149+
new EventDispatcher($this->createMock(ListenerProviderInterface::class)),
150+
]
151+
);
152+
$validator->_set('pluginNamespace', $pluginNamespace);
153+
$validator->_set('referrerActionName', $referrerActionName);
154+
155+
return $validator;
156+
}
157+
158+
/**
159+
* ConfigurationUtility::getConfiguration() reads the settings through the extbase configuration manager.
160+
*/
161+
protected function stubTypoScriptSettings(array $typoScriptSettings): void
162+
{
163+
$configurationManager = $this->createMock(ConfigurationManagerInterface::class);
164+
$configurationManager->method('getConfiguration')->willReturn($typoScriptSettings);
165+
GeneralUtility::setSingletonInstance(ConfigurationManagerInterface::class, $configurationManager);
166+
}
167+
168+
protected function setSrFreecapLoaded(bool $isLoaded): void
169+
{
170+
$packageManager = $this->createMock(PackageManager::class);
171+
$packageManager->method('isPackageActive')->willReturnCallback(
172+
static fn (string $packageKey): bool => $packageKey === self::SR_FREECAP_EXTENSION_KEY && $isLoaded
173+
);
174+
ExtensionManagementUtility::setPackageManager($packageManager);
175+
}
176+
}

0 commit comments

Comments
 (0)