Category function now working
[lhc/web/wiklou.git] / includes / OutputPage.php
1 <?php
2 # See design.doc
3
4 if($wgUseTeX) include_once( "Math.php" );
5
6 class OutputPage {
7 var $mHeaders, $mCookies, $mMetatags, $mKeywords;
8 var $mLinktags, $mPagetitle, $mBodytext, $mDebugtext;
9 var $mHTMLtitle, $mRobotpolicy, $mIsarticle, $mPrintable;
10 var $mSubtitle, $mRedirect, $mAutonumber, $mHeadtext;
11 var $mLastModified, $mCategoryLinks;
12
13 var $mDTopen, $mLastSection; # Used for processing DL, PRE
14 var $mLanguageLinks, $mSupressQuickbar;
15 var $mOnloadHandler;
16 var $mDoNothing;
17 var $mContainsOldMagic, $mContainsNewMagic;
18 var $mIsArticleRelated;
19
20 function OutputPage()
21 {
22 $this->mHeaders = $this->mCookies = $this->mMetatags =
23 $this->mKeywords = $this->mLinktags = array();
24 $this->mHTMLtitle = $this->mPagetitle = $this->mBodytext =
25 $this->mLastSection = $this->mRedirect = $this->mLastModified =
26 $this->mSubtitle = $this->mDebugtext = $this->mRobotpolicy =
27 $this->mOnloadHandler = "";
28 $this->mIsArticleRelated = $this->mIsarticle = $this->mPrintable = true;
29 $this->mSupressQuickbar = $this->mDTopen = $this->mPrintable = false;
30 $this->mLanguageLinks = array();
31 $this->mCategoryLinks = array() ;
32 $this->mAutonumber = 0;
33 $this->mDoNothing = false;
34 $this->mContainsOldMagic = $this->mContainsNewMagic = 0;
35 }
36
37 function addHeader( $name, $val ) { array_push( $this->mHeaders, "$name: $val" ) ; }
38 function addCookie( $name, $val ) { array_push( $this->mCookies, array( $name, $val ) ); }
39 function redirect( $url ) { $this->mRedirect = $url; }
40
41 # To add an http-equiv meta tag, precede the name with "http:"
42 function addMeta( $name, $val ) { array_push( $this->mMetatags, array( $name, $val ) ); }
43 function addKeyword( $text ) { array_push( $this->mKeywords, $text ); }
44 function addLink( $rel, $rev, $target ) { array_push( $this->mLinktags, array( $rel, $rev, $target ) ); }
45
46 # checkLastModified tells the client to use the client-cached page if
47 # possible. If sucessful, the OutputPage is disabled so that
48 # any future call to OutputPage->output() have no effect. The method
49 # returns true iff cache-ok headers was sent.
50 function checkLastModified ( $timestamp )
51 {
52 global $wgLang, $wgCachePages, $wgUser;
53 if( !$wgCachePages ) {
54 wfDebug( "CACHE DISABLED\n", false );
55 return;
56 }
57 if( preg_match( '/MSIE ([1-4]|5\.0)/', $_SERVER["HTTP_USER_AGENT"] ) ) {
58 # IE 5.0 has probs with our caching
59 wfDebug( "-- bad client, not caching\n", false );
60 return;
61 }
62 if( $wgUser->getOption( "nocache" ) ) {
63 wfDebug( "USER DISABLED CACHE\n", false );
64 return;
65 }
66
67 $lastmod = gmdate( "D, j M Y H:i:s", wfTimestamp2Unix(
68 max( $timestamp, $wgUser->mTouched ) ) ) . " GMT";
69
70 if( !empty( $_SERVER["HTTP_IF_MODIFIED_SINCE"] ) ) {
71 # IE sends sizes after the date like this:
72 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
73 # this breaks strtotime().
74 $modsince = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
75 $ismodsince = wfUnix2Timestamp( strtotime( $modsince ) );
76 wfDebug( "-- client send If-Modified-Since: " . $modsince . "\n", false );
77 wfDebug( "-- we might send Last-Modified : $lastmod\n", false );
78
79 if( ($ismodsince >= $timestamp ) and $wgUser->validateCache( $ismodsince ) ) {
80 # Make sure you're in a place you can leave when you call us!
81 header( "HTTP/1.0 304 Not Modified" );
82 $this->mLastModified = $lastmod;
83 $this->sendCacheControl();
84 wfDebug( "CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
85 $this->disable();
86 return true;
87 } else {
88 wfDebug( "READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
89 $this->mLastModified = $lastmod;
90 }
91 } else {
92 wfDebug( "We're confused.\n", false );
93 $this->mLastModified = $lastmod;
94 }
95 }
96
97 function setRobotpolicy( $str ) { $this->mRobotpolicy = $str; }
98 function setHTMLtitle( $name ) { $this->mHTMLtitle = $name; }
99 function setPageTitle( $name ) { $this->mPagetitle = $name; }
100 function getPageTitle() { return $this->mPagetitle; }
101 function setSubtitle( $str ) { $this->mSubtitle = $str; }
102 function getSubtitle() { return $this->mSubtitle; }
103 function isArticle() { return $this->mIsarticle; }
104 function setPrintable() { $this->mPrintable = true; }
105 function isPrintable() { return $this->mPrintable; }
106 function setOnloadHandler( $js ) { $this->mOnloadHandler = $js; }
107 function getOnloadHandler() { return $this->mOnloadHandler; }
108 function disable() { $this->mDoNothing = true; }
109
110 function setArticleRelated( $v )
111 {
112 $this->mIsArticleRelated = $v;
113 if ( !$v ) {
114 $this->mIsarticle = false;
115 }
116 }
117 function setArticleFlag( $v ) {
118 $this->mIsarticle = $v;
119 if ( $v ) {
120 $this->mIsArticleRelated = $v;
121 }
122 }
123
124 function isArticleRelated()
125 {
126 return $this->mIsArticleRelated;
127 }
128
129 function getLanguageLinks() {
130 global $wgTitle, $wgLanguageCode;
131 global $wgDBconnection, $wgDBname;
132 return $this->mLanguageLinks;
133 }
134 function supressQuickbar() { $this->mSupressQuickbar = true; }
135 function isQuickbarSupressed() { return $this->mSupressQuickbar; }
136
137 function addHTML( $text ) { $this->mBodytext .= $text; }
138 function addHeadtext( $text ) { $this->mHeadtext .= $text; }
139 function debug( $text ) { $this->mDebugtext .= $text; }
140
141 # First pass--just handle <nowiki> sections, pass the rest off
142 # to doWikiPass2() which does all the real work.
143 #
144 function addWikiText( $text, $linestart = true )
145 {
146 global $wgUseTeX, $wgArticle, $wgUser, $action;
147 $fname = "OutputPage::addWikiText";
148 wfProfileIn( $fname );
149 $unique = "3iyZiyA7iMwg5rhxP0Dcc9oTnj8qD1jm1Sfv4";
150 $unique2 = "4LIQ9nXtiYFPCSfitVwDw7EYwQlL4GeeQ7qSO";
151 $unique3 = "fPaA8gDfdLBqzj68Yjg9Hil3qEF8JGO0uszIp";
152 $nwlist = array();
153 $nwsecs = 0;
154 $mathlist = array();
155 $mathsecs = 0;
156 $prelist = array ();
157 $presecs = 0;
158 $stripped = "";
159 $stripped2 = "";
160 $stripped3 = "";
161
162 # Replace any instances of the placeholders
163 $text = str_replace( $unique, wfHtmlEscapeFirst( $unique ), $text );
164 $text = str_replace( $unique2, wfHtmlEscapeFirst( $unique2 ), $text );
165 $text = str_replace( $unique3, wfHtmlEscapeFirst( $unique3 ), $text );
166
167 global $wgEnableParserCache;
168 $use_parser_cache =
169 $wgEnableParserCache && $action == "view" &&
170 intval($wgUser->getOption( "stubthreshold" )) == 0 &&
171 isset($wgArticle) && $wgArticle->getID() > 0;
172
173 if( $use_parser_cache ){
174 if( $this->fillFromParserCache() ){
175 wfProfileOut( $fname );
176 return;
177 }
178 }
179
180 while ( "" != $text ) {
181 $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 );
182 $stripped .= $p[0];
183 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $text = ""; }
184 else {
185 $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
186 ++$nwsecs;
187 $nwlist[$nwsecs] = wfEscapeHTMLTagsOnly($q[0]);
188 $stripped .= $unique . $nwsecs . "s";
189 $text = $q[1];
190 }
191 }
192
193 if( $wgUseTeX ) {
194 while ( "" != $stripped ) {
195 $p = preg_split( "/<\\s*math\\s*>/i", $stripped, 2 );
196 $stripped2 .= $p[0];
197 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped = ""; }
198 else {
199 $q = preg_split( "/<\\/\\s*math\\s*>/i", $p[1], 2 );
200 ++$mathsecs;
201 $mathlist[$mathsecs] = renderMath($q[0]);
202 $stripped2 .= $unique2 . $mathsecs . "s";
203 $stripped = $q[1];
204 }
205 }
206 } else {
207 $stripped2 = $stripped;
208 }
209
210 while ( "" != $stripped2 ) {
211 $p = preg_split( "/<\\s*pre\\s*>/i", $stripped2, 2 );
212 $stripped3 .= $p[0];
213 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped2 = ""; }
214 else {
215 $q = preg_split( "/<\\/\\s*pre\\s*>/i", $p[1], 2 );
216 ++$presecs;
217 $prelist[$presecs] = "<pre>". wfEscapeHTMLTagsOnly($q[0]). "</pre>\n";
218 $stripped3 .= $unique3 . $presecs . "s";
219 $stripped2 = $q[1];
220 }
221 }
222
223 $text = $this->doWikiPass2( $stripped3, $linestart );
224
225 $specialChars = array("\\", "$");
226 $escapedChars = array("\\\\", "\\$");
227 for ( $i = 1; $i <= $presecs; ++$i ) {
228 $text = preg_replace( "/{$unique3}{$i}s/", str_replace( $specialChars,
229 $escapedChars, $prelist[$i] ), $text );
230 }
231
232 for ( $i = 1; $i <= $mathsecs; ++$i ) {
233 $text = preg_replace( "/{$unique2}{$i}s/", str_replace( $specialChars,
234 $escapedChars, $mathlist[$i] ), $text );
235 }
236
237 for ( $i = 1; $i <= $nwsecs; ++$i ) {
238 $text = preg_replace( "/{$unique}{$i}s/", str_replace( $specialChars,
239 $escapedChars, $nwlist[$i] ), $text );
240 }
241 $this->addHTML( $text );
242
243 if($use_parser_cache ){
244 $this->saveParserCache( $text );
245 }
246 wfProfileOut( $fname );
247 }
248
249 # Set the maximum cache time on the Squid in seconds
250 function setSquidMaxage( $maxage ) {
251 global $wgSquidMaxage;
252 $wgSquidMaxage = $maxage;
253 }
254
255 function sendCacheControl() {
256 global $wgUseSquid, $wgUseESI, $wgSquidMaxage;
257 # FIXME: This header may cause trouble with some versions of Internet Explorer
258 header( "Vary: Accept-Encoding, Cookie" );
259 if( $this->mLastModified != "" ) {
260 if( $wgUseSquid && ! isset( $_COOKIE[ini_get( "session.name") ] ) &&
261 ! $this->isPrintable() )
262 {
263 if ( $wgUseESI ) {
264 # We'll purge the proxy cache explicitly, but require end user agents
265 # to revalidate against the proxy on each visit.
266 # Surrogate-Control controls our Squid, Cache-Control downstream caches
267 wfDebug( "** proxy caching with ESI; {$this->mLastModified} **\n", false );
268 # start with a shorter timeout for initial testing
269 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
270 header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$wgSquidMaxage.', content="ESI/1.0"');
271 header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
272 } else {
273 # We'll purge the proxy cache for anons explicitly, but require end user agents
274 # to revalidate against the proxy on each visit.
275 # IMPORTANT! The Squid needs to replace the Cache-Control header with
276 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
277 wfDebug( "** local proxy caching; {$this->mLastModified} **\n", false );
278 # start with a shorter timeout for initial testing
279 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
280 header( 'Cache-Control: s-maxage='.$wgSquidMaxage.', must-revalidate, max-age=0' );
281 }
282 } else {
283 # We do want clients to cache if they can, but they *must* check for updates
284 # on revisiting the page.
285 wfDebug( "** private caching; {$this->mLastModified} **\n", false );
286 header( "Expires: -1" );
287 header( "Cache-Control: private, must-revalidate, max-age=0" );
288 }
289 header( "Last-modified: {$this->mLastModified}" );
290 } else {
291 wfDebug( "** no caching **\n", false );
292 header( "Expires: -1" );
293 header( "Cache-Control: no-cache" );
294 header( "Pragma: no-cache" );
295 header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" );
296 }
297 }
298
299 # Finally, all the text has been munged and accumulated into
300 # the object, let's actually output it:
301 #
302 function output()
303 {
304 global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
305 global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
306 if( $this->mDoNothing ){
307 return;
308 }
309 $fname = "OutputPage::output";
310 wfProfileIn( $fname );
311
312 $sk = $wgUser->getSkin();
313
314 $this->sendCacheControl();
315
316 header( "Content-type: text/html; charset={$wgOutputEncoding}" );
317 header( "Content-language: {$wgLanguageCode}" );
318
319 if ( "" != $this->mRedirect ) {
320 if( substr( $this->mRedirect, 0, 4 ) != "http" ) {
321 # Standards require redirect URLs to be absolute
322 global $wgServer;
323 $this->mRedirect = $wgServer . $this->mRedirect;
324 }
325 header( "Location: {$this->mRedirect}" );
326 return;
327 }
328
329 $exp = time() + $wgCookieExpiration;
330 foreach( $this->mCookies as $name => $val ) {
331 setcookie( $name, $val, $exp, "/" );
332 }
333
334 $sk->outputPage( $this );
335 # flush();
336 }
337
338 function out( $ins )
339 {
340 global $wgInputEncoding, $wgOutputEncoding, $wgLang;
341 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
342 $outs = $ins;
343 } else {
344 $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
345 if ( false === $outs ) { $outs = $ins; }
346 }
347 print $outs;
348 }
349
350 function setEncodings()
351 {
352 global $wgInputEncoding, $wgOutputEncoding;
353 global $wgUser, $wgLang;
354
355 $wgInputEncoding = strtolower( $wgInputEncoding );
356
357 if( $wgUser->getOption( 'altencoding' ) ) {
358 $wgLang->setAltEncoding();
359 return;
360 }
361
362 if ( empty( $_SERVER['HTTP_ACCEPT_CHARSET'] ) ) {
363 $wgOutputEncoding = strtolower( $wgOutputEncoding );
364 return;
365 }
366
367 /*
368 # This code is unused anyway!
369 # Commenting out. --bv 2003-11-15
370
371 $a = explode( ",", $_SERVER['HTTP_ACCEPT_CHARSET'] );
372 $best = 0.0;
373 $bestset = "*";
374
375 foreach ( $a as $s ) {
376 if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) {
377 $set = $m[1];
378 $q = (float)($m[2]);
379 } else {
380 $set = $s;
381 $q = 1.0;
382 }
383 if ( $q > $best ) {
384 $bestset = $set;
385 $best = $q;
386 }
387 }
388 #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; }
389 if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; }
390 $wgOutputEncoding = strtolower( $bestset );
391
392 # Disable for now
393 #
394 */
395 $wgOutputEncoding = $wgInputEncoding;
396 }
397
398 # Returns a HTML comment with the elapsed time since request.
399 # This method has no side effects.
400 function reportTime()
401 {
402 global $wgRequestTime;
403
404 $now = wfTime();
405 list( $usec, $sec ) = explode( " ", $wgRequestTime );
406 $start = (float)$sec + (float)$usec;
407 $elapsed = $now - $start;
408 $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
409 $elapsed );
410 return $com;
411 }
412
413 # Note: these arguments are keys into wfMsg(), not text!
414 #
415 function errorpage( $title, $msg )
416 {
417 global $wgTitle;
418
419 $this->mDebugtext .= "Original title: " .
420 $wgTitle->getPrefixedText() . "\n";
421 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
422 $this->setPageTitle( wfMsg( $title ) );
423 $this->setRobotpolicy( "noindex,nofollow" );
424 $this->setArticleRelated( false );
425
426 $this->mBodytext = "";
427 $this->addHTML( "<p>" . wfMsg( $msg ) . "\n" );
428 $this->returnToMain( false );
429
430 $this->output();
431 wfAbruptExit();
432 }
433
434 function sysopRequired()
435 {
436 global $wgUser;
437
438 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
439 $this->setPageTitle( wfMsg( "sysoptitle" ) );
440 $this->setRobotpolicy( "noindex,nofollow" );
441 $this->setArticleRelated( false );
442 $this->mBodytext = "";
443
444 $sk = $wgUser->getSkin();
445 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
446 $this->addHTML( wfMsg( "sysoptext", $ap ) );
447 $this->returnToMain();
448 }
449
450 function developerRequired()
451 {
452 global $wgUser;
453
454 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
455 $this->setPageTitle( wfMsg( "developertitle" ) );
456 $this->setRobotpolicy( "noindex,nofollow" );
457 $this->setArticleRelated( false );
458 $this->mBodytext = "";
459
460 $sk = $wgUser->getSkin();
461 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
462 $this->addHTML( wfMsg( "developertext", $ap ) );
463 $this->returnToMain();
464 }
465
466 function databaseError( $fname, &$conn )
467 {
468 global $wgUser, $wgCommandLineMode;
469
470 $this->setPageTitle( wfMsgNoDB( "databaseerror" ) );
471 $this->setRobotpolicy( "noindex,nofollow" );
472 $this->setArticleRelated( false );
473
474 if ( $wgCommandLineMode ) {
475 $msg = wfMsgNoDB( "dberrortextcl" );
476 } else {
477 $msg = wfMsgNoDB( "dberrortext" );
478 }
479
480 $msg = str_replace( "$1", htmlspecialchars( $conn->lastQuery() ), $msg );
481 $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
482 $msg = str_replace( "$3", $conn->lastErrno(), $msg );
483 $msg = str_replace( "$4", htmlspecialchars( $conn->lastError() ), $msg );
484
485 if ( $wgCommandLineMode || !is_object( $wgUser )) {
486 print "$msg\n";
487 wfAbruptExit();
488 }
489 $sk = $wgUser->getSkin();
490 $shlink = $sk->makeKnownLink( wfMsgNoDB( "searchhelppage" ),
491 wfMsgNoDB( "searchingwikipedia" ) );
492 $msg = str_replace( "$5", $shlink, $msg );
493 $this->mBodytext = $msg;
494 $this->output();
495 wfAbruptExit();
496 }
497
498 function readOnlyPage( $source = "", $protected = false )
499 {
500 global $wgUser, $wgReadOnlyFile;
501
502 $this->setRobotpolicy( "noindex,nofollow" );
503 $this->setArticleRelated( false );
504
505 if( $protected ) {
506 $this->setPageTitle( wfMsg( "viewsource" ) );
507 $this->addWikiText( wfMsg( "protectedtext" ) );
508 } else {
509 $this->setPageTitle( wfMsg( "readonly" ) );
510 $reason = file_get_contents( $wgReadOnlyFile );
511 $this->addHTML( wfMsg( "readonlytext", $reason ) );
512 }
513
514 if($source) {
515 $rows = $wgUser->getOption( "rows" );
516 $cols = $wgUser->getOption( "cols" );
517 $text .= "</p>\n<textarea cols='$cols' rows='$rows' readonly>" .
518 htmlspecialchars( $source ) . "\n</textarea>";
519 $this->addHTML( $text );
520 }
521
522 $this->returnToMain( false );
523 }
524
525 function fatalError( $message )
526 {
527 $this->setPageTitle( wfMsg( "internalerror" ) );
528 $this->setRobotpolicy( "noindex,nofollow" );
529 $this->setArticleRelated( false );
530
531 $this->mBodytext = $message;
532 $this->output();
533 wfAbruptExit();
534 }
535
536 function unexpectedValueError( $name, $val )
537 {
538 $this->fatalError( wfMsg( "unexpected", $name, $val ) );
539 }
540
541 function fileCopyError( $old, $new )
542 {
543 $this->fatalError( wfMsg( "filecopyerror", $old, $new ) );
544 }
545
546 function fileRenameError( $old, $new )
547 {
548 $this->fatalError( wfMsg( "filerenameerror", $old, $new ) );
549 }
550
551 function fileDeleteError( $name )
552 {
553 $this->fatalError( wfMsg( "filedeleteerror", $name ) );
554 }
555
556 function fileNotFoundError( $name )
557 {
558 $this->fatalError( wfMsg( "filenotfound", $name ) );
559 }
560
561 function returnToMain( $auto = true )
562 {
563 global $wgUser, $wgOut, $returnto;
564
565 $sk = $wgUser->getSkin();
566 if ( "" == $returnto ) {
567 $returnto = wfMsg( "mainpage" );
568 }
569 $link = $sk->makeKnownLink( $returnto, "" );
570
571 $r = wfMsg( "returnto", $link );
572 if ( $auto ) {
573 $wgOut->addMeta( "http:Refresh", "10;url=" .
574 wfLocalUrlE( wfUrlencode( $returnto ) ) );
575 }
576 $wgOut->addHTML( "\n<p>$r\n" );
577 }
578
579
580 function categoryMagic ()
581 {
582 global $wgTitle , $wgUseCategoryMagic , $wgLang ;
583 if ( !isset ( $wgUseCategoryMagic ) || !$wgUseCategoryMagic ) return ;
584 $id = $wgTitle->getArticleID() ;
585 $cat = ucfirst ( wfMsg ( "category" ) ) ;
586 $ti = $wgTitle->getText() ;
587 $ti = explode ( ":" , $ti , 2 ) ;
588 if ( $cat != $ti[0] ) return "" ;
589 $r = "<br break=all>\n" ;
590
591 $articles = array() ;
592 $parents = array () ;
593 $children = array() ;
594
595
596 global $wgUser ;
597 $sk = $wgUser->getSkin() ;
598
599 $doesexist = false ;
600 if ( $doesexist ) $sql = "SELECT l_from FROM links WHERE l_to={$id}" ;
601 else $sql = "SELECT cur_title,cur_namespace FROM cur,brokenlinks WHERE bl_to={$id} AND bl_from=cur_id" ;
602 $res = wfQuery ( $sql, DB_READ ) ;
603 while ( $x = wfFetchObject ( $res ) )
604 {
605 # $t = new Title ;
606 # $t->newFromDBkey ( $x->l_from ) ;
607 # $t = $t->getText() ;
608 if ( $doesexist ) $t = $x->l_from ;
609 else {
610 $t = $wgLang->getNsText ( $x->cur_namespace ) ;
611 if ( $t != "" ) $t .= ":" ;
612 $t .= $x->cur_title ;
613 }
614 $y = explode ( ":" , $t , 2 ) ;
615 if ( count ( $y ) == 2 && $y[0] == $cat ) {
616 array_push ( $children , $sk->makeLink ( $t , $y[1] ) ) ;
617 } else {
618 array_push ( $articles , $sk->makeLink ( $t ) ) ;
619 }
620 }
621 wfFreeResult ( $res ) ;
622
623 # Children
624 if ( count ( $children ) > 0 )
625 {
626 asort ( $children ) ;
627 $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
628 $r .= implode ( ", " , $children ) ;
629 }
630
631 # Articles
632 if ( count ( $articles ) > 0 )
633 {
634 asort ( $articles ) ;
635 $h = wfMsg( "category_header", $ti[1] );
636 $r .= "<h2>{$h}</h2>\n" ;
637 $r .= implode ( ", " , $articles ) ;
638 }
639
640 return $r ;
641 }
642
643 function getHTMLattrs ()
644 {
645 $htmlattrs = array( # Allowed attributes--no scripting, etc.
646 "title", "align", "lang", "dir", "width", "height",
647 "bgcolor", "clear", /* BR */ "noshade", /* HR */
648 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
649 /* FONT */ "type", "start", "value", "compact",
650 /* For various lists, mostly deprecated but safe */
651 "summary", "width", "border", "frame", "rules",
652 "cellspacing", "cellpadding", "valign", "char",
653 "charoff", "colgroup", "col", "span", "abbr", "axis",
654 "headers", "scope", "rowspan", "colspan", /* Tables */
655 "id", "class", "name", "style" /* For CSS */
656 );
657 return $htmlattrs ;
658 }
659
660 function fixTagAttributes ( $t )
661 {
662 if ( trim ( $t ) == "" ) return "" ; # Saves runtime ;-)
663 $htmlattrs = $this->getHTMLattrs() ;
664
665 # Strip non-approved attributes from the tag
666 $t = preg_replace(
667 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
668 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
669 $t);
670 # Strip javascript "expression" from stylesheets. Brute force approach:
671 # If anythin offensive is found, all attributes of the HTML tag are dropped
672
673 if( preg_match(
674 "/style\\s*=.*(expression|tps*:\/\/|url\\s*\().*/is",
675 wfMungeToUtf8( $t ) ) )
676 {
677 $t="";
678 }
679
680 return trim ( $t ) ;
681 }
682
683 function doTableStuff ( $t )
684 {
685 $t = explode ( "\n" , $t ) ;
686 $td = array () ; # Is currently a td tag open?
687 $ltd = array () ; # Was it TD or TH?
688 $tr = array () ; # Is currently a tr tag open?
689 $ltr = array () ; # tr attributes
690 foreach ( $t AS $k => $x )
691 {
692 $x = rtrim ( $x ) ;
693 $fc = substr ( $x , 0 , 1 ) ;
694 if ( "{|" == substr ( $x , 0 , 2 ) )
695 {
696 $t[$k] = "<table " . $this->fixTagAttributes ( substr ( $x , 3 ) ) . ">" ;
697 array_push ( $td , false ) ;
698 array_push ( $ltd , "" ) ;
699 array_push ( $tr , false ) ;
700 array_push ( $ltr , "" ) ;
701 }
702 else if ( count ( $td ) == 0 ) { } # Don't do any of the following
703 else if ( "|}" == substr ( $x , 0 , 2 ) )
704 {
705 $z = "</table>\n" ;
706 $l = array_pop ( $ltd ) ;
707 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
708 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
709 array_pop ( $ltr ) ;
710 $t[$k] = $z ;
711 }
712 /* else if ( "|_" == substr ( $x , 0 , 2 ) ) # Caption
713 {
714 $z = trim ( substr ( $x , 2 ) ) ;
715 $t[$k] = "<caption>{$z}</caption>\n" ;
716 }*/
717 else if ( "|-" == substr ( $x , 0 , 2 ) ) # Allows for |---------------
718 {
719 $x = substr ( $x , 1 ) ;
720 while ( $x != "" && substr ( $x , 0 , 1 ) == '-' ) $x = substr ( $x , 1 ) ;
721 $z = "" ;
722 $l = array_pop ( $ltd ) ;
723 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
724 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
725 array_pop ( $ltr ) ;
726 $t[$k] = $z ;
727 array_push ( $tr , false ) ;
728 array_push ( $td , false ) ;
729 array_push ( $ltd , "" ) ;
730 array_push ( $ltr , $this->fixTagAttributes ( $x ) ) ;
731 }
732 else if ( "|" == $fc || "!" == $fc || "|+" == substr ( $x , 0 , 2 ) ) # Caption
733 {
734 if ( "|+" == substr ( $x , 0 , 2 ) )
735 {
736 $fc = "+" ;
737 $x = substr ( $x , 1 ) ;
738 }
739 $after = substr ( $x , 1 ) ;
740 if ( $fc == "!" ) $after = str_replace ( "!!" , "||" , $after ) ;
741 $after = explode ( "||" , $after ) ;
742 $t[$k] = "" ;
743 foreach ( $after AS $theline )
744 {
745 $z = "" ;
746 if ( $fc != "+" )
747 {
748 $tra = array_pop ( $ltr ) ;
749 if ( !array_pop ( $tr ) ) $z = "<tr {$tra}>\n" ;
750 array_push ( $tr , true ) ;
751 array_push ( $ltr , "" ) ;
752 }
753
754 $l = array_pop ( $ltd ) ;
755 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
756 if ( $fc == "|" ) $l = "TD" ;
757 else if ( $fc == "!" ) $l = "TH" ;
758 else if ( $fc == "+" ) $l = "CAPTION" ;
759 else $l = "" ;
760 array_push ( $ltd , $l ) ;
761 $y = explode ( "|" , $theline , 2 ) ;
762 if ( count ( $y ) == 1 ) $y = "{$z}<{$l}>{$y[0]}" ;
763 else $y = $y = "{$z}<{$l} ".$this->fixTagAttributes($y[0]).">{$y[1]}" ;
764 $t[$k] .= $y ;
765 array_push ( $td , true ) ;
766 }
767 }
768 }
769
770 # Closing open td, tr && table
771 while ( count ( $td ) > 0 )
772 {
773 if ( array_pop ( $td ) ) $t[] = "</td>" ;
774 if ( array_pop ( $tr ) ) $t[] = "</tr>" ;
775 $t[] = "</table>" ;
776 }
777
778 $t = implode ( "\n" , $t ) ;
779 # $t = $this->removeHTMLtags( $t );
780 return $t ;
781 }
782
783 # Well, OK, it's actually about 14 passes. But since all the
784 # hard lifting is done inside PHP's regex code, it probably
785 # wouldn't speed things up much to add a real parser.
786 #
787 function doWikiPass2( $text, $linestart )
788 {
789 global $wgUser, $wgLang, $wgUseDynamicDates;
790 $fname = "OutputPage::doWikiPass2";
791 wfProfileIn( $fname );
792
793 $text = $this->removeHTMLtags( $text );
794 $text = $this->replaceVariables( $text );
795
796 $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
797 $text = str_replace ( "<HR>", "<hr>", $text );
798
799 $text = $this->doAllQuotes( $text );
800 $text = $this->doHeadings( $text );
801 $text = $this->doBlockLevels( $text, $linestart );
802
803 if($wgUseDynamicDates) {
804 global $wgDateFormatter;
805 $text = $wgDateFormatter->reformat( $wgUser->getOption("date"), $text );
806 }
807
808 $text = $this->replaceExternalLinks( $text );
809 $text = $this->replaceInternalLinks ( $text );
810 $text = $this->doTableStuff ( $text ) ;
811
812 $text = $this->magicISBN( $text );
813 $text = $this->magicRFC( $text );
814 $text = $this->formatHeadings( $text );
815
816 $sk = $wgUser->getSkin();
817 $text = $sk->transformContent( $text );
818 $text .= $this->categoryMagic () ;
819
820 wfProfileOut( $fname );
821 return $text;
822 }
823
824 /* private */ function doAllQuotes( $text )
825 {
826 $outtext = "";
827 $lines = explode( "\r\n", $text );
828 foreach ( $lines as $line ) {
829 $outtext .= $this->doQuotes ( "", $line, "" ) . "\r\n";
830 }
831 return $outtext;
832 }
833
834 /* private */ function doQuotes( $pre, $text, $mode )
835 {
836 if ( preg_match( "/^(.*)''(.*)$/sU", $text, $m ) ) {
837 $m1_strong = ($m[1] == "") ? "" : "<strong>{$m[1]}</strong>";
838 $m1_em = ($m[1] == "") ? "" : "<em>{$m[1]}</em>";
839 if ( substr ($m[2], 0, 1) == "'" ) {
840 $m[2] = substr ($m[2], 1);
841 if ($mode == "em") {
842 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ? "both" : "emstrong" );
843 } else if ($mode == "strong") {
844 return $m1_strong . $this->doQuotes ( "", $m[2], "" );
845 } else if (($mode == "emstrong") || ($mode == "both")) {
846 return $this->doQuotes ( "", $pre.$m1_strong.$m[2], "em" );
847 } else if ($mode == "strongem") {
848 return "<strong>{$pre}{$m1_em}</strong>" . $this->doQuotes ( "", $m[2], "em" );
849 } else {
850 return $m[1] . $this->doQuotes ( "", $m[2], "strong" );
851 }
852 } else {
853 if ($mode == "strong") {
854 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ? "both" : "strongem" );
855 } else if ($mode == "em") {
856 return $m1_em . $this->doQuotes ( "", $m[2], "" );
857 } else if ($mode == "emstrong") {
858 return "<em>{$pre}{$m1_strong}</em>" . $this->doQuotes ( "", $m[2], "strong" );
859 } else if (($mode == "strongem") || ($mode == "both")) {
860 return $this->doQuotes ( "", $pre.$m1_em.$m[2], "strong" );
861 } else {
862 return $m[1] . $this->doQuotes ( "", $m[2], "em" );
863 }
864 }
865 } else {
866 $text_strong = ($text == "") ? "" : "<strong>{$text}</strong>";
867 $text_em = ($text == "") ? "" : "<em>{$text}</em>";
868 if ($mode == "") {
869 return $pre . $text;
870 } else if ($mode == "em") {
871 return $pre . $text_em;
872 } else if ($mode == "strong") {
873 return $pre . $text_strong;
874 } else if ($mode == "strongem") {
875 return (($pre == "") && ($text == "")) ? "" : "<strong>{$pre}{$text_em}</strong>";
876 } else {
877 return (($pre == "") && ($text == "")) ? "" : "<em>{$pre}{$text_strong}</em>";
878 }
879 }
880 }
881
882 /* private */ function doHeadings( $text )
883 {
884 for ( $i = 6; $i >= 1; --$i ) {
885 $h = substr( "======", 0, $i );
886 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
887 "<h{$i}>\\1</h{$i}>\\2", $text );
888 }
889 return $text;
890 }
891
892 # Note: we have to do external links before the internal ones,
893 # and otherwise take great care in the order of things here, so
894 # that we don't end up interpreting some URLs twice.
895
896 /* private */ function replaceExternalLinks( $text )
897 {
898 $fname = "OutputPage::replaceExternalLinks";
899 wfProfileIn( $fname );
900 $text = $this->subReplaceExternalLinks( $text, "http", true );
901 $text = $this->subReplaceExternalLinks( $text, "https", true );
902 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
903 $text = $this->subReplaceExternalLinks( $text, "irc", false );
904 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
905 $text = $this->subReplaceExternalLinks( $text, "news", false );
906 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
907 wfProfileOut( $fname );
908 return $text;
909 }
910
911 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
912 {
913 global $wgUser, $printable;
914 global $wgAllowExternalImages;
915
916
917 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
918 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
919
920 # this is the list of separators that should be ignored if they
921 # are the last character of an URL but that should be included
922 # if they occur within the URL, e.g. "go to www.foo.com, where .."
923 # in this case, the last comma should not become part of the URL,
924 # but in "www.foo.com/123,2342,32.htm" it should.
925 $sep = ",;\.:";
926 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
927 $images = "gif|png|jpg|jpeg";
928
929 # PLEASE NOTE: The curly braces { } are not part of the regex,
930 # they are interpreted as part of the string (used to tell PHP
931 # that the content of the string should be inserted there).
932 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
933 "((?i){$images})([^{$uc}]|$)/";
934
935 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
936 $sk = $wgUser->getSkin();
937
938 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
939 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
940 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
941 }
942 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
943 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
944 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
945 "</a>\\5", $s );
946 $s = str_replace( $unique, $protocol, $s );
947
948 $a = explode( "[{$protocol}:", " " . $s );
949 $s = array_shift( $a );
950 $s = substr( $s, 1 );
951
952 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
953 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
954
955 foreach ( $a as $line ) {
956 if ( preg_match( $e1, $line, $m ) ) {
957 $link = "{$protocol}:{$m[1]}";
958 $trail = $m[2];
959 if ( $autonumber ) { $text = "[" . ++$this->mAutonumber . "]"; }
960 else { $text = wfEscapeHTML( $link ); }
961 } else if ( preg_match( $e2, $line, $m ) ) {
962 $link = "{$protocol}:{$m[1]}";
963 $text = $m[2];
964 $trail = $m[3];
965 } else {
966 $s .= "[{$protocol}:" . $line;
967 continue;
968 }
969 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
970 else $paren = "";
971 $la = $sk->getExternalLinkAttributes( $link, $text );
972 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
973
974 }
975 return $s;
976 }
977
978 /* private */ function replaceInternalLinks( $s )
979 {
980 global $wgTitle, $wgUser, $wgLang;
981 global $wgLinkCache, $wgInterwikiMagic, $wgUseCategoryMagic;
982 global $wgNamespacesWithSubpages, $wgLanguageCode;
983 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
984
985 wfProfileIn( "$fname-setup" );
986 $tc = Title::legalChars() . "#";
987 $sk = $wgUser->getSkin();
988
989 $a = explode( "[[", " " . $s );
990 $s = array_shift( $a );
991 $s = substr( $s, 1 );
992
993 # Match a link having the form [[namespace:link|alternate]]trail
994 $e1 = "/^([{$tc}]+)(?:\\|([^]]+))?]](.*)\$/sD";
995 # Match the end of a line for a word that's not followed by whitespace,
996 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
997 #$e2 = "/^(.*)\\b(\\w+)\$/suD";
998 #$e2 = "/^(.*\\s)(\\S+)\$/suD";
999 $e2 = '/^(.*\s)([a-zA-Z\x80-\xff]+)$/sD';
1000
1001
1002 # Special and Media are pseudo-namespaces; no pages actually exist in them
1003 $image = Namespace::getImage();
1004 $special = Namespace::getSpecial();
1005 $media = Namespace::getMedia();
1006 $category = wfMsg ( "category" ) ;
1007 $nottalk = !Namespace::isTalk( $wgTitle->getNamespace() );
1008
1009 if ( $wgLang->linkPrefixExtension() && preg_match( $e2, $s, $m ) ) {
1010 $new_prefix = $m[2];
1011 $s = $m[1];
1012 } else {
1013 $new_prefix="";
1014 }
1015
1016 wfProfileOut( "$fname-setup" );
1017
1018 foreach ( $a as $line ) {
1019 $prefix = $new_prefix;
1020 if ( $wgLang->linkPrefixExtension() && preg_match( $e2, $line, $m ) ) {
1021 $new_prefix = $m[2];
1022 $line = $m[1];
1023 } else {
1024 $new_prefix = "";
1025 }
1026 if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
1027 $text = $m[2];
1028 $trail = $m[3];
1029 } else { # Invalid form; output directly
1030 $s .= $prefix . "[[" . $line ;
1031 continue;
1032 }
1033
1034 /* Valid link forms:
1035 Foobar -- normal
1036 :Foobar -- override special treatment of prefix (images, language links)
1037 /Foobar -- convert to CurrentPage/Foobar
1038 /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
1039 */
1040 $c = substr($m[1],0,1);
1041 $noforce = ($c != ":");
1042 if( $c == "/" ) { # subpage
1043 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
1044 $m[1]=substr($m[1],1,strlen($m[1])-2);
1045 $noslash=$m[1];
1046 } else {
1047 $noslash=substr($m[1],1);
1048 }
1049 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
1050 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
1051 if( "" == $text ) {
1052 $text= $m[1];
1053 } # this might be changed for ugliness reasons
1054 } else {
1055 $link = $noslash; # no subpage allowed, use standard link
1056 }
1057 } elseif( $noforce ) { # no subpage
1058 $link = $m[1];
1059 } else {
1060 $link = substr( $m[1], 1 );
1061 }
1062 if( "" == $text )
1063 $text = $link;
1064
1065 $nt = Title::newFromText( $link );
1066 if( !$nt ) {
1067 $s .= $prefix . "[[" . $line;
1068 continue;
1069 }
1070 $ns = $nt->getNamespace();
1071 $iw = $nt->getInterWiki();
1072 if( $noforce ) {
1073 if( $iw && $wgInterwikiMagic && $nottalk && $wgLang->getLanguageName( $iw ) ) {
1074 array_push( $this->mLanguageLinks, $nt->getPrefixedText() );
1075 $s .= $prefix . $trail;
1076 continue;
1077 }
1078 if( $ns == $image ) {
1079 $s .= $prefix . $sk->makeImageLinkObj( $nt, $text ) . $trail;
1080 $wgLinkCache->addImageLinkObj( $nt );
1081 continue;
1082 }
1083 }
1084 if( ( $nt->getPrefixedText() == $wgTitle->getPrefixedText() ) &&
1085 ( strpos( $link, "#" ) == FALSE ) ) {
1086 $s .= $prefix . "<strong>" . $text . "</strong>" . $trail;
1087 continue;
1088 }
1089 if ( $ns == $category && $wgUseCategoryMagic ) {
1090 $t = explode ( ":" , $nt->getText() ) ;
1091 array_shift ( $t ) ;
1092 $t = implode ( ":" , $t ) ;
1093 $t = $wgLang->ucFirst ( $t ) ;
1094 # $t = $sk->makeKnownLink( $category.":".$t, $t, "", $trail , $prefix );
1095 $nnt = Title::newFromText ( $category.":".$t ) ;
1096 $t = $sk->makeLinkObj( $nnt, $t, "", $trail , $prefix );
1097 $this->mCategoryLinks[] = $t ;
1098 $s .= $prefix . $trail ;
1099 continue ;
1100 }
1101 if( $ns == $media ) {
1102 $s .= $prefix . $sk->makeMediaLinkObj( $nt, $text ) . $trail;
1103 $wgLinkCache->addImageLinkObj( $nt );
1104 continue;
1105 } elseif( $ns == $special ) {
1106 $s .= $prefix . $sk->makeKnownLinkObj( $nt, $text, "", $trail );
1107 continue;
1108 }
1109 $s .= $sk->makeLinkObj( $nt, $text, "", $trail , $prefix );
1110 }
1111 wfProfileOut( $fname );
1112 return $s;
1113 }
1114
1115 # Some functions here used by doBlockLevels()
1116 #
1117 /* private */ function closeParagraph()
1118 {
1119 $result = "";
1120 if ( 0 != strcmp( "p", $this->mLastSection ) &&
1121 0 != strcmp( "", $this->mLastSection ) ) {
1122 $result = "</" . $this->mLastSection . ">";
1123 }
1124 $this->mLastSection = "";
1125 return $result."\n";
1126 }
1127 # getCommon() returns the length of the longest common substring
1128 # of both arguments, starting at the beginning of both.
1129 #
1130 /* private */ function getCommon( $st1, $st2 )
1131 {
1132 $fl = strlen( $st1 );
1133 $shorter = strlen( $st2 );
1134 if ( $fl < $shorter ) { $shorter = $fl; }
1135
1136 for ( $i = 0; $i < $shorter; ++$i ) {
1137 if ( $st1{$i} != $st2{$i} ) { break; }
1138 }
1139 return $i;
1140 }
1141 # These next three functions open, continue, and close the list
1142 # element appropriate to the prefix character passed into them.
1143 #
1144 /* private */ function openList( $char )
1145 {
1146 $result = $this->closeParagraph();
1147
1148 if ( "*" == $char ) { $result .= "<ul><li>"; }
1149 else if ( "#" == $char ) { $result .= "<ol><li>"; }
1150 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
1151 else if ( ";" == $char ) {
1152 $result .= "<dl><dt>";
1153 $this->mDTopen = true;
1154 }
1155 else { $result = "<!-- ERR 1 -->"; }
1156
1157 return $result;
1158 }
1159
1160 /* private */ function nextItem( $char )
1161 {
1162 if ( "*" == $char || "#" == $char ) { return "</li><li>"; }
1163 else if ( ":" == $char || ";" == $char ) {
1164 $close = "</dd>";
1165 if ( $this->mDTopen ) { $close = "</dt>"; }
1166 if ( ";" == $char ) {
1167 $this->mDTopen = true;
1168 return $close . "<dt>";
1169 } else {
1170 $this->mDTopen = false;
1171 return $close . "<dd>";
1172 }
1173 }
1174 return "<!-- ERR 2 -->";
1175 }
1176
1177 /* private */function closeList( $char )
1178 {
1179 if ( "*" == $char ) { $text = "</li></ul>"; }
1180 else if ( "#" == $char ) { $text = "</li></ol>"; }
1181 else if ( ":" == $char ) {
1182 if ( $this->mDTopen ) {
1183 $this->mDTopen = false;
1184 $text = "</dt></dl>";
1185 } else {
1186 $text = "</dd></dl>";
1187 }
1188 }
1189 else { return "<!-- ERR 3 -->"; }
1190 return $text."\n";
1191 }
1192
1193 /* private */ function doBlockLevels( $text, $linestart )
1194 {
1195 $fname = "OutputPage::doBlockLevels";
1196 wfProfileIn( $fname );
1197 # Parsing through the text line by line. The main thing
1198 # happening here is handling of block-level elements p, pre,
1199 # and making lists from lines starting with * # : etc.
1200 #
1201 $a = explode( "\n", $text );
1202 $text = $lastPref = "";
1203 $this->mDTopen = $inBlockElem = false;
1204
1205 if ( ! $linestart ) { $text .= array_shift( $a ); }
1206 foreach ( $a as $t ) {
1207 if ( "" != $text ) { $text .= "\n"; }
1208
1209 $oLine = $t;
1210 $opl = strlen( $lastPref );
1211 $npl = strspn( $t, "*#:;" );
1212 $pref = substr( $t, 0, $npl );
1213 $pref2 = str_replace( ";", ":", $pref );
1214 $t = substr( $t, $npl );
1215
1216 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
1217 $text .= $this->nextItem( substr( $pref, -1 ) );
1218
1219 if ( ";" == substr( $pref, -1 ) ) {
1220 $cpos = strpos( $t, ":" );
1221 if ( ! ( false === $cpos ) ) {
1222 $term = substr( $t, 0, $cpos );
1223 $text .= $term . $this->nextItem( ":" );
1224 $t = substr( $t, $cpos + 1 );
1225 }
1226 }
1227 } else if (0 != $npl || 0 != $opl) {
1228 $cpl = $this->getCommon( $pref, $lastPref );
1229
1230 while ( $cpl < $opl ) {
1231 $text .= $this->closeList( $lastPref{$opl-1} );
1232 --$opl;
1233 }
1234 if ( $npl <= $cpl && $cpl > 0 ) {
1235 $text .= $this->nextItem( $pref{$cpl-1} );
1236 }
1237 while ( $npl > $cpl ) {
1238 $char = substr( $pref, $cpl, 1 );
1239 $text .= $this->openList( $char );
1240
1241 if ( ";" == $char ) {
1242 $cpos = strpos( $t, ":" );
1243 if ( ! ( false === $cpos ) ) {
1244 $term = substr( $t, 0, $cpos );
1245 $text .= $term . $this->nextItem( ":" );
1246 $t = substr( $t, $cpos + 1 );
1247 }
1248 }
1249 ++$cpl;
1250 }
1251 $lastPref = $pref2;
1252 }
1253 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1254 if ( preg_match(
1255 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1256 $text .= $this->closeParagraph();
1257 $inBlockElem = true;
1258 }
1259 if ( ! $inBlockElem ) {
1260 if ( " " == $t{0} ) {
1261 $newSection = "pre";
1262 # $t = wfEscapeHTML( $t );
1263 }
1264 else { $newSection = "p"; }
1265
1266 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1267 $text .= $this->closeParagraph();
1268 $text .= "<" . $newSection . ">";
1269 } else if ( 0 != strcmp( $this->mLastSection,
1270 $newSection ) ) {
1271 $text .= $this->closeParagraph();
1272 if ( 0 != strcmp( "p", $newSection ) ) {
1273 $text .= "<" . $newSection . ">";
1274 }
1275 }
1276 $this->mLastSection = $newSection;
1277 }
1278 if ( $inBlockElem &&
1279 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1280 $inBlockElem = false;
1281 }
1282 }
1283 $text .= $t;
1284 }
1285 while ( $npl ) {
1286 $text .= $this->closeList( $pref2{$npl-1} );
1287 --$npl;
1288 }
1289 if ( "" != $this->mLastSection ) {
1290 if ( "p" != $this->mLastSection ) {
1291 $text .= "</" . $this->mLastSection . ">";
1292 }
1293 $this->mLastSection = "";
1294 }
1295 wfProfileOut( $fname );
1296 return $text;
1297 }
1298
1299 /* private */ function replaceVariables( $text )
1300 {
1301 global $wgLang, $wgCurOut;
1302 $fname = "OutputPage::replaceVariables";
1303 wfProfileIn( $fname );
1304
1305 $magic = array();
1306
1307 # Basic variables
1308 # See Language.php for the definition of each magic word
1309 # As with sigs, this uses the server's local time -- ensure
1310 # this is appropriate for your audience!
1311
1312 $magic[MAG_CURRENTMONTH] = date( "m" );
1313 $magic[MAG_CURRENTMONTHNAME] = $wgLang->getMonthName( date("n") );
1314 $magic[MAG_CURRENTMONTHNAMEGEN] = $wgLang->getMonthNameGen( date("n") );
1315 $magic[MAG_CURRENTDAY] = date("j");
1316 $magic[MAG_CURRENTDAYNAME] = $wgLang->getWeekdayName( date("w")+1 );
1317 $magic[MAG_CURRENTYEAR] = date( "Y" );
1318 $magic[MAG_CURRENTTIME] = $wgLang->time( wfTimestampNow(), false );
1319
1320 $this->mContainsOldMagic += MagicWord::replaceMultiple($magic, $text, $text);
1321
1322 $mw =& MagicWord::get( MAG_NUMBEROFARTICLES );
1323 if ( $mw->match( $text ) ) {
1324 $v = wfNumberOfArticles();
1325 $text = $mw->replace( $v, $text );
1326 if( $mw->getWasModified() ) { $this->mContainsOldMagic++; }
1327 }
1328
1329 # "Variables" with an additional parameter e.g. {{MSG:wikipedia}}
1330 # The callbacks are at the bottom of this file
1331 $wgCurOut = $this;
1332 $mw =& MagicWord::get( MAG_MSG );
1333 $text = $mw->substituteCallback( $text, "wfReplaceMsgVar" );
1334 if( $mw->getWasModified() ) { $this->mContainsNewMagic++; }
1335
1336 $mw =& MagicWord::get( MAG_MSGNW );
1337 $text = $mw->substituteCallback( $text, "wfReplaceMsgnwVar" );
1338 if( $mw->getWasModified() ) { $this->mContainsNewMagic++; }
1339
1340 wfProfileOut( $fname );
1341 return $text;
1342 }
1343
1344 # Cleans up HTML, removes dangerous tags and attributes
1345 /* private */ function removeHTMLtags( $text )
1346 {
1347 $fname = "OutputPage::removeHTMLtags";
1348 wfProfileIn( $fname );
1349 $htmlpairs = array( # Tags that must be closed
1350 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1351 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1352 "strike", "strong", "tt", "var", "div", "center",
1353 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1354 "ruby", "rt" , "rb" , "rp"
1355 );
1356 $htmlsingle = array(
1357 "br", "p", "hr", "li", "dt", "dd"
1358 );
1359 $htmlnest = array( # Tags that can be nested--??
1360 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1361 "dl", "font", "big", "small", "sub", "sup"
1362 );
1363 $tabletags = array( # Can only appear inside table
1364 "td", "th", "tr"
1365 );
1366
1367 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1368 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1369
1370 $htmlattrs = $this->getHTMLattrs () ;
1371
1372 # Remove HTML comments
1373 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1374
1375 $bits = explode( "<", $text );
1376 $text = array_shift( $bits );
1377 $tagstack = array(); $tablestack = array();
1378
1379 foreach ( $bits as $x ) {
1380 $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
1381 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1382 $x, $regs );
1383 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1384 error_reporting( $prev );
1385
1386 $badtag = 0 ;
1387 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1388 # Check our stack
1389 if ( $slash ) {
1390 # Closing a tag...
1391 if ( ! in_array( $t, $htmlsingle ) &&
1392 ( $ot = array_pop( $tagstack ) ) != $t ) {
1393 array_push( $tagstack, $ot );
1394 $badtag = 1;
1395 } else {
1396 if ( $t == "table" ) {
1397 $tagstack = array_pop( $tablestack );
1398 }
1399 $newparams = "";
1400 }
1401 } else {
1402 # Keep track for later
1403 if ( in_array( $t, $tabletags ) &&
1404 ! in_array( "table", $tagstack ) ) {
1405 $badtag = 1;
1406 } else if ( in_array( $t, $tagstack ) &&
1407 ! in_array ( $t , $htmlnest ) ) {
1408 $badtag = 1 ;
1409 } else if ( ! in_array( $t, $htmlsingle ) ) {
1410 if ( $t == "table" ) {
1411 array_push( $tablestack, $tagstack );
1412 $tagstack = array();
1413 }
1414 array_push( $tagstack, $t );
1415 }
1416 # Strip non-approved attributes from the tag
1417 $newparams = $this->fixTagAttributes($params);
1418
1419 }
1420 if ( ! $badtag ) {
1421 $rest = str_replace( ">", "&gt;", $rest );
1422 $text .= "<$slash$t $newparams$brace$rest";
1423 continue;
1424 }
1425 }
1426 $text .= "&lt;" . str_replace( ">", "&gt;", $x);
1427 }
1428 # Close off any remaining tags
1429 while ( $t = array_pop( $tagstack ) ) {
1430 $text .= "</$t>\n";
1431 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1432 }
1433 wfProfileOut( $fname );
1434 return $text;
1435 }
1436
1437 /*
1438 *
1439 * This function accomplishes several tasks:
1440 * 1) Auto-number headings if that option is enabled
1441 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1442 * 3) Add a Table of contents on the top for users who have enabled the option
1443 * 4) Auto-anchor headings
1444 *
1445 * It loops through all headlines, collects the necessary data, then splits up the
1446 * string and re-inserts the newly formatted headlines.
1447 *
1448 * */
1449 /* private */ function formatHeadings( $text )
1450 {
1451 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1452 $nh=$wgUser->getOption( "numberheadings" );
1453 $st=$wgUser->getOption( "showtoc" );
1454 if(!$wgTitle->userCanEdit()) {
1455 $es=0;
1456 $esr=0;
1457 } else {
1458 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1459 $esr=$wgUser->getID() && $wgUser->getOption( "editsectiononrightclick" );
1460 }
1461
1462 # Inhibit editsection links if requested in the page
1463 $esw =& MagicWord::get( MAG_NOEDITSECTION );
1464 if ($esw->matchAndRemove( $text )) {
1465 $es=0;
1466 }
1467 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
1468 # do not add TOC
1469 $mw =& MagicWord::get( MAG_NOTOC );
1470 if ($mw->matchAndRemove( $text ))
1471 {
1472 $st = 0;
1473 }
1474
1475 # never add the TOC to the Main Page. This is an entry page that should not
1476 # be more than 1-2 screens large anyway
1477 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1478
1479 # We need this to perform operations on the HTML
1480 $sk=$wgUser->getSkin();
1481
1482 # Get all headlines for numbering them and adding funky stuff like [edit]
1483 # links
1484 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1485
1486 # headline counter
1487 $c=0;
1488
1489 # Ugh .. the TOC should have neat indentation levels which can be
1490 # passed to the skin functions. These are determined here
1491 foreach($matches[3] as $headline) {
1492 if($level) { $prevlevel=$level;}
1493 $level=$matches[1][$c];
1494 if(($nh||$st) && $prevlevel && $level>$prevlevel) {
1495
1496 $h[$level]=0; // reset when we enter a new level
1497 $toc.=$sk->tocIndent($level-$prevlevel);
1498 $toclevel+=$level-$prevlevel;
1499
1500 }
1501 if(($nh||$st) && $level<$prevlevel) {
1502 $h[$level+1]=0; // reset when we step back a level
1503 $toc.=$sk->tocUnindent($prevlevel-$level);
1504 $toclevel-=$prevlevel-$level;
1505
1506 }
1507 $h[$level]++; // count number of headlines for each level
1508
1509 if($nh||$st) {
1510 for($i=1;$i<=$level;$i++) {
1511 if($h[$i]) {
1512 if($dot) {$numbering.=".";}
1513 $numbering.=$h[$i];
1514 $dot=1;
1515 }
1516 }
1517 }
1518
1519 // The canonized header is a version of the header text safe to use for links
1520
1521 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1522 $tocline = trim( $canonized_headline );
1523 $canonized_headline=str_replace('"',"",$canonized_headline);
1524 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1525 $refer[$c]=$canonized_headline;
1526 $refers[$canonized_headline]++; // count how many in assoc. array so we can track dupes in anchors
1527 $refcount[$c]=$refers[$canonized_headline];
1528
1529 // Prepend the number to the heading text
1530
1531 if($nh||$st) {
1532 $tocline=$numbering ." ". $tocline;
1533
1534 // Don't number the heading if it is the only one (looks silly)
1535 if($nh && count($matches[3]) > 1) {
1536 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1537 }
1538 }
1539
1540 // Create the anchor for linking from the TOC to the section
1541
1542 $anchor=$canonized_headline;
1543 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1544 if($st) {
1545 $toc.=$sk->tocLine($anchor,$tocline,$toclevel);
1546 }
1547 if($es && !isset($wpPreview)) {
1548 $head[$c].=$sk->editSectionLink($c+1);
1549 }
1550
1551 // Put it all together
1552
1553 $head[$c].="<h".$level.$matches[2][$c]
1554 ."<a name=\"".$anchor."\">"
1555 .$headline
1556 ."</a>"
1557 ."</h".$level.">";
1558
1559 // Add the edit section link
1560
1561 if($esr && !isset($wpPreview)) {
1562 $head[$c]=$sk->editSectionScript($c+1,$head[$c]);
1563 }
1564
1565 $numbering="";
1566 $c++;
1567 $dot=0;
1568 }
1569
1570 if($st) {
1571 $toclines=$c;
1572 $toc.=$sk->tocUnindent($toclevel);
1573 $toc=$sk->tocTable($toc);
1574 }
1575
1576 // split up and insert constructed headlines
1577
1578 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1579 $i=0;
1580
1581 foreach($blocks as $block) {
1582 if(($es) && !isset($wpPreview) && $c>0 && $i==0) {
1583 # This is the [edit] link that appears for the top block of text when
1584 # section editing is enabled
1585 $full.=$sk->editSectionLink(0);
1586 }
1587 $full.=$block;
1588 if($st && $toclines>3 && !$i) {
1589 # Let's add a top anchor just in case we want to link to the top of the page
1590 $full="<a name=\"top\"></a>".$full.$toc;
1591 }
1592
1593 $full.=$head[$i];
1594 $i++;
1595 }
1596
1597 return $full;
1598 }
1599
1600 /* private */ function magicISBN( $text )
1601 {
1602 global $wgLang;
1603
1604 $a = split( "ISBN ", " $text" );
1605 if ( count ( $a ) < 2 ) return $text;
1606 $text = substr( array_shift( $a ), 1);
1607 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1608
1609 foreach ( $a as $x ) {
1610 $isbn = $blank = "" ;
1611 while ( " " == $x{0} ) {
1612 $blank .= " ";
1613 $x = substr( $x, 1 );
1614 }
1615 while ( strstr( $valid, $x{0} ) != false ) {
1616 $isbn .= $x{0};
1617 $x = substr( $x, 1 );
1618 }
1619 $num = str_replace( "-", "", $isbn );
1620 $num = str_replace( " ", "", $num );
1621
1622 if ( "" == $num ) {
1623 $text .= "ISBN $blank$x";
1624 } else {
1625 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1626 "Booksources"), "isbn={$num}" ) . "\" class=\"internal\">ISBN $isbn</a>";
1627 $text .= $x;
1628 }
1629 }
1630 return $text;
1631 }
1632
1633 /* private */ function magicRFC( $text )
1634 {
1635 return $text;
1636 }
1637
1638 /* private */ function headElement()
1639 {
1640 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding, $wgLang;
1641
1642 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
1643
1644 if ( "" == $this->mHTMLtitle ) {
1645 $this->mHTMLtitle = $this->mPagetitle;
1646 }
1647 $rtl = $wgLang->isRTL() ? " dir='RTL'" : "";
1648 $ret .= "<html lang=\"$wgLanguageCode\"$rtl><head><title>{$this->mHTMLtitle}</title>\n";
1649 array_push( $this->mMetatags, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1650 foreach ( $this->mMetatags as $tag ) {
1651 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1652 $a = "http-equiv";
1653 $tag[0] = substr( $tag[0], 5 );
1654 } else {
1655 $a = "name";
1656 }
1657 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1658 }
1659 $p = $this->mRobotpolicy;
1660 if ( "" == $p ) { $p = "index,follow"; }
1661 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1662
1663 if ( count( $this->mKeywords ) > 0 ) {
1664 $ret .= "<meta name=\"keywords\" content=\"" .
1665 implode( ",", $this->mKeywords ) . "\">\n";
1666 }
1667 foreach ( $this->mLinktags as $tag ) {
1668 $ret .= "<link ";
1669 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1670 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1671 $ret .= "href=\"{$tag[2]}\">\n";
1672 }
1673 $sk = $wgUser->getSkin();
1674 $ret .= $sk->getHeadScripts();
1675 $ret .= $sk->getUserStyles();
1676
1677 $ret .= "</head>\n";
1678 return $ret;
1679 }
1680
1681 /* private */ function fillFromParserCache(){
1682 global $wgUser, $wgArticle;
1683 $hash = $wgUser->getPageRenderingHash();
1684 $pageid = intval( $wgArticle->getID() );
1685 $res = wfQuery("SELECT pc_data FROM parsercache WHERE pc_pageid = {$pageid} ".
1686 " AND pc_prefhash = '{$hash}' AND pc_expire > NOW()", DB_WRITE);
1687 $row = wfFetchObject ( $res );
1688 if( $row ){
1689 $data = unserialize( gzuncompress($row->pc_data) );
1690 $this->addHTML( $data['html'] );
1691 $this->mLanguageLinks = $data['mLanguageLinks'];
1692 $this->mCategoryLinks = $data['mCategoryLinks'];
1693 wfProfileOut( $fname );
1694 return true;
1695 } else {
1696 return false;
1697 }
1698 }
1699
1700 /* private */ function saveParserCache( $text ){
1701 global $wgUser, $wgArticle;
1702 $hash = $wgUser->getPageRenderingHash();
1703 $pageid = intval( $wgArticle->getID() );
1704 $title = wfStrencode( $wgArticle->mTitle->getPrefixedDBKey() );
1705 $data = array();
1706 $data['html'] = $text;
1707 $data['mLanguageLinks'] = $this->mLanguageLinks;
1708 $data['mCategoryLinks'] = $this->mCategoryLinks;
1709 $ser = addslashes( gzcompress( serialize( $data ) ) );
1710 if( $this->mContainsOldMagic ){
1711 $expire = "1 HOUR";
1712 } else {
1713 $expire = "7 DAY";
1714 }
1715
1716 wfQuery("REPLACE INTO parsercache (pc_prefhash,pc_pageid,pc_title,pc_data, pc_expire) ".
1717 "VALUES('{$hash}', {$pageid}, '{$title}', '{$ser}', ".
1718 "DATE_ADD(NOW(), INTERVAL {$expire}))", DB_WRITE);
1719
1720 if( rand() % 50 == 0 ){ // more efficient to just do it sometimes
1721 $this->purgeParserCache();
1722 }
1723 }
1724
1725 /* static private */ function purgeParserCache(){
1726 wfQuery("DELETE FROM parsercache WHERE pc_expire < NOW() LIMIT 250", DB_WRITE);
1727 }
1728
1729 /* static */ function parsercacheClearLinksTo( $pid ){
1730 $pid = intval( $pid );
1731 wfQuery("DELETE parsercache FROM parsercache,links ".
1732 "WHERE pc_title=links.l_from AND l_to={$pid}", DB_WRITE);
1733 wfQuery("DELETE FROM parsercache WHERE pc_pageid='{$pid}'", DB_WRITE);
1734 }
1735
1736 # $title is a prefixed db title, for example like Title->getPrefixedDBkey() returns.
1737 /* static */ function parsercacheClearBrokenLinksTo( $title ){
1738 $title = wfStrencode( $title );
1739 wfQuery("DELETE parsercache FROM parsercache,brokenlinks ".
1740 "WHERE pc_pageid=bl_from AND bl_to='{$title}'", DB_WRITE);
1741 }
1742
1743 # $pid is a page id
1744 /* static */ function parsercacheClearPage( $pid, $namespace ){
1745 $pid = intval( $pid );
1746 if( $namespace == NS_MEDIAWIKI ){
1747 OutputPage::parsercacheClearLinksTo( $pid );
1748 } else {
1749 wfQuery("DELETE FROM parsercache WHERE pc_pageid='{$pid}'", DB_WRITE);
1750 }
1751 }
1752 }
1753
1754 # Regex callbacks, used in OutputPage::replaceVariables
1755
1756 # Just get rid of the dangerous stuff
1757 # Necessary because replaceVariables is called after removeHTMLtags,
1758 # and message text can come from any user
1759 function wfReplaceMsgVar( $matches ) {
1760 global $wgCurOut, $wgLinkCache;
1761 $text = $wgCurOut->removeHTMLtags( wfMsg( $matches[1] ) );
1762 $wgLinkCache->suspend();
1763 $text = $wgCurOut->replaceInternalLinks( $text );
1764 $wgLinkCache->resume();
1765 $wgLinkCache->addLinkObj( Title::makeTitle( NS_MEDIAWIKI, $matches[1] ) );
1766 return $text;
1767 }
1768
1769 # Effective <nowiki></nowiki>
1770 # Not real <nowiki> because this is called after nowiki sections are processed
1771 function wfReplaceMsgnwVar( $matches ) {
1772 global $wgCurOut, $wgLinkCache;
1773 $text = wfEscapeWikiText( wfMsg( $matches[1] ) );
1774 $wgLinkCache->addLinkObj( Title::makeTitle( NS_MEDIAWIKI, $matches[1] ) );
1775 return $text;
1776 }
1777
1778 ?>