Staged compilation for users. Extra local caching.
[lhc/web/wiklou.git] / skins / disabled / MonoBookCBT.php
1 <?php
2
3 if ( !defined( 'MEDIAWIKI' ) ) {
4 die( "This file is part of MediaWiki, it is not a valid entry point\n" );
5 }
6
7 require_once( 'includes/cbt/CBTProcessor.php' );
8 require_once( 'includes/cbt/CBTCompiler.php' );
9 require_once( 'includes/SkinTemplate.php' );
10
11 /**
12 * MonoBook clone using the new dependency-tracking template processor.
13 * EXPERIMENTAL - use only for testing and profiling at this stage
14 *
15 * The main thing that's missing is cache invalidation, on change of:
16 * * messages
17 * * user preferences
18 * * source template
19 * * source code and configuration files
20 *
21 * The other thing is that lots of dependencies that are declared in the callbacks
22 * are not intelligently handled. There's some room for improvement there.
23 *
24 * The class is derived from SkinTemplate, but that's only temporary. Eventually
25 * it'll be derived from Skin, and I've avoided using SkinTemplate functions as
26 * much as possible. In fact, the only SkinTemplate dependencies I know of at the
27 * moment are the functions to generate the gen=css and gen=js files.
28 *
29 */
30 class SkinMonoBookCBT extends SkinTemplate {
31 var $mOut, $mTitle;
32 var $mStyleName = 'monobook';
33 var $mCompiling = false;
34 var $mFunctionCache = array();
35
36 /******************************************************
37 * General functions *
38 ******************************************************/
39
40 /** Execute the template and write out the result */
41 function outputPage( &$out ) {
42 echo $this->execute( $out );
43 }
44
45 function execute( &$out ) {
46 global $wgTitle, $wgStyleDirectory, $wgParserCacheType;
47 $fname = 'SkinMonoBookCBT::execute';
48 wfProfileIn( $fname );
49 wfProfileIn( "$fname-setup" );
50 Skin::initPage( $out );
51
52 $this->mOut =& $out;
53 $this->mTitle =& $wgTitle;
54
55 $sourceFile = "$wgStyleDirectory/MonoBook.tpl";
56
57 wfProfileOut( "$fname-setup" );
58
59 if ( $wgParserCacheType == CACHE_NONE ) {
60 $template = file_get_contents( $sourceFile );
61 $text = $this->executeTemplate( $template );
62 } else {
63 $compiled = $this->getCompiledTemplate( $sourceFile );
64
65 wfProfileIn( "$fname-eval" );
66 $text = eval( $compiled );
67 wfProfileOut( "$fname-eval" );
68 }
69 wfProfileOut( $fname );
70 return $text;
71 }
72
73 function getCompiledTemplate( $sourceFile ) {
74 global $wgDBname, $wgMemc, $wgRequest, $wgUser, $parserMemc;
75 $fname = 'SkinMonoBookCBT::getCompiledTemplate';
76
77 $expiry = 3600;
78
79 // Sandbox template execution
80 if ( $this->mCompiling ) {
81 return;
82 }
83
84 wfProfileIn( $fname );
85
86 // Is the request an ordinary page view?
87 if ( $wgRequest->wasPosted() ||
88 count( array_diff( array_keys( $_GET ), array( 'title', 'useskin', 'recompile' ) ) ) != 0 )
89 {
90 $type = 'nonview';
91 } else {
92 $type = 'view';
93 }
94
95 // Per-user compiled template
96 // Put all logged-out users on the same cache key
97 $cacheKey = "$wgDBname:monobookcbt:$type:" . $wgUser->getId();
98
99 $recompile = $wgRequest->getVal( 'recompile' );
100 if ( $recompile == 'user' ) {
101 $recompileUser = true;
102 $recompileGeneric = false;
103 } elseif ( $recompile ) {
104 $recompileUser = true;
105 $recompileGeneric = true;
106 } else {
107 $recompileUser = false;
108 $recompileGeneric = false;
109 }
110
111 if ( !$recompileUser ) {
112 $php = $parserMemc->get( $cacheKey );
113 }
114 if ( $recompileUser || !$php ) {
115 if ( $wgUser->isLoggedIn() ) {
116 // Perform staged compilation
117 // First compile a generic template for all logged-in users
118 $genericKey = "$wgDBname:monobookcbt:$type:loggedin";
119 if ( !$recompileGeneric ) {
120 $template = $parserMemc->get( $genericKey );
121 }
122 if ( $recompileGeneric || !$template ) {
123 $template = file_get_contents( $sourceFile );
124 $ignore = array( 'loggedin', '!loggedin dynamic' );
125 if ( $type == 'view' ) {
126 $ignore[] = 'nonview dynamic';
127 }
128 $template = $this->compileTemplate( $template, $ignore );
129 $parserMemc->set( $genericKey, $template, $expiry );
130 }
131 } else {
132 $template = file_get_contents( $sourceFile );
133 }
134
135 $ignore = array( 'lang', 'loggedin', 'user' );
136 if ( $wgUser->isLoggedIn() ) {
137 $ignore[] = '!loggedin dynamic';
138 } else {
139 $ignore[] = 'loggedin dynamic';
140 }
141 if ( $type == 'view' ) {
142 $ignore[] = 'nonview dynamic';
143 }
144 $compiled = $this->compileTemplate( $template, $ignore );
145
146 // Reduce whitespace
147 // This is done here instead of in CBTProcessor because we can be
148 // more sure it is safe here.
149 $compiled = preg_replace( '/^[ \t]+/m', '', $compiled );
150 $compiled = preg_replace( '/[\r\n]+/', "\n", $compiled );
151
152 // Compile to PHP
153 $compiler = new CBTCompiler( $compiled );
154 $ret = $compiler->compile();
155 if ( $ret !== true ) {
156 echo $ret;
157 wfErrorExit();
158 }
159 $php = $compiler->generatePHP( '$this' );
160
161 $parserMemc->set( $cacheKey, $php, $expiry );
162 }
163 wfProfileOut( $fname );
164 return $php;
165 }
166
167 function compileTemplate( $template, $ignore ) {
168 $tp = new CBTProcessor( $template, $this, $ignore );
169 $tp->mFunctionCache = $this->mFunctionCache;
170
171 $this->mCompiling = true;
172 $compiled = $tp->compile();
173 $this->mCompiling = false;
174
175 if ( $tp->getLastError() ) {
176 // If there was a compile error, don't save the template
177 // Instead just print the error and exit
178 echo $compiled;
179 wfErrorExit();
180 }
181 $this->mFunctionCache = $tp->mFunctionCache;
182 return $compiled;
183 }
184
185 function executeTemplate( $template ) {
186 $fname = 'SkinMonoBookCBT::executeTemplate';
187 wfProfileIn( $fname );
188 $tp = new CBTProcessor( $template, $this );
189 $tp->mFunctionCache = $this->mFunctionCache;
190
191 $this->mCompiling = true;
192 $text = $tp->execute();
193 $this->mCompiling = false;
194
195 $this->mFunctionCache = $tp->mFunctionCache;
196 wfProfileOut( $fname );
197 return $text;
198 }
199
200 /******************************************************
201 * Callbacks *
202 ******************************************************/
203
204 function lang() { return $GLOBALS['wgContLanguageCode']; }
205
206 function dir() {
207 global $wgContLang;
208 return $wgContLang->isRTL() ? 'rtl' : 'ltr';
209 }
210
211 function mimetype() { return $GLOBALS['wgMimeType']; }
212 function charset() { return $GLOBALS['wgOutputEncoding']; }
213 function headlinks() {
214 return cbt_value( $this->mOut->getHeadLinks(), 'dynamic' );
215 }
216 function headscripts() {
217 return cbt_value( $this->mOut->getScript(), 'dynamic' );
218 }
219
220 function pagetitle() {
221 return cbt_value( $this->mOut->getHTMLTitle(), array( 'title', 'lang' ) );
222 }
223
224 function stylepath() { return $GLOBALS['wgStylePath']; }
225 function stylename() { return $this->mStyleName; }
226
227 function notprintable() {
228 global $wgRequest;
229 return cbt_value( !$wgRequest->getBool( 'printable' ), 'nonview dynamic' );
230 }
231
232 function jsmimetype() { return $GLOBALS['wgJsMimeType']; }
233
234 function jsvarurl() {
235 global $wgUseSiteJs, $wgUser;
236 if ( !$wgUseSiteJs ) return '';
237
238 if ( $wgUser->isLoggedIn() ) {
239 $url = $this->makeUrl('-','action=raw&smaxage=0&gen=js');
240 } else {
241 $url = $this->makeUrl('-','action=raw&gen=js');
242 }
243 return cbt_value( $url, 'loggedin' );
244 }
245
246 function pagecss() {
247 global $wgHooks;
248
249 $out = false;
250 wfRunHooks( 'SkinTemplateSetupPageCss', array( &$out ) );
251
252 // Unknown dependencies
253 return cbt_value( $out, 'dynamic' );
254 }
255
256 function usercss() {
257 if ( $this->isCssPreview() ) {
258 global $wgRequest;
259 $usercss = $this->makeStylesheetCdata( $wgRequest->getText('wpTextbox1') );
260 } else {
261 $usercss = $this->makeStylesheetLink( $this->makeUrl($this->getUserPageText() .
262 '/'.$this->mStyleName.'.css', 'action=raw&ctype=text/css' ) );
263 }
264
265 // Dynamic when not an ordinary page view, also depends on the username
266 return cbt_value( $usercss, array( 'nonview dynamic', 'user' ) );
267 }
268
269 function sitecss() {
270 global $wgUseSiteCss;
271 if ( !$wgUseSiteCss ) {
272 return '';
273 }
274
275 global $wgSquidMaxage, $wgContLang, $wgStylePath;
276
277 $query = "action=raw&ctype=text/css&smaxage=$wgSquidMaxage";
278
279 $sitecss = '';
280 if ( $wgContLang->isRTL() ) {
281 $sitecss .= $this->makeStylesheetLink( $wgStylePath . '/' . $this->mStyleName . '/rtl.css' ) . "\n";
282 }
283
284 $sitecss .= $this->makeStylesheetLink( $this->makeNSUrl('Common.css', $query, NS_MEDIAWIKI) ) . "\n";
285 $sitecss .= $this->makeStylesheetLink( $this->makeNSUrl(
286 ucfirst($this->mStyleName) . '.css', $query, NS_MEDIAWIKI) ) . "\n";
287
288 // No deps
289 return $sitecss;
290 }
291
292 function gencss() {
293 global $wgUseSiteCss;
294 if ( !$wgUseSiteCss ) return '';
295
296 global $wgSquidMaxage, $wgUser, $wgAllowUserCss;
297 if ( $this->isCssPreview() ) {
298 $siteargs = '&smaxage=0&maxage=0';
299 } else {
300 $siteargs = '&maxage=' . $wgSquidMaxage;
301 }
302 if ( $wgAllowUserCss && $wgUser->isLoggedIn() ) {
303 $siteargs .= '&ts={user_touched}';
304 $isTemplate = true;
305 } else {
306 $isTemplate = false;
307 }
308
309 $link = $this->makeStylesheetLink( $this->makeUrl('-','action=raw&gen=css' . $siteargs) ) . "\n";
310
311 if ( $wgAllowUserCss ) {
312 $deps = 'loggedin';
313 } else {
314 $deps = array();
315 }
316 return cbt_value( $link, $deps, $isTemplate );
317 }
318
319 function user_touched() {
320 global $wgUser;
321 return cbt_value( $wgUser->mTouched, 'dynamic' );
322 }
323
324 function userjs() {
325 global $wgAllowUserJs, $wgJsMimeType;
326 if ( !$wgAllowUserJs ) return '';
327
328 if ( $this->isJsPreview() ) {
329 $url = '';
330 } else {
331 $url = $this->makeUrl($this->getUserPageText().'/'.$this->mStyleName.'.js', 'action=raw&ctype='.$wgJsMimeType.'&dontcountme=s');
332 }
333 return cbt_value( $url, array( 'nonview dynamic', 'user' ) );
334 }
335
336 function userjsprev() {
337 global $wgAllowUserJs, $wgRequest;
338 if ( !$wgAllowUserJs ) return '';
339 if ( $this->isJsPreview() ) {
340 $js = '/*<![CDATA[*/ ' . $wgRequest->getText('wpTextbox1') . ' /*]]>*/';
341 } else {
342 $js = '';
343 }
344 return cbt_value( $js, array( 'nonview dynamic' ) );
345 }
346
347 function trackbackhtml() {
348 global $wgUseTrackbacks;
349 if ( !$wgUseTrackbacks ) return '';
350
351 if ( $this->mOut->isArticleRelated() ) {
352 $tb = $this->mTitle->trackbackRDF();
353 } else {
354 $tb = '';
355 }
356 return cbt_value( $tb, 'dynamic' );
357 }
358
359 function body_ondblclick() {
360 global $wgUser;
361 if( $this->isEditable() && $wgUser->getOption("editondblclick") ) {
362 $js = 'document.location = "' . $this->getEditUrl() .'";';
363 } else {
364 $js = '';
365 }
366
367 if ( User::getDefaultOption('editondblclick') ) {
368 return cbt_value( $js, 'user', 'title' );
369 } else {
370 // Optimise away for logged-out users
371 return cbt_value( $js, 'loggedin dynamic' );
372 }
373 }
374
375 function body_onload() {
376 global $wgUser;
377 if ( $this->isEditable() && $wgUser->getOption( 'editsectiononrightclick' ) ) {
378 $js = 'setupRightClickEdit()';
379 } else {
380 $js = '';
381 }
382 return cbt_value( $js, 'loggedin dynamic' );
383 }
384
385 function nsclass() {
386 return cbt_value( 'ns-' . $this->mTitle->getNamespace(), 'title' );
387 }
388
389 function sitenotice() {
390 // Perhaps this could be given special dependencies using our knowledge of what
391 // wfGetSiteNotice() depends on.
392 return cbt_value( wfGetSiteNotice(), 'dynamic' );
393 }
394
395 function title() {
396 return cbt_value( $this->mOut->getPageTitle(), array( 'title', 'lang' ) );
397 }
398
399 function title_urlform() {
400 return cbt_value( $this->getThisTitleUrlForm(), 'title' );
401 }
402
403 function title_userurl() {
404 return cbt_value( urlencode( $this->mTitle->getDBkey() ), 'title' );
405 }
406
407 function subtitle() {
408 $subpagestr = $this->subPageSubtitle();
409 if ( !empty( $subpagestr ) ) {
410 $s = '<span class="subpages">'.$subpagestr.'</span>'.$this->mOut->getSubtitle();
411 } else {
412 $s = $this->mOut->getSubtitle();
413 }
414 return cbt_value( $s, array( 'title', 'nonview dynamic' ) );
415 }
416
417 function undelete() {
418 return cbt_value( $this->getUndeleteLink(), array( 'title', 'lang' ) );
419 }
420
421 function newtalk() {
422 global $wgUser, $wgDBname;
423 $newtalks = $wgUser->getNewMessageLinks();
424
425 if (count($newtalks) == 1 && $newtalks[0]["wiki"] === $wgDBname) {
426 $usertitle = $this->getUserPageTitle();
427 $usertalktitle = $usertitle->getTalkPage();
428 if( !$usertalktitle->equals( $this->mTitle ) ) {
429 $ntl = wfMsg( 'youhavenewmessages',
430 $this->makeKnownLinkObj(
431 $usertalktitle,
432 wfMsgHtml( 'newmessageslink' ),
433 'redirect=no'
434 ),
435 $this->makeKnownLinkObj(
436 $usertalktitle,
437 wfMsgHtml( 'newmessagesdifflink' ),
438 'diff=cur'
439 )
440 );
441 # Disable Cache
442 $this->mOut->setSquidMaxage(0);
443 }
444 } else if (count($newtalks)) {
445 $sep = str_replace("_", " ", wfMsgHtml("newtalkseperator"));
446 $msgs = array();
447 foreach ($newtalks as $newtalk) {
448 $msgs[] = wfElement("a",
449 array('href' => $newtalk["link"]), $newtalk["wiki"]);
450 }
451 $parts = implode($sep, $msgs);
452 $ntl = wfMsgHtml('youhavenewmessagesmulti', $parts);
453 $this->mOut->setSquidMaxage(0);
454 } else {
455 $ntl = '';
456 }
457 return cbt_value( $ntl, 'dynamic' );
458 }
459
460 function showjumplinks() {
461 global $wgUser;
462 return cbt_value( $wgUser->getOption( 'showjumplinks' ) ? 'true' : '', 'user' );
463 }
464
465 function bodytext() {
466 return cbt_value( $this->mOut->getHTML(), 'dynamic' );
467 }
468
469 function catlinks() {
470 if ( !isset( $this->mCatlinks ) ) {
471 $this->mCatlinks = $this->getCategories();
472 }
473 return cbt_value( $this->mCatlinks, 'dynamic' );
474 }
475
476 function extratabs( $itemTemplate ) {
477 global $wgContLang, $wgDisableLangConversion;
478
479 $etpl = cbt_escape( $itemTemplate );
480
481 /* show links to different language variants */
482 $variants = $wgContLang->getVariants();
483 $s = '';
484 if ( !$wgDisableLangConversion && count( $wgContLang->getVariants() ) > 1 ) {
485 $vcount=0;
486 foreach ( $variants as $code ) {
487 $name = $wgContLang->getVariantname( $code );
488 if ( $name == 'disable' ) {
489 continue;
490 }
491 $code = cbt_escape( $code );
492 $name = cbt_escape( $name );
493 $s .= "{ca_variant {{$code}} {{$name}} {{$vcount}} {{$etpl}}}\n";
494 $vcount ++;
495 }
496 }
497 return cbt_value( $s, array(), true );
498 }
499
500 function is_special() { return cbt_value( $this->mTitle->getNamespace() == NS_SPECIAL, 'title' ); }
501 function can_edit() { return cbt_value( (string)($this->mTitle->userCanEdit()), 'dynamic' ); }
502 function can_move() { return cbt_value( (string)($this->mTitle->userCanMove()), 'dynamic' ); }
503 function is_talk() { return cbt_value( (string)($this->mTitle->isTalkPage()), 'title' ); }
504 function is_protected() { return cbt_value( (string)$this->mTitle->isProtected(), 'dynamic' ); }
505 function nskey() { return cbt_value( $this->mTitle->getNamespaceKey(), 'title' ); }
506
507 function request_url() {
508 global $wgRequest;
509 return cbt_value( $wgRequest->getRequestURL(), 'dynamic' );
510 }
511
512 function subject_url() {
513 $title = $this->getSubjectPage();
514 if ( $title->exists() ) {
515 $url = $title->getLocalUrl();
516 } else {
517 $url = $title->getLocalUrl( 'action=edit' );
518 }
519 return cbt_value( $url, 'title' );
520 }
521
522 function talk_url() {
523 $title = $this->getTalkPage();
524 if ( $title->exists() ) {
525 $url = $title->getLocalUrl();
526 } else {
527 $url = $title->getLocalUrl( 'action=edit' );
528 }
529 return cbt_value( $url, 'title' );
530 }
531
532 function edit_url() {
533 return cbt_value( $this->getEditUrl(), array( 'title', 'nonview dynamic' ) );
534 }
535
536 function move_url() {
537 return cbt_value( $this->makeSpecialParamUrl( 'Movepage' ), array(), true );
538 }
539
540 function localurl( $query ) {
541 return cbt_value( $this->mTitle->getLocalURL( $query ), 'title' );
542 }
543
544 function selecttab( $tab, $extraclass = '' ) {
545 if ( !isset( $this->mSelectedTab ) ) {
546 $prevent_active_tabs = false ;
547 wfRunHooks( 'SkinTemplatePreventOtherActiveTabs', array( &$this , &$preventActiveTabs ) );
548
549 $actionTabs = array(
550 'edit' => 'edit',
551 'submit' => 'edit',
552 'history' => 'history',
553 'protect' => 'protect',
554 'unprotect' => 'protect',
555 'delete' => 'delete',
556 'watch' => 'watch',
557 'unwatch' => 'watch',
558 );
559 if ( $preventActiveTabs ) {
560 $this->mSelectedTab = false;
561 } else {
562 $action = $this->getAction();
563 $section = $this->getSection();
564
565 if ( isset( $actionTabs[$action] ) ) {
566 $this->mSelectedTab = $actionTabs[$action];
567
568 if ( $this->mSelectedTab == 'edit' && $section == 'new' ) {
569 $this->mSelectedTab = 'addsection';
570 }
571 } elseif ( $this->mTitle->isTalkPage() ) {
572 $this->mSelectedTab = 'talk';
573 } else {
574 $this->mSelectedTab = 'subject';
575 }
576 }
577 }
578 if ( $extraclass ) {
579 if ( $this->mSelectedTab == $tab ) {
580 $s = 'class="selected ' . htmlspecialchars( $extraclass ) . '"';
581 } else {
582 $s = 'class="' . htmlspecialchars( $extraclass ) . '"';
583 }
584 } else {
585 if ( $this->mSelectedTab == $tab ) {
586 $s = 'class="selected"';
587 } else {
588 $s = '';
589 }
590 }
591 return cbt_value( $s, array( 'nonview dynamic', 'title' ) );
592 }
593
594 function subject_newclass() {
595 $title = $this->getSubjectPage();
596 $class = $title->exists() ? '' : 'new';
597 return cbt_value( $class, 'dynamic' );
598 }
599
600 function talk_newclass() {
601 $title = $this->getTalkPage();
602 $class = $title->exists() ? '' : 'new';
603 return cbt_value( $class, 'dynamic' );
604 }
605
606 function ca_variant( $code, $name, $index, $template ) {
607 global $wgContLang;
608 $selected = ($code == $wgContLang->getPreferredVariant());
609 $action = $this->getAction();
610 $actstr = '';
611 if( $action )
612 $actstr = 'action=' . $action . '&';
613 $s = strtr( $template, array(
614 '$id' => htmlspecialchars( 'varlang-' . $index ),
615 '$class' => $selected ? 'class="selected"' : '',
616 '$text' => $name,
617 '$href' => htmlspecialchars( $this->mTitle->getLocalUrl( $actstr . 'variant=' . $code ) )
618 ));
619 return cbt_value( $s, 'dynamic' );
620 }
621
622 function is_watching() {
623 return cbt_value( (string)$this->mTitle->userIsWatching(), array( 'dynamic' ) );
624 }
625
626
627 function personal_urls( $itemTemplate ) {
628 global $wgShowIPinHeader, $wgContLang;
629
630 # Split this function up into many small functions, to obtain the
631 # best specificity in the dependencies of each one. The template below
632 # has no dependencies, so its generation, and any static subfunctions,
633 # can be optimised away.
634 $etpl = cbt_escape( $itemTemplate );
635 $s = "
636 {userpage {{$etpl}}}
637 {mytalk {{$etpl}}}
638 {preferences {{$etpl}}}
639 {watchlist {{$etpl}}}
640 {mycontris {{$etpl}}}
641 {logout {{$etpl}}}
642 ";
643
644 if ( $wgShowIPinHeader ) {
645 $s .= "
646 {anonuserpage {{$etpl}}}
647 {anontalk {{$etpl}}}
648 {anonlogin {{$etpl}}}
649 ";
650 } else {
651 $s .= "{login {{$etpl}}}\n";
652 }
653 // No dependencies
654 return cbt_value( $s, array(), true /*this is a template*/ );
655 }
656
657 function userpage( $itemTemplate ) {
658 global $wgUser;
659 if ( $this->isLoggedIn() ) {
660 $userPage = $this->getUserPageTitle();
661 $s = $this->makeTemplateLink( $itemTemplate, 'userpage', $userPage, $wgUser->getName() );
662 } else {
663 $s = '';
664 }
665 return cbt_value( $s, 'user' );
666 }
667
668 function mytalk( $itemTemplate ) {
669 global $wgUser;
670 if ( $this->isLoggedIn() ) {
671 $userPage = $this->getUserPageTitle();
672 $talkPage = $userPage->getTalkPage();
673 $s = $this->makeTemplateLink( $itemTemplate, 'mytalk', $talkPage, wfMsg('mytalk') );
674 } else {
675 $s = '';
676 }
677 return cbt_value( $s, 'user' );
678 }
679
680 function preferences( $itemTemplate ) {
681 if ( $this->isLoggedIn() ) {
682 $s = $this->makeSpecialTemplateLink( $itemTemplate, 'preferences',
683 'Preferences', wfMsg( 'preferences' ) );
684 } else {
685 $s = '';
686 }
687 return cbt_value( $s, array( 'loggedin', 'lang' ) );
688 }
689
690 function watchlist( $itemTemplate ) {
691 if ( $this->isLoggedIn() ) {
692 $s = $this->makeSpecialTemplateLink( $itemTemplate, 'watchlist',
693 'Watchlist', wfMsg( 'watchlist' ) );
694 } else {
695 $s = '';
696 }
697 return cbt_value( $s, array( 'loggedin', 'lang' ) );
698 }
699
700 function mycontris( $itemTemplate ) {
701 if ( $this->isLoggedIn() ) {
702 global $wgUser;
703 $s = $this->makeSpecialTemplateLink( $itemTemplate, 'mycontris',
704 "Contributions/" . $wgUser->getTitleKey(), wfMsg('mycontris') );
705 } else {
706 $s = '';
707 }
708 return cbt_value( $s, 'user' );
709 }
710
711 function logout( $itemTemplate ) {
712 if ( $this->isLoggedIn() ) {
713 $s = $this->makeSpecialTemplateLink( $itemTemplate, 'logout',
714 'Userlogout', wfMsg( 'userlogout' ),
715 $this->mTitle->getNamespace() === NS_SPECIAL && $this->mTitle->getText() === 'Preferences'
716 ? '' : "returnto=" . $this->mTitle->getPrefixedURL() );
717 } else {
718 $s = '';
719 }
720 return cbt_value( $s, 'loggedin dynamic' );
721 }
722
723 function anonuserpage( $itemTemplate ) {
724 if ( $this->isLoggedIn() ) {
725 $s = '';
726 } else {
727 global $wgUser;
728 $userPage = $this->getUserPageTitle();
729 $s = $this->makeTemplateLink( $itemTemplate, 'userpage', $userPage, $wgUser->getName() );
730 }
731 return cbt_value( $s, '!loggedin dynamic' );
732 }
733
734 function anontalk( $itemTemplate ) {
735 if ( $this->isLoggedIn() ) {
736 $s = '';
737 } else {
738 $userPage = $this->getUserPageTitle();
739 $talkPage = $userPage->getTalkPage();
740 $s = $this->makeTemplateLink( $itemTemplate, 'mytalk', $talkPage, wfMsg('anontalk') );
741 }
742 return cbt_value( $s, '!loggedin dynamic' );
743 }
744
745 function anonlogin( $itemTemplate ) {
746 if ( $this->isLoggedIn() ) {
747 $s = '';
748 } else {
749 $s = $this->makeSpecialTemplateLink( $itemTemplate, 'anonlogin', 'Userlogin',
750 wfMsg( 'userlogin' ), 'returnto=' . urlencode( $this->getThisPDBK() ) );
751 }
752 return cbt_value( $s, '!loggedin dynamic' );
753 }
754
755 function login( $itemTemplate ) {
756 if ( $this->isLoggedIn() ) {
757 $s = '';
758 } else {
759 $s = $this->makeSpecialTemplateLink( $itemTemplate, 'login', 'Userlogin',
760 wfMsg( 'userlogin' ), 'returnto=' . urlencode( $this->getThisPDBK() ) );
761 }
762 return cbt_value( $s, '!loggedin dynamic' );
763 }
764
765 function logopath() { return $GLOBALS['wgLogo']; }
766 function mainpage() { return $this->makeI18nUrl( 'mainpage' ); }
767
768 function sidebar( $startSection, $endSection, $innerTpl ) {
769 $s = '';
770 $lines = explode( "\n", wfMsgForContent( 'sidebar' ) );
771 $firstSection = true;
772 foreach ($lines as $line) {
773 if (strpos($line, '*') !== 0)
774 continue;
775 if (strpos($line, '**') !== 0) {
776 $bar = trim($line, '* ');
777 $name = wfMsg( $bar );
778 if (wfEmptyMsg($bar, $name)) {
779 $name = $bar;
780 }
781 if ( $firstSection ) {
782 $firstSection = false;
783 } else {
784 $s .= $endSection;
785 }
786 $s .= strtr( $startSection,
787 array(
788 '$bar' => htmlspecialchars( $bar ),
789 '$barname' => $name
790 ) );
791 } else {
792 if (strpos($line, '|') !== false) { // sanity check
793 $line = explode( '|' , trim($line, '* '), 2 );
794 $link = wfMsgForContent( $line[0] );
795 if ($link == '-')
796 continue;
797 if (wfEmptyMsg($line[1], $text = wfMsg($line[1])))
798 $text = $line[1];
799 if (wfEmptyMsg($line[0], $link))
800 $link = $line[0];
801 $href = $this->makeInternalOrExternalUrl( $link );
802
803 $s .= strtr( $innerTpl,
804 array(
805 '$text' => htmlspecialchars( $text ),
806 '$href' => htmlspecialchars( $href ),
807 '$id' => htmlspecialchars( 'n-' . strtr($line[1], ' ', '-') ),
808 '$classactive' => ''
809 ) );
810 } else { continue; }
811 }
812 }
813 if ( !$firstSection ) {
814 $s .= $endSection;
815 }
816
817 // Depends on user language only
818 return cbt_value( $s, 'lang' );
819 }
820
821 function searchaction() {
822 // Static link
823 return $this->getSearchLink();
824 }
825
826 function search() {
827 global $wgRequest;
828 return cbt_value( trim( $this->getSearch() ), 'special dynamic' );
829 }
830
831 function notspecialpage() {
832 return cbt_value( $this->mTitle->getNamespace() != NS_SPECIAL, 'special' );
833 }
834
835 function nav_whatlinkshere() {
836 return cbt_value( $this->makeSpecialParamUrl('Whatlinkshere' ), array(), true );
837 }
838
839 function article_exists() {
840 return cbt_value( (string)($this->mTitle->getArticleId() !== 0), 'title' );
841 }
842
843 function nav_recentchangeslinked() {
844 return cbt_value( $this->makeSpecialParamUrl('Recentchangeslinked' ), array(), true );
845 }
846
847 function feeds( $itemTemplate = '' ) {
848 if ( !$this->mOut->isSyndicated() ) {
849 $feeds = '';
850 } elseif ( $itemTemplate == '' ) {
851 // boolean only required
852 $feeds = 'true';
853 } else {
854 $feeds = '';
855 global $wgFeedClasses, $wgRequest;
856 foreach( $wgFeedClasses as $format => $class ) {
857 $feeds .= strtr( $itemTemplate,
858 array(
859 '$key' => htmlspecialchars( $format ),
860 '$text' => $format,
861 '$href' => $wgRequest->appendQuery( "feed=$format" )
862 ) );
863 }
864 }
865 return cbt_value( $feeds, 'special dynamic' );
866 }
867
868 function is_userpage() {
869 list( $id, $ip ) = $this->getUserPageIdIp();
870 return cbt_value( (string)($id || $ip), 'title' );
871 }
872
873 function is_ns_mediawiki() {
874 return cbt_value( (string)$this->mTitle->getNamespace() == NS_MEDIAWIKI, 'title' );
875 }
876
877 function is_loggedin() {
878 global $wgUser;
879 return cbt_value( (string)($wgUser->isLoggedIn()), 'loggedin' );
880 }
881
882 function nav_contributions() {
883 $url = $this->makeSpecialParamUrl( 'Contributions', '', '{title_userurl}' );
884 return cbt_value( $url, array(), true );
885 }
886
887 function is_allowed( $right ) {
888 global $wgUser;
889 return cbt_value( (string)$wgUser->isAllowed( $right ), 'user' );
890 }
891
892 function nav_blockip() {
893 $url = $this->makeSpecialParamUrl( 'Blockip', '', '{title_userurl}' );
894 return cbt_value( $url, array(), true );
895 }
896
897 function nav_emailuser() {
898 global $wgEnableEmail, $wgEnableUserEmail, $wgUser;
899 if ( !$wgEnableEmail || !$wgEnableUserEmail ) return '';
900
901 $url = $this->makeSpecialParamUrl( 'Emailuser', '', '{title_userurl}' );
902 return cbt_value( $url, array(), true );
903 }
904
905 function nav_upload() {
906 global $wgEnableUploads, $wgUploadNavigationUrl;
907 if ( !$wgEnableUploads ) {
908 return '';
909 } elseif ( $wgUploadNavigationUrl ) {
910 return $wgUploadNavigationUrl;
911 } else {
912 return $this->makeSpecialUrl('Upload');
913 }
914 }
915
916 function nav_specialpages() {
917 return $this->makeSpecialUrl('Specialpages');
918 }
919
920 function nav_print() {
921 global $wgRequest, $wgArticle;
922 $action = $this->getAction();
923 $url = '';
924 if( $this->mTitle->getNamespace() !== NS_SPECIAL
925 && ($action == '' || $action == 'view' || $action == 'purge' ) )
926 {
927 $revid = $wgArticle->getLatest();
928 if ( $revid != 0 ) {
929 $url = $wgRequest->appendQuery( 'printable=yes' );
930 }
931 }
932 return cbt_value( $url, array( 'nonview dynamic', 'title' ) );
933 }
934
935 function nav_permalink() {
936 $url = (string)$this->getPermalink();
937 return cbt_value( $url, 'dynamic' );
938 }
939
940 function nav_trackbacklink() {
941 global $wgUseTrackbacks;
942 if ( !$wgUseTrackbacks ) return '';
943
944 return cbt_value( $this->mTitle->trackbackURL(), 'title' );
945 }
946
947 function is_permalink() {
948 return cbt_value( (string)($this->getPermalink() === false), 'nonview dynamic' );
949 }
950
951 function toolboxend() {
952 // This is where the MonoBookTemplateToolboxEnd hook went in the old skin
953 return '';
954 }
955
956 function language_urls( $outer, $inner ) {
957 global $wgHideInterlanguageLinks, $wgOut, $wgContLang;
958 if ( $wgHideInterlanguageLinks ) return '';
959
960 $links = $wgOut->getLanguageLinks();
961 $s = '';
962 if ( count( $links ) ) {
963 foreach( $links as $l ) {
964 $tmp = explode( ':', $l, 2 );
965 $nt = Title::newFromText( $l );
966 $s .= strtr( $inner,
967 array(
968 '$class' => htmlspecialchars( 'interwiki-' . $tmp[0] ),
969 '$href' => htmlspecialchars( $nt->getFullURL() ),
970 '$text' => ($wgContLang->getLanguageName( $nt->getInterwiki() ) != ''?
971 $wgContLang->getLanguageName( $nt->getInterwiki() ) : $l ),
972 )
973 );
974 }
975 $s = str_replace( '$body', $s, $outer );
976 }
977 return cbt_value( $s, 'dynamic' );
978 }
979
980 function poweredbyico() { return $this->getPoweredBy(); }
981 function copyrightico() { return $this->getCopyrightIcon(); }
982
983 function lastmod() {
984 global $wgMaxCredits;
985 if ( $wgMaxCredits ) return '';
986
987 if ( !isset( $this->mLastmod ) ) {
988 if ( $this->isCurrentArticleView() ) {
989 $this->mLastmod = $this->lastModified();
990 } else {
991 $this->mLastmod = '';
992 }
993 }
994 return cbt_value( $this->mLastmod, 'dynamic' );
995 }
996
997 function viewcount() {
998 global $wgDisableCounters;
999 if ( $wgDisableCounters ) return '';
1000
1001 global $wgLang, $wgArticle;
1002 if ( is_object( $wgArticle ) ) {
1003 $viewcount = $wgLang->formatNum( $wgArticle->getCount() );
1004 if ( $viewcount ) {
1005 $viewcount = wfMsg( "viewcount", $viewcount );
1006 } else {
1007 $viewcount = '';
1008 }
1009 } else {
1010 $viewcount = '';
1011 }
1012 return cbt_value( $viewcount, 'dynamic' );
1013 }
1014
1015 function numberofwatchingusers() {
1016 global $wgPageShowWatchingUsers;
1017 if ( !$wgPageShowWatchingUsers ) return '';
1018
1019 $dbr =& wfGetDB( DB_SLAVE );
1020 extract( $dbr->tableNames( 'watchlist' ) );
1021 $sql = "SELECT COUNT(*) AS n FROM $watchlist
1022 WHERE wl_title='" . $dbr->strencode($this->mTitle->getDBKey()) .
1023 "' AND wl_namespace=" . $this->mTitle->getNamespace() ;
1024 $res = $dbr->query( $sql, 'SkinTemplate::outputPage');
1025 $row = $dbr->fetchObject( $res );
1026 $num = $row->n;
1027 if ($num > 0) {
1028 $s = wfMsg('number_of_watching_users_pageview', $num);
1029 } else {
1030 $s = '';
1031 }
1032 return cbt_value( $s, 'dynamic' );
1033 }
1034
1035 function credits() {
1036 global $wgMaxCredits;
1037 if ( !$wgMaxCredits ) return '';
1038
1039 if ( $this->isCurrentArticleView() ) {
1040 require_once("Credits.php");
1041 global $wgArticle, $wgShowCreditsIfMax;
1042 $credits = getCredits($wgArticle, $wgMaxCredits, $wgShowCreditsIfMax);
1043 } else {
1044 $credits = '';
1045 }
1046 return cbt_value( $credits, 'view dynamic' );
1047 }
1048
1049 function normalcopyright() {
1050 return $this->getCopyright( 'normal' );
1051 }
1052
1053 function historycopyright() {
1054 return $this->getCopyright( 'history' );
1055 }
1056
1057 function is_currentview() {
1058 global $wgRequest;
1059 return cbt_value( (string)$this->isCurrentArticleView(), 'view' );
1060 }
1061
1062 function usehistorycopyright() {
1063 global $wgRequest;
1064 if ( wfMsgForContent( 'history_copyright' ) == '-' ) return '';
1065
1066 $oldid = $this->getOldId();
1067 $diff = $this->getDiff();
1068 $use = (string)(!is_null( $oldid ) && is_null( $diff ));
1069 return cbt_value( $use, 'nonview dynamic' );
1070 }
1071
1072 function privacy() {
1073 return cbt_value( $this->privacyLink(), 'lang' );
1074 }
1075 function about() {
1076 return cbt_value( $this->aboutLink(), 'lang' );
1077 }
1078 function disclaimer() {
1079 return cbt_value( $this->disclaimerLink(), 'lang' );
1080 }
1081 function tagline() {
1082 # A reference to this tag existed in the old MonoBook.php, but the
1083 # template data wasn't set anywhere
1084 return '';
1085 }
1086 function reporttime() {
1087 return cbt_value( $this->mOut->reportTime(), 'dynamic' );
1088 }
1089
1090 function msg( $name ) {
1091 return cbt_value( wfMsg( $name ), 'lang' );
1092 }
1093
1094 function fallbackmsg( $name, $fallback ) {
1095 $text = wfMsg( $name );
1096 if ( wfEmptyMsg( $name, $text ) ) {
1097 $text = $fallback;
1098 }
1099 return cbt_value( $text, 'lang' );
1100 }
1101
1102 /******************************************************
1103 * Utility functions *
1104 ******************************************************/
1105
1106 /** Return true if this request is a valid, secure CSS preview */
1107 function isCssPreview() {
1108 if ( !isset( $this->mCssPreview ) ) {
1109 global $wgRequest, $wgAllowUserCss, $wgUser;
1110 $this->mCssPreview =
1111 $wgAllowUserCss &&
1112 $wgUser->isLoggedIn() &&
1113 $this->mTitle->isCssSubpage() &&
1114 $this->userCanPreview( $this->getAction() );
1115 }
1116 return $this->mCssPreview;
1117 }
1118
1119 /** Return true if this request is a valid, secure JS preview */
1120 function isJsPreview() {
1121 if ( !isset( $this->mJsPreview ) ) {
1122 global $wgRequest, $wgAllowUserJs, $wgUser;
1123 $this->mJsPreview =
1124 $wgAllowUserJs &&
1125 $wgUser->isLoggedIn() &&
1126 $this->mTitle->isJsSubpage() &&
1127 $this->userCanPreview( $this->getAction() );
1128 }
1129 return $this->mJsPreview;
1130 }
1131
1132 /** Get the title of the $wgUser's user page */
1133 function getUserPageTitle() {
1134 if ( !isset( $this->mUserPageTitle ) ) {
1135 global $wgUser;
1136 $this->mUserPageTitle = $wgUser->getUserPage();
1137 }
1138 return $this->mUserPageTitle;
1139 }
1140
1141 /** Get the text of the user page title */
1142 function getUserPageText() {
1143 if ( !isset( $this->mUserPageText ) ) {
1144 $userPage = $this->getUserPageTitle();
1145 $this->mUserPageText = $userPage->getPrefixedText();
1146 }
1147 return $this->mUserPageText;
1148 }
1149
1150 /** Make an HTML element for a stylesheet link */
1151 function makeStylesheetLink( $url ) {
1152 return '<link rel="stylesheet" type="text/css" href="' . htmlspecialchars( $url ) . "\"/>";
1153 }
1154
1155 /** Make an XHTML element for inline CSS */
1156 function makeStylesheetCdata( $style ) {
1157 return "<style type=\"text/css\"> /*<![CDATA[*/ {$style} /*]]>*/ </style>";
1158 }
1159
1160 /** Get the edit URL for this page */
1161 function getEditUrl() {
1162 if ( !isset( $this->mEditUrl ) ) {
1163 $this->mEditUrl = $this->mTitle->getLocalUrl( $this->editUrlOptions() );
1164 }
1165 return $this->mEditUrl;
1166 }
1167
1168 /** Get the prefixed DB key for this page */
1169 function getThisPDBK() {
1170 if ( !isset( $this->mThisPDBK ) ) {
1171 $this->mThisPDBK = $this->mTitle->getPrefixedDbKey();
1172 }
1173 return $this->mThisPDBK;
1174 }
1175
1176 function getThisTitleUrlForm() {
1177 if ( !isset( $this->mThisTitleUrlForm ) ) {
1178 $this->mThisTitleUrlForm = $this->mTitle->getPrefixedURL();
1179 }
1180 return $this->mThisTitleUrlForm;
1181 }
1182
1183 /**
1184 * If the current page is a user page, get the user's ID and IP. Otherwise return array(0,false)
1185 */
1186 function getUserPageIdIp() {
1187 if ( !isset( $this->mUserPageId ) ) {
1188 if( $this->mTitle->getNamespace() == NS_USER || $this->mTitle->getNamespace() == NS_USER_TALK ) {
1189 $this->mUserPageId = User::idFromName($this->mTitle->getText());
1190 $this->mUserPageIp = User::isIP($this->mTitle->getText());
1191 } else {
1192 $this->mUserPageId = 0;
1193 $this->mUserPageIp = false;
1194 }
1195 }
1196 return array( $this->mUserPageId, $this->mUserPageIp );
1197 }
1198
1199 /**
1200 * Returns a permalink URL, or false if the current page is already a
1201 * permalink, or blank if a permalink shouldn't be displayed
1202 */
1203 function getPermalink() {
1204 if ( !isset( $this->mPermalink ) ) {
1205 global $wgRequest, $wgArticle;
1206 $action = $this->getAction();
1207 $oldid = $this->getOldId();
1208 $url = '';
1209 if( $this->mTitle->getNamespace() !== NS_SPECIAL
1210 && $this->mTitle->getArticleId() != 0
1211 && ($action == '' || $action == 'view' || $action == 'purge' ) )
1212 {
1213 if ( !$oldid ) {
1214 $revid = $wgArticle->getLatest();
1215 $url = $this->mTitle->getLocalURL( "oldid=$revid" );
1216 } else {
1217 $url = false;
1218 }
1219 } else {
1220 $url = '';
1221 }
1222 }
1223 return $url;
1224 }
1225
1226 /**
1227 * Returns true if the current page is an article, not a special page,
1228 * and we are viewing a revision, not a diff
1229 */
1230 function isArticleView() {
1231 global $wgOut, $wgArticle, $wgRequest;
1232 if ( !isset( $this->mIsArticleView ) ) {
1233 $oldid = $this->getOldId();
1234 $diff = $this->getDiff();
1235 $this->mIsArticleView = $wgOut->isArticle() and
1236 (!is_null( $oldid ) or is_null( $diff )) and 0 != $wgArticle->getID();
1237 }
1238 return $this->mIsArticleView;
1239 }
1240
1241 function isCurrentArticleView() {
1242 if ( !isset( $this->mIsCurrentArticleView ) ) {
1243 global $wgOut, $wgArticle, $wgRequest;
1244 $oldid = $this->getOldId();
1245 $this->mIsCurrentArticleView = $wgOut->isArticle() && is_null( $oldid ) && 0 != $wgArticle->getID();
1246 }
1247 return $this->mIsCurrentArticleView;
1248 }
1249
1250
1251 /**
1252 * Return true if the current page is editable; if edit section on right
1253 * click should be enabled.
1254 */
1255 function isEditable() {
1256 global $wgRequest;
1257 $action = $this->getAction();
1258 return ($this->mTitle->getNamespace() != NS_SPECIAL and !($action == 'edit' or $action == 'submit'));
1259 }
1260
1261 /** Return true if the user is logged in */
1262 function isLoggedIn() {
1263 global $wgUser;
1264 return $wgUser->isLoggedIn();
1265 }
1266
1267 /** Get the local URL of the current page */
1268 function getPageUrl() {
1269 if ( !isset( $this->mPageUrl ) ) {
1270 $this->mPageUrl = $this->mTitle->getLocalURL();
1271 }
1272 return $this->mPageUrl;
1273 }
1274
1275 /** Make a link to a title using a template */
1276 function makeTemplateLink( $template, $key, $title, $text ) {
1277 $url = $title->getLocalUrl();
1278 return strtr( $template,
1279 array(
1280 '$key' => $key,
1281 '$classactive' => ($url == $this->getPageUrl()) ? 'class="active"' : '',
1282 '$class' => $title->getArticleID() == 0 ? 'class="new"' : '',
1283 '$href' => htmlspecialchars( $url ),
1284 '$text' => $text
1285 ) );
1286 }
1287
1288 /** Make a link to a URL using a template */
1289 function makeTemplateLinkUrl( $template, $key, $url, $text ) {
1290 return strtr( $template,
1291 array(
1292 '$key' => $key,
1293 '$classactive' => ($url == $this->getPageUrl()) ? 'class="active"' : '',
1294 '$class' => '',
1295 '$href' => htmlspecialchars( $url ),
1296 '$text' => $text
1297 ) );
1298 }
1299
1300 /** Make a link to a special page using a template */
1301 function makeSpecialTemplateLink( $template, $key, $specialName, $text, $query = '' ) {
1302 $url = $this->makeSpecialUrl( $specialName, $query );
1303 // Ignore the query when comparing
1304 $active = ($this->mTitle->getNamespace() == NS_SPECIAL && $this->mTitle->getDBkey() == $specialName);
1305 return strtr( $template,
1306 array(
1307 '$key' => $key,
1308 '$classactive' => $active ? 'class="active"' : '',
1309 '$class' => '',
1310 '$href' => htmlspecialchars( $url ),
1311 '$text' => $text
1312 ) );
1313 }
1314
1315 function loadRequestValues() {
1316 global $wgRequest;
1317 $this->mAction = $wgRequest->getText( 'action' );
1318 $this->mOldId = $wgRequest->getVal( 'oldid' );
1319 $this->mDiff = $wgRequest->getVal( 'diff' );
1320 $this->mSection = $wgRequest->getVal( 'section' );
1321 $this->mSearch = $wgRequest->getVal( 'search' );
1322 $this->mRequestValuesLoaded = true;
1323 }
1324
1325
1326
1327 /** Get the action parameter of the request */
1328 function getAction() {
1329 if ( !isset( $this->mRequestValuesLoaded ) ) {
1330 $this->loadRequestValues();
1331 }
1332 return $this->mAction;
1333 }
1334
1335 /** Get the oldid parameter */
1336 function getOldId() {
1337 if ( !isset( $this->mRequestValuesLoaded ) ) {
1338 $this->loadRequestValues();
1339 }
1340 return $this->mOldId;
1341 }
1342
1343 /** Get the diff parameter */
1344 function getDiff() {
1345 if ( !isset( $this->mRequestValuesLoaded ) ) {
1346 $this->loadRequestValues();
1347 }
1348 return $this->mDiff;
1349 }
1350
1351 function getSection() {
1352 if ( !isset( $this->mRequestValuesLoaded ) ) {
1353 $this->loadRequestValues();
1354 }
1355 return $this->mSection;
1356 }
1357
1358 function getSearch() {
1359 if ( !isset( $this->mRequestValuesLoaded ) ) {
1360 $this->loadRequestValues();
1361 }
1362 return $this->mSearch;
1363 }
1364
1365 /** Make a special page URL of the form [[Special:Somepage/{title_urlform}]] */
1366 function makeSpecialParamUrl( $name, $query = '', $param = '{title_urlform}' ) {
1367 // Abuse makeTitle's lax validity checking to slip a control character into the URL
1368 $title = Title::makeTitle( NS_SPECIAL, "$name/\x1a" );
1369 $url = cbt_escape( $title->getLocalURL( $query ) );
1370 // Now replace it with the parameter
1371 return str_replace( '%1A', $param, $url );
1372 }
1373
1374 function getSubjectPage() {
1375 if ( !isset( $this->mSubjectPage ) ) {
1376 $this->mSubjectPage = $this->mTitle->getSubjectPage();
1377 }
1378 return $this->mSubjectPage;
1379 }
1380
1381 function getTalkPage() {
1382 if ( !isset( $this->mTalkPage ) ) {
1383 $this->mTalkPage = $this->mTitle->getTalkPage();
1384 }
1385 return $this->mTalkPage;
1386 }
1387 }
1388 ?>