MediaWiki  REL1_20
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                 global $wgGroupPermissions;
00168 
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                 # @todo FIXME: Throws E_NOTICEs if not set; and doesn't obey hooks etc.
00185                 if ( $wgGroupPermissions['*']['createpage'] !== true ) {
00186                         unset( $filters['hideliu'] );
00187                 }
00188                 if ( !$this->getUser()->useNPPatrol() ) {
00189                         unset( $filters['hidepatrolled'] );
00190                 }
00191 
00192                 $links = array();
00193                 $changed = $this->opts->getChangedValues();
00194                 unset( $changed['offset'] ); // Reset offset if query type changes
00195 
00196                 $self = $this->getTitle();
00197                 foreach ( $filters as $key => $msg ) {
00198                         $onoff = 1 - $this->opts->getValue( $key );
00199                         $link = Linker::link( $self, $showhide[$onoff], array(),
00200                                         array( $key => $onoff ) + $changed
00201                         );
00202                         $links[$key] = $this->msg( $msg )->rawParams( $link )->escaped();
00203                 }
00204 
00205                 return $this->getLanguage()->pipeList( $links );
00206         }
00207 
00208         protected function form() {
00209                 global $wgEnableNewpagesUserFilter, $wgScript;
00210 
00211                 // Consume values
00212                 $this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW
00213                 $namespace = $this->opts->consumeValue( 'namespace' );
00214                 $username = $this->opts->consumeValue( 'username' );
00215                 $tagFilterVal = $this->opts->consumeValue( 'tagfilter' );
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                                 '</td>
00253                         </tr>' . ( $tagFilter ? (
00254                         '<tr>
00255                                 <td class="mw-label">' .
00256                                         $tagFilterLabel .
00257                                 '</td>
00258                                 <td class="mw-input">' .
00259                                         $tagFilterSelector .
00260                                 '</td>
00261                         </tr>' ) : '' ) .
00262                         ( $wgEnableNewpagesUserFilter ?
00263                         '<tr>
00264                                 <td class="mw-label">' .
00265                                         Xml::label( $this->msg( 'newpages-username' )->text(), 'mw-np-username' ) .
00266                                 '</td>
00267                                 <td class="mw-input">' .
00268                                         Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) .
00269                                 '</td>
00270                         </tr>' : '' ) .
00271                         '<tr> <td></td>
00272                                 <td class="mw-submit">' .
00273                                         Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
00274                                 '</td>
00275                         </tr>' .
00276                         '<tr>
00277                                 <td></td>
00278                                 <td class="mw-input">' .
00279                                         $this->filterLinks() .
00280                                 '</td>
00281                         </tr>' .
00282                         Xml::closeElement( 'table' ) .
00283                         Xml::closeElement( 'fieldset' ) .
00284                         $hidden .
00285                         Xml::closeElement( 'form' );
00286 
00287                 $this->getOutput()->addHTML( $form );
00288         }
00289 
00296         public function formatRow( $result ) {
00297                 $title = Title::newFromRow( $result );
00298 
00299                 # Revision deletion works on revisions, so we should cast one
00300                 $row = array(
00301                                           'comment' => $result->rc_comment,
00302                                           'deleted' => $result->rc_deleted,
00303                                           'user_text' => $result->rc_user_text,
00304                                           'user' => $result->rc_user,
00305                                         );
00306                 $rev = new Revision( $row );
00307                 $rev->setTitle( $title );
00308 
00309                 $classes = array();
00310 
00311                 $lang = $this->getLanguage();
00312                 $dm = $lang->getDirMark();
00313 
00314                 $spanTime = Html::element( 'span', array( 'class' => 'mw-newpages-time' ),
00315                         $lang->userTimeAndDate( $result->rc_timestamp, $this->getUser() )
00316                 );
00317                 $time = Linker::linkKnown(
00318                         $title,
00319                         $spanTime,
00320                         array(),
00321                         array( 'oldid' => $result->rc_this_oldid ),
00322                         array()
00323                 );
00324 
00325                 $query = array( 'redirect' => 'no' );
00326 
00327                 if( $this->patrollable( $result ) ) {
00328                         $query['rcid'] = $result->rc_id;
00329                 }
00330 
00331                 $plink = Linker::linkKnown(
00332                         $title,
00333                         null,
00334                         array( 'class' => 'mw-newpages-pagename' ),
00335                         $query,
00336                         array( 'known' ) // Set explicitly to avoid the default of 'known','noclasses'. This breaks the colouration for stubs
00337                 );
00338                 $histLink = Linker::linkKnown(
00339                         $title,
00340                         $this->msg( 'hist' )->escaped(),
00341                         array(),
00342                         array( 'action' => 'history' )
00343                 );
00344                 $hist = Html::rawElement( 'span', array( 'class' => 'mw-newpages-history' ),
00345                         $this->msg( 'parentheses' )->rawParams( $histLink )->escaped() );
00346 
00347                 $length = Html::element( 'span', array( 'class' => 'mw-newpages-length' ),
00348                         $this->msg( 'brackets' )->params( $this->msg( 'nbytes' )->numParams( $result->length )->text() )
00349                 );
00350 
00351                 $ulink = Linker::revUserTools( $rev );
00352                 $comment = Linker::revComment( $rev );
00353 
00354                 if ( $this->patrollable( $result ) ) {
00355                         $classes[] = 'not-patrolled';
00356                 }
00357 
00358                 # Add a class for zero byte pages
00359                 if ( $result->length == 0 ) {
00360                         $classes[] = 'mw-newpages-zero-byte-page';
00361                 }
00362 
00363                 # Tags, if any.
00364                 if( isset( $result->ts_tags ) ) {
00365                         list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' );
00366                         $classes = array_merge( $classes, $newClasses );
00367                 } else {
00368                         $tagDisplay = '';
00369                 }
00370 
00371                 $css = count( $classes ) ? ' class="' . implode( ' ', $classes ) . '"' : '';
00372 
00373                 # Display the old title if the namespace/title has been changed
00374                 $oldTitleText = '';
00375                 $oldTitle = Title::makeTitle( $result->rc_namespace, $result->rc_title );
00376                 if ( !$title->equals( $oldTitle ) ) {
00377                         $oldTitleText = $this->msg( 'rc-old-title' )->params( $oldTitle->getPrefixedText() )->escaped();
00378                 }
00379 
00380                 return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay} {$oldTitleText}</li>\n";
00381         }
00382 
00389         protected function patrollable( $result ) {
00390                 return ( $this->getUser()->useNPPatrol() && !$result->rc_patrolled );
00391         }
00392 
00398         protected function feed( $type ) {
00399                 global $wgFeed, $wgFeedClasses, $wgFeedLimit;
00400 
00401                 if ( !$wgFeed ) {
00402                         $this->getOutput()->addWikiMsg( 'feed-unavailable' );
00403                         return;
00404                 }
00405 
00406                 if( !isset( $wgFeedClasses[$type] ) ) {
00407                         $this->getOutput()->addWikiMsg( 'feed-invalid' );
00408                         return;
00409                 }
00410 
00411                 $feed = new $wgFeedClasses[$type](
00412                         $this->feedTitle(),
00413                         $this->msg( 'tagline' )->text(),
00414                         $this->getTitle()->getFullUrl()
00415                 );
00416 
00417                 $pager = new NewPagesPager( $this, $this->opts );
00418                 $limit = $this->opts->getValue( 'limit' );
00419                 $pager->mLimit = min( $limit, $wgFeedLimit );
00420 
00421                 $feed->outHeader();
00422                 if( $pager->getNumRows() > 0 ) {
00423                         foreach ( $pager->mResult as $row ) {
00424                                 $feed->outItem( $this->feedItem( $row ) );
00425                         }
00426                 }
00427                 $feed->outFooter();
00428         }
00429 
00430         protected function feedTitle() {
00431                 global $wgLanguageCode, $wgSitename;
00432                 $desc = $this->getDescription();
00433                 return "$wgSitename - $desc [$wgLanguageCode]";
00434         }
00435 
00436         protected function feedItem( $row ) {
00437                 $title = Title::makeTitle( intval( $row->rc_namespace ), $row->rc_title );
00438                 if( $title ) {
00439                         $date = $row->rc_timestamp;
00440                         $comments = $title->getTalkPage()->getFullURL();
00441 
00442                         return new FeedItem(
00443                                 $title->getPrefixedText(),
00444                                 $this->feedItemDesc( $row ),
00445                                 $title->getFullURL(),
00446                                 $date,
00447                                 $this->feedItemAuthor( $row ),
00448                                 $comments
00449                         );
00450                 } else {
00451                         return null;
00452                 }
00453         }
00454 
00455         protected function feedItemAuthor( $row ) {
00456                 return isset( $row->rc_user_text ) ? $row->rc_user_text : '';
00457         }
00458 
00459         protected function feedItemDesc( $row ) {
00460                 $revision = Revision::newFromId( $row->rev_id );
00461                 if( $revision ) {
00462                         return '<p>' . htmlspecialchars( $revision->getUserText() ) .
00463                                 $this->msg( 'colon-separator' )->inContentLanguage()->escaped() .
00464                                 htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
00465                                 "</p>\n<hr />\n<div>" .
00466                                 nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
00467                 }
00468                 return '';
00469         }
00470 }
00471 
00475 class NewPagesPager extends ReverseChronologicalPager {
00476         // Stored opts
00477         protected $opts;
00478 
00482         protected $mForm;
00483 
00484         function __construct( $form, FormOptions $opts ) {
00485                 parent::__construct( $form->getContext() );
00486                 $this->mForm = $form;
00487                 $this->opts = $opts;
00488         }
00489 
00490         function getQueryInfo() {
00491                 global $wgEnableNewpagesUserFilter, $wgGroupPermissions;
00492                 $conds = array();
00493                 $conds['rc_new'] = 1;
00494 
00495                 $namespace = $this->opts->getValue( 'namespace' );
00496                 $namespace = ( $namespace === 'all' ) ? false : intval( $namespace );
00497 
00498                 $username = $this->opts->getValue( 'username' );
00499                 $user = Title::makeTitleSafe( NS_USER, $username );
00500 
00501                 if( $namespace !== false ) {
00502                         $conds['rc_namespace'] = $namespace;
00503                         $rcIndexes = array( 'new_name_timestamp' );
00504                 } else {
00505                         $rcIndexes = array( 'rc_timestamp' );
00506                 }
00507 
00508                 # $wgEnableNewpagesUserFilter - temp WMF hack
00509                 if( $wgEnableNewpagesUserFilter && $user ) {
00510                         $conds['rc_user_text'] = $user->getText();
00511                         $rcIndexes = 'rc_user_text';
00512                 # If anons cannot make new pages, don't "exclude logged in users"!
00513                 } elseif( $wgGroupPermissions['*']['createpage'] && $this->opts->getValue( 'hideliu' ) ) {
00514                         $conds['rc_user'] = 0;
00515                 }
00516                 # If this user cannot see patrolled edits or they are off, don't do dumb queries!
00517                 if( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) {
00518                         $conds['rc_patrolled'] = 0;
00519                 }
00520                 if( $this->opts->getValue( 'hidebots' ) ) {
00521                         $conds['rc_bot'] = 0;
00522                 }
00523 
00524                 if ( $this->opts->getValue( 'hideredirs' ) ) {
00525                         $conds['page_is_redirect'] = 0;
00526                 }
00527 
00528                 // Allow changes to the New Pages query
00529                 $tables = array( 'recentchanges', 'page' );
00530                 $fields = array(
00531                         'rc_namespace', 'rc_title', 'rc_cur_id', 'rc_user', 'rc_user_text',
00532                         'rc_comment', 'rc_timestamp', 'rc_patrolled','rc_id', 'rc_deleted',
00533                         'length' => 'page_len', 'rev_id' => 'page_latest', 'rc_this_oldid',
00534                         'page_namespace', 'page_title'
00535                 );
00536                 $join_conds = array( 'page' => array( 'INNER JOIN', 'page_id=rc_cur_id' ) );
00537 
00538                 wfRunHooks( 'SpecialNewpagesConditions',
00539                         array( &$this, $this->opts, &$conds, &$tables, &$fields, &$join_conds ) );
00540 
00541                 $info = array(
00542                         'tables'         => $tables,
00543                         'fields'         => $fields,
00544                         'conds'          => $conds,
00545                         'options'        => array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ),
00546                         'join_conds' => $join_conds
00547                 );
00548 
00549                 // Modify query for tags
00550                 ChangeTags::modifyDisplayQuery(
00551                         $info['tables'],
00552                         $info['fields'],
00553                         $info['conds'],
00554                         $info['join_conds'],
00555                         $info['options'],
00556                         $this->opts['tagfilter']
00557                 );
00558 
00559                 return $info;
00560         }
00561 
00562         function getIndexField() {
00563                 return 'rc_timestamp';
00564         }
00565 
00566         function formatRow( $row ) {
00567                 return $this->mForm->formatRow( $row );
00568         }
00569 
00570         function getStartBody() {
00571                 # Do a batch existence check on pages
00572                 $linkBatch = new LinkBatch();
00573                 foreach ( $this->mResult as $row ) {
00574                         $linkBatch->add( NS_USER, $row->rc_user_text );
00575                         $linkBatch->add( NS_USER_TALK, $row->rc_user_text );
00576                         $linkBatch->add( $row->rc_namespace, $row->rc_title );
00577                 }
00578                 $linkBatch->execute();
00579                 return '<ul>';
00580         }
00581 
00582         function getEndBody() {
00583                 return '</ul>';
00584         }
00585 }