MediaWiki REL1_30
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
295 protected function revisionFromRcResult( stdClass $result ) {
296 return new Revision( [
297 'comment' => CommentStore::newKey( 'rc_comment' )->getComment( $result )->text,
298 'deleted' => $result->rc_deleted,
299 'user_text' => $result->rc_user_text,
300 'user' => $result->rc_user,
301 ] );
302 }
303
311 public function formatRow( $result ) {
312 $title = Title::newFromRow( $result );
313
314 // Revision deletion works on revisions,
315 // so cast our recent change row to a revision row.
316 $rev = $this->revisionFromRcResult( $result );
317 $rev->setTitle( $title );
318
319 $classes = [];
320 $attribs = [ 'data-mw-revid' => $result->rev_id ];
321
322 $lang = $this->getLanguage();
323 $dm = $lang->getDirMark();
324
325 $spanTime = Html::element( 'span', [ 'class' => 'mw-newpages-time' ],
326 $lang->userTimeAndDate( $result->rc_timestamp, $this->getUser() )
327 );
329 $time = $linkRenderer->makeKnownLink(
330 $title,
331 new HtmlArmor( $spanTime ),
332 [],
333 [ 'oldid' => $result->rc_this_oldid ]
334 );
335
336 $query = $title->isRedirect() ? [ 'redirect' => 'no' ] : [];
337
338 $plink = $linkRenderer->makeKnownLink(
339 $title,
340 null,
341 [ 'class' => 'mw-newpages-pagename' ],
342 $query
343 );
344 $histLink = $linkRenderer->makeKnownLink(
345 $title,
346 $this->msg( 'hist' )->text(),
347 [],
348 [ 'action' => 'history' ]
349 );
350 $hist = Html::rawElement( 'span', [ 'class' => 'mw-newpages-history' ],
351 $this->msg( 'parentheses' )->rawParams( $histLink )->escaped() );
352
353 $length = Html::rawElement(
354 'span',
355 [ 'class' => 'mw-newpages-length' ],
356 $this->msg( 'brackets' )->rawParams(
357 $this->msg( 'nbytes' )->numParams( $result->length )->escaped()
358 )->escaped()
359 );
360
361 $ulink = Linker::revUserTools( $rev );
362 $comment = Linker::revComment( $rev );
363
364 if ( $this->patrollable( $result ) ) {
365 $classes[] = 'not-patrolled';
366 }
367
368 # Add a class for zero byte pages
369 if ( $result->length == 0 ) {
370 $classes[] = 'mw-newpages-zero-byte-page';
371 }
372
373 # Tags, if any.
374 if ( isset( $result->ts_tags ) ) {
375 list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow(
376 $result->ts_tags,
377 'newpages',
378 $this->getContext()
379 );
380 $classes = array_merge( $classes, $newClasses );
381 } else {
382 $tagDisplay = '';
383 }
384
385 # Display the old title if the namespace/title has been changed
386 $oldTitleText = '';
387 $oldTitle = Title::makeTitle( $result->rc_namespace, $result->rc_title );
388
389 if ( !$title->equals( $oldTitle ) ) {
390 $oldTitleText = $oldTitle->getPrefixedText();
391 $oldTitleText = Html::rawElement(
392 'span',
393 [ 'class' => 'mw-newpages-oldtitle' ],
394 $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped()
395 );
396 }
397
398 $ret = "{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} "
399 . "{$tagDisplay} {$oldTitleText}";
400
401 // Let extensions add data
402 Hooks::run( 'NewPagesLineEnding', [ $this, &$ret, $result, &$classes, &$attribs ] );
403 $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 'isReservedDataAttribute' ] );
404
405 if ( count( $classes ) ) {
406 $attribs['class'] = implode( ' ', $classes );
407 }
408
409 return Html::rawElement( 'li', $attribs, $ret ) . "\n";
410 }
411
418 protected function patrollable( $result ) {
419 return ( $this->getUser()->useNPPatrol() && !$result->rc_patrolled );
420 }
421
427 protected function feed( $type ) {
428 if ( !$this->getConfig()->get( 'Feed' ) ) {
429 $this->getOutput()->addWikiMsg( 'feed-unavailable' );
430
431 return;
432 }
433
434 $feedClasses = $this->getConfig()->get( 'FeedClasses' );
435 if ( !isset( $feedClasses[$type] ) ) {
436 $this->getOutput()->addWikiMsg( 'feed-invalid' );
437
438 return;
439 }
440
441 $feed = new $feedClasses[$type](
442 $this->feedTitle(),
443 $this->msg( 'tagline' )->text(),
444 $this->getPageTitle()->getFullURL()
445 );
446
447 $pager = new NewPagesPager( $this, $this->opts );
448 $limit = $this->opts->getValue( 'limit' );
449 $pager->mLimit = min( $limit, $this->getConfig()->get( 'FeedLimit' ) );
450
451 $feed->outHeader();
452 if ( $pager->getNumRows() > 0 ) {
453 foreach ( $pager->mResult as $row ) {
454 $feed->outItem( $this->feedItem( $row ) );
455 }
456 }
457 $feed->outFooter();
458 }
459
460 protected function feedTitle() {
461 $desc = $this->getDescription();
462 $code = $this->getConfig()->get( 'LanguageCode' );
463 $sitename = $this->getConfig()->get( 'Sitename' );
464
465 return "$sitename - $desc [$code]";
466 }
467
468 protected function feedItem( $row ) {
469 $title = Title::makeTitle( intval( $row->rc_namespace ), $row->rc_title );
470 if ( $title ) {
471 $date = $row->rc_timestamp;
472 $comments = $title->getTalkPage()->getFullURL();
473
474 return new FeedItem(
475 $title->getPrefixedText(),
476 $this->feedItemDesc( $row ),
477 $title->getFullURL(),
478 $date,
479 $this->feedItemAuthor( $row ),
480 $comments
481 );
482 } else {
483 return null;
484 }
485 }
486
487 protected function feedItemAuthor( $row ) {
488 return isset( $row->rc_user_text ) ? $row->rc_user_text : '';
489 }
490
491 protected function feedItemDesc( $row ) {
492 $revision = Revision::newFromId( $row->rev_id );
493 if ( !$revision ) {
494 return '';
495 }
496
497 $content = $revision->getContent();
498 if ( $content === null ) {
499 return '';
500 }
501
502 // XXX: include content model/type in feed item?
503 return '<p>' . htmlspecialchars( $revision->getUserText() ) .
504 $this->msg( 'colon-separator' )->inContentLanguage()->escaped() .
505 htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
506 "</p>\n<hr />\n<div>" .
507 nl2br( htmlspecialchars( $content->serialize() ) ) . "</div>";
508 }
509
510 protected function getGroupName() {
511 return 'changes';
512 }
513
514 protected function getCacheTTL() {
515 return 60 * 5;
516 }
517}
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.
static newKey( $key)
Static constructor for easier chaining.
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:178
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:128
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:1470
static revUserTools( $rev, $isPublic=false)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1060
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition Revision.php:116
A special page that list newly created pages.
revisionFromRcResult(stdClass $result)
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,...
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
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:1778
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:863
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:1975
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:862
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:2989
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:783
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:1984
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:1610
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:1760
versus $oldTitle
Definition globals.txt:16
$params
if(!isset( $args[0])) $lang