Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
91.49% |
43 / 47 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
FindPotentialInvitees | |
93.48% |
43 / 46 |
|
33.33% |
1 / 3 |
10.03 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
94.12% |
16 / 17 |
|
0.00% |
0 / 1 |
4.00 | |||
getArticlesByWiki | |
87.50% |
14 / 16 |
|
0.00% |
0 / 1 |
5.05 |
1 | <?php |
2 | |
3 | declare( strict_types=1 ); |
4 | |
5 | namespace MediaWiki\Extension\CampaignEvents\Maintenance; |
6 | |
7 | use MediaWiki\Extension\CampaignEvents\CampaignEventsServices; |
8 | use MediaWiki\Maintenance\Maintenance; |
9 | use MediaWiki\WikiMap\WikiMap; |
10 | |
11 | /** |
12 | * This scripts takes a list of pages as input, and outputs a list of users who've edited those pages the most. Each |
13 | * user is given a score from 0 to 100 based on how likely they are to be a productive participant in an event that is |
14 | * focused on improving the given pages. This score is based on how many edits someone made (and how big they are) to |
15 | * the pages in the list, their global edit count, and their recent global activity. |
16 | * NOTE: This script is just a demo / proof of concept. It is not based on any real-world data and the calculations |
17 | * are very much non-rigorous. |
18 | */ |
19 | class FindPotentialInvitees extends Maintenance { |
20 | /** |
21 | * How many days to look back into the past when scanning revisions. |
22 | * TODO: Is 3 years OK? |
23 | */ |
24 | public const CUTOFF_DAYS = 3 * 365; |
25 | |
26 | public function __construct() { |
27 | parent::__construct(); |
28 | $this->addDescription( |
29 | 'Generates a list of potential event participants by looking at who contributed to a given list of pages' |
30 | ); |
31 | $this->requireExtension( 'CampaignEvents' ); |
32 | $this->addOption( |
33 | 'listfile', |
34 | 'Path to a file with a list of articles to get contributors for. The file should have one page per ' . |
35 | 'line, in the following format: `[wikiID, or empty for the local wiki]:[page title]`. All the pages ' . |
36 | 'must be in the mainspace.', |
37 | true, |
38 | true |
39 | ); |
40 | } |
41 | |
42 | /** |
43 | * @inheritDoc |
44 | */ |
45 | public function execute(): void { |
46 | $finder = CampaignEventsServices::getPotentialInviteesFinder(); |
47 | $finder->setDebugLogger( function ( string $msg ): void { |
48 | $this->output( $msg . "\n" ); |
49 | } ); |
50 | |
51 | $pageNamesByWiki = $this->getArticlesByWiki(); |
52 | $this->output( "==Articles==\n" ); |
53 | foreach ( $pageNamesByWiki as $wiki => $pageNames ) { |
54 | $this->output( "===$wiki===\n" . implode( "\n", $pageNames ) ); |
55 | } |
56 | $this->output( "\n\n" ); |
57 | |
58 | $worklistStatus = CampaignEventsServices::getWorklistParser()->parseWorklist( $pageNamesByWiki ); |
59 | if ( !$worklistStatus->isGood() ) { |
60 | $this->fatalError( $worklistStatus ); |
61 | } |
62 | $invitationList = $finder->generate( $worklistStatus->getValue() ); |
63 | $out = "\n==Contributor scores==\n"; |
64 | foreach ( $invitationList as $username => $score ) { |
65 | $out .= "$username - $score\n"; |
66 | } |
67 | $this->output( $out . "\n\n" ); |
68 | } |
69 | |
70 | /** |
71 | * Reads a list of articles from the file passed as `listfile` to the script. |
72 | * |
73 | * @return string[][] Map of [ wiki ID => non-empty list of articles ] |
74 | * @phan-return non-empty-array<string,non-empty-list<string>> |
75 | */ |
76 | private function getArticlesByWiki(): array { |
77 | $listPath = $this->getOption( 'listfile' ); |
78 | // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged |
79 | $rawList = @file_get_contents( $listPath ); |
80 | if ( $rawList === false ) { |
81 | $this->fatalError( "Cannot read list of articles" ); |
82 | } |
83 | |
84 | $listLines = array_filter( explode( "\n", $rawList ), static fn ( string $line ) => $line !== '' ); |
85 | |
86 | $curWikiID = WikiMap::getCurrentWikiId(); |
87 | $pageNamesByWiki = []; |
88 | foreach ( $listLines as $line ) { |
89 | $lineParts = explode( ':', $line, 2 ); |
90 | if ( count( $lineParts ) !== 2 ) { |
91 | $this->fatalError( "Line without wiki ID: $line" ); |
92 | } |
93 | $wikiID = $lineParts[0] === '' ? $curWikiID : $lineParts[0]; |
94 | $title = $lineParts[1]; |
95 | $pageNamesByWiki[$wikiID] ??= []; |
96 | $pageNamesByWiki[$wikiID][] = $title; |
97 | } |
98 | |
99 | return $pageNamesByWiki; |
100 | } |
101 | } |
102 | |
103 | return FindPotentialInvitees::class; |