Skip to content

Commit e80a3f3

Browse files
author
Konrad Michalik
authored
Merge pull request #13 from move-elevator/extension-configuration
feat: implement detector configuration builder and update notification logic
2 parents 4ddb9dd + d33a9bc commit e80a3f3

13 files changed

Lines changed: 448 additions & 569 deletions

File tree

.ddev/.setup/scripts/utils.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ function _progress() {
3030
_spinner &
3131
SPINNER_PID=$!
3232
# Save current stdout/stderr
33-
# exec 3>&1 4>&2
34-
# exec >/dev/null 2>&1
33+
exec 3>&1 4>&2
34+
exec >/dev/null 2>&1
3535
else
3636
printf "\n"
3737
fi
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS extension "typo3_login_warning".
7+
*
8+
* Copyright (C) 2025 Konrad Michalik <km@move-elevator.de>
9+
*
10+
* This program is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU General Public License as published by
12+
* the Free Software Foundation, either version 2 of the License, or
13+
* (at your option) any later version.
14+
*
15+
* This program is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU General Public License
21+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
24+
namespace MoveElevator\Typo3LoginWarning\Configuration;
25+
26+
use MoveElevator\Typo3LoginWarning\Configuration;
27+
use MoveElevator\Typo3LoginWarning\Detector\LongTimeNoSeeDetector;
28+
use MoveElevator\Typo3LoginWarning\Detector\NewIpDetector;
29+
use MoveElevator\Typo3LoginWarning\Detector\OutOfOfficeDetector;
30+
use Psr\Log\LoggerAwareInterface;
31+
use Psr\Log\LoggerAwareTrait;
32+
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
33+
use TYPO3\CMS\Core\Utility\GeneralUtility;
34+
35+
/**
36+
* DetectorConfigurationBuilder.
37+
*
38+
* @author Konrad Michalik <hej@konradmichalik.dev>
39+
* @license GPL-2.0
40+
*/
41+
final class DetectorConfigurationBuilder implements LoggerAwareInterface
42+
{
43+
use LoggerAwareTrait;
44+
45+
/**
46+
* @var array<string, mixed>
47+
*/
48+
private array $extensionConfiguration = [];
49+
50+
/**
51+
* @return array<string, mixed>
52+
*/
53+
public function getExtensionConfiguration(): array
54+
{
55+
if ($this->extensionConfiguration !== []) {
56+
return $this->extensionConfiguration;
57+
}
58+
59+
try {
60+
$this->extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class)
61+
->get(Configuration::EXT_KEY);
62+
} catch (\Exception $e) {
63+
$this->logger?->warning('Could not load extension configuration: {message}', [
64+
'message' => $e->getMessage(),
65+
]);
66+
$this->extensionConfiguration = [];
67+
}
68+
69+
return $this->extensionConfiguration;
70+
}
71+
public function isActive(string $detectorClass): bool
72+
{
73+
$extensionConfiguration = $this->getExtensionConfiguration();
74+
$prefix = $this->getConfigPrefix($detectorClass);
75+
return (bool)($extensionConfiguration[$prefix]['active'] ?? false);
76+
}
77+
78+
/**
79+
* @return array<string, mixed>
80+
*/
81+
public function build(string $detectorClass): array
82+
{
83+
$extensionConfiguration = $this->getExtensionConfiguration();
84+
$prefix = $this->getConfigPrefix($detectorClass);
85+
$config = $this->extractConfigForPrefix($prefix, $extensionConfiguration);
86+
87+
return match ($detectorClass) {
88+
NewIpDetector::class => $this->buildNewIpConfig($config),
89+
LongTimeNoSeeDetector::class => $this->buildLongTimeNoSeeConfig($config),
90+
OutOfOfficeDetector::class => $this->buildOutOfOfficeConfig($config),
91+
default => $config,
92+
};
93+
}
94+
95+
/**
96+
* @return array<string, mixed>
97+
*/
98+
public function buildNotificationConfig(): array
99+
{
100+
$extensionConfiguration = $this->getExtensionConfiguration();
101+
return [
102+
'recipient' => $extensionConfiguration['notificationRecipients'] ?? $GLOBALS['TYPO3_CONF_VARS']['BE']['warning_email_addr'] ?? '',
103+
'notifyUser' => (bool)($extensionConfiguration['notifyUser'] ?? false),
104+
];
105+
}
106+
107+
/**
108+
* @param array<string, mixed> $extensionConfiguration
109+
* @return array<string, mixed>
110+
*/
111+
private function extractConfigForPrefix(string $prefix, array $extensionConfiguration): array
112+
{
113+
if (!isset($extensionConfiguration[$prefix]) || !is_array($extensionConfiguration[$prefix])) {
114+
return [];
115+
}
116+
117+
$config = $extensionConfiguration[$prefix];
118+
119+
// Remove 'active' key as it's handled separately
120+
unset($config['active']);
121+
122+
return $config;
123+
}
124+
125+
/**
126+
* @param array<string, mixed> $config
127+
* @return array<string, mixed>
128+
*/
129+
private function buildNewIpConfig(array $config): array
130+
{
131+
return [
132+
'hashIpAddress' => (bool)($config['hashIpAddress'] ?? true),
133+
'fetchGeolocation' => (bool)($config['fetchGeolocation'] ?? true),
134+
'onlyAdmins' => (bool)($config['onlyAdmins'] ?? false),
135+
'onlySystemMaintainers' => (bool)($config['onlySystemMaintainers'] ?? false),
136+
'whitelist' => $this->parseCommaSeparatedList($config['whitelist'] ?? '127.0.0.1'),
137+
];
138+
}
139+
140+
/**
141+
* @param array<string, mixed> $config
142+
* @return array<string, mixed>
143+
*/
144+
private function buildLongTimeNoSeeConfig(array $config): array
145+
{
146+
return [
147+
'thresholdDays' => (int)($config['thresholdDays'] ?? 365),
148+
'onlyAdmins' => (bool)($config['onlyAdmins'] ?? false),
149+
'onlySystemMaintainers' => (bool)($config['onlySystemMaintainers'] ?? false),
150+
];
151+
}
152+
153+
/**
154+
* @param array<string, mixed> $config
155+
* @return array<string, mixed>
156+
*/
157+
private function buildOutOfOfficeConfig(array $config): array
158+
{
159+
$result = [
160+
'timezone' => $config['timezone'] ?? $GLOBALS['TYPO3_CONF_VARS']['SYS']['phpTimeZone'] ?? 'UTC',
161+
'onlyAdmins' => (bool)($config['onlyAdmins'] ?? false),
162+
'onlySystemMaintainers' => (bool)($config['onlySystemMaintainers'] ?? false),
163+
];
164+
165+
// Parse working hours JSON
166+
if (isset($config['workingHours']) && is_string($config['workingHours']) && $config['workingHours'] !== '') {
167+
$workingHours = json_decode($config['workingHours'], true);
168+
if (is_array($workingHours)) {
169+
$result['workingHours'] = $workingHours;
170+
}
171+
}
172+
173+
// Default working hours if not set
174+
if (!isset($result['workingHours'])) {
175+
$result['workingHours'] = [
176+
'monday' => ['06:00', '19:00'],
177+
'tuesday' => ['06:00', '19:00'],
178+
'wednesday' => ['06:00', '19:00'],
179+
'thursday' => ['06:00', '19:00'],
180+
'friday' => ['06:00', '19:00'],
181+
];
182+
}
183+
184+
// Parse holidays
185+
if (isset($config['holidays']) && is_string($config['holidays']) && $config['holidays'] !== '') {
186+
$result['holidays'] = $this->parseCommaSeparatedList($config['holidays']);
187+
} else {
188+
$result['holidays'] = [];
189+
}
190+
191+
// Parse vacation periods
192+
if (isset($config['vacationPeriods']) && is_string($config['vacationPeriods']) && $config['vacationPeriods'] !== '') {
193+
$result['vacationPeriods'] = $this->parseVacationPeriods($config['vacationPeriods']);
194+
} else {
195+
$result['vacationPeriods'] = [];
196+
}
197+
198+
return $result;
199+
}
200+
201+
/**
202+
* @return array<int, string>
203+
*/
204+
private function parseCommaSeparatedList(string $value): array
205+
{
206+
if ($value === '') {
207+
return [];
208+
}
209+
210+
return array_map('trim', explode(',', $value));
211+
}
212+
213+
/**
214+
* @return array<int, array<int, string>>
215+
*/
216+
private function parseVacationPeriods(string $value): array
217+
{
218+
$vacationPeriods = [];
219+
$periods = explode(',', $value);
220+
221+
foreach ($periods as $period) {
222+
$period = trim($period);
223+
if (str_contains($period, ':')) {
224+
$vacationPeriods[] = explode(':', $period);
225+
}
226+
}
227+
228+
return $vacationPeriods;
229+
}
230+
231+
public function getConfigPrefix(string $detectorClass): string
232+
{
233+
return match ($detectorClass) {
234+
NewIpDetector::class => 'newIp',
235+
LongTimeNoSeeDetector::class => 'longTimeNoSee',
236+
OutOfOfficeDetector::class => 'outOfOffice',
237+
default => '',
238+
};
239+
}
240+
}

Classes/Configuration/LoginWarning.php

Lines changed: 0 additions & 69 deletions
This file was deleted.

Classes/Detector/LongTimeNoSeeDetector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public function detect(AbstractUserAuthentication $user, array $configuration =
7171

7272
$this->userLogRepository->updateLastLoginCheckTimestamp($userId, $currentTimestamp);
7373

74-
return $lastLoginCheckTimestamp === null || $lastLoginCheckTimestamp <= $thresholdTimestamp;
74+
return $lastLoginCheckTimestamp !== null && $lastLoginCheckTimestamp <= $thresholdTimestamp;
7575
}
7676

7777
public function getDaysSinceLastLogin(): ?int

Classes/Detector/OutOfOfficeDetector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function detect(AbstractUserAuthentication $user, array $configuration =
4949
return false;
5050
}
5151

52-
$timezone = $configuration['timezone'] ?? 'UTC';
52+
$timezone = ($configuration['timezone'] ?? '') !== '' ? $configuration['timezone'] : 'UTC';
5353
$currentTime = new \DateTime('now', new \DateTimeZone($timezone));
5454

5555
if ($this->isHoliday($currentTime, $configuration)) {

0 commit comments

Comments
 (0)