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