4 if($wgUseTeX) include_once( "Math.php" );
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;
13 var $mDTopen, $mLastSection; # Used for processing DL, PRE
14 var $mLanguageLinks, $mSupressQuickbar;
17 var $mContainsOldMagic, $mContainsNewMagic;
18 var $mIsArticleRelated;
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;
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; }
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 ) ); }
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 )
52 global $wgLang, $wgCachePages, $wgUser;
53 if( !$wgCachePages ) {
54 wfDebug( "CACHE DISABLED\n", false );
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 );
62 if( $wgUser->getOption( "nocache" ) ) {
63 wfDebug( "USER DISABLED CACHE\n", false );
67 $this->sendCacheControl();
69 $lastmod = gmdate( "D, j M Y H:i:s", wfTimestamp2Unix(
70 max( $timestamp, $wgUser->mTouched
) ) ) . " GMT";
72 if( !empty( $_SERVER["HTTP_IF_MODIFIED_SINCE"] ) ) {
73 # IE sends sizes after the date like this:
74 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
75 # this breaks strtotime().
76 $modsince = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
77 $ismodsince = wfUnix2Timestamp( strtotime( $modsince ) );
78 wfDebug( "-- client send If-Modified-Since: " . $modsince . "\n", false );
79 wfDebug( "-- we might send Last-Modified : $lastmod\n", false );
81 if( ($ismodsince >= $timestamp ) and $wgUser->validateCache( $ismodsince ) ) {
82 # Make sure you're in a place you can leave when you call us!
83 header( "HTTP/1.0 304 Not Modified" );
84 header( "Last-Modified: {$lastmod}" );
85 wfDebug( "CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
89 wfDebug( "READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
90 $this->mLastModified
= $lastmod;
93 wfDebug( "We're confused.\n", false );
94 $this->mLastModified
= $lastmod;
98 function setRobotpolicy( $str ) { $this->mRobotpolicy
= $str; }
99 function setHTMLtitle( $name ) { $this->mHTMLtitle
= $name; }
100 function setPageTitle( $name ) { $this->mPagetitle
= $name; }
101 function getPageTitle() { return $this->mPagetitle
; }
102 function setSubtitle( $str ) { $this->mSubtitle
= $str; }
103 function getSubtitle() { return $this->mSubtitle
; }
104 function isArticle() { return $this->mIsarticle
; }
105 function setPrintable() { $this->mPrintable
= true; }
106 function isPrintable() { return $this->mPrintable
; }
107 function setOnloadHandler( $js ) { $this->mOnloadHandler
= $js; }
108 function getOnloadHandler() { return $this->mOnloadHandler
; }
109 function disable() { $this->mDoNothing
= true; }
111 function setArticleRelated( $v )
113 $this->mIsArticleRelated
= $v;
115 $this->mIsarticle
= false;
118 function setArticleFlag( $v ) {
119 $this->mIsarticle
= $v;
121 $this->mIsArticleRelated
= $v;
125 function isArticleRelated()
127 return $this->mIsArticleRelated
;
130 function getLanguageLinks() {
131 global $wgTitle, $wgLanguageCode;
132 global $wgDBconnection, $wgDBname;
133 return $this->mLanguageLinks
;
135 function supressQuickbar() { $this->mSupressQuickbar
= true; }
136 function isQuickbarSupressed() { return $this->mSupressQuickbar
; }
138 function addHTML( $text ) { $this->mBodytext
.= $text; }
139 function addHeadtext( $text ) { $this->mHeadtext
.= $text; }
140 function debug( $text ) { $this->mDebugtext
.= $text; }
142 # First pass--just handle <nowiki> sections, pass the rest off
143 # to doWikiPass2() which does all the real work.
145 function addWikiText( $text, $linestart = true )
147 global $wgUseTeX, $wgArticle, $wgUser, $action;
148 $fname = "OutputPage::addWikiText";
149 wfProfileIn( $fname );
150 $unique = "3iyZiyA7iMwg5rhxP0Dcc9oTnj8qD1jm1Sfv4";
151 $unique2 = "4LIQ9nXtiYFPCSfitVwDw7EYwQlL4GeeQ7qSO";
152 $unique3 = "fPaA8gDfdLBqzj68Yjg9Hil3qEF8JGO0uszIp";
163 # Replace any instances of the placeholders
164 $text = str_replace( $unique, wfHtmlEscapeFirst( $unique ), $text );
165 $text = str_replace( $unique2, wfHtmlEscapeFirst( $unique2 ), $text );
166 $text = str_replace( $unique3, wfHtmlEscapeFirst( $unique3 ), $text );
168 global $wgEnableParserCache;
170 $wgEnableParserCache && $action == "view" &&
171 intval($wgUser->getOption( "stubthreshold" )) == 0 &&
172 isset($wgArticle) && $wgArticle->getID() > 0;
174 if( $use_parser_cache ){
175 if( $this->fillFromParserCache() ){
176 wfProfileOut( $fname );
181 while ( "" != $text ) {
182 $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 );
184 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $text = ""; }
186 $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
188 $nwlist[$nwsecs] = wfEscapeHTMLTagsOnly($q[0]);
189 $stripped .= $unique . $nwsecs . "s";
195 while ( "" != $stripped ) {
196 $p = preg_split( "/<\\s*math\\s*>/i", $stripped, 2 );
198 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $stripped = ""; }
200 $q = preg_split( "/<\\/\\s*math\\s*>/i", $p[1], 2 );
202 $mathlist[$mathsecs] = renderMath($q[0]);
203 $stripped2 .= $unique2 . $mathsecs . "s";
208 $stripped2 = $stripped;
211 while ( "" != $stripped2 ) {
212 $p = preg_split( "/<\\s*pre\\s*>/i", $stripped2, 2 );
214 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $stripped2 = ""; }
216 $q = preg_split( "/<\\/\\s*pre\\s*>/i", $p[1], 2 );
218 $prelist[$presecs] = "<pre>". wfEscapeHTMLTagsOnly($q[0]). "</pre>\n";
219 $stripped3 .= $unique3 . $presecs . "s";
224 $text = $this->doWikiPass2( $stripped3, $linestart );
226 $specialChars = array("\\", "$");
227 $escapedChars = array("\\\\", "\\$");
228 for ( $i = 1; $i <= $presecs; ++
$i ) {
229 $text = preg_replace( "/{$unique3}{$i}s/", str_replace( $specialChars,
230 $escapedChars, $prelist[$i] ), $text );
233 for ( $i = 1; $i <= $mathsecs; ++
$i ) {
234 $text = preg_replace( "/{$unique2}{$i}s/", str_replace( $specialChars,
235 $escapedChars, $mathlist[$i] ), $text );
238 for ( $i = 1; $i <= $nwsecs; ++
$i ) {
239 $text = preg_replace( "/{$unique}{$i}s/", str_replace( $specialChars,
240 $escapedChars, $nwlist[$i] ), $text );
242 $this->addHTML( $text );
244 if($use_parser_cache ){
245 $this->saveParserCache( $text );
247 wfProfileOut( $fname );
250 function sendCacheControl() {
252 # FIXME: This header may cause trouble with some versions of Internet Explorer
253 header( "Vary: Accept-Encoding, Cookie" );
254 if( $this->mLastModified
!= "" ) {
255 if( $wgUseSquid && ! isset( $_COOKIE[ini_get( "session.name") ] ) ) {
256 # We'll purge the proxy cache for anons explicitly, but require end user agents
257 # to revalidate against the proxy on each visit.
258 wfDebug( "** local proxy caching; {$this->mLastModified} **\n", false );
259 header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
261 # We do want clients to cache if they can, but they *must* check for updates
262 # on revisiting the page.
263 wfDebug( "** private caching; {$this->mLastModified} **\n", false );
264 header( "Expires: -1" );
265 header( "Cache-Control: private, must-revalidate, max-age=0" );
267 header( "Last-modified: {$this->mLastModified}" );
269 wfDebug( "** no caching **\n", false );
270 header( "Expires: -1" );
271 header( "Cache-Control: no-cache" );
272 header( "Pragma: no-cache" );
273 header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" );
277 # Finally, all the text has been munged and accumulated into
278 # the object, let's actually output it:
282 global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
283 global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
284 if( $this->mDoNothing
){
287 $fname = "OutputPage::output";
288 wfProfileIn( $fname );
290 $sk = $wgUser->getSkin();
292 $this->sendCacheControl();
294 header( "Content-type: text/html; charset={$wgOutputEncoding}" );
295 header( "Content-language: {$wgLanguageCode}" );
297 if ( "" != $this->mRedirect
) {
298 if( substr( $this->mRedirect
, 0, 4 ) != "http" ) {
299 # Standards require redirect URLs to be absolute
301 $this->mRedirect
= $wgServer . $this->mRedirect
;
303 header( "Location: {$this->mRedirect}" );
307 $exp = time() +
$wgCookieExpiration;
308 foreach( $this->mCookies
as $name => $val ) {
309 setcookie( $name, $val, $exp, "/" );
312 $sk->outputPage( $this );
318 global $wgInputEncoding, $wgOutputEncoding, $wgLang;
319 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
322 $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
323 if ( false === $outs ) { $outs = $ins; }
328 function setEncodings()
330 global $wgInputEncoding, $wgOutputEncoding;
331 global $wgUser, $wgLang;
333 $wgInputEncoding = strtolower( $wgInputEncoding );
335 if( $wgUser->getOption( 'altencoding' ) ) {
336 $wgLang->setAltEncoding();
340 if ( empty( $_SERVER['HTTP_ACCEPT_CHARSET'] ) ) {
341 $wgOutputEncoding = strtolower( $wgOutputEncoding );
346 # This code is unused anyway!
347 # Commenting out. --bv 2003-11-15
349 $a = explode( ",", $_SERVER['HTTP_ACCEPT_CHARSET'] );
353 foreach ( $a as $s ) {
354 if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) {
366 #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; }
367 if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; }
368 $wgOutputEncoding = strtolower( $bestset );
373 $wgOutputEncoding = $wgInputEncoding;
376 # Returns a HTML comment with the elapsed time since request.
377 # This method has no side effects.
378 function reportTime()
380 global $wgRequestTime;
382 list( $usec, $sec ) = explode( " ", microtime() );
383 $now = (float)$sec +
(float)$usec;
385 list( $usec, $sec ) = explode( " ", $wgRequestTime );
386 $start = (float)$sec +
(float)$usec;
387 $elapsed = $now - $start;
388 $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
393 # Note: these arguments are keys into wfMsg(), not text!
395 function errorpage( $title, $msg )
399 $this->mDebugtext
.= "Original title: " .
400 $wgTitle->getPrefixedText() . "\n";
401 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
402 $this->setPageTitle( wfMsg( $title ) );
403 $this->setRobotpolicy( "noindex,nofollow" );
404 $this->setArticleRelated( false );
406 $this->mBodytext
= "";
407 $this->addHTML( "<p>" . wfMsg( $msg ) . "\n" );
408 $this->returnToMain( false );
414 function sysopRequired()
418 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
419 $this->setPageTitle( wfMsg( "sysoptitle" ) );
420 $this->setRobotpolicy( "noindex,nofollow" );
421 $this->setArticleRelated( false );
422 $this->mBodytext
= "";
424 $sk = $wgUser->getSkin();
425 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
426 $this->addHTML( wfMsg( "sysoptext", $ap ) );
427 $this->returnToMain();
430 function developerRequired()
434 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
435 $this->setPageTitle( wfMsg( "developertitle" ) );
436 $this->setRobotpolicy( "noindex,nofollow" );
437 $this->setArticleRelated( false );
438 $this->mBodytext
= "";
440 $sk = $wgUser->getSkin();
441 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
442 $this->addHTML( wfMsg( "developertext", $ap ) );
443 $this->returnToMain();
446 function databaseError( $fname )
448 global $wgUser, $wgCommandLineMode;
450 $this->setPageTitle( wfMsgNoDB( "databaseerror" ) );
451 $this->setRobotpolicy( "noindex,nofollow" );
452 $this->setArticleRelated( false );
454 if ( $wgCommandLineMode ) {
455 $msg = wfMsgNoDB( "dberrortextcl" );
457 $msg = wfMsgNoDB( "dberrortext" );
460 $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), $msg );
461 $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
462 $msg = str_replace( "$3", wfLastErrno(), $msg );
463 $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg );
465 if ( $wgCommandLineMode ||
!is_object( $wgUser )) {
469 $sk = $wgUser->getSkin();
470 $shlink = $sk->makeKnownLink( wfMsgNoDB( "searchhelppage" ),
471 wfMsgNoDB( "searchingwikipedia" ) );
472 $msg = str_replace( "$5", $shlink, $msg );
473 $this->mBodytext
= $msg;
478 function readOnlyPage( $source = "", $protected = false )
480 global $wgUser, $wgReadOnlyFile;
482 $this->setRobotpolicy( "noindex,nofollow" );
483 $this->setArticleRelated( false );
486 $this->setPageTitle( wfMsg( "viewsource" ) );
487 $this->addWikiText( wfMsg( "protectedtext" ) );
489 $this->setPageTitle( wfMsg( "readonly" ) );
490 $reason = file_get_contents( $wgReadOnlyFile );
491 $this->addHTML( wfMsg( "readonlytext", $reason ) );
495 $rows = $wgUser->getOption( "rows" );
496 $cols = $wgUser->getOption( "cols" );
497 $text .= "</p>\n<textarea cols='$cols' rows='$rows' readonly>" .
498 htmlspecialchars( $source ) . "\n</textarea>";
499 $this->addHTML( $text );
502 $this->returnToMain( false );
505 function fatalError( $message )
507 $this->setPageTitle( wfMsg( "internalerror" ) );
508 $this->setRobotpolicy( "noindex,nofollow" );
509 $this->setArticleRelated( false );
511 $this->mBodytext
= $message;
516 function unexpectedValueError( $name, $val )
518 $this->fatalError( wfMsg( "unexpected", $name, $val ) );
521 function fileCopyError( $old, $new )
523 $this->fatalError( wfMsg( "filecopyerror", $old, $new ) );
526 function fileRenameError( $old, $new )
528 $this->fatalError( wfMsg( "filerenameerror", $old, $new ) );
531 function fileDeleteError( $name )
533 $this->fatalError( wfMsg( "filedeleteerror", $name ) );
536 function fileNotFoundError( $name )
538 $this->fatalError( wfMsg( "filenotfound", $name ) );
541 function returnToMain( $auto = true )
543 global $wgUser, $wgOut, $returnto;
545 $sk = $wgUser->getSkin();
546 if ( "" == $returnto ) {
547 $returnto = wfMsg( "mainpage" );
549 $link = $sk->makeKnownLink( $returnto, "" );
551 $r = wfMsg( "returnto", $link );
553 $wgOut->addMeta( "http:Refresh", "10;url=" .
554 wfLocalUrlE( wfUrlencode( $returnto ) ) );
556 $wgOut->addHTML( "\n<p>$r\n" );
560 function categoryMagic ()
562 global $wgTitle , $wgUseCategoryMagic ;
563 if ( !isset ( $wgUseCategoryMagic ) ||
!$wgUseCategoryMagic ) return ;
564 $id = $wgTitle->getArticleID() ;
565 $cat = ucfirst ( wfMsg ( "category" ) ) ;
566 $ti = $wgTitle->getText() ;
567 $ti = explode ( ":" , $ti , 2 ) ;
568 if ( $cat != $ti[0] ) return "" ;
569 $r = "<br break=all>\n" ;
571 $articles = array() ;
572 $parents = array () ;
573 $children = array() ;
577 $sk = $wgUser->getSkin() ;
578 $sql = "SELECT l_from FROM links WHERE l_to={$id}" ;
579 $res = wfQuery ( $sql, DB_READ
) ;
580 while ( $x = wfFetchObject ( $res ) )
583 # $t->newFromDBkey ( $x->l_from ) ;
584 # $t = $t->getText() ;
586 $y = explode ( ":" , $t , 2 ) ;
587 if ( count ( $y ) == 2 && $y[0] == $cat ) {
588 array_push ( $children , $sk->makeLink ( $t , $y[1] ) ) ;
590 array_push ( $articles , $sk->makeLink ( $t ) ) ;
593 wfFreeResult ( $res ) ;
596 if ( count ( $children ) > 0 )
598 asort ( $children ) ;
599 $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
600 $r .= implode ( ", " , $children ) ;
604 if ( count ( $articles ) > 0 )
606 asort ( $articles ) ;
607 $h = wfMsg( "category_header", $ti[1] );
608 $r .= "<h2>{$h}</h2>\n" ;
609 $r .= implode ( ", " , $articles ) ;
616 function getHTMLattrs ()
618 $htmlattrs = array( # Allowed attributes--no scripting, etc.
619 "title", "align", "lang", "dir", "width", "height",
620 "bgcolor", "clear", /* BR */ "noshade", /* HR */
621 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
622 /* FONT */ "type", "start", "value", "compact",
623 /* For various lists, mostly deprecated but safe */
624 "summary", "width", "border", "frame", "rules",
625 "cellspacing", "cellpadding", "valign", "char",
626 "charoff", "colgroup", "col", "span", "abbr", "axis",
627 "headers", "scope", "rowspan", "colspan", /* Tables */
628 "id", "class", "name", "style" /* For CSS */
633 function fixTagAttributes ( $t )
635 if ( trim ( $t ) == "" ) return "" ; # Saves runtime ;-)
636 $htmlattrs = $this->getHTMLattrs() ;
638 # Strip non-approved attributes from the tag
640 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
641 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
643 # Strip javascript "expression" from stylesheets. Brute force approach:
644 # If anythin offensive is found, all attributes of the HTML tag are dropped
647 "/style\\s*=.*(expression|tps*:\/\/|url\\s*\().*/is",
648 wfMungeToUtf8( $t ) ) )
656 function doTableStuff ( $t )
658 $t = explode ( "\n" , $t ) ;
659 $td = array () ; # Is currently a td tag open?
660 $ltd = array () ; # Was it TD or TH?
661 $tr = array () ; # Is currently a tr tag open?
662 $ltr = array () ; # tr attributes
663 foreach ( $t AS $k => $x )
666 $fc = substr ( $x , 0 , 1 ) ;
667 if ( "{|" == substr ( $x , 0 , 2 ) )
669 $t[$k] = "<table " . $this->fixTagAttributes ( substr ( $x , 3 ) ) . ">" ;
670 array_push ( $td , false ) ;
671 array_push ( $ltd , "" ) ;
672 array_push ( $tr , false ) ;
673 array_push ( $ltr , "" ) ;
675 else if ( count ( $td ) == 0 ) { } # Don't do any of the following
676 else if ( "|}" == substr ( $x , 0 , 2 ) )
679 $l = array_pop ( $ltd ) ;
680 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
681 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
685 /* else if ( "|_" == substr ( $x , 0 , 2 ) ) # Caption
687 $z = trim ( substr ( $x , 2 ) ) ;
688 $t[$k] = "<caption>{$z}</caption>\n" ;
690 else if ( "|-" == substr ( $x , 0 , 2 ) ) # Allows for |---------------
692 $x = substr ( $x , 1 ) ;
693 while ( $x != "" && substr ( $x , 0 , 1 ) == '-' ) $x = substr ( $x , 1 ) ;
695 $l = array_pop ( $ltd ) ;
696 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
697 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
700 array_push ( $tr , false ) ;
701 array_push ( $td , false ) ;
702 array_push ( $ltd , "" ) ;
703 array_push ( $ltr , $this->fixTagAttributes ( $x ) ) ;
705 else if ( "|" == $fc ||
"!" == $fc ||
"|+" == substr ( $x , 0 , 2 ) ) # Caption
707 if ( "|+" == substr ( $x , 0 , 2 ) )
710 $x = substr ( $x , 1 ) ;
712 $after = substr ( $x , 1 ) ;
713 if ( $fc == "!" ) $after = str_replace ( "!!" , "||" , $after ) ;
714 $after = explode ( "||" , $after ) ;
716 foreach ( $after AS $theline )
719 $tra = array_pop ( $ltr ) ;
720 if ( !array_pop ( $tr ) ) $z = "<tr {$tra}>\n" ;
721 array_push ( $tr , true ) ;
722 array_push ( $ltr , "" ) ;
724 $l = array_pop ( $ltd ) ;
725 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
726 if ( $fc == "|" ) $l = "TD" ;
727 else if ( $fc == "!" ) $l = "TH" ;
728 else if ( $fc == "+" ) $l = "CAPTION" ;
730 array_push ( $ltd , $l ) ;
731 $y = explode ( "|" , $theline , 2 ) ;
732 if ( count ( $y ) == 1 ) $y = "{$z}<{$l}>{$y[0]}" ;
733 else $y = $y = "{$z}<{$l} ".$this->fixTagAttributes($y[0]).">{$y[1]}" ;
735 array_push ( $td , true ) ;
740 # Closing open td, tr && table
741 while ( count ( $td ) > 0 )
743 if ( array_pop ( $td ) ) $t[] = "</td>" ;
744 if ( array_pop ( $tr ) ) $t[] = "</tr>" ;
748 $t = implode ( "\n" , $t ) ;
749 # $t = $this->removeHTMLtags( $t );
753 # Well, OK, it's actually about 14 passes. But since all the
754 # hard lifting is done inside PHP's regex code, it probably
755 # wouldn't speed things up much to add a real parser.
757 function doWikiPass2( $text, $linestart )
759 global $wgUser, $wgLang, $wgUseDynamicDates;
760 $fname = "OutputPage::doWikiPass2";
761 wfProfileIn( $fname );
763 $text = $this->removeHTMLtags( $text );
764 $text = $this->replaceVariables( $text );
766 $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
767 $text = str_replace ( "<HR>", "<hr>", $text );
769 $text = $this->doAllQuotes( $text );
770 $text = $this->doHeadings( $text );
771 $text = $this->doBlockLevels( $text, $linestart );
773 if($wgUseDynamicDates) {
774 global $wgDateFormatter;
775 $text = $wgDateFormatter->reformat( $wgUser->getOption("date"), $text );
778 $text = $this->replaceExternalLinks( $text );
779 $text = $this->replaceInternalLinks ( $text );
780 $text = $this->doTableStuff ( $text ) ;
782 $text = $this->magicISBN( $text );
783 $text = $this->magicRFC( $text );
784 $text = $this->formatHeadings( $text );
786 $sk = $wgUser->getSkin();
787 $text = $sk->transformContent( $text );
788 $text .= $this->categoryMagic () ;
790 wfProfileOut( $fname );
794 /* private */ function doAllQuotes( $text )
797 $lines = explode( "\r\n", $text );
798 foreach ( $lines as $line ) {
799 $outtext .= $this->doQuotes ( "", $line, "" ) . "\r\n";
804 /* private */ function doQuotes( $pre, $text, $mode )
806 if ( preg_match( "/^(.*)''(.*)$/sU", $text, $m ) ) {
807 $m1_strong = ($m[1] == "") ?
"" : "<strong>{$m[1]}</strong>";
808 $m1_em = ($m[1] == "") ?
"" : "<em>{$m[1]}</em>";
809 if ( substr ($m[2], 0, 1) == "'" ) {
810 $m[2] = substr ($m[2], 1);
812 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ?
"both" : "emstrong" );
813 } else if ($mode == "strong") {
814 return $m1_strong . $this->doQuotes ( "", $m[2], "" );
815 } else if (($mode == "emstrong") ||
($mode == "both")) {
816 return $this->doQuotes ( "", $pre.$m1_strong.$m[2], "em" );
817 } else if ($mode == "strongem") {
818 return "<strong>{$pre}{$m1_em}</strong>" . $this->doQuotes ( "", $m[2], "em" );
820 return $m[1] . $this->doQuotes ( "", $m[2], "strong" );
823 if ($mode == "strong") {
824 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ?
"both" : "strongem" );
825 } else if ($mode == "em") {
826 return $m1_em . $this->doQuotes ( "", $m[2], "" );
827 } else if ($mode == "emstrong") {
828 return "<em>{$pre}{$m1_strong}</em>" . $this->doQuotes ( "", $m[2], "strong" );
829 } else if (($mode == "strongem") ||
($mode == "both")) {
830 return $this->doQuotes ( "", $pre.$m1_em.$m[2], "strong" );
832 return $m[1] . $this->doQuotes ( "", $m[2], "em" );
836 $text_strong = ($text == "") ?
"" : "<strong>{$text}</strong>";
837 $text_em = ($text == "") ?
"" : "<em>{$text}</em>";
840 } else if ($mode == "em") {
841 return $pre . $text_em;
842 } else if ($mode == "strong") {
843 return $pre . $text_strong;
844 } else if ($mode == "strongem") {
845 return (($pre == "") && ($text == "")) ?
"" : "<strong>{$pre}{$text_em}</strong>";
847 return (($pre == "") && ($text == "")) ?
"" : "<em>{$pre}{$text_strong}</em>";
852 /* private */ function doHeadings( $text )
854 for ( $i = 6; $i >= 1; --$i ) {
855 $h = substr( "======", 0, $i );
856 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
857 "<h{$i}>\\1</h{$i}>\\2", $text );
862 # Note: we have to do external links before the internal ones,
863 # and otherwise take great care in the order of things here, so
864 # that we don't end up interpreting some URLs twice.
866 /* private */ function replaceExternalLinks( $text )
868 $fname = "OutputPage::replaceExternalLinks";
869 wfProfileIn( $fname );
870 $text = $this->subReplaceExternalLinks( $text, "http", true );
871 $text = $this->subReplaceExternalLinks( $text, "https", true );
872 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
873 $text = $this->subReplaceExternalLinks( $text, "irc", false );
874 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
875 $text = $this->subReplaceExternalLinks( $text, "news", false );
876 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
877 wfProfileOut( $fname );
881 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
883 global $wgUser, $printable;
884 global $wgAllowExternalImages;
887 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
888 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
890 # this is the list of separators that should be ignored if they
891 # are the last character of an URL but that should be included
892 # if they occur within the URL, e.g. "go to www.foo.com, where .."
893 # in this case, the last comma should not become part of the URL,
894 # but in "www.foo.com/123,2342,32.htm" it should.
896 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
897 $images = "gif|png|jpg|jpeg";
899 # PLEASE NOTE: The curly braces { } are not part of the regex,
900 # they are interpreted as part of the string (used to tell PHP
901 # that the content of the string should be inserted there).
902 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
903 "((?i){$images})([^{$uc}]|$)/";
905 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
906 $sk = $wgUser->getSkin();
908 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
909 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
910 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
912 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
913 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
914 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
916 $s = str_replace( $unique, $protocol, $s );
918 $a = explode( "[{$protocol}:", " " . $s );
919 $s = array_shift( $a );
920 $s = substr( $s, 1 );
922 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
923 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
925 foreach ( $a as $line ) {
926 if ( preg_match( $e1, $line, $m ) ) {
927 $link = "{$protocol}:{$m[1]}";
929 if ( $autonumber ) { $text = "[" . ++
$this->mAutonumber
. "]"; }
930 else { $text = wfEscapeHTML( $link ); }
931 } else if ( preg_match( $e2, $line, $m ) ) {
932 $link = "{$protocol}:{$m[1]}";
936 $s .= "[{$protocol}:" . $line;
939 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
941 $la = $sk->getExternalLinkAttributes( $link, $text );
942 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
948 /* private */ function replaceInternalLinks( $s )
950 global $wgTitle, $wgUser, $wgLang;
951 global $wgLinkCache, $wgInterwikiMagic, $wgUseCategoryMagic;
952 global $wgNamespacesWithSubpages, $wgLanguageCode;
953 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
955 wfProfileIn( "$fname-setup" );
956 $tc = Title
::legalChars() . "#";
957 $sk = $wgUser->getSkin();
959 $a = explode( "[[", " " . $s );
960 $s = array_shift( $a );
961 $s = substr( $s, 1 );
963 $e1 = "/^([{$tc}]+)(?:\\|([^]]+))?]](.*)\$/sD";
965 # Special and Media are pseudo-namespaces; no pages actually exist in them
966 $image = Namespace::getImage();
967 $special = Namespace::getSpecial();
968 $media = Namespace::getMedia();
969 $nottalk = !Namespace::isTalk( $wgTitle->getNamespace() );
970 wfProfileOut( "$fname-setup" );
972 foreach ( $a as $line ) {
973 if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
976 } else { # Invalid form; output directly
983 :Foobar -- override special treatment of prefix (images, language links)
984 /Foobar -- convert to CurrentPage/Foobar
985 /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
987 $c = substr($m[1],0,1);
988 $noforce = ($c != ":");
989 if( $c == "/" ) { # subpage
990 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
991 $m[1]=substr($m[1],1,strlen($m[1])-2);
994 $noslash=substr($m[1],1);
996 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
997 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
1000 } # this might be changed for ugliness reasons
1002 $link = $noslash; # no subpage allowed, use standard link
1004 } elseif( $noforce ) { # no subpage
1007 $link = substr( $m[1], 1 );
1012 $nt = Title
::newFromText( $link );
1017 $ns = $nt->getNamespace();
1018 $iw = $nt->getInterWiki();
1020 if( $iw && $wgInterwikiMagic && $nottalk && $wgLang->getLanguageName( $iw ) ) {
1021 array_push( $this->mLanguageLinks
, $nt->getPrefixedText() );
1024 } else if ( "media" == $pre ) {
1025 $nt = Title::newFromText( $suf );
1026 $name = $nt->getDBkey();
1027 if ( "" == $text ) { $text = $nt->GetText(); }
1029 $wgLinkCache->addImageLink( $name );
1030 $s .= $sk->makeMediaLink( $name,
1031 wfImageUrl( $name ), $text );
1033 } else if ( isset($wgUseCategoryMagic) && $wgUseCategoryMagic && $pre == wfMsg ( "category" ) ) {
1034 $l = $sk->makeLink ( $pre.":".ucfirst( $m[2] ), ucfirst ( $m[2] ) ) ;
1035 array_push ( $this->mCategoryLinks , $l ) ;
1038 $l = $wgLang->getLanguageName( $pre );
1039 if ( "" == $l or !$wgInterwikiMagic or Namespace::isTalk( $wgTitle->getNamespace() ) ) {
1040 if ( "" == $text ) {
1043 $s .= $sk->makeLink( $link, $text, "", $trail );
1044 } else if ( $pre != $wgLanguageCode ) {
1045 array_push( $this->mLanguageLinks, "$pre:$suf" );
1051 if( $ns == $image ) {
1052 $s .= $sk->makeImageLinkObj( $nt, $text ) . $trail;
1053 $wgLinkCache->addImageLinkObj( $nt );
1057 # } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
1058 # $link = substr( $link, 2 );
1059 # $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
1061 if ( "" == $text ) { $text = $link; }
1063 $s .= $sk->makeLink( $link, $text, "", $trail );
1066 if( $ns == $media ) {
1067 $s .= $sk->makeMediaLinkObj( $nt, $text ) . $trail;
1068 $wgLinkCache->addImageLinkObj( $nt );
1070 } elseif( $ns == $special ) {
1071 $s .= $sk->makeKnownLinkObj( $nt, $text, "", $trail );
1074 $s .= $sk->makeLinkObj( $nt, $text, "", $trail );
1076 wfProfileOut( $fname );
1080 # Some functions here used by doBlockLevels()
1082 /* private */ function closeParagraph()
1085 if ( 0 != strcmp( "p", $this->mLastSection
) &&
1086 0 != strcmp( "", $this->mLastSection
) ) {
1087 $result = "</" . $this->mLastSection
. ">";
1089 $this->mLastSection
= "";
1090 return $result."\n";
1092 # getCommon() returns the length of the longest common substring
1093 # of both arguments, starting at the beginning of both.
1095 /* private */ function getCommon( $st1, $st2 )
1097 $fl = strlen( $st1 );
1098 $shorter = strlen( $st2 );
1099 if ( $fl < $shorter ) { $shorter = $fl; }
1101 for ( $i = 0; $i < $shorter; ++
$i ) {
1102 if ( $st1{$i} != $st2{$i} ) { break; }
1106 # These next three functions open, continue, and close the list
1107 # element appropriate to the prefix character passed into them.
1109 /* private */ function openList( $char )
1111 $result = $this->closeParagraph();
1113 if ( "*" == $char ) { $result .= "<ul><li>"; }
1114 else if ( "#" == $char ) { $result .= "<ol><li>"; }
1115 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
1116 else if ( ";" == $char ) {
1117 $result .= "<dl><dt>";
1118 $this->mDTopen
= true;
1120 else { $result = "<!-- ERR 1 -->"; }
1125 /* private */ function nextItem( $char )
1127 if ( "*" == $char ||
"#" == $char ) { return "</li><li>"; }
1128 else if ( ":" == $char ||
";" == $char ) {
1130 if ( $this->mDTopen
) { $close = "</dt>"; }
1131 if ( ";" == $char ) {
1132 $this->mDTopen
= true;
1133 return $close . "<dt>";
1135 $this->mDTopen
= false;
1136 return $close . "<dd>";
1139 return "<!-- ERR 2 -->";
1142 /* private */function closeList( $char )
1144 if ( "*" == $char ) { $text = "</li></ul>"; }
1145 else if ( "#" == $char ) { $text = "</li></ol>"; }
1146 else if ( ":" == $char ) {
1147 if ( $this->mDTopen
) {
1148 $this->mDTopen
= false;
1149 $text = "</dt></dl>";
1151 $text = "</dd></dl>";
1154 else { return "<!-- ERR 3 -->"; }
1158 /* private */ function doBlockLevels( $text, $linestart )
1160 $fname = "OutputPage::doBlockLevels";
1161 wfProfileIn( $fname );
1162 # Parsing through the text line by line. The main thing
1163 # happening here is handling of block-level elements p, pre,
1164 # and making lists from lines starting with * # : etc.
1166 $a = explode( "\n", $text );
1167 $text = $lastPref = "";
1168 $this->mDTopen
= $inBlockElem = false;
1170 if ( ! $linestart ) { $text .= array_shift( $a ); }
1171 foreach ( $a as $t ) {
1172 if ( "" != $text ) { $text .= "\n"; }
1175 $opl = strlen( $lastPref );
1176 $npl = strspn( $t, "*#:;" );
1177 $pref = substr( $t, 0, $npl );
1178 $pref2 = str_replace( ";", ":", $pref );
1179 $t = substr( $t, $npl );
1181 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
1182 $text .= $this->nextItem( substr( $pref, -1 ) );
1184 if ( ";" == substr( $pref, -1 ) ) {
1185 $cpos = strpos( $t, ":" );
1186 if ( ! ( false === $cpos ) ) {
1187 $term = substr( $t, 0, $cpos );
1188 $text .= $term . $this->nextItem( ":" );
1189 $t = substr( $t, $cpos +
1 );
1192 } else if (0 != $npl ||
0 != $opl) {
1193 $cpl = $this->getCommon( $pref, $lastPref );
1195 while ( $cpl < $opl ) {
1196 $text .= $this->closeList( $lastPref{$opl-1} );
1199 if ( $npl <= $cpl && $cpl > 0 ) {
1200 $text .= $this->nextItem( $pref{$cpl-1} );
1202 while ( $npl > $cpl ) {
1203 $char = substr( $pref, $cpl, 1 );
1204 $text .= $this->openList( $char );
1206 if ( ";" == $char ) {
1207 $cpos = strpos( $t, ":" );
1208 if ( ! ( false === $cpos ) ) {
1209 $term = substr( $t, 0, $cpos );
1210 $text .= $term . $this->nextItem( ":" );
1211 $t = substr( $t, $cpos +
1 );
1218 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1220 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1221 $text .= $this->closeParagraph();
1222 $inBlockElem = true;
1224 if ( ! $inBlockElem ) {
1225 if ( " " == $t{0} ) {
1226 $newSection = "pre";
1227 # $t = wfEscapeHTML( $t );
1229 else { $newSection = "p"; }
1231 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1232 $text .= $this->closeParagraph();
1233 $text .= "<" . $newSection . ">";
1234 } else if ( 0 != strcmp( $this->mLastSection
,
1236 $text .= $this->closeParagraph();
1237 if ( 0 != strcmp( "p", $newSection ) ) {
1238 $text .= "<" . $newSection . ">";
1241 $this->mLastSection
= $newSection;
1243 if ( $inBlockElem &&
1244 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1245 $inBlockElem = false;
1251 $text .= $this->closeList( $pref2{$npl-1} );
1254 if ( "" != $this->mLastSection
) {
1255 if ( "p" != $this->mLastSection
) {
1256 $text .= "</" . $this->mLastSection
. ">";
1258 $this->mLastSection
= "";
1260 wfProfileOut( $fname );
1264 /* private */ function replaceVariables( $text )
1266 global $wgLang, $wgCurOut;
1267 $fname = "OutputPage::replaceVariables";
1268 wfProfileIn( $fname );
1273 # See Language.php for the definition of each magic word
1274 # As with sigs, this uses the server's local time -- ensure
1275 # this is appropriate for your audience!
1277 $magic[MAG_CURRENTMONTH
] = date( "m" );
1278 $magic[MAG_CURRENTMONTHNAME
] = $wgLang->getMonthName( date("n") );
1279 $magic[MAG_CURRENTMONTHNAMEGEN
] = $wgLang->getMonthNameGen( date("n") );
1280 $magic[MAG_CURRENTDAY
] = date("j");
1281 $magic[MAG_CURRENTDAYNAME
] = $wgLang->getWeekdayName( date("w")+
1 );
1282 $magic[MAG_CURRENTYEAR
] = date( "Y" );
1283 $magic[MAG_CURRENTTIME
] = $wgLang->time( wfTimestampNow(), false );
1285 $this->mContainsOldMagic +
= MagicWord
::replaceMultiple($magic, $text, $text);
1287 $mw =& MagicWord
::get( MAG_NUMBEROFARTICLES
);
1288 if ( $mw->match( $text ) ) {
1289 $v = wfNumberOfArticles();
1290 $text = $mw->replace( $v, $text );
1291 if( $mw->getWasModified() ) { $this->mContainsOldMagic++
; }
1294 # "Variables" with an additional parameter e.g. {{MSG:wikipedia}}
1295 # The callbacks are at the bottom of this file
1297 $mw =& MagicWord
::get( MAG_MSG
);
1298 $text = $mw->substituteCallback( $text, "wfReplaceMsgVar" );
1299 if( $mw->getWasModified() ) { $this->mContainsNewMagic++
; }
1301 $mw =& MagicWord
::get( MAG_MSGNW
);
1302 $text = $mw->substituteCallback( $text, "wfReplaceMsgnwVar" );
1303 if( $mw->getWasModified() ) { $this->mContainsNewMagic++
; }
1305 wfProfileOut( $fname );
1309 # Cleans up HTML, removes dangerous tags and attributes
1310 /* private */ function removeHTMLtags( $text )
1312 $fname = "OutputPage::removeHTMLtags";
1313 wfProfileIn( $fname );
1314 $htmlpairs = array( # Tags that must be closed
1315 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1316 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1317 "strike", "strong", "tt", "var", "div", "center",
1318 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1319 "ruby", "rt" , "rb" , "rp"
1321 $htmlsingle = array(
1322 "br", "p", "hr", "li", "dt", "dd"
1324 $htmlnest = array( # Tags that can be nested--??
1325 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1326 "dl", "font", "big", "small", "sub", "sup"
1328 $tabletags = array( # Can only appear inside table
1332 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1333 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1335 $htmlattrs = $this->getHTMLattrs () ;
1337 # Remove HTML comments
1338 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1340 $bits = explode( "<", $text );
1341 $text = array_shift( $bits );
1342 $tagstack = array(); $tablestack = array();
1344 foreach ( $bits as $x ) {
1345 $prev = error_reporting( E_ALL
& ~
( E_NOTICE | E_WARNING
) );
1346 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1348 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1349 error_reporting( $prev );
1352 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1356 if ( ! in_array( $t, $htmlsingle ) &&
1357 ( $ot = array_pop( $tagstack ) ) != $t ) {
1358 array_push( $tagstack, $ot );
1361 if ( $t == "table" ) {
1362 $tagstack = array_pop( $tablestack );
1367 # Keep track for later
1368 if ( in_array( $t, $tabletags ) &&
1369 ! in_array( "table", $tagstack ) ) {
1371 } else if ( in_array( $t, $tagstack ) &&
1372 ! in_array ( $t , $htmlnest ) ) {
1374 } else if ( ! in_array( $t, $htmlsingle ) ) {
1375 if ( $t == "table" ) {
1376 array_push( $tablestack, $tagstack );
1377 $tagstack = array();
1379 array_push( $tagstack, $t );
1381 # Strip non-approved attributes from the tag
1382 $newparams = $this->fixTagAttributes($params);
1386 $rest = str_replace( ">", ">", $rest );
1387 $text .= "<$slash$t $newparams$brace$rest";
1391 $text .= "<" . str_replace( ">", ">", $x);
1393 # Close off any remaining tags
1394 while ( $t = array_pop( $tagstack ) ) {
1396 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1398 wfProfileOut( $fname );
1404 * This function accomplishes several tasks:
1405 * 1) Auto-number headings if that option is enabled
1406 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1407 * 3) Add a Table of contents on the top for users who have enabled the option
1408 * 4) Auto-anchor headings
1410 * It loops through all headlines, collects the necessary data, then splits up the
1411 * string and re-inserts the newly formatted headlines.
1414 /* private */ function formatHeadings( $text )
1416 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1417 $nh=$wgUser->getOption( "numberheadings" );
1418 $st=$wgUser->getOption( "showtoc" );
1419 if(!$wgTitle->userCanEdit()) {
1423 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1424 $esr=$wgUser->getID() && $wgUser->getOption( "editsectiononrightclick" );
1427 # Inhibit editsection links if requested in the page
1428 $esw =& MagicWord
::get( MAG_NOEDITSECTION
);
1429 if ($esw->matchAndRemove( $text )) {
1432 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
1434 $mw =& MagicWord
::get( MAG_NOTOC
);
1435 if ($mw->matchAndRemove( $text ))
1440 # never add the TOC to the Main Page. This is an entry page that should not
1441 # be more than 1-2 screens large anyway
1442 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1444 # We need this to perform operations on the HTML
1445 $sk=$wgUser->getSkin();
1447 # Get all headlines for numbering them and adding funky stuff like [edit]
1449 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1454 # Ugh .. the TOC should have neat indentation levels which can be
1455 # passed to the skin functions. These are determined here
1456 foreach($matches[3] as $headline) {
1457 if($level) { $prevlevel=$level;}
1458 $level=$matches[1][$c];
1459 if(($nh||
$st) && $prevlevel && $level>$prevlevel) {
1461 $h[$level]=0; // reset when we enter a new level
1462 $toc.=$sk->tocIndent($level-$prevlevel);
1463 $toclevel+
=$level-$prevlevel;
1466 if(($nh||
$st) && $level<$prevlevel) {
1467 $h[$level+
1]=0; // reset when we step back a level
1468 $toc.=$sk->tocUnindent($prevlevel-$level);
1469 $toclevel-=$prevlevel-$level;
1472 $h[$level]++
; // count number of headlines for each level
1475 for($i=1;$i<=$level;$i++
) {
1477 if($dot) {$numbering.=".";}
1484 // The canonized header is a version of the header text safe to use for links
1486 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1487 $tocline = trim( $canonized_headline );
1488 $canonized_headline=str_replace('"',"",$canonized_headline);
1489 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1490 $refer[$c]=$canonized_headline;
1491 $refers[$canonized_headline]++
; // count how many in assoc. array so we can track dupes in anchors
1492 $refcount[$c]=$refers[$canonized_headline];
1494 // Prepend the number to the heading text
1497 $tocline=$numbering ." ". $tocline;
1499 // Don't number the heading if it is the only one (looks silly)
1500 if($nh && count($matches[3]) > 1) {
1501 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1505 // Create the anchor for linking from the TOC to the section
1507 $anchor=$canonized_headline;
1508 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1510 $toc.=$sk->tocLine($anchor,$tocline,$toclevel);
1512 if($es && !isset($wpPreview)) {
1513 $head[$c].=$sk->editSectionLink($c+
1);
1516 // Put it all together
1518 $head[$c].="<h".$level.$matches[2][$c]
1519 ."<a name=\"".$anchor."\">"
1524 // Add the edit section link
1526 if($esr && !isset($wpPreview)) {
1527 $head[$c]=$sk->editSectionScript($c+
1,$head[$c]);
1537 $toc.=$sk->tocUnindent($toclevel);
1538 $toc=$sk->tocTable($toc);
1541 // split up and insert constructed headlines
1543 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1546 foreach($blocks as $block) {
1547 if(($es) && !isset($wpPreview) && $c>0 && $i==0) {
1548 # This is the [edit] link that appears for the top block of text when
1549 # section editing is enabled
1550 $full.=$sk->editSectionLink(0);
1553 if($st && $toclines>3 && !$i) {
1554 # Let's add a top anchor just in case we want to link to the top of the page
1555 $full="<a name=\"top\"></a>".$full.$toc;
1565 /* private */ function magicISBN( $text )
1569 $a = split( "ISBN ", " $text" );
1570 if ( count ( $a ) < 2 ) return $text;
1571 $text = substr( array_shift( $a ), 1);
1572 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1574 foreach ( $a as $x ) {
1575 $isbn = $blank = "" ;
1576 while ( " " == $x{0} ) {
1578 $x = substr( $x, 1 );
1580 while ( strstr( $valid, $x{0} ) != false ) {
1582 $x = substr( $x, 1 );
1584 $num = str_replace( "-", "", $isbn );
1585 $num = str_replace( " ", "", $num );
1588 $text .= "ISBN $blank$x";
1590 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1591 "Booksources"), "isbn={$num}" ) . "\" class=\"internal\">ISBN $isbn</a>";
1598 /* private */ function magicRFC( $text )
1603 /* private */ function headElement()
1605 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding, $wgLang;
1607 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
1609 if ( "" == $this->mHTMLtitle
) {
1610 $this->mHTMLtitle
= $this->mPagetitle
;
1612 $rtl = $wgLang->isRTL() ?
" dir='RTL'" : "";
1613 $ret .= "<html lang=\"$wgLanguageCode\"$rtl><head><title>{$this->mHTMLtitle}</title>\n";
1614 array_push( $this->mMetatags
, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1615 foreach ( $this->mMetatags
as $tag ) {
1616 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1618 $tag[0] = substr( $tag[0], 5 );
1622 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1624 $p = $this->mRobotpolicy
;
1625 if ( "" == $p ) { $p = "index,follow"; }
1626 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1628 if ( count( $this->mKeywords
) > 0 ) {
1629 $ret .= "<meta name=\"keywords\" content=\"" .
1630 implode( ",", $this->mKeywords
) . "\">\n";
1632 foreach ( $this->mLinktags
as $tag ) {
1634 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1635 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1636 $ret .= "href=\"{$tag[2]}\">\n";
1638 $sk = $wgUser->getSkin();
1639 $ret .= $sk->getHeadScripts();
1640 $ret .= $sk->getUserStyles();
1642 $ret .= "</head>\n";
1646 /* private */ function fillFromParserCache(){
1647 global $wgUser, $wgArticle;
1648 $hash = $wgUser->getPageRenderingHash();
1649 $pageid = intval( $wgArticle->getID() );
1650 $res = wfQuery("SELECT pc_data FROM parsercache WHERE pc_pageid = {$pageid} ".
1651 " AND pc_prefhash = '{$hash}' AND pc_expire > NOW()", DB_WRITE
);
1652 $row = wfFetchObject ( $res );
1654 $data = unserialize( gzuncompress($row->pc_data
) );
1655 $this->addHTML( $data['html'] );
1656 $this->mLanguageLinks
= $data['mLanguageLinks'];
1657 $this->mCategoryLinks
= $data['mCategoryLinks'];
1658 wfProfileOut( $fname );
1665 /* private */ function saveParserCache( $text ){
1666 global $wgUser, $wgArticle;
1667 $hash = $wgUser->getPageRenderingHash();
1668 $pageid = intval( $wgArticle->getID() );
1669 $title = wfStrencode( $wgArticle->mTitle
->getPrefixedDBKey() );
1671 $data['html'] = $text;
1672 $data['mLanguageLinks'] = $this->mLanguageLinks
;
1673 $data['mCategoryLinks'] = $this->mCategoryLinks
;
1674 $ser = addslashes( gzcompress( serialize( $data ) ) );
1675 if( $this->mContainsOldMagic
){
1677 } else if( $this->mContainsNewMagic
){
1683 wfQuery("REPLACE INTO parsercache (pc_prefhash,pc_pageid,pc_title,pc_data, pc_expire) ".
1684 "VALUES('{$hash}', {$pageid}, '{$title}', '{$ser}', ".
1685 "DATE_ADD(NOW(), INTERVAL {$expire}))", DB_WRITE
);
1687 if( rand() %
50 == 0 ){ // more efficient to just do it sometimes
1688 $this->purgeParserCache();
1692 /* static private */ function purgeParserCache(){
1693 wfQuery("DELETE FROM parsercache WHERE pc_expire < NOW() LIMIT 250", DB_WRITE
);
1696 /* static */ function parsercacheClearLinksTo( $pid ){
1697 $pid = intval( $pid );
1698 wfQuery("DELETE parsercache FROM parsercache,links ".
1699 "WHERE pc_title=links.l_from AND l_to={$pid}", DB_WRITE
);
1700 wfQuery("DELETE FROM parsercache WHERE pc_pageid='{$pid}'", DB_WRITE
);
1703 # $title is a prefixed db title, for example like Title->getPrefixedDBkey() returns.
1704 /* static */ function parsercacheClearBrokenLinksTo( $title ){
1705 $title = wfStrencode( $title );
1706 wfQuery("DELETE parsercache FROM parsercache,brokenlinks ".
1707 "WHERE pc_pageid=bl_from AND bl_to='{$title}'", DB_WRITE
);
1711 /* static */ function parsercacheClearPage( $pid ){
1712 $pid = intval( $pid );
1713 wfQuery("DELETE FROM parsercache WHERE pc_pageid='{$pid}'", DB_WRITE
);
1717 # Regex callbacks, used in OutputPage::replaceVariables
1719 # Just get rid of the dangerous stuff
1720 # Necessary because replaceVariables is called after removeHTMLtags,
1721 # and message text can come from any user
1722 function wfReplaceMsgVar( $matches ) {
1723 global $wgCurOut, $wgLinkCache;
1724 $text = $wgCurOut->removeHTMLtags( wfMsg( $matches[1] ) );
1725 $wgLinkCache->suspend();
1726 $text = $wgCurOut->replaceInternalLinks( $text );
1727 $wgLinkCache->resume();
1728 $wgLinkCache->addLinkObj( Title
::makeTitle( NS_MEDIAWIKI
, $matches[1] ) );
1732 # Effective <nowiki></nowiki>
1733 # Not real <nowiki> because this is called after nowiki sections are processed
1734 function wfReplaceMsgnwVar( $matches ) {
1735 global $wgCurOut, $wgLinkCache;
1736 $text = wfEscapeWikiText( wfMsg( $matches[1] ) );
1737 $wgLinkCache->addLinkObj( Title
::makeTitle( NS_MEDIAWIKI
, $matches[1] ) );