Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
7.94% covered (danger)
7.94%
5 / 63
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
EditBoxBuilder
7.94% covered (danger)
7.94%
5 / 63
25.00% covered (danger)
25.00%
1 / 4
72.20
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
 buildEditBox
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
30
 getSuggestionsDropdown
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
6
 getEditorControls
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getEditBox
n/a
0 / 0
n/a
0 / 0
0
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter\EditBox;
4
5use MediaWiki\Extension\AbuseFilter\AbuseFilterPermissionManager;
6use MediaWiki\Extension\AbuseFilter\KeywordsManager;
7use MediaWiki\Html\Html;
8use MediaWiki\Output\OutputPage;
9use MediaWiki\Permissions\Authority;
10use MessageLocalizer;
11use OOUI\ButtonWidget;
12use OOUI\DropdownInputWidget;
13use OOUI\FieldLayout;
14use OOUI\FieldsetLayout;
15use OOUI\Widget;
16
17/**
18 * Base class for classes responsible for building filter edit boxes
19 */
20abstract class EditBoxBuilder {
21    /** @var AbuseFilterPermissionManager */
22    protected $afPermManager;
23
24    /** @var KeywordsManager */
25    protected $keywordsManager;
26
27    /** @var MessageLocalizer */
28    protected $localizer;
29
30    /** @var Authority */
31    protected $authority;
32
33    /** @var OutputPage */
34    protected $output;
35
36    /**
37     * @param AbuseFilterPermissionManager $afPermManager
38     * @param KeywordsManager $keywordsManager
39     * @param MessageLocalizer $messageLocalizer
40     * @param Authority $authority
41     * @param OutputPage $output
42     */
43    public function __construct(
44        AbuseFilterPermissionManager $afPermManager,
45        KeywordsManager $keywordsManager,
46        MessageLocalizer $messageLocalizer,
47        Authority $authority,
48        OutputPage $output
49    ) {
50        $this->afPermManager = $afPermManager;
51        $this->keywordsManager = $keywordsManager;
52        $this->localizer = $messageLocalizer;
53        $this->authority = $authority;
54        $this->output = $output;
55    }
56
57    /**
58     * @param string $rules
59     * @param bool $addResultDiv
60     * @param bool $externalForm
61     * @param bool $needsModifyRights
62     * @param-taint $rules none
63     * @return string
64     */
65    public function buildEditBox(
66        string $rules,
67        bool $addResultDiv = true,
68        bool $externalForm = false,
69        bool $needsModifyRights = true
70    ): string {
71        $this->output->addModules( 'ext.abuseFilter.edit' );
72        $this->output->enableOOUI();
73
74        $isUserAllowed = $needsModifyRights ?
75            $this->afPermManager->canEdit( $this->authority ) :
76            $this->afPermManager->canUseTestTools( $this->authority );
77        if ( !$isUserAllowed ) {
78            $addResultDiv = false;
79        }
80
81        $output = $this->getEditBox( $rules, $isUserAllowed, $externalForm );
82
83        if ( $isUserAllowed ) {
84            $dropdown = $this->getSuggestionsDropdown();
85
86            $formElements = [
87                new FieldLayout( $dropdown ),
88                new FieldLayout( $this->getEditorControls() )
89            ];
90
91            $fieldSet = new FieldsetLayout( [
92                'items' => $formElements,
93                'classes' => [ 'mw-abusefilter-edit-buttons', 'mw-abusefilter-javascript-tools' ]
94            ] );
95
96            $output .= $fieldSet;
97        }
98
99        if ( $addResultDiv ) {
100            $output .= Html::element(
101                'div',
102                [ 'id' => 'mw-abusefilter-syntaxresult', 'style' => 'display: none;' ]
103            );
104        }
105
106        return $output;
107    }
108
109    private function getSuggestionsDropdown(): DropdownInputWidget {
110        $rawDropdown = $this->keywordsManager->getBuilderValues();
111
112        // The array needs to be rearranged to be understood by OOUI. It comes with the format
113        // [ group-msg-key => [ text-to-add => text-msg-key ] ] and we need it as
114        // [ group-msg => [ text-msg => text-to-add ] ]
115        // Also, the 'other' element must be the first one.
116        $dropdownOptions = [ $this->localizer->msg( 'abusefilter-edit-builder-select' )->text() => 'other' ];
117        foreach ( $rawDropdown as $group => $values ) {
118            // Give grep a chance to find the usages:
119            // abusefilter-edit-builder-group-op-arithmetic
120            // abusefilter-edit-builder-group-op-comparison
121            // abusefilter-edit-builder-group-op-bool
122            // abusefilter-edit-builder-group-misc
123            // abusefilter-edit-builder-group-funcs
124            // abusefilter-edit-builder-group-vars
125            $localisedGroup = $this->localizer->msg( "abusefilter-edit-builder-group-$group" )->text();
126            $dropdownOptions[ $localisedGroup ] = array_flip( $values );
127            $newKeys = array_map(
128                function ( $key ) use ( $group, $dropdownOptions, $localisedGroup ) {
129                    // Force all operators and functions to be always shown as left to right text
130                    // with the help of control characters:
131                    // * 202A is LEFT-TO-RIGHT EMBEDDING (LRE)
132                    // * 202C is POP DIRECTIONAL FORMATTING (PDF)
133                    // This has to be done with control characters because
134                    // markup cannot be used within <option> elements.
135                    $operatorExample = "\u{202A}" .
136                        $dropdownOptions[ $localisedGroup ][ $key ] .
137                        "\u{202C}";
138                    return $this->localizer->msg(
139                        "abusefilter-edit-builder-$group-$key",
140                        $operatorExample
141                    )->text();
142                },
143                array_keys( $dropdownOptions[ $localisedGroup ] )
144            );
145            $dropdownOptions[ $localisedGroup ] = array_combine(
146                $newKeys,
147                $dropdownOptions[ $localisedGroup ]
148            );
149        }
150
151        $dropdownList = Html::listDropdownOptionsOoui( $dropdownOptions );
152        return new DropdownInputWidget( [
153            'name' => 'wpFilterBuilder',
154            'inputId' => 'wpFilterBuilder',
155            'options' => $dropdownList
156        ] );
157    }
158
159    /**
160     * Get an additional widget that "controls" the editor, and is placed next to it
161     * Precondition: the user has full rights.
162     */
163    protected function getEditorControls(): Widget {
164        return new ButtonWidget(
165            [
166                'label' => $this->localizer->msg( 'abusefilter-edit-check' )->text(),
167                'id' => 'mw-abusefilter-syntaxcheck'
168            ]
169        );
170    }
171
172    /**
173     * Generate the HTML for the actual edit box
174     *
175     * @param string $rules
176     * @param bool $isUserAllowed
177     * @param bool $externalForm
178     * @return string
179     */
180    abstract protected function getEditBox( string $rules, bool $isUserAllowed, bool $externalForm ): string;
181
182}