Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions docs/reference/extending.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ $registrar->addNeo4jValueBuilder( ColorType::NAME, static fn ( $value ) => $valu
### Page Property Providers

Page Property Providers contribute key/value metadata to the Page node in the graph (queryable via Cypher;
Neo4j is currently the only graph backend). Implement `PagePropertyProvider`:
Neo4j is currently the only graph backend). They run when a revision is stored for a page carrying Subject
data (including undeletions), and again for such pages when the graph is rebuilt with the
`RebuildGraphDatabases` maintenance script. Pages without Subject data are not stored in the graph, so
providers are never invoked for them. Implement `PagePropertyProvider`:

```php
class StaticPagePropertyProvider implements PagePropertyProvider {
Expand All @@ -87,14 +90,14 @@ class StaticPagePropertyProvider implements PagePropertyProvider {
```

Register with `NeoWikiRegistrar::addPagePropertyProvider()`. The context exposes the page id, title,
creation and modification times, categories, and last editor, so providers can derive Page Properties
from the page content without re-fetching or re-parsing it.
creation and modification times, categories, and last editor, as well as the page content and its parse
products, so providers can derive Page Properties from the page content without re-fetching or re-parsing it.

To derive Page Properties from the content, prefer the parse products: `categories`, and
To derive Page Properties from the content, prefer the parse products: `categories` and
`parserProperties` — the MediaWiki page properties recorded during parsing (e.g. those a parser hook
sets via `ParserOutput::setPageProperty`). These are template-expansion-safe and robust. (Note that
`parserProperties` are an input from MediaWiki's parse; they are not the NeoWiki Page Properties this
provider returns.) The raw main slot `content` and its `contentModel` are also exposed, but scraping
provider returns.) The raw main slot `content` and its `contentModel` are the fallback: scraping
raw wikitext is fragile — reach for them mainly when handling a custom, non-wikitext content model that
the parse products do not cover. Example:
[`src/StaticPagePropertyProvider.php`](https://github.com/ProfessionalWiki/NeoWiki/blob/master/tests/RedHerb/src/StaticPagePropertyProvider.php).
Expand Down
2 changes: 1 addition & 1 deletion src/PagePropertiesBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private function buildContext( RevisionRecord $revision, ?UserIdentity $user ):

private function parse( Content $content, RevisionRecord $revision ): ParserOutput {
return $this->contentHandlerFactory->getContentHandler( $content->getModel() )
->getParserOutput( $content, new ContentParseParams( $revision->getPage() ) );
->getParserOutput( $content, new ContentParseParams( $revision->getPage(), $revision->getId() ) );
}

private function getCreationTime( RevisionRecord $revision ): string {
Expand Down
40 changes: 39 additions & 1 deletion tests/phpunit/PagePropertiesBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace ProfessionalWiki\NeoWiki\Tests;

use MediaWiki\Revision\RevisionRecord;
use ProfessionalWiki\NeoWiki\Domain\Page\PagePropertyProviderContext;
use ProfessionalWiki\NeoWiki\Domain\Page\PagePropertyProviderRegistry;
use ProfessionalWiki\NeoWiki\PagePropertiesBuilder;
Expand Down Expand Up @@ -39,9 +40,46 @@ public function testProviderReceivesCategoriesFromParsedContent(): void {
$this->assertSame( [ 'Cats' ], $context->categories );
}

public function testParseIsBoundToTheProvidedRevision(): void {
$revision = $this->editPage( 'PagePropertiesBuilderTestPage', '{{DEFAULTSORT:Rev{{REVISIONID}}}}' )->getNewRevision();
$this->editPage( 'PagePropertiesBuilderTestPage', 'Newer revision without a defaultsort' );

$this->assertSame(
'Rev' . $revision->getId(),
$this->getContextForRevision( $revision )->parserProperties['defaultsort']
);
}

public function testProviderReceivesContentModelOfNonWikitextContent(): void {
$revision = $this->editPage( 'MediaWiki:PagePropertiesBuilderTest.json', '{ "answer": 42 }' )->getNewRevision();

$this->assertSame( CONTENT_MODEL_JSON, $this->getContextForRevision( $revision )->contentModel );
}

public function testContextHasEmptySentinelsWhenContentIsUnavailable(): void {
$revision = $this->editPage( 'PagePropertiesBuilderTestPage', 'Hidden [[Category:Cats]] {{DEFAULTSORT:Zebra}}' )->getNewRevision();
$this->editPage( 'PagePropertiesBuilderTestPage', 'Newer public revision' );
$this->revisionDelete( $revision );

$context = $this->getContextForRevision( $this->getSuppressedRevision( $revision->getId() ) );

$this->assertSame( '', $context->content );
$this->assertSame( '', $context->contentModel );
$this->assertSame( [], $context->parserProperties );
$this->assertSame( [], $context->categories );
}

private function getSuppressedRevision( int $revisionId ): RevisionRecord {
return $this->getServiceContainer()->getRevisionStore()->getRevisionById( $revisionId );
}

private function getContextForNewPageWithContent( string $wikitext ): PagePropertyProviderContext {
$revision = $this->editPage( 'PagePropertiesBuilderTestPage', $wikitext )->getNewRevision();
return $this->getContextForRevision(
$this->editPage( 'PagePropertiesBuilderTestPage', $wikitext )->getNewRevision()
);
}

private function getContextForRevision( RevisionRecord $revision ): PagePropertyProviderContext {
$spy = new SpyPagePropertyProvider();

$this->newPagePropertiesBuilder( $spy )->getPagePropertiesFor( $revision, null );
Expand Down
Loading