MediaWiki
REL1_19
|
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 $out->setFeedAppendQuery( wfArrayToCGI( $this->opts->getAllValues() ) ); 00147 } 00148 00149 $pager = new NewPagesPager( $this, $this->opts ); 00150 $pager->mLimit = $this->opts->getValue( 'limit' ); 00151 $pager->mOffset = $this->opts->getValue( 'offset' ); 00152 00153 if( $pager->getNumRows() ) { 00154 $navigation = ''; 00155 if ( $this->showNavigation ) { 00156 $navigation = $pager->getNavigationBar(); 00157 } 00158 $out->addHTML( $navigation . $pager->getBody() . $navigation ); 00159 } else { 00160 $out->addWikiMsg( 'specialpage-empty' ); 00161 } 00162 } 00163 00164 protected function filterLinks() { 00165 global $wgGroupPermissions; 00166 00167 // show/hide links 00168 $showhide = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) ); 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 # @todo FIXME: Throws E_NOTICEs if not set; and doesn't obey hooks etc. 00183 if ( $wgGroupPermissions['*']['createpage'] !== true ) { 00184 unset( $filters['hideliu'] ); 00185 } 00186 if ( !$this->getUser()->useNPPatrol() ) { 00187 unset( $filters['hidepatrolled'] ); 00188 } 00189 00190 $links = array(); 00191 $changed = $this->opts->getChangedValues(); 00192 unset( $changed['offset'] ); // Reset offset if query type changes 00193 00194 $self = $this->getTitle(); 00195 foreach ( $filters as $key => $msg ) { 00196 $onoff = 1 - $this->opts->getValue( $key ); 00197 $link = Linker::link( $self, $showhide[$onoff], array(), 00198 array( $key => $onoff ) + $changed 00199 ); 00200 $links[$key] = wfMsgHtml( $msg, $link ); 00201 } 00202 00203 return $this->getLanguage()->pipeList( $links ); 00204 } 00205 00206 protected function form() { 00207 global $wgEnableNewpagesUserFilter, $wgScript; 00208 00209 // Consume values 00210 $this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW 00211 $namespace = $this->opts->consumeValue( 'namespace' ); 00212 $username = $this->opts->consumeValue( 'username' ); 00213 $tagFilterVal = $this->opts->consumeValue( 'tagfilter' ); 00214 00215 // Check username input validity 00216 $ut = Title::makeTitleSafe( NS_USER, $username ); 00217 $userText = $ut ? $ut->getText() : ''; 00218 00219 // Store query values in hidden fields so that form submission doesn't lose them 00220 $hidden = array(); 00221 foreach ( $this->opts->getUnconsumedValues() as $key => $value ) { 00222 $hidden[] = Html::hidden( $key, $value ); 00223 } 00224 $hidden = implode( "\n", $hidden ); 00225 00226 $tagFilter = ChangeTags::buildTagFilterSelector( $tagFilterVal ); 00227 if ( $tagFilter ) { 00228 list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter; 00229 } 00230 00231 $form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) . 00232 Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . 00233 Xml::fieldset( wfMsg( 'newpages' ) ) . 00234 Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) . 00235 '<tr> 00236 <td class="mw-label">' . 00237 Xml::label( wfMsg( 'namespace' ), 'namespace' ) . 00238 '</td> 00239 <td class="mw-input">' . 00240 Xml::namespaceSelector( $namespace, 'all' ) . 00241 '</td> 00242 </tr>' . ( $tagFilter ? ( 00243 '<tr> 00244 <td class="mw-label">' . 00245 $tagFilterLabel . 00246 '</td> 00247 <td class="mw-input">' . 00248 $tagFilterSelector . 00249 '</td> 00250 </tr>' ) : '' ) . 00251 ( $wgEnableNewpagesUserFilter ? 00252 '<tr> 00253 <td class="mw-label">' . 00254 Xml::label( wfMsg( 'newpages-username' ), 'mw-np-username' ) . 00255 '</td> 00256 <td class="mw-input">' . 00257 Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) . 00258 '</td> 00259 </tr>' : '' ) . 00260 '<tr> <td></td> 00261 <td class="mw-submit">' . 00262 Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . 00263 '</td> 00264 </tr>' . 00265 '<tr> 00266 <td></td> 00267 <td class="mw-input">' . 00268 $this->filterLinks() . 00269 '</td> 00270 </tr>' . 00271 Xml::closeElement( 'table' ) . 00272 Xml::closeElement( 'fieldset' ) . 00273 $hidden . 00274 Xml::closeElement( 'form' ); 00275 00276 $this->getOutput()->addHTML( $form ); 00277 } 00278 00285 public function formatRow( $result ) { 00286 # Revision deletion works on revisions, so we should cast one 00287 $row = array( 00288 'comment' => $result->rc_comment, 00289 'deleted' => $result->rc_deleted, 00290 'user_text' => $result->rc_user_text, 00291 'user' => $result->rc_user, 00292 ); 00293 $rev = new Revision( $row ); 00294 00295 $classes = array(); 00296 00297 $lang = $this->getLanguage(); 00298 $dm = $lang->getDirMark(); 00299 00300 $title = Title::newFromRow( $result ); 00301 $spanTime = Html::element( 'span', array( 'class' => 'mw-newpages-time' ), 00302 $lang->timeanddate( $result->rc_timestamp, true ) 00303 ); 00304 $time = Linker::linkKnown( 00305 $title, 00306 $spanTime, 00307 array(), 00308 array( 'oldid' => $result->rc_this_oldid ), 00309 array() 00310 ); 00311 00312 $query = array( 'redirect' => 'no' ); 00313 00314 if( $this->patrollable( $result ) ) { 00315 $query['rcid'] = $result->rc_id; 00316 } 00317 00318 $plink = Linker::linkKnown( 00319 $title, 00320 null, 00321 array( 'class' => 'mw-newpages-pagename' ), 00322 $query, 00323 array( 'known' ) // Set explicitly to avoid the default of 'known','noclasses'. This breaks the colouration for stubs 00324 ); 00325 $histLink = Linker::linkKnown( 00326 $title, 00327 wfMsgHtml( 'hist' ), 00328 array(), 00329 array( 'action' => 'history' ) 00330 ); 00331 $hist = Html::rawElement( 'span', array( 'class' => 'mw-newpages-history' ), wfMsg( 'parentheses', $histLink ) ); 00332 00333 $length = Html::element( 'span', array( 'class' => 'mw-newpages-length' ), 00334 '[' . $this->msg( 'nbytes' )->numParams( $result->length )->text() . ']' 00335 ); 00336 00337 $ulink = Linker::revUserTools( $rev ); 00338 $comment = Linker::revComment( $rev ); 00339 00340 if ( $this->patrollable( $result ) ) { 00341 $classes[] = 'not-patrolled'; 00342 } 00343 00344 # Add a class for zero byte pages 00345 if ( $result->length == 0 ) { 00346 $classes[] = 'mw-newpages-zero-byte-page'; 00347 } 00348 00349 # Tags, if any. check for including due to bug 23293 00350 if ( !$this->including() ) { 00351 list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' ); 00352 $classes = array_merge( $classes, $newClasses ); 00353 } else { 00354 $tagDisplay = ''; 00355 } 00356 00357 $css = count( $classes ) ? ' class="' . implode( ' ', $classes ) . '"' : ''; 00358 00359 # Display the old title if the namespace has been changed 00360 $oldTitleText = ''; 00361 if ( $result->page_namespace !== $result->rc_namespace ) { 00362 $oldTitleText = wfMessage( 'rc-old-title' )->params( Title::makeTitle( $result->rc_namespace, $result->rc_title ) 00363 ->getPrefixedText() )->escaped(); 00364 } 00365 00366 return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay} {$oldTitleText}</li>\n"; 00367 } 00368 00375 protected function patrollable( $result ) { 00376 return ( $this->getUser()->useNPPatrol() && !$result->rc_patrolled ); 00377 } 00378 00384 protected function feed( $type ) { 00385 global $wgFeed, $wgFeedClasses, $wgFeedLimit; 00386 00387 if ( !$wgFeed ) { 00388 $this->getOutput()->addWikiMsg( 'feed-unavailable' ); 00389 return; 00390 } 00391 00392 if( !isset( $wgFeedClasses[$type] ) ) { 00393 $this->getOutput()->addWikiMsg( 'feed-invalid' ); 00394 return; 00395 } 00396 00397 $feed = new $wgFeedClasses[$type]( 00398 $this->feedTitle(), 00399 wfMsgExt( 'tagline', 'parsemag' ), 00400 $this->getTitle()->getFullUrl() 00401 ); 00402 00403 $pager = new NewPagesPager( $this, $this->opts ); 00404 $limit = $this->opts->getValue( 'limit' ); 00405 $pager->mLimit = min( $limit, $wgFeedLimit ); 00406 00407 $feed->outHeader(); 00408 if( $pager->getNumRows() > 0 ) { 00409 foreach ( $pager->mResult as $row ) { 00410 $feed->outItem( $this->feedItem( $row ) ); 00411 } 00412 } 00413 $feed->outFooter(); 00414 } 00415 00416 protected function feedTitle() { 00417 global $wgLanguageCode, $wgSitename; 00418 $desc = $this->getDescription(); 00419 return "$wgSitename - $desc [$wgLanguageCode]"; 00420 } 00421 00422 protected function feedItem( $row ) { 00423 $title = Title::MakeTitle( intval( $row->rc_namespace ), $row->rc_title ); 00424 if( $title ) { 00425 $date = $row->rc_timestamp; 00426 $comments = $title->getTalkPage()->getFullURL(); 00427 00428 return new FeedItem( 00429 $title->getPrefixedText(), 00430 $this->feedItemDesc( $row ), 00431 $title->getFullURL(), 00432 $date, 00433 $this->feedItemAuthor( $row ), 00434 $comments 00435 ); 00436 } else { 00437 return null; 00438 } 00439 } 00440 00441 protected function feedItemAuthor( $row ) { 00442 return isset( $row->rc_user_text ) ? $row->rc_user_text : ''; 00443 } 00444 00445 protected function feedItemDesc( $row ) { 00446 $revision = Revision::newFromId( $row->rev_id ); 00447 if( $revision ) { 00448 return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) . 00449 htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . 00450 "</p>\n<hr />\n<div>" . 00451 nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>"; 00452 } 00453 return ''; 00454 } 00455 } 00456 00460 class NewPagesPager extends ReverseChronologicalPager { 00461 // Stored opts 00462 protected $opts; 00463 00467 protected $mForm; 00468 00469 function __construct( $form, FormOptions $opts ) { 00470 parent::__construct( $form->getContext() ); 00471 $this->mForm = $form; 00472 $this->opts = $opts; 00473 } 00474 00475 function getQueryInfo() { 00476 global $wgEnableNewpagesUserFilter, $wgGroupPermissions; 00477 $conds = array(); 00478 $conds['rc_new'] = 1; 00479 00480 $namespace = $this->opts->getValue( 'namespace' ); 00481 $namespace = ( $namespace === 'all' ) ? false : intval( $namespace ); 00482 00483 $username = $this->opts->getValue( 'username' ); 00484 $user = Title::makeTitleSafe( NS_USER, $username ); 00485 00486 if( $namespace !== false ) { 00487 $conds['rc_namespace'] = $namespace; 00488 $rcIndexes = array( 'new_name_timestamp' ); 00489 } else { 00490 $rcIndexes = array( 'rc_timestamp' ); 00491 } 00492 00493 # $wgEnableNewpagesUserFilter - temp WMF hack 00494 if( $wgEnableNewpagesUserFilter && $user ) { 00495 $conds['rc_user_text'] = $user->getText(); 00496 $rcIndexes = 'rc_user_text'; 00497 # If anons cannot make new pages, don't "exclude logged in users"! 00498 } elseif( $wgGroupPermissions['*']['createpage'] && $this->opts->getValue( 'hideliu' ) ) { 00499 $conds['rc_user'] = 0; 00500 } 00501 # If this user cannot see patrolled edits or they are off, don't do dumb queries! 00502 if( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) { 00503 $conds['rc_patrolled'] = 0; 00504 } 00505 if( $this->opts->getValue( 'hidebots' ) ) { 00506 $conds['rc_bot'] = 0; 00507 } 00508 00509 if ( $this->opts->getValue( 'hideredirs' ) ) { 00510 $conds['page_is_redirect'] = 0; 00511 } 00512 00513 // Allow changes to the New Pages query 00514 $tables = array( 'recentchanges', 'page' ); 00515 $fields = array( 00516 'rc_namespace', 'rc_title', 'rc_cur_id', 'rc_user', 'rc_user_text', 00517 'rc_comment', 'rc_timestamp', 'rc_patrolled','rc_id', 'rc_deleted', 00518 'page_len AS length', 'page_latest AS rev_id', 'ts_tags', 'rc_this_oldid', 00519 'page_namespace', 'page_title' 00520 ); 00521 $join_conds = array( 'page' => array( 'INNER JOIN', 'page_id=rc_cur_id' ) ); 00522 00523 wfRunHooks( 'SpecialNewpagesConditions', 00524 array( &$this, $this->opts, &$conds, &$tables, &$fields, &$join_conds ) ); 00525 00526 $info = array( 00527 'tables' => $tables, 00528 'fields' => $fields, 00529 'conds' => $conds, 00530 'options' => array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ), 00531 'join_conds' => $join_conds 00532 ); 00533 00534 // Empty array for fields, it'll be set by us anyway. 00535 $fields = array(); 00536 00537 // Modify query for tags 00538 ChangeTags::modifyDisplayQuery( 00539 $info['tables'], 00540 $fields, 00541 $info['conds'], 00542 $info['join_conds'], 00543 $info['options'], 00544 $this->opts['tagfilter'] 00545 ); 00546 00547 return $info; 00548 } 00549 00550 function getIndexField() { 00551 return 'rc_timestamp'; 00552 } 00553 00554 function formatRow( $row ) { 00555 return $this->mForm->formatRow( $row ); 00556 } 00557 00558 function getStartBody() { 00559 # Do a batch existence check on pages 00560 $linkBatch = new LinkBatch(); 00561 foreach ( $this->mResult as $row ) { 00562 $linkBatch->add( NS_USER, $row->rc_user_text ); 00563 $linkBatch->add( NS_USER_TALK, $row->rc_user_text ); 00564 $linkBatch->add( $row->rc_namespace, $row->rc_title ); 00565 } 00566 $linkBatch->execute(); 00567 return '<ul>'; 00568 } 00569 00570 function getEndBody() { 00571 return '</ul>'; 00572 } 00573 }