MediaWiki
REL1_24
|
00001 <?php 00029 class SpecialNewpages extends IncludableSpecialPage { 00033 protected $opts; 00034 protected $customFilters; 00035 00036 protected $showNavigation = false; 00037 00038 public function __construct() { 00039 parent::__construct( 'Newpages' ); 00040 } 00041 00042 protected function setup( $par ) { 00043 // Options 00044 $opts = new FormOptions(); 00045 $this->opts = $opts; // bind 00046 $opts->add( 'hideliu', false ); 00047 $opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'newpageshidepatrolled' ) ); 00048 $opts->add( 'hidebots', false ); 00049 $opts->add( 'hideredirs', true ); 00050 $opts->add( 'limit', $this->getUser()->getIntOption( 'rclimit' ) ); 00051 $opts->add( 'offset', '' ); 00052 $opts->add( 'namespace', '0' ); 00053 $opts->add( 'username', '' ); 00054 $opts->add( 'feed', '' ); 00055 $opts->add( 'tagfilter', '' ); 00056 $opts->add( 'invert', false ); 00057 00058 $this->customFilters = array(); 00059 wfRunHooks( 'SpecialNewPagesFilters', array( $this, &$this->customFilters ) ); 00060 foreach ( $this->customFilters as $key => $params ) { 00061 $opts->add( $key, $params['default'] ); 00062 } 00063 00064 // Set values 00065 $opts->fetchValuesFromRequest( $this->getRequest() ); 00066 if ( $par ) { 00067 $this->parseParams( $par ); 00068 } 00069 00070 // Validate 00071 $opts->validateIntBounds( 'limit', 0, 5000 ); 00072 } 00073 00074 protected function parseParams( $par ) { 00075 $bits = preg_split( '/\s*,\s*/', trim( $par ) ); 00076 foreach ( $bits as $bit ) { 00077 if ( 'shownav' == $bit ) { 00078 $this->showNavigation = true; 00079 } 00080 if ( 'hideliu' === $bit ) { 00081 $this->opts->setValue( 'hideliu', true ); 00082 } 00083 if ( 'hidepatrolled' == $bit ) { 00084 $this->opts->setValue( 'hidepatrolled', true ); 00085 } 00086 if ( 'hidebots' == $bit ) { 00087 $this->opts->setValue( 'hidebots', true ); 00088 } 00089 if ( 'showredirs' == $bit ) { 00090 $this->opts->setValue( 'hideredirs', false ); 00091 } 00092 if ( is_numeric( $bit ) ) { 00093 $this->opts->setValue( 'limit', intval( $bit ) ); 00094 } 00095 00096 $m = array(); 00097 if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { 00098 $this->opts->setValue( 'limit', intval( $m[1] ) ); 00099 } 00100 // PG offsets not just digits! 00101 if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) { 00102 $this->opts->setValue( 'offset', intval( $m[1] ) ); 00103 } 00104 if ( preg_match( '/^username=(.*)$/', $bit, $m ) ) { 00105 $this->opts->setValue( 'username', $m[1] ); 00106 } 00107 if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) { 00108 $ns = $this->getLanguage()->getNsIndex( $m[1] ); 00109 if ( $ns !== false ) { 00110 $this->opts->setValue( 'namespace', $ns ); 00111 } 00112 } 00113 } 00114 } 00115 00121 public function execute( $par ) { 00122 $out = $this->getOutput(); 00123 00124 $this->setHeaders(); 00125 $this->outputHeader(); 00126 00127 $this->showNavigation = !$this->including(); // Maybe changed in setup 00128 $this->setup( $par ); 00129 00130 if ( !$this->including() ) { 00131 // Settings 00132 $this->form(); 00133 00134 $feedType = $this->opts->getValue( 'feed' ); 00135 if ( $feedType ) { 00136 $this->feed( $feedType ); 00137 00138 return; 00139 } 00140 00141 $allValues = $this->opts->getAllValues(); 00142 unset( $allValues['feed'] ); 00143 $out->setFeedAppendQuery( wfArrayToCgi( $allValues ) ); 00144 } 00145 00146 $pager = new NewPagesPager( $this, $this->opts ); 00147 $pager->mLimit = $this->opts->getValue( 'limit' ); 00148 $pager->mOffset = $this->opts->getValue( 'offset' ); 00149 00150 if ( $pager->getNumRows() ) { 00151 $navigation = ''; 00152 if ( $this->showNavigation ) { 00153 $navigation = $pager->getNavigationBar(); 00154 } 00155 $out->addHTML( $navigation . $pager->getBody() . $navigation ); 00156 } else { 00157 $out->addWikiMsg( 'specialpage-empty' ); 00158 } 00159 } 00160 00161 protected function filterLinks() { 00162 // show/hide links 00163 $showhide = array( $this->msg( 'show' )->escaped(), $this->msg( 'hide' )->escaped() ); 00164 00165 // Option value -> message mapping 00166 $filters = array( 00167 'hideliu' => 'rcshowhideliu', 00168 'hidepatrolled' => 'rcshowhidepatr', 00169 'hidebots' => 'rcshowhidebots', 00170 'hideredirs' => 'whatlinkshere-hideredirs' 00171 ); 00172 foreach ( $this->customFilters as $key => $params ) { 00173 $filters[$key] = $params['msg']; 00174 } 00175 00176 // Disable some if needed 00177 if ( !User::groupHasPermission( '*', 'createpage' ) ) { 00178 unset( $filters['hideliu'] ); 00179 } 00180 if ( !$this->getUser()->useNPPatrol() ) { 00181 unset( $filters['hidepatrolled'] ); 00182 } 00183 00184 $links = array(); 00185 $changed = $this->opts->getChangedValues(); 00186 unset( $changed['offset'] ); // Reset offset if query type changes 00187 00188 $self = $this->getPageTitle(); 00189 foreach ( $filters as $key => $msg ) { 00190 $onoff = 1 - $this->opts->getValue( $key ); 00191 $link = Linker::link( $self, $showhide[$onoff], array(), 00192 array( $key => $onoff ) + $changed 00193 ); 00194 $links[$key] = $this->msg( $msg )->rawParams( $link )->escaped(); 00195 } 00196 00197 return $this->getLanguage()->pipeList( $links ); 00198 } 00199 00200 protected function form() { 00201 // Consume values 00202 $this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW 00203 $namespace = $this->opts->consumeValue( 'namespace' ); 00204 $username = $this->opts->consumeValue( 'username' ); 00205 $tagFilterVal = $this->opts->consumeValue( 'tagfilter' ); 00206 $nsinvert = $this->opts->consumeValue( 'invert' ); 00207 00208 // Check username input validity 00209 $ut = Title::makeTitleSafe( NS_USER, $username ); 00210 $userText = $ut ? $ut->getText() : ''; 00211 00212 // Store query values in hidden fields so that form submission doesn't lose them 00213 $hidden = array(); 00214 foreach ( $this->opts->getUnconsumedValues() as $key => $value ) { 00215 $hidden[] = Html::hidden( $key, $value ); 00216 } 00217 $hidden = implode( "\n", $hidden ); 00218 00219 $tagFilter = ChangeTags::buildTagFilterSelector( $tagFilterVal ); 00220 if ( $tagFilter ) { 00221 list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter; 00222 } 00223 00224 $form = Xml::openElement( 'form', array( 'action' => wfScript() ) ) . 00225 Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . 00226 Xml::fieldset( $this->msg( 'newpages' )->text() ) . 00227 Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) . 00228 '<tr> 00229 <td class="mw-label">' . 00230 Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) . 00231 '</td> 00232 <td class="mw-input">' . 00233 Html::namespaceSelector( 00234 array( 00235 'selected' => $namespace, 00236 'all' => 'all', 00237 ), array( 00238 'name' => 'namespace', 00239 'id' => 'namespace', 00240 'class' => 'namespaceselector', 00241 ) 00242 ) . ' ' . 00243 Xml::checkLabel( 00244 $this->msg( 'invert' )->text(), 00245 'invert', 00246 'nsinvert', 00247 $nsinvert, 00248 array( 'title' => $this->msg( 'tooltip-invert' )->text() ) 00249 ) . 00250 '</td> 00251 </tr>' . ( $tagFilter ? ( 00252 '<tr> 00253 <td class="mw-label">' . 00254 $tagFilterLabel . 00255 '</td> 00256 <td class="mw-input">' . 00257 $tagFilterSelector . 00258 '</td> 00259 </tr>' ) : '' ) . 00260 '<tr> 00261 <td class="mw-label">' . 00262 Xml::label( $this->msg( 'newpages-username' )->text(), 'mw-np-username' ) . 00263 '</td> 00264 <td class="mw-input">' . 00265 Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) . 00266 '</td> 00267 </tr>' . 00268 '<tr> <td></td> 00269 <td class="mw-submit">' . 00270 Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . 00271 '</td> 00272 </tr>' . 00273 '<tr> 00274 <td></td> 00275 <td class="mw-input">' . 00276 $this->filterLinks() . 00277 '</td> 00278 </tr>' . 00279 Xml::closeElement( 'table' ) . 00280 Xml::closeElement( 'fieldset' ) . 00281 $hidden . 00282 Xml::closeElement( 'form' ); 00283 00284 $this->getOutput()->addHTML( $form ); 00285 } 00286 00294 public function formatRow( $result ) { 00295 $title = Title::newFromRow( $result ); 00296 00297 # Revision deletion works on revisions, so we should cast one 00298 $row = array( 00299 'comment' => $result->rc_comment, 00300 'deleted' => $result->rc_deleted, 00301 'user_text' => $result->rc_user_text, 00302 'user' => $result->rc_user, 00303 ); 00304 $rev = new Revision( $row ); 00305 $rev->setTitle( $title ); 00306 00307 $classes = array(); 00308 00309 $lang = $this->getLanguage(); 00310 $dm = $lang->getDirMark(); 00311 00312 $spanTime = Html::element( 'span', array( 'class' => 'mw-newpages-time' ), 00313 $lang->userTimeAndDate( $result->rc_timestamp, $this->getUser() ) 00314 ); 00315 $time = Linker::linkKnown( 00316 $title, 00317 $spanTime, 00318 array(), 00319 array( 'oldid' => $result->rc_this_oldid ), 00320 array() 00321 ); 00322 00323 $query = array( 'redirect' => 'no' ); 00324 00325 // Linker::linkKnown() uses 'known' and 'noclasses' options. 00326 // This breaks the colouration for stubs. 00327 $plink = Linker::link( 00328 $title, 00329 null, 00330 array( 'class' => 'mw-newpages-pagename' ), 00331 $query, 00332 array( 'known' ) 00333 ); 00334 $histLink = Linker::linkKnown( 00335 $title, 00336 $this->msg( 'hist' )->escaped(), 00337 array(), 00338 array( 'action' => 'history' ) 00339 ); 00340 $hist = Html::rawElement( 'span', array( 'class' => 'mw-newpages-history' ), 00341 $this->msg( 'parentheses' )->rawParams( $histLink )->escaped() ); 00342 00343 $length = Html::element( 00344 'span', 00345 array( 'class' => 'mw-newpages-length' ), 00346 $this->msg( 'brackets' )->params( $this->msg( 'nbytes' ) 00347 ->numParams( $result->length )->text() 00348 ) 00349 ); 00350 00351 $ulink = Linker::revUserTools( $rev ); 00352 $comment = Linker::revComment( $rev ); 00353 00354 if ( $this->patrollable( $result ) ) { 00355 $classes[] = 'not-patrolled'; 00356 } 00357 00358 # Add a class for zero byte pages 00359 if ( $result->length == 0 ) { 00360 $classes[] = 'mw-newpages-zero-byte-page'; 00361 } 00362 00363 # Tags, if any. 00364 if ( isset( $result->ts_tags ) ) { 00365 list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( 00366 $result->ts_tags, 00367 'newpages' 00368 ); 00369 $classes = array_merge( $classes, $newClasses ); 00370 } else { 00371 $tagDisplay = ''; 00372 } 00373 00374 $css = count( $classes ) ? ' class="' . implode( ' ', $classes ) . '"' : ''; 00375 00376 # Display the old title if the namespace/title has been changed 00377 $oldTitleText = ''; 00378 $oldTitle = Title::makeTitle( $result->rc_namespace, $result->rc_title ); 00379 00380 if ( !$title->equals( $oldTitle ) ) { 00381 $oldTitleText = $oldTitle->getPrefixedText(); 00382 $oldTitleText = $this->msg( 'rc-old-title' )->params( $oldTitleText )->escaped(); 00383 } 00384 00385 return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} " 00386 . "{$dm}{$ulink} {$comment} {$tagDisplay} {$oldTitleText}</li>\n"; 00387 } 00388 00395 protected function patrollable( $result ) { 00396 return ( $this->getUser()->useNPPatrol() && !$result->rc_patrolled ); 00397 } 00398 00404 protected function feed( $type ) { 00405 if ( !$this->getConfig()->get( 'Feed' ) ) { 00406 $this->getOutput()->addWikiMsg( 'feed-unavailable' ); 00407 00408 return; 00409 } 00410 00411 $feedClasses = $this->getConfig()->get( 'FeedClasses' ); 00412 if ( !isset( $feedClasses[$type] ) ) { 00413 $this->getOutput()->addWikiMsg( 'feed-invalid' ); 00414 00415 return; 00416 } 00417 00418 $feed = new $feedClasses[$type]( 00419 $this->feedTitle(), 00420 $this->msg( 'tagline' )->text(), 00421 $this->getPageTitle()->getFullURL() 00422 ); 00423 00424 $pager = new NewPagesPager( $this, $this->opts ); 00425 $limit = $this->opts->getValue( 'limit' ); 00426 $pager->mLimit = min( $limit, $this->getConfig()->get( 'FeedLimit' ) ); 00427 00428 $feed->outHeader(); 00429 if ( $pager->getNumRows() > 0 ) { 00430 foreach ( $pager->mResult as $row ) { 00431 $feed->outItem( $this->feedItem( $row ) ); 00432 } 00433 } 00434 $feed->outFooter(); 00435 } 00436 00437 protected function feedTitle() { 00438 $desc = $this->getDescription(); 00439 $code = $this->getConfig()->get( 'LanguageCode' ); 00440 $sitename = $this->getConfig()->get( 'Sitename' ); 00441 00442 return "$sitename - $desc [$code]"; 00443 } 00444 00445 protected function feedItem( $row ) { 00446 $title = Title::makeTitle( intval( $row->rc_namespace ), $row->rc_title ); 00447 if ( $title ) { 00448 $date = $row->rc_timestamp; 00449 $comments = $title->getTalkPage()->getFullURL(); 00450 00451 return new FeedItem( 00452 $title->getPrefixedText(), 00453 $this->feedItemDesc( $row ), 00454 $title->getFullURL(), 00455 $date, 00456 $this->feedItemAuthor( $row ), 00457 $comments 00458 ); 00459 } else { 00460 return null; 00461 } 00462 } 00463 00464 protected function feedItemAuthor( $row ) { 00465 return isset( $row->rc_user_text ) ? $row->rc_user_text : ''; 00466 } 00467 00468 protected function feedItemDesc( $row ) { 00469 $revision = Revision::newFromId( $row->rev_id ); 00470 if ( $revision ) { 00471 //XXX: include content model/type in feed item? 00472 return '<p>' . htmlspecialchars( $revision->getUserText() ) . 00473 $this->msg( 'colon-separator' )->inContentLanguage()->escaped() . 00474 htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . 00475 "</p>\n<hr />\n<div>" . 00476 nl2br( htmlspecialchars( $revision->getContent()->serialize() ) ) . "</div>"; 00477 } 00478 00479 return ''; 00480 } 00481 00482 protected function getGroupName() { 00483 return 'changes'; 00484 } 00485 } 00486 00490 class NewPagesPager extends ReverseChronologicalPager { 00491 // Stored opts 00492 protected $opts; 00493 00497 protected $mForm; 00498 00499 function __construct( $form, FormOptions $opts ) { 00500 parent::__construct( $form->getContext() ); 00501 $this->mForm = $form; 00502 $this->opts = $opts; 00503 } 00504 00505 function getQueryInfo() { 00506 $conds = array(); 00507 $conds['rc_new'] = 1; 00508 00509 $namespace = $this->opts->getValue( 'namespace' ); 00510 $namespace = ( $namespace === 'all' ) ? false : intval( $namespace ); 00511 00512 $username = $this->opts->getValue( 'username' ); 00513 $user = Title::makeTitleSafe( NS_USER, $username ); 00514 00515 $rcIndexes = array(); 00516 00517 if ( $namespace !== false ) { 00518 if ( $this->opts->getValue( 'invert' ) ) { 00519 $conds[] = 'rc_namespace != ' . $this->mDb->addQuotes( $namespace ); 00520 } else { 00521 $conds['rc_namespace'] = $namespace; 00522 } 00523 } 00524 00525 if ( $user ) { 00526 $conds['rc_user_text'] = $user->getText(); 00527 $rcIndexes = 'rc_user_text'; 00528 } elseif ( User::groupHasPermission( '*', 'createpage' ) && 00529 $this->opts->getValue( 'hideliu' ) 00530 ) { 00531 # If anons cannot make new pages, don't "exclude logged in users"! 00532 $conds['rc_user'] = 0; 00533 } 00534 00535 # If this user cannot see patrolled edits or they are off, don't do dumb queries! 00536 if ( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) { 00537 $conds['rc_patrolled'] = 0; 00538 } 00539 00540 if ( $this->opts->getValue( 'hidebots' ) ) { 00541 $conds['rc_bot'] = 0; 00542 } 00543 00544 if ( $this->opts->getValue( 'hideredirs' ) ) { 00545 $conds['page_is_redirect'] = 0; 00546 } 00547 00548 // Allow changes to the New Pages query 00549 $tables = array( 'recentchanges', 'page' ); 00550 $fields = array( 00551 'rc_namespace', 'rc_title', 'rc_cur_id', 'rc_user', 'rc_user_text', 00552 'rc_comment', 'rc_timestamp', 'rc_patrolled', 'rc_id', 'rc_deleted', 00553 'length' => 'page_len', 'rev_id' => 'page_latest', 'rc_this_oldid', 00554 'page_namespace', 'page_title' 00555 ); 00556 $join_conds = array( 'page' => array( 'INNER JOIN', 'page_id=rc_cur_id' ) ); 00557 00558 wfRunHooks( 'SpecialNewpagesConditions', 00559 array( &$this, $this->opts, &$conds, &$tables, &$fields, &$join_conds ) ); 00560 00561 $options = array(); 00562 00563 if ( $rcIndexes ) { 00564 $options = array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ); 00565 } 00566 00567 $info = array( 00568 'tables' => $tables, 00569 'fields' => $fields, 00570 'conds' => $conds, 00571 'options' => $options, 00572 'join_conds' => $join_conds 00573 ); 00574 00575 // Modify query for tags 00576 ChangeTags::modifyDisplayQuery( 00577 $info['tables'], 00578 $info['fields'], 00579 $info['conds'], 00580 $info['join_conds'], 00581 $info['options'], 00582 $this->opts['tagfilter'] 00583 ); 00584 00585 return $info; 00586 } 00587 00588 function getIndexField() { 00589 return 'rc_timestamp'; 00590 } 00591 00592 function formatRow( $row ) { 00593 return $this->mForm->formatRow( $row ); 00594 } 00595 00596 function getStartBody() { 00597 # Do a batch existence check on pages 00598 $linkBatch = new LinkBatch(); 00599 foreach ( $this->mResult as $row ) { 00600 $linkBatch->add( NS_USER, $row->rc_user_text ); 00601 $linkBatch->add( NS_USER_TALK, $row->rc_user_text ); 00602 $linkBatch->add( $row->rc_namespace, $row->rc_title ); 00603 } 00604 $linkBatch->execute(); 00605 00606 return '<ul>'; 00607 } 00608 00609 function getEndBody() { 00610 return '</ul>'; 00611 } 00612 }