Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.35% covered (success)
95.35%
41 / 43
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbuseFilterChecker
95.35% covered (success)
95.35%
41 / 43
50.00% covered (danger)
50.00%
2 / 4
11
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
 checkTitleForUser
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
2.00
 checkSectionForTitleAndUser
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
4.00
 getResults
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2declare( strict_types = 1 );
3
4namespace ContentTranslation;
5
6use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesLookup;
7use MediaWiki\Extension\AbuseFilter\FilterLookup;
8use MediaWiki\Extension\AbuseFilter\FilterRunnerFactory;
9use MediaWiki\Extension\AbuseFilter\GlobalNameUtils;
10use MediaWiki\Extension\AbuseFilter\VariableGenerator\VariableGeneratorFactory;
11use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
12use MediaWiki\Page\WikiPageFactory;
13use MediaWiki\Title\Title;
14use MediaWiki\User\User;
15
16/**
17 * Utility class for checking AbuseFilter rules before publishing.
18 *
19 * The results from these methods return an array, where the keys are
20 * the public names of the rules, and values are arrays consisting of
21 * different actions the rules would cause. Those can be tag, warn,
22 * disallow and others. Example:
23 * @code
24 * * array = [
25 * *   'rule1' => [
26 * *     'warn' => [
27 * *       'action' => 'warn',
28 * *       'parameters' => [ 'abusefilter-warning' ]
29 * *     ]
30 * *   ]
31 * * ];
32 * * @endcode
33 *
34 * With type 'warn' there is also warning_html for html warning message.
35 *
36 * @copyright See AUTHORS.txt
37 * @license GPL-2.0-or-later
38 */
39class AbuseFilterChecker {
40
41    public function __construct(
42        private readonly bool $isAbuseFilterExtensionLoaded,
43        private readonly WikiPageFactory $wikiPageFactory,
44        private readonly ?VariableGeneratorFactory $variableGeneratorFactory,
45        private readonly ?ConsequencesLookup $consequencesLookup,
46        private readonly ?FilterLookup $filterLookup,
47        private readonly ?FilterRunnerFactory $filterRunnerFactory
48    ) {
49    }
50
51    /**
52     * Check a title for any rule violations.
53     *
54     * @param Title $title Title to check
55     * @param User $user User performing the action
56     *
57     * @return array List of any rule violations
58     */
59    public function checkTitleForUser( Title $title, User $user ): array {
60        if ( !$this->isAbuseFilterExtensionLoaded ) {
61            return [];
62        }
63
64        $gen = $this->variableGeneratorFactory->newGenerator();
65        $vars = $gen
66            ->addUserVars( $user )
67            ->addTitleVars( $title, 'page' )
68            ->addGenericVars()
69            ->getVariableHolder();
70        $vars->setVar( 'action', 'edit' );
71
72        return $this->getResults( $user, $title, $vars );
73    }
74
75    /**
76     * Check some text for rule violations.
77     *
78     * @param User $user
79     * @param Title $title
80     * @param string|null $text Text to check
81     * @return array List of any rule violations
82     */
83    public function checkSectionForTitleAndUser( User $user, Title $title, ?string $text ): array {
84        if ( !$this->isAbuseFilterExtensionLoaded ) {
85            return [];
86        }
87
88        if ( $text === null || mb_strlen( $text ) < 150 ) {
89            // Don't validate sections that are too short. The validations
90            // happen while editing is going on.
91            return [];
92        }
93
94        // Add AbuseFilter variables. Note that we are adding the title
95        // here. That will cause filters about titles executed for every section.
96        // But not passing title will cause content filters with namespace rules
97        // not to produce results. We will attempt to filter out title errors
98        // away with array_diff_key.
99        $gen = $this->variableGeneratorFactory->newGenerator();
100        $vars = $gen
101            ->addUserVars( $user )
102            ->addTitleVars( $title, 'page' )
103            ->addEditVars( $this->wikiPageFactory->newFromTitle( $title ), $user )
104            ->addGenericVars()
105            ->getVariableHolder();
106        $vars->setVar( 'action', 'edit' );
107        $vars->setVar( 'old_wikitext', '' );
108        $vars->setVar( 'new_wikitext', $text );
109
110        $results = $this->getResults( $user, $title, $vars );
111        return array_diff_key( $results, $this->checkTitleForUser( $title, $user ) );
112    }
113
114    private function getResults( User $user, Title $title, VariableHolder $vars ): array {
115        static $seriousActions = [ 'warn', 'block', 'disallow', 'degroup' ];
116
117        $runner = $this->filterRunnerFactory->newRunner( $user, $title, $vars, 'default' );
118        $filters = $runner->checkAllFilters();
119
120        $filters = array_keys( array_filter( $filters ) );
121        $actions = $this->consequencesLookup->getConsequencesForFilters( $filters );
122
123        $results = [];
124        foreach ( $actions as $key => $val ) {
125            [ $filterID, $isGlobal ] = GlobalNameUtils::splitGlobalName( $key );
126            $rulename = $this->filterLookup->getFilter( $filterID, $isGlobal )->getName();
127
128            // No point alerting the user about non-serious actions. T136596
129            $actionsForRule = array_keys( $val );
130            if ( array_intersect( $seriousActions, $actionsForRule ) === [] ) {
131                continue;
132            }
133
134            if ( isset( $val['warn'][0] ) ) {
135                $val['warn']['messageHtml'] = wfMessage( $val['warn'][0] )->params( $rulename )->parse();
136            }
137
138            $results[$key] = $val;
139        }
140
141        return $results;
142    }
143}