Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.49% covered (success)
91.49%
43 / 47
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
FindPotentialInvitees
93.48% covered (success)
93.48%
43 / 46
33.33% covered (danger)
33.33%
1 / 3
10.03
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 execute
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
4.00
 getArticlesByWiki
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
5.05
1<?php
2
3declare( strict_types=1 );
4
5namespace MediaWiki\Extension\CampaignEvents\Maintenance;
6
7use MediaWiki\Extension\CampaignEvents\CampaignEventsServices;
8use MediaWiki\Maintenance\Maintenance;
9use 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 */
19class 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
103return FindPotentialInvitees::class;