[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Implements Special:Newpages 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @ingroup SpecialPage 22 */ 23 24 /** 25 * A special page that list newly created pages 26 * 27 * @ingroup SpecialPage 28 */ 29 class SpecialNewpages extends IncludableSpecialPage { 30 /** 31 * @var FormOptions 32 */ 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 58 $this->customFilters = array(); 59 wfRunHooks( 'SpecialNewPagesFilters', array( $this, &$this->customFilters ) ); 60 foreach ( $this->customFilters as $key => $params ) { 61 $opts->add( $key, $params['default'] ); 62 } 63 64 // Set values 65 $opts->fetchValuesFromRequest( $this->getRequest() ); 66 if ( $par ) { 67 $this->parseParams( $par ); 68 } 69 70 // Validate 71 $opts->validateIntBounds( 'limit', 0, 5000 ); 72 } 73 74 protected function parseParams( $par ) { 75 $bits = preg_split( '/\s*,\s*/', trim( $par ) ); 76 foreach ( $bits as $bit ) { 77 if ( 'shownav' == $bit ) { 78 $this->showNavigation = true; 79 } 80 if ( 'hideliu' === $bit ) { 81 $this->opts->setValue( 'hideliu', true ); 82 } 83 if ( 'hidepatrolled' == $bit ) { 84 $this->opts->setValue( 'hidepatrolled', true ); 85 } 86 if ( 'hidebots' == $bit ) { 87 $this->opts->setValue( 'hidebots', true ); 88 } 89 if ( 'showredirs' == $bit ) { 90 $this->opts->setValue( 'hideredirs', false ); 91 } 92 if ( is_numeric( $bit ) ) { 93 $this->opts->setValue( 'limit', intval( $bit ) ); 94 } 95 96 $m = array(); 97 if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { 98 $this->opts->setValue( 'limit', intval( $m[1] ) ); 99 } 100 // PG offsets not just digits! 101 if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) { 102 $this->opts->setValue( 'offset', intval( $m[1] ) ); 103 } 104 if ( preg_match( '/^username=(.*)$/', $bit, $m ) ) { 105 $this->opts->setValue( 'username', $m[1] ); 106 } 107 if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) { 108 $ns = $this->getLanguage()->getNsIndex( $m[1] ); 109 if ( $ns !== false ) { 110 $this->opts->setValue( 'namespace', $ns ); 111 } 112 } 113 } 114 } 115 116 /** 117 * Show a form for filtering namespace and username 118 * 119 * @param string $par 120 */ 121 public function execute( $par ) { 122 $out = $this->getOutput(); 123 124 $this->setHeaders(); 125 $this->outputHeader(); 126 127 $this->showNavigation = !$this->including(); // Maybe changed in setup 128 $this->setup( $par ); 129 130 if ( !$this->including() ) { 131 // Settings 132 $this->form(); 133 134 $feedType = $this->opts->getValue( 'feed' ); 135 if ( $feedType ) { 136 $this->feed( $feedType ); 137 138 return; 139 } 140 141 $allValues = $this->opts->getAllValues(); 142 unset( $allValues['feed'] ); 143 $out->setFeedAppendQuery( wfArrayToCgi( $allValues ) ); 144 } 145 146 $pager = new NewPagesPager( $this, $this->opts ); 147 $pager->mLimit = $this->opts->getValue( 'limit' ); 148 $pager->mOffset = $this->opts->getValue( 'offset' ); 149 150 if ( $pager->getNumRows() ) { 151 $navigation = ''; 152 if ( $this->showNavigation ) { 153 $navigation = $pager->getNavigationBar(); 154 } 155 $out->addHTML( $navigation . $pager->getBody() . $navigation ); 156 } else { 157 $out->addWikiMsg( 'specialpage-empty' ); 158 } 159 } 160 161 protected function filterLinks() { 162 // show/hide links 163 $showhide = array( $this->msg( 'show' )->escaped(), $this->msg( 'hide' )->escaped() ); 164 165 // Option value -> message mapping 166 $filters = array( 167 'hideliu' => 'rcshowhideliu', 168 'hidepatrolled' => 'rcshowhidepatr', 169 'hidebots' => 'rcshowhidebots', 170 'hideredirs' => 'whatlinkshere-hideredirs' 171 ); 172 foreach ( $this->customFilters as $key => $params ) { 173 $filters[$key] = $params['msg']; 174 } 175 176 // Disable some if needed 177 if ( !User::groupHasPermission( '*', 'createpage' ) ) { 178 unset( $filters['hideliu'] ); 179 } 180 if ( !$this->getUser()->useNPPatrol() ) { 181 unset( $filters['hidepatrolled'] ); 182 } 183 184 $links = array(); 185 $changed = $this->opts->getChangedValues(); 186 unset( $changed['offset'] ); // Reset offset if query type changes 187 188 $self = $this->getPageTitle(); 189 foreach ( $filters as $key => $msg ) { 190 $onoff = 1 - $this->opts->getValue( $key ); 191 $link = Linker::link( $self, $showhide[$onoff], array(), 192 array( $key => $onoff ) + $changed 193 ); 194 $links[$key] = $this->msg( $msg )->rawParams( $link )->escaped(); 195 } 196 197 return $this->getLanguage()->pipeList( $links ); 198 } 199 200 protected function form() { 201 // Consume values 202 $this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW 203 $namespace = $this->opts->consumeValue( 'namespace' ); 204 $username = $this->opts->consumeValue( 'username' ); 205 $tagFilterVal = $this->opts->consumeValue( 'tagfilter' ); 206 $nsinvert = $this->opts->consumeValue( 'invert' ); 207 208 // Check username input validity 209 $ut = Title::makeTitleSafe( NS_USER, $username ); 210 $userText = $ut ? $ut->getText() : ''; 211 212 // Store query values in hidden fields so that form submission doesn't lose them 213 $hidden = array(); 214 foreach ( $this->opts->getUnconsumedValues() as $key => $value ) { 215 $hidden[] = Html::hidden( $key, $value ); 216 } 217 $hidden = implode( "\n", $hidden ); 218 219 $tagFilter = ChangeTags::buildTagFilterSelector( $tagFilterVal ); 220 if ( $tagFilter ) { 221 list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter; 222 } 223 224 $form = Xml::openElement( 'form', array( 'action' => wfScript() ) ) . 225 Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . 226 Xml::fieldset( $this->msg( 'newpages' )->text() ) . 227 Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) . 228 '<tr> 229 <td class="mw-label">' . 230 Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) . 231 '</td> 232 <td class="mw-input">' . 233 Html::namespaceSelector( 234 array( 235 'selected' => $namespace, 236 'all' => 'all', 237 ), array( 238 'name' => 'namespace', 239 'id' => 'namespace', 240 'class' => 'namespaceselector', 241 ) 242 ) . ' ' . 243 Xml::checkLabel( 244 $this->msg( 'invert' )->text(), 245 'invert', 246 'nsinvert', 247 $nsinvert, 248 array( 'title' => $this->msg( 'tooltip-invert' )->text() ) 249 ) . 250 '</td> 251 </tr>' . ( $tagFilter ? ( 252 '<tr> 253 <td class="mw-label">' . 254 $tagFilterLabel . 255 '</td> 256 <td class="mw-input">' . 257 $tagFilterSelector . 258 '</td> 259 </tr>' ) : '' ) . 260 '<tr> 261 <td class="mw-label">' . 262 Xml::label( $this->msg( 'newpages-username' )->text(), 'mw-np-username' ) . 263 '</td> 264 <td class="mw-input">' . 265 Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) . 266 '</td> 267 </tr>' . 268 '<tr> <td></td> 269 <td class="mw-submit">' . 270 Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . 271 '</td> 272 </tr>' . 273 '<tr> 274 <td></td> 275 <td class="mw-input">' . 276 $this->filterLinks() . 277 '</td> 278 </tr>' . 279 Xml::closeElement( 'table' ) . 280 Xml::closeElement( 'fieldset' ) . 281 $hidden . 282 Xml::closeElement( 'form' ); 283 284 $this->getOutput()->addHTML( $form ); 285 } 286 287 /** 288 * Format a row, providing the timestamp, links to the page/history, 289 * size, user links, and a comment 290 * 291 * @param object $result Result row 292 * @return string 293 */ 294 public function formatRow( $result ) { 295 $title = Title::newFromRow( $result ); 296 297 # Revision deletion works on revisions, so we should cast one 298 $row = array( 299 'comment' => $result->rc_comment, 300 'deleted' => $result->rc_deleted, 301 'user_text' => $result->rc_user_text, 302 'user' => $result->rc_user, 303 ); 304 $rev = new Revision( $row ); 305 $rev->setTitle( $title ); 306 307 $classes = array(); 308 309 $lang = $this->getLanguage(); 310 $dm = $lang->getDirMark(); 311 312 $spanTime = Html::element( 'span', array( 'class' => 'mw-newpages-time' ), 313 $lang->userTimeAndDate( $result->rc_timestamp, $this->getUser() ) 314 ); 315 $time = Linker::linkKnown( 316 $title, 317 $spanTime, 318 array(), 319 array( 'oldid' => $result->rc_this_oldid ), 320 array() 321 ); 322 323 $query = array( 'redirect' => 'no' ); 324 325 // Linker::linkKnown() uses 'known' and 'noclasses' options. 326 // This breaks the colouration for stubs. 327 $plink = Linker::link( 328 $title, 329 null, 330 array( 'class' => 'mw-newpages-pagename' ), 331 $query, 332 array( 'known' ) 333 ); 334 $histLink = Linker::linkKnown( 335 $title, 336 $this->msg( 'hist' )->escaped(), 337 array(), 338 array( 'action' => 'history' ) 339 ); 340 $hist = Html::rawElement( 'span', array( 'class' => 'mw-newpages-history' ), 341 $this->msg( 'parentheses' )->rawParams( $histLink )->escaped() ); 342 343 $length = Html::element( 344 'span', 345 array( 'class' => 'mw-newpages-length' ), 346 $this->msg( 'brackets' )->params( $this->msg( 'nbytes' ) 347 ->numParams( $result->length )->text() 348 ) 349 ); 350 351 $ulink = Linker::revUserTools( $rev ); 352 $comment = Linker::revComment( $rev ); 353 354 if ( $this->patrollable( $result ) ) { 355 $classes[] = 'not-patrolled'; 356 } 357 358 # Add a class for zero byte pages 359 if ( $result->length == 0 ) { 360 $classes[] = 'mw-newpages-zero-byte-page'; 361 } 362 363 # Tags, if any. 364 if ( isset( $result->ts_tags ) ) { 365 list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( 366 $result->ts_tags, 367 'newpages' 368 ); 369 $classes = array_merge( $classes, $newClasses ); 370 } else { 371 $tagDisplay = ''; 372 } 373 374 $css = count( $classes ) ? ' class="' . implode( ' ', $classes ) . '"' : ''; 375 376 # Display the old title if the namespace/title has been changed 377 $oldTitleText = ''; 378 $oldTitle = Title::makeTitle( $result->rc_namespace, $result->rc_title ); 379 380 if ( !$title->equals( $oldTitle ) ) { 381 $oldTitleText = $oldTitle->getPrefixedText(); 382 $oldTitleText = $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped(); 383 } 384 385 return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} " 386 . "{$dm}{$ulink} {$comment} {$tagDisplay} {$oldTitleText}</li>\n"; 387 } 388 389 /** 390 * Should a specific result row provide "patrollable" links? 391 * 392 * @param object $result Result row 393 * @return bool 394 */ 395 protected function patrollable( $result ) { 396 return ( $this->getUser()->useNPPatrol() && !$result->rc_patrolled ); 397 } 398 399 /** 400 * Output a subscription feed listing recent edits to this page. 401 * 402 * @param string $type 403 */ 404 protected function feed( $type ) { 405 if ( !$this->getConfig()->get( 'Feed' ) ) { 406 $this->getOutput()->addWikiMsg( 'feed-unavailable' ); 407 408 return; 409 } 410 411 $feedClasses = $this->getConfig()->get( 'FeedClasses' ); 412 if ( !isset( $feedClasses[$type] ) ) { 413 $this->getOutput()->addWikiMsg( 'feed-invalid' ); 414 415 return; 416 } 417 418 $feed = new $feedClasses[$type]( 419 $this->feedTitle(), 420 $this->msg( 'tagline' )->text(), 421 $this->getPageTitle()->getFullURL() 422 ); 423 424 $pager = new NewPagesPager( $this, $this->opts ); 425 $limit = $this->opts->getValue( 'limit' ); 426 $pager->mLimit = min( $limit, $this->getConfig()->get( 'FeedLimit' ) ); 427 428 $feed->outHeader(); 429 if ( $pager->getNumRows() > 0 ) { 430 foreach ( $pager->mResult as $row ) { 431 $feed->outItem( $this->feedItem( $row ) ); 432 } 433 } 434 $feed->outFooter(); 435 } 436 437 protected function feedTitle() { 438 $desc = $this->getDescription(); 439 $code = $this->getConfig()->get( 'LanguageCode' ); 440 $sitename = $this->getConfig()->get( 'Sitename' ); 441 442 return "$sitename - $desc [$code]"; 443 } 444 445 protected function feedItem( $row ) { 446 $title = Title::makeTitle( intval( $row->rc_namespace ), $row->rc_title ); 447 if ( $title ) { 448 $date = $row->rc_timestamp; 449 $comments = $title->getTalkPage()->getFullURL(); 450 451 return new FeedItem( 452 $title->getPrefixedText(), 453 $this->feedItemDesc( $row ), 454 $title->getFullURL(), 455 $date, 456 $this->feedItemAuthor( $row ), 457 $comments 458 ); 459 } else { 460 return null; 461 } 462 } 463 464 protected function feedItemAuthor( $row ) { 465 return isset( $row->rc_user_text ) ? $row->rc_user_text : ''; 466 } 467 468 protected function feedItemDesc( $row ) { 469 $revision = Revision::newFromId( $row->rev_id ); 470 if ( $revision ) { 471 //XXX: include content model/type in feed item? 472 return '<p>' . htmlspecialchars( $revision->getUserText() ) . 473 $this->msg( 'colon-separator' )->inContentLanguage()->escaped() . 474 htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . 475 "</p>\n<hr />\n<div>" . 476 nl2br( htmlspecialchars( $revision->getContent()->serialize() ) ) . "</div>"; 477 } 478 479 return ''; 480 } 481 482 protected function getGroupName() { 483 return 'changes'; 484 } 485 } 486 487 /** 488 * @ingroup SpecialPage Pager 489 */ 490 class NewPagesPager extends ReverseChronologicalPager { 491 // Stored opts 492 protected $opts; 493 494 /** 495 * @var HtmlForm 496 */ 497 protected $mForm; 498 499 function __construct( $form, FormOptions $opts ) { 500 parent::__construct( $form->getContext() ); 501 $this->mForm = $form; 502 $this->opts = $opts; 503 } 504 505 function getQueryInfo() { 506 $conds = array(); 507 $conds['rc_new'] = 1; 508 509 $namespace = $this->opts->getValue( 'namespace' ); 510 $namespace = ( $namespace === 'all' ) ? false : intval( $namespace ); 511 512 $username = $this->opts->getValue( 'username' ); 513 $user = Title::makeTitleSafe( NS_USER, $username ); 514 515 $rcIndexes = array(); 516 517 if ( $namespace !== false ) { 518 if ( $this->opts->getValue( 'invert' ) ) { 519 $conds[] = 'rc_namespace != ' . $this->mDb->addQuotes( $namespace ); 520 } else { 521 $conds['rc_namespace'] = $namespace; 522 } 523 } 524 525 if ( $user ) { 526 $conds['rc_user_text'] = $user->getText(); 527 $rcIndexes = 'rc_user_text'; 528 } elseif ( User::groupHasPermission( '*', 'createpage' ) && 529 $this->opts->getValue( 'hideliu' ) 530 ) { 531 # If anons cannot make new pages, don't "exclude logged in users"! 532 $conds['rc_user'] = 0; 533 } 534 535 # If this user cannot see patrolled edits or they are off, don't do dumb queries! 536 if ( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) { 537 $conds['rc_patrolled'] = 0; 538 } 539 540 if ( $this->opts->getValue( 'hidebots' ) ) { 541 $conds['rc_bot'] = 0; 542 } 543 544 if ( $this->opts->getValue( 'hideredirs' ) ) { 545 $conds['page_is_redirect'] = 0; 546 } 547 548 // Allow changes to the New Pages query 549 $tables = array( 'recentchanges', 'page' ); 550 $fields = array( 551 'rc_namespace', 'rc_title', 'rc_cur_id', 'rc_user', 'rc_user_text', 552 'rc_comment', 'rc_timestamp', 'rc_patrolled', 'rc_id', 'rc_deleted', 553 'length' => 'page_len', 'rev_id' => 'page_latest', 'rc_this_oldid', 554 'page_namespace', 'page_title' 555 ); 556 $join_conds = array( 'page' => array( 'INNER JOIN', 'page_id=rc_cur_id' ) ); 557 558 wfRunHooks( 'SpecialNewpagesConditions', 559 array( &$this, $this->opts, &$conds, &$tables, &$fields, &$join_conds ) ); 560 561 $options = array(); 562 563 if ( $rcIndexes ) { 564 $options = array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ); 565 } 566 567 $info = array( 568 'tables' => $tables, 569 'fields' => $fields, 570 'conds' => $conds, 571 'options' => $options, 572 'join_conds' => $join_conds 573 ); 574 575 // Modify query for tags 576 ChangeTags::modifyDisplayQuery( 577 $info['tables'], 578 $info['fields'], 579 $info['conds'], 580 $info['join_conds'], 581 $info['options'], 582 $this->opts['tagfilter'] 583 ); 584 585 return $info; 586 } 587 588 function getIndexField() { 589 return 'rc_timestamp'; 590 } 591 592 function formatRow( $row ) { 593 return $this->mForm->formatRow( $row ); 594 } 595 596 function getStartBody() { 597 # Do a batch existence check on pages 598 $linkBatch = new LinkBatch(); 599 foreach ( $this->mResult as $row ) { 600 $linkBatch->add( NS_USER, $row->rc_user_text ); 601 $linkBatch->add( NS_USER_TALK, $row->rc_user_text ); 602 $linkBatch->add( $row->rc_namespace, $row->rc_title ); 603 } 604 $linkBatch->execute(); 605 606 return '<ul>'; 607 } 608 609 function getEndBody() { 610 return '</ul>'; 611 } 612 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |