Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ResultsFormatter
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
6 / 6
15
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 formatResults
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 formatRow
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 formatRowProperty
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
7
 getOverlapMessageKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 msg
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\SimilarEditors;
4
5use Html;
6use Language;
7use Linker;
8use MediaWiki\User\UserFactory;
9use MediaWiki\User\UserRigorOptions;
10use Message;
11
12class ResultsFormatter {
13    /**
14     * Properties, corresponding to table columns
15     */
16    private const PROPERTIES = [
17        'user',
18        'day-overlap',
19        'hour-overlap',
20        'edit-overlap',
21        'inverse-edit-overlap',
22        'edits',
23    ];
24
25    /** @var UserFactory */
26    private $userFactory;
27
28    /** @var Language */
29    private $language;
30
31    /**
32     * @param UserFactory $userFactory
33     * @param Language $language
34     */
35    public function __construct(
36        UserFactory $userFactory,
37        Language $language
38    ) {
39        $this->userFactory = $userFactory;
40        $this->language = $language;
41    }
42
43    /**
44     * @param string $target
45     * @param Neighbor[] $neighbors
46     * @return string
47     */
48    public function formatResults( string $target, array $neighbors ): string {
49        $headCells = '';
50        foreach ( self::PROPERTIES as $property ) {
51            // For grepping. The following messages can be used here:
52            // * similareditors-results-user
53            // * similareditors-results-day-overlap
54            // * similareditors-results-hour-overlap
55            // * similareditors-results-edit-overlap
56            // * similareditors-results-inverse-edit-overlap
57            // * similareditors-results-edits
58            $headCellText = $this->msg( 'similareditors-results-' . $property )->parse();
59            $headCells .= Html::rawElement( 'th', [], $headCellText );
60        }
61        $headRow = Html::rawElement( 'tr', [], $headCells );
62        $head = Html::rawElement( 'thead', [], $headRow );
63
64        $bodyRows = '';
65        foreach ( $neighbors as $neighbor ) {
66            $bodyRows .= $this->formatRow( $target, $neighbor );
67        }
68        $body = Html::rawElement( 'tbody', [], $bodyRows );
69
70        return Html::rawElement(
71            'table',
72            [ 'class' => 'mw-datatable sortable' ],
73            $head . $body
74        );
75    }
76
77    /**
78     * @param string $target
79     * @param Neighbor $neighbor
80     * @return string
81     */
82    private function formatRow( string $target, Neighbor $neighbor ): string {
83        $row = Html::openElement( 'tr', [] );
84
85        foreach ( self::PROPERTIES as $property ) {
86            $row .= Html::rawElement( 'td', [], $this->formatRowProperty( $target, $neighbor, $property ) ) . "\n";
87        }
88
89        $row .= Html::closeElement( 'tr' );
90
91        return $row;
92    }
93
94    /**
95     * @param string $target
96     * @param Neighbor $neighbor
97     * @param string $property
98     * @return string
99     */
100    private function formatRowProperty(
101        string $target,
102        Neighbor $neighbor,
103        string $property
104    ): string {
105        switch ( $property ) {
106            case 'user':
107                $user = $this->userFactory->newFromName(
108                    $neighbor->getUserText(),
109                    // May be an IP address
110                    UserRigorOptions::RIGOR_NONE
111                );
112                // TODO: revert as part of T309675
113                return Linker::userLink( 0, 'en>' . $user->getName() ) .
114                    ' ' . $this->msg( 'parentheses-start' ) .
115                    // @phan-suppress-next-line SecurityCheck-DoubleEscaped
116                    Linker::makeExternalLink(
117                        'https://interaction-timeline.toolforge.org/' .
118                            '?wiki=enwiki&user=' . urlencode( $target ) .
119                            '&user=' . urlencode( $user->getName() ),
120                        $this->msg( 'similareditors-results-user-timeline' )->parse(),
121                        false
122                    ) .
123                    $this->msg( 'parentheses-end' );
124            case 'day-overlap':
125                return $this->msg(
126                    $this->getOverlapMessageKey( $neighbor->getDayOverlap()->getLevel() )
127                )->parse();
128            case 'hour-overlap':
129                return $this->msg(
130                    $this->getOverlapMessageKey( $neighbor->getHourOverlap()->getLevel() )
131                )->parse();
132            case 'edit-overlap':
133                return (string)$neighbor->getEditOverlap();
134            case 'inverse-edit-overlap':
135                return (string)$neighbor->getEditOverlapInv();
136            case 'edits':
137                return (string)$neighbor->getNumEditsInData();
138        }
139    }
140
141    /**
142     * @param string $level
143     * @return string
144     */
145    private function getOverlapMessageKey( string $level ): string {
146        // For grepping. The following messages can be used here:
147        // * similareditors-overlap-level-no-overlap
148        // * similareditors-overlap-level-low
149        // * similareditors-overlap-level-medium
150        // * similareditors-overlap-level-high
151        return 'similareditors-overlap-level-' . strtolower( str_replace( ' ', '-', $level ) );
152    }
153
154    /**
155     * @param string $key
156     * @param array $params
157     * @return Message
158     */
159    private function msg( string $key, array $params = [] ): Message {
160        return new Message( $key, $params, $this->language );
161    }
162}