Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
58.49% covered (warning)
58.49%
31 / 53
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbuseFilter
58.49% covered (warning)
58.49%
31 / 53
50.00% covered (danger)
50.00%
3 / 6
22.30
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
 setup
58.33% covered (warning)
58.33%
7 / 12
0.00% covered (danger)
0.00%
0 / 1
3.65
 validate
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 generateRecentChangesVars
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
 enabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 lazyLoadMethods
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
3.01
1<?php
2
3namespace Flow\SpamFilter;
4
5use ExtensionRegistry;
6use Flow\Container;
7use Flow\Data\ManagerGroup;
8use Flow\Model\AbstractRevision;
9use Flow\Model\UUID;
10use IContextSource;
11use MediaWiki\Extension\AbuseFilter\AbuseFilterServices;
12use MediaWiki\Extension\AbuseFilter\VariableGenerator\RCVariableGenerator;
13use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
14use MediaWiki\MediaWikiServices;
15use MediaWiki\Status\Status;
16use MediaWiki\Title\Title;
17use MediaWiki\User\User;
18use RecentChange;
19
20class AbuseFilter implements SpamFilter {
21    /**
22     * @var string
23     */
24    protected $group;
25
26    /**
27     * @param string $group The abuse filter group to use
28     */
29    public function __construct( $group ) {
30        $this->group = $group;
31    }
32
33    /**
34     * Set up AbuseFilter for Flow extension
35     *
36     * @param array $emergencyDisable optional AbuseFilter emergency disable values
37     */
38    public function setup( array $emergencyDisable = [] ) {
39        global
40        $wgAbuseFilterValidGroups,
41        $wgAbuseFilterEmergencyDisableThreshold,
42        $wgAbuseFilterEmergencyDisableCount,
43        $wgAbuseFilterEmergencyDisableAge;
44
45        if ( !$this->enabled() ) {
46            return;
47        }
48
49        // if no Flow-specific emergency disable threshold given, use defaults
50        $emergencyDisable += [
51            'threshold' => $wgAbuseFilterEmergencyDisableThreshold['default'],
52            'count' => $wgAbuseFilterEmergencyDisableCount['default'],
53            'age' => $wgAbuseFilterEmergencyDisableAge['default'],
54        ];
55
56        // register Flow's AbuseFilter filter group
57        if ( !in_array( $this->group, $wgAbuseFilterValidGroups ) ) {
58            $wgAbuseFilterValidGroups[] = $this->group;
59
60            // AbuseFilter emergency disable values for Flow
61            $wgAbuseFilterEmergencyDisableThreshold[$this->group] = $emergencyDisable['threshold'];
62            $wgAbuseFilterEmergencyDisableCount[$this->group] = $emergencyDisable['count'];
63            $wgAbuseFilterEmergencyDisableAge[$this->group] = $emergencyDisable['age'];
64        }
65    }
66
67    /**
68     * @param IContextSource $context
69     * @param AbstractRevision $newRevision
70     * @param AbstractRevision|null $oldRevision
71     * @param Title $title
72     * @param Title $ownerTitle
73     * @return Status
74     */
75    public function validate(
76        IContextSource $context,
77        AbstractRevision $newRevision,
78        ?AbstractRevision $oldRevision,
79        Title $title,
80        Title $ownerTitle
81    ) {
82        $vars = AbuseFilterServices::getVariableGeneratorFactory()->newGenerator()
83            ->addEditVars( MediaWikiServices::getInstance()->getWikiPageFactory()
84                ->newFromTitle( $title ), $context->getUser() )
85            ->addUserVars( $context->getUser() )
86            ->addTitleVars( $title, 'page' )
87            ->addTitleVars( $ownerTitle, 'board' )
88            ->getVariableHolder();
89
90        $vars->setVar( 'action', $newRevision->getChangeType() );
91
92        /*
93         * This should not roundtrip to Parsoid; AbuseFilter checks will be
94         * performed upon submitting new content, and content is always
95         * submitted in wikitext. It will only be transformed once it's being
96         * saved to DB.
97         */
98        $vars->setLazyLoadVar( 'new_wikitext', 'FlowRevisionContent', [ 'revision' => $newRevision ] );
99
100        /*
101         * This may roundtrip to Parsoid if content is stored in HTML.
102         * Since the variable is lazy-loaded, it will not roundtrip unless the
103         * variable is actually used.
104         */
105        $vars->setLazyLoadVar( 'old_wikitext', 'FlowRevisionContent', [ 'revision' => $oldRevision ] );
106
107        $runnerFactory = AbuseFilterServices::getFilterRunnerFactory();
108        $runner = $runnerFactory->newRunner( $context->getUser(), $title, $vars, $this->group );
109        return $runner->run();
110    }
111
112    /**
113     * @see RCVariableGenerator::addEditVarsForRow()
114     * @param RecentChange $recentChange
115     * @param VariableHolder $vars
116     * @param User $contextUser
117     */
118    public function generateRecentChangesVars(
119        RecentChange $recentChange,
120        VariableHolder $vars,
121        User $contextUser
122    ): void {
123        $changeData = $recentChange->parseParams()['flow-workflow-change'];
124        /** @var ManagerGroup $storage */
125        $storage = Container::get( 'storage' );
126        /** @var AbstractRevision $rev */
127        $rev = $storage->get( $changeData['revision_type'], UUID::create( $changeData['revision'] ) );
128
129        $vars->setVar( 'action', $rev->getChangeType() );
130        $vars->setLazyLoadVar( 'new_wikitext', 'FlowRevisionContent', [ 'revision' => $rev ] );
131
132        $prevRev = $rev->getCollection()->getPrevRevision( $rev );
133        if ( $prevRev ) {
134            $vars->setLazyLoadVar( 'old_wikitext', 'FlowRevisionContent', [ 'revision' => $prevRev ] );
135        } else {
136            $vars->setVar( 'old_wikitext', '' );
137        }
138
139        $title = $recentChange->getTitle();
140        AbuseFilterServices::getVariableGeneratorFactory()->newGenerator( $vars )
141            ->addUserVars( $recentChange->getPerformerIdentity() )
142            ->addTitleVars( $title, 'page' )
143            ->addTitleVars( $rev->getCollection()->getWorkflow()->getOwnerTitle(), 'board' )
144            ->addEditVars( MediaWikiServices::getInstance()->getWikiPageFactory()
145                ->newFromTitle( $title ), $contextUser );
146    }
147
148    /**
149     * Checks if AbuseFilter is installed.
150     *
151     * @return bool
152     */
153    public function enabled() {
154        return ExtensionRegistry::getInstance()->isLoaded( 'Abuse Filter' ) && (bool)$this->group;
155    }
156
157    /**
158     * Additional lazy-load methods for dealing with AbstractRevision objects,
159     * to delay processing data until/if variables are actually used.
160     *
161     * @return array
162     */
163    public function lazyLoadMethods() {
164        return [
165            /**
166             * @param VariableHolder $vars
167             * @param array $parameters Parameters with data to compute the value
168             */
169            'FlowRevisionContent' => static function ( VariableHolder $vars, array $parameters ) {
170                if ( !isset( $parameters['revision'] ) ) {
171                    return '';
172                }
173                $revision = $parameters['revision'];
174                if ( $revision instanceof AbstractRevision ) {
175                    return $revision->getContentInWikitext();
176                } else {
177                    return '';
178                }
179            }
180        ];
181    }
182}