Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 192 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
PendingChanges | |
0.00% |
0 / 192 |
|
0.00% |
0 / 12 |
2070 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
42 | |||
setSyndicated | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
showForm | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
42 | |||
showPageList | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
parseParams | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
56 | |||
feed | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
30 | |||
feedTitle | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
feedItem | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
20 | |||
formatRow | |
0.00% |
0 / 41 |
|
0.00% |
0 / 1 |
72 | |||
getLineClass | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | use MediaWiki\Feed\FeedItem; |
4 | use MediaWiki\Feed\FeedUtils; |
5 | use MediaWiki\Html\Html; |
6 | use MediaWiki\MainConfigNames; |
7 | use MediaWiki\MediaWikiServices; |
8 | use MediaWiki\SpecialPage\SpecialPage; |
9 | use MediaWiki\Title\Title; |
10 | |
11 | class PendingChanges extends SpecialPage { |
12 | /** @var PendingChangesPager|null */ |
13 | private $pager = null; |
14 | |
15 | /** @var int */ |
16 | private $currentUnixTS; |
17 | |
18 | /** @var int|null */ |
19 | private $namespace; |
20 | |
21 | /** @var string */ |
22 | private $category; |
23 | |
24 | /** @var int|null */ |
25 | private $size; |
26 | |
27 | /** @var bool */ |
28 | private $watched; |
29 | |
30 | /** @var bool */ |
31 | private $stable; |
32 | |
33 | public function __construct() { |
34 | parent::__construct( 'PendingChanges' ); |
35 | $this->mIncludable = true; |
36 | } |
37 | |
38 | /** |
39 | * @inheritDoc |
40 | */ |
41 | public function execute( $par ) { |
42 | $request = $this->getRequest(); |
43 | |
44 | $this->setHeaders(); |
45 | $this->addHelpLink( 'Help:Extension:FlaggedRevs' ); |
46 | $this->currentUnixTS = (int)wfTimestamp(); |
47 | |
48 | $this->namespace = $request->getIntOrNull( 'namespace' ); |
49 | $level = $request->getInt( 'level', -1 ); |
50 | $category = trim( $request->getVal( 'category', '' ) ); |
51 | $catTitle = Title::makeTitleSafe( NS_CATEGORY, $category ); |
52 | $this->category = $catTitle === null ? '' : $catTitle->getText(); |
53 | $this->size = $request->getIntOrNull( 'size' ); |
54 | $this->watched = $request->getCheck( 'watched' ); |
55 | $this->stable = $request->getCheck( 'stable' ); |
56 | $feedType = $request->getVal( 'feed' ); |
57 | |
58 | $incLimit = 0; |
59 | if ( $this->including() ) { |
60 | $incLimit = $this->parseParams( $par ); // apply non-URL params |
61 | } |
62 | |
63 | $this->pager = new PendingChangesPager( $this, $this->namespace, |
64 | $level, $this->category, $this->size, $this->watched, $this->stable ); |
65 | |
66 | # Output appropriate format... |
67 | if ( $feedType != null ) { |
68 | $this->feed( $feedType ); |
69 | } else { |
70 | if ( $this->including() ) { |
71 | if ( $incLimit ) { // limit provided |
72 | $this->pager->setLimit( $incLimit ); // apply non-URL limit |
73 | } |
74 | } else { |
75 | $this->setSyndicated(); |
76 | $this->showForm(); |
77 | } |
78 | $this->showPageList(); |
79 | } |
80 | } |
81 | |
82 | private function setSyndicated() { |
83 | $request = $this->getRequest(); |
84 | $queryParams = [ |
85 | 'namespace' => $request->getIntOrNull( 'namespace' ), |
86 | 'level' => $request->getIntOrNull( 'level' ), |
87 | 'category' => $request->getVal( 'category' ), |
88 | ]; |
89 | $this->getOutput()->setSyndicated(); |
90 | $this->getOutput()->setFeedAppendQuery( wfArrayToCgi( $queryParams ) ); |
91 | } |
92 | |
93 | private function showForm() { |
94 | # Explanatory text |
95 | $this->getOutput()->addWikiMsg( 'pendingchanges-list', |
96 | $this->getLanguage()->formatNum( $this->pager->getNumRows() ) ); |
97 | |
98 | $form = Html::openElement( 'form', [ |
99 | 'name' => 'pendingchanges', |
100 | 'action' => $this->getConfig()->get( MainConfigNames::Script ), |
101 | 'method' => 'get', |
102 | ] ) . "\n"; |
103 | $form .= "<fieldset><legend>" . $this->msg( 'pendingchanges-legend' )->escaped() . "</legend>\n"; |
104 | $form .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . "\n"; |
105 | |
106 | $items = []; |
107 | if ( count( FlaggedRevs::getReviewNamespaces() ) > 1 ) { |
108 | $items[] = "<span style='white-space: nowrap;'>" . |
109 | FlaggedRevsXML::getNamespaceMenu( $this->namespace, '' ) . '</span>'; |
110 | } |
111 | if ( !FlaggedRevs::isStableShownByDefault() && !FlaggedRevs::useOnlyIfProtected() ) { |
112 | $items[] = "<span style='white-space: nowrap;'>" . |
113 | Xml::check( 'stable', $this->stable, [ 'id' => 'wpStable' ] ) . |
114 | Xml::label( $this->msg( 'pendingchanges-stable' )->text(), 'wpStable' ) . '</span>'; |
115 | } |
116 | if ( $items ) { |
117 | $form .= implode( ' ', $items ) . '<br />'; |
118 | } |
119 | |
120 | $items = []; |
121 | $items[] = |
122 | Xml::label( $this->msg( "pendingchanges-category" )->text(), 'wpCategory' ) . ' ' . |
123 | Xml::input( 'category', 30, $this->category, [ 'id' => 'wpCategory' ] ); |
124 | if ( $this->getUser()->getId() ) { |
125 | $items[] = Xml::check( 'watched', $this->watched, [ 'id' => 'wpWatched' ] ) . |
126 | Xml::label( $this->msg( 'pendingchanges-onwatchlist' )->text(), 'wpWatched' ); |
127 | } |
128 | $form .= implode( ' ', $items ) . '<br />'; |
129 | $form .= |
130 | Xml::label( $this->msg( 'pendingchanges-size' )->text(), 'wpSize' ) . |
131 | Xml::input( 'size', 4, (string)$this->size, [ 'id' => 'wpSize' ] ) . ' ' . |
132 | Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "\n"; |
133 | $form .= "</fieldset>"; |
134 | $form .= Html::closeElement( 'form' ) . "\n"; |
135 | |
136 | $this->getOutput()->addHTML( $form ); |
137 | } |
138 | |
139 | private function showPageList() { |
140 | $out = $this->getOutput(); |
141 | |
142 | if ( !$this->pager->getNumRows() ) { |
143 | $out->addWikiMsg( 'pendingchanges-none' ); |
144 | return; |
145 | } |
146 | |
147 | // To style output of ChangesList::showCharacterDifference |
148 | $out->addModuleStyles( 'mediawiki.special.changeslist' ); |
149 | $out->addModuleStyles( 'mediawiki.interface.helpers.styles' ); |
150 | |
151 | if ( $this->including() ) { |
152 | // If this list is transcluded... |
153 | $out->addHTML( $this->pager->getBody() ); |
154 | } else { |
155 | // Viewing the list normally... |
156 | $navigationBar = $this->pager->getNavigationBar(); |
157 | $out->addHTML( $navigationBar ); |
158 | $out->addHTML( $this->pager->getBody() ); |
159 | $out->addHTML( $navigationBar ); |
160 | } |
161 | } |
162 | |
163 | /** |
164 | * Set pager parameters from $par, return pager limit |
165 | * @param string $par |
166 | * @return bool|int |
167 | */ |
168 | private function parseParams( $par ) { |
169 | $bits = preg_split( '/\s*,\s*/', trim( $par ) ); |
170 | $limit = false; |
171 | foreach ( $bits as $bit ) { |
172 | if ( is_numeric( $bit ) ) { |
173 | $limit = intval( $bit ); |
174 | } |
175 | $m = []; |
176 | if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { |
177 | $limit = intval( $m[1] ); |
178 | } |
179 | if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) { |
180 | $ns = $this->getLanguage()->getNsIndex( $m[1] ); |
181 | if ( $ns !== false ) { |
182 | $this->namespace = $ns; |
183 | } |
184 | } |
185 | if ( preg_match( '/^category=(.+)$/', $bit, $m ) ) { |
186 | $this->category = $m[1]; |
187 | } |
188 | } |
189 | return $limit; |
190 | } |
191 | |
192 | /** |
193 | * Output a subscription feed listing recent edits to this page. |
194 | * @param string $type |
195 | */ |
196 | private function feed( $type ) { |
197 | if ( !$this->getConfig()->get( MainConfigNames::Feed ) ) { |
198 | $this->getOutput()->addWikiMsg( 'feed-unavailable' ); |
199 | return; |
200 | } |
201 | |
202 | $feedClasses = $this->getConfig()->get( MainConfigNames::FeedClasses ); |
203 | if ( !isset( $feedClasses[$type] ) ) { |
204 | $this->getOutput()->addWikiMsg( 'feed-invalid' ); |
205 | return; |
206 | } |
207 | |
208 | $feed = new $feedClasses[$type]( |
209 | $this->feedTitle(), |
210 | $this->msg( 'tagline' )->text(), |
211 | $this->getPageTitle()->getFullURL() |
212 | ); |
213 | $this->pager->mLimit = min( $this->getConfig()->get( MainConfigNames::FeedLimit ), $this->pager->mLimit ); |
214 | |
215 | $feed->outHeader(); |
216 | if ( $this->pager->getNumRows() > 0 ) { |
217 | foreach ( $this->pager->mResult as $row ) { |
218 | $feed->outItem( $this->feedItem( $row ) ); |
219 | } |
220 | } |
221 | $feed->outFooter(); |
222 | } |
223 | |
224 | private function feedTitle() { |
225 | $languageCode = $this->getConfig()->get( MainConfigNames::LanguageCode ); |
226 | $sitename = $this->getConfig()->get( MainConfigNames::Sitename ); |
227 | |
228 | $page = MediaWikiServices::getInstance()->getSpecialPageFactory() |
229 | ->getPage( 'PendingChanges' ); |
230 | $desc = $page->getDescription(); |
231 | return "$sitename - $desc [$languageCode]"; |
232 | } |
233 | |
234 | /** |
235 | * @param stdClass $row |
236 | * @return FeedItem|null |
237 | */ |
238 | private function feedItem( $row ) { |
239 | $title = Title::makeTitle( $row->page_namespace, $row->page_title ); |
240 | if ( !$title ) { |
241 | return null; |
242 | } |
243 | |
244 | $date = $row->pending_since; |
245 | $comments = $title->getTalkPage()->getFullURL(); |
246 | $curRevRecord = MediaWikiServices::getInstance() |
247 | ->getRevisionLookup() |
248 | ->getRevisionByTitle( $title ); |
249 | $currentComment = $curRevRecord->getComment() ? |
250 | $curRevRecord->getComment()->text : |
251 | ''; |
252 | $currentUserText = $curRevRecord->getUser() ? |
253 | $curRevRecord->getUser()->getName() : |
254 | ''; |
255 | return new FeedItem( |
256 | $title->getPrefixedText(), |
257 | FeedUtils::formatDiffRow( |
258 | $title, |
259 | $row->stable, |
260 | $curRevRecord->getId(), |
261 | $row->pending_since, |
262 | $currentComment |
263 | ), |
264 | $title->getFullURL(), |
265 | $date, |
266 | $currentUserText, |
267 | $comments |
268 | ); |
269 | } |
270 | |
271 | /** |
272 | * @param stdClass $row |
273 | * @return string HTML |
274 | */ |
275 | public function formatRow( $row ) { |
276 | $css = ''; |
277 | $quality = ''; |
278 | $title = Title::newFromRow( $row ); |
279 | $stxt = ChangesList::showCharacterDifference( $row->rev_len, $row->page_len ); |
280 | # Page links... |
281 | $linkRenderer = $this->getLinkRenderer(); |
282 | $link = $linkRenderer->makeLink( $title ); |
283 | $hist = $linkRenderer->makeKnownLink( |
284 | $title, |
285 | $this->msg( 'hist' )->text(), |
286 | [], |
287 | [ 'action' => 'history' ] |
288 | ); |
289 | $review = $linkRenderer->makeKnownLink( |
290 | $title, |
291 | $this->msg( 'pendingchanges-diff' )->text(), |
292 | [], |
293 | [ 'diff' => 'cur', 'oldid' => $row->stable ] |
294 | ); |
295 | # Is anybody watching? |
296 | // Only show information to users with the `unwatchedpages` who could find this |
297 | // information elsewhere anyway, T281065 |
298 | if ( !$this->including() && MediaWikiServices::getInstance()->getPermissionManager() |
299 | ->userHasRight( $this->getUser(), 'unwatchedpages' ) |
300 | ) { |
301 | $uw = FRUserActivity::numUsersWatchingPage( $title ); |
302 | $watching = ' '; |
303 | $watching .= $uw |
304 | ? $this->msg( 'pendingchanges-watched' )->numParams( $uw )->escaped() |
305 | : $this->msg( 'pendingchanges-unwatched' )->escaped(); |
306 | } else { |
307 | $uw = -1; |
308 | $watching = ''; |
309 | } |
310 | # Get how long the first unreviewed edit has been waiting... |
311 | if ( $row->pending_since ) { |
312 | $firstPendingTime = (int)wfTimestamp( TS_UNIX, $row->pending_since ); |
313 | $hours = ( $this->currentUnixTS - $firstPendingTime ) / 3600; |
314 | $days = round( $hours / 24 ); |
315 | if ( $days >= 3 ) { |
316 | $age = $this->msg( 'pendingchanges-days' )->numParams( $days )->escaped(); |
317 | } elseif ( $hours >= 1 ) { |
318 | $age = $this->msg( 'pendingchanges-hours' )->numParams( round( $hours ) )->escaped(); |
319 | } else { |
320 | $age = $this->msg( 'pendingchanges-recent' )->escaped(); // hot off the press :) |
321 | } |
322 | // Oh-noes! |
323 | $class = $this->getLineClass( $uw ); |
324 | $css = $class ? " class='$class'" : ""; |
325 | } else { |
326 | $age = ""; // wtf? |
327 | } |
328 | |
329 | return ( "<li{$css}>{$link} ({$hist}) {$stxt} ({$review}) <i>{$age}</i>" . |
330 | "{$quality}{$watching}</li>" ); |
331 | } |
332 | |
333 | /** |
334 | * @param int $numUsersWatching Number of users or -1 when not allowed to see the number |
335 | * @return string |
336 | */ |
337 | private function getLineClass( $numUsersWatching ) { |
338 | return $numUsersWatching == 0 ? 'fr-unreviewed-unwatched' : ''; |
339 | } |
340 | |
341 | /** |
342 | * @return string |
343 | */ |
344 | protected function getGroupName() { |
345 | return 'quality'; |
346 | } |
347 | } |