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