summaryrefslogtreecommitdiff
path: root/includes/specialpage/ChangesListSpecialPage.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/specialpage/ChangesListSpecialPage.php')
-rw-r--r--includes/specialpage/ChangesListSpecialPage.php468
1 files changed, 468 insertions, 0 deletions
diff --git a/includes/specialpage/ChangesListSpecialPage.php b/includes/specialpage/ChangesListSpecialPage.php
new file mode 100644
index 00000000..c28aa867
--- /dev/null
+++ b/includes/specialpage/ChangesListSpecialPage.php
@@ -0,0 +1,468 @@
+<?php
+/**
+ * Special page which uses a ChangesList to show query results.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Special page which uses a ChangesList to show query results.
+ * @todo Way too many public functions, most of them should be protected
+ *
+ * @ingroup SpecialPage
+ */
+abstract class ChangesListSpecialPage extends SpecialPage {
+ /** @var string */
+ protected $rcSubpage;
+
+ /** @var FormOptions */
+ protected $rcOptions;
+
+ /** @var array */
+ protected $customFilters;
+
+ /**
+ * Main execution point
+ *
+ * @param string $subpage
+ */
+ public function execute( $subpage ) {
+ $this->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 );
+ }
+ $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();
+ wfRunHooks( '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 wfRunHooks(
+ 'ChangesListSpecialPageQuery',
+ array( $this->getName(), &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts )
+ );
+ }
+
+ /**
+ * Return a DatabaseBase object for reading
+ *
+ * @return DatabaseBase
+ */
+ 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 =
+ '<div class="mw-changeslist-legend">' .
+ $context->msg( 'recentchanges-legend-heading' )->parse() .
+ '<div class="mw-collapsible-content">' . $legend . '</div>' .
+ '</div>';
+
+ return $legend;
+ }
+
+ /**
+ * Add page-specific modules.
+ */
+ protected function addModules() {
+ $out = $this->getOutput();
+ // Styles and behavior for the legend box (see makeLegend())
+ $out->addModuleStyles( 'mediawiki.special.changeslist.legend' );
+ $out->addModules( 'mediawiki.special.changeslist.legend.js' );
+ }
+
+ protected function getGroupName() {
+ return 'changes';
+ }
+}