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