Skip to content

Commit 754b070

Browse files
JeroenDeDauwclaudealistair3149
authored
Store the full prefixed page title and namespace ID on page nodes (#946)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: alistair3149 <alistair31494322@gmail.com>
1 parent d1f46d2 commit 754b070

12 files changed

Lines changed: 106 additions & 16 deletions

docs/concepts/glossary.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ Display Rules have:
140140
A key-value pair stored on the Page node in the graph database. Page Properties are metadata about the wiki page
141141
itself, as opposed to Subject Statements, which are structured data about the entities described on the page.
142142

143-
Built-in Page Properties include `name`, `creationTime`, `lastUpdated`, `categories`, and `lastEditor`. Extensions can
144-
contribute additional Page Properties.
143+
Built-in Page Properties include `name`, `namespaceId`, `creationTime`, `lastUpdated`, `categories`, and `lastEditor`.
144+
Extensions can contribute additional Page Properties.
145145

146146
When using Neo4j, Page Properties are available on every Page node and are queryable via Cypher
147147
(e.g., `MATCH (page:Page) WHERE page.lastUpdated > datetime("2024-01-01")`).

docs/reference/graph-model.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ page ids are only unique within a single wiki. Each page node therefore carries
3737
|----------|------------|-------------|
3838
| `wiki_id` | string | [MediaWiki Wiki ID](https://www.mediawiki.org/wiki/Manual:Wiki_ID) (database name + table prefix) of the owning wiki |
3939
| `id` | integer | MediaWiki page ID (unique per wiki) |
40-
| `name` | string | Page title |
40+
| `name` | string | Full page title, including the namespace prefix (e.g. `Help:Installation`) |
41+
| `namespaceId` | integer | MediaWiki namespace ID of the page (e.g. `0` for the main namespace, `12` for Help) |
4142
| `creationTime` | datetime | When the page was created |
4243
| `lastUpdated` | datetime | When the page was last modified |
4344
| `lastEditor` | string | Username of the last editor |

docs/reference/subject-format.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,8 @@ See [ADR 014](../adr/014-improved-id-format.md) for details on the ID format.
193193

194194
Returns the same statement format as storage, with additional fields:
195195
- `requestedId`: The ID that was requested
196-
- Each subject includes `id`, `pageId`, and `pageTitle` fields
196+
- Each subject includes `id`, `pageId`, and `pageTitle` fields. `pageTitle` is the full page
197+
title including the namespace prefix (e.g. `Help:Installation`).
197198

198199
### Writing Subjects
199200

src/Domain/Page/PagePropertyProviderContext.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
readonly class PagePropertyProviderContext {
88

99
/**
10-
* @param string $pageTitle Page title text without namespace prefix, e.g. "My Page" not "Talk:My Page"
10+
* @param string $pageTitle Full prefixed page title, e.g. "Help:My Page" ("My Page" in the main namespace)
11+
* @param int $namespaceId MediaWiki namespace ID, e.g. 0 for the main namespace or 12 for Help
1112
* @param string $creationTime In the standard MediaWiki format, ie 20230726163439
1213
* @param string $modificationTime In the standard MediaWiki format, ie 20230726163439
1314
* @param string[] $categories
@@ -16,6 +17,7 @@
1617
public function __construct(
1718
public PageId $pageId,
1819
public string $pageTitle,
20+
public int $namespaceId,
1921
public string $creationTime,
2022
public string $modificationTime,
2123
public array $categories,

src/NeoWikiExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ public function getStoreContentUC(): OnRevisionCreatedHandler {
204204
new PagePropertiesBuilder(
205205
revisionStore: MediaWikiServices::getInstance()->getRevisionStore(),
206206
contentHandlerFactory: MediaWikiServices::getInstance()->getContentHandlerFactory(),
207+
titleFormatter: MediaWikiServices::getInstance()->getTitleFormatter(),
207208
providerRegistry: $this->getPagePropertyProviderRegistry(),
208209
)
209210
);

src/PagePropertiesBuilder.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use MediaWiki\Revision\RevisionRecord;
1010
use MediaWiki\Revision\RevisionStore;
1111
use MediaWiki\Revision\SlotRecord;
12+
use MediaWiki\Title\TitleFormatter;
1213
use MediaWiki\User\UserIdentity;
1314
use ProfessionalWiki\NeoWiki\Domain\Page\PageId;
1415
use ProfessionalWiki\NeoWiki\Domain\Page\PageProperties;
@@ -20,6 +21,7 @@
2021
public function __construct(
2122
private RevisionStore $revisionStore,
2223
private IContentHandlerFactory $contentHandlerFactory,
24+
private TitleFormatter $titleFormatter,
2325
private PagePropertyProviderRegistry $providerRegistry,
2426
) {
2527
}
@@ -37,9 +39,12 @@ public function getPagePropertiesFor( RevisionRecord $revision, ?UserIdentity $u
3739
}
3840

3941
private function buildContext( RevisionRecord $revision, ?UserIdentity $user ): PagePropertyProviderContext {
42+
$linkTarget = $revision->getPageAsLinkTarget();
43+
4044
return new PagePropertyProviderContext(
4145
pageId: new PageId( $revision->getPageId() ),
42-
pageTitle: $revision->getPageAsLinkTarget()->getText(),
46+
pageTitle: $this->titleFormatter->getPrefixedText( $linkTarget ),
47+
namespaceId: $linkTarget->getNamespace(),
4348
creationTime: $this->getCreationTime( $revision ),
4449
modificationTime: $this->getModificationTime( $revision ),
4550
categories: $this->getCategories( $revision ),

src/Persistence/CorePagePropertyProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class CorePagePropertyProvider implements PagePropertyProvider {
1313
public function getProperties( PagePropertyProviderContext $context ): array {
1414
return [
1515
'name' => $context->pageTitle,
16+
'namespaceId' => $context->namespaceId,
1617
'creationTime' => new PageDateTime( $context->creationTime ),
1718
'lastUpdated' => new PageDateTime( $context->modificationTime ),
1819
'categories' => $context->categories,

tests/phpunit/Data/TestPageProperties.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class TestPageProperties {
1414
*/
1515
public static function build(
1616
string $title = 'PageTitle',
17+
int $namespaceId = 0,
1718
string $creationTime = '20230726163439',
1819
string $modificationTime = '20230726163439',
1920
array $categories = [],
@@ -23,6 +24,7 @@ public static function build(
2324
return new PageProperties( array_merge(
2425
[
2526
'name' => $title,
27+
'namespaceId' => $namespaceId,
2628
'creationTime' => new PageDateTime( $creationTime ),
2729
'lastUpdated' => new PageDateTime( $modificationTime ),
2830
'categories' => $categories,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare( strict_types = 1 );
4+
5+
namespace ProfessionalWiki\NeoWiki\Tests\EntryPoints;
6+
7+
use ProfessionalWiki\NeoWiki\Tests\Data\TestSubject;
8+
use ProfessionalWiki\NeoWiki\Tests\NeoWikiIntegrationTestCase;
9+
10+
/**
11+
* The page node name must carry the full title including the namespace prefix, so that
12+
* consumers such as the relation value link can build a correct URL for subjects stored
13+
* on pages outside the main namespace.
14+
*
15+
* @covers \ProfessionalWiki\NeoWiki\PagePropertiesBuilder
16+
* @covers \ProfessionalWiki\NeoWiki\Persistence\CorePagePropertyProvider
17+
* @group Database
18+
*/
19+
class NamespacedPageGraphProjectionTest extends NeoWikiIntegrationTestCase {
20+
21+
private const PAGE_NAME = 'Help:Namespaced subject page';
22+
23+
protected function setUp(): void {
24+
parent::setUp();
25+
$this->setUpNeo4j();
26+
$this->createSchema( TestSubject::DEFAULT_SCHEMA_ID );
27+
$this->markPageTableAsUsed();
28+
}
29+
30+
public function testPageNodeNameIncludesNamespacePrefix(): void {
31+
$revision = $this->createPageWithSubjects( self::PAGE_NAME, TestSubject::build() );
32+
33+
$this->assertSame(
34+
self::PAGE_NAME,
35+
$this->readPageNodeName( $revision->getPageId() )
36+
);
37+
}
38+
39+
public function testPageNodeStoresNamespaceId(): void {
40+
$revision = $this->createPageWithSubjects( self::PAGE_NAME, TestSubject::build() );
41+
42+
$this->assertSame(
43+
NS_HELP,
44+
$this->readPageNodeNamespaceId( $revision->getPageId() )
45+
);
46+
}
47+
48+
}

tests/phpunit/EntryPoints/PageMoveGraphProjectionTest.php

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ public function testMovingPageUpdatesGraphNodeName(): void {
3939
);
4040
}
4141

42+
public function testMovingPageToAnotherNamespaceUpdatesNamespaceId(): void {
43+
$revision = $this->createPageWithSubjects( 'Namespace move source', TestSubject::build() );
44+
$pageId = $revision->getPageId();
45+
46+
$this->movePage( 'Namespace move source', 'Help:Namespace move target' );
47+
48+
$this->assertSame(
49+
NS_HELP,
50+
$this->readPageNodeNamespaceId( $pageId )
51+
);
52+
}
53+
4254
private function movePage( string $from, string $to ): void {
4355
$movePage = MediaWikiServices::getInstance()->getMovePageFactory()->newMovePage(
4456
Title::newFromText( $from ),
@@ -51,13 +63,4 @@ private function movePage( string $from, string $to ): void {
5163
DeferredUpdates::doUpdates();
5264
}
5365

54-
private function readPageNodeName( int $pageId ): ?string {
55-
$result = $this->newNeo4jQueryStore()->runReadQuery(
56-
'MATCH (page:Page {id: $pageId}) RETURN page.name AS name',
57-
[ 'pageId' => $pageId ]
58-
);
59-
60-
return $result->first()->toRecursiveArray()['name'] ?? null;
61-
}
62-
6366
}

0 commit comments

Comments
 (0)