From 54b768f4c0e77791342f5f72d2b98d164cc4741a Mon Sep 17 00:00:00 2001 From: "H. C. Kruse" Date: Sat, 24 Jun 2023 23:01:54 +0200 Subject: [PATCH] feat: Add LinksTOPropertyAnnotator --- data/definitions.json | 42 +++--- extension.json | 6 + i18n/en.json | 5 +- i18n/qqq.json | 4 +- .../LinksToPropertyAnnotator.php | 99 ++++++++++++++ .../LinksToPropertyAnnotatorTest.php | 124 ++++++++++++++++++ 6 files changed, 260 insertions(+), 20 deletions(-) create mode 100644 src/PropertyAnnotators/LinksToPropertyAnnotator.php create mode 100644 tests/phpunit/Unit/PropertyAnnotators/LinksToPropertyAnnotatorTest.php diff --git a/data/definitions.json b/data/definitions.json index e57efd99..b64c6011 100644 --- a/data/definitions.json +++ b/data/definitions.json @@ -945,23 +945,29 @@ "alias": "sesp-exif-languagecode", "label": "Exif:Language code" }, - "GPSLATITUDE": { - "id": "___EXIFGPSLATITUDE", - "type": "_txt", - "alias": "sesp-exif-gpslatitude", - "label": "Exif:GPSLatitude" - }, - "GPSLONGITUDE": { - "id": "___EXIFGPSLONGITUDE", - "type": "_txt", - "alias": "sesp-exif-gpslongitude", - "label": "Exif:GPSLongitude" - }, - "GPSALTITUDE": { - "id": "___EXIFGPSALTITUDE", - "type": "_txt", - "alias": "sesp-exif-gpsaltitude", - "label": "Exif:GPSAltitude" - } + "GPSLATITUDE": { + "id": "___EXIFGPSLATITUDE", + "type": "_txt", + "alias": "sesp-exif-gpslatitude", + "label": "Exif:GPSLatitude" + }, + "GPSLONGITUDE": { + "id": "___EXIFGPSLONGITUDE", + "type": "_txt", + "alias": "sesp-exif-gpslongitude", + "label": "Exif:GPSLongitude" + }, + "GPSALTITUDE": { + "id": "___EXIFGPSALTITUDE", + "type": "_txt", + "alias": "sesp-exif-gpsaltitude", + "label": "Exif:GPSAltitude" + }, + "LINKSTO": { + "id": "___LINKSTO", + "type": "_wpg", + "alias": "sesp-property-links-to", + "label": "Links to" + } } } diff --git a/extension.json b/extension.json index d7b994e7..4fb0d515 100644 --- a/extension.json +++ b/extension.json @@ -67,6 +67,12 @@ "path": false, "description": "Should edits via user accounts in usergroup \"bot\" be ignored when storing data for the special properties?", "descriptionmsg": "sesp-config-exclude-bot-edits" + }, + "LinksToEnabledNamespaces": { + "value": [], + "path": false, + "description": "List of namespaces where the 'Links to' property should be enabled", + "descriptionmsg": "sesp-config-links-to-enabled-namespaces" } }, "Hooks": { diff --git a/i18n/en.json b/i18n/en.json index c3220925..abceaa40 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -61,6 +61,8 @@ "sesp-property-user-group-desc": "\"$1\" records a list of groups applied to a user. It is provided by the [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties] extension.", "sesp-property-exif-data": "Exif data", "sesp-property-exif-data-desc": "\"$1\" records the file's EXIF data. It is provided by the [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties] extension.", + "sesp-property-links-to": "Links to", + "sesp-property-links-to-desc": "\"$1\" records a list of pages the current page links to.", "sesp-exif-languagecode": "Exif:Language code", "sesp-exif-gpslatitude": "Exif:Latitude", "sesp-exif-gpslongitude": "Exif:Longitude", @@ -77,5 +79,6 @@ "sesp-config-enabled-property-list": "Specifies the enabled properties", "sesp-config-label-cache-version": "Specifies an internal cache modifier", "sesp-config-exclude-bot-edits": "Should edits via user accounts in usergroup \"bot\" be ignored when storing data for the special properties?", - "sesp-config-short-url-prefix": "Used in connection with ShortUrlUtils" + "sesp-config-short-url-prefix": "Used in connection with ShortUrlUtils", + "sesp-config-links-to-enabled-namespaces": "List of namespace ids that are enabled for the 'Links to' property." } diff --git a/i18n/qqq.json b/i18n/qqq.json index 4175165a..f1fdf043 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -64,6 +64,7 @@ "sesp-property-user-group-desc": "{{Doc-smw-sp-desc|extension=SESP}}", "sesp-property-exif-data": "{{Doc-smw-sp|extension=SESP}}", "sesp-property-exif-data-desc": "{{Doc-smw-sp-desc|extension=SESP}}", + "sesp-property-links-to": "{{Doc-smw-sp-desc|extension=SESP}}", "sesp-exif-languagecode": "This is the name of a special property.\n\nRelated to {{msg-mw|Exif-languagecode}}", "sesp-exif-gpslatitude": "This is the name of a special property.\n\nRelated to {{msg-mw|Exif-gpslatitude}}", "sesp-exif-gpslongitude": "This is the name of a special property.\n\nRelated to {{msg-mw|Exif-gpslongitude}}", @@ -80,5 +81,6 @@ "sesp-config-enabled-property-list": "Explains what a configuration variable does.", "sesp-config-label-cache-version": "Explains what a configuration variable does.", "sesp-config-exclude-bot-edits": "Explains what a configuration variable does.", - "sesp-config-short-url-prefix": "Explains what a configuration variable does." + "sesp-config-short-url-prefix": "Explains what a configuration variable does.", + "sesp-config-links-to-enabled-namespaces": "Explains what a configuration variable does." } diff --git a/src/PropertyAnnotators/LinksToPropertyAnnotator.php b/src/PropertyAnnotators/LinksToPropertyAnnotator.php new file mode 100644 index 00000000..0f3b87e6 --- /dev/null +++ b/src/PropertyAnnotators/LinksToPropertyAnnotator.php @@ -0,0 +1,99 @@ +appFactory = $appFactory; + + $this->enabledNamespaces = MediaWikiServices::getInstance() + ->getConfigFactory() + ->makeConfig( 'sespg' ) + ->get( 'LinksToEnabledNamespaces' ); + } + + public function setEnabledNamespaces( array $namespaces ) { + $this->enabledNamespaces = $namespaces; + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function isAnnotatorFor( DIProperty $property ) { + return $property->getKey() === self::PROP_ID ; + } + + /** + * @since 2.0 + * + * {@inheritDoc} + */ + public function addAnnotation( DIProperty $property, SemanticData $semanticData ) { + $page = $semanticData->getSubject()->getTitle(); + + if ( $page === null || ( !empty( $this->enabledNamespaces ) && !$page->inNamespaces( $this->enabledNamespaces ) ) ) { + return; + } + + $con = $this->appFactory->getConnection(); + + if ( $con === null ) { + return; + } + + $where = sprintf( + 'pl.pl_from = %s AND pl.pl_title != %s', + $page->getArticleID(), + $con->addQuotes( $page->getDBkey() ) + ); + + $res = $con->select( + [ 'pl' => 'pagelinks', 'page' ], + [ 'sel_title' => 'pl.pl_title', 'sel_ns' => 'pl.pl_namespace' ], + [ $where ], + __METHOD__, + [ 'DISTINCT' ], + [ 'page' => [ 'JOIN', 'page_id=pl_from' ] ] + ); + + foreach( $res as $row ) { + $title = Title::newFromText( $row->sel_title, $row->sel_ns ); + if ( $title !== null && $title->exists() ) { + $semanticData->addPropertyObjectValue( $property, DIWikiPage::newFromTitle( $title ) ); + } + } + } +} diff --git a/tests/phpunit/Unit/PropertyAnnotators/LinksToPropertyAnnotatorTest.php b/tests/phpunit/Unit/PropertyAnnotators/LinksToPropertyAnnotatorTest.php new file mode 100644 index 00000000..3ffbf3c2 --- /dev/null +++ b/tests/phpunit/Unit/PropertyAnnotators/LinksToPropertyAnnotatorTest.php @@ -0,0 +1,124 @@ +appFactory = $this->getMockBuilder( '\SESP\AppFactory' ) + ->disableOriginalConstructor() + ->getMock(); + + $this->property = new DIProperty( '___LINKSTO' ); + } + + public function testCanConstruct() { + + $this->assertInstanceOf( + LinksToPropertyAnnotator::class, + new LinksToPropertyAnnotator( $this->appFactory ) + ); + } + + public function testIsAnnotatorFor() { + + $instance = new LinksToPropertyAnnotator( + $this->appFactory + ); + + $this->assertTrue( + $instance->isAnnotatorFor( $this->property ) + ); + } + + public function testAddAnnotation() { + $factory = $this->getMockBuilder( AppFactory::class ) + ->disableOriginalConstructor() + ->getMock(); + + $factory->expects( $this->once() )->method('getConnection'); + + $subject = $this->getMockBuilder( DIWikiPage::class ) + ->disableOriginalConstructor() + ->getMock(); + + $subject->expects( $this->once() ) + ->method( 'getTitle' ) + ->willReturn( Title::newFromText( 'Foo' ) ); + + $semanticData = $this->getMockBuilder( SemanticData::class ) + ->disableOriginalConstructor() + ->getMock(); + + $semanticData->expects( $this->once() ) + ->method( 'getSubject' ) + ->willReturn( $subject ); + + $semanticData->expects( $this->never() ) + ->method( 'addPropertyObjectValue' ); + + $annotator = new LinksToPropertyAnnotator( + $factory + ); + + $annotator->addAnnotation( $this->property, $semanticData ); + } + + public function testNotAddAnnotation() { + $factory = $this->getMockBuilder( AppFactory::class ) + ->disableOriginalConstructor() + ->getMock(); + + $factory->expects( $this->never() ) + ->method('getConnection'); + + $subject = $this->getMockBuilder( DIWikiPage::class ) + ->disableOriginalConstructor() + ->getMock(); + + $subject->expects( $this->once() ) + ->method( 'getTitle' ) + ->willReturn( Title::newFromText( 'Foo' ) ); + + $semanticData = $this->getMockBuilder( SemanticData::class ) + ->disableOriginalConstructor() + ->getMock(); + + $semanticData->expects( $this->once() ) + ->method( 'getSubject' ) + ->willReturn( $subject ); + + $semanticData->expects( $this->never() ) + ->method( 'addPropertyObjectValue' ); + + $annotator = new LinksToPropertyAnnotator( + $factory + ); + + $annotator->setEnabledNamespaces( [ 2 ] ); + + $annotator->addAnnotation( $this->property, $semanticData ); + } +}