Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 139 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
AbuseFilterHistoryPager | |
0.00% |
0 / 139 |
|
0.00% |
0 / 7 |
1260 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
getFieldNames | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
formatValue | |
0.00% |
0 / 54 |
|
0.00% |
0 / 1 |
210 | |||
getQueryInfo | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
20 | |||
preprocessResults | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
getDefaultSort | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
isFieldSortable | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
getCellAttrs | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
30 | |||
getTitle | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\AbuseFilter\Pager; |
4 | |
5 | use HtmlArmor; |
6 | use IContextSource; |
7 | use MediaWiki\Cache\LinkBatchFactory; |
8 | use MediaWiki\Extension\AbuseFilter\AbuseFilter; |
9 | use MediaWiki\Extension\AbuseFilter\AbuseFilterServices; |
10 | use MediaWiki\Extension\AbuseFilter\FilterLookup; |
11 | use MediaWiki\Extension\AbuseFilter\Special\SpecialAbuseFilter; |
12 | use MediaWiki\Extension\AbuseFilter\SpecsFormatter; |
13 | use MediaWiki\Linker\Linker; |
14 | use MediaWiki\Linker\LinkRenderer; |
15 | use MediaWiki\MediaWikiServices; |
16 | use MediaWiki\Pager\TablePager; |
17 | use MediaWiki\Title\Title; |
18 | use MediaWiki\User\UserRigorOptions; |
19 | use UnexpectedValueException; |
20 | use Wikimedia\Rdbms\IResultWrapper; |
21 | use Xml; |
22 | |
23 | class AbuseFilterHistoryPager extends TablePager { |
24 | |
25 | /** @var LinkBatchFactory */ |
26 | private $linkBatchFactory; |
27 | |
28 | /** @var FilterLookup */ |
29 | private $filterLookup; |
30 | |
31 | /** @var SpecsFormatter */ |
32 | private $specsFormatter; |
33 | |
34 | /** @var int|null The filter ID */ |
35 | private $filter; |
36 | |
37 | /** @var string|null The user whose changes we're looking up for */ |
38 | private $user; |
39 | |
40 | /** @var bool */ |
41 | private $canViewPrivateFilters; |
42 | |
43 | /** |
44 | * @param IContextSource $context |
45 | * @param LinkRenderer $linkRenderer |
46 | * @param LinkBatchFactory $linkBatchFactory |
47 | * @param FilterLookup $filterLookup |
48 | * @param SpecsFormatter $specsFormatter |
49 | * @param ?int $filter |
50 | * @param ?string $user User name |
51 | * @param bool $canViewPrivateFilters |
52 | */ |
53 | public function __construct( |
54 | IContextSource $context, |
55 | LinkRenderer $linkRenderer, |
56 | LinkBatchFactory $linkBatchFactory, |
57 | FilterLookup $filterLookup, |
58 | SpecsFormatter $specsFormatter, |
59 | ?int $filter, |
60 | ?string $user, |
61 | bool $canViewPrivateFilters = false |
62 | ) { |
63 | // needed by parent's constructor call |
64 | $this->filter = $filter; |
65 | parent::__construct( $context, $linkRenderer ); |
66 | $this->linkBatchFactory = $linkBatchFactory; |
67 | $this->filterLookup = $filterLookup; |
68 | $this->specsFormatter = $specsFormatter; |
69 | $this->user = $user; |
70 | $this->canViewPrivateFilters = $canViewPrivateFilters; |
71 | $this->mDefaultDirection = true; |
72 | } |
73 | |
74 | /** |
75 | * Note: this method is called by parent::__construct |
76 | * @return array |
77 | * @see MediaWiki\Pager\Pager::getFieldNames() |
78 | */ |
79 | public function getFieldNames() { |
80 | static $headers = null; |
81 | |
82 | if ( $headers !== null ) { |
83 | return $headers; |
84 | } |
85 | |
86 | $headers = [ |
87 | 'afh_timestamp' => 'abusefilter-history-timestamp', |
88 | 'afh_user_text' => 'abusefilter-history-user', |
89 | 'afh_public_comments' => 'abusefilter-history-public', |
90 | 'afh_flags' => 'abusefilter-history-flags', |
91 | 'afh_actions' => 'abusefilter-history-actions', |
92 | 'afh_id' => 'abusefilter-history-diff', |
93 | ]; |
94 | |
95 | if ( !$this->filter ) { |
96 | // awful hack |
97 | $headers = [ 'afh_filter' => 'abusefilter-history-filterid' ] + $headers; |
98 | } |
99 | |
100 | foreach ( $headers as &$msg ) { |
101 | $msg = $this->msg( $msg )->text(); |
102 | } |
103 | |
104 | return $headers; |
105 | } |
106 | |
107 | /** |
108 | * @param string $name |
109 | * @param string|null $value |
110 | * @return string |
111 | */ |
112 | public function formatValue( $name, $value ) { |
113 | $lang = $this->getLanguage(); |
114 | $linkRenderer = $this->getLinkRenderer(); |
115 | |
116 | $row = $this->mCurrentRow; |
117 | |
118 | switch ( $name ) { |
119 | case 'afh_filter': |
120 | $formatted = $linkRenderer->makeLink( |
121 | SpecialAbuseFilter::getTitleForSubpage( $row->afh_filter ), |
122 | $lang->formatNum( $row->afh_filter ) |
123 | ); |
124 | break; |
125 | case 'afh_timestamp': |
126 | $title = SpecialAbuseFilter::getTitleForSubpage( |
127 | 'history/' . $row->afh_filter . '/item/' . $row->afh_id ); |
128 | $formatted = $linkRenderer->makeLink( |
129 | $title, |
130 | $lang->userTimeAndDate( $row->afh_timestamp, $this->getUser() ) |
131 | ); |
132 | break; |
133 | case 'afh_user_text': |
134 | $formatted = |
135 | Linker::userLink( $row->afh_user, $row->afh_user_text ) . ' ' . |
136 | Linker::userToolLinks( $row->afh_user, $row->afh_user_text ); |
137 | break; |
138 | case 'afh_public_comments': |
139 | $formatted = htmlspecialchars( $value, ENT_QUOTES, 'UTF-8', false ); |
140 | break; |
141 | case 'afh_flags': |
142 | $formatted = $this->specsFormatter->formatFlags( $value, $lang ); |
143 | break; |
144 | case 'afh_actions': |
145 | $actions = unserialize( $value ); |
146 | |
147 | $display_actions = ''; |
148 | |
149 | foreach ( $actions as $action => $parameters ) { |
150 | $displayAction = $this->specsFormatter->formatAction( $action, $parameters, $lang ); |
151 | $display_actions .= Xml::tags( 'li', null, $displayAction ); |
152 | } |
153 | $display_actions = Xml::tags( 'ul', null, $display_actions ); |
154 | |
155 | $formatted = $display_actions; |
156 | break; |
157 | case 'afh_id': |
158 | // Set a link to a diff with the previous version if this isn't the first edit to the filter. |
159 | // Like in AbuseFilterViewDiff, don't show it if the user cannot see private filters and any |
160 | // of the versions is hidden. |
161 | $formatted = ''; |
162 | if ( $this->filterLookup->getFirstFilterVersionID( $row->afh_filter ) !== (int)$value ) { |
163 | // @todo Should we also hide actions? |
164 | $prevFilter = $this->filterLookup->getClosestVersion( |
165 | $row->afh_id, $row->afh_filter, FilterLookup::DIR_PREV ); |
166 | if ( $this->canViewPrivateFilters || |
167 | ( |
168 | !in_array( 'hidden', explode( ',', $row->afh_flags ) ) && |
169 | !$prevFilter->isHidden() |
170 | ) |
171 | ) { |
172 | $title = SpecialAbuseFilter::getTitleForSubpage( |
173 | 'history/' . $row->afh_filter . "/diff/prev/$value" ); |
174 | $formatted = $linkRenderer->makeLink( |
175 | $title, |
176 | new HtmlArmor( $this->msg( 'abusefilter-history-diff' )->parse() ) |
177 | ); |
178 | } |
179 | } |
180 | break; |
181 | default: |
182 | throw new UnexpectedValueException( "Unknown row type $name!" ); |
183 | } |
184 | |
185 | return $formatted; |
186 | } |
187 | |
188 | /** |
189 | * @return array |
190 | */ |
191 | public function getQueryInfo() { |
192 | $afActorMigration = AbuseFilterServices::getActorMigration(); |
193 | $actorQuery = $afActorMigration->getJoin( 'afh_user' ); |
194 | $info = [ |
195 | 'tables' => [ 'abuse_filter_history', 'abuse_filter' ] + $actorQuery['tables'], |
196 | // All fields but afh_deleted on abuse_filter_history |
197 | 'fields' => [ |
198 | 'afh_filter', |
199 | 'afh_timestamp', |
200 | 'afh_public_comments', |
201 | 'afh_flags', |
202 | 'afh_comments', |
203 | 'afh_actions', |
204 | 'afh_id', |
205 | 'afh_changed_fields', |
206 | 'afh_pattern', |
207 | 'af_hidden' |
208 | ] + $actorQuery['fields'], |
209 | 'conds' => [], |
210 | 'join_conds' => [ |
211 | 'abuse_filter' => |
212 | [ |
213 | 'LEFT JOIN', |
214 | 'afh_filter=af_id', |
215 | ], |
216 | ] + $actorQuery['joins'], |
217 | ]; |
218 | |
219 | if ( $this->user !== null ) { |
220 | $user = MediaWikiServices::getInstance()->getUserFactory() |
221 | ->newFromName( $this->user, UserRigorOptions::RIGOR_NONE ); |
222 | $whereQuery = $afActorMigration->getWhere( $this->mDb, 'afh_user', $user ); |
223 | $info['conds'][] = $whereQuery['conds']; |
224 | } |
225 | |
226 | if ( $this->filter ) { |
227 | $info['conds']['afh_filter'] = $this->filter; |
228 | } |
229 | |
230 | if ( !$this->canViewPrivateFilters ) { |
231 | // Hide data the user can't see. |
232 | $info['conds']['af_hidden'] = 0; |
233 | } |
234 | |
235 | return $info; |
236 | } |
237 | |
238 | /** |
239 | * @param IResultWrapper $result |
240 | */ |
241 | protected function preprocessResults( $result ) { |
242 | if ( $this->getNumRows() === 0 ) { |
243 | return; |
244 | } |
245 | |
246 | $lb = $this->linkBatchFactory->newLinkBatch(); |
247 | $lb->setCaller( __METHOD__ ); |
248 | foreach ( $result as $row ) { |
249 | $lb->add( NS_USER, $row->afh_user_text ); |
250 | $lb->add( NS_USER_TALK, $row->afh_user_text ); |
251 | } |
252 | $lb->execute(); |
253 | $result->seek( 0 ); |
254 | } |
255 | |
256 | /** |
257 | * @codeCoverageIgnore Merely declarative |
258 | * @inheritDoc |
259 | */ |
260 | public function getDefaultSort() { |
261 | return 'afh_timestamp'; |
262 | } |
263 | |
264 | /** |
265 | * @codeCoverageIgnore Merely declarative |
266 | * @inheritDoc |
267 | */ |
268 | public function isFieldSortable( $field ) { |
269 | return $field === 'afh_timestamp'; |
270 | } |
271 | |
272 | /** |
273 | * @param string $field |
274 | * @param string $value |
275 | * @return array |
276 | * @see TablePager::getCellAttrs |
277 | */ |
278 | public function getCellAttrs( $field, $value ) { |
279 | $row = $this->mCurrentRow; |
280 | $mappings = array_flip( AbuseFilter::HISTORY_MAPPINGS ) + |
281 | [ 'afh_actions' => 'actions', 'afh_id' => 'id' ]; |
282 | $changed = explode( ',', $row->afh_changed_fields ); |
283 | |
284 | $fieldChanged = false; |
285 | if ( $field === 'afh_flags' ) { |
286 | // The field is changed if any of these filters are in the $changed array. |
287 | $filters = [ 'af_enabled', 'af_hidden', 'af_deleted', 'af_global' ]; |
288 | if ( count( array_intersect( $filters, $changed ) ) ) { |
289 | $fieldChanged = true; |
290 | } |
291 | } elseif ( in_array( $mappings[$field], $changed ) ) { |
292 | $fieldChanged = true; |
293 | } |
294 | |
295 | $class = $fieldChanged ? ' mw-abusefilter-history-changed' : ''; |
296 | $attrs = parent::getCellAttrs( $field, $value ); |
297 | $attrs['class'] .= $class; |
298 | return $attrs; |
299 | } |
300 | |
301 | /** |
302 | * Title used for self-links. |
303 | * |
304 | * @return Title |
305 | */ |
306 | public function getTitle() { |
307 | $subpage = $this->filter ? ( 'history/' . $this->filter ) : 'history'; |
308 | return SpecialAbuseFilter::getTitleForSubpage( $subpage ); |
309 | } |
310 | } |