3 * Special page which uses a ChangesList to show query results.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
21 * @ingroup SpecialPage
25 * Special page which uses a ChangesList to show query results.
26 * @todo Way too many public functions, most of them should be protected
28 * @ingroup SpecialPage
30 abstract class ChangesListSpecialPage
extends SpecialPage
{
31 var $rcSubpage, $rcOptions; // @todo Rename these, make protected
32 protected $customFilters;
35 * The feed format to output as (either 'rss' or 'atom'), or null if no
36 * feed output was requested
38 * @var string $feedFormat
40 protected $feedFormat;
43 * Main execution point
45 * @param string $subpage
47 public function execute( $subpage ) {
48 $this->rcSubpage
= $subpage;
49 $this->feedFormat
= $this->including() ?
null : $this->getRequest()->getVal( 'feed' );
50 if ( $this->feedFormat
!== 'atom' && $this->feedFormat
!== 'rss' ) {
51 $this->feedFormat
= null;
55 $this->outputHeader();
58 $opts = $this->getOptions();
59 // Fetch results, prepare a batch link existence check query
60 $conds = $this->buildMainQueryConds( $opts );
61 $rows = $this->doMainQuery( $conds, $opts );
62 if ( $rows === false ) {
63 if ( !$this->including() ) {
64 $this->doHeader( $opts );
70 if ( !$this->feedFormat
) {
71 $batch = new LinkBatch
;
72 foreach ( $rows as $row ) {
73 $batch->add( NS_USER
, $row->rc_user_text
);
74 $batch->add( NS_USER_TALK
, $row->rc_user_text
);
75 $batch->add( $row->rc_namespace
, $row->rc_title
);
79 if ( $this->feedFormat
) {
80 list( $changesFeed, $formatter ) = $this->getFeedObject( $this->feedFormat
);
81 /** @var ChangesFeed $changesFeed */
82 $changesFeed->execute( $formatter, $rows, $this->checkLastModified( $this->feedFormat
), $opts );
84 $this->webOutput( $rows, $opts );
91 * Get the current FormOptions for this request
95 public function getOptions() {
96 if ( $this->rcOptions
=== null ) {
97 $this->rcOptions
= $this->setup( $this->rcSubpage
);
100 return $this->rcOptions
;
104 * Create a FormOptions object with options as specified by the user
106 * @param array $parameters
108 * @return FormOptions
110 public function setup( $parameters ) {
111 $opts = $this->getDefaultOptions();
112 foreach ( $this->getCustomFilters() as $key => $params ) {
113 $opts->add( $key, $params['default'] );
116 $opts = $this->fetchOptionsFromRequest( $opts );
118 // Give precedence to subpage syntax
119 if ( $parameters !== null ) {
120 $this->parseParameters( $parameters, $opts );
123 $this->validateOptions( $opts );
129 * Get a FormOptions object containing the default options. By default returns some basic options,
130 * you might not call parent method and discard them.
132 * @return FormOptions
134 public function getDefaultOptions() {
135 $opts = new FormOptions();
137 $opts->add( 'namespace', '', FormOptions
::INTNULL
);
138 $opts->add( 'invert', false );
139 $opts->add( 'associated', false );
145 * Get custom show/hide filters
147 * @return array Map of filter URL param names to properties (msg/default)
149 protected function getCustomFilters() {
150 // @todo Fire a Special{$this->getName()}Filters hook here
155 * Fetch values for a FormOptions object from the WebRequest associated with this instance.
157 * Intended for subclassing, e.g. to add a backwards-compatibility layer.
159 * @param FormOptions $parameters
160 * @return FormOptions
162 protected function fetchOptionsFromRequest( $opts ) {
163 $opts->fetchValuesFromRequest( $this->getRequest() );
168 * Process $par and put options found in $opts. Used when including the page.
171 * @param FormOptions $opts
173 public function parseParameters( $par, FormOptions
$opts ) {
174 // nothing by default
178 * Validate a FormOptions object generated by getDefaultOptions() with values already populated.
180 * @param FormOptions $opts
182 public function validateOptions( FormOptions
$opts ) {
183 // nothing by default
187 * Return an array of conditions depending of options set in $opts
188 * @todo This should build some basic conditions here…
190 * @param FormOptions $opts
193 abstract public function buildMainQueryConds( FormOptions
$opts );
197 * @todo This should build some basic processing here…
199 * @param array $conds
200 * @param FormOptions $opts
201 * @return bool|ResultWrapper Result or false (for Recentchangeslinked only)
203 abstract public function doMainQuery( $conds, $opts );
206 * Send output to the OutputPage object, only called if not used feeds
207 * @todo This should do most, if not all, of the outputting now done by subclasses
209 * @param ResultWrapper $rows Database rows
210 * @param FormOptions $opts
212 abstract public function webOutput( $rows, $opts );
215 * Return the text to be displayed above the changes
216 * @todo Not called by anything, should be called by webOutput()
218 * @param FormOptions $opts
219 * @return string XHTML
221 public function doHeader( $opts ) {
222 $this->setTopText( $opts );
224 // @todo Lots of stuff should be done here.
226 $this->setBottomText( $opts );
230 * Send the text to be displayed before the options. Should use $this->getOutput()->addWikiText()
231 * or similar methods to print the text.
233 * @param FormOptions $opts
235 function setTopText( FormOptions
$opts ) {
236 // nothing by default
240 * Send the text to be displayed after the options. Should use $this->getOutput()->addWikiText()
241 * or similar methods to print the text.
243 * @param FormOptions $opts
245 function setBottomText( FormOptions
$opts ) {
246 // nothing by default
250 * Get options to be displayed in a form
251 * @todo This should handle options returned by getDefaultOptions().
252 * @todo Not called by anything, should be called by something… doHeader() maybe?
254 * @param FormOptions $opts
257 function getExtraOptions( $opts ) {
262 * Return the legend displayed within the fieldset
263 * @todo This should not be static, then we can drop the parameter
264 * @todo Not called by anything, should be called by doHeader()
266 * @param $context the object available as $this in non-static functions
269 public static function makeLegend( IContextSource
$context ) {
270 global $wgRecentChangesFlags;
271 $user = $context->getUser();
272 # The legend showing what the letters and stuff mean
273 $legend = Xml
::openElement( 'dl' ) . "\n";
274 # Iterates through them and gets the messages for both letter and tooltip
275 $legendItems = $wgRecentChangesFlags;
276 if ( !$user->useRCPatrol() ) {
277 unset( $legendItems['unpatrolled'] );
279 foreach ( $legendItems as $key => $legendInfo ) { # generate items of the legend
280 $label = $legendInfo['title'];
281 $letter = $legendInfo['letter'];
282 $cssClass = isset( $legendInfo['class'] ) ?
$legendInfo['class'] : $key;
284 $legend .= Xml
::element( 'dt',
285 array( 'class' => $cssClass ), $context->msg( $letter )->text()
287 if ( $key === 'newpage' ) {
288 $legend .= Xml
::openElement( 'dd' );
289 $legend .= $context->msg( $label )->escaped();
290 $legend .= ' ' . $context->msg( 'recentchanges-legend-newpage' )->parse();
291 $legend .= Xml
::closeElement( 'dd' ) . "\n";
293 $legend .= Xml
::element( 'dd', array(),
294 $context->msg( $label )->text()
299 $legend .= Xml
::tags( 'dt',
300 array( 'class' => 'mw-plusminus-pos' ),
301 $context->msg( 'recentchanges-legend-plusminus' )->parse()
303 $legend .= Xml
::element(
305 array( 'class' => 'mw-changeslist-legend-plusminus' ),
306 $context->msg( 'recentchanges-label-plusminus' )->text()
308 $legend .= Xml
::closeElement( 'dl' ) . "\n";
312 '<div class="mw-changeslist-legend">' .
313 $context->msg( 'recentchanges-legend-heading' )->parse() .
314 '<div class="mw-collapsible-content">' . $legend . '</div>' .
321 * Add page-specific modules.
323 protected function addModules() {
324 $out = $this->getOutput();
325 // Styles and behavior for the legend box (see makeLegend())
326 $out->addModuleStyles( 'mediawiki.special.changeslist.legend' );
327 $out->addModules( 'mediawiki.special.changeslist.legend.js' );
331 * Return an array with a ChangesFeed object and ChannelFeed object.
333 * This is intentionally not abstract not to require subclasses which don't
334 * use feeds functionality to implement it.
336 * @param string $feedFormat Feed's format (either 'rss' or 'atom')
339 public function getFeedObject( $feedFormat ) {
340 throw new MWException( "Not implemented" );
344 * Get last-modified date, for client caching. Not implemented by default
345 * (returns current time).
347 * @param string $feedFormat
348 * @return string|bool
350 public function checkLastModified( $feedFormat ) {
351 return wfTimestampNow();
354 protected function getGroupName() {