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