Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 192
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
PendingChanges
0.00% covered (danger)
0.00%
0 / 192
0.00% covered (danger)
0.00%
0 / 12
2070
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
 execute
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
42
 setSyndicated
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 showForm
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
42
 showPageList
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 parseParams
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
 feed
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 feedTitle
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 feedItem
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
20
 formatRow
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
72
 getLineClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Feed\FeedItem;
4use MediaWiki\Feed\FeedUtils;
5use MediaWiki\Html\Html;
6use MediaWiki\MainConfigNames;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\SpecialPage\SpecialPage;
9use MediaWiki\Title\Title;
10
11class 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' ) . '&#160;' .
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}