MediaWiki  master
SpecialNewpages.php
Go to the documentation of this file.
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 }