Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.23% covered (warning)
69.23%
36 / 52
22.22% covered (danger)
22.22%
2 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserNameBatch
69.23% covered (warning)
69.23%
36 / 52
22.22% covered (danger)
22.22%
2 / 9
40.78
0.00% covered (danger)
0.00%
0 / 1
 __construct
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 ensureLRU
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 clear
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 add
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 addFromTuple
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 getFromTuple
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 resolve
89.47% covered (warning)
89.47%
17 / 19
0.00% covered (danger)
0.00%
0 / 1
6.04
 resolveUserPages
20.00% covered (danger)
20.00%
2 / 10
0.00% covered (danger)
0.00%
0 / 1
17.80
1<?php
2/**
3 * Provide usernames filtered by per-wiki ipblocks. Batches together
4 * database requests for multiple usernames when possible.
5 */
6namespace Flow\Repository;
7
8use Flow\Model\UserTuple;
9use MapCacheLRU;
10use MediaWiki\MediaWikiServices;
11use MediaWiki\User\User;
12use MediaWiki\WikiMap\WikiMap;
13
14/**
15 * Batch together queries for a bunch of wiki+userid -> username
16 */
17class UserNameBatch {
18    // Maximum number of usernames to cache for each wiki
19    private const USERNAMES_PER_WIKI = 250;
20
21    /**
22     * @var UserName\UserNameQuery
23     */
24    protected $query;
25
26    /**
27     * @var array[] map from wikiid to list of userid's to request
28     */
29    protected $queued = [];
30
31    /**
32     * @var array Map from wiki id to MapCacheLRU.  MapCacheLRU is a map of user ID (as
33     *  string, though to PHP it's the same anyway...) to username.
34     */
35    protected $usernames = [];
36
37    /**
38     * @param UserName\UserNameQuery $query
39     * @param array $queued map from wikiid to list of userid's to request
40     */
41    public function __construct( UserName\UserNameQuery $query, array $queued = [] ) {
42        $this->query = $query;
43        foreach ( $queued as $wiki => $userIds ) {
44            $this->queued[$wiki] = array_map( 'intval', $userIds );
45        }
46    }
47
48    /**
49     * Make sure the LRU for the given wiki is in place.
50     *
51     * @param string $wiki Wiki identifier
52     */
53    protected function ensureLRU( $wiki ) {
54        if ( !isset( $this->usernames[$wiki] ) ) {
55            $this->usernames[$wiki] = new MapCacheLRU( self::USERNAMES_PER_WIKI );
56        }
57    }
58
59    public function clear() {
60        $this->queued = [];
61        $this->usernames = [];
62    }
63
64    /**
65     * @param string $wiki
66     * @param int $userId
67     * @param string|null $userName Non null to set known usernames
68     */
69    public function add( $wiki, $userId, $userName = null ) {
70        $userId = (int)$userId;
71
72        $this->ensureLRU( $wiki );
73        if ( $userName !== null ) {
74            $this->usernames[$wiki]->set( (string)$userId, $userName );
75        } elseif ( !$this->usernames[$wiki]->has( (string)$userId ) ) {
76            $this->queued[$wiki][] = $userId;
77        }
78    }
79
80    /**
81     * @param UserTuple $tuple
82     */
83    public function addFromTuple( UserTuple $tuple ) {
84        $this->add( $tuple->wiki, $tuple->id, $tuple->ip );
85    }
86
87    /**
88     * Get the displayable username
89     *
90     * @param string $wiki
91     * @param int $userId
92     * @param string|bool $userIp
93     * @return string|bool false if username is not found or display is suppressed
94     * @todo Return something better for not found / suppressed, but what? Making
95     *   return type string|Message would suck.
96     */
97    public function get( $wiki, $userId, $userIp = false ) {
98        $userId = (int)$userId;
99        if ( $userId === 0 ) {
100            return $userIp;
101        }
102
103        $this->ensureLRU( $wiki );
104        if ( !$this->usernames[$wiki]->has( (string)$userId ) ) {
105            $this->queued[$wiki][] = $userId;
106            $this->resolve( $wiki );
107        }
108        return $this->usernames[$wiki]->get( (string)$userId );
109    }
110
111    /**
112     * @param UserTuple $tuple
113     * @return string|bool false if username is not found or display is suppressed
114     */
115    public function getFromTuple( UserTuple $tuple ) {
116        return $this->get( $tuple->wiki, $tuple->id, $tuple->ip );
117    }
118
119    /**
120     * Resolve all queued user ids to usernames for the given wiki
121     *
122     * @param string $wiki
123     */
124    public function resolve( $wiki ) {
125        if ( empty( $this->queued[$wiki] ) ) {
126            return;
127        }
128        $queued = array_unique( $this->queued[$wiki] );
129        if ( isset( $this->usernames[$wiki] ) ) {
130            $queued = array_diff( $queued, $this->usernames[$wiki]->getAllKeys() );
131        } else {
132            $this->ensureLRU( $wiki );
133        }
134
135        $res = $this->query->execute( $wiki, $queued );
136        unset( $this->queued[$wiki] );
137        if ( $res ) {
138            $usernames = [];
139            foreach ( $res as $row ) {
140                $id = (int)$row->user_id;
141                $usernames[$id] = $row->user_name;
142                $this->usernames[$wiki]->set( (string)$id, $row->user_name );
143            }
144            $this->resolveUserPages( $wiki, $usernames );
145            $missing = array_diff( $queued, array_keys( $usernames ) );
146        } else {
147            $missing = $queued;
148        }
149        foreach ( $missing as $id ) {
150            $this->usernames[$wiki]->set( (string)$id, false );
151        }
152    }
153
154    /**
155     * Update in-process title existence cache with NS_USER and
156     * NS_USER_TALK pages related to the provided usernames.
157     *
158     * @param string $wiki Wiki the users belong to
159     * @param array $usernames List of user names
160     */
161    protected function resolveUserPages( $wiki, array $usernames ) {
162        // LinkBatch currently only supports the current wiki
163        if ( $wiki !== WikiMap::getCurrentWikiId() || !$usernames ) {
164            return;
165        }
166
167        $lb = MediaWikiServices::getInstance()->getLinkBatchFactory()->newLinkBatch();
168        foreach ( $usernames as $name ) {
169            $user = User::newFromName( $name );
170            if ( $user ) {
171                $lb->addObj( $user->getUserPage() );
172                $lb->addObj( $user->getTalkPage() );
173            }
174        }
175        $lb->setCaller( __METHOD__ );
176        $lb->execute();
177    }
178}