Skip to content

Commit 76ba461

Browse files
authored
Merge pull request #286 from dotkernel/develop
Develop
2 parents c99044d + 1db2bc5 commit 76ba461

16 files changed

Lines changed: 479 additions & 29 deletions

‎composer.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"php": "~8.5.0",
3030
"ext-curl": "*",
3131
"ext-dom": "*",
32+
"akrabat/ip-address-middleware": "^2.6",
3233
"doctrine/data-fixtures": "^2.2",
3334
"doctrine/doctrine-fixtures-bundle": "^4.3",
3435
"dotkernel/dot-cache": "^4.0",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
// @see https://github.com/akrabat/ip-address-middleware/blob/main/src/IpAddress.php
6+
return [
7+
'rka' => [
8+
'ip_address' => [
9+
// Inspect proxy headers below instead of using REMOTE_ADDR only.
10+
'check_proxy_headers' => true,
11+
// Proxy IPs/CIDRs/wildcards trusted to set the headers below.
12+
'trusted_proxies' => [],
13+
// Request attribute the resolved IP is stored under.
14+
'attribute_name' => 'ipAddress',
15+
// Headers checked, in order, for the client IP.
16+
'headers_to_inspect' => [
17+
// nginx specific
18+
'X-Real-IP',
19+
'X-Forwarded-For',
20+
// cloudflare specific
21+
'CF-Connecting-IP',
22+
'True-Client-IP',
23+
// generic
24+
'Forwarded',
25+
'X-Forwarded',
26+
'X-Cluster-Client-Ip',
27+
'Client-Ip',
28+
],
29+
],
30+
],
31+
];

‎config/config.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
\Mezzio\Router\ConfigProvider::class,
2424
\Mezzio\Router\FastRouteRouter\ConfigProvider::class,
2525
\Mezzio\Twig\ConfigProvider::class,
26+
\RKA\Middleware\Mezzio\ConfigProvider::class,
2627

2728
// Dotkernel packages
2829
\Dot\DependencyInjection\ConfigProvider::class,

‎src/App/src/Handler/PostCreateContactHandler.php‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,30 @@
1818
use function is_array;
1919
use function is_string;
2020

21+
/**
22+
* @phpstan-type IpAddressConfigType array{
23+
* check_proxy_headers: bool,
24+
* trusted_proxies: array<non-empty-string>,
25+
* attribute_name: non-empty-string,
26+
* headers_to_inspect: array<non-empty-string>,
27+
* }
28+
*/
2129
class PostCreateContactHandler implements RequestHandlerInterface
2230
{
31+
/**
32+
* @param IpAddressConfigType $ipAddressConfig
33+
*/
2334
#[Inject(
2435
TemplateRendererInterface::class,
2536
UrlHelper::class,
2637
ContactService::class,
38+
'config.rka.ip_address',
2739
)]
2840
public function __construct(
2941
protected TemplateRendererInterface $template,
3042
protected UrlHelper $urlHelper,
3143
protected ContactService $contactService,
44+
protected array $ipAddressConfig,
3245
) {
3346
}
3447

@@ -42,13 +55,13 @@ public function handle(ServerRequestInterface $request): ResponseInterface
4255
$token = is_array($rawBody) && is_string($rawBody['cf-turnstile-response'] ?? null)
4356
? $rawBody['cf-turnstile-response']
4457
: '';
45-
$ip = $request->getServerParams()['REMOTE_ADDR'] ?? null;
58+
$ip = (string) $request->getAttribute($this->ipAddressConfig['attribute_name']);
4659

