Skip to content

Commit 0439b33

Browse files
feat: add Polylang-aware page tree menu resolution (#1879)
* feat(polylang): resolve page tree menu page ids by language Agent-Logs-Url: https://github.com/helsingborg-stad/Municipio/sessions/46de5753-db0c-4571-8542-59b0e117f583 Co-authored-by: sebastianthulin <797129+sebastianthulin@users.noreply.github.com> * test(polylang): align new page tree integration with lint rules Agent-Logs-Url: https://github.com/helsingborg-stad/Municipio/sessions/46de5753-db0c-4571-8542-59b0e117f583 Co-authored-by: sebastianthulin <797129+sebastianthulin@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sebastianthulin <797129+sebastianthulin@users.noreply.github.com>
1 parent 53fad54 commit 0439b33

3 files changed

Lines changed: 193 additions & 0 deletions

File tree

library/App.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ public function __construct(
223223
$moveAdminPageToSettings = new \Municipio\Integrations\MiniOrange\MoveAdminPageToSettings($this->wpService);
224224
$this->hooksRegistrar->register($moveAdminPageToSettings);
225225

226+
/* Integration: Polylang */
227+
$resolvePageTreeMenuPageIds = new \Municipio\Integrations\Polylang\ResolvePageTreeMenuPageIds($this->wpService);
228+
$this->hooksRegistrar->register($resolvePageTreeMenuPageIds);
229+
226230
/* Admin uploads */
227231
$uploads = new \Municipio\Admin\Uploads();
228232
$uploads->addHooks();
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Municipio\Integrations\Polylang;
6+
7+
use Closure;
8+
use Municipio\HooksRegistrar\Hookable;
9+
use WpService\Contracts\AddFilter;
10+
use WpService\Contracts\GetPostTypes;
11+
12+
/**
13+
* Resolves language-specific page IDs used by page tree menus.
14+
*/
15+
class ResolvePageTreeMenuPageIds implements Hookable
16+
{
17+
/**
18+
* Constructor.
19+
*
20+
* @param AddFilter&GetPostTypes $wpService The WordPress service.
21+
* @param ?Closure $translatedPostResolver Optional translated post resolver.
22+
*/
23+
public function __construct(
24+
private AddFilter&GetPostTypes $wpService,
25+
private ?Closure $translatedPostResolver = null
26+
) {
27+
}
28+
29+
/**
30+
* @inheritDoc
31+
*/
32+
public function addHooks(): void
33+
{
34+
foreach ($this->getOptionHooks() as $hookName) {
35+
$this->wpService->addFilter($hookName, [$this, 'resolveTranslatedPageId']);
36+
}
37+
}
38+
39+
/**
40+
* Resolve a translated page ID for the active language when possible.
41+
*
42+
* @param mixed $pageId The original page ID.
43+
*
44+
* @return mixed The translated page ID, or the original value.
45+
*/
46+
public function resolveTranslatedPageId(mixed $pageId): mixed
47+
{
48+
if (!is_numeric($pageId) || (int) $pageId <= 0) {
49+
return $pageId;
50+
}
51+
52+
$translatedPostResolver = $this->getTranslatedPostResolver();
53+
54+
if ($translatedPostResolver === null) {
55+
return $pageId;
56+
}
57+
58+
$translatedPageId = $translatedPostResolver((int) $pageId);
59+
60+
return is_numeric($translatedPageId) && (int) $translatedPageId > 0
61+
? (int) $translatedPageId
62+
: (int) $pageId;
63+
}
64+
65+
/**
66+
* Get the option hooks that should resolve translated page IDs.
67+
*
68+
* @return array<string> The option hooks to register.
69+
*/
70+
private function getOptionHooks(): array
71+
{
72+
$hooks = [
73+
'option_page_on_front',
74+
'option_page_for_posts',
75+
];
76+
77+
foreach ($this->wpService->getPostTypes(['public' => true, 'hierarchical' => true]) as $postType) {
78+
$hooks[] = sprintf('option_page_for_%s', $postType);
79+
}
80+
81+
return array_values(array_unique($hooks));
82+
}
83+
84+
/**
85+
* Get the translated post resolver.
86+
*
87+
* @return ?Closure The translated post resolver.
88+
*/
89+
private function getTranslatedPostResolver(): ?Closure
90+
{
91+
if ($this->translatedPostResolver instanceof Closure) {
92+
return $this->translatedPostResolver;
93+
}
94+
95+
if (!is_callable('pll_get_post')) {
96+
return null;
97+
}
98+
99+
return static fn (int $pageId): mixed => call_user_func('pll_get_post', $pageId);
100+
}
101+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Municipio\Integrations\Polylang;
6+
7+
use Closure;
8+
use PHPUnit\Framework\Attributes\TestDox;
9+
use PHPUnit\Framework\TestCase;
10+
use WpService\Implementations\FakeWpService;
11+
12+
class ResolvePageTreeMenuPageIdsTest extends TestCase
13+
{
14+
#[TestDox('class can be instantiated')]
15+
public function testCanBeInstantiated(): void
16+
{
17+
static::assertInstanceOf(ResolvePageTreeMenuPageIds::class, $this->getSut());
18+
}
19+
20+
#[TestDox('addHooks() registers filters for translated page tree related options')]
21+
public function testAddHooksRegistersFiltersForTranslatedPageTreeRelatedOptions(): void
22+
{
23+
$wpService = new FakeWpService([
24+
'addFilter' => true,
25+
'getPostTypes' => ['page', 'event'],
26+
]);
27+
28+
$sut = new ResolvePageTreeMenuPageIds($wpService);
29+
30+
$sut->addHooks();
31+
32+
static::assertSame(
33+
[
34+
'option_page_on_front',
35+
'option_page_for_posts',
36+
'option_page_for_page',
37+
'option_page_for_event',
38+
],
39+
array_column($wpService->methodCalls['addFilter'], 0)
40+
);
41+
}
42+
43+
#[TestDox('resolveTranslatedPageId() returns the translated page ID when Polylang resolves one')]
44+
public function testResolveTranslatedPageIdReturnsTranslatedPageId(): void
45+
{
46+
$sut = $this->getSut(
47+
translatedPostResolver: static fn (int $pageId): int => $pageId + 100
48+
);
49+
50+
static::assertSame(110, $sut->resolveTranslatedPageId(10));
51+
}
52+
53+
#[TestDox('resolveTranslatedPageId() returns the original value when the page ID is not numeric')]
54+
public function testResolveTranslatedPageIdReturnsOriginalValueWhenPageIdIsNotNumeric(): void
55+
{
56+
$sut = $this->getSut();
57+
58+
static::assertSame('not-a-page-id', $sut->resolveTranslatedPageId('not-a-page-id'));
59+
}
60+
61+
#[TestDox('resolveTranslatedPageId() returns the original page ID when no translated page is found')]
62+
public function testResolveTranslatedPageIdReturnsOriginalPageIdWhenNoTranslatedPageIsFound(): void
63+
{
64+
$sut = $this->getSut(
65+
translatedPostResolver: static fn (int $pageId): int => 0
66+
);
67+
68+
static::assertSame(10, $sut->resolveTranslatedPageId(10));
69+
}
70+
71+
/**
72+
* Get the system under test.
73+
*
74+
* @param ?Closure $translatedPostResolver Optional translated post resolver.
75+
*
76+
* @return ResolvePageTreeMenuPageIds The system under test.
77+
*/
78+
private function getSut(?Closure $translatedPostResolver = null): ResolvePageTreeMenuPageIds
79+
{
80+
return new ResolvePageTreeMenuPageIds(
81+
new FakeWpService([
82+
'addFilter' => true,
83+
'getPostTypes' => ['page'],
84+
]),
85+
$translatedPostResolver
86+
);
87+
}
88+
}

0 commit comments

Comments
 (0)