Create ChangesListSpecialPage as a base class for Watchlist and RC
authorBartosz Dziewoński <matma.rex@gmail.com>
Mon, 9 Dec 2013 11:11:14 +0000 (12:11 +0100)
committerBartosz Dziewoński <matma.rex@gmail.com>
Thu, 16 Jan 2014 18:25:27 +0000 (19:25 +0100)
Most of the code has yet to be actually moved there, but it's a start!
It's full of @todo comments, I'm going to go through them later.

This should introduce no functional changes and no backwards
incompatibilities. Verified that all special pages being subclasses
work as they did before (SpecialRecentChanges, SpecialWatchlist,
SpecialRecentChangesLinked).

Change-Id: Icb7671e92a9255619e047ccbe5f457aa22581479

includes/AutoLoader.php
includes/specialpage/ChangesListSpecialPage.php [new file with mode: 0644]
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialWatchlist.php

index f179e78..dde07b4 100644 (file)
@@ -47,6 +47,7 @@ $wgAutoloadLocalClasses = array(
        'CategoryPage' => 'includes/CategoryPage.php',
        'CategoryViewer' => 'includes/CategoryViewer.php',
        'ChangesFeed' => 'includes/ChangesFeed.php',
+       'ChangesListSpecialPage' => 'includes/specialpage/ChangesListSpecialPage.php',
        'ChangeTags' => 'includes/ChangeTags.php',
        'ChannelFeed' => 'includes/Feed.php',
        'Collation' => 'includes/Collation.php',
diff --git a/includes/specialpage/ChangesListSpecialPage.php b/includes/specialpage/ChangesListSpecialPage.php
new file mode 100644 (file)
index 0000000..cedd49c
--- /dev/null
@@ -0,0 +1,293 @@
+<?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 $rcSubpage, $rcOptions; // @todo Rename these, make protected
+       protected $customFilters;
+
+       /**
+        * Get a FormOptions object containing the default options. By default returns some basic options,
+        * you might not call parent method and discard them.
+        *
+        * @return FormOptions
+        */
+       public function getDefaultOptions() {
+               $opts = new FormOptions();
+
+               $opts->add( 'namespace', '', FormOptions::INTNULL );
+               $opts->add( 'invert', false );
+               $opts->add( 'associated', false );
+
+               return $opts;
+       }
+
+       /**
+        * 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;
+       }
+
+       /**
+        * Validate a FormOptions object generated by getDefaultOptions() with values already populated.
+        *
+        * @param FormOptions $opts
+        */
+       public function validateOptions( FormOptions $opts ) {
+               // nothing by default
+       }
+
+       /**
+        * 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 $parameters
+        * @return FormOptions
+        */
+       protected function fetchOptionsFromRequest( $opts ) {
+               $opts->fetchValuesFromRequest( $this->getRequest() );
+               return $opts;
+       }
+
+       /**
+        * Get custom show/hide filters
+        *
+        * @return array Map of filter URL param names to properties (msg/default)
+        */
+       protected function getCustomFilters() {
+               // @todo Fire a Special{$this->getName()}Filters hook here
+               return array();
+       }
+
+       /**
+        * 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
+       }
+
+       /**
+        * Get the current FormOptions for this request
+        * @todo Not called by anything, should be called by execute()
+        *
+        * @return FormOptions
+        */
+       public function getOptions() {
+               if ( $this->rcOptions === null ) {
+                       $this->rcOptions = $this->setup( $this->rcSubpage );
+               }
+
+               return $this->rcOptions;
+       }
+
+       /**
+        * Main execution point
+        * @todo This should totally do things
+        *
+        * @param string $subpage
+        */
+       public function execute( $subpage ) {
+               $this->rcSubpage = $subpage;
+               throw new MWException( "Not implemented" );
+       }
+
+       /**
+        * Return an array of conditions depending of options set in $opts
+        * @todo This should build some basic conditions here…
+        * @todo Not called by anything, should be called by execute()
+        *
+        * @param FormOptions $opts
+        * @return array
+        */
+       abstract public function buildMainQueryConds( FormOptions $opts );
+
+       /**
+        * Process the query
+        * @todo This should build some basic processing here…
+        * @todo Not called by anything, should be called by execute()
+        *
+        * @param array $conds
+        * @param FormOptions $opts
+        * @return bool|ResultWrapper Result or false (for Recentchangeslinked only)
+        */
+       abstract public function doMainQuery( $conds, $opts );
+
+       /**
+        * Send output to the OutputPage object, only called if not used feeds
+        * @todo This should do most, if not all, of the outputting now done by subclasses
+        * @todo Not called by anything, should be called by execute()
+        *
+        * @param array $rows Database rows
+        * @param FormOptions $opts
+        */
+       abstract public function webOutput( $rows, $opts );
+
+       /**
+        * Return the text to be displayed above the changes
+        * @todo Not called by anything, should be called by webOutput()
+        *
+        * @param FormOptions $opts
+        * @return string XHTML
+        */
+       public function doHeader( $opts ) {
+               $this->setTopText( $opts );
+
+               // @todo Lots of stuff should be done here.
+
+               $this->setBottomText( $opts );
+       }
+
+       /**
+        * 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 $context the object available as $this in non-static functions
+        * @return string
+        */
+       public static function makeLegend( IContextSource $context ) {
+               global $wgRecentChangesFlags;
+               $user = $context->getUser();
+               # The legend showing what the letters and stuff mean
+               $legend = Xml::openElement( 'dl' ) . "\n";
+               # Iterates through them and gets the messages for both letter and tooltip
+               $legendItems = $wgRecentChangesFlags;
+               if ( !$user->useRCPatrol() ) {
+                       unset( $legendItems['unpatrolled'] );
+               }
+               foreach ( $legendItems as $key => $legendInfo ) { # generate items of the legend
+                       $label = $legendInfo['title'];
+                       $letter = $legendInfo['letter'];
+                       $cssClass = isset( $legendInfo['class'] ) ? $legendInfo['class'] : $key;
+
+                       $legend .= Xml::element( 'dt',
+                               array( 'class' => $cssClass ), $context->msg( $letter )->text()
+                       ) . "\n";
+                       if ( $key === 'newpage' ) {
+                               $legend .= Xml::openElement( 'dd' );
+                               $legend .= $context->msg( $label )->escaped();
+                               $legend .= ' ' . $context->msg( 'recentchanges-legend-newpage' )->parse();
+                               $legend .= Xml::closeElement( 'dd' ) . "\n";
+                       } else {
+                               $legend .= Xml::element( 'dd', array(),
+                                       $context->msg( $label )->text()
+                               ) . "\n";
+                       }
+               }
+               # (+-123)
+               $legend .= Xml::tags( 'dt',
+                       array( 'class' => 'mw-plusminus-pos' ),
+                       $context->msg( 'recentchanges-legend-plusminus' )->parse()
+               ) . "\n";
+               $legend .= Xml::element(
+                       'dd',
+                       array( 'class' => 'mw-changeslist-legend-plusminus' ),
+                       $context->msg( 'recentchanges-label-plusminus' )->text()
+               ) . "\n";
+               $legend .= Xml::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;
+       }
+
+       /**
+        * 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
+       }
+
+       /**
+        * Add page-specific modules.
+        * @todo Not called by anything, should be called by execute()
+        */
+       protected function addModules() {
+               $out = $this->getOutput();
+               // These modules include styles and behavior for the legend box, load them unconditionally
+               $out->addModuleStyles( 'mediawiki.special.changeslist' );
+               $out->addModules( 'mediawiki.special.changeslist.js' );
+       }
+
+       protected function getGroupName() {
+               return 'changes';
+       }
+}
index 90abe1c..37fa32c 100644 (file)
  *
  * @ingroup SpecialPage
  */
