Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserLinker
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 5
72
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 generateUserLink
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 generateUserLinkWithFallback
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 getUserPagePath
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 preloadUserLinks
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3declare( strict_types=1 );
4
5namespace MediaWiki\Extension\CampaignEvents\MWEntity;
6
7use MediaWiki\Cache\LinkBatchFactory;
8use MediaWiki\Html\Html;
9use MediaWiki\Linker\Linker;
10use MediaWiki\Parser\Sanitizer;
11use Wikimedia\Message\IMessageFormatterFactory;
12use Wikimedia\Message\MessageValue;
13
14/**
15 * This class generates links to (global) user accounts.
16 */
17class UserLinker {
18    public const SERVICE_NAME = 'CampaignEventsUserLinker';
19
20    public const MODULE_STYLES = [
21        // Needed by Linker::userLink
22        'mediawiki.interface.helpers.styles',
23    ];
24
25    private CampaignsCentralUserLookup $centralUserLookup;
26    private IMessageFormatterFactory $messageFormatterFactory;
27    private LinkBatchFactory $linkBatchFactory;
28
29    /**
30     * @param CampaignsCentralUserLookup $centralUserLookup
31     * @param IMessageFormatterFactory $messageFormatterFactory
32     * @param LinkBatchFactory $linkBatchFactory
33     */
34    public function __construct(
35        CampaignsCentralUserLookup $centralUserLookup,
36        IMessageFormatterFactory $messageFormatterFactory,
37        LinkBatchFactory $linkBatchFactory
38    ) {
39        $this->centralUserLookup = $centralUserLookup;
40        $this->messageFormatterFactory = $messageFormatterFactory;
41        $this->linkBatchFactory = $linkBatchFactory;
42    }
43
44    /**
45     * Generates a link to the given user, if it can be found and is visible, throwing an exception otherwise.
46     *
47     * @param CentralUser $user
48     * @return string HTML
49     * @throws CentralUserNotFoundException
50     * @throws HiddenCentralUserException
51     * @note When using this method, make sure to add self::MODULE_STYLES to the output.
52     */
53    public function generateUserLink( CentralUser $user ): string {
54        $name = $this->centralUserLookup->getUserName( $user );
55        // HACK: Linker::userLink does not really need the user ID (T308000), so don't bother looking it up, which
56        // would be too slow (T345250).
57        // TODO: Here we'll generate a red link if the account does not exist locally. Is that OK? Could we maybe
58        // link to Special:CentralAuth (if CA is installed)?
59        return Linker::userLink( 1, $name );
60    }
61
62    /**
63     * Like ::generateUserLink, but returns placeholders instead of throwing an exception for users that
64     * cannot be found or are not visible.
65     *
66     * @param CentralUser $user
67     * @param string $langCode Used for localizing placeholders.
68     * @return string HTML
69     *
70     * @note When using this method, make sure to add self::MODULE_STYLES to the output, and to include the
71     * ext.campaignEvents.userlinks.styles.less file as well.
72     * @note This assumes that the given central user exists, or existed in the past. As such, if the account
73     * cannot be found it will consider it as being deleted.
74     * @fixme This must be kept in sync with ParticipantsManager.getDeletedOrNotFoundParticipantElement in JS
75     */
76    public function generateUserLinkWithFallback( CentralUser $user, string $langCode ): string {
77        try {
78            return $this->generateUserLink( $user );
79        } catch ( CentralUserNotFoundException $_ ) {
80            $msgFormatter = $this->messageFormatterFactory->getTextFormatter( $langCode );
81            return Html::element(
82                'span',
83                [ 'class' => 'ext-campaignevents-userlink-deleted' ],
84                $msgFormatter->format(
85                    MessageValue::new( 'campaignevents-userlink-deleted-user' )
86                )
87            );
88        } catch ( HiddenCentralUserException $_ ) {
89            $msgFormatter = $this->messageFormatterFactory->getTextFormatter( $langCode );
90            return Html::element(
91                'span',
92                [ 'class' => 'ext-campaignevents-userlink-hidden' ],
93                $msgFormatter->format(
94                    MessageValue::new( 'campaignevents-userlink-suppressed-user' )
95                )
96            );
97        }
98    }
99
100    /**
101     * @param CentralUser $centralUser
102     * @return string[]
103     * NOTE: Make sure that the user is not hidden before calling this method, or it will throw an exception.
104     * TODO: Remove this hack and replace with a proper javascript implementation of Linker::userLink
105     */
106    public function getUserPagePath( CentralUser $centralUser ): array {
107        $html = $this->generateUserLink( $centralUser );
108        $attribs = Sanitizer::decodeTagAttributes( $html );
109        return [
110            'path' => $attribs['href'] ?? '',
111            'title' => $attribs['title'] ?? '',
112            'classes' => $attribs['class'] ?? '',
113        ];
114    }
115
116    /**
117     * Preloads link data for linking to the user pages of the given users.
118     *
119     * @param string[] $usernames
120     */
121    public function preloadUserLinks( array $usernames ): void {
122        $lb = $this->linkBatchFactory->newLinkBatch();
123        foreach ( $usernames as $username ) {
124            $lb->add( NS_USER, $username );
125        }
126        $lb->setCaller( wfGetCaller() );
127        $lb->execute();
128    }
129}