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