]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/Wiki.php
MediaWiki 1.17.0
[autoinstalls/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                 // If the user is not logged in, the Namespace:title of the article must be in
153                 // the Read array in order for the user to see it. (We have to check here to
154                 // catch special pages etc. We check again in Article::view())
155                 if( !is_null( $title ) && !$title->userCanRead() ) {
156                         $output->loginToUse();
157                         $this->finalCleanup( $output );
158                         $output->disable();
159                         return false;
160                 }
161                 return true;
162         }
163
164         /**
165          * Initialize some special cases:
166          * - bad titles
167          * - local interwiki redirects
168          * - redirect loop
169          * - special pages
170          *
171          * @param $title Title
172          * @param $output OutputPage
173          * @param $request WebRequest
174          * @return bool true if the request is already executed
175          */
176         function handleSpecialCases( &$title, &$output, $request ) {
177                 wfProfileIn( __METHOD__ );
178
179                 $action = $this->getVal( 'Action' );
180
181                 // Invalid titles. Bug 21776: The interwikis must redirect even if the page name is empty.
182                 if( is_null($title) || ( ( $title->getDBkey() == '' ) && ( $title->getInterwiki() == '' ) ) ) {
183                         $title = SpecialPage::getTitleFor( 'Badtitle' );
184                         $output->setTitle( $title ); // bug 21456
185                         // Die now before we mess up $wgArticle and the skin stops working
186                         throw new ErrorPageError( 'badtitle', 'badtitletext' );
187
188                 // Interwiki redirects
189                 } else if( $title->getInterwiki() != '' ) {
190                         $rdfrom = $request->getVal( 'rdfrom' );
191                         if( $rdfrom ) {
192                                 $url = $title->getFullURL( 'rdfrom=' . urlencode( $rdfrom ) );
193                         } else {
194                                 $query = $request->getValues();
195                                 unset( $query['title'] );
196                                 $url = $title->getFullURL( $query );
197                         }
198                         /* Check for a redirect loop */
199                         if( !preg_match( '/^' . preg_quote( $this->getVal('Server'), '/' ) . '/', $url ) && $title->isLocal() ) {
200                                 // 301 so google et al report the target as the actual url.
201                                 $output->redirect( $url, 301 );
202                         } else {
203                                 $title = SpecialPage::getTitleFor( 'Badtitle' );
204                                 $output->setTitle( $title ); // bug 21456
205                                 wfProfileOut( __METHOD__ );
206                                 throw new ErrorPageError( 'badtitle', 'badtitletext' );
207                         }
208                 // Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
209                 } else if ( $action == 'view' && !$request->wasPosted()
210                         && ( $request->getVal( 'title' ) === null || $title->getPrefixedDBKey() != $request->getVal( 'title' ) )
211                         && !count( array_diff( array_keys( $request->getValues() ), array( 'action', 'title' ) ) ) )
212                 {
213                         if ( $title->getNamespace() == NS_SPECIAL ) {
214                                 list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() );
215                                 if ( $name ) {
216                                         $title = SpecialPage::getTitleFor( $name, $subpage );
217                                 }
218                         }
219                         $targetUrl = $title->getFullURL();
220                         // Redirect to canonical url, make it a 301 to allow caching
221                         if( $targetUrl == $request->getFullRequestURL() ) {
222                                 $message = "Redirect loop detected!\n\n" .
223                                         "This means the wiki got confused about what page was " .
224                                         "requested; this sometimes happens when moving a wiki " .
225                                         "to a new server or changing the server configuration.\n\n";
226
227                                 if( $this->getVal( 'UsePathInfo' ) ) {
228                                         $message .= "The wiki is trying to interpret the page " .
229                                                 "title from the URL path portion (PATH_INFO), which " .
230                                                 "sometimes fails depending on the web server. Try " .
231                                                 "setting \"\$wgUsePathInfo = false;\" in your " .
232                                                 "LocalSettings.php, or check that \$wgArticlePath " .
233                                                 "is correct.";
234                                 } else {
235                                         $message .= "Your web server was detected as possibly not " .
236                                                 "supporting URL path components (PATH_INFO) correctly; " .
237                                                 "check your LocalSettings.php for a customized " .
238                                                 "\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
239                                                 "to true.";
240                                 }
241                                 wfHttpError( 500, "Internal error", $message );
242                                 wfProfileOut( __METHOD__ );
243                                 return false;
244                         } else {
245                                 $output->setSquidMaxage( 1200 );
246                                 $output->redirect( $targetUrl, '301' );
247                         }
248                 // Special pages
249                 } else if( NS_SPECIAL == $title->getNamespace() ) {
250                         /* actions that need to be made when we have a special pages */
251                         SpecialPage::executePath( $title );
252                 } else {
253                         /* No match to special cases */
254                         wfProfileOut( __METHOD__ );
255                         return false;
256                 }
257                 /* Did match a special case */
258                 wfProfileOut( __METHOD__ );
259                 return true;
260         }
261
262         /**
263          * Create an Article object of the appropriate class for the given page.
264          *
265          * @param $title Title
266          * @return Article object
267          */
268         static function articleFromTitle( &$title ) {
269                 if( NS_MEDIA == $title->getNamespace() ) {
270                         // FIXME: where should this go?
271                         $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
272                 }
273
274                 $article = null;
275                 wfRunHooks( 'ArticleFromTitle', array( &$title, &$article ) );
276                 if( $article ) {
277                         return $article;
278                 }
279
280                 switch( $title->getNamespace() ) {
281                         case NS_FILE:
282                                 return new ImagePage( $title );
283                         case NS_CATEGORY:
284                                 return new CategoryPage( $title );
285                         default:
286                                 return new Article( $title );
287                 }
288         }
289
290         /**
291          * Initialize the object to be known as $wgArticle for "standard" actions
292          * Create an Article object for the page, following redirects if needed.
293          *
294          * @param $title Title ($wgTitle)
295          * @param $output OutputPage ($wgOut)
296          * @param $request WebRequest ($wgRequest)
297          * @return mixed an Article, or a string to redirect to another URL
298          */
299         function initializeArticle( &$title, &$output, $request ) {
300                 wfProfileIn( __METHOD__ );
301
302                 $action = $this->getVal( 'action', 'view' );
303                 $article = self::articleFromTitle( $title );
304                 // NS_MEDIAWIKI has no redirects.
305                 // It is also used for CSS/JS, so performance matters here...
306                 if( $title->getNamespace() == NS_MEDIAWIKI ) {
307                         wfProfileOut( __METHOD__ );
308                         return $article;
309                 }
310                 // Namespace might change when using redirects
311                 // Check for redirects ...
312                 $file = ($title->getNamespace() == NS_FILE) ? $article->getFile() : null;
313                 if( ( $action == 'view' || $action == 'render' )        // ... for actions that show content
314                         && !$request->getVal( 'oldid' ) &&    // ... and are not old revisions
315                         !$request->getVal( 'diff' ) &&    // ... and not when showing diff
316                         $request->getVal( 'redirect' ) != 'no' &&       // ... unless explicitly told not to
317                         // ... and the article is not a non-redirect image page with associated file
318                         !( is_object( $file ) && $file->exists() && !$file->getRedirected() ) )
319                 {
320                         // Give extensions a change to ignore/handle redirects as needed
321                         $ignoreRedirect = $target = false;
322
323                         $dbr = wfGetDB( DB_SLAVE );
324                         $article->loadPageData( $article->pageDataFromTitle( $dbr, $title ) );
325
326                         wfRunHooks( 'InitializeArticleMaybeRedirect',
327                                 array(&$title,&$request,&$ignoreRedirect,&$target,&$article) );
328
329                         // Follow redirects only for... redirects.
330                         // If $target is set, then a hook wanted to redirect.
331                         if( !$ignoreRedirect && ($target || $article->isRedirect()) ) {
332                                 // Is the target already set by an extension?
333                                 $target = $target ? $target : $article->followRedirect();
334                                 if( is_string( $target ) ) {
335                                         if( !$this->getVal( 'DisableHardRedirects' ) ) {
336                                                 // we'll need to redirect
337                                                 wfProfileOut( __METHOD__ );
338                                                 return $target;
339                                         }
340                                 }
341                                 if( is_object($target) ) {
342                                         // Rewrite environment to redirected article
343                                         $rarticle = self::articleFromTitle( $target );
344                                         $rarticle->loadPageData( $rarticle->pageDataFromTitle( $dbr, $target ) );
345                                         if( $rarticle->exists() || ( is_object( $file ) && !$file->isLocal() ) ) {
346                                                 $rarticle->setRedirectedFrom( $title );
347                                                 $article = $rarticle;
348                                                 $title = $target;
349                                                 $output->setTitle( $title );
350                                         }
351                                 }
352                         } else {
353                                 $title = $article->getTitle();
354                         }
355                 }
356                 wfProfileOut( __METHOD__ );
357                 return $article;
358         }
359
360         /**
361          * Cleaning up request by doing:
362          ** deferred updates, DB transaction, and the output
363          *
364          * @param $output OutputPage
365          */
366         function finalCleanup( &$output ) {
367                 wfProfileIn( __METHOD__ );
368                 // Now commit any transactions, so that unreported errors after
369                 // output() don't roll back the whole DB transaction
370                 $factory = wfGetLBFactory();
371                 $factory->commitMasterChanges();
372                 // Output everything!
373                 $output->output();
374                 // Do any deferred jobs
375                 wfDoUpdates( true );
376                 $this->doJobs();
377                 wfProfileOut( __METHOD__ );
378         }
379
380         /**
381          * Do a job from the job queue
382          */
383         function doJobs() {
384                 $jobRunRate = $this->getVal( 'JobRunRate' );
385
386                 if( $jobRunRate <= 0 || wfReadOnly() ) {
387                         return;
388                 }
389                 if( $jobRunRate < 1 ) {
390                         $max = mt_getrandmax();
391                         if( mt_rand( 0, $max ) > $max * $jobRunRate ) {
392                                 return;
393                         }
394                         $n = 1;
395                 } else {
396                         $n = intval( $jobRunRate );
397                 }
398
399                 while ( $n-- && false != ( $job = Job::pop() ) ) {
400                         $output = $job->toString() . "\n";
401                         $t = -wfTime();
402                         $success = $job->run();
403                         $t += wfTime();
404                         $t = round( $t*1000 );
405                         if( !$success ) {
406                                 $output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
407                         } else {
408                                 $output .= "Success, Time: $t ms\n";
409                         }
410                         wfDebugLog( 'jobqueue', $output );
411                 }
412         }
413
414         /**
415          * Ends this task peacefully
416          */
417         function restInPeace() {
418                 wfLogProfilingData();
419                 // Commit and close up!
420                 $factory = wfGetLBFactory();
421                 $factory->commitMasterChanges();
422                 $factory->shutdown();
423                 wfDebug( "Request ended normally\n" );
424         }
425
426         /**
427          * Perform one of the "standard" actions
428          *
429          * @param $output OutputPage
430          * @param $article Article
431          * @param $title Title
432          * @param $user User
433          * @param $request WebRequest
434          */
435         function performAction( &$output, &$article, &$title, &$user, &$request ) {
436                 wfProfileIn( __METHOD__ );
437
438                 if( !wfRunHooks( 'MediaWikiPerformAction', array( $output, $article, $title, $user, $request, $this ) ) ) {
439                         wfProfileOut( __METHOD__ );
440                         return;
441                 }
442
443                 $action = $this->getVal( 'Action' );
444                 if( in_array( $action, $this->getVal( 'DisabledActions', array() ) ) ) {
445                         /* No such action; this will switch to the default case */
446                         $action = 'nosuchaction';
447                 }
448
449                 // Workaround for bug #20966: inability of IE to provide an action dependent
450                 // on which submit button is clicked.
451                 if ( $action === 'historysubmit' ) {
452                         if ( $request->getBool( 'revisiondelete' ) ) {
453                                 $action = 'revisiondelete';
454                         } else {
455                                 $action = 'view';
456                         }
457                 }
458
459                 switch( $action ) {
460                         case 'view':
461                                 $output->setSquidMaxage( $this->getVal( 'SquidMaxage' ) );
462                                 $article->view();
463                                 break;
464                         case 'raw': // includes JS/CSS
465                                 wfProfileIn( __METHOD__.'-raw' );
466                                 $raw = new RawPage( $article );
467                                 $raw->view();
468                                 wfProfileOut( __METHOD__.'-raw' );
469                                 break;
470                         case 'watch':
471                         case 'unwatch':
472                         case 'delete':
473                         case 'revert':
474                         case 'rollback':
475                         case 'protect':
476                         case 'unprotect':
477                         case 'info':
478                         case 'markpatrolled':
479                         case 'render':
480                         case 'deletetrackback':
481                         case 'purge':
482                                 $article->$action();
483                                 break;
484                         case 'print':
485                                 $article->view();
486                                 break;
487                         case 'dublincore':
488                                 if( !$this->getVal( 'EnableDublinCoreRdf' ) ) {
489                                         wfHttpError( 403, 'Forbidden', wfMsg( 'nodublincore' ) );
490                                 } else {
491                                         $rdf = new DublinCoreRdf( $article );
492                                         $rdf->show();
493                                 }
494                                 break;
495                         case 'creativecommons':
496                                 if( !$this->getVal( 'EnableCreativeCommonsRdf' ) ) {
497                                         wfHttpError( 403, 'Forbidden', wfMsg( 'nocreativecommons' ) );
498                                 } else {
499                                         $rdf = new CreativeCommonsRdf( $article );
500                                         $rdf->show();
501                                 }
502                                 break;
503                         case 'credits':
504                                 Credits::showPage( $article );
505                                 break;
506                         case 'submit':
507                                 if( session_id() == '' ) {
508                                         /* Send a cookie so anons get talk message notifications */
509                                         wfSetupSession();
510                                 }
511                                 /* Continue... */
512                         case 'edit':
513                         case 'editredlink':
514                                 if( wfRunHooks( 'CustomEditor', array( $article, $user ) ) ) {
515                                         $internal = $request->getVal( 'internaledit' );
516                                         $external = $request->getVal( 'externaledit' );
517                                         $section = $request->getVal( 'section' );
518                                         $oldid = $request->getVal( 'oldid' );
519                                         if( !$this->getVal( 'UseExternalEditor' ) || $action=='submit' || $internal ||
520                                            $section || $oldid || ( !$user->getOption( 'externaleditor' ) && !$external ) ) {
521                                                 $editor = new EditPage( $article );
522                                                 $editor->submit();
523                                         } elseif( $this->getVal( 'UseExternalEditor' ) && ( $external || $user->getOption( 'externaleditor' ) ) ) {
524                                                 $mode = $request->getVal( 'mode' );
525                                                 $extedit = new ExternalEdit( $article, $mode );
526                                                 $extedit->edit();
527                                         }
528                                 }
529                                 break;
530                         case 'history':
531                                 if( $request->getFullRequestURL() == $title->getInternalURL( 'action=history' ) ) {
532                                         $output->setSquidMaxage( $this->getVal( 'SquidMaxage' ) );
533                                 }
534                                 $history = new HistoryPage( $article );
535                                 $history->history();
536                                 break;
537                         case 'revisiondelete':
538                                 // For show/hide submission from history page
539                                 $special = SpecialPage::getPage( 'Revisiondelete' );
540                                 $special->execute( '' );
541                                 break;
542                         default:
543                                 if( wfRunHooks( 'UnknownAction', array( $action, $article ) ) ) {
544                                         $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
545                                 }
546                 }
547                 wfProfileOut( __METHOD__ );
548
549         }
550
551 }