Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbuseFilterChangesList
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 8
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 insertExtra
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
56
 recentChangesLine
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 insertUserRelatedLinks
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 insertComment
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 insertLogEntry
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 insertRollback
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setRCResult
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter;
4
5use HtmlArmor;
6use MediaWiki\Context\IContextSource;
7use MediaWiki\Linker\Linker;
8use MediaWiki\Logging\LogFormatter;
9use MediaWiki\MediaWikiServices;
10use MediaWiki\RecentChanges\OldChangesList;
11use MediaWiki\RecentChanges\RecentChange;
12use MediaWiki\Revision\RevisionRecord;
13use MediaWiki\SpecialPage\SpecialPage;
14use MediaWiki\Title\TitleValue;
15
16class AbuseFilterChangesList extends OldChangesList {
17
18    /**
19     * @var array<int,bool> Maps RC IDs to a boolean indicating whether the RC would match a filter that is being tested
20     */
21    private array $rcResults = [];
22
23    /**
24     * @param IContextSource $context
25     * @param string $testFilter
26     */
27    public function __construct( IContextSource $context, private readonly string $testFilter ) {
28        parent::__construct( $context );
29    }
30
31    /**
32     * @param string &$s
33     * @param RecentChange &$rc
34     * @param string[] &$classes
35     */
36    public function insertExtra( &$s, &$rc, &$classes ) {
37        if ( (int)$rc->getAttribute( 'rc_deleted' ) !== 0 ) {
38            $s .= ' ' . $this->msg( 'abusefilter-log-hidden-implicit' )->parse();
39            if ( !self::userCan( $rc, RevisionRecord::SUPPRESSED_ALL ) ) {
40                // Remember to keep this in sync with the CheckMatch API
41                return;
42            }
43        }
44
45        $examineParams = [];
46        if ( $this->testFilter && strlen( $this->testFilter ) < 2000 ) {
47            // Since this is GETed, don't send it if it's too long to prevent broken URLs 2000 is taken from
48            // https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-
49            // in-different-browsers/417184#417184
50            $examineParams['testfilter'] = $this->testFilter;
51        }
52
53        $rcid = $rc->getAttribute( 'rc_id' );
54        $title = SpecialPage::getTitleFor( 'AbuseFilter', 'examine/' . $rcid );
55        $examineLink = $this->linkRenderer->makeLink(
56            $title,
57            new HtmlArmor( $this->msg( 'abusefilter-changeslist-examine' )->parse() ),
58            [],
59            $examineParams
60        );
61
62        $s .= ' ' . $this->msg( 'parentheses' )->rawParams( $examineLink )->escaped();
63
64        // Add CSS classes for match and not match
65        if ( isset( $this->rcResults[$rcid] ) ) {
66            $class = $this->rcResults[$rcid] ?
67                'mw-abusefilter-changeslist-match' :
68                'mw-abusefilter-changeslist-nomatch';
69
70            $classes[] = $class;
71        }
72    }
73
74    /**
75     * Overridden as a hacky workaround for T273387. Yuck!
76     * @inheritDoc
77     */
78    public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
79        $par = parent::recentChangesLine( $rc, $watched, $linenumber );
80        if ( $par === false || $par === '' ) {
81            return $par;
82        }
83        $ret = preg_replace( '/<\/li>$/', '', $par );
84        if ( $rc->getAttribute( 'rc_source' ) === 'flow' ) {
85            $classes = [];
86            $this->insertExtra( $ret, $rc, $classes );
87        }
88        return $ret . '</li>';
89    }
90
91    /**
92     * Insert links to user page, user talk page and eventually a blocking link.
93     *   Like the parent, but don't hide details if user can see them.
94     *
95     * @param string &$s HTML to update
96     * @param RecentChange &$rc
97     */
98    public function insertUserRelatedLinks( &$s, &$rc ) {
99        $links = $this->getLanguage()->getDirMark() . Linker::userLink( $rc->getAttribute( 'rc_user' ),
100                $rc->getAttribute( 'rc_user_text' ) ) .
101                Linker::userToolLinks( $rc->getAttribute( 'rc_user' ), $rc->getAttribute( 'rc_user_text' ) );
102
103        if ( $this->isDeleted( $rc, RevisionRecord::DELETED_USER ) ) {
104            if ( $this->userCan( $rc, RevisionRecord::DELETED_USER ) ) {
105                $s .= ' <span class="history-deleted">' . $links . '</span>';
106            } else {
107                $s .= ' <span class="history-deleted">' .
108                    $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
109            }
110        } else {
111            $s .= $links;
112        }
113    }
114
115    /**
116     * Insert a formatted comment. Like the parent, but don't hide details if user can see them.
117     * @param RecentChange $rc
118     * @return string
119     */
120    public function insertComment( $rc ) {
121        if ( $this->isDeleted( $rc, RevisionRecord::DELETED_COMMENT ) ) {
122            if ( $this->userCan( $rc, RevisionRecord::DELETED_COMMENT ) ) {
123                return ' <span class="history-deleted">' .
124                    MediaWikiServices::getInstance()->getCommentFormatter()
125                        ->formatBlock(
126                            $rc->getAttribute( 'rc_comment' ),
127                            TitleValue::castPageToLinkTarget( $rc->getPage() )
128                        ) . '</span>';
129            } else {
130                return ' <span class="history-deleted">' .
131                    $this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
132            }
133        } else {
134            return MediaWikiServices::getInstance()->getCommentFormatter()
135                ->formatBlock( $rc->getAttribute( 'rc_comment' ), TitleValue::castPageToLinkTarget( $rc->getPage() ) );
136        }
137    }
138
139    /**
140     * Insert a formatted action. The same as parent, but with a different audience in LogFormatter
141     *
142     * @param RecentChange $rc
143     * @return string
144     */
145    public function insertLogEntry( $rc ) {
146        $formatter = MediaWikiServices::getInstance()->getLogFormatterFactory()->newFromRow( $rc->getAttributes() );
147        $formatter->setContext( $this->getContext() );
148        $formatter->setAudience( LogFormatter::FOR_THIS_USER );
149        $formatter->setShowUserToolLinks( true );
150        $mark = $this->getLanguage()->getDirMark();
151        return $formatter->getActionText() . " $mark" . $formatter->getComment();
152    }
153
154    /**
155     * @param string &$s
156     * @param RecentChange &$rc
157     */
158    public function insertRollback( &$s, &$rc ) {
159        // Kill rollback links.
160    }
161
162    public function setRCResult( RecentChange $rc, bool $matches ): void {
163        $id = $rc->getAttribute( 'rc_id' );
164        $this->rcResults[$id] = $matches;
165    }
166}