Merge "(bug 47070) check content model namespace on import."
[lhc/web/wiklou.git] / includes / specialpage / ChangesListSpecialPage.php
1 <?php
2 /**
3 * Special page which uses a ChangesList to show query results.
4 *
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.
9 *
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.
14 *
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
19 *
20 * @file
21 * @ingroup SpecialPage
22 */
23
24 /**
25 * Special page which uses a ChangesList to show query results.
26 * @todo Way too many public functions, most of them should be protected
27 *
28 * @ingroup SpecialPage
29 */
30 abstract class ChangesListSpecialPage extends SpecialPage {
31 var $rcSubpage, $rcOptions; // @todo Rename these, make protected
32 protected $customFilters;
33
34 /**
35 * The feed format to output as (either 'rss' or 'atom'), or null if no
36 * feed output was requested
37 *
38 * @var string $feedFormat
39 */
40 protected $feedFormat;
41
42 /**
43 * Main execution point
44 *
45 * @param string $subpage
46 */
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;
52 }
53
54 $this->setHeaders();
55 $this->outputHeader();
56 $this->addModules();
57
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 );
65 }
66
67 return;
68 }
69
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 );
76 }
77 $batch->execute();
78 }
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 );
83 } else {
84 $this->webOutput( $rows, $opts );
85 }
86
87 $rows->free();
88 }
89
90 /**
91 * Get the current FormOptions for this request
92 *
93 * @return FormOptions
94 */
95 public function getOptions() {
96 if ( $this->rcOptions === null ) {
97 $this->rcOptions = $this->setup( $this->rcSubpage );
98 }
99
100 return $this->rcOptions;
101 }
102
103 /**
104 * Create a FormOptions object with options as specified by the user
105 *
106 * @param array $parameters
107 *
108 * @return FormOptions
109 */
110 public function setup( $parameters ) {
111 $opts = $this->getDefaultOptions();
112 foreach ( $this->getCustomFilters() as $key => $params ) {
113 $opts->add( $key, $params['default'] );
114 }
115
116 $opts = $this->fetchOptionsFromRequest( $opts );
117
118 // Give precedence to subpage syntax
119 if ( $parameters !== null ) {
120 $this->parseParameters( $parameters, $opts );
121 }
122
123 $this->validateOptions( $opts );
124
125 return $opts;
126 }
127
128 /**
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.
131 *
132 * @return FormOptions
133 */
134 public function getDefaultOptions() {
135 $opts = new FormOptions();
136
137 $opts->add( 'namespace', '', FormOptions::INTNULL );
138 $opts->add( 'invert', false );
139 $opts->add( 'associated', false );
140
141 return $opts;
142 }
143
144 /**
145 * Get custom show/hide filters
146 *
147 * @return array Map of filter URL param names to properties (msg/default)
148 */
149 protected function getCustomFilters() {
150 // @todo Fire a Special{$this->getName()}Filters hook here
151 return array();
152 }
153
154 /**
155 * Fetch values for a FormOptions object from the WebRequest associated with this instance.
156 *
157 * Intended for subclassing, e.g. to add a backwards-compatibility layer.
158 *
159 * @param FormOptions $parameters
160 * @return FormOptions
161 */
162 protected function fetchOptionsFromRequest( $opts ) {
163 $opts->fetchValuesFromRequest( $this->getRequest() );
164 return $opts;
165 }
166
167 /**
168 * Process $par and put options found in $opts. Used when including the page.
169 *
170 * @param string $par
171 * @param FormOptions $opts
172 */
173 public function parseParameters( $par, FormOptions $opts ) {
174 // nothing by default
175 }
176
177 /**
178 * Validate a FormOptions object generated by getDefaultOptions() with values already populated.
179 *
180 * @param FormOptions $opts
181 */
182 public function validateOptions( FormOptions $opts ) {
183 // nothing by default
184 }
185
186 /**
187 * Return an array of conditions depending of options set in $opts
188 * @todo This should build some basic conditions here…
189 *
190 * @param FormOptions $opts
191 * @return array
192 */
193 abstract public function buildMainQueryConds( FormOptions $opts );
194
195 /**
196 * Process the query
197 * @todo This should build some basic processing here…
198 *
199 * @param array $conds
200 * @param FormOptions $opts
201 * @return bool|ResultWrapper Result or false (for Recentchangeslinked only)
202 */
203 abstract public function doMainQuery( $conds, $opts );
204
205 /**
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
208 *
209 * @param ResultWrapper $rows Database rows
210 * @param FormOptions $opts
211 */
212 abstract public function webOutput( $rows, $opts );
213
214 /**
215 * Return the text to be displayed above the changes
216 * @todo Not called by anything, should be called by webOutput()
217 *
218 * @param FormOptions $opts
219 * @return string XHTML
220 */
221 public function doHeader( $opts ) {
222 $this->setTopText( $opts );
223
224 // @todo Lots of stuff should be done here.
225
226 $this->setBottomText( $opts );
227 }
228
229 /**
230 * Send the text to be displayed before the options. Should use $this->getOutput()->addWikiText()
231 * or similar methods to print the text.
232 *
233 * @param FormOptions $opts
234 */
235 function setTopText( FormOptions $opts ) {
236 // nothing by default
237 }
238
239 /**
240 * Send the text to be displayed after the options. Should use $this->getOutput()->addWikiText()
241 * or similar methods to print the text.
242 *
243 * @param FormOptions $opts
244 */
245 function setBottomText( FormOptions $opts ) {
246 // nothing by default
247 }
248
249 /**
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?
253 *
254 * @param FormOptions $opts
255 * @return array
256 */
257 function getExtraOptions( $opts ) {
258 return array();
259 }
260
261 /**
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()
265 *
266 * @param $context the object available as $this in non-static functions
267 * @return string
268 */
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'] );
278 }
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;
283
284 $legend .= Xml::element( 'dt',
285 array( 'class' => $cssClass ), $context->msg( $letter )->text()
286 ) . "\n";
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";
292 } else {
293 $legend .= Xml::element( 'dd', array(),
294 $context->msg( $label )->text()
295 ) . "\n";
296 }
297 }
298 # (+-123)
299 $legend .= Xml::tags( 'dt',
300 array( 'class' => 'mw-plusminus-pos' ),
301 $context->msg( 'recentchanges-legend-plusminus' )->parse()
302 ) . "\n";
303 $legend .= Xml::element(
304 'dd',
305 array( 'class' => 'mw-changeslist-legend-plusminus' ),
306 $context->msg( 'recentchanges-label-plusminus' )->text()
307 ) . "\n";
308 $legend .= Xml::closeElement( 'dl' ) . "\n";
309
310 # Collapsibility
311 $legend =
312 '<div class="mw-changeslist-legend">' .
313 $context->msg( 'recentchanges-legend-heading' )->parse() .
314 '<div class="mw-collapsible-content">' . $legend . '</div>' .
315 '</div>';
316
317 return $legend;
318 }
319
320 /**
321 * Add page-specific modules.
322 */
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' );
328 }
329
330 /**
331 * Return an array with a ChangesFeed object and ChannelFeed object.
332 *
333 * This is intentionally not abstract not to require subclasses which don't
334 * use feeds functionality to implement it.
335 *
336 * @param string $feedFormat Feed's format (either 'rss' or 'atom')
337 * @return array
338 */
339 public function getFeedObject( $feedFormat ) {
340 throw new MWException( "Not implemented" );
341 }
342
343 /**
344 * Get last-modified date, for client caching. Not implemented by default
345 * (returns current time).
346 *
347 * @param string $feedFormat
348 * @return string|bool
349 */
350 public function checkLastModified( $feedFormat ) {
351 return wfTimestampNow();
352 }
353
354 protected function getGroupName() {
355 return 'changes';
356 }
357 }