rvv: r89398. Tim wants me to wait
[lhc/web/wiklou.git] / includes / Wiki.php
1 <?php
2 /**
3 * MediaWiki is the to-be base class for this whole project
4 *
5 * @internal documentation reviewed 15 Mar 2010
6 */
7 class MediaWiki {
8
9 /**
10 * TODO: fold $output, etc, into this
11 * @var RequestContext
12 */
13 private $context;
14
15 public function request( WebRequest $x = null ){
16 return wfSetVar( $this->context->request, $x );
17 }
18
19 public function output( OutputPage $x = null ){
20 return wfSetVar( $this->context->output, $x );
21 }
22
23 public function __construct( RequestContext $context ){
24 $this->context = $context;
25 $this->context->setTitle( $this->parseTitle() );
26 }
27
28 /**
29 * Parse $request to get the Title object
30 *
31 * @return Title object to be $wgTitle
32 */
33 private function parseTitle() {
34 global $wgContLang;
35
36 $curid = $this->context->request->getInt( 'curid' );
37 $title = $this->context->request->getVal( 'title' );
38
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 );
55 }
56 }
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;
66 }
67 }
68
69 if( $ret === null || ( $ret->getDBkey() == '' && $ret->getInterwiki() == '' ) ){
70 $ret = new BadTitle;
71 }
72 return $ret;
73 }
74
75 /**
76 * Get the Title object that we'll be acting on, as specified in the WebRequest
77 * @return Title
78 */
79 public function getTitle(){
80 if( $this->context->title === null ){
81 $this->context->title = $this->parseTitle();
82 }
83 return $this->context->title;
84 }
85
86 /**
87 * Performs the request.
88 * - bad titles
89 * - read restriction
90 * - local interwiki redirects
91 * - redirect loop
92 * - special pages
93 * - normal pages
94 *
95 * @return Article object
96 */
97 public function performRequest() {
98 global $wgServer, $wgUsePathInfo;
99
100 wfProfileIn( __METHOD__ );
101
102 if ( $this->context->request->getVal( 'printable' ) === 'yes' ) {
103 $this->context->output->setPrintable();
104 }
105
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 ) );
114
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 );
132 }
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' );
141 }
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' ) ) ) )
146 {
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 );
151 }
152 }
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";
160
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.";
174 }
175 wfHttpError( 500, "Internal error", $message );
176 } else {
177 $this->context->output->setSquidMaxage( 1200 );
178 $this->context->output->redirect( $targetUrl, '301' );
179 }
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" );
197 }
198 }
199 wfProfileOut( __METHOD__ );
200 }
201
202 /**
203 * Create an Article object of the appropriate class for the given page.
204 *
205 * @deprecated in 1.19; use Article::newFromTitle() instead
206 * @param $title Title
207 * @param $context RequestContext
208 * @return Article object
209 */
210 public static function articleFromTitle( $title, RequestContext $context ) {
211 return Article::newFromTitle( $title, $context );
212 }
213
214 /**
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"
218 *
219 * @return String: action
220 */
221 public function getAction() {
222 global $wgDisabledActions;
223
224 $action = $this->context->request->getVal( 'action', 'view' );
225
226 // Check for disabled actions
227 if ( in_array( $action, $wgDisabledActions ) ) {
228 return 'nosuchaction';
229 }
230
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';
238 }
239 } elseif ( $action == 'editredlink' ) {
240 return 'edit';
241 }
242
243 return $action;
244 }
245
246 /**
247 * Initialize the main Article object for "standard" actions (view, etc)
248 * Create an Article object for the page, following redirects if needed.
249 *
250 * @return mixed an Article, or a string to redirect to another URL
251 */
252 private function initializeArticle() {
253 global $wgDisableHardRedirects;
254
255 wfProfileIn( __METHOD__ );
256
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;
264 }
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() ) )
274 {
275 // Give extensions a change to ignore/handle redirects as needed
276 $ignoreRedirect = $target = false;
277
278 wfRunHooks( 'InitializeArticleMaybeRedirect',
279 array( &$this->context->title, &$this->context->request, &$ignoreRedirect, &$target, &$article ) );
280
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;
291 }
292 }
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;
301 }
302 }
303 } else {
304 $this->context->title = $article->getTitle();
305 }
306 }
307 wfProfileOut( __METHOD__ );
308 return $article;
309 }
310
311 /**
312 * Cleaning up request by doing deferred updates, DB transaction, and the output
313 */
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' );
324
325 $this->doJobs();
326 wfProfileOut( __METHOD__ );
327 }
328
329 /**
330 * Do a job from the job queue
331 */
332 private function doJobs() {
333 global $wgJobRunRate;
334
335 if ( $wgJobRunRate <= 0 || wfReadOnly() ) {
336 return;
337 }
338 if ( $wgJobRunRate < 1 ) {
339 $max = mt_getrandmax();
340 if ( mt_rand( 0, $max ) > $max * $wgJobRunRate ) {
341 return;
342 }
343 $n = 1;
344 } else {
345 $n = intval( $wgJobRunRate );
346 }
347
348 // Close the session so that jobs don't access the current session
349 $this->shutdownLBFactory();
350 session_write_close();
351
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";
362 }
363 wfDebugLog( 'jobqueue', $output );
364 }
365 }
366
367 /**
368 * Ends this task peacefully
369 */
370 public function restInPeace() {
371 MessageCache::logMessages();
372 wfLogProfilingData();
373 $this->shutdownLBFactory();
374 wfDebug( "Request ended normally\n" );
375 }
376
377 /**
378 * Commit pending master changes, shutdown the current loadbalancer
379 * factory and destroys the factory instance.
380 */
381 private function shutdownLBFactory() {
382 // Commit and close up!
383 $factory = LBFactory::singleton();
384 $factory->commitMasterChanges();
385 $factory->shutdown();
386 LBFactory::destroyInstance();
387 }
388
389 /**
390 * Perform one of the "standard" actions
391 *
392 * @param $article Article
393 */
394 private function performAction( $article ) {
395 global $wgSquidMaxage, $wgUseExternalEditor;
396
397 wfProfileIn( __METHOD__ );
398
399 if ( !wfRunHooks( 'MediaWikiPerformAction', array(
400 $this->context->output, $article, $this->context->title,
401 $this->context->user, $this->context->request, $this ) ) )
402 {
403 wfProfileOut( __METHOD__ );
404 return;
405 }
406
407 $act = $this->getAction();
408
409 $action = Action::factory( $act, $article );
410 if( $action instanceof Action ){
411 $action->show();
412 wfProfileOut( __METHOD__ );
413 return;
414 }
415
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();
442 }
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();
458 }
459 }
460 break;
461 case 'history':
462 if ( $this->context->request->getFullRequestURL() == $this->context->title->getInternalURL( 'action=history' ) ) {
463 $this->context->output->setSquidMaxage( $wgSquidMaxage );
464 }
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' );
476 }
477 }
478 wfProfileOut( __METHOD__ );
479 }
480 }