Property type: record (formally page).

Property ID: '___USEREDITCNT'.
Property structure:
* '___USEREDITCNT_NS': _num (namespace number),
* '___USEREDITCNTNS_CNT': _num (user's edit count in this namespace).
Property domain: User namespace.
Property uniqueness: one value per namespace provided user has made edits in it.
Property annotator class: UserEditCountPropertyAnnotator.
Property annotator test class: UserEditCountPerNsPropertyAnnotatorTest.

{{#ask:}} query examples:

Edit count rating per user per namespace:
{{#ask:[[-User edit count per namesoace::+]]
| mainlabel = -
| ?-User edit count per namespace
| ?Namespace
| ?Edit count
| sort = Edit count
| order = DESC
}}

Certain user's edits in the main namespace:
{{#ask:[[-User edit count per namespace::User:Alex Mashin]]
[[Namespace::0]]
| ?Edit count =
| mainlabel = -
}}

Made in reference to #169.
这个提交包含在:
Alexander Mashin 2022-02-07 11:13:19 +07:00
父节点 ddc5d96b41
当前提交 944ba5048c
共有 10 个文件被更改,包括 406 次插入1 次删除

查看文件

@ -141,6 +141,25 @@
"alias": "sesp-property-user-edit-count",
"label": "User edit count"
},
"_USEREDITCNTNS": {
"id": "___USEREDITCNTNS",
"type": "_wpg",
"show": false,
"alias": "sesp-property-user-edit-count-per-ns",
"label": "User edit count per namespace"
},
"_USEREDITCNTNS_NS": {
"id": "___USEREDITCNTNS_NS",
"type": "_num",
"alias": "sesp-property-user-edit-count-ns",
"label": "User edit count per namespace:Namespace"
},
"_USEREDITCNTNS_CNT": {
"id": "___USEREDITCNTNS_CNT",
"type": "_num",
"alias": "sesp-property-user-edit-count-cnt",
"label": "User edit count per namespace:Edit count"
},
"_USERBLOCK": {
"id": "___USERBLOCK",
"type": "_txt",

查看文件

@ -32,6 +32,7 @@ Property labels differ according to the language the wiki was set up. An easy wa
- `_SUBP` adds a property called "Subpage" which records all subpages to a page
- `_USERREG` adds a property called "User registration date" to user pages which records the user's registration date
- `_USEREDITCNT` adds a property called "User edit count" to user pages which records the user's edit count
- `_USEREDITCNTNS` adds a property called "User edit count per namespace" to user pages, which records the user's edit counts in all namespaces
- `_USERBLOCK` adds a property called "User block" to user pages which records the user's block status
- `_USERRIGHT` adds a property called "User right" to user pages which records the user's assigned rights
- `_USERGROUP` adds a property called "User group" to user pages which records the user's assigned groups

查看文件

@ -47,6 +47,12 @@
"sesp-property-user-registration-date-desc": "\"$1\" records the user's registration date. It is provided by the [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties] extension.",
"sesp-property-user-edit-count": "User edit count",
"sesp-property-user-edit-count-desc": "\"$1\" records the user's edit count. It is provided by the [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties] extension.",
"sesp-property-user-edit-count-per-ns": "User edit count per namespace",
"sesp-property-user-edit-count-per-ns-desc": "\"$1\" records the user's edit count in certain namespace. It is provided by the [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties] extension.",
"sesp-property-user-edit-count-ns": "Namespace",
"sesp-property-user-edit-count-ns-desc": "\"$1\" records the namespace number in the user's edit count record. It is provided by the [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties] extension.",
"sesp-property-user-edit-count-cnt": "Edit count",
"sesp-property-user-edit-count-cnt-desc": "\"$1\" records the edit count in the user's edit count record. It is provided by the [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties] extension.",
"sesp-property-user-block": "User block",
"sesp-property-user-block-desc": "\"$1\" records a list of blocks 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-user-right": "User right",

查看文件

@ -48,6 +48,12 @@
"sesp-property-user-registration-date-desc": "{{Doc-smw-sp-desc|extension=SESP}}",
"sesp-property-user-edit-count": "{{Doc-smw-sp|extension=SESP}}",
"sesp-property-user-edit-count-desc": "{{Doc-smw-sp-desc|extension=SESP}}",
"sesp-property-user-edit-count-per-ns": "{{Doc-smw-sp|extension=SESP}}",
"sesp-property-user-edit-count-per-ns-desc": "{{Doc-smw-sp-desc|extension=SESP}}",
"sesp-property-user-edit-count-ns": "{{Doc-smw-sp|extension=SESP}}",
"sesp-property-user-edit-count-ns-desc": "{{Doc-smw-sp-desc|extension=SESP}}",
"sesp-property-user-edit-count-cnt": "{{Doc-smw-sp|extension=SESP}}",
"sesp-property-user-edit-count-cnt-desc": "{{Doc-smw-sp-desc|extension=SESP}}",
"sesp-property-user-block": "This is the name of a special property.",
"sesp-property-user-block-desc": "{{Doc-smw-sp-desc|extension=SESP}}",
"sesp-property-user-right": "This is the name of a special property.",

查看文件

@ -43,6 +43,12 @@
"sesp-property-approved-status-desc": "«$1» определяет статус одобрения страницы. Предоставляется расширением [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties].",
"sesp-property-user-registration-date": "Дата регистрации пользователя",
"sesp-property-user-edit-count": "Число правок участника",
"sesp-property-user-edit-count-per-ns": "Число правок участника в пространстве имён",
"sesp-property-user-edit-count-per-ns-desc": "\"$1\" содержит число правок участника в определённом пространстве имён. Предоставляется расширением [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties].",
"sesp-property-user-edit-count-ns": "Пространство имён",
"sesp-property-user-edit-count-ns-desc": "\"$1\" содержит номер пространства имён в записи о числе правок в пространстве имён. Предоставляется расширением [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties].",
"sesp-property-user-edit-count-cnt": "Число правок",
"sesp-property-user-edit-count-cnt-desc": "\"$1\" содержит число правок в записи о пространстве имён. Предоставляется расширением [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties].",
"sesp-property-user-block": "Блокировка участника",
"sesp-property-user-block-desc": "«$1» предоставляет список блоков, применяемых к пользователю. Предоставляется расширением [https://www.semantic-mediawiki.org/wiki/Extension:Semantic_Extra_Special_Properties Semantic Extra Special Properties]",
"sesp-property-user-right": "Право участника",

查看文件

@ -87,7 +87,7 @@ class HookRegistry {
$exemptionlist = [
'___EUSER', '___CUSER', '___SUBP', '___REVID', '___VIEWS',
'___NREV', '___NTREV', '___USEREDITCNT', '___EXIFDATA', '___NSID', '___NSNAME'
'___NREV', '___NTREV', '___USEREDITCNT', '___EXIFDATA', '___NSID', '___NSNAME', '___NAMESPACE'
];
// Exclude listed properties from indexing

查看文件

@ -143,6 +143,10 @@ class DispatchingPropertyAnnotator implements PropertyAnnotator {
return new UserEditCountPropertyAnnotator( $appFactory );
},
UserEditCountPerNsPropertyAnnotator::PROP_ID => function( $appFactory ) {
return new UserEditCountPerNsPropertyAnnotator( $appFactory );
},
UserBlockPropertyAnnotator::PROP_ID => function( $appFactory ) {
return new UserBlockPropertyAnnotator( $appFactory );
},

查看文件

@ -0,0 +1,137 @@
<?php
namespace SESP\PropertyAnnotators;
use SESP\PropertyAnnotator;
use SESP\AppFactory;
use SMW\DIProperty;
use SMW\DIWikiPage;
use SMW\SemanticData;
use SMWContainerSemanticData as ContainerSemanticData;
use SMWDataItem as DataItem;
use SMWDIContainer as DIContainer;
use SMWDINumber as DINumber;
use User;
use Wikimedia\IPUtils;
/**
* @private
* @ingroup SESP
*
* @license GNU GPL v2+
*
* @author Alexander Mashin
*/
class UserEditCountPerNsPropertyAnnotator implements PropertyAnnotator {
/**
* Predefined property IDs
*/
/** @const string PROP_ID ID for the whole record. */
public const PROP_ID = '___USEREDITCNTNS';
/** @const string PROP_NS_ID ID for the NS number in the record. */
private const PROP_NS_ID = '___USEREDITCNTNS_NS';
/** @const string PROP_CNT_ID ID for the edit count in the record. */
private const PROP_CNT_ID = '___USEREDITCNTNS_CNT';
/** @var DIProperty $nsProperty DIProperty object for namespace number. */
private static $nsProperty;
/** @var DIProperty $editsProperty DIProperty object for number if edits in NS. */
private static $editsProperty;
/** @var AppFactory $appFactory */
private $appFactory;
/**
* @param AppFactory $appFactory
*/
public function __construct( AppFactory $appFactory ) {
$this->appFactory = $appFactory;
self::$nsProperty = self::$nsProperty ?: new DIProperty( self::PROP_NS_ID );
self::$editsProperty = self::$editsProperty ?: new DIProperty( self::PROP_CNT_ID );
}
/**
* {@inheritDoc}
*/
public function isAnnotatorFor( DIProperty $property ) {
return $property->getKey() === self::PROP_ID;
}
/**
* @inheritDoc
*/
public function addAnnotation( DIProperty $property, SemanticData $semanticData ) {
$subject = $semanticData->getSubject();
$title = $subject->getTitle();
if ( !$title->inNamespace( NS_USER ) ) {
return;
}
$user = $this->appFactory->newUserFromTitle( $title );
$id = null;
$ip = null;
if ( $user instanceof User && ( $count = $user->getEditCount() ) && is_int( $count ) ) {
$id = $user->getID();
} elseif ( IPUtils::isIPAddress( $title->getText() ) ) {
$ip = $title->getText();
} else {
return;
}
$stats = $this->getEditsPerNs( $id, $ip );
foreach ( $stats as $ns => $edits ) {
$dataItem = self::container( $subject, $ns, $edits );
if ( $dataItem instanceof DataItem ) {
$semanticData->addPropertyObjectValue( $property, $dataItem );
}
}
}
/**
* Form a DIContainer holding namespace and number of edits.
*
* @param DIWikiPage $subject User page
* @param int $ns Namespace
* @param int $edits Number of edits
* @return DIContainer
*/
public static function container( DIWikiPage $subject, $ns, $edits ): DIContainer {
$container = new ContainerSemanticData( new DIWikiPage(
$subject->getDBkey(),
$subject->getNamespace(),
$subject->getInterwiki(),
'_USEREDITCNTNS' . (string)$ns
), false );
$container->addPropertyObjectValue( self::$nsProperty, new DINumber( $ns ) );
$container->addPropertyObjectValue( self::$editsProperty, new DINumber( $edits ) );
return new DIContainer( $container );
}
/**
* @param int|null $id User ID (0 for anonymous users)
* @param string|null $ip Anonymous user's IP address
* @return int[] An associative array NS number => revision count
*/
private function getEditsPerNs( $id, $ip ): array {
$db = $this->appFactory->getConnection();
$result = $db->select(
[ 'revision', 'revision_actor_temp', 'actor', 'page' ], // FROM.
[ 'ns' => 'page.page_namespace', 'edits' => 'COUNT(revision.rev_id)' ], // SELECT.
$id === null ? [ 'actor.actor_name' => $ip ] : [ 'actor.actor_user' => $id ], // WHERE.
__METHOD__,
[ 'GROUP BY' => [ 'page.page_namespace' ] ], // GROUP BY.
[ // JOIN conditions.
'page' => [ 'INNER JOIN', ['page.page_id=revision.rev_page'] ],
'revision_actor_temp' => [ 'INNER JOIN', ['revision_actor_temp.revactor_rev=revision.rev_id'] ],
'actor' => [ 'INNER JOIN', ['actor.actor_id=revision_actor_temp.revactor_actor'] ]
]
);
$records = [];
foreach ( $result as $row ) {
$records[$row->ns] = $row->edits;
}
return $records;
}
}

查看文件

@ -16,6 +16,7 @@ use SESP\PropertyAnnotators\PageViewsPropertyAnnotator;
use SESP\PropertyAnnotators\LocalPropertyAnnotator;
use SESP\PropertyAnnotators\UserRegistrationDatePropertyAnnotator;
use SESP\PropertyAnnotators\UserEditCountPropertyAnnotator;
use SESP\PropertyAnnotators\UserEditCountPerNsPropertyAnnotator;
use SESP\PropertyAnnotators\UserBlockPropertyAnnotator;
use SESP\PropertyAnnotators\UserRightPropertyAnnotator;
use SESP\PropertyAnnotators\UserGroupPropertyAnnotator;
@ -163,6 +164,11 @@ class DispatchingPropertyAnnotatorTest extends \PHPUnit_Framework_TestCase {
UserEditCountPropertyAnnotator::class
];
$provider[] = [
UserEditCountPerNsPropertyAnnotator::PROP_ID,
UserEditCountPerNsPropertyAnnotator::class
];
$provider[] = [
UserBlockPropertyAnnotator::PROP_ID,
UserBlockPropertyAnnotator::class

查看文件

@ -0,0 +1,220 @@
<?php
namespace SESP\Tests\PropertyAnnotators;
use SESP\AppFactory;
use SESP\PropertyAnnotators\UserEditCountPerNsPropertyAnnotator;
use SMWDIContainer;
use SMW\DIProperty;
use SMW\DIWikiPage;
use SMW\SemanticData;
use User;
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\FakeResultWrapper;
/**
* @covers \SESP\PropertyAnnotators\UserEditCountPerNsPropertyAnnotator
* @group semantic-extra-special-properties
* @group Databasa
*
* @license GNU GPL v2+
*
* @author Alexander Mashin
*/
class UserEditCountPerNsPropertyAnnotatorTest extends \PHPUnit_Framework_TestCase {
/** @var DIProperty $property */
private $property;
/** @var AppFactory $appFactory */
private $appFactory;
protected function setUp(): void {
parent::setUp();
$this->appFactory = $this->getMockBuilder( AppFactory::class )
->disableOriginalConstructor()
->getMock();
$this->property = new DIProperty( '___USEREDITCNTNS' );
}
public function testIsAnnotatorFor() {
$instance = new UserEditCountPerNsPropertyAnnotator( $this->appFactory );
$this->assertTrue(
$instance->isAnnotatorFor( $this->property )
);
}
/**
* @dataProvider getEditsPerNsProvider
* @param int $id User ID.
* @param string $ip Anonymous user's IP address.
* @param array $expected Expected result.
*/
public function testGetEditsPerNs( $id, $ip, array $expected ) {
// Mock the database.
$db = $this->getMockBuilder( Database::class )
->disableOriginalConstructor()
->getMock();
$fake = [];
foreach ( $expected as $ns => $edits ) {
$fake[] = [ 'ns' => $ns, 'edits' => $edits ];
}
$db->method( 'select' )
->willReturn( new FakeResultWrapper( $fake ) );
// Mock the application factory (only for the mock database).
$factory = $this->appFactory;
$factory->method( 'getConnection' )
->willReturn( $db );
$annotator = new UserEditCountPerNsPropertyAnnotator( $factory );
// Expose the private method getEditsPerNs().
$reflector = new \ReflectionObject( $annotator );
$method = $reflector->getMethod( 'getEditsPerNs' );
$method->setAccessible ( true );
// Call the tested method.
$stats = $method->invokeArgs( $annotator, [ $id, $ip ] );
$this->assertEquals( $expected, $stats );
}
/**
* @return array
*/
public function getEditsPerNsProvider(): array {
$user = $this->getMockBuilder( User::class )
->disableOriginalConstructor()
->getMock();
$id = $user->getId();
$provider = [];
// Registered user.
$provider[] = [ $id, '', [ 0 => 42, 1 => 21 ] ];
// Anonymous user.
$provider[] = [ 0, '127.0.0.1', [ 0 => 84, 1 => 42 ] ];
return $provider;
}
/**
* @dataProvider containerProvider
* @param int $ns Namespace
* @param int $edits Number of edits
* @param $expected
*/
public function testContainer( $ns, $edits ) {
$subject = new DIWikiPage ( 'Test', NS_USER );
$annotator = new UserEditCountPerNsPropertyAnnotator( $this->appFactory );
// Expose the private method container().
$reflector = new \ReflectionObject( $annotator );
$method = $reflector->getMethod( 'container' );
$method->setAccessible ( true );
$container = $method->invokeArgs( $annotator, [ $subject, $ns, $edits ] );
$this->assertInstanceOf( SMWDIContainer::class, $container, 'Container is not an instance of SMWDIContainer' );
$data = $container->getSemanticData();
$properties = $data->getProperties();
$this->assertArrayHasKey( '___USEREDITCNTNS_NS', $properties, 'No namespace number in the container' );
$nsValue = $data->getPropertyValues(new DIProperty( '___USEREDITCNTNS_NS' ) )[0]->getNumber();
$this->assertEquals( $ns, $nsValue, 'Wrong namespace number' );
$this->assertArrayHasKey( '___USEREDITCNTNS_CNT', $properties, 'No edit cont in the container' );
$editsValue = $data->getPropertyValues(new DIProperty( '___USEREDITCNTNS_CNT' ) )[0]->getNumber();
$this->assertEquals( $edits, $editsValue, 'Wrong edit count' );
}
/**
* @return array
*/
public function containerProvider (): array {
return [
[ 0, 42 ],
[ 1, 21 ]
];
}
/**
* @dataProvider addAnnotationProvider
* @param string|null $name User name
* @param string|null $ip Anonymous iser's I address
* @param array $stats
*/
public function testAddAnnotation( $name, $ip, array $stats ) {
$subject = new DIWikiPage ( $name ?: $ip, NS_USER );
$semanticData = new SemanticData( $subject );
// Mock the database.
$db = $this->getMockBuilder( Database::class )
->disableOriginalConstructor()
->getMock();
$fake = [];
foreach ( $stats as $ns => $edits ) {
$fake[] = [ 'ns' => $ns, 'edits' => $edits ];
}
$db->method( 'select' )
->willReturn( new FakeResultWrapper( $fake ), new FakeResultWrapper( $fake ) );
// Mock the application factory; only for the database.
$factory = $this->appFactory;
$factory->method( 'getConnection' )
->willReturn( $db );
$user = $this->getMockBuilder( User::class )
->disableOriginalConstructor()
->getMock();
$user->method( 'getId' )
->willReturn( 1 );
$user->expects( $this->once() )
->method( 'getEditCount' )
->willReturn( array_sum( $stats ) );
$factory = $this->appFactory;
$this->appFactory->expects( $this->once() )
->method( 'newUserFromTitle' )
->willReturn( $user );
$annotator = new UserEditCountPerNsPropertyAnnotator( $factory );
$annotator->addAnnotation( $this->property, $semanticData );
$properties = $semanticData->getProperties();
if ( count( $stats ) > 0 ) {
$this->assertArrayHasKey( '___USEREDITCNTNS', $properties, 'No edit count record for the page' );
$records = $semanticData->getPropertyValues( new DIProperty( '___USEREDITCNTNS' ) );
foreach ( $records as $record ) {
$this->assertInstanceOf( DIWikiPage::class, $record );
}
$actual = [];
foreach ( $semanticData->getSubSemanticData() as $subSemanticDatum ) {
$properties = $subSemanticDatum->getProperties();
$this->assertArrayHasKey( '___USEREDITCNTNS_NS', $properties, 'No namespace number for the record' );
$ns = $subSemanticDatum->getPropertyValues( $properties['___USEREDITCNTNS_NS'] )[0]->getNumber();
$this->assertArrayHasKey( '___USEREDITCNTNS_CNT', $properties, 'No edit count for the record' );
$edits = $subSemanticDatum->getPropertyValues( $properties['___USEREDITCNTNS_CNT'] )[0]->getNumber();
$actual[$ns] = $edits;
}
$this->assertEquals( $stats, $actual, 'Edit count arrays mismatch' );
}
}
/**
* @return array
*/
public function addAnnotationProvider(): array {
$provider = [];
$provider[] = [ 'Test user', null, [ 0 => 42, 1 => 2, 8 => 7 ] ];
$provider[] = [ null, '127.0.0.1', [ 0 => 84, 8 => 21 ] ];
$provider[] = [ null, '127.0.0.1', [] ];
return $provider;
}
}