-class SpecialRecentChanges extends SpecialPage {
-       var $rcOptions, $rcSubpage;
-       protected $customFilters;
-
+class SpecialRecentChanges extends ChangesListSpecialPage {
        /**
         * The feed format to output as (either 'rss' or 'atom'), or null if no
         * feed output was requested
@@ -52,7 +49,7 @@ class SpecialRecentChanges extends SpecialPage {
         * @return FormOptions
         */
        public function getDefaultOptions() {
-               $opts = new FormOptions();
+               $opts = parent::getDefaultOptions();
                $user = $this->getUser();
 
                $opts->add( 'days', $user->getIntOption( 'rcdays' ) );
@@ -66,10 +63,6 @@ class SpecialRecentChanges extends SpecialPage {
                $opts->add( 'hidepatrolled', $user->getBoolOption( 'hidepatrolled' ) );
                $opts->add( 'hidemyself', false );
 
-               $opts->add( 'namespace', '', FormOptions::INTNULL );
-               $opts->add( 'invert', false );
-               $opts->add( 'associated', false );
-
                $opts->add( 'categories', '' );
                $opts->add( 'categories_any', false );
                $opts->add( 'tagfilter', '' );
@@ -77,43 +70,9 @@ class SpecialRecentChanges extends SpecialPage {
                return $opts;
        }
 
-       /**
-        * Create a FormOptions object with options as specified by the user
-        *
-        * @param array $parameters
-        * @return FormOptions
-        */
-       public function setup( $parameters ) {
+       public function validateOptions( FormOptions $opts ) {
                global $wgFeedLimit;
-
-               $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 );
-               }
-
                $opts->validateIntBounds( 'limit', 0, $this->feedFormat ? $wgFeedLimit : 5000 );
-
-               return $opts;
-       }
-
-       /**
-        * 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 $parameters
-        * @return FormOptions
-        */
-       protected function fetchOptionsFromRequest( $opts ) {
-               $opts->fetchValuesFromRequest( $this->getRequest() );
-               return $opts;
        }
 
        /**
@@ -130,17 +89,6 @@ class SpecialRecentChanges extends SpecialPage {
                return $this->customFilters;
        }
 
-       /**
-        * Get the current FormOptions for this request
-        */
-       public function getOptions() {
-               if ( $this->rcOptions === null ) {
-                       $this->rcOptions = $this->setup( $this->rcSubpage );
-               }
-
-               return $this->rcOptions;
-       }
-
        /**
         * Main execution point
         *
@@ -212,8 +160,7 @@ class SpecialRecentChanges extends SpecialPage {
        }
 
        /**
-        * Process $par and put options found if $opts
-        * Mainly used when including the page
+        * Process $par and put options found in $opts. Used when including the page.
         *
         * @param string $par
         * @param FormOptions $opts
@@ -658,65 +605,6 @@ class SpecialRecentChanges extends SpecialPage {
                return $extraOpts;
        }
 
-       /**
-        * Return the legend displayed within the fieldset.
-        *
-        * This method is also called from SpecialWatchlist.
-        *
-        * @param $context the object available as $this in non-static functions
-        * @return string
-        */
-       public static function makeLegend( IContextSource $context ) {
-               global $wgRecentChangesFlags;
-               $user = $context->getUser();
-               # The legend showing what the letters and stuff mean
-               $legend = Xml::openElement( 'dl' ) . "\n";
-               # Iterates through them and gets the messages for both letter and tooltip
-               $legendItems = $wgRecentChangesFlags;
-               if ( !$user->useRCPatrol() ) {
-                       unset( $legendItems['unpatrolled'] );
-               }
-               foreach ( $legendItems as $key => $legendInfo ) { # generate items of the legend
-                       $label = $legendInfo['title'];
-                       $letter = $legendInfo['letter'];
-                       $cssClass = isset( $legendInfo['class'] ) ? $legendInfo['class'] : $key;
-
-                       $legend .= Xml::element( 'dt',
-                               array( 'class' => $cssClass ), $context->msg( $letter )->text()
-                       ) . "\n";
-                       if ( $key === 'newpage' ) {
-                               $legend .= Xml::openElement( 'dd' );
-                               $legend .= $context->msg( $label )->escaped();
-                               $legend .= ' ' . $context->msg( 'recentchanges-legend-newpage' )->parse();
-                               $legend .= Xml::closeElement( 'dd' ) . "\n";
-                       } else {
-                               $legend .= Xml::element( 'dd', array(),
-                                       $context->msg( $label )->text()
-                               ) . "\n";
-                       }
-               }
-               # (+-123)
-               $legend .= Xml::tags( 'dt',
-                       array( 'class' => 'mw-plusminus-pos' ),
-                       $context->msg( 'recentchanges-legend-plusminus' )->parse()
-               ) . "\n";
-               $legend .= Xml::element(
-                       'dd',
-                       array( 'class' => 'mw-changeslist-legend-plusminus' ),
-                       $context->msg( 'recentchanges-label-plusminus' )->text()
-               ) . "\n";
-               $legend .= Xml::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;
-       }
-
        /**
         * Send the text to be displayed above the options
         *
@@ -738,14 +626,6 @@ class SpecialRecentChanges extends SpecialPage {
                }
        }
 
-       /**
-        * Send the text to be displayed after the options, for use in subclasses.
-        *
-        * @param FormOptions $opts
-        */
-       function setBottomText( FormOptions $opts ) {
-       }
-
        /**
         * Creates the choose namespace selection
         *
@@ -978,14 +858,8 @@ class SpecialRecentChanges extends SpecialPage {
         * Add page-specific modules.
         */
        protected function addModules() {
+               parent::addModules();
                $out = $this->getOutput();
                $out->addModules( 'mediawiki.special.recentchanges' );
-               // This modules include styles and behavior for the legend box, load it unconditionally
-               $out->addModuleStyles( 'mediawiki.special.changeslist' );
-               $out->addModules( 'mediawiki.special.changeslist.js' );
-       }
-
-       protected function getGroupName() {
-               return 'changes';
        }
 }
index ac41340..7432a40 100644 (file)
@@ -20,9 +20,7 @@
  * @file
  * @ingroup SpecialPage Watchlist
  */
-class SpecialWatchlist extends SpecialRecentChanges {
-       protected $customFilters;
-
+class SpecialWatchlist extends ChangesListSpecialPage {
        /**
         * Constructor
         */
@@ -30,10 +28,6 @@ class SpecialWatchlist extends SpecialRecentChanges {
                parent::__construct( $page, $restriction );
        }
 
-       public function isIncludable() {
-               return false;
-       }
-
        /**
         * Get a FormOptions object containing the default options
         *
@@ -43,9 +37,8 @@ class SpecialWatchlist extends SpecialRecentChanges {
                $opts = parent::getDefaultOptions();
                $user = $this->getUser();
 
-               // Overwrite RC options with Watchlist options
-               // (calling #add() again is okay)
                $opts->add( 'days', $user->getOption( 'watchlistdays' ), FormOptions::FLOAT );
+
                $opts->add( 'hideminor', $user->getBoolOption( 'watchlisthideminor' ) );
                $opts->add( 'hidebots', $user->getBoolOption( 'watchlisthidebots' ) );
                $opts->add( 'hideanons', $user->getBoolOption( 'watchlisthideanons' ) );
@@ -53,7 +46,6 @@ class SpecialWatchlist extends SpecialRecentChanges {
                $opts->add( 'hidepatrolled', $user->getBoolOption( 'watchlisthidepatrolled' ) );
                $opts->add( 'hidemyself', $user->getBoolOption( 'watchlisthideown' ) );
 
-               // Add new ones
                $opts->add( 'extended', $user->getBoolOption( 'extendwatchlist' ) );
 
                return $opts;
@@ -107,26 +99,6 @@ class SpecialWatchlist extends SpecialRecentChanges {
                return $this->customFilters;
        }
 
-       /**
-        * Process $par and put options found if $opts. Not used for Watchlist.
-        *
-        * @param string $par
-        * @param FormOptions $opts
-        */
-       public function parseParameters( $par, FormOptions $opts ) {
-       }
-
-       /**
-        * Get the current FormOptions for this request
-        */
-       public function getOptions() {
-               if ( $this->rcOptions === null ) {
-                       $this->rcOptions = $this->setup( null );
-               }
-
-               return $this->rcOptions;
-       }
-
        /**
         * Execute
         * @param $par Parameter passed to the page