rcSubpage = $subpage; $this->setHeaders(); $this->outputHeader(); $this->addModules(); $rows = $this->getRows(); $opts = $this->getOptions(); if ( $rows === false ) { if ( !$this->including() ) { $this->doHeader( $opts, 0 ); $this->getOutput()->setStatusCode( 404 ); } return; } $batch = new LinkBatch; foreach ( $rows as $row ) { $batch->add( NS_USER, $row->rc_user_text ); $batch->add( NS_USER_TALK, $row->rc_user_text ); $batch->add( $row->rc_namespace, $row->rc_title ); if ( $row->rc_source === RecentChange::SRC_LOG ) { $formatter = LogFormatter::newFromRow( $row ); foreach ( $formatter->getPreloadTitles() as $title ) { $batch->addObj( $title ); } } } $batch->execute(); $this->webOutput( $rows, $opts ); $rows->free(); } /** * Get the database result for this special page instance. Used by ApiFeedRecentChanges. * * @return bool|ResultWrapper Result or false */ public function getRows() { $opts = $this->getOptions(); $conds = $this->buildMainQueryConds( $opts ); return $this->doMainQuery( $conds, $opts ); } /** * Get the current FormOptions for this request * * @return FormOptions */ public function getOptions() { if ( $this->rcOptions === null ) { $this->rcOptions = $this->setup( $this->rcSubpage ); } return $this->rcOptions; } /** * Create a FormOptions object with options as specified by the user * * @param array $parameters * * @return FormOptions */ public function setup( $parameters ) { $opts = $this->getDefaultOptions(); foreach ( $this->getCustomFilters() as $key => $params ) { $opts->add( $key, $params['default'] ); } $opts = $this->fetchOptionsFromRequest( $opts ); // Give precedence to subpage syntax if ( $parameters !== null ) { $this->parseParameters( $parameters, $opts ); } $this->validateOptions( $opts ); return $opts; } /** * Get a FormOptions object containing the default options. By default returns some basic options, * you might want to not call parent method and discard them, or to override default values. * * @return FormOptions */ public function getDefaultOptions() { $opts = new FormOptions(); $opts->add( 'hideminor', false ); $opts->add( 'hidebots', false ); $opts->add( 'hideanons', false ); $opts->add( 'hideliu', false ); $opts->add( 'hidepatrolled', false ); $opts->add( 'hidemyself', false ); $opts->add( 'namespace', '', FormOptions::INTNULL ); $opts->add( 'invert', false ); $opts->add( 'associated', false ); return $opts; } /** * Get custom show/hide filters * * @return array Map of filter URL param names to properties (msg/default) */ protected function getCustomFilters() { if ( $this->customFilters === null ) { $this->customFilters = array(); Hooks::run( 'ChangesListSpecialPageFilters', array( $this, &$this->customFilters ) ); } return $this->customFilters; } /** * Fetch values for a FormOptions object from the WebRequest associated with this instance. * * Intended for subclassing, e.g. to add a backwards-compatibility layer. * * @param FormOptions $opts * @return FormOptions */ protected function fetchOptionsFromRequest( $opts ) { $opts->fetchValuesFromRequest( $this->getRequest() ); return $opts; } /** * Process $par and put options found in $opts. Used when including the page. * * @param string $par * @param FormOptions $opts */ public function parseParameters( $par, FormOptions $opts ) { // nothing by default } /** * Validate a FormOptions object generated by getDefaultOptions() with values already populated. * * @param FormOptions $opts */ public function validateOptions( FormOptions $opts ) { // nothing by default } /** * Return an array of conditions depending of options set in $opts * * @param FormOptions $opts * @return array */ public function buildMainQueryConds( FormOptions $opts ) { $dbr = $this->getDB(); $user = $this->getUser(); $conds = array(); // It makes no sense to hide both anons and logged-in users. When this occurs, try a guess on // what the user meant and either show only bots or force anons to be shown. $botsonly = false; $hideanons = $opts['hideanons']; if ( $opts['hideanons'] && $opts['hideliu'] ) { if ( $opts['hidebots'] ) { $hideanons = false; } else { $botsonly = true; } } // Toggles if ( $opts['hideminor'] ) { $conds['rc_minor'] = 0; } if ( $opts['hidebots'] ) { $conds['rc_bot'] = 0; } if ( $user->useRCPatrol() && $opts['hidepatrolled'] ) { $conds['rc_patrolled'] = 0; } if ( $botsonly ) { $conds['rc_bot'] = 1; } else { if ( $opts['hideliu'] ) { $conds[] = 'rc_user = 0'; } if ( $hideanons ) { $conds[] = 'rc_user != 0'; } } if ( $opts['hidemyself'] ) { if ( $user->getId() ) { $conds[] = 'rc_user != ' . $dbr->addQuotes( $user->getId() ); } else { $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $user->getName() ); } } // Namespace filtering if ( $opts['namespace'] !== '' ) { $selectedNS = $dbr->addQuotes( $opts['namespace'] ); $operator = $opts['invert'] ? '!=' : '='; $boolean = $opts['invert'] ? 'AND' : 'OR'; // Namespace association (bug 2429) if ( !$opts['associated'] ) { $condition = "rc_namespace $operator $selectedNS"; } else { // Also add the associated namespace $associatedNS = $dbr->addQuotes( MWNamespace::getAssociated( $opts['namespace'] ) ); $condition = "(rc_namespace $operator $selectedNS " . $boolean . " rc_namespace $operator $associatedNS)"; } $conds[] = $condition; } return $conds; } /** * Process the query * * @param array $conds * @param FormOptions $opts * @return bool|ResultWrapper Result or false */ public function doMainQuery( $conds, $opts ) { $tables = array( 'recentchanges' ); $fields = RecentChange::selectFields(); $query_options = array(); $join_conds = array(); ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $query_options, '' ); if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts ) ) { return false; } $dbr = $this->getDB(); return $dbr->select( $tables, $fields, $conds, __METHOD__, $query_options, $join_conds ); } protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) { return Hooks::run( 'ChangesListSpecialPageQuery', array( $this->getName(), &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) ); } /** * Return a IDatabase object for reading * * @return IDatabase */ protected function getDB() { return wfGetDB( DB_SLAVE ); } /** * Send output to the OutputPage object, only called if not used feeds * * @param ResultWrapper $rows Database rows * @param FormOptions $opts */ public function webOutput( $rows, $opts ) { if ( !$this->including() ) { $this->outputFeedLinks(); $this->doHeader( $opts, $rows->numRows() ); } $this->outputChangesList( $rows, $opts ); } /** * Output feed links. */ public function outputFeedLinks() { // nothing by default } /** * Build and output the actual changes list. * * @param array $rows Database rows * @param FormOptions $opts */ abstract public function outputChangesList( $rows, $opts ); /** * Set the text to be displayed above the changes * * @param FormOptions $opts * @param int $numRows Number of rows in the result to show after this header */ public function doHeader( $opts, $numRows ) { $this->setTopText( $opts ); // @todo Lots of stuff should be done here. $this->setBottomText( $opts ); } /** * Send the text to be displayed before the options. Should use $this->getOutput()->addWikiText() * or similar methods to print the text. * * @param FormOptions $opts */ function setTopText( FormOptions $opts ) { // nothing by default } /** * Send the text to be displayed after the options. Should use $this->getOutput()->addWikiText() * or similar methods to print the text. * * @param FormOptions $opts */ function setBottomText( FormOptions $opts ) { // nothing by default } /** * Get options to be displayed in a form * @todo This should handle options returned by getDefaultOptions(). * @todo Not called by anything, should be called by something… doHeader() maybe? * * @param FormOptions $opts * @return array */ function getExtraOptions( $opts ) { return array(); } /** * Return the legend displayed within the fieldset * @todo This should not be static, then we can drop the parameter * @todo Not called by anything, should be called by doHeader() * * @param IContextSource $context The object available as $this in non-static functions * @return string */ public static function makeLegend( IContextSource $context ) { $user = $context->getUser(); # The legend showing what the letters and stuff mean $legend = Html::openElement( 'dl' ) . "\n"; # Iterates through them and gets the messages for both letter and tooltip $legendItems = $context->getConfig()->get( 'RecentChangesFlags' ); if ( !( $user->useRCPatrol() || $user->useNPPatrol() ) ) { unset( $legendItems['unpatrolled'] ); } foreach ( $legendItems as $key => $item ) { # generate items of the legend $label = isset( $item['legend'] ) ? $item['legend'] : $item['title']; $letter = $item['letter']; $cssClass = isset( $item['class'] ) ? $item['class'] : $key; $legend .= Html::element( 'dt', array( 'class' => $cssClass ), $context->msg( $letter )->text() ) . "\n" . Html::rawElement( 'dd', array(), $context->msg( $label )->parse() ) . "\n"; } # (+-123) $legend .= Html::rawElement( 'dt', array( 'class' => 'mw-plusminus-pos' ), $context->msg( 'recentchanges-legend-plusminus' )->parse() ) . "\n"; $legend .= Html::element( 'dd', array( 'class' => 'mw-changeslist-legend-plusminus' ), $context->msg( 'recentchanges-label-plusminus' )->text() ) . "\n"; $legend .= Html::closeElement( 'dl' ) . "\n"; # Collapsibility $legend = '