Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
59.26% covered (warning)
59.26%
32 / 54
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbuseFilter
59.26% covered (warning)
59.26%
32 / 54
50.00% covered (danger)
50.00%
3 / 6
21.74
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%
14 / 14
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 Flow\Container;
6use Flow\Data\ManagerGroup;
7use Flow\Model\AbstractRevision;
8use Flow\Model\UUID;
9use MediaWiki\Context\IContextSource;
10use MediaWiki\Extension\AbuseFilter\AbuseFilterServices;
11use MediaWiki\Extension\AbuseFilter\VariableGenerator\RCVariableGenerator;
12use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
13use MediaWiki\MediaWikiServices;
14use MediaWiki\Registration\ExtensionRegistry;
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            ->addGenericVars()
89            ->getVariableHolder();
90
91        $vars->setVar( 'action', $newRevision->getChangeType() );
92
93        /*
94         * This should not roundtrip to Parsoid; AbuseFilter checks will be
95         * performed upon submitting new content, and content is always
96         * submitted in wikitext. It will only be transformed once it's being
97         * saved to DB.
98         */
99        $vars->setLazyLoadVar( 'new_wikitext', 'FlowRevisionContent', [ 'revision' => $newRevision ] );
100
101        /*
102         * This may roundtrip to Parsoid if content is stored in HTML.
103         * Since the variable is lazy-loaded, it will not roundtrip unless the
104         * variable is actually used.
105         */
106        $vars->setLazyLoadVar( 'old_wikitext', 'FlowRevisionContent', [ 'revision' => $oldRevision ] );
107
108        $runnerFactory = AbuseFilterServices::getFilterRunnerFactory();
109        $runner = $runnerFactory->newRunner( $context->getUser(), $title, $vars, $this->group );
110        return $runner->run();
111    }
112
113    /**
114     * @see RCVariableGenerator::addEditVarsForRow()
115     * @param RecentChange $recentChange
116     * @param VariableHolder $vars
117     * @param User $contextUser
118     */
119    public function generateRecentChangesVars(
120        RecentChange $recentChange,
121        VariableHolder $vars,
122        User $contextUser
123    ): void {
124        $changeData = $recentChange->parseParams()['flow-workflow-change'];
125        /** @var ManagerGroup $storage */
126        $storage = Container::get( 'storage' );
127        /** @var AbstractRevision $rev */
128        $rev = $storage->get( $changeData['revision_type'], UUID::create( $changeData['revision'] ) );
129
130        $vars->setVar( 'action', $rev->getChangeType() );
131        $vars->setLazyLoadVar( 'new_wikitext', 'FlowRevisionContent', [ 'revision' => $rev ] );
132
133        $prevRev = $rev->getCollection()->getPrevRevision( $rev );
134        if ( $prevRev ) {
135            $vars->setLazyLoadVar( 'old_wikitext', 'FlowRevisionContent', [ 'revision' => $prevRev ] );
136        } else {
137            $vars->setVar( 'old_wikitext', '' );
138        }
139
140        $title = $recentChange->getTitle();
141        AbuseFilterServices::getVariableGeneratorFactory()->newGenerator( $vars )
142            ->addUserVars( $recentChange->getPerformerIdentity() )
143            ->addTitleVars( $title, 'page' )
144            ->addTitleVars( $rev->getCollection()->getWorkflow()->getOwnerTitle(), 'board' )
145            ->addEditVars( MediaWikiServices::getInstance()->getWikiPageFactory()
146                ->newFromTitle( $title ), $contextUser );
147    }
148
149    /**
150     * Checks if AbuseFilter is installed.
151     *
152     * @return bool
153     */
154    public function enabled() {
155        return ExtensionRegistry::getInstance()->isLoaded( 'Abuse Filter' ) && (bool)$this->group;
156    }
157
158    /**
159     * Additional lazy-load methods for dealing with AbstractRevision objects,
160     * to delay processing data until/if variables are actually used.
161     *
162     * @return array
163     */
164    public function lazyLoadMethods() {
165        return [
166            /**
167             * @param VariableHolder $vars
168             * @param array $parameters Parameters with data to compute the value
169             */
170            'FlowRevisionContent' => static function ( VariableHolder $vars, array $parameters ) {
171                if ( !isset( $parameters['revision'] ) ) {
172                    return '';
173                }
174                $revision = $parameters['revision'];
175                if ( $revision instanceof AbstractRevision ) {
176                    return $revision->getContentInWikitext();
177                } else {
178                    return '';
179                }
180            }
181        ];
182    }
183}