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