]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/Wiki.php
MediaWiki 1.17.4
[autoinstallsdev/mediawiki.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         var $params = array();
9
10         /** Constructor */
11         function __construct() {}
12
13         /**
14          * Stores key/value pairs to circumvent global variables
15          * Note that keys are case-insensitive!
16          *
17          * @param $key String: key to store
18          * @param $value Mixed: value to put for the key
19          */
20         function setVal( $key, &$value ) {
21                 $key = strtolower( $key );
22                 $this->params[$key] =& $value;
23         }
24
25         /**
26          * Retrieves key/value pairs to circumvent global variables
27          * Note that keys are case-insensitive!
28          *
29          * @param $key String: key to get
30          * @param $default string default value, defaults to empty string
31          * @return $default Mixed: default value if if the key doesn't exist
32          */
33         function getVal( $key, $default = '' ) {
34                 $key = strtolower( $key );
35                 if( isset( $this->params[$key] ) ) {
36                         return $this->params[$key];
37                 }
38                 return $default;
39         }
40
41         /**
42          * Initialization of ... everything
43          * Performs the request too
44          *
45          * @param $title Title ($wgTitle)
46          * @param $article Article
47          * @param $output OutputPage
48          * @param $user User
49          * @param $request WebRequest
50          */
51         function performRequestForTitle( &$title, &$article, &$output, &$user, $request ) {
52                 wfProfileIn( __METHOD__ );
53
54                 $output->setTitle( $title );
55
56                 wfRunHooks( 'BeforeInitialize', array( &$title, &$article, &$output, &$user, $request, $this ) );
57
58                 if( !$this->preliminaryChecks( $title, $output ) ) {
59                         wfProfileOut( __METHOD__ );
60                         return;
61                 }
62                 // Call handleSpecialCases() to deal with all special requests...
63                 if( !$this->handleSpecialCases( $title, $output, $request ) ) {
64                         // ...otherwise treat it as an article view. The article
65                         // may be a redirect to another article or URL.
66                         $new_article = $this->initializeArticle( $title, $output, $request );
67                         if( is_object( $new_article ) ) {
68                                 $article = $new_article;
69                                 $this->performAction( $output, $article, $title, $user, $request );
70                         } elseif( is_string( $new_article ) ) {
71                                 $output->redirect( $new_article );
72                         } else {
73                                 wfProfileOut( __METHOD__ );
74                                 throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle() returned neither an object nor a URL" );
75                         }
76                 }
77                 wfProfileOut( __METHOD__ );
78         }
79
80         /**
81          * Check if the maximum lag of database slaves is higher that $maxLag, and
82          * if it's the case, output an error message
83          *
84          * @param $maxLag int: maximum lag allowed for the request, as supplied by
85          *                the client
86          * @return bool true if the request can continue
87          */
88         function checkMaxLag( $maxLag ) {
89                 list( $host, $lag ) = wfGetLB()->getMaxLag();
90                 if( $lag > $maxLag ) {
91                         wfMaxlagError( $host, $lag, $maxLag );
92                         return false;
93                 } else {
94                         return true;
95                 }
96         }
97
98         /**
99          * Checks some initial queries
100          * Note that $title here is *not* a Title object, but a string!
101          *
102          * @param $title String
103          * @param $action String
104          * @return Title object to be $wgTitle
105          */
106         function checkInitialQueries( $title, $action ) {
107                 global $wgOut, $wgRequest, $wgContLang;
108                 if( $wgRequest->getVal( 'printable' ) === 'yes' ) {
109                         $wgOut->setPrintable();
110                 }
111
112                 $curid = $wgRequest->getInt( 'curid' );
113                 if( $wgRequest->getCheck( 'search' ) ) {
114                         // Compatibility with old search URLs which didn't use Special:Search
115                         // Just check for presence here, so blank requests still
116                         // show the search page when using ugly URLs (bug 8054).
117                         $ret = SpecialPage::getTitleFor( 'Search' );
118                 } elseif( $curid ) {
119                         // URLs like this are generated by RC, because rc_title isn't always accurate
120                         $ret = Title::newFromID( $curid );
121                 } elseif( $title == '' && $action != 'delete' ) {
122                         $ret = Title::newMainPage();
123                 } else {
124                         $ret = Title::newFromURL( $title );
125                         // check variant links so that interwiki links don't have to worry
126                         // about the possible different language variants
127                         if( count( $wgContLang->getVariants() ) > 1 && !is_null( $ret ) && $ret->getArticleID() == 0 )
128                                 $wgContLang->findVariantLink( $title, $ret );
129                 }
130                 // For non-special titles, check for implicit titles
131                 if( is_null( $ret ) || $ret->getNamespace() != NS_SPECIAL ) {
132                         // We can have urls with just ?diff=,?oldid= or even just ?diff=
133                         $oldid = $wgRequest->getInt( 'oldid' );
134                         $oldid = $oldid ? $oldid : $wgRequest->getInt( 'diff' );
135                         // Allow oldid to override a changed or missing title
136                         if( $oldid ) {
137                                 $rev = Revision::newFromId( $oldid );
138                                 $ret = $rev ? $rev->getTitle() : $ret;
139                         }
140                 }
141                 return $ret;
142         }
143
144         /**
145          * Checks for anon-cannot-read case
146          *
147          * @param $title Title
148          * @param $output OutputPage
149          * @return boolean true if successful
150          */
151         function preliminaryChecks( &$title, &$output ) {
152                 global $wgTitle;
153                 // If the user is not logged in, the Namespace:title of the article must be in
154                 // the Read array in order for the user to see it. (We have to check here to
155                 // catch special pages etc. We check again in Article::view())
156                 if( !is_null( $title ) && !$title->userCanRead() ) {
157                         // Bug 32276: allowing the skin to generate output with $wgTitle 
158                         // set to the input title would allow anonymous users to 
159                         // determine whether a page exists, potentially leaking private data. In fact, the 
160                         // curid and oldid request  parameters would allow page titles to be enumerated even 
161                         // when they are not guessable. So we reset the title to Special:Badtitle before the 
162                         // permissions error is displayed.
163                         $badtitle = SpecialPage::getTitleFor( 'Badtitle' );
164                         $output->setTitle( $badtitle );
165                         $wgTitle = $badtitle;
166
167                         $output->loginToUse();
168                         $this->finalCleanup( $output );
169                         $output->disable();
170                         return false;
171                 }
172                 return true;
173         }
174
175         /**
176          * Initialize some special cases:
177          * - bad titles
178          * - local interwiki redirects
179          * - redirect loop
180          * - special pages
181          *
182          * @param $title Title
183          * @param $output OutputPage
184          * @param $request WebRequest
185          * @return bool true if the request is already executed
186          */
187         function handleSpecialCases( &$title, &$output, $request ) {
188                 wfProfileIn( __METHOD__ );
189
190                 $action = $this->getVal( 'Action' );
191
192                 // Invalid titles. Bug 21776: The interwikis must redirect even if the page name is empty.
193                 if( is_null($title) || ( ( $title->getDBkey() == '' ) && ( $title->getInterwiki() == '' ) ) ) {
194                         $title = SpecialPage::getTitleFor( 'Badtitle' );
195                         $output->setTitle( $title ); // bug 21456
196                         // Die now before we mess up $wgArticle and the skin stops working
197                         throw new ErrorPageError( 'badtitle', 'badtitletext' );
198
199                 // Interwiki redirects
200                 } else if( $title->getInterwiki() != '' ) {
201                         $rdfrom = $request->getVal( 'rdfrom' );
202                         if( $rdfrom ) {
203                                 $url = $title->getFullURL( 'rdfrom=' . urlencode( $rdfrom ) );
204                         } else {
205                                 $query = $request->getValues();
206                                 unset( $query['title'] );
207                                 $url = $title->getFullURL( $query );
208                         }
209                         /* Check for a redirect loop */
210                         if( !preg_match( '/^' . preg_quote( $this->getVal('Server'), '/' ) . '/', $url ) && $title->isLocal() ) {
211                                 // 301 so google et al report the target as the actual url.
212                                 $output->redirect( $url, 301 );
213                         } else {
214                                 $title = SpecialPage::getTitleFor( 'Badtitle' );
215                                 $output->setTitle( $title ); // bug 21456
216                                 wfProfileOut( __METHOD__ );
217                                 throw new ErrorPageError( 'badtitle', 'badtitletext' );
218                         }
219                 // Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
220                 } else if ( $action == 'view' && !$request->wasPosted()
221                         && ( $request->getVal( 'title' ) === null || $title->getPrefixedDBKey() != $request->getVal( 'title' ) )
222                         && !count( array_diff( array_keys( $request->getValues() ), array( 'action', 'title' ) ) ) )
223                 {
224                         if ( $title->getNamespace() == NS_SPECIAL ) {
225                                 list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() );
226                                 if ( $name ) {
227                                         $title = SpecialPage::getTitleFor( $name, $subpage );
228                                 }
229                         }
230                         $targetUrl = $title->getFullURL();
231                         // Redirect to canonical url, make it a 301 to allow caching
232                         if( $targetUrl == $request->getFullRequestURL() ) {
233                                 $message = "Redirect loop detected!\n\n" .
234                                         "This means the wiki got confused about what page was " .
235                                         "requested; this sometimes happens when moving a wiki " .
236                                         "to a new server or changing the server configuration.\n\n";
237
238                                 if( $this->getVal( 'UsePathInfo' ) ) {
239                                         $message .= "The wiki is trying to interpret the page " .
240                                                 "title from the URL path portion (PATH_INFO), which " .
241                                                 "sometimes fails depending on the web server. Try " .
242                                                 "setting \"\$wgUsePathInfo = false;\" in your " .
243                                                 "LocalSettings.php, or check that \$wgArticlePath " .
244                                                 "is correct.";
245                                 } else {
246                                         $message .= "Your web server was detected as possibly not " .
247                                                 "supporting URL path components (PATH_INFO) correctly; " .
248                                                 "check your LocalSettings.php for a customized " .
249                                                 "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
250                                                 "to true.";
251                                 }
252                                 wfHttpError( 500, "Internal error", $message );
253                                 wfProfileOut( __METHOD__ );
254                                 return false;
255                         } else {
256                                 $output->setSquidMaxage( 1200 );
257                                 $output->redirect( $targetUrl, '301' );
258                         }
259                 // Special pages
260                 } else if( NS_SPECIAL == $title->getNamespace() ) {
261                         /* actions that need to be made when we have a special pages */
262                         SpecialPage::executePath( $title );
263                 } else {
264                         /* No match to special cases */
265                         wfProfileOut( __METHOD__ );
266                         return false;
267                 }
268                 /* Did match a special case */
269                 wfProfileOut( __METHOD__ );
270                 return true;
271         }
272
273         /**
274          * Create an Article object of the appropriate class for the given page.
275          *
276          * @param $title Title
277          * @return Article object
278          */
279         static function articleFromTitle( &$title ) {
280                 if( NS_MEDIA == $title->getNamespace() ) {
281                         // FIXME: where should this go?
282                         $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
283                 }
284
285                 $article = null;
286                 wfRunHooks( 'ArticleFromTitle', array( &$title, &$article ) );
287                 if( $article ) {
288                         return $article;
289                 }
290
291                 switch( $title->getNamespace() ) {
292                         case NS_FILE:
293                                 return new ImagePage( $title );
294                         case NS_CATEGORY:
295                                 return new CategoryPage( $title );
296                         default:
297                                 return new Article( $title );
298                 }
299         }
300
301         /**
302          * Initialize the object to be known as $wgArticle for "standard" actions
303          * Create an Article object for the page, following redirects if needed.
304          *
305          * @param $title Title ($wgTitle)
306          * @param $output OutputPage ($wgOut)
307          * @param $request WebRequest ($wgRequest)
308          * @return mixed an Article, or a string to redirect to another URL
309          */
310         function initializeArticle( &$title, &$output, $request ) {
311                 wfProfileIn( __METHOD__ );
312
313                 $action = $this->getVal( 'action', 'view' );
314                 $article = self::articleFromTitle( $title );
315                 // NS_MEDIAWIKI has no redirects.
316                 // It is also used for CSS/JS, so performance matters here...
317                 if( $title->getNamespace() == NS_MEDIAWIKI ) {
318                         wfProfileOut( __METHOD__ );
319                         return $article;
320                 }
321                 // Namespace might change when using redirects
322                 // Check for redirects ...
323                 $file = ($title->getNamespace() == NS_FILE) ? $article->getFile() : null;
324                 if( ( $action == 'view' || $action == 'render' )        // ... for actions that show content
325                         && !$request->getVal( 'oldid' ) &&    // ... and are not old revisions
326                         !$request->getVal( 'diff' ) &&    // ... and not when showing diff
327                         $request->getVal( 'redirect' ) != 'no' &&       // ... unless explicitly told not to
328                         // ... and the article is not a non-redirect image page with associated file
329                         !( is_object( $file ) && $file->exists() && !$file->getRedirected() ) )
330                 {
331                         // Give extensions a change to ignore/handle redirects as needed
332                         $ignoreRedirect = $target = false;
333
334                         $dbr = wfGetDB( DB_SLAVE );
335                         $article->loadPageData( $article->pageDataFromTitle( $dbr, $title ) );
336
337                         wfRunHooks( 'InitializeArticleMaybeRedirect',
338                                 array(&$title,&$request,&$ignoreRedirect,&$target,&$article) );
339
340                         // Follow redirects only for... redirects.
341                         // If $target is set, then a hook wanted to redirect.
342                         if( !$ignoreRedirect && ($target || $article->isRedirect()) ) {
343                                 // Is the target already set by an extension?
344                                 $target = $target ? $target : $article->followRedirect();
345                                 if( is_string( $target ) ) {
346                                         if( !$this->getVal( 'DisableHardRedirects' ) ) {
347                                                 // we'll need to redirect
348                                                 wfProfileOut( __METHOD__ );
349                                                 return $target;
350                                         }
351                                 }
352                                 if( is_object($target) ) {
353                                         // Rewrite environment to redirected article
354                                         $rarticle = self::articleFromTitle( $target );
355                                         $rarticle->loadPageData( $rarticle->pageDataFromTitle( $dbr, $target ) );
356                                         if( $rarticle->exists() || ( is_object( $file ) && !$file->isLocal() ) ) {
357                                                 $rarticle->setRedirectedFrom( $title );
358                                                 $article = $rarticle;
359                                                 $title = $target;
360                                                 $output->setTitle( $title );
361                                         }
362                                 }
363                         } else {
364                                 $title = $article->getTitle();
365                         }
366                 }
367                 wfProfileOut( __METHOD__ );
368                 return $article;
369         }
370
371         /**
372          * Cleaning up request by doing:
373          ** deferred updates, DB transaction, and the output
374          *
375          * @param $output OutputPage
376          */
377         function finalCleanup( &$output ) {
378                 wfProfileIn( __METHOD__ );
379                 // Now commit any transactions, so that unreported errors after
380                 // output() don't roll back the whole DB transaction
381                 $factory = wfGetLBFactory();
382                 $factory->commitMasterChanges();
383                 // Output everything!
384                 $output->output();
385                 // Do any deferred jobs
386                 wfDoUpdates( true );
387                 $this->doJobs();
388                 wfProfileOut( __METHOD__ );
389         }
390
391         /**
392          * Do a job from the job queue
393          */
394         function doJobs() {
395                 $jobRunRate = $this->getVal( 'JobRunRate' );
396
397                 if( $jobRunRate <= 0 || wfReadOnly() ) {
398                         return;
399                 }
400                 if( $jobRunRate < 1 ) {
401                         $max = mt_getrandmax();
402                         if( mt_rand( 0, $max ) > $max * $jobRunRate ) {
403                                 return;
404                         }
405                         $n = 1;
406                 } else {
407                         $n = intval( $jobRunRate );
408                 }
409
410                 while ( $n-- && false != ( $job = Job::pop() ) ) {
411                         $output = $job->toString() . "\n";
412                         $t = -wfTime();
413                         $success = $job->run();
414                         $t += wfTime();
415                         $t = round( $t*1000 );
416                         if( !$success ) {
417                                 $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
418                         } else {
419                                 $output .= "Success, Time: $t ms\n";
420                         }
421                         wfDebugLog( 'jobqueue', $output );
422                 }
423         }
424
425         /**
426          * Ends this task peacefully
427          */
428         function restInPeace() {
429                 wfLogProfilingData();
430                 // Commit and close up!
431                 $factory = wfGetLBFactory();
432                 $factory->commitMasterChanges();
433                 $factory->shutdown();
434                 wfDebug( "Request ended normally\n" );
435         }
436
437         /**
438          * Perform one of the "standard" actions
439          *
440          * @param $output OutputPage
441          * @param $article Article
442          * @param $title Title
443          * @param $user User
444          * @param $request WebRequest
445          */
446         function performAction( &$output, &$article, &$title, &$user, &$request ) {
447                 wfProfileIn( __METHOD__ );
448
449                 if( !wfRunHooks( 'MediaWikiPerformAction', array( $output, $article, $title, $user, $request, $this ) ) ) {
450                         wfProfileOut( __METHOD__ );
451                         return;
452                 }
453
454                 $action = $this->getVal( 'Action' );
455                 if( in_array( $action, $this->getVal( 'DisabledActions', array() ) ) ) {
456                         /* No such action; this will switch to the default case */
457                         $action = 'nosuchaction';
458                 }
459
460                 // Workaround for bug #20966: inability of IE to provide an action dependent
461                 // on which submit button is clicked.
462                 if ( $action === 'historysubmit' ) {
463                         if ( $request->getBool( 'revisiondelete' ) ) {
464                                 $action = 'revisiondelete';
465                         } else {
466                                 $action = 'view';
467                         }
468                 }
469
470                 switch( $action ) {
471                         case 'view':
472                                 $output->setSquidMaxage( $this->getVal( 'SquidMaxage' ) );
473                                 $article->view();
474                                 break;
475                         case 'raw': // includes JS/CSS
476                                 wfProfileIn( __METHOD__.'-raw' );
477                                 $raw = new RawPage( $article );
478                                 $raw->view();
479                                 wfProfileOut( __METHOD__.'-raw' );
480                                 break;
481                         case 'watch':
482                         case 'unwatch':
483                         case 'delete':
484                         case 'revert':
485                         case 'rollback':
486                         case 'protect':
487                         case 'unprotect':
488                         case 'info':
489                         case 'markpatrolled':
490                         case 'render':
491                         case 'deletetrackback':
492                         case 'purge':
493                                 $article->$action();
494                                 break;
495                         case 'print':
496                                 $article->view();
497                                 break;
498                         case 'dublincore':
499                                 if( !$this->getVal( 'EnableDublinCoreRdf' ) ) {
500                                         wfHttpError( 403, 'Forbidden', wfMsg( 'nodublincore' ) );
501                                 } else {
502                                         $rdf = new DublinCoreRdf( $article );
503                                         $rdf->show();
504                                 }
505                                 break;
506                         case 'creativecommons':
507                                 if( !$this->getVal( 'EnableCreativeCommonsRdf' ) ) {
508                                         wfHttpError( 403, 'Forbidden', wfMsg( 'nocreativecommons' ) );
509                                 } else {
510                                         $rdf = new CreativeCommonsRdf( $article );
511                                         $rdf->show();
512                                 }
513                                 break;
514                         case 'credits':
515                                 Credits::showPage( $article );
516                                 break;
517                         case 'submit':
518                                 if( session_id() == '' ) {
519                                         /* Send a cookie so anons get talk message notifications */
520                                         wfSetupSession();
521                                 }
522                                 /* Continue... */
523                         case 'edit':
524                         case 'editredlink':
525                                 if( wfRunHooks( 'CustomEditor', array( $article, $user ) ) ) {
526                                         $internal = $request->getVal( 'internaledit' );
527                                         $external = $request->getVal( 'externaledit' );
528                                         $section = $request->getVal( 'section' );
529                                         $oldid = $request->getVal( 'oldid' );
530                                         if( !$this->getVal( 'UseExternalEditor' ) || $action=='submit' || $internal ||
531                                            $section || $oldid || ( !$user->getOption( 'externaleditor' ) && !$external ) ) {
532                                                 $editor = new EditPage( $article );
533                                                 $editor->submit();
534                                         } elseif( $this->getVal( 'UseExternalEditor' ) && ( $external || $user->getOption( 'externaleditor' ) ) ) {
535                                                 $mode = $request->getVal( 'mode' );
536                                                 $extedit = new ExternalEdit( $article, $mode );
537                                                 $extedit->edit();
538                                         }
539                                 }
540                                 break;
541                         case 'history':
542                                 if( $request->getFullRequestURL() == $title->getInternalURL( 'action=history' ) ) {
543                                         $output->setSquidMaxage( $this->getVal( 'SquidMaxage' ) );
544                                 }
545                                 $history = new HistoryPage( $article );
546                                 $history->history();
547                                 break;
548                         case 'revisiondelete':
549                                 // For show/hide submission from history page
550                                 $special = SpecialPage::getPage( 'Revisiondelete' );
551                                 $special->execute( '' );
552                                 break;
553                         default:
554                                 if( wfRunHooks( 'UnknownAction', array( $action, $article ) ) ) {
555                                         $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
556                                 }
557                 }
558                 wfProfileOut( __METHOD__ );
559
560         }
561
562 }