MediaWiki  1.34.0
SpecialNewpages.php
Go to the documentation of this file.
1 <?php
25 
35  protected $opts;
36  protected $customFilters;
37 
38  protected $showNavigation = false;
39 
40  public function __construct() {
41  parent::__construct( 'Newpages' );
42  }
43 
47  protected function setup( $par ) {
48  $opts = new FormOptions();
49  $this->opts = $opts; // bind
50  $opts->add( 'hideliu', false );
51  $opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'newpageshidepatrolled' ) );
52  $opts->add( 'hidebots', false );
53  $opts->add( 'hideredirs', true );
54  $opts->add( 'limit', $this->getUser()->getIntOption( 'rclimit' ) );
55  $opts->add( 'offset', '' );
56  $opts->add( 'namespace', '0' );
57  $opts->add( 'username', '' );
58  $opts->add( 'feed', '' );
59  $opts->add( 'tagfilter', '' );
60  $opts->add( 'invert', false );
61  $opts->add( 'associated', false );
62  $opts->add( 'size-mode', 'max' );
63  $opts->add( 'size', 0 );
64 
65  $this->customFilters = [];
66  Hooks::run( 'SpecialNewPagesFilters', [ $this, &$this->customFilters ] );
67  foreach ( $this->customFilters as $key => $params ) {
68  $opts->add( $key, $params['default'] );
69  }
70 
72  if ( $par ) {
73  $this->parseParams( $par );
74  }
75 
76  $opts->validateIntBounds( 'limit', 0, 5000 );
77  }
78 
82  protected function parseParams( $par ) {
83  $bits = preg_split( '/\s*,\s*/', trim( $par ) );
84  foreach ( $bits as $bit ) {
85  if ( $bit === 'shownav' ) {
86  $this->showNavigation = true;
87  }
88  if ( $bit === 'hideliu' ) {
89  $this->opts->setValue( 'hideliu', true );
90  }
91  if ( $bit === 'hidepatrolled' ) {
92  $this->opts->setValue( 'hidepatrolled', true );
93  }
94  if ( $bit === 'hidebots' ) {
95  $this->opts->setValue( 'hidebots', true );
96  }
97  if ( $bit === 'showredirs' ) {
98  $this->opts->setValue( 'hideredirs', false );
99  }
100  if ( is_numeric( $bit ) ) {
101  $this->opts->setValue( 'limit', intval( $bit ) );
102  }
103 
104  $m = [];
105  if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
106  $this->opts->setValue( 'limit', intval( $m[1] ) );
107  }
108  // PG offsets not just digits!
109  if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) {
110  $this->opts->setValue( 'offset', intval( $m[1] ) );
111  }
112  if ( preg_match( '/^username=(.*)$/', $bit, $m ) ) {
113  $this->opts->setValue( 'username', $m[1] );
114  }
115  if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
116  $ns = $this->getLanguage()->getNsIndex( $m[1] );
117  if ( $ns !== false ) {
118  $this->opts->setValue( 'namespace', $ns );
119  }
120  }
121  }
122  }
123 
129  public function execute( $par ) {
130  $out = $this->getOutput();
131 
132  $this->setHeaders();
133  $this->outputHeader();
134 
135  $this->showNavigation = !$this->including(); // Maybe changed in setup
136  $this->setup( $par );
137 
138  $this->addHelpLink( 'Help:New pages' );
139 
140  if ( !$this->including() ) {
141  // Settings
142  $this->form();
143 
144  $feedType = $this->opts->getValue( 'feed' );
145  if ( $feedType ) {
146  $this->feed( $feedType );
147 
148  return;
149  }
150 
151  $allValues = $this->opts->getAllValues();
152  unset( $allValues['feed'] );
153  $out->setFeedAppendQuery( wfArrayToCgi( $allValues ) );
154  }
155 
156  $pager = new NewPagesPager( $this, $this->opts );
157  $pager->mLimit = $this->opts->getValue( 'limit' );
158  $pager->mOffset = $this->opts->getValue( 'offset' );
159 
160  if ( $pager->getNumRows() ) {
161  $navigation = '';
162  if ( $this->showNavigation ) {
163  $navigation = $pager->getNavigationBar();
164  }
165  $out->addHTML( $navigation . $pager->getBody() . $navigation );
166  // Add styles for change tags
167  $out->addModuleStyles( 'mediawiki.interface.helpers.styles' );
168  } else {
169  $out->addWikiMsg( 'specialpage-empty' );
170  }
171  }
172 
173  protected function filterLinks() {
174  // show/hide links
175  $showhide = [ $this->msg( 'show' )->escaped(), $this->msg( 'hide' )->escaped() ];
176 
177  // Option value -> message mapping
178  $filters = [
179  'hideliu' => 'rcshowhideliu',
180  'hidepatrolled' => 'rcshowhidepatr',
181  'hidebots' => 'rcshowhidebots',
182  'hideredirs' => 'whatlinkshere-hideredirs'
183  ];
184  foreach ( $this->customFilters as $key => $params ) {
185  $filters[$key] = $params['msg'];
186  }
187 
188  // Disable some if needed
189  if ( !MediaWikiServices::getInstance()->getPermissionManager()
190  ->groupHasPermission( '*', 'createpage' )
191  ) {
192  unset( $filters['hideliu'] );
193  }
194  if ( !$this->getUser()->useNPPatrol() ) {
195  unset( $filters['hidepatrolled'] );
196  }
197 
198  $links = [];
199  $changed = $this->opts->getChangedValues();
200  unset( $changed['offset'] ); // Reset offset if query type changes
201 
202  // wfArrayToCgi(), called from LinkRenderer/Title, will not output null and false values
203  // to the URL, which would omit some options (T158504). Fix it by explicitly setting them
204  // to 0 or 1.
205  // Also do this only for boolean options, not eg. namespace or tagfilter
206  foreach ( $changed as $key => $value ) {
207  if ( array_key_exists( $key, $filters ) ) {
208  $changed[$key] = $changed[$key] ? '1' : '0';
209  }
210  }
211 
212  $self = $this->getPageTitle();
213  $linkRenderer = $this->getLinkRenderer();
214  foreach ( $filters as $key => $msg ) {
215  $onoff = 1 - $this->opts->getValue( $key );
216  $link = $linkRenderer->makeLink(
217  $self,
218  new HtmlArmor( $showhide[$onoff] ),
219  [],
220  [ $key => $onoff ] + $changed
221  );
222  $links[$key] = $this->msg( $msg )->rawParams( $link )->escaped();
223  }
224 
225  return $this->getLanguage()->pipeList( $links );
226  }
227 
228  protected function form() {
229  $out = $this->getOutput();
230 
231  // Consume values
232  $this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW
233  $namespace = $this->opts->consumeValue( 'namespace' );
234  $username = $this->opts->consumeValue( 'username' );
235  $tagFilterVal = $this->opts->consumeValue( 'tagfilter' );
236  $nsinvert = $this->opts->consumeValue( 'invert' );
237  $nsassociated = $this->opts->consumeValue( 'associated' );
238 
239  $size = $this->opts->consumeValue( 'size' );
240  $max = $this->opts->consumeValue( 'size-mode' ) === 'max';
241 
242  // Check username input validity
243  $ut = Title::makeTitleSafe( NS_USER, $username );
244  $userText = $ut ? $ut->getText() : '';
245 
246  $formDescriptor = [
247  'namespace' => [
248  'type' => 'namespaceselect',
249  'name' => 'namespace',
250  'label-message' => 'namespace',
251  'default' => $namespace,
252  ],
253  'nsinvert' => [
254  'type' => 'check',
255  'name' => 'invert',
256  'label-message' => 'invert',
257  'default' => $nsinvert,
258  'tooltip' => 'invert',
259  ],
260  'nsassociated' => [
261  'type' => 'check',
262  'name' => 'associated',
263  'label-message' => 'namespace_association',
264  'default' => $nsassociated,
265  'tooltip' => 'namespace_association',
266  ],
267  'tagFilter' => [
268  'type' => 'tagfilter',
269  'name' => 'tagfilter',
270  'label-raw' => $this->msg( 'tag-filter' )->parse(),
271  'default' => $tagFilterVal,
272  ],
273  'username' => [
274  'type' => 'user',
275  'name' => 'username',
276  'label-message' => 'newpages-username',
277  'default' => $userText,
278  'id' => 'mw-np-username',
279  'size' => 30,
280  ],
281  'size' => [
282  'type' => 'sizefilter',
283  'name' => 'size',
284  'default' => -$max * $size,
285  ],
286  ];
287 
288  $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() );
289 
290  // Store query values in hidden fields so that form submission doesn't lose them
291  foreach ( $this->opts->getUnconsumedValues() as $key => $value ) {
292  $htmlForm->addHiddenField( $key, $value );
293  }
294 
295  $htmlForm
296  ->setMethod( 'get' )
297  ->setFormIdentifier( 'newpagesform' )
298  // The form should be visible on each request (inclusive requests with submitted forms), so
299  // return always false here.
300  ->setSubmitCallback(
301  function () {
302  return false;
303  }
304  )
305  ->setSubmitText( $this->msg( 'newpages-submit' )->text() )
306  ->setWrapperLegend( $this->msg( 'newpages' )->text() )
307  ->addFooterText( Html::rawElement(
308  'div',
309  null,
310  $this->filterLinks()
311  ) )
312  ->show();
313  $out->addModuleStyles( 'mediawiki.special' );
314  }
315 
321  protected function revisionFromRcResult( stdClass $result, Title $title ) {
322  return new Revision( [
323  'comment' => CommentStore::getStore()->getComment( 'rc_comment', $result )->text,
324  'deleted' => $result->rc_deleted,
325  'user_text' => $result->rc_user_text,
326  'user' => $result->rc_user,
327  'actor' => $result->rc_actor,
328  ], 0, $title );
329  }
330 
338  public function formatRow( $result ) {
339  $title = Title::newFromRow( $result );
340 
341  // Revision deletion works on revisions,
342  // so cast our recent change row to a revision row.
343  $rev = $this->revisionFromRcResult( $result, $title );
344 
345  $classes = [];
346  $attribs = [ 'data-mw-revid' => $result->rev_id ];
347 
348  $lang = $this->getLanguage();
349  $dm = $lang->getDirMark();
350 
351  $spanTime = Html::element( 'span', [ 'class' => 'mw-newpages-time' ],
352  $lang->userTimeAndDate( $result->rc_timestamp, $this->getUser() )
353  );
354  $linkRenderer = $this->getLinkRenderer();
355  $time = $linkRenderer->makeKnownLink(
356  $title,
357  new HtmlArmor( $spanTime ),
358  [],
359  [ 'oldid' => $result->rc_this_oldid ]
360  );
361 
362  $query = $title->isRedirect() ? [ 'redirect' => 'no' ] : [];
363 
364  $plink = $linkRenderer->makeKnownLink(
365  $title,
366  null,
367  [ 'class' => 'mw-newpages-pagename' ],
368  $query
369  );
370  $histLink = $linkRenderer->makeKnownLink(
371  $title,
372  $this->msg( 'hist' )->text(),
373  [],
374  [ 'action' => 'history' ]
375  );
376  $hist = Html::rawElement( 'span', [ 'class' => 'mw-newpages-history' ],
377  $this->msg( 'parentheses' )->rawParams( $histLink )->escaped() );
378 
379  $length = Html::rawElement(
380  'span',
381  [ 'class' => 'mw-newpages-length' ],
382  $this->msg( 'brackets' )->rawParams(
383  $this->msg( 'nbytes' )->numParams( $result->length )->escaped()
384  )->escaped()
385  );
386 
387  $ulink = Linker::revUserTools( $rev );
388  $comment = Linker::revComment( $rev );
389 
390  if ( $this->patrollable( $result ) ) {
391  $classes[] = 'not-patrolled';
392  }
393 
394  # Add a class for zero byte pages
395  if ( $result->length == 0 ) {
396  $classes[] = 'mw-newpages-zero-byte-page';
397  }
398 
399  # Tags, if any.
400  if ( isset( $result->ts_tags ) ) {
401  list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow(
402  $result->ts_tags,
403  'newpages',
404  $this->getContext()
405  );
406  $classes = array_merge( $classes, $newClasses );
407  } else {
408  $tagDisplay = '';
409  }
410 
411  # Display the old title if the namespace/title has been changed
412  $oldTitleText = '';
413  $oldTitle = Title::makeTitle( $result->rc_namespace, $result->rc_title );
414 
415  if ( !$title->equals( $oldTitle ) ) {
416  $oldTitleText = $oldTitle->getPrefixedText();
417  $oldTitleText = Html::rawElement(
418  'span',
419  [ 'class' => 'mw-newpages-oldtitle' ],
420  $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped()
421  );
422  }
423 
424  $ret = "{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} "
425  . "{$tagDisplay} {$oldTitleText}";
426 
427  // Let extensions add data
428  Hooks::run( 'NewPagesLineEnding', [ $this, &$ret, $result, &$classes, &$attribs ] );
429  $attribs = array_filter( $attribs,
430  [ Sanitizer::class, 'isReservedDataAttribute' ],
431  ARRAY_FILTER_USE_KEY
432  );
433 
434  if ( count( $classes ) ) {
435  $attribs['class'] = implode( ' ', $classes );
436  }
437 
438  return Html::rawElement( 'li', $attribs, $ret ) . "\n";
439  }
440 
447  protected function patrollable( $result ) {
448  return ( $this->getUser()->useNPPatrol() && !$result->rc_patrolled );
449  }
450 
456  protected function feed( $type ) {
457  if ( !$this->getConfig()->get( 'Feed' ) ) {
458  $this->getOutput()->addWikiMsg( 'feed-unavailable' );
459 
460  return;
461  }
462 
463  $feedClasses = $this->getConfig()->get( 'FeedClasses' );
464  if ( !isset( $feedClasses[$type] ) ) {
465  $this->getOutput()->addWikiMsg( 'feed-invalid' );
466 
467  return;
468  }
469 
470  $feed = new $feedClasses[$type](
471  $this->feedTitle(),
472  $this->msg( 'tagline' )->text(),
473  $this->getPageTitle()->getFullURL()
474  );
475 
476  $pager = new NewPagesPager( $this, $this->opts );
477  $limit = $this->opts->getValue( 'limit' );
478  $pager->mLimit = min( $limit, $this->getConfig()->get( 'FeedLimit' ) );
479 
480  $feed->outHeader();
481  if ( $pager->getNumRows() > 0 ) {
482  foreach ( $pager->mResult as $row ) {
483  $feed->outItem( $this->feedItem( $row ) );
484  }
485  }
486  $feed->outFooter();
487  }
488 
489  protected function feedTitle() {
490  $desc = $this->getDescription();
491  $code = $this->getConfig()->get( 'LanguageCode' );
492  $sitename = $this->getConfig()->get( 'Sitename' );
493 
494  return "$sitename - $desc [$code]";
495  }
496 
497  protected function feedItem( $row ) {
498  $title = Title::makeTitle( intval( $row->rc_namespace ), $row->rc_title );
499  if ( $title ) {
500  $date = $row->rc_timestamp;
501  $comments = $title->getTalkPage()->getFullURL();
502 
503  return new FeedItem(
504  $title->getPrefixedText(),
505  $this->feedItemDesc( $row ),
506  $title->getFullURL(),
507  $date,
508  $this->feedItemAuthor( $row ),
509  $comments
510  );
511  } else {
512  return null;
513  }
514  }
515 
516  protected function feedItemAuthor( $row ) {
517  return $row->rc_user_text ?? '';
518  }
519 
520  protected function feedItemDesc( $row ) {
521  $revision = Revision::newFromId( $row->rev_id );
522  if ( !$revision ) {
523  return '';
524  }
525 
526  $content = $revision->getContent();
527  if ( $content === null ) {
528  return '';
529  }
530 
531  // XXX: include content model/type in feed item?
532  return '<p>' . htmlspecialchars( $revision->getUserText() ) .
533  $this->msg( 'colon-separator' )->inContentLanguage()->escaped() .
534  htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
535  "</p>\n<hr />\n<div>" .
536  nl2br( htmlspecialchars( $content->serialize() ) ) . "</div>";
537  }
538 
539  protected function getGroupName() {
540  return 'changes';
541  }
542 
543  protected function getCacheTTL() {
544  return 60 * 5;
545  }
546 }
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:672
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:792
FeedItem
A base class for outputting syndication feeds (e.g.
Definition: FeedItem.php:33
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:28
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:119
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:719
SpecialNewpages\__construct
__construct()
Definition: SpecialNewpages.php:40
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
SpecialNewpages\execute
execute( $par)
Show a form for filtering namespace and username.
Definition: SpecialNewpages.php:129
FeedItem\stripComment
static stripComment( $text)
Quickie hack...
Definition: FeedItem.php:218
Linker\revComment
static revComment(Revision $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition: Linker.php:1577
IncludableSpecialPage
Shortcut to construct an includable special page.
Definition: IncludableSpecialPage.php:29
FormOptions\validateIntBounds
validateIntBounds( $name, $min, $max)
Definition: FormOptions.php:255
SpecialPage\getLanguage
getLanguage()
Shortcut to get user's language.
Definition: SpecialPage.php:749
SpecialNewpages\form
form()
Definition: SpecialNewpages.php:228
FormOptions\fetchValuesFromRequest
fetchValuesFromRequest(WebRequest $r, $optionKeys=null)
Fetch values for all options (or selected options) from the given WebRequest, making them available f...
Definition: FormOptions.php:346
SpecialNewpages\feed
feed( $type)
Output a subscription feed listing recent edits to this page.
Definition: SpecialNewpages.php:456
SpecialNewpages\setup
setup( $par)
Definition: SpecialNewpages.php:47
Revision
Definition: Revision.php:40
FormOptions\add
add( $name, $default, $type=self::AUTO)
Add an option to be handled by this FormOptions instance.
Definition: FormOptions.php:83
SpecialPage\getDescription
getDescription()
Returns the name that goes in the <h1> in the special page itself, and also the name that will be l...
Definition: SpecialPage.php:661
SpecialPage\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: SpecialPage.php:828
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:758
SpecialNewpages\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialNewpages.php:539
SpecialNewpages\$opts
FormOptions $opts
Definition: SpecialNewpages.php:35
Title\newFromRow
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:518
getPermissionManager
getPermissionManager()
SpecialNewpages\feedItem
feedItem( $row)
Definition: SpecialNewpages.php:497
$title
$title
Definition: testCompression.php:34
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:537
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:729
SpecialNewpages\parseParams
parseParams( $par)
Definition: SpecialNewpages.php:82
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
Linker\revUserTools
static revUserTools( $rev, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1124
SpecialPage\getContext
getContext()
Gets the context this SpecialPage is executed in.
Definition: SpecialPage.php:692
SpecialNewpages\feedTitle
feedTitle()
Definition: SpecialNewpages.php:489
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:613
$content
$content
Definition: router.php:78
SpecialNewpages\$showNavigation
$showNavigation
Definition: SpecialNewpages.php:38
SpecialNewpages\$customFilters
$customFilters
Definition: SpecialNewpages.php:36
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:709
SpecialNewpages\filterLinks
filterLinks()
Definition: SpecialNewpages.php:173
NewPagesPager
Definition: NewPagesPager.php:27
SpecialNewpages\formatRow
formatRow( $result)
Format a row, providing the timestamp, links to the page/history, size, user links,...
Definition: SpecialNewpages.php:338
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:904
$self
$self
Definition: doMaintenance.php:55
Title
Represents a title within MediaWiki.
Definition: Title.php:42
SpecialNewpages\revisionFromRcResult
revisionFromRcResult(stdClass $result, Title $title)
Definition: SpecialNewpages.php:321
SpecialNewpages
A special page that list newly created pages.
Definition: SpecialNewpages.php:31
SpecialNewpages\getCacheTTL
getCacheTTL()
Definition: SpecialNewpages.php:543
NS_USER
const NS_USER
Definition: Defines.php:62
SpecialNewpages\feedItemAuthor
feedItemAuthor( $row)
Definition: SpecialNewpages.php:516
FormOptions
Helper class to keep track of options when mixing links and form elements.
Definition: FormOptions.php:35
SpecialPage\$linkRenderer
MediaWiki Linker LinkRenderer null $linkRenderer
Definition: SpecialPage.php:67
CommentStore\getStore
static getStore()
Definition: CommentStore.php:139
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
HTMLForm\factory
static factory( $displayFormat,... $arguments)
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:303
SpecialPage\outputHeader
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
Definition: SpecialPage.php:639
ChangeTags\formatSummaryRow
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:94
SpecialPage\including
including( $x=null)
Whether the special page is being evaluated via transclusion.
Definition: SpecialPage.php:230
SpecialNewpages\patrollable
patrollable( $result)
Should a specific result row provide "patrollable" links?
Definition: SpecialNewpages.php:447
SpecialNewpages\feedItemDesc
feedItemDesc( $row)
Definition: SpecialNewpages.php:520
wfArrayToCgi
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
Definition: GlobalFunctions.php:347
$type
$type
Definition: testCompression.php:48