47-
if (! $this->contactService->verifyTurnstile($token, is_string($ip) ? $ip : null)) {
60+
if (! $this->contactService->verifyTurnstile($token, $ip)) {
4861
$errors['captcha'] = "We couldn't verify you're human. Please try again.";
4962
}
5063

51-
if ($errors === [] && ! $this->contactService->send($data)) {
64+
if ($errors === [] && ! $this->contactService->send($data, $ip)) {
5265
$mailFailed = true;
5366
}
5467

‎src/App/src/RoutesDelegator.php‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Light\App\Handler\PostCreateContactHandler;
1414
use Mezzio\Application;
1515
use Psr\Container\ContainerInterface;
16+
use RKA\Middleware\IpAddress;
1617

1718
use function assert;
1819

@@ -28,7 +29,7 @@ public function __invoke(ContainerInterface $container, string $serviceName, cal
2829
$app->get('/{categorySlug}/{slug}.md', [GetMarkdownArticleHandler::class], 'app::markdown-article');
2930

3031
$app->get('/contact/', [GetCreateContactFormHandler::class], 'app::create-contact-form');
31-
$app->post('/contact/', [PostCreateContactHandler::class], 'app::create-contact');
32+
$app->post('/contact/', [IpAddress::class, PostCreateContactHandler::class], 'app::create-contact');
3233

3334
// Route name kept as `page::…` because `@layout/default.html.twig` links it by name.
3435
// The matching entry must stay out of `routes.page` in local.php to avoid a duplicate.

‎src/App/src/Service/ContactService.php‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public function turnstileSiteKey(): string
7979
return $this->turnstile['secret_key'] !== '' ? $this->turnstile['site_key'] : '';
8080
}
8181

82-
public function verifyTurnstile(string $token, ?string $remoteIp): bool
82+
public function verifyTurnstile(string $token, string $remoteIp): bool
8383
{
8484
if ($this->turnstileSiteKey() === '') {
8585
return true;
@@ -93,7 +93,7 @@ public function verifyTurnstile(string $token, ?string $remoteIp): bool
9393
'secret' => $this->turnstile['secret_key'],
9494
'response' => $token,
9595
];
96-
if ($remoteIp) {
96+
if ($remoteIp !== '') {
9797
$data['remoteip'] = $remoteIp;
9898
}
9999

@@ -176,11 +176,11 @@ public function validate(array $data): array
176176
/**
177177
* @param ContactData $data
178178
*/
179-
public function send(array $data): bool
179+
public function send(array $data, string $ipAddress = ''): bool
180180
{
181181
$this->mailService->getMessage()->setReplyTo($data['email'], $data['name']);
182182
$this->mailService->setSubject('New contact form submission on dotkernel.com');
183-
$this->mailService->setBody($this->buildBody($data));
183+
$this->mailService->setBody($this->buildBody($data, $ipAddress));
184184

185185
try {
186186
$result = $this->mailService->send();
@@ -237,7 +237,7 @@ private function stringValue(array $raw, string $key): string
237237
/**
238238
* @param ContactData $data
239239
*/
240-
private function buildBody(array $data): string
240+
private function buildBody(array $data, string $ipAddress): string
241241
{
242242
$rows = [
243243
'Topic' => ContactTopicEnum::tryFrom($data['topic'])?->label() ?? '',
@@ -247,6 +247,7 @@ private function buildBody(array $data): string
247247
'Current stack' => $data['stack'],
248248
'Message' => $data['message'],
249249
'Query params' => $data['query'],
250+
'IP address' => $ipAddress,
250251
];
251252

252253
$html = '';

‎src/Blog/src/Entity/Category.php‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ public function setSlug(string $slug): void
5858
$this->slug = $slug;
5959
}
6060

61+
/**
62+
* Visibility only controls the category sidebar widget (and the 404/410 pages, post, tag, author
63+
* and page views that render it), via CategoryRepository::getCategories().
64+
*
65+
* It does not hide a category that has published posts: those are still listed on /categories/
66+
* and in the sitemap (CategoryRepository::getCategoriesWithPublishedPosts()), and their
67+
* /category/{slug}/ page is served.
68+
*/
6169
public function isVisible(): bool
6270
{
6371
return $this->isVisible;

‎src/Blog/src/Handler/GetCategoryCollectionHandler.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function __construct(
2121

2222
public function handle(ServerRequestInterface $request): ResponseInterface
2323
{
24-
$categories = $this->categoryRepository->getCategories();
24+
$categories = $this->categoryRepository->getCategoriesWithPublishedPosts();
2525

2626
return new HtmlResponse(
2727
$this->template->render('page::categories', [

‎src/Blog/src/Handler/GetCategoryResourceHandler.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface
3131
if ($category === null) {
3232
return $this->blogService->notFound($categories);
3333
}
34-
if (! $category->isVisible()) {
34+
if ($category->getPublishedPostsCount() === 0) {
3535
return $this->blogService->gone($categories);
3636
}
3737
$meta = $category;

‎src/Blog/src/Repository/CategoryRepository.php‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ public function getCategoriesWithPublishedPosts(): array
4242
->from(Category::class, 'categories');
4343

4444
$qb->where($qb->expr()->in('categories.id', $publishedCategoryIds->getDQL()))
45-
->andWhere('categories.isVisible = :visible')
46-
->setParameter('published', PostStatusEnum::Published)
47-
->setParameter('visible', true);
45+
->setParameter('published', PostStatusEnum::Published);
4846

4947
return $qb->getQuery()->getResult();
5048
}

0 commit comments

Comments
 (0)