MediaWiki REL1_31
SpecialNewpages.php
Go to the documentation of this file.
1<?php
33 protected $opts;
34 protected $customFilters;
35
36 protected $showNavigation = false;
37
38 public function __construct() {
39 parent::__construct( 'Newpages' );
40 }
41
42 protected function setup( $par ) {
43 // Options
44 $opts = new FormOptions();
45 $this->opts = $opts; // bind
46 $opts->add( 'hideliu', false );
47 $opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'newpageshidepatrolled' ) );
48 $opts->add( 'hidebots', false );
49 $opts->add( 'hideredirs', true );
50 $opts->add( 'limit', $this->getUser()->getIntOption( 'rclimit' ) );
51 $opts->add( 'offset', '' );
52 $opts->add( 'namespace', '0' );
53 $opts->add( 'username', '' );
54 $opts->add( 'feed', '' );
55 $opts->add( 'tagfilter', '' );
56 $opts->add( 'invert', false );
57 $opts->add( 'size-mode', 'max' );
58 $opts->add( 'size', 0 );
59
60 $this->customFilters = [];
61 Hooks::run( 'SpecialNewPagesFilters', [ $this, &$this->customFilters ] );
62 foreach ( $this->customFilters as $key => $params ) {
63 $opts->add( $key, $params['default'] );
64 }
65
66 // Set values
68 if ( $par ) {
69 $this->parseParams( $par );
70 }
71
72 // Validate
73 $opts->validateIntBounds( 'limit', 0, 5000 );
74 }
75
76 protected function parseParams( $par ) {
77 $bits = preg_split( '/\s*,\s*/', trim( $par ) );
78 foreach ( $bits as $bit ) {
79 if ( 'shownav' == $bit ) {
80 $this->showNavigation = true;
81 }
82 if ( 'hideliu' === $bit ) {
83 $this->opts->setValue( 'hideliu', true );
84 }
85 if ( 'hidepatrolled' == $bit ) {
86 $this->opts->setValue( 'hidepatrolled', true );
87 }
88 if ( 'hidebots' == $bit ) {
89 $this->opts->setValue( 'hidebots', true );
90 }
91 if ( 'showredirs' == $bit ) {
92 $this->opts->setValue( 'hideredirs', false );
93 }
94 if ( is_numeric( $bit ) ) {
95 $this->opts->setValue( 'limit', intval( $bit ) );
96 }
97
98 $m = [];
99 if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
100 $this->opts->setValue( 'limit', intval( $m[1] ) );
101 }
102 // PG offsets not just digits!
103 if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) {
104 $this->opts->setValue( 'offset', intval( $m[1] ) );
105 }
106 if ( preg_match( '/^username=(.*)$/', $bit, $m ) ) {
107 $this->opts->setValue( 'username', $m[1] );
108 }
109 if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
110 $ns = $this->getLanguage()->getNsIndex( $m[1] );
111 if ( $ns !== false ) {
112 $this->opts->setValue( 'namespace', $ns );
113 }
114 }
115 }
116 }
117
123 public function execute( $par ) {
124 $out = $this->getOutput();
125
126 $this->setHeaders();
127 $this->outputHeader();
128
129 $this->showNavigation = !$this->including(); // Maybe changed in setup
130 $this->setup( $par );
131
132 $this->addHelpLink( 'Help:New pages' );
133
134 if ( !$this->including() ) {
135 // Settings
136 $this->form();
137
138 $feedType = $this->opts->getValue( 'feed' );
139 if ( $feedType ) {
140 $this->feed( $feedType );
141
142 return;
143 }
144
145 $allValues = $this->opts->getAllValues();
146 unset( $allValues['feed'] );
147 $out->setFeedAppendQuery( wfArrayToCgi( $allValues ) );
148 }
149
150 $pager = new NewPagesPager( $this, $this->opts );
151 $pager->mLimit = $this->opts->getValue( 'limit' );
152 $pager->mOffset = $this->opts->getValue( 'offset' );
153
154 if ( $pager->getNumRows() ) {
155 $navigation = '';
156 if ( $this->showNavigation ) {
157 $navigation = $pager->getNavigationBar();
158 }
159 $out->addHTML( $navigation . $pager->getBody() . $navigation );
160 } else {
161 $out->addWikiMsg( 'specialpage-empty' );
162 }
163 }
164
165 protected function filterLinks() {
166 // show/hide links
167 $showhide = [ $this->msg( 'show' )->escaped(), $this->msg( 'hide' )->escaped() ];
168
169 // Option value -> message mapping
170 $filters = [
171 'hideliu' => 'rcshowhideliu',
172 'hidepatrolled' => 'rcshowhidepatr',
173 'hidebots' => 'rcshowhidebots',
174 'hideredirs' => 'whatlinkshere-hideredirs'
175 ];
176 foreach ( $this->customFilters as $key => $params ) {
177 $filters[$key] = $params['msg'];
178 }
179
180 // Disable some if needed
181 if ( !User::groupHasPermission( '*', 'createpage' ) ) {
182 unset( $filters['hideliu'] );
183 }
184 if ( !$this->getUser()->useNPPatrol() ) {
185 unset( $filters['hidepatrolled'] );
186 }
187
188 $links = [];
189 $changed = $this->opts->getChangedValues();
190 unset( $changed['offset'] ); // Reset offset if query type changes
191
192 $self = $this->getPageTitle();
194 foreach ( $filters as $key => $msg ) {
195 $onoff = 1 - $this->opts->getValue( $key );
196 $link = $linkRenderer->makeLink(
197 $self,
198 new HtmlArmor( $showhide[$onoff] ),
199 [],
200 [ $key => $onoff ] + $changed
201 );
202 $links[$key] = $this->msg( $msg )->rawParams( $link )->escaped();
203 }
204
205 return $this->getLanguage()->pipeList( $links );
206 }
207
208 protected function form() {
209 $out = $this->getOutput();
210 $out->addModules( 'mediawiki.userSuggest' );
211
212 // Consume values
213 $this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW
214 $namespace = $this->opts->consumeValue( 'namespace' );
215 $username = $this->opts->consumeValue( 'username' );
216 $tagFilterVal = $this->opts->consumeValue( 'tagfilter' );
217 $nsinvert = $this->opts->consumeValue( 'invert' );
218
219 $size = $this->opts->consumeValue( 'size' );
220 $max = $this->opts->consumeValue( 'size-mode' ) === 'max';
221
222 // Check username input validity
223 $ut = Title::makeTitleSafe( NS_USER, $username );
224 $userText = $ut ? $ut->getText() : '';
225
226 // Store query values in hidden fields so that form submission doesn't lose them
227 $hidden = [];
228 foreach ( $this->opts->getUnconsumedValues() as $key => $value ) {
229 $hidden[] = Html::hidden( $key, $value );
230 }
231 $hidden = implode( "\n", $hidden );
232
233 $form = [
234 'namespace' => [
235 'type' => 'namespaceselect',
236 'name' => 'namespace',
237 'label-message' => 'namespace',
238 'default' => $namespace,
239 ],
240 'nsinvert' => [
241 'type' => 'check',
242 'name' => 'invert',
243 'label-message' => 'invert',
244 'default' => $nsinvert,
245 'tooltip' => 'invert',
246 ],
247 'tagFilter' => [
248 'type' => 'tagfilter',
249 'name' => 'tagfilter',
250 'label-raw' => $this->msg( 'tag-filter' )->parse(),
251 'default' => $tagFilterVal,
252 ],
253 'username' => [
254 'type' => 'text',
255 'name' => 'username',
256 'label-message' => 'newpages-username',
257 'default' => $userText,
258 'id' => 'mw-np-username',
259 'size' => 30,
260 'cssclass' => 'mw-autocomplete-user', // used by mediawiki.userSuggest
261 ],
262 'size' => [
263 'type' => 'sizefilter',
264 'name' => 'size',
265 'default' => -$max * $size,
266 ],
267 ];
268
269 $htmlForm = new HTMLForm( $form, $this->getContext() );
270
271 $htmlForm->setSubmitText( $this->msg( 'newpages-submit' )->text() );
272 $htmlForm->setSubmitProgressive();
273 // The form should be visible on each request (inclusive requests with submitted forms), so
274 // return always false here.
275 $htmlForm->setSubmitCallback(
276 function () {
277 return false;
278 }
279 );
280 $htmlForm->setMethod( 'get' );
281 $htmlForm->setWrapperLegend( true );
282 $htmlForm->setWrapperLegendMsg( 'newpages' );
283 $htmlForm->addFooterText( Html::rawElement(
284 'div',
285 null,
286 $this->filterLinks()
287 ) );
288 $htmlForm->show();
289 }
290
296 protected function revisionFromRcResult( stdClass $result, Title $title ) {
297 return new Revision( [
298 'comment' => CommentStore::getStore()->getComment( 'rc_comment', $result )->text,
299 'deleted' => $result->rc_deleted,
300 'user_text' => $result->rc_user_text,
301 'user' => $result->rc_user,
302 'actor' => $result->rc_actor,
303 ], 0, $title );
304 }
305
313 public function formatRow( $result ) {
314 $title = Title::newFromRow( $result );
315
316 // Revision deletion works on revisions,
317 // so cast our recent change row to a revision row.
318 $rev = $this->revisionFromRcResult( $result, $title );
319
320 $classes = [];
321 $attribs = [ 'data-mw-revid' => $result->rev_id ];
322
323 $lang = $this->getLanguage();
324 $dm = $lang->getDirMark();
325
326 $spanTime = Html::element( 'span', [ 'class' => 'mw-newpages-time' ],
327 $lang->userTimeAndDate( $result->rc_timestamp, $this->getUser() )
328 );
330 $time = $linkRenderer->makeKnownLink(
331 $title,
332 new HtmlArmor( $spanTime ),
333 [],
334 [ 'oldid' => $result->rc_this_oldid ]
335 );
336
337 $query = $title->isRedirect() ? [ 'redirect' => 'no' ] : [];
338
339 $plink = $linkRenderer->makeKnownLink(
340 $title,
341 null,
342 [ 'class' => 'mw-newpages-pagename' ],
343 $query
344 );
345 $histLink = $linkRenderer->makeKnownLink(
346 $title,
347 $this->msg( 'hist' )->text(),
348 [],
349 [ 'action' => 'history' ]
350 );
351 $hist = Html::rawElement( 'span', [ 'class' => 'mw-newpages-history' ],
352 $this->msg( 'parentheses' )->rawParams( $histLink )->escaped() );
353
354 $length = Html::rawElement(
355 'span',
356 [ 'class' => 'mw-newpages-length' ],
357 $this->msg( 'brackets' )->rawParams(
358 $this->msg( 'nbytes' )->numParams( $result->length )->escaped()
359 )->escaped()
360 );
361
362 $ulink = Linker::revUserTools( $rev );
363 $comment = Linker::revComment( $rev );
364
365 if ( $this->patrollable( $result ) ) {
366 $classes[] = 'not-patrolled';
367 }
368
369 # Add a class for zero byte pages
370 if ( $result->length == 0 ) {
371 $classes[] = 'mw-newpages-zero-byte-page';
372 }
373
374 # Tags, if any.
375 if ( isset( $result->ts_tags ) ) {
376 list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow(
377 $result->ts_tags,
378 'newpages',
379 $this->getContext()
380 );
381 $classes = array_merge( $classes, $newClasses );
382 } else {
383 $tagDisplay = '';
384 }
385
386 # Display the old title if the namespace/title has been changed
387 $oldTitleText = '';
388 $oldTitle = Title::makeTitle( $result->rc_namespace, $result->rc_title );
389
390 if ( !$title->equals( $oldTitle ) ) {
391 $oldTitleText = $oldTitle->getPrefixedText();
392 $oldTitleText = Html::rawElement(
393 'span',
394 [ 'class' => 'mw-newpages-oldtitle' ],
395 $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped()
396 );
397 }
398
399 $ret = "{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} "
400 . "{$tagDisplay} {$oldTitleText}";
401
402 // Let extensions add data
403 Hooks::run( 'NewPagesLineEnding', [ $this, &$ret, $result, &$classes, &$attribs ] );
404 $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 'isReservedDataAttribute' ] );
405
406 if ( count( $classes ) ) {
407 $attribs['class'] = implode( ' ', $classes );
408 }
409
410 return Html::rawElement( 'li', $attribs, $ret ) . "\n";
411 }
412
419 protected function patrollable( $result ) {
420 return ( $this->getUser()->useNPPatrol() && !$result->rc_patrolled );
421 }
422
428 protected function feed( $type ) {
429 if ( !$this->getConfig()->get( 'Feed' ) ) {
430 $this->getOutput()->addWikiMsg( 'feed-unavailable' );
431
432 return;
433 }
434
435 $feedClasses = $this->getConfig()->get( 'FeedClasses' );
436 if ( !isset( $feedClasses[$type] ) ) {
437 $this->getOutput()->addWikiMsg( 'feed-invalid' );
438
439 return;
440 }
441
442 $feed = new $feedClasses[$type](
443 $this->feedTitle(),
444 $this->msg( 'tagline' )->text(),
445 $this->getPageTitle()->getFullURL()
446 );
447
448 $pager = new NewPagesPager( $this, $this->opts );
449 $limit = $this->opts->getValue( 'limit' );
450 $pager->mLimit = min( $limit, $this->getConfig()->get( 'FeedLimit' ) );
451
452 $feed->outHeader();
453 if ( $pager->getNumRows() > 0 ) {
454 foreach ( $pager->mResult as $row ) {
455 $feed->outItem( $this->feedItem( $row ) );
456 }
457 }
458 $feed->outFooter();
459 }
460
461 protected function feedTitle() {
462 $desc = $this->getDescription();
463 $code = $this->getConfig()->get( 'LanguageCode' );
464 $sitename = $this->getConfig()->get( 'Sitename' );
465
466 return "$sitename - $desc [$code]";
467 }
468
469 protected function feedItem( $row ) {
470 $title = Title::makeTitle( intval( $row->rc_namespace ), $row->rc_title );
471 if ( $title ) {
472 $date = $row->rc_timestamp;
473 $comments = $title->getTalkPage()->getFullURL();
474
475 return new FeedItem(
476 $title->getPrefixedText(),
477 $this->feedItemDesc( $row ),
478 $title->getFullURL(),
479 $date,
480 $this->feedItemAuthor( $row ),
481 $comments
482 );
483 } else {
484 return null;
485 }
486 }
487
488 protected function feedItemAuthor( $row ) {
489 return isset( $row->rc_user_text ) ? $row->rc_user_text : '';
490 }
491
492 protected function feedItemDesc( $row ) {
493 $revision = Revision::newFromId( $row->rev_id );
494 if ( !$revision ) {
495 return '';
496 }
497
498 $content = $revision->getContent();
499 if ( $content === null ) {
500 return '';
501 }
502
503 // XXX: include content model/type in feed item?
504 return '<p>' . htmlspecialchars( $revision->getUserText() ) .
505 $this->msg( 'colon-separator' )->inContentLanguage()->escaped() .
506 htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
507 "</p>\n<hr />\n<div>" .
508 nl2br( htmlspecialchars( $content->serialize() ) ) . "</div>";
509 }
510
511 protected function getGroupName() {
512 return 'changes';
513 }
514
515 protected function getCacheTTL() {
516 return 60 * 5;
517 }
518}
within a display generated by the Derivative if and wherever such third party notices normally appear The contents of the NOTICE file are for informational purposes only and do not modify the License You may add Your own attribution notices within Derivative Works that You alongside or as an addendum to the NOTICE text from the provided that such additional attribution notices cannot be construed as modifying the License You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for or distribution of Your or for any such Derivative Works as a provided Your and distribution of the Work otherwise complies with the conditions stated in this License Submission of Contributions Unless You explicitly state any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this without any additional terms or conditions Notwithstanding the nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions Trademarks This License does not grant permission to use the trade service or product names of the except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file Disclaimer of Warranty Unless required by applicable law or agreed to in Licensor provides the WITHOUT WARRANTIES OR CONDITIONS OF ANY either express or including
wfArrayFilterByKey(array $arr, callable $callback)
Like array_filter with ARRAY_FILTER_USE_KEY, but works pre-5.6.
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
A base class for basic support for outputting syndication feeds in RSS and other formats.
Definition Feed.php:38
static stripComment( $text)
Quickie hack... strip out wikilinks to more legible form from the comment.
Definition Feed.php:223
Helper class to keep track of options when mixing links and form elements.
add( $name, $default, $type=self::AUTO)
Add an option to be handled by this FormOptions instance.
fetchValuesFromRequest(WebRequest $r, $optionKeys=null)
Fetch values for all options (or selected options) from the given WebRequest, making them available f...
validateIntBounds( $name, $min, $max)
Object handling generic submission, CSRF protection, layout and other logic for UI forms.
Definition HTMLForm.php:130
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:28
Shortcut to construct an includable special page.
static revComment(Revision $rev, $local=false, $isPublic=false)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition Linker.php:1480
static revUserTools( $rev, $isPublic=false)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1070
A special page that list newly created pages.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
patrollable( $result)
Should a specific result row provide "patrollable" links?
feed( $type)
Output a subscription feed listing recent edits to this page.
execute( $par)
Show a form for filtering namespace and username.
formatRow( $result)
Format a row, providing the timestamp, links to the page/history, size, user links,...
revisionFromRcResult(stdClass $result, Title $title)
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getDescription()
Returns the name that goes in the <h1> in the special page itself, and also the name that will be l...
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
getContext()
Gets the context this SpecialPage is executed in.
msg( $key)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
getLanguage()
Shortcut to get user's language.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
MediaWiki Linker LinkRenderer null $linkRenderer
Represents a title within MediaWiki.
Definition Title.php:39
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition User.php:5005
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition design.txt:18
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1795
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition hooks.txt:865
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition hooks.txt:2005
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition hooks.txt:864
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3021
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:785
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition hooks.txt:2014
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition hooks.txt:1620
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1777
versus $oldTitle
Definition globals.txt:16
$params
if(!isset( $args[0])) $lang