Only whitelist it if QUnit is in that mode though (bug in QUnit?), caused it to repor...
[mediawiki.git] / includes / Wiki.php
blob1f9c9393a7c4c1821bec1896556d7a2dcffb21e8
1 <?php
2 /**
3 * MediaWiki is the to-be base class for this whole project
5 * @internal documentation reviewed 15 Mar 2010
6 */
7 class MediaWiki {
9 /**
10 * TODO: fold $output, etc, into this
11 * @var RequestContext
13 private $context;
15 public function request( WebRequest $x = null ){
16 return wfSetVar( $this->context->request, $x );
19 public function output( OutputPage $x = null ){
20 return wfSetVar( $this->context->output, $x );
23 public function __construct( RequestContext $context ){
24 $this->context = $context;
25 $this->context->setTitle( $this->parseTitle() );
28 /**
29 * Parse $request to get the Title object
31 * @return Title object to be $wgTitle
33 private function parseTitle() {
34 global $wgContLang;
36 $curid = $this->context->request->getInt( 'curid' );
37 $title = $this->context->request->getVal( 'title' );
39 if ( $this->context->request->getCheck( 'search' ) ) {
40 // Compatibility with old search URLs which didn't use Special:Search
41 // Just check for presence here, so blank requests still
42 // show the search page when using ugly URLs (bug 8054).
43 $ret = SpecialPage::getTitleFor( 'Search' );
44 } elseif ( $curid ) {
45 // URLs like this are generated by RC, because rc_title isn't always accurate
46 $ret = Title::newFromID( $curid );
47 } elseif ( $title == '' && $this->getAction() != 'delete' ) {
48 $ret = Title::newMainPage();
49 } else {
50 $ret = Title::newFromURL( $title );
51 // check variant links so that interwiki links don't have to worry
52 // about the possible different language variants
53 if ( count( $wgContLang->getVariants() ) > 1 && !is_null( $ret ) && $ret->getArticleID() == 0 ){
54 $wgContLang->findVariantLink( $title, $ret );
57 // For non-special titles, check for implicit titles
58 if ( is_null( $ret ) || $ret->getNamespace() != NS_SPECIAL ) {
59 // We can have urls with just ?diff=,?oldid= or even just ?diff=
60 $oldid = $this->context->request->getInt( 'oldid' );
61 $oldid = $oldid ? $oldid : $this->context->request->getInt( 'diff' );
62 // Allow oldid to override a changed or missing title
63 if ( $oldid ) {
64 $rev = Revision::newFromId( $oldid );
65 $ret = $rev ? $rev->getTitle() : $ret;
69 if( $ret === null || ( $ret->getDBkey() == '' && $ret->getInterwiki() == '' ) ){
70 $ret = new BadTitle;
72 return $ret;
75 /**
76 * Get the Title object that we'll be acting on, as specified in the WebRequest
77 * @return Title
79 public function getTitle(){
80 if( $this->context->title === null ){
81 $this->context->title = $this->parseTitle();
83 return $this->context->title;
86 /**
87 * Performs the request.
88 * - bad titles
89 * - read restriction
90 * - local interwiki redirects
91 * - redirect loop
92 * - special pages
93 * - normal pages
95 * @return Article object
97 public function performRequest() {
98 global $wgServer, $wgUsePathInfo;
100 wfProfileIn( __METHOD__ );
102 if ( $this->context->request->getVal( 'printable' ) === 'yes' ) {
103 $this->context->output->setPrintable();
106 wfRunHooks( 'BeforeInitialize', array(
107 &$this->context->title,
108 null,
109 &$this->context->output,
110 &$this->context->user,
111 $this->context->request,
112 $this
113 ) );
115 // Invalid titles. Bug 21776: The interwikis must redirect even if the page name is empty.
116 if ( $this->context->title instanceof BadTitle ) {
117 throw new ErrorPageError( 'badtitle', 'badtitletext' );
118 // If the user is not logged in, the Namespace:title of the article must be in
119 // the Read array in order for the user to see it. (We have to check here to
120 // catch special pages etc. We check again in Article::view())
121 } else if ( !$this->context->title->userCanRead() ) {
122 $this->context->output->loginToUse();
123 // Interwiki redirects
124 } else if ( $this->context->title->getInterwiki() != '' ) {
125 $rdfrom = $this->context->request->getVal( 'rdfrom' );
126 if ( $rdfrom ) {
127 $url = $this->context->title->getFullURL( 'rdfrom=' . urlencode( $rdfrom ) );
128 } else {
129 $query = $this->context->request->getValues();
130 unset( $query['title'] );
131 $url = $this->context->title->getFullURL( $query );
133 // Check for a redirect loop
134 if ( !preg_match( '/^' . preg_quote( $wgServer, '/' ) . '/', $url ) && $this->context->title->isLocal() ) {
135 // 301 so google et al report the target as the actual url.
136 $this->context->output->redirect( $url, 301 );
137 } else {
138 $this->context->title = new BadTitle;
139 wfProfileOut( __METHOD__ );
140 throw new ErrorPageError( 'badtitle', 'badtitletext' );
142 // Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
143 } else if ( $this->context->request->getVal( 'action', 'view' ) == 'view' && !$this->context->request->wasPosted()
144 && ( $this->context->request->getVal( 'title' ) === null || $this->context->title->getPrefixedDBKey() != $this->context->request->getVal( 'title' ) )
145 && !count( array_diff( array_keys( $this->context->request->getValues() ), array( 'action', 'title' ) ) ) )
147 if ( $this->context->title->getNamespace() == NS_SPECIAL ) {
148 list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $this->context->title->getDBkey() );
149 if ( $name ) {
150 $this->context->title = SpecialPage::getTitleFor( $name, $subpage );
153 $targetUrl = $this->context->title->getFullURL();
154 // Redirect to canonical url, make it a 301 to allow caching
155 if ( $targetUrl == $this->context->request->getFullRequestURL() ) {
156 $message = "Redirect loop detected!\n\n" .
157 "This means the wiki got confused about what page was " .
158 "requested; this sometimes happens when moving a wiki " .
159 "to a new server or changing the server configuration.\n\n";
161 if ( $wgUsePathInfo ) {
162 $message .= "The wiki is trying to interpret the page " .
163 "title from the URL path portion (PATH_INFO), which " .
164 "sometimes fails depending on the web server. Try " .
165 "setting \"\$wgUsePathInfo = false;\" in your " .
166 "LocalSettings.php, or check that \$wgArticlePath " .
167 "is correct.";
168 } else {
169 $message .= "Your web server was detected as possibly not " .
170 "supporting URL path components (PATH_INFO) correctly; " .
171 "check your LocalSettings.php for a customized " .
172 "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
173 "to true.";
175 wfHttpError( 500, "Internal error", $message );
176 } else {
177 $this->context->output->setSquidMaxage( 1200 );
178 $this->context->output->redirect( $targetUrl, '301' );
180 // Special pages
181 } else if ( NS_SPECIAL == $this->context->title->getNamespace() ) {
182 // actions that need to be made when we have a special pages
183 SpecialPageFactory::executePath( $this->context->title, $this->context );
184 } else {
185 // ...otherwise treat it as an article view. The article
186 // may be a redirect to another article or URL.
187 $article = $this->initializeArticle();
188 if ( is_object( $article ) ) {
189 $this->performAction( $article );
190 wfProfileOut( __METHOD__ );
191 return $article;
192 } elseif ( is_string( $article ) ) {
193 $this->context->output->redirect( $article );
194 } else {
195 wfProfileOut( __METHOD__ );
196 throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle() returned neither an object nor a URL" );
199 wfProfileOut( __METHOD__ );
203 * Create an Article object of the appropriate class for the given page.
205 * @deprecated in 1.19; use Article::newFromTitle() instead
206 * @param $title Title
207 * @param $context RequestContext
208 * @return Article object
210 public static function articleFromTitle( $title, RequestContext $context ) {
211 return Article::newFromTitle( $title, $context );
215 * Returns the action that will be executed, not necesserly the one passed
216 * passed through the "action" parameter. Actions disabled in
217 * $wgDisabledActions will be replaced by "nosuchaction"
219 * @return String: action
221 public function getAction() {
222 global $wgDisabledActions;
224 $action = $this->context->request->getVal( 'action', 'view' );
226 // Check for disabled actions
227 if ( in_array( $action, $wgDisabledActions ) ) {
228 return 'nosuchaction';
231 // Workaround for bug #20966: inability of IE to provide an action dependent
232 // on which submit button is clicked.
233 if ( $action === 'historysubmit' ) {
234 if ( $this->context->request->getBool( 'revisiondelete' ) ) {
235 return 'revisiondelete';
236 } else {
237 return 'view';
239 } elseif ( $action == 'editredlink' ) {
240 return 'edit';
243 return $action;
247 * Initialize the main Article object for "standard" actions (view, etc)
248 * Create an Article object for the page, following redirects if needed.
250 * @return mixed an Article, or a string to redirect to another URL
252 private function initializeArticle() {
253 global $wgDisableHardRedirects;
255 wfProfileIn( __METHOD__ );
257 $action = $this->context->request->getVal( 'action', 'view' );
258 $article = Article::newFromTitle( $this->context->title, $this->context );
259 // NS_MEDIAWIKI has no redirects.
260 // It is also used for CSS/JS, so performance matters here...
261 if ( $this->context->title->getNamespace() == NS_MEDIAWIKI ) {
262 wfProfileOut( __METHOD__ );
263 return $article;
265 // Namespace might change when using redirects
266 // Check for redirects ...
267 $file = ( $this->context->title->getNamespace() == NS_FILE ) ? $article->getFile() : null;
268 if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
269 && !$this->context->request->getVal( 'oldid' ) && // ... and are not old revisions
270 !$this->context->request->getVal( 'diff' ) && // ... and not when showing diff
271 $this->context->request->getVal( 'redirect' ) != 'no' && // ... unless explicitly told not to
272 // ... and the article is not a non-redirect image page with associated file
273 !( is_object( $file ) && $file->exists() && !$file->getRedirected() ) )
275 // Give extensions a change to ignore/handle redirects as needed
276 $ignoreRedirect = $target = false;
278 wfRunHooks( 'InitializeArticleMaybeRedirect',
279 array( &$this->context->title, &$this->context->request, &$ignoreRedirect, &$target, &$article ) );
281 // Follow redirects only for... redirects.
282 // If $target is set, then a hook wanted to redirect.
283 if ( !$ignoreRedirect && ( $target || $article->isRedirect() ) ) {
284 // Is the target already set by an extension?
285 $target = $target ? $target : $article->followRedirect();
286 if ( is_string( $target ) ) {
287 if ( !$wgDisableHardRedirects ) {
288 // we'll need to redirect
289 wfProfileOut( __METHOD__ );
290 return $target;
293 if ( is_object( $target ) ) {
294 // Rewrite environment to redirected article
295 $rarticle = Article::newFromTitle( $target, $this->context );
296 $rarticle->loadPageData();
297 if ( $rarticle->exists() || ( is_object( $file ) && !$file->isLocal() ) ) {
298 $rarticle->setRedirectedFrom( $this->context->title );
299 $article = $rarticle;
300 $this->context->title = $target;
303 } else {
304 $this->context->title = $article->getTitle();
307 wfProfileOut( __METHOD__ );
308 return $article;
312 * Cleaning up request by doing deferred updates, DB transaction, and the output
314 public function finalCleanup() {
315 wfProfileIn( __METHOD__ );
316 // Now commit any transactions, so that unreported errors after
317 // output() don't roll back the whole DB transaction
318 $factory = wfGetLBFactory();
319 $factory->commitMasterChanges();
320 // Output everything!
321 $this->context->output->output();
322 // Do any deferred jobs
323 wfDoUpdates( 'commit' );
325 $this->doJobs();
326 wfProfileOut( __METHOD__ );
330 * Do a job from the job queue
332 private function doJobs() {
333 global $wgJobRunRate;
335 if ( $wgJobRunRate <= 0 || wfReadOnly() ) {
336 return;
338 if ( $wgJobRunRate < 1 ) {
339 $max = mt_getrandmax();
340 if ( mt_rand( 0, $max ) > $max * $wgJobRunRate ) {
341 return;
343 $n = 1;
344 } else {
345 $n = intval( $wgJobRunRate );
348 // Close the session so that jobs don't access the current session
349 $this->shutdownLBFactory();
350 session_write_close();
352 while ( $n-- && false != ( $job = Job::pop() ) ) {
353 $output = $job->toString() . "\n";
354 $t = -wfTime();
355 $success = $job->run();
356 $t += wfTime();
357 $t = round( $t * 1000 );
358 if ( !$success ) {
359 $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
360 } else {
361 $output .= "Success, Time: $t ms\n";
363 wfDebugLog( 'jobqueue', $output );
368 * Ends this task peacefully
370 public function restInPeace() {
371 MessageCache::logMessages();
372 wfLogProfilingData();
373 $this->shutdownLBFactory();
374 wfDebug( "Request ended normally\n" );
378 * Commit pending master changes, shutdown the current loadbalancer
379 * factory and destroys the factory instance.
381 private function shutdownLBFactory() {
382 // Commit and close up!
383 $factory = LBFactory::singleton();
384 $factory->commitMasterChanges();
385 $factory->shutdown();
386 LBFactory::destroyInstance();
390 * Perform one of the "standard" actions
392 * @param $article Article
394 private function performAction( $article ) {
395 global $wgSquidMaxage, $wgUseExternalEditor;
397 wfProfileIn( __METHOD__ );
399 if ( !wfRunHooks( 'MediaWikiPerformAction', array(
400 $this->context->output, $article, $this->context->title,
401 $this->context->user, $this->context->request, $this ) ) )
403 wfProfileOut( __METHOD__ );
404 return;
407 $act = $this->getAction();
409 $action = Action::factory( $act, $article );
410 if( $action instanceof Action ){
411 $action->show();
412 wfProfileOut( __METHOD__ );
413 return;
416 switch( $act ) {
417 case 'view':
418 $this->context->output->setSquidMaxage( $wgSquidMaxage );
419 $article->view();
420 break;
421 case 'raw': // includes JS/CSS
422 wfProfileIn( __METHOD__ . '-raw' );
423 $raw = new RawPage( $article );
424 $raw->view();
425 wfProfileOut( __METHOD__ . '-raw' );
426 break;
427 case 'delete':
428 case 'revert':
429 case 'rollback':
430 case 'protect':
431 case 'unprotect':
432 case 'info':
433 case 'markpatrolled':
434 case 'render':
435 case 'deletetrackback':
436 $article->$act();
437 break;
438 case 'submit':
439 if ( session_id() == '' ) {
440 // Send a cookie so anons get talk message notifications
441 wfSetupSession();
443 // Continue...
444 case 'edit':
445 if ( wfRunHooks( 'CustomEditor', array( $article, $this->context->user ) ) ) {
446 $internal = $this->context->request->getVal( 'internaledit' );
447 $external = $this->context->request->getVal( 'externaledit' );
448 $section = $this->context->request->getVal( 'section' );
449 $oldid = $this->context->request->getVal( 'oldid' );
450 if ( !$wgUseExternalEditor || $act == 'submit' || $internal ||
451 $section || $oldid || ( !$this->context->user->getOption( 'externaleditor' ) && !$external ) ) {
452 $editor = new EditPage( $article );
453 $editor->submit();
454 } elseif ( $wgUseExternalEditor && ( $external || $this->context->user->getOption( 'externaleditor' ) ) ) {
455 $mode = $this->context->request->getVal( 'mode' );
456 $extedit = new ExternalEdit( $article, $mode );
457 $extedit->edit();
460 break;
461 case 'history':
462 if ( $this->context->request->getFullRequestURL() == $this->context->title->getInternalURL( 'action=history' ) ) {
463 $this->context->output->setSquidMaxage( $wgSquidMaxage );
465 $history = new HistoryPage( $article );
466 $history->history();
467 break;
468 case 'revisiondelete':
469 // For show/hide submission from history page
470 $special = SpecialPageFactory::getPage( 'Revisiondelete' );
471 $special->execute( '' );
472 break;
473 default:
474 if ( wfRunHooks( 'UnknownAction', array( $act, $article ) ) ) {
475 $this->context->output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
478 wfProfileOut( __METHOD__ );