Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.72% covered (success)
96.72%
59 / 61
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecsFormatter
96.72% covered (success)
96.72%
59 / 61
87.50% covered (warning)
87.50%
7 / 8
24
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMessageLocalizer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getActionDisplay
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getActionMessage
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 formatAction
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
1 / 1
12
 formatFlags
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 formatFilterFlags
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 nameGroup
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter;
4
5use MediaWiki\Extension\AbuseFilter\Filter\AbstractFilter;
6use MediaWiki\Language\Language;
7use MediaWiki\Language\RawMessage;
8use MediaWiki\Message\Message;
9use MessageLocalizer;
10
11/**
12 * @todo Improve this once DI around Message objects is improved in MW core.
13 */
14class SpecsFormatter {
15    public const SERVICE_NAME = 'AbuseFilterSpecsFormatter';
16
17    public function __construct( private MessageLocalizer $messageLocalizer ) {
18    }
19
20    public function setMessageLocalizer( MessageLocalizer $messageLocalizer ): void {
21        $this->messageLocalizer = $messageLocalizer;
22    }
23
24    /**
25     * @param string $action
26     * @return string HTML
27     * @todo Replace usage with getActionMessage
28     */
29    public function getActionDisplay( string $action ): string {
30        // Give grep a chance to find the usages:
31        // abusefilter-action-tag, abusefilter-action-throttle, abusefilter-action-warn,
32        // abusefilter-action-blockautopromote, abusefilter-action-block, abusefilter-action-degroup,
33        // abusefilter-action-rangeblock, abusefilter-action-disallow
34        $msg = $this->messageLocalizer->msg( "abusefilter-action-$action" );
35        return $msg->isDisabled() ? htmlspecialchars( $action ) : $msg->escaped();
36    }
37
38    public function getActionMessage( string $action ): Message {
39        // Give grep a chance to find the usages:
40        // abusefilter-action-tag, abusefilter-action-throttle, abusefilter-action-warn,
41        // abusefilter-action-blockautopromote, abusefilter-action-block, abusefilter-action-degroup,
42        // abusefilter-action-rangeblock, abusefilter-action-disallow
43        $msg = $this->messageLocalizer->msg( "abusefilter-action-$action" );
44        // XXX Why do we expect the message to be disabled?
45        return $msg->isDisabled() ? new RawMessage( $action ) : $msg;
46    }
47
48    /**
49     * @param string $action
50     * @param string[] $parameters
51     * @param Language $lang
52     * @return string
53     */
54    public function formatAction( string $action, array $parameters, Language $lang ): string {
55        if ( count( $parameters ) === 0 || ( $action === 'block' && count( $parameters ) !== 3 ) ) {
56            $displayAction = $this->getActionDisplay( $action );
57        } elseif ( $action === 'block' ) {
58            // Needs to be treated separately since the message is more complex
59            $messages = [
60                $this->messageLocalizer->msg( 'abusefilter-block-anon' )->escaped() .
61                $this->messageLocalizer->msg( 'colon-separator' )->escaped() .
62                $lang->translateBlockExpiry( $parameters[1] ),
63                $this->messageLocalizer->msg( 'abusefilter-block-user' )->escaped() .
64                $this->messageLocalizer->msg( 'colon-separator' )->escaped() .
65                $lang->translateBlockExpiry( $parameters[2] )
66            ];
67            if ( $parameters[0] === 'blocktalk' ) {
68                $messages[] = $this->messageLocalizer->msg( 'abusefilter-block-talk' )->escaped();
69            }
70            $displayAction = $lang->commaList( $messages );
71        } elseif ( $action === 'throttle' ) {
72            array_shift( $parameters );
73            [ $actions, $time ] = explode( ',', array_shift( $parameters ) );
74
75            // Join comma-separated groups in a commaList with a final "and", and convert to messages.
76            // Messages used here: abusefilter-throttle-ip, abusefilter-throttle-user,
77            // abusefilter-throttle-site, abusefilter-throttle-creationdate, abusefilter-throttle-editcount
78            // abusefilter-throttle-range, abusefilter-throttle-page, abusefilter-throttle-none
79            foreach ( $parameters as &$val ) {
80                if ( str_contains( $val, ',' ) ) {
81                    $subGroups = explode( ',', $val );
82                    foreach ( $subGroups as &$group ) {
83                        $msg = $this->messageLocalizer->msg( "abusefilter-throttle-$group" );
84                        // We previously accepted literally everything in this field, so old entries
85                        // may have weird stuff.
86                        $group = $msg->exists() ? $msg->text() : $group;
87                    }
88                    unset( $group );
89                    $val = $lang->listToText( $subGroups );
90                } else {
91                    $msg = $this->messageLocalizer->msg( "abusefilter-throttle-$val" );
92                    $val = $msg->exists() ? $msg->text() : $val;
93                }
94            }
95            unset( $val );
96            $groups = $lang->semicolonList( $parameters );
97
98            $displayAction = $this->getActionDisplay( $action ) .
99                $this->messageLocalizer->msg( 'colon-separator' )->escaped() .
100                $this->messageLocalizer->msg( 'abusefilter-throttle-details' )
101                    ->params( $actions, $time, $groups )->escaped();
102        } else {
103            $displayAction = $this->getActionDisplay( $action ) .
104                $this->messageLocalizer->msg( 'colon-separator' )->escaped() .
105                $lang->semicolonList( array_map( 'htmlspecialchars', $parameters ) );
106        }
107
108        return $displayAction;
109    }
110
111    /**
112     * @param string $value
113     * @param Language $lang
114     * @return string
115     */
116    public function formatFlags( string $value, Language $lang ): string {
117        $flags = array_filter( explode( ',', $value ) );
118        $flagsDisplay = [];
119        foreach ( $flags as $flag ) {
120            $flagsDisplay[] = $this->messageLocalizer->msg( "abusefilter-history-$flag" )->escaped();
121        }
122
123        return $lang->commaList( $flagsDisplay );
124    }
125
126    /**
127     * @param AbstractFilter $filter
128     * @param Language $lang
129     * @return string
130     */
131    public function formatFilterFlags( AbstractFilter $filter, Language $lang ): string {
132        $flags = array_filter( [
133            'enabled' => $filter->isEnabled(),
134            'deleted' => $filter->isDeleted(),
135            'hidden' => $filter->isHidden(),
136            'protected' => $filter->isProtected(),
137            'global' => $filter->isGlobal()
138        ] );
139        $flagsDisplay = [];
140        foreach ( $flags as $flag => $_ ) {
141            // The following messages are generated here:
142            // * abusefilter-history-enabled
143            // * abusefilter-history-deleted
144            // * abusefilter-history-hidden
145            // * abusefilter-history-protected
146            // * abusefilter-history-global
147            $flagsDisplay[] = $this->messageLocalizer->msg( "abusefilter-history-$flag" )->escaped();
148        }
149
150        return $lang->commaList( $flagsDisplay );
151    }
152
153    /**
154     * Gives either the user-specified name for a group,
155     * or spits the input back out when the message for the group is disabled
156     * @param string $group The filter's group (as defined in $wgAbuseFilterValidGroups)
157     * @return string A name for that filter group, or the input.
158     */
159    public function nameGroup( string $group ): string {
160        // Give grep a chance to find the usages: abusefilter-group-default
161        $msg = $this->messageLocalizer->msg( "abusefilter-group-$group" );
162        return $msg->isDisabled() ? $group : $msg->escaped();
163    }
164}