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 $lastmod = gmdate( "D, j M Y H:i:s", wfTimestamp2Unix(
68 max( $timestamp, $wgUser->mTouched
) ) ) . " GMT";
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 );
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->sendCacheControl();
83 wfDebug( "CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
87 wfDebug( "READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
88 $this->mLastModified
= $lastmod;
91 wfDebug( "We're confused.\n", false );
92 $this->mLastModified
= $lastmod;
96 function setRobotpolicy( $str ) { $this->mRobotpolicy
= $str; }
97 function setHTMLtitle( $name ) { $this->mHTMLtitle
= $name; }
98 function setPageTitle( $name ) { $this->mPagetitle
= $name; }
99 function getPageTitle() { return $this->mPagetitle
; }
100 function setSubtitle( $str ) { $this->mSubtitle
= $str; }
101 function getSubtitle() { return $this->mSubtitle
; }
102 function isArticle() { return $this->mIsarticle
; }
103 function setPrintable() { $this->mPrintable
= true; }
104 function isPrintable() { return $this->mPrintable
; }
105 function setOnloadHandler( $js ) { $this->mOnloadHandler
= $js; }
106 function getOnloadHandler() { return $this->mOnloadHandler
; }
107 function disable() { $this->mDoNothing
= true; }
109 function setArticleRelated( $v )
111 $this->mIsArticleRelated
= $v;
113 $this->mIsarticle
= false;
116 function setArticleFlag( $v ) {
117 $this->mIsarticle
= $v;
119 $this->mIsArticleRelated
= $v;
123 function isArticleRelated()
125 return $this->mIsArticleRelated
;
128 function getLanguageLinks() {
129 global $wgTitle, $wgLanguageCode;
130 global $wgDBconnection, $wgDBname;
131 return $this->mLanguageLinks
;
133 function supressQuickbar() { $this->mSupressQuickbar
= true; }
134 function isQuickbarSupressed() { return $this->mSupressQuickbar
; }
136 function addHTML( $text ) { $this->mBodytext
.= $text; }
137 function addHeadtext( $text ) { $this->mHeadtext
.= $text; }
138 function debug( $text ) { $this->mDebugtext
.= $text; }
140 # First pass--just handle <nowiki> sections, pass the rest off
141 # to doWikiPass2() which does all the real work.
143 function addWikiText( $text, $linestart = true )
145 global $wgUseTeX, $wgArticle, $wgUser, $action;
146 $fname = "OutputPage::addWikiText";
147 wfProfileIn( $fname );
148 $unique = "3iyZiyA7iMwg5rhxP0Dcc9oTnj8qD1jm1Sfv4";
149 $unique2 = "4LIQ9nXtiYFPCSfitVwDw7EYwQlL4GeeQ7qSO";
150 $unique3 = "fPaA8gDfdLBqzj68Yjg9Hil3qEF8JGO0uszIp";
161 # Replace any instances of the placeholders
162 $text = str_replace( $unique, wfHtmlEscapeFirst( $unique ), $text );
163 $text = str_replace( $unique2, wfHtmlEscapeFirst( $unique2 ), $text );
164 $text = str_replace( $unique3, wfHtmlEscapeFirst( $unique3 ), $text );
166 global $wgEnableParserCache;
168 $wgEnableParserCache && $action == "view" &&
169 intval($wgUser->getOption( "stubthreshold" )) == 0 &&
170 isset($wgArticle) && $wgArticle->getID() > 0;
172 if( $use_parser_cache ){
173 if( $this->fillFromParserCache() ){
174 wfProfileOut( $fname );
179 while ( "" != $text ) {
180 $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 );
182 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $text = ""; }
184 $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
186 $nwlist[$nwsecs] = wfEscapeHTMLTagsOnly($q[0]);
187 $stripped .= $unique . $nwsecs . "s";
193 while ( "" != $stripped ) {
194 $p = preg_split( "/<\\s*math\\s*>/i", $stripped, 2 );
196 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $stripped = ""; }
198 $q = preg_split( "/<\\/\\s*math\\s*>/i", $p[1], 2 );
200 $mathlist[$mathsecs] = renderMath($q[0]);
201 $stripped2 .= $unique2 . $mathsecs . "s";
206 $stripped2 = $stripped;
209 while ( "" != $stripped2 ) {
210 $p = preg_split( "/<\\s*pre\\s*>/i", $stripped2, 2 );
212 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $stripped2 = ""; }
214 $q = preg_split( "/<\\/\\s*pre\\s*>/i", $p[1], 2 );
216 $prelist[$presecs] = "<pre>". wfEscapeHTMLTagsOnly($q[0]). "</pre>\n";
217 $stripped3 .= $unique3 . $presecs . "s";
222 $text = $this->doWikiPass2( $stripped3, $linestart );
224 $specialChars = array("\\", "$");
225 $escapedChars = array("\\\\", "\\$");
226 for ( $i = 1; $i <= $presecs; ++
$i ) {
227 $text = preg_replace( "/{$unique3}{$i}s/", str_replace( $specialChars,
228 $escapedChars, $prelist[$i] ), $text );
231 for ( $i = 1; $i <= $mathsecs; ++
$i ) {
232 $text = preg_replace( "/{$unique2}{$i}s/", str_replace( $specialChars,
233 $escapedChars, $mathlist[$i] ), $text );
236 for ( $i = 1; $i <= $nwsecs; ++
$i ) {
237 $text = preg_replace( "/{$unique}{$i}s/", str_replace( $specialChars,
238 $escapedChars, $nwlist[$i] ), $text );
240 $this->addHTML( $text );
242 if($use_parser_cache ){
243 $this->saveParserCache( $text );
245 wfProfileOut( $fname );
248 function sendCacheControl() {
249 global $wgUseSquid, $wgUseESI;
250 # FIXME: This header may cause trouble with some versions of Internet Explorer
251 header( "Vary: Accept-Encoding, Cookie" );
252 if( $this->mLastModified
!= "" ) {
253 if( $wgUseSquid && ! isset( $_COOKIE[ini_get( "session.name") ] ) ) {
255 # We'll purge the proxy cache explicitly, but require end user agents
256 # to revalidate against the proxy on each visit.
257 # Surrogate-Control controls our Squid, Cache-Control downstream caches
258 wfDebug( "** proxy caching with ESI; {$this->mLastModified} **\n", false );
259 # start with a shorter timeout for initial testing
260 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
261 header( 'Surrogate-Control: max-age=18000+18000, content="ESI/1.0"');
262 header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
264 # We'll purge the proxy cache for anons explicitly, but require end user agents
265 # to revalidate against the proxy on each visit.
266 # The Squid need to replace the Cache-Control header with
267 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
268 wfDebug( "** local proxy caching; {$this->mLastModified} **\n", false );
269 # start with a shorter timeout for initial testing
270 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
271 header( "Cache-Control: s-maxage=18000, must-revalidate, max-age=0" );
274 # We do want clients to cache if they can, but they *must* check for updates
275 # on revisiting the page.
276 wfDebug( "** private caching; {$this->mLastModified} **\n", false );
277 header( "Expires: -1" );
278 header( "Cache-Control: private, must-revalidate, max-age=0" );
280 header( "Last-modified: {$this->mLastModified}" );
282 wfDebug( "** no caching **\n", false );
283 header( "Expires: -1" );
284 header( "Cache-Control: no-cache" );
285 header( "Pragma: no-cache" );
286 header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" );
290 # Finally, all the text has been munged and accumulated into
291 # the object, let's actually output it:
295 global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
296 global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
297 if( $this->mDoNothing
){
300 $fname = "OutputPage::output";
301 wfProfileIn( $fname );
303 $sk = $wgUser->getSkin();
305 $this->sendCacheControl();
307 header( "Content-type: text/html; charset={$wgOutputEncoding}" );
308 header( "Content-language: {$wgLanguageCode}" );
310 if ( "" != $this->mRedirect
) {
311 if( substr( $this->mRedirect
, 0, 4 ) != "http" ) {
312 # Standards require redirect URLs to be absolute
314 $this->mRedirect
= $wgServer . $this->mRedirect
;
316 header( "Location: {$this->mRedirect}" );
320 $exp = time() +
$wgCookieExpiration;
321 foreach( $this->mCookies
as $name => $val ) {
322 setcookie( $name, $val, $exp, "/" );
325 $sk->outputPage( $this );
331 global $wgInputEncoding, $wgOutputEncoding, $wgLang;
332 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
335 $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
336 if ( false === $outs ) { $outs = $ins; }
341 function setEncodings()
343 global $wgInputEncoding, $wgOutputEncoding;
344 global $wgUser, $wgLang;
346 $wgInputEncoding = strtolower( $wgInputEncoding );
348 if( $wgUser->getOption( 'altencoding' ) ) {
349 $wgLang->setAltEncoding();
353 if ( empty( $_SERVER['HTTP_ACCEPT_CHARSET'] ) ) {
354 $wgOutputEncoding = strtolower( $wgOutputEncoding );
359 # This code is unused anyway!
360 # Commenting out. --bv 2003-11-15
362 $a = explode( ",", $_SERVER['HTTP_ACCEPT_CHARSET'] );
366 foreach ( $a as $s ) {
367 if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) {
379 #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; }
380 if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; }
381 $wgOutputEncoding = strtolower( $bestset );
386 $wgOutputEncoding = $wgInputEncoding;
389 # Returns a HTML comment with the elapsed time since request.
390 # This method has no side effects.
391 function reportTime()
393 global $wgRequestTime;
395 list( $usec, $sec ) = explode( " ", microtime() );
396 $now = (float)$sec +
(float)$usec;
398 list( $usec, $sec ) = explode( " ", $wgRequestTime );
399 $start = (float)$sec +
(float)$usec;
400 $elapsed = $now - $start;
401 $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
406 # Note: these arguments are keys into wfMsg(), not text!
408 function errorpage( $title, $msg )
412 $this->mDebugtext
.= "Original title: " .
413 $wgTitle->getPrefixedText() . "\n";
414 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
415 $this->setPageTitle( wfMsg( $title ) );
416 $this->setRobotpolicy( "noindex,nofollow" );
417 $this->setArticleRelated( false );
419 $this->mBodytext
= "";
420 $this->addHTML( "<p>" . wfMsg( $msg ) . "\n" );
421 $this->returnToMain( false );
427 function sysopRequired()
431 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
432 $this->setPageTitle( wfMsg( "sysoptitle" ) );
433 $this->setRobotpolicy( "noindex,nofollow" );
434 $this->setArticleRelated( false );
435 $this->mBodytext
= "";
437 $sk = $wgUser->getSkin();
438 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
439 $this->addHTML( wfMsg( "sysoptext", $ap ) );
440 $this->returnToMain();
443 function developerRequired()
447 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
448 $this->setPageTitle( wfMsg( "developertitle" ) );
449 $this->setRobotpolicy( "noindex,nofollow" );
450 $this->setArticleRelated( false );
451 $this->mBodytext
= "";
453 $sk = $wgUser->getSkin();
454 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
455 $this->addHTML( wfMsg( "developertext", $ap ) );
456 $this->returnToMain();
459 function databaseError( $fname )
461 global $wgUser, $wgCommandLineMode;
463 $this->setPageTitle( wfMsgNoDB( "databaseerror" ) );
464 $this->setRobotpolicy( "noindex,nofollow" );
465 $this->setArticleRelated( false );
467 if ( $wgCommandLineMode ) {
468 $msg = wfMsgNoDB( "dberrortextcl" );
470 $msg = wfMsgNoDB( "dberrortext" );
473 $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), $msg );
474 $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
475 $msg = str_replace( "$3", wfLastErrno(), $msg );
476 $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg );
478 if ( $wgCommandLineMode ||
!is_object( $wgUser )) {
482 $sk = $wgUser->getSkin();
483 $shlink = $sk->makeKnownLink( wfMsgNoDB( "searchhelppage" ),
484 wfMsgNoDB( "searchingwikipedia" ) );
485 $msg = str_replace( "$5", $shlink, $msg );
486 $this->mBodytext
= $msg;
491 function readOnlyPage( $source = "", $protected = false )
493 global $wgUser, $wgReadOnlyFile;
495 $this->setRobotpolicy( "noindex,nofollow" );
496 $this->setArticleRelated( false );
499 $this->setPageTitle( wfMsg( "viewsource" ) );
500 $this->addWikiText( wfMsg( "protectedtext" ) );
502 $this->setPageTitle( wfMsg( "readonly" ) );
503 $reason = file_get_contents( $wgReadOnlyFile );
504 $this->addHTML( wfMsg( "readonlytext", $reason ) );
508 $rows = $wgUser->getOption( "rows" );
509 $cols = $wgUser->getOption( "cols" );
510 $text .= "</p>\n<textarea cols='$cols' rows='$rows' readonly>" .
511 htmlspecialchars( $source ) . "\n</textarea>";
512 $this->addHTML( $text );
515 $this->returnToMain( false );
518 function fatalError( $message )
520 $this->setPageTitle( wfMsg( "internalerror" ) );
521 $this->setRobotpolicy( "noindex,nofollow" );
522 $this->setArticleRelated( false );
524 $this->mBodytext
= $message;
529 function unexpectedValueError( $name, $val )
531 $this->fatalError( wfMsg( "unexpected", $name, $val ) );
534 function fileCopyError( $old, $new )
536 $this->fatalError( wfMsg( "filecopyerror", $old, $new ) );
539 function fileRenameError( $old, $new )
541 $this->fatalError( wfMsg( "filerenameerror", $old, $new ) );
544 function fileDeleteError( $name )
546 $this->fatalError( wfMsg( "filedeleteerror", $name ) );
549 function fileNotFoundError( $name )
551 $this->fatalError( wfMsg( "filenotfound", $name ) );
554 function returnToMain( $auto = true )
556 global $wgUser, $wgOut, $returnto;
558 $sk = $wgUser->getSkin();
559 if ( "" == $returnto ) {
560 $returnto = wfMsg( "mainpage" );
562 $link = $sk->makeKnownLink( $returnto, "" );
564 $r = wfMsg( "returnto", $link );
566 $wgOut->addMeta( "http:Refresh", "10;url=" .
567 wfLocalUrlE( wfUrlencode( $returnto ) ) );
569 $wgOut->addHTML( "\n<p>$r\n" );
573 function categoryMagic ()
575 global $wgTitle , $wgUseCategoryMagic ;
576 if ( !isset ( $wgUseCategoryMagic ) ||
!$wgUseCategoryMagic ) return ;
577 $id = $wgTitle->getArticleID() ;
578 $cat = ucfirst ( wfMsg ( "category" ) ) ;
579 $ti = $wgTitle->getText() ;
580 $ti = explode ( ":" , $ti , 2 ) ;
581 if ( $cat != $ti[0] ) return "" ;
582 $r = "<br break=all>\n" ;
584 $articles = array() ;
585 $parents = array () ;
586 $children = array() ;
590 $sk = $wgUser->getSkin() ;
591 $sql = "SELECT l_from FROM links WHERE l_to={$id}" ;
592 $res = wfQuery ( $sql, DB_READ
) ;
593 while ( $x = wfFetchObject ( $res ) )
596 # $t->newFromDBkey ( $x->l_from ) ;
597 # $t = $t->getText() ;
599 $y = explode ( ":" , $t , 2 ) ;
600 if ( count ( $y ) == 2 && $y[0] == $cat ) {
601 array_push ( $children , $sk->makeLink ( $t , $y[1] ) ) ;
603 array_push ( $articles , $sk->makeLink ( $t ) ) ;
606 wfFreeResult ( $res ) ;
609 if ( count ( $children ) > 0 )
611 asort ( $children ) ;
612 $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
613 $r .= implode ( ", " , $children ) ;
617 if ( count ( $articles ) > 0 )
619 asort ( $articles ) ;
620 $h = wfMsg( "category_header", $ti[1] );
621 $r .= "<h2>{$h}</h2>\n" ;
622 $r .= implode ( ", " , $articles ) ;
629 function getHTMLattrs ()
631 $htmlattrs = array( # Allowed attributes--no scripting, etc.
632 "title", "align", "lang", "dir", "width", "height",
633 "bgcolor", "clear", /* BR */ "noshade", /* HR */
634 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
635 /* FONT */ "type", "start", "value", "compact",
636 /* For various lists, mostly deprecated but safe */
637 "summary", "width", "border", "frame", "rules",
638 "cellspacing", "cellpadding", "valign", "char",
639 "charoff", "colgroup", "col", "span", "abbr", "axis",
640 "headers", "scope", "rowspan", "colspan", /* Tables */
641 "id", "class", "name", "style" /* For CSS */
646 function fixTagAttributes ( $t )
648 if ( trim ( $t ) == "" ) return "" ; # Saves runtime ;-)
649 $htmlattrs = $this->getHTMLattrs() ;
651 # Strip non-approved attributes from the tag
653 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
654 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
656 # Strip javascript "expression" from stylesheets. Brute force approach:
657 # If anythin offensive is found, all attributes of the HTML tag are dropped
660 "/style\\s*=.*(expression|tps*:\/\/|url\\s*\().*/is",
661 wfMungeToUtf8( $t ) ) )
669 function doTableStuff ( $t )
671 $t = explode ( "\n" , $t ) ;
672 $td = array () ; # Is currently a td tag open?
673 $ltd = array () ; # Was it TD or TH?
674 $tr = array () ; # Is currently a tr tag open?
675 $ltr = array () ; # tr attributes
676 foreach ( $t AS $k => $x )
679 $fc = substr ( $x , 0 , 1 ) ;
680 if ( "{|" == substr ( $x , 0 , 2 ) )
682 $t[$k] = "<table " . $this->fixTagAttributes ( substr ( $x , 3 ) ) . ">" ;
683 array_push ( $td , false ) ;
684 array_push ( $ltd , "" ) ;
685 array_push ( $tr , false ) ;
686 array_push ( $ltr , "" ) ;
688 else if ( count ( $td ) == 0 ) { } # Don't do any of the following
689 else if ( "|}" == substr ( $x , 0 , 2 ) )
692 $l = array_pop ( $ltd ) ;
693 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
694 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
698 /* else if ( "|_" == substr ( $x , 0 , 2 ) ) # Caption
700 $z = trim ( substr ( $x , 2 ) ) ;
701 $t[$k] = "<caption>{$z}</caption>\n" ;
703 else if ( "|-" == substr ( $x , 0 , 2 ) ) # Allows for |---------------
705 $x = substr ( $x , 1 ) ;
706 while ( $x != "" && substr ( $x , 0 , 1 ) == '-' ) $x = substr ( $x , 1 ) ;
708 $l = array_pop ( $ltd ) ;
709 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
710 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
713 array_push ( $tr , false ) ;
714 array_push ( $td , false ) ;
715 array_push ( $ltd , "" ) ;
716 array_push ( $ltr , $this->fixTagAttributes ( $x ) ) ;
718 else if ( "|" == $fc ||
"!" == $fc ||
"|+" == substr ( $x , 0 , 2 ) ) # Caption
720 if ( "|+" == substr ( $x , 0 , 2 ) )
723 $x = substr ( $x , 1 ) ;
725 $after = substr ( $x , 1 ) ;
726 if ( $fc == "!" ) $after = str_replace ( "!!" , "||" , $after ) ;
727 $after = explode ( "||" , $after ) ;
729 foreach ( $after AS $theline )
732 $tra = array_pop ( $ltr ) ;
733 if ( !array_pop ( $tr ) ) $z = "<tr {$tra}>\n" ;
734 array_push ( $tr , true ) ;
735 array_push ( $ltr , "" ) ;
737 $l = array_pop ( $ltd ) ;
738 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
739 if ( $fc == "|" ) $l = "TD" ;
740 else if ( $fc == "!" ) $l = "TH" ;
741 else if ( $fc == "+" ) $l = "CAPTION" ;
743 array_push ( $ltd , $l ) ;
744 $y = explode ( "|" , $theline , 2 ) ;
745 if ( count ( $y ) == 1 ) $y = "{$z}<{$l}>{$y[0]}" ;
746 else $y = $y = "{$z}<{$l} ".$this->fixTagAttributes($y[0]).">{$y[1]}" ;
748 array_push ( $td , true ) ;
753 # Closing open td, tr && table
754 while ( count ( $td ) > 0 )
756 if ( array_pop ( $td ) ) $t[] = "</td>" ;
757 if ( array_pop ( $tr ) ) $t[] = "</tr>" ;
761 $t = implode ( "\n" , $t ) ;
762 # $t = $this->removeHTMLtags( $t );
766 # Well, OK, it's actually about 14 passes. But since all the
767 # hard lifting is done inside PHP's regex code, it probably
768 # wouldn't speed things up much to add a real parser.
770 function doWikiPass2( $text, $linestart )
772 global $wgUser, $wgLang, $wgUseDynamicDates;
773 $fname = "OutputPage::doWikiPass2";
774 wfProfileIn( $fname );
776 $text = $this->removeHTMLtags( $text );
777 $text = $this->replaceVariables( $text );
779 $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
780 $text = str_replace ( "<HR>", "<hr>", $text );
782 $text = $this->doAllQuotes( $text );
783 $text = $this->doHeadings( $text );
784 $text = $this->doBlockLevels( $text, $linestart );
786 if($wgUseDynamicDates) {
787 global $wgDateFormatter;
788 $text = $wgDateFormatter->reformat( $wgUser->getOption("date"), $text );
791 $text = $this->replaceExternalLinks( $text );
792 $text = $this->replaceInternalLinks ( $text );
793 $text = $this->doTableStuff ( $text ) ;
795 $text = $this->magicISBN( $text );
796 $text = $this->magicRFC( $text );
797 $text = $this->formatHeadings( $text );
799 $sk = $wgUser->getSkin();
800 $text = $sk->transformContent( $text );
801 $text .= $this->categoryMagic () ;
803 wfProfileOut( $fname );
807 /* private */ function doAllQuotes( $text )
810 $lines = explode( "\r\n", $text );
811 foreach ( $lines as $line ) {
812 $outtext .= $this->doQuotes ( "", $line, "" ) . "\r\n";
817 /* private */ function doQuotes( $pre, $text, $mode )
819 if ( preg_match( "/^(.*)''(.*)$/sU", $text, $m ) ) {
820 $m1_strong = ($m[1] == "") ?
"" : "<strong>{$m[1]}</strong>";
821 $m1_em = ($m[1] == "") ?
"" : "<em>{$m[1]}</em>";
822 if ( substr ($m[2], 0, 1) == "'" ) {
823 $m[2] = substr ($m[2], 1);
825 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ?
"both" : "emstrong" );
826 } else if ($mode == "strong") {
827 return $m1_strong . $this->doQuotes ( "", $m[2], "" );
828 } else if (($mode == "emstrong") ||
($mode == "both")) {
829 return $this->doQuotes ( "", $pre.$m1_strong.$m[2], "em" );
830 } else if ($mode == "strongem") {
831 return "<strong>{$pre}{$m1_em}</strong>" . $this->doQuotes ( "", $m[2], "em" );
833 return $m[1] . $this->doQuotes ( "", $m[2], "strong" );
836 if ($mode == "strong") {
837 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ?
"both" : "strongem" );
838 } else if ($mode == "em") {
839 return $m1_em . $this->doQuotes ( "", $m[2], "" );
840 } else if ($mode == "emstrong") {
841 return "<em>{$pre}{$m1_strong}</em>" . $this->doQuotes ( "", $m[2], "strong" );
842 } else if (($mode == "strongem") ||
($mode == "both")) {
843 return $this->doQuotes ( "", $pre.$m1_em.$m[2], "strong" );
845 return $m[1] . $this->doQuotes ( "", $m[2], "em" );
849 $text_strong = ($text == "") ?
"" : "<strong>{$text}</strong>";
850 $text_em = ($text == "") ?
"" : "<em>{$text}</em>";
853 } else if ($mode == "em") {
854 return $pre . $text_em;
855 } else if ($mode == "strong") {
856 return $pre . $text_strong;
857 } else if ($mode == "strongem") {
858 return (($pre == "") && ($text == "")) ?
"" : "<strong>{$pre}{$text_em}</strong>";
860 return (($pre == "") && ($text == "")) ?
"" : "<em>{$pre}{$text_strong}</em>";
865 /* private */ function doHeadings( $text )
867 for ( $i = 6; $i >= 1; --$i ) {
868 $h = substr( "======", 0, $i );
869 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
870 "<h{$i}>\\1</h{$i}>\\2", $text );
875 # Note: we have to do external links before the internal ones,
876 # and otherwise take great care in the order of things here, so
877 # that we don't end up interpreting some URLs twice.
879 /* private */ function replaceExternalLinks( $text )
881 $fname = "OutputPage::replaceExternalLinks";
882 wfProfileIn( $fname );
883 $text = $this->subReplaceExternalLinks( $text, "http", true );
884 $text = $this->subReplaceExternalLinks( $text, "https", true );
885 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
886 $text = $this->subReplaceExternalLinks( $text, "irc", false );
887 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
888 $text = $this->subReplaceExternalLinks( $text, "news", false );
889 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
890 wfProfileOut( $fname );
894 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
896 global $wgUser, $printable;
897 global $wgAllowExternalImages;
900 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
901 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
903 # this is the list of separators that should be ignored if they
904 # are the last character of an URL but that should be included
905 # if they occur within the URL, e.g. "go to www.foo.com, where .."
906 # in this case, the last comma should not become part of the URL,
907 # but in "www.foo.com/123,2342,32.htm" it should.
909 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
910 $images = "gif|png|jpg|jpeg";
912 # PLEASE NOTE: The curly braces { } are not part of the regex,
913 # they are interpreted as part of the string (used to tell PHP
914 # that the content of the string should be inserted there).
915 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
916 "((?i){$images})([^{$uc}]|$)/";
918 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
919 $sk = $wgUser->getSkin();
921 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
922 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
923 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
925 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
926 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
927 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
929 $s = str_replace( $unique, $protocol, $s );
931 $a = explode( "[{$protocol}:", " " . $s );
932 $s = array_shift( $a );
933 $s = substr( $s, 1 );
935 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
936 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
938 foreach ( $a as $line ) {
939 if ( preg_match( $e1, $line, $m ) ) {
940 $link = "{$protocol}:{$m[1]}";
942 if ( $autonumber ) { $text = "[" . ++
$this->mAutonumber
. "]"; }
943 else { $text = wfEscapeHTML( $link ); }
944 } else if ( preg_match( $e2, $line, $m ) ) {
945 $link = "{$protocol}:{$m[1]}";
949 $s .= "[{$protocol}:" . $line;
952 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
954 $la = $sk->getExternalLinkAttributes( $link, $text );
955 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
961 /* private */ function replaceInternalLinks( $s )
963 global $wgTitle, $wgUser, $wgLang;
964 global $wgLinkCache, $wgInterwikiMagic, $wgUseCategoryMagic;
965 global $wgNamespacesWithSubpages, $wgLanguageCode;
966 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
968 wfProfileIn( "$fname-setup" );
969 $tc = Title
::legalChars() . "#";
970 $sk = $wgUser->getSkin();
972 $a = explode( "[[", " " . $s );
973 $s = array_shift( $a );
974 $s = substr( $s, 1 );
976 $e1 = "/^([{$tc}]+)(?:\\|([^]]+))?]](.*)\$/sD";
978 # Special and Media are pseudo-namespaces; no pages actually exist in them
979 $image = Namespace::getImage();
980 $special = Namespace::getSpecial();
981 $media = Namespace::getMedia();
982 $nottalk = !Namespace::isTalk( $wgTitle->getNamespace() );
983 wfProfileOut( "$fname-setup" );
985 foreach ( $a as $line ) {
986 if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
989 } else { # Invalid form; output directly
996 :Foobar -- override special treatment of prefix (images, language links)
997 /Foobar -- convert to CurrentPage/Foobar
998 /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
1000 $c = substr($m[1],0,1);
1001 $noforce = ($c != ":");
1002 if( $c == "/" ) { # subpage
1003 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
1004 $m[1]=substr($m[1],1,strlen($m[1])-2);
1007 $noslash=substr($m[1],1);
1009 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
1010 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
1013 } # this might be changed for ugliness reasons
1015 $link = $noslash; # no subpage allowed, use standard link
1017 } elseif( $noforce ) { # no subpage
1020 $link = substr( $m[1], 1 );
1025 $nt = Title
::newFromText( $link );
1030 $ns = $nt->getNamespace();
1031 $iw = $nt->getInterWiki();
1033 if( $iw && $wgInterwikiMagic && $nottalk && $wgLang->getLanguageName( $iw ) ) {
1034 array_push( $this->mLanguageLinks
, $nt->getPrefixedText() );
1037 } else if ( "media" == $pre ) {
1038 $nt = Title::newFromText( $suf );
1039 $name = $nt->getDBkey();
1040 if ( "" == $text ) { $text = $nt->GetText(); }
1042 $wgLinkCache->addImageLink( $name );
1043 $s .= $sk->makeMediaLink( $name,
1044 wfImageUrl( $name ), $text );
1046 } else if ( isset($wgUseCategoryMagic) && $wgUseCategoryMagic && $pre == wfMsg ( "category" ) ) {
1047 $l = $sk->makeLink ( $pre.":".ucfirst( $m[2] ), ucfirst ( $m[2] ) ) ;
1048 array_push ( $this->mCategoryLinks , $l ) ;
1051 $l = $wgLang->getLanguageName( $pre );
1052 if ( "" == $l or !$wgInterwikiMagic or Namespace::isTalk( $wgTitle->getNamespace() ) ) {
1053 if ( "" == $text ) {
1056 $s .= $sk->makeLink( $link, $text, "", $trail );
1057 } else if ( $pre != $wgLanguageCode ) {
1058 array_push( $this->mLanguageLinks, "$pre:$suf" );
1064 if( $ns == $image ) {
1065 $s .= $sk->makeImageLinkObj( $nt, $text ) . $trail;
1066 $wgLinkCache->addImageLinkObj( $nt );
1070 # } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
1071 # $link = substr( $link, 2 );
1072 # $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
1074 if ( "" == $text ) { $text = $link; }
1076 $s .= $sk->makeLink( $link, $text, "", $trail );
1079 if( $ns == $media ) {
1080 $s .= $sk->makeMediaLinkObj( $nt, $text ) . $trail;
1081 $wgLinkCache->addImageLinkObj( $nt );
1083 } elseif( $ns == $special ) {
1084 $s .= $sk->makeKnownLinkObj( $nt, $text, "", $trail );
1087 $s .= $sk->makeLinkObj( $nt, $text, "", $trail );
1089 wfProfileOut( $fname );
1093 # Some functions here used by doBlockLevels()
1095 /* private */ function closeParagraph()
1098 if ( 0 != strcmp( "p", $this->mLastSection
) &&
1099 0 != strcmp( "", $this->mLastSection
) ) {
1100 $result = "</" . $this->mLastSection
. ">";
1102 $this->mLastSection
= "";
1103 return $result."\n";
1105 # getCommon() returns the length of the longest common substring
1106 # of both arguments, starting at the beginning of both.
1108 /* private */ function getCommon( $st1, $st2 )
1110 $fl = strlen( $st1 );
1111 $shorter = strlen( $st2 );
1112 if ( $fl < $shorter ) { $shorter = $fl; }
1114 for ( $i = 0; $i < $shorter; ++
$i ) {
1115 if ( $st1{$i} != $st2{$i} ) { break; }
1119 # These next three functions open, continue, and close the list
1120 # element appropriate to the prefix character passed into them.
1122 /* private */ function openList( $char )
1124 $result = $this->closeParagraph();
1126 if ( "*" == $char ) { $result .= "<ul><li>"; }
1127 else if ( "#" == $char ) { $result .= "<ol><li>"; }
1128 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
1129 else if ( ";" == $char ) {
1130 $result .= "<dl><dt>";
1131 $this->mDTopen
= true;
1133 else { $result = "<!-- ERR 1 -->"; }
1138 /* private */ function nextItem( $char )
1140 if ( "*" == $char ||
"#" == $char ) { return "</li><li>"; }
1141 else if ( ":" == $char ||
";" == $char ) {
1143 if ( $this->mDTopen
) { $close = "</dt>"; }
1144 if ( ";" == $char ) {
1145 $this->mDTopen
= true;
1146 return $close . "<dt>";
1148 $this->mDTopen
= false;
1149 return $close . "<dd>";
1152 return "<!-- ERR 2 -->";
1155 /* private */function closeList( $char )
1157 if ( "*" == $char ) { $text = "</li></ul>"; }
1158 else if ( "#" == $char ) { $text = "</li></ol>"; }
1159 else if ( ":" == $char ) {
1160 if ( $this->mDTopen
) {
1161 $this->mDTopen
= false;
1162 $text = "</dt></dl>";
1164 $text = "</dd></dl>";
1167 else { return "<!-- ERR 3 -->"; }
1171 /* private */ function doBlockLevels( $text, $linestart )
1173 $fname = "OutputPage::doBlockLevels";
1174 wfProfileIn( $fname );
1175 # Parsing through the text line by line. The main thing
1176 # happening here is handling of block-level elements p, pre,
1177 # and making lists from lines starting with * # : etc.
1179 $a = explode( "\n", $text );
1180 $text = $lastPref = "";
1181 $this->mDTopen
= $inBlockElem = false;
1183 if ( ! $linestart ) { $text .= array_shift( $a ); }
1184 foreach ( $a as $t ) {
1185 if ( "" != $text ) { $text .= "\n"; }
1188 $opl = strlen( $lastPref );
1189 $npl = strspn( $t, "*#:;" );
1190 $pref = substr( $t, 0, $npl );
1191 $pref2 = str_replace( ";", ":", $pref );
1192 $t = substr( $t, $npl );
1194 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
1195 $text .= $this->nextItem( substr( $pref, -1 ) );
1197 if ( ";" == substr( $pref, -1 ) ) {
1198 $cpos = strpos( $t, ":" );
1199 if ( ! ( false === $cpos ) ) {
1200 $term = substr( $t, 0, $cpos );
1201 $text .= $term . $this->nextItem( ":" );
1202 $t = substr( $t, $cpos +
1 );
1205 } else if (0 != $npl ||
0 != $opl) {
1206 $cpl = $this->getCommon( $pref, $lastPref );
1208 while ( $cpl < $opl ) {
1209 $text .= $this->closeList( $lastPref{$opl-1} );
1212 if ( $npl <= $cpl && $cpl > 0 ) {
1213 $text .= $this->nextItem( $pref{$cpl-1} );
1215 while ( $npl > $cpl ) {
1216 $char = substr( $pref, $cpl, 1 );
1217 $text .= $this->openList( $char );
1219 if ( ";" == $char ) {
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 );
1231 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1233 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1234 $text .= $this->closeParagraph();
1235 $inBlockElem = true;
1237 if ( ! $inBlockElem ) {
1238 if ( " " == $t{0} ) {
1239 $newSection = "pre";
1240 # $t = wfEscapeHTML( $t );
1242 else { $newSection = "p"; }
1244 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1245 $text .= $this->closeParagraph();
1246 $text .= "<" . $newSection . ">";
1247 } else if ( 0 != strcmp( $this->mLastSection
,
1249 $text .= $this->closeParagraph();
1250 if ( 0 != strcmp( "p", $newSection ) ) {
1251 $text .= "<" . $newSection . ">";
1254 $this->mLastSection
= $newSection;
1256 if ( $inBlockElem &&
1257 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1258 $inBlockElem = false;
1264 $text .= $this->closeList( $pref2{$npl-1} );
1267 if ( "" != $this->mLastSection
) {
1268 if ( "p" != $this->mLastSection
) {
1269 $text .= "</" . $this->mLastSection
. ">";
1271 $this->mLastSection
= "";
1273 wfProfileOut( $fname );
1277 /* private */ function replaceVariables( $text )
1279 global $wgLang, $wgCurOut;
1280 $fname = "OutputPage::replaceVariables";
1281 wfProfileIn( $fname );
1286 # See Language.php for the definition of each magic word
1287 # As with sigs, this uses the server's local time -- ensure
1288 # this is appropriate for your audience!
1290 $magic[MAG_CURRENTMONTH
] = date( "m" );
1291 $magic[MAG_CURRENTMONTHNAME
] = $wgLang->getMonthName( date("n") );
1292 $magic[MAG_CURRENTMONTHNAMEGEN
] = $wgLang->getMonthNameGen( date("n") );
1293 $magic[MAG_CURRENTDAY
] = date("j");
1294 $magic[MAG_CURRENTDAYNAME
] = $wgLang->getWeekdayName( date("w")+
1 );
1295 $magic[MAG_CURRENTYEAR
] = date( "Y" );
1296 $magic[MAG_CURRENTTIME
] = $wgLang->time( wfTimestampNow(), false );
1298 $this->mContainsOldMagic +
= MagicWord
::replaceMultiple($magic, $text, $text);
1300 $mw =& MagicWord
::get( MAG_NUMBEROFARTICLES
);
1301 if ( $mw->match( $text ) ) {
1302 $v = wfNumberOfArticles();
1303 $text = $mw->replace( $v, $text );
1304 if( $mw->getWasModified() ) { $this->mContainsOldMagic++
; }
1307 # "Variables" with an additional parameter e.g. {{MSG:wikipedia}}
1308 # The callbacks are at the bottom of this file
1310 $mw =& MagicWord
::get( MAG_MSG
);
1311 $text = $mw->substituteCallback( $text, "wfReplaceMsgVar" );
1312 if( $mw->getWasModified() ) { $this->mContainsNewMagic++
; }
1314 $mw =& MagicWord
::get( MAG_MSGNW
);
1315 $text = $mw->substituteCallback( $text, "wfReplaceMsgnwVar" );
1316 if( $mw->getWasModified() ) { $this->mContainsNewMagic++
; }
1318 wfProfileOut( $fname );
1322 # Cleans up HTML, removes dangerous tags and attributes
1323 /* private */ function removeHTMLtags( $text )
1325 $fname = "OutputPage::removeHTMLtags";
1326 wfProfileIn( $fname );
1327 $htmlpairs = array( # Tags that must be closed
1328 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1329 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1330 "strike", "strong", "tt", "var", "div", "center",
1331 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1332 "ruby", "rt" , "rb" , "rp"
1334 $htmlsingle = array(
1335 "br", "p", "hr", "li", "dt", "dd"
1337 $htmlnest = array( # Tags that can be nested--??
1338 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1339 "dl", "font", "big", "small", "sub", "sup"
1341 $tabletags = array( # Can only appear inside table
1345 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1346 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1348 $htmlattrs = $this->getHTMLattrs () ;
1350 # Remove HTML comments
1351 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1353 $bits = explode( "<", $text );
1354 $text = array_shift( $bits );
1355 $tagstack = array(); $tablestack = array();
1357 foreach ( $bits as $x ) {
1358 $prev = error_reporting( E_ALL
& ~
( E_NOTICE | E_WARNING
) );
1359 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1361 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1362 error_reporting( $prev );
1365 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1369 if ( ! in_array( $t, $htmlsingle ) &&
1370 ( $ot = array_pop( $tagstack ) ) != $t ) {
1371 array_push( $tagstack, $ot );
1374 if ( $t == "table" ) {
1375 $tagstack = array_pop( $tablestack );
1380 # Keep track for later
1381 if ( in_array( $t, $tabletags ) &&
1382 ! in_array( "table", $tagstack ) ) {
1384 } else if ( in_array( $t, $tagstack ) &&
1385 ! in_array ( $t , $htmlnest ) ) {
1387 } else if ( ! in_array( $t, $htmlsingle ) ) {
1388 if ( $t == "table" ) {
1389 array_push( $tablestack, $tagstack );
1390 $tagstack = array();
1392 array_push( $tagstack, $t );
1394 # Strip non-approved attributes from the tag
1395 $newparams = $this->fixTagAttributes($params);
1399 $rest = str_replace( ">", ">", $rest );
1400 $text .= "<$slash$t $newparams$brace$rest";
1404 $text .= "<" . str_replace( ">", ">", $x);
1406 # Close off any remaining tags
1407 while ( $t = array_pop( $tagstack ) ) {
1409 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1411 wfProfileOut( $fname );
1417 * This function accomplishes several tasks:
1418 * 1) Auto-number headings if that option is enabled
1419 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1420 * 3) Add a Table of contents on the top for users who have enabled the option
1421 * 4) Auto-anchor headings
1423 * It loops through all headlines, collects the necessary data, then splits up the
1424 * string and re-inserts the newly formatted headlines.
1427 /* private */ function formatHeadings( $text )
1429 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1430 $nh=$wgUser->getOption( "numberheadings" );
1431 $st=$wgUser->getOption( "showtoc" );
1432 if(!$wgTitle->userCanEdit()) {
1436 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1437 $esr=$wgUser->getID() && $wgUser->getOption( "editsectiononrightclick" );
1440 # Inhibit editsection links if requested in the page
1441 $esw =& MagicWord
::get( MAG_NOEDITSECTION
);
1442 if ($esw->matchAndRemove( $text )) {
1445 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
1447 $mw =& MagicWord
::get( MAG_NOTOC
);
1448 if ($mw->matchAndRemove( $text ))
1453 # never add the TOC to the Main Page. This is an entry page that should not
1454 # be more than 1-2 screens large anyway
1455 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1457 # We need this to perform operations on the HTML
1458 $sk=$wgUser->getSkin();
1460 # Get all headlines for numbering them and adding funky stuff like [edit]
1462 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1467 # Ugh .. the TOC should have neat indentation levels which can be
1468 # passed to the skin functions. These are determined here
1469 foreach($matches[3] as $headline) {
1470 if($level) { $prevlevel=$level;}
1471 $level=$matches[1][$c];
1472 if(($nh||
$st) && $prevlevel && $level>$prevlevel) {
1474 $h[$level]=0; // reset when we enter a new level
1475 $toc.=$sk->tocIndent($level-$prevlevel);
1476 $toclevel+
=$level-$prevlevel;
1479 if(($nh||
$st) && $level<$prevlevel) {
1480 $h[$level+
1]=0; // reset when we step back a level
1481 $toc.=$sk->tocUnindent($prevlevel-$level);
1482 $toclevel-=$prevlevel-$level;
1485 $h[$level]++
; // count number of headlines for each level
1488 for($i=1;$i<=$level;$i++
) {
1490 if($dot) {$numbering.=".";}
1497 // The canonized header is a version of the header text safe to use for links
1499 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1500 $tocline = trim( $canonized_headline );
1501 $canonized_headline=str_replace('"',"",$canonized_headline);
1502 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1503 $refer[$c]=$canonized_headline;
1504 $refers[$canonized_headline]++
; // count how many in assoc. array so we can track dupes in anchors
1505 $refcount[$c]=$refers[$canonized_headline];
1507 // Prepend the number to the heading text
1510 $tocline=$numbering ." ". $tocline;
1512 // Don't number the heading if it is the only one (looks silly)
1513 if($nh && count($matches[3]) > 1) {
1514 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1518 // Create the anchor for linking from the TOC to the section
1520 $anchor=$canonized_headline;
1521 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1523 $toc.=$sk->tocLine($anchor,$tocline,$toclevel);
1525 if($es && !isset($wpPreview)) {
1526 $head[$c].=$sk->editSectionLink($c+
1);
1529 // Put it all together
1531 $head[$c].="<h".$level.$matches[2][$c]
1532 ."<a name=\"".$anchor."\">"
1537 // Add the edit section link
1539 if($esr && !isset($wpPreview)) {
1540 $head[$c]=$sk->editSectionScript($c+
1,$head[$c]);
1550 $toc.=$sk->tocUnindent($toclevel);
1551 $toc=$sk->tocTable($toc);
1554 // split up and insert constructed headlines
1556 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1559 foreach($blocks as $block) {
1560 if(($es) && !isset($wpPreview) && $c>0 && $i==0) {
1561 # This is the [edit] link that appears for the top block of text when
1562 # section editing is enabled
1563 $full.=$sk->editSectionLink(0);
1566 if($st && $toclines>3 && !$i) {
1567 # Let's add a top anchor just in case we want to link to the top of the page
1568 $full="<a name=\"top\"></a>".$full.$toc;
1578 /* private */ function magicISBN( $text )
1582 $a = split( "ISBN ", " $text" );
1583 if ( count ( $a ) < 2 ) return $text;
1584 $text = substr( array_shift( $a ), 1);
1585 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1587 foreach ( $a as $x ) {
1588 $isbn = $blank = "" ;
1589 while ( " " == $x{0} ) {
1591 $x = substr( $x, 1 );
1593 while ( strstr( $valid, $x{0} ) != false ) {
1595 $x = substr( $x, 1 );
1597 $num = str_replace( "-", "", $isbn );
1598 $num = str_replace( " ", "", $num );
1601 $text .= "ISBN $blank$x";
1603 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1604 "Booksources"), "isbn={$num}" ) . "\" class=\"internal\">ISBN $isbn</a>";
1611 /* private */ function magicRFC( $text )
1616 /* private */ function headElement()
1618 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding, $wgLang;
1620 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
1622 if ( "" == $this->mHTMLtitle
) {
1623 $this->mHTMLtitle
= $this->mPagetitle
;
1625 $rtl = $wgLang->isRTL() ?
" dir='RTL'" : "";
1626 $ret .= "<html lang=\"$wgLanguageCode\"$rtl><head><title>{$this->mHTMLtitle}</title>\n";
1627 array_push( $this->mMetatags
, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1628 foreach ( $this->mMetatags
as $tag ) {
1629 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1631 $tag[0] = substr( $tag[0], 5 );
1635 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1637 $p = $this->mRobotpolicy
;
1638 if ( "" == $p ) { $p = "index,follow"; }
1639 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1641 if ( count( $this->mKeywords
) > 0 ) {
1642 $ret .= "<meta name=\"keywords\" content=\"" .
1643 implode( ",", $this->mKeywords
) . "\">\n";
1645 foreach ( $this->mLinktags
as $tag ) {
1647 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1648 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1649 $ret .= "href=\"{$tag[2]}\">\n";
1651 $sk = $wgUser->getSkin();
1652 $ret .= $sk->getHeadScripts();
1653 $ret .= $sk->getUserStyles();
1655 $ret .= "</head>\n";
1659 /* private */ function fillFromParserCache(){
1660 global $wgUser, $wgArticle;
1661 $hash = $wgUser->getPageRenderingHash();
1662 $pageid = intval( $wgArticle->getID() );
1663 $res = wfQuery("SELECT pc_data FROM parsercache WHERE pc_pageid = {$pageid} ".
1664 " AND pc_prefhash = '{$hash}' AND pc_expire > NOW()", DB_WRITE
);
1665 $row = wfFetchObject ( $res );
1667 $data = unserialize( gzuncompress($row->pc_data
) );
1668 $this->addHTML( $data['html'] );
1669 $this->mLanguageLinks
= $data['mLanguageLinks'];
1670 $this->mCategoryLinks
= $data['mCategoryLinks'];
1671 wfProfileOut( $fname );
1678 /* private */ function saveParserCache( $text ){
1679 global $wgUser, $wgArticle;
1680 $hash = $wgUser->getPageRenderingHash();
1681 $pageid = intval( $wgArticle->getID() );
1682 $title = wfStrencode( $wgArticle->mTitle
->getPrefixedDBKey() );
1684 $data['html'] = $text;
1685 $data['mLanguageLinks'] = $this->mLanguageLinks
;
1686 $data['mCategoryLinks'] = $this->mCategoryLinks
;
1687 $ser = addslashes( gzcompress( serialize( $data ) ) );
1688 if( $this->mContainsOldMagic
){
1690 } else if( $this->mContainsNewMagic
){
1696 wfQuery("REPLACE INTO parsercache (pc_prefhash,pc_pageid,pc_title,pc_data, pc_expire) ".
1697 "VALUES('{$hash}', {$pageid}, '{$title}', '{$ser}', ".
1698 "DATE_ADD(NOW(), INTERVAL {$expire}))", DB_WRITE
);
1700 if( rand() %
50 == 0 ){ // more efficient to just do it sometimes
1701 $this->purgeParserCache();
1705 /* static private */ function purgeParserCache(){
1706 wfQuery("DELETE FROM parsercache WHERE pc_expire < NOW() LIMIT 250", DB_WRITE
);
1709 /* static */ function parsercacheClearLinksTo( $pid ){
1710 $pid = intval( $pid );
1711 wfQuery("DELETE parsercache FROM parsercache,links ".
1712 "WHERE pc_title=links.l_from AND l_to={$pid}", DB_WRITE
);
1713 wfQuery("DELETE FROM parsercache WHERE pc_pageid='{$pid}'", DB_WRITE
);
1716 # $title is a prefixed db title, for example like Title->getPrefixedDBkey() returns.
1717 /* static */ function parsercacheClearBrokenLinksTo( $title ){
1718 $title = wfStrencode( $title );
1719 wfQuery("DELETE parsercache FROM parsercache,brokenlinks ".
1720 "WHERE pc_pageid=bl_from AND bl_to='{$title}'", DB_WRITE
);
1724 /* static */ function parsercacheClearPage( $pid ){
1725 $pid = intval( $pid );
1726 wfQuery("DELETE FROM parsercache WHERE pc_pageid='{$pid}'", DB_WRITE
);
1730 # Regex callbacks, used in OutputPage::replaceVariables
1732 # Just get rid of the dangerous stuff
1733 # Necessary because replaceVariables is called after removeHTMLtags,
1734 # and message text can come from any user
1735 function wfReplaceMsgVar( $matches ) {
1736 global $wgCurOut, $wgLinkCache;
1737 $text = $wgCurOut->removeHTMLtags( wfMsg( $matches[1] ) );
1738 $wgLinkCache->suspend();
1739 $text = $wgCurOut->replaceInternalLinks( $text );
1740 $wgLinkCache->resume();
1741 $wgLinkCache->addLinkObj( Title
::makeTitle( NS_MEDIAWIKI
, $matches[1] ) );
1745 # Effective <nowiki></nowiki>
1746 # Not real <nowiki> because this is called after nowiki sections are processed
1747 function wfReplaceMsgnwVar( $matches ) {
1748 global $wgCurOut, $wgLinkCache;
1749 $text = wfEscapeWikiText( wfMsg( $matches[1] ) );
1750 $wgLinkCache->addLinkObj( Title
::makeTitle( NS_MEDIAWIKI
, $matches[1] ) );