Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.52% covered (warning)
89.52%
111 / 124
75.00% covered (warning)
75.00%
9 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
RCCacheEntryFactory
90.24% covered (success)
90.24%
111 / 123
75.00% covered (warning)
75.00%
9 / 12
29.78
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 newFromRecentChange
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
3
 buildCLink
66.67% covered (warning)
66.67%
8 / 12
0.00% covered (danger)
0.00%
0 / 1
4.59
 getLogLink
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 buildTimestamp
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 buildCurQueryParams
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 buildCurLink
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 buildDiffQueryParams
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 buildDiffLink
61.11% covered (warning)
61.11%
11 / 18
0.00% covered (danger)
0.00%
0 / 1
6.47
 buildLastLink
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 getUserLink
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
 getMessage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\RecentChanges;
8
9use MediaWiki\Context\IContextSource;
10use MediaWiki\Linker\Linker;
11use MediaWiki\Linker\LinkRenderer;
12use MediaWiki\Linker\UserLinkRenderer;
13use MediaWiki\Logging\LogPage;
14use MediaWiki\Revision\RevisionRecord;
15use MediaWiki\SpecialPage\SpecialPage;
16use MediaWiki\Title\Title;
17use MediaWiki\User\ExternalUserNames;
18use Wikimedia\HtmlArmor\HtmlArmor;
19use Wikimedia\MapCacheLRU\MapCacheLRU;
20
21/**
22 * Create a RCCacheEntry from a RecentChange to use in EnhancedChangesList
23 *
24 * @ingroup RecentChanges
25 */
26class RCCacheEntryFactory {
27
28    /** @var IContextSource */
29    private $context;
30
31    /** @var string[] */
32    private $messages;
33
34    /**
35     * @var LinkRenderer
36     */
37    private $linkRenderer;
38
39    private UserLinkRenderer $userLinkRenderer;
40
41    private MapCacheLRU $toolLinkCache;
42
43    /**
44     * @param IContextSource $context
45     * @param string[] $messages
46     * @param LinkRenderer $linkRenderer
47     * @param UserLinkRenderer $userLinkRenderer
48     */
49    public function __construct(
50        IContextSource $context,
51        $messages,
52        LinkRenderer $linkRenderer,
53        UserLinkRenderer $userLinkRenderer
54    ) {
55        $this->context = $context;
56        $this->messages = $messages;
57        $this->linkRenderer = $linkRenderer;
58        $this->userLinkRenderer = $userLinkRenderer;
59        $this->toolLinkCache = new MapCacheLRU( 50 );
60    }
61
62    /**
63     * @param RecentChange $baseRC
64     * @param bool $watched
65     *
66     * @return RCCacheEntry
67     */
68    public function newFromRecentChange( RecentChange $baseRC, $watched ) {
69        $user = $this->context->getUser();
70
71        $cacheEntry = RCCacheEntry::newFromParent( $baseRC );
72
73        // Should patrol-related stuff be shown?
74        $cacheEntry->unpatrolled = ChangesList::isUnpatrolled( $baseRC, $user );
75
76        $cacheEntry->watched = $cacheEntry->mAttribs['rc_source'] == RecentChange::SRC_LOG ? false : $watched;
77        $cacheEntry->numberofWatchingusers = $baseRC->numberofWatchingusers;
78        $cacheEntry->watchlistExpiry = $baseRC->watchlistExpiry;
79
80        $cacheEntry->link = $this->buildCLink( $cacheEntry );
81        $cacheEntry->timestamp = $this->buildTimestamp( $cacheEntry );
82
83        // Make "cur" and "diff" links.  Do not use link(), it is too slow if
84        // called too many times (50% of CPU time on RecentChanges!).
85        $showDiffLinks = ChangesList::userCan( $cacheEntry, RevisionRecord::DELETED_TEXT, $user );
86
87        $cacheEntry->difflink = $this->buildDiffLink( $cacheEntry, $showDiffLinks );
88        $cacheEntry->curlink = $this->buildCurLink( $cacheEntry, $showDiffLinks );
89        $cacheEntry->lastlink = $this->buildLastLink( $cacheEntry, $showDiffLinks );
90
91        // Make user links
92        $cacheEntry->userlink = $this->getUserLink( $cacheEntry );
93
94        if ( !ChangesList::isDeleted( $cacheEntry, RevisionRecord::DELETED_USER ) ) {
95            /**
96             * userToolLinks requires a lot of parser work to process multiple links that are
97             * rendered there, like contrib page, user talk etc. Often, active
98             * users will appear multiple times on same run of RecentChanges, and therefore it is
99             * unnecessary to process it for each RC record separately.
100             */
101            $cacheEntry->usertalklink = $this->toolLinkCache->getWithSetCallback(
102                $this->toolLinkCache->makeKey(
103                    $cacheEntry->mAttribs['rc_user_text'],
104                    $this->context->getUser()->getName(),
105                    $this->context->getLanguage()->getCode()
106                ),
107                static fn () => Linker::userToolLinks(
108                    $cacheEntry->mAttribs['rc_user'],
109                    $cacheEntry->mAttribs['rc_user_text'],
110                    // Should the contributions link be red if the user has no edits (using default)
111                    false,
112                    // Customisation flags (using default 0)
113                    0,
114                    // User edit count (using default )
115                    null,
116                    // do not wrap the message in parentheses
117                    false
118                )
119            );
120        }
121
122        return $cacheEntry;
123    }
124
125    /**
126     * @param RCCacheEntry $cacheEntry
127     *
128     * @return string
129     */
130    private function buildCLink( RCCacheEntry $cacheEntry ) {
131        $source = $cacheEntry->mAttribs['rc_source'];
132
133        // Log entries
134        if ( $source == RecentChange::SRC_LOG ) {
135            $logType = $cacheEntry->mAttribs['rc_log_type'];
136
137            if ( $logType ) {
138                $clink = $this->getLogLink( $logType );
139            } else {
140                wfDebugLog( 'recentchanges', 'Unexpected log entry with no log type in recent changes' );
141                $clink = $this->linkRenderer->makeLink( $cacheEntry->getTitle() );
142            }
143        // Log entries (old format) and special pages
144        } elseif ( $cacheEntry->mAttribs['rc_namespace'] == NS_SPECIAL ) {
145            wfDebugLog( 'recentchanges', 'Unexpected special page in recentchanges' );
146            $clink = '';
147        // Edits and everything else
148        } else {
149            $clink = $this->linkRenderer->makeKnownLink( $cacheEntry->getTitle() );
150        }
151
152        return $clink;
153    }
154
155    private function getLogLink( string $logType ): string {
156        $logtitle = SpecialPage::getTitleFor( 'Log', $logType );
157        $logpage = new LogPage( $logType );
158        $logname = $logpage->getName()->text();
159
160        $logLink = $this->context->msg( 'parentheses' )
161            ->rawParams(
162                $this->linkRenderer->makeKnownLink( $logtitle, $logname )
163            )->escaped();
164
165        return $logLink;
166    }
167
168    /**
169     * @param RecentChange $cacheEntry
170     *
171     * @return string
172     */
173    private function buildTimestamp( RecentChange $cacheEntry ) {
174        return $this->context->getLanguage()->userTime(
175            $cacheEntry->mAttribs['rc_timestamp'],
176            $this->context->getUser()
177        );
178    }
179
180    /**
181     * @param RecentChange $recentChange
182     *
183     * @return array
184     */
185    private function buildCurQueryParams( RecentChange $recentChange ) {
186        return [
187            'curid' => $recentChange->mAttribs['rc_cur_id'],
188            'diff' => 0,
189            'oldid' => $recentChange->mAttribs['rc_this_oldid']
190        ];
191    }
192
193    /**
194     * @param RecentChange $cacheEntry
195     * @param bool $showDiffLinks
196     *
197     * @return string
198     */
199    private function buildCurLink( RecentChange $cacheEntry, $showDiffLinks ) {
200        $curMessage = $this->getMessage( 'cur' );
201        $logTypes = [ RecentChange::SRC_LOG ];
202        if ( $cacheEntry->mAttribs['rc_this_oldid'] == $cacheEntry->getAttribute( 'page_latest' ) ) {
203            $showDiffLinks = false;
204        }
205
206        if ( !$showDiffLinks || in_array( $cacheEntry->mAttribs['rc_source'], $logTypes ) ) {
207            $curLink = $curMessage;
208        } else {
209            $queryParams = $this->buildCurQueryParams( $cacheEntry );
210            $curUrl = htmlspecialchars( $cacheEntry->getTitle()->getLinkURL( $queryParams ) );
211            $curLink = "<a class=\"mw-changeslist-diff-cur\" href=\"$curUrl\">$curMessage</a>";
212        }
213
214        return $curLink;
215    }
216
217    /**
218     * @param RecentChange $recentChange
219     *
220     * @return array
221     */
222    private function buildDiffQueryParams( RecentChange $recentChange ) {
223        return [
224            'curid' => $recentChange->mAttribs['rc_cur_id'],
225            'diff' => $recentChange->mAttribs['rc_this_oldid'],
226            'oldid' => $recentChange->mAttribs['rc_last_oldid']
227        ];
228    }
229
230    /**
231     * @param RecentChange $cacheEntry
232     * @param bool $showDiffLinks
233     *
234     * @return string
235     */
236    private function buildDiffLink( RecentChange $cacheEntry, $showDiffLinks ) {
237        $queryParams = $this->buildDiffQueryParams( $cacheEntry );
238        $diffMessage = $this->getMessage( 'diff' );
239        $logTypes = [ RecentChange::SRC_NEW, RecentChange::SRC_LOG ];
240
241        if ( !$showDiffLinks ) {
242            $diffLink = $diffMessage;
243        } elseif ( in_array( $cacheEntry->mAttribs['rc_source'], $logTypes ) ) {
244            $diffLink = $diffMessage;
245        } elseif ( $cacheEntry->getAttribute( 'rc_source' ) == RecentChange::SRC_CATEGORIZE ) {
246            $rcCurId = $cacheEntry->getAttribute( 'rc_cur_id' );
247            $pageTitle = Title::newFromID( $rcCurId );
248            if ( $pageTitle === null ) {
249                wfDebugLog( 'RCCacheEntryFactory', 'Could not get Title for rc_cur_id: ' . $rcCurId );
250                return $diffMessage;
251            }
252            $diffUrl = htmlspecialchars( $pageTitle->getLinkURL( $queryParams ) );
253            $diffLink = "<a class=\"mw-changeslist-diff\" href=\"$diffUrl\">$diffMessage</a>";
254        } else {
255            $diffUrl = htmlspecialchars( $cacheEntry->getTitle()->getLinkURL( $queryParams ) );
256            $diffLink = "<a class=\"mw-changeslist-diff\" href=\"$diffUrl\">$diffMessage</a>";
257        }
258
259        return $diffLink;
260    }
261
262    /**
263     * Builds the link to the previous version
264     *
265     * @param RecentChange $cacheEntry
266     * @param bool $showDiffLinks
267     *
268     * @return string
269     */
270    private function buildLastLink( RecentChange $cacheEntry, $showDiffLinks ) {
271        $lastOldid = $cacheEntry->mAttribs['rc_last_oldid'];
272        $lastMessage = $this->getMessage( 'last' );
273        $source = $cacheEntry->mAttribs['rc_source'];
274        $logTypes = [ RecentChange::SRC_LOG ];
275
276        // Make "last" link
277        if ( !$showDiffLinks || !$lastOldid || in_array( $source, $logTypes ) ) {
278            $lastLink = $lastMessage;
279        } else {
280            $lastLink = $this->linkRenderer->makeKnownLink(
281                $cacheEntry->getTitle(),
282                new HtmlArmor( $lastMessage ),
283                [ 'class' => 'mw-changeslist-diff' ],
284                $this->buildDiffQueryParams( $cacheEntry )
285            );
286        }
287
288        return $lastLink;
289    }
290
291    /**
292     * @param RecentChange $cacheEntry
293     *
294     * @return string
295     */
296    private function getUserLink( RecentChange $cacheEntry ) {
297        if ( ChangesList::isDeleted( $cacheEntry, RevisionRecord::DELETED_USER ) ) {
298            $deletedClass = 'history-deleted';
299            if ( ChangesList::isDeleted( $cacheEntry, RevisionRecord::DELETED_RESTRICTED ) ) {
300                $deletedClass .= ' mw-history-suppressed';
301            }
302            $userLink = ' <span class="' . $deletedClass . '">' .
303                $this->context->msg( 'rev-deleted-user' )->escaped() . '</span>';
304        } else {
305            return $this->linkRenderer->makeUserLink(
306                $cacheEntry->getPerformerIdentity(),
307                $this->context,
308                ExternalUserNames::getLocal( $cacheEntry->mAttribs['rc_user_text'] )
309            );
310        }
311
312        return $userLink;
313    }
314
315    /**
316     * @param string $key
317     *
318     * @return string
319     */
320    private function getMessage( $key ) {
321        return $this->messages[$key];
322    }
323
324}
325
326/** @deprecated class alias since 1.44 */
327class_alias( RCCacheEntryFactory::class, 'RCCacheEntryFactory' );