MediaWiki  REL1_24
ChangesListSpecialPage.php
Go to the documentation of this file.
00001 <?php
00030 abstract class ChangesListSpecialPage extends SpecialPage {
00032     protected $rcSubpage;
00033 
00035     protected $rcOptions;
00036 
00038     protected $customFilters;
00039 
00045     public function execute( $subpage ) {
00046         $this->rcSubpage = $subpage;
00047 
00048         $this->setHeaders();
00049         $this->outputHeader();
00050         $this->addModules();
00051 
00052         $rows = $this->getRows();
00053         $opts = $this->getOptions();
00054         if ( $rows === false ) {
00055             if ( !$this->including() ) {
00056                 $this->doHeader( $opts, 0 );
00057                 $this->getOutput()->setStatusCode( 404 );
00058             }
00059 
00060             return;
00061         }
00062 
00063         $batch = new LinkBatch;
00064         foreach ( $rows as $row ) {
00065             $batch->add( NS_USER, $row->rc_user_text );
00066             $batch->add( NS_USER_TALK, $row->rc_user_text );
00067             $batch->add( $row->rc_namespace, $row->rc_title );
00068         }
00069         $batch->execute();
00070 
00071         $this->webOutput( $rows, $opts );
00072 
00073         $rows->free();
00074     }
00075 
00081     public function getRows() {
00082         $opts = $this->getOptions();
00083         $conds = $this->buildMainQueryConds( $opts );
00084 
00085         return $this->doMainQuery( $conds, $opts );
00086     }
00087 
00093     public function getOptions() {
00094         if ( $this->rcOptions === null ) {
00095             $this->rcOptions = $this->setup( $this->rcSubpage );
00096         }
00097 
00098         return $this->rcOptions;
00099     }
00100 
00108     public function setup( $parameters ) {
00109         $opts = $this->getDefaultOptions();
00110         foreach ( $this->getCustomFilters() as $key => $params ) {
00111             $opts->add( $key, $params['default'] );
00112         }
00113 
00114         $opts = $this->fetchOptionsFromRequest( $opts );
00115 
00116         // Give precedence to subpage syntax
00117         if ( $parameters !== null ) {
00118             $this->parseParameters( $parameters, $opts );
00119         }
00120 
00121         $this->validateOptions( $opts );
00122 
00123         return $opts;
00124     }
00125 
00132     public function getDefaultOptions() {
00133         $opts = new FormOptions();
00134 
00135         $opts->add( 'hideminor', false );
00136         $opts->add( 'hidebots', false );
00137         $opts->add( 'hideanons', false );
00138         $opts->add( 'hideliu', false );
00139         $opts->add( 'hidepatrolled', false );
00140         $opts->add( 'hidemyself', false );
00141 
00142         $opts->add( 'namespace', '', FormOptions::INTNULL );
00143         $opts->add( 'invert', false );
00144         $opts->add( 'associated', false );
00145 
00146         return $opts;
00147     }
00148 
00154     protected function getCustomFilters() {
00155         if ( $this->customFilters === null ) {
00156             $this->customFilters = array();
00157             wfRunHooks( 'ChangesListSpecialPageFilters', array( $this, &$this->customFilters ) );
00158         }
00159 
00160         return $this->customFilters;
00161     }
00162 
00171     protected function fetchOptionsFromRequest( $opts ) {
00172         $opts->fetchValuesFromRequest( $this->getRequest() );
00173 
00174         return $opts;
00175     }
00176 
00183     public function parseParameters( $par, FormOptions $opts ) {
00184         // nothing by default
00185     }
00186 
00192     public function validateOptions( FormOptions $opts ) {
00193         // nothing by default
00194     }
00195 
00202     public function buildMainQueryConds( FormOptions $opts ) {
00203         $dbr = $this->getDB();
00204         $user = $this->getUser();
00205         $conds = array();
00206 
00207         // It makes no sense to hide both anons and logged-in users. When this occurs, try a guess on
00208         // what the user meant and either show only bots or force anons to be shown.
00209         $botsonly = false;
00210         $hideanons = $opts['hideanons'];
00211         if ( $opts['hideanons'] && $opts['hideliu'] ) {
00212             if ( $opts['hidebots'] ) {
00213                 $hideanons = false;
00214             } else {
00215                 $botsonly = true;
00216             }
00217         }
00218 
00219         // Toggles
00220         if ( $opts['hideminor'] ) {
00221             $conds['rc_minor'] = 0;
00222         }
00223         if ( $opts['hidebots'] ) {
00224             $conds['rc_bot'] = 0;
00225         }
00226         if ( $user->useRCPatrol() && $opts['hidepatrolled'] ) {
00227             $conds['rc_patrolled'] = 0;
00228         }
00229         if ( $botsonly ) {
00230             $conds['rc_bot'] = 1;
00231         } else {
00232             if ( $opts['hideliu'] ) {
00233                 $conds[] = 'rc_user = 0';
00234             }
00235             if ( $hideanons ) {
00236                 $conds[] = 'rc_user != 0';
00237             }
00238         }
00239         if ( $opts['hidemyself'] ) {
00240             if ( $user->getId() ) {
00241                 $conds[] = 'rc_user != ' . $dbr->addQuotes( $user->getId() );
00242             } else {
00243                 $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $user->getName() );
00244             }
00245         }
00246 
00247         // Namespace filtering
00248         if ( $opts['namespace'] !== '' ) {
00249             $selectedNS = $dbr->addQuotes( $opts['namespace'] );
00250             $operator = $opts['invert'] ? '!=' : '=';
00251             $boolean = $opts['invert'] ? 'AND' : 'OR';
00252 
00253             // Namespace association (bug 2429)
00254             if ( !$opts['associated'] ) {
00255                 $condition = "rc_namespace $operator $selectedNS";
00256             } else {
00257                 // Also add the associated namespace
00258                 $associatedNS = $dbr->addQuotes(
00259                     MWNamespace::getAssociated( $opts['namespace'] )
00260                 );
00261                 $condition = "(rc_namespace $operator $selectedNS "
00262                     . $boolean
00263                     . " rc_namespace $operator $associatedNS)";
00264             }
00265 
00266             $conds[] = $condition;
00267         }
00268 
00269         return $conds;
00270     }
00271 
00279     public function doMainQuery( $conds, $opts ) {
00280         $tables = array( 'recentchanges' );
00281         $fields = RecentChange::selectFields();
00282         $query_options = array();
00283         $join_conds = array();
00284 
00285         ChangeTags::modifyDisplayQuery(
00286             $tables,
00287             $fields,
00288             $conds,
00289             $join_conds,
00290             $query_options,
00291             ''
00292         );
00293 
00294         if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds,
00295             $opts )
00296         ) {
00297             return false;
00298         }
00299 
00300         $dbr = $this->getDB();
00301 
00302         return $dbr->select(
00303             $tables,
00304             $fields,
00305             $conds,
00306             __METHOD__,
00307             $query_options,
00308             $join_conds
00309         );
00310     }
00311 
00312     protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) {
00313         return wfRunHooks(
00314             'ChangesListSpecialPageQuery',
00315             array( $this->getName(), &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts )
00316         );
00317     }
00318 
00324     protected function getDB() {
00325         return wfGetDB( DB_SLAVE );
00326     }
00327 
00334     public function webOutput( $rows, $opts ) {
00335         if ( !$this->including() ) {
00336             $this->outputFeedLinks();
00337             $this->doHeader( $opts, $rows->numRows() );
00338         }
00339 
00340         $this->outputChangesList( $rows, $opts );
00341     }
00342 
00346     public function outputFeedLinks() {
00347         // nothing by default
00348     }
00349 
00356     abstract public function outputChangesList( $rows, $opts );
00357 
00364     public function doHeader( $opts, $numRows ) {
00365         $this->setTopText( $opts );
00366 
00367         // @todo Lots of stuff should be done here.
00368 
00369         $this->setBottomText( $opts );
00370     }
00371 
00378     function setTopText( FormOptions $opts ) {
00379         // nothing by default
00380     }
00381 
00388     function setBottomText( FormOptions $opts ) {
00389         // nothing by default
00390     }
00391 
00400     function getExtraOptions( $opts ) {
00401         return array();
00402     }
00403 
00412     public static function makeLegend( IContextSource $context ) {
00413         $user = $context->getUser();
00414         # The legend showing what the letters and stuff mean
00415         $legend = Html::openElement( 'dl' ) . "\n";
00416         # Iterates through them and gets the messages for both letter and tooltip
00417         $legendItems = $context->getConfig()->get( 'RecentChangesFlags' );
00418         if ( !( $user->useRCPatrol() || $user->useNPPatrol() ) ) {
00419             unset( $legendItems['unpatrolled'] );
00420         }
00421         foreach ( $legendItems as $key => $item ) { # generate items of the legend
00422             $label = isset( $item['legend'] ) ? $item['legend'] : $item['title'];
00423             $letter = $item['letter'];
00424             $cssClass = isset( $item['class'] ) ? $item['class'] : $key;
00425 
00426             $legend .= Html::element( 'dt',
00427                 array( 'class' => $cssClass ), $context->msg( $letter )->text()
00428             ) . "\n" .
00429             Html::rawElement( 'dd', array(),
00430                 $context->msg( $label )->parse()
00431             ) . "\n";
00432         }
00433         # (+-123)
00434         $legend .= Html::rawElement( 'dt',
00435             array( 'class' => 'mw-plusminus-pos' ),
00436             $context->msg( 'recentchanges-legend-plusminus' )->parse()
00437         ) . "\n";
00438         $legend .= Html::element(
00439             'dd',
00440             array( 'class' => 'mw-changeslist-legend-plusminus' ),
00441             $context->msg( 'recentchanges-label-plusminus' )->text()
00442         ) . "\n";
00443         $legend .= Html::closeElement( 'dl' ) . "\n";
00444 
00445         # Collapsibility
00446         $legend =
00447             '<div class="mw-changeslist-legend">' .
00448                 $context->msg( 'recentchanges-legend-heading' )->parse() .
00449                 '<div class="mw-collapsible-content">' . $legend . '</div>' .
00450             '</div>';
00451 
00452         return $legend;
00453     }
00454 
00458     protected function addModules() {
00459         $out = $this->getOutput();
00460         // Styles and behavior for the legend box (see makeLegend())
00461         $out->addModuleStyles( 'mediawiki.special.changeslist.legend' );
00462         $out->addModules( 'mediawiki.special.changeslist.legend.js' );
00463     }
00464 
00465     protected function getGroupName() {
00466         return 'changes';
00467     }
00468 }