MediaWiki  REL1_20
Linker.php
Go to the documentation of this file.
00001 <?php
00030 class Linker {
00031 
00035         const TOOL_LINKS_NOBLOCK = 1;
00036         const TOOL_LINKS_EMAIL   = 2;
00037 
00047         static function getExternalLinkAttributes( $class = 'external' ) {
00048                 wfDeprecated( __METHOD__, '1.18' );
00049                 return self::getLinkAttributesInternal( '', $class );
00050         }
00051 
00063         static function getInterwikiLinkAttributes( $title, $unused = null, $class = 'external' ) {
00064                 global $wgContLang;
00065 
00066                 # @todo FIXME: We have a whole bunch of handling here that doesn't happen in
00067                 # getExternalLinkAttributes, why?
00068                 $title = urldecode( $title );
00069                 $title = $wgContLang->checkTitleEncoding( $title );
00070                 $title = preg_replace( '/[\\x00-\\x1f]/', ' ', $title );
00071 
00072                 return self::getLinkAttributesInternal( $title, $class );
00073         }
00074 
00085         static function getInternalLinkAttributes( $title, $unused = null, $class = '' ) {
00086                 $title = urldecode( $title );
00087                 $title = str_replace( '_', ' ', $title );
00088                 return self::getLinkAttributesInternal( $title, $class );
00089         }
00090 
00102         static function getInternalLinkAttributesObj( $nt, $unused = null, $class = '', $title = false ) {
00103                 if ( $title === false ) {
00104                         $title = $nt->getPrefixedText();
00105                 }
00106                 return self::getLinkAttributesInternal( $title, $class );
00107         }
00108 
00117         private static function getLinkAttributesInternal( $title, $class ) {
00118                 $title = htmlspecialchars( $title );
00119                 $class = htmlspecialchars( $class );
00120                 $r = '';
00121                 if ( $class != '' ) {
00122                         $r .= " class=\"$class\"";
00123                 }
00124                 if ( $title != '' ) {
00125                         $r .= " title=\"$title\"";
00126                 }
00127                 return $r;
00128         }
00129 
00137         public static function getLinkColour( $t, $threshold ) {
00138                 $colour = '';
00139                 if ( $t->isRedirect() ) {
00140                         # Page is a redirect
00141                         $colour = 'mw-redirect';
00142                 } elseif ( $threshold > 0 && $t->isContentPage() &&
00143                         $t->exists() && $t->getLength() < $threshold
00144                 ) {
00145                         # Page is a stub
00146                         $colour = 'stub';
00147                 }
00148                 return $colour;
00149         }
00150 
00193         public static function link(
00194                 $target, $html = null, $customAttribs = array(), $query = array(), $options = array()
00195         ) {
00196                 wfProfileIn( __METHOD__ );
00197                 if ( !$target instanceof Title ) {
00198                         wfProfileOut( __METHOD__ );
00199                         return "<!-- ERROR -->$html";
00200                 }
00201 
00202                 if( is_string( $query ) ) {
00203                         // some functions withing core using this still hand over query strings
00204                         wfDeprecated( __METHOD__ . ' with parameter $query as string (should be array)', '1.20' );
00205                         $query = wfCgiToArray( $query );
00206                 }
00207                 $options = (array)$options;
00208 
00209                 $dummy = new DummyLinker; // dummy linker instance for bc on the hooks
00210 
00211                 $ret = null;
00212                 if ( !wfRunHooks( 'LinkBegin', array( $dummy, $target, &$html,
00213                 &$customAttribs, &$query, &$options, &$ret ) ) ) {
00214                         wfProfileOut( __METHOD__ );
00215                         return $ret;
00216                 }
00217 
00218                 # Normalize the Title if it's a special page
00219                 $target = self::normaliseSpecialPage( $target );
00220 
00221                 # If we don't know whether the page exists, let's find out.
00222                 wfProfileIn( __METHOD__ . '-checkPageExistence' );
00223                 if ( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) {
00224                         if ( $target->isKnown() ) {
00225                                 $options[] = 'known';
00226                         } else {
00227                                 $options[] = 'broken';
00228                         }
00229                 }
00230                 wfProfileOut( __METHOD__ . '-checkPageExistence' );
00231 
00232                 $oldquery = array();
00233                 if ( in_array( "forcearticlepath", $options ) && $query ) {
00234                         $oldquery = $query;
00235                         $query = array();
00236                 }
00237 
00238                 # Note: we want the href attribute first, for prettiness.
00239                 $attribs = array( 'href' => self::linkUrl( $target, $query, $options ) );
00240                 if ( in_array( 'forcearticlepath', $options ) && $oldquery ) {
00241                         $attribs['href'] = wfAppendQuery( $attribs['href'], wfArrayToCgi( $oldquery ) );
00242                 }
00243 
00244                 $attribs = array_merge(
00245                         $attribs,
00246                         self::linkAttribs( $target, $customAttribs, $options )
00247                 );
00248                 if ( is_null( $html ) ) {
00249                         $html = self::linkText( $target );
00250                 }
00251 
00252                 $ret = null;
00253                 if ( wfRunHooks( 'LinkEnd', array( $dummy, $target, $options, &$html, &$attribs, &$ret ) ) ) {
00254                         $ret = Html::rawElement( 'a', $attribs, $html );
00255                 }
00256 
00257                 wfProfileOut( __METHOD__ );
00258                 return $ret;
00259         }
00260 
00265         public static function linkKnown(
00266                 $target, $html = null, $customAttribs = array(),
00267                 $query = array(), $options = array( 'known', 'noclasses' ) )
00268         {
00269                 return self::link( $target, $html, $customAttribs, $query, $options );
00270         }
00271 
00280         private static function linkUrl( $target, $query, $options ) {
00281                 wfProfileIn( __METHOD__ );
00282                 # We don't want to include fragments for broken links, because they
00283                 # generally make no sense.
00284                 if ( in_array( 'broken', $options ) && $target->mFragment !== '' ) {
00285                         $target = clone $target;
00286                         $target->mFragment = '';
00287                 }
00288 
00289                 # If it's a broken link, add the appropriate query pieces, unless
00290                 # there's already an action specified, or unless 'edit' makes no sense
00291                 # (i.e., for a nonexistent special page).
00292                 if ( in_array( 'broken', $options ) && empty( $query['action'] )
00293                         && !$target->isSpecialPage() ) {
00294                         $query['action'] = 'edit';
00295                         $query['redlink'] = '1';
00296                 }
00297                 $ret = $target->getLinkURL( $query );
00298                 wfProfileOut( __METHOD__ );
00299                 return $ret;
00300         }
00301 
00311         private static function linkAttribs( $target, $attribs, $options ) {
00312                 wfProfileIn( __METHOD__ );
00313                 global $wgUser;
00314                 $defaults = array();
00315 
00316                 if ( !in_array( 'noclasses', $options ) ) {
00317                         wfProfileIn( __METHOD__ . '-getClasses' );
00318                         # Now build the classes.
00319                         $classes = array();
00320 
00321                         if ( in_array( 'broken', $options ) ) {
00322                                 $classes[] = 'new';
00323                         }
00324 
00325                         if ( $target->isExternal() ) {
00326                                 $classes[] = 'extiw';
00327                         }
00328 
00329                         if ( !in_array( 'broken', $options ) ) { # Avoid useless calls to LinkCache (see r50387)
00330                                 $colour = self::getLinkColour( $target, $wgUser->getStubThreshold() );
00331                                 if ( $colour !== '' ) {
00332                                         $classes[] = $colour; # mw-redirect or stub
00333                                 }
00334                         }
00335                         if ( $classes != array() ) {
00336                                 $defaults['class'] = implode( ' ', $classes );
00337                         }
00338                         wfProfileOut( __METHOD__ . '-getClasses' );
00339                 }
00340 
00341                 # Get a default title attribute.
00342                 if ( $target->getPrefixedText() == '' ) {
00343                         # A link like [[#Foo]].  This used to mean an empty title
00344                         # attribute, but that's silly.  Just don't output a title.
00345                 } elseif ( in_array( 'known', $options ) ) {
00346                         $defaults['title'] = $target->getPrefixedText();
00347                 } else {
00348                         $defaults['title'] = wfMessage( 'red-link-title', $target->getPrefixedText() )->text();
00349                 }
00350 
00351                 # Finally, merge the custom attribs with the default ones, and iterate
00352                 # over that, deleting all "false" attributes.
00353                 $ret = array();
00354                 $merged = Sanitizer::mergeAttributes( $defaults, $attribs );
00355                 foreach ( $merged as $key => $val ) {
00356                         # A false value suppresses the attribute, and we don't want the
00357                         # href attribute to be overridden.
00358                         if ( $key != 'href' and $val !== false ) {
00359                                 $ret[$key] = $val;
00360                         }
00361                 }
00362                 wfProfileOut( __METHOD__ );
00363                 return $ret;
00364         }
00365 
00373         private static function linkText( $target ) {
00374                 # We might be passed a non-Title by make*LinkObj().  Fail gracefully.
00375                 if ( !$target instanceof Title ) {
00376                         return '';
00377                 }
00378 
00379                 # If the target is just a fragment, with no title, we return the frag-
00380                 # ment text.  Otherwise, we return the title text itself.
00381                 if ( $target->getPrefixedText() === '' && $target->getFragment() !== '' ) {
00382                         return htmlspecialchars( $target->getFragment() );
00383                 }
00384                 return htmlspecialchars( $target->getPrefixedText() );
00385         }
00386 
00400         static function makeSizeLinkObj( $size, $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
00401                 global $wgUser;
00402                 wfDeprecated( __METHOD__, '1.17' );
00403 
00404                 $threshold = $wgUser->getStubThreshold();
00405                 $colour = ( $size < $threshold ) ? 'stub' : '';
00406                 // @todo FIXME: Replace deprecated makeColouredLinkObj by link()
00407                 return self::makeColouredLinkObj( $nt, $colour, $text, $query, $trail, $prefix );
00408         }
00409 
00424         public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) {
00425                 if ( $html == '' ) {
00426                         $html = htmlspecialchars( $nt->getPrefixedText() );
00427                 }
00428                 list( $inside, $trail ) = self::splitTrail( $trail );
00429                 return "<strong class=\"selflink\">{$prefix}{$html}{$inside}</strong>{$trail}";
00430         }
00431 
00441         public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
00442                 global $wgContLang;
00443 
00444                 // First we check whether the namespace exists or not.
00445                 if ( MWNamespace::exists( $namespace ) ) {
00446                         if ( $namespace == NS_MAIN ) {
00447                                 $name = $context->msg( 'blanknamespace' )->text();
00448                         } else {
00449                                 $name = $wgContLang->getFormattedNsText( $namespace );
00450                         }
00451                         return $context->msg( 'invalidtitle-knownnamespace', $namespace, $name, $title )->text();
00452                 } else {
00453                         return $context->msg( 'invalidtitle-unknownnamespace', $namespace, $title )->text();
00454                 }
00455         }
00456 
00461         static function normaliseSpecialPage( Title $title ) {
00462                 if ( $title->isSpecialPage() ) {
00463                         list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
00464                         if ( !$name ) {
00465                                 return $title;
00466                         }
00467                         $ret = SpecialPage::getTitleFor( $name, $subpage );
00468                         $ret->mFragment = $title->getFragment();
00469                         return $ret;
00470                 } else {
00471                         return $title;
00472                 }
00473         }
00474 
00483         private static function fnamePart( $url ) {
00484                 $basename = strrchr( $url, '/' );
00485                 if ( false === $basename ) {
00486                         $basename = $url;
00487                 } else {
00488                         $basename = substr( $basename, 1 );
00489                 }
00490                 return $basename;
00491         }
00492 
00502         public static function makeExternalImage( $url, $alt = '' ) {
00503                 if ( $alt == '' ) {
00504                         $alt = self::fnamePart( $url );
00505                 }
00506                 $img = '';
00507                 $success = wfRunHooks( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
00508                 if ( !$success ) {
00509                         wfDebug( "Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}\n", true );
00510                         return $img;
00511                 }
00512                 return Html::element( 'img',
00513                         array(
00514                                 'src' => $url,
00515                                 'alt' => $alt ) );
00516         }
00517 
00554         public static function makeImageLink( /*Parser*/ $parser, Title $title, $file, $frameParams = array(),
00555                 $handlerParams = array(), $time = false, $query = "", $widthOption = null )
00556         {
00557                 $res = null;
00558                 $dummy = new DummyLinker;
00559                 if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$dummy, &$title,
00560                         &$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) {
00561                         return $res;
00562                 }
00563 
00564                 if ( $file && !$file->allowInlineDisplay() ) {
00565                         wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display\n" );
00566                         return self::link( $title );
00567                 }
00568 
00569                 // Shortcuts
00570                 $fp =& $frameParams;
00571                 $hp =& $handlerParams;
00572 
00573                 // Clean up parameters
00574                 $page = isset( $hp['page'] ) ? $hp['page'] : false;
00575                 if ( !isset( $fp['align'] ) ) {
00576                         $fp['align'] = '';
00577                 }
00578                 if ( !isset( $fp['alt'] ) ) {
00579                         $fp['alt'] = '';
00580                 }
00581                 if ( !isset( $fp['title'] ) ) {
00582                         $fp['title'] = '';
00583                 }
00584                 if ( !isset( $fp['class'] ) ) {
00585                         $fp['class'] = '';
00586                 }
00587 
00588                 $prefix = $postfix = '';
00589 
00590                 if ( 'center' == $fp['align'] ) {
00591                         $prefix  = '<div class="center">';
00592                         $postfix = '</div>';
00593                         $fp['align']   = 'none';
00594                 }
00595                 if ( $file && !isset( $hp['width'] ) ) {
00596                         if ( isset( $hp['height'] ) && $file->isVectorized() ) {
00597                                 // If its a vector image, and user only specifies height
00598                                 // we don't want it to be limited by its "normal" width.
00599                                 global $wgSVGMaxSize;
00600                                 $hp['width'] = $wgSVGMaxSize;
00601                         } else {
00602                                 $hp['width'] = $file->getWidth( $page );
00603                         }
00604 
00605                         if ( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
00606                                 global $wgThumbLimits, $wgThumbUpright;
00607                                 if ( !isset( $widthOption ) || !isset( $wgThumbLimits[$widthOption] ) ) {
00608                                         $widthOption = User::getDefaultOption( 'thumbsize' );
00609                                 }
00610 
00611                                 // Reduce width for upright images when parameter 'upright' is used
00612                                 if ( isset( $fp['upright'] ) && $fp['upright'] == 0 ) {
00613                                         $fp['upright'] = $wgThumbUpright;
00614                                 }
00615                                 // For caching health: If width scaled down due to upright parameter, round to full __0 pixel to avoid the creation of a lot of odd thumbs
00616                                 $prefWidth = isset( $fp['upright'] ) ?
00617                                         round( $wgThumbLimits[$widthOption] * $fp['upright'], -1 ) :
00618                                         $wgThumbLimits[$widthOption];
00619 
00620                                 // Use width which is smaller: real image width or user preference width
00621                                 // Unless image is scalable vector.
00622                                 if ( !isset( $hp['height'] ) && ( $hp['width'] <= 0 ||
00623                                                 $prefWidth < $hp['width'] || $file->isVectorized() ) ) {
00624                                         $hp['width'] = $prefWidth;
00625                                 }
00626                         }
00627                 }
00628 
00629                 if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) ) {
00630                         # Create a thumbnail. Alignment depends on the writing direction of
00631                         # the page content language (right-aligned for LTR languages,
00632                         # left-aligned for RTL languages)
00633                         #
00634                         # If a thumbnail width has not been provided, it is set
00635                         # to the default user option as specified in Language*.php
00636                         if ( $fp['align'] == '' ) {
00637                                 if( $parser instanceof Parser ) {
00638                                         $fp['align'] = $parser->getTargetLanguage()->alignEnd();
00639                                 } else {
00640                                         # backwards compatibility, remove with makeImageLink2()
00641                                         global $wgContLang;
00642                                         $fp['align'] = $wgContLang->alignEnd();
00643                                 }
00644                         }
00645                         return $prefix . self::makeThumbLink2( $title, $file, $fp, $hp, $time, $query ) . $postfix;
00646                 }
00647 
00648                 if ( $file && isset( $fp['frameless'] ) ) {
00649                         $srcWidth = $file->getWidth( $page );
00650                         # For "frameless" option: do not present an image bigger than the source (for bitmap-style images)
00651                         # This is the same behaviour as the "thumb" option does it already.
00652                         if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
00653                                 $hp['width'] = $srcWidth;
00654                         }
00655                 }
00656 
00657                 if ( $file && isset( $hp['width'] ) ) {
00658                         # Create a resized image, without the additional thumbnail features
00659                         $thumb = $file->transform( $hp );
00660                 } else {
00661                         $thumb = false;
00662                 }
00663 
00664                 if ( !$thumb ) {
00665                         $s = self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
00666                 } else {
00667                         $params = array(
00668                                 'alt' => $fp['alt'],
00669                                 'title' => $fp['title'],
00670                                 'valign' => isset( $fp['valign'] ) ? $fp['valign'] : false,
00671                                 'img-class' => $fp['class'] );
00672                         if ( isset( $fp['border'] ) ) {
00673                                 $params['img-class'] .= ( $params['img-class'] !== '' ) ? ' thumbborder' : 'thumbborder';
00674                         }
00675                         $params = self::getImageLinkMTOParams( $fp, $query, $parser ) + $params;
00676 
00677                         $s = $thumb->toHtml( $params );
00678                 }
00679                 if ( $fp['align'] != '' ) {
00680                         $s = "<div class=\"float{$fp['align']}\">{$s}</div>";
00681                 }
00682                 return str_replace( "\n", ' ', $prefix . $s . $postfix );
00683         }
00684 
00690         public static function makeImageLink2( Title $title, $file, $frameParams = array(),
00691                 $handlerParams = array(), $time = false, $query = "", $widthOption = null ) {
00692                 return self::makeImageLink( null, $title, $file, $frameParams,
00693                         $handlerParams, $time, $query, $widthOption );
00694         }
00695 
00703         private static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
00704                 $mtoParams = array();
00705                 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
00706                         $mtoParams['custom-url-link'] = $frameParams['link-url'];
00707                         if ( isset( $frameParams['link-target'] ) ) {
00708                                 $mtoParams['custom-target-link'] = $frameParams['link-target'];
00709                         }
00710                         if ( $parser ) {
00711                                 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
00712                                 foreach ( $extLinkAttrs as $name => $val ) {
00713                                         // Currently could include 'rel' and 'target'
00714                                         $mtoParams['parser-extlink-'.$name] = $val;
00715                                 }
00716                         }
00717                 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
00718                         $mtoParams['custom-title-link'] = self::normaliseSpecialPage( $frameParams['link-title'] );
00719                 } elseif ( !empty( $frameParams['no-link'] ) ) {
00720                         // No link
00721                 } else {
00722                         $mtoParams['desc-link'] = true;
00723                         $mtoParams['desc-query'] = $query;
00724                 }
00725                 return $mtoParams;
00726         }
00727 
00740         public static function makeThumbLinkObj( Title $title, $file, $label = '', $alt,
00741                 $align = 'right', $params = array(), $framed = false , $manualthumb = "" )
00742         {
00743                 $frameParams = array(
00744                         'alt' => $alt,
00745                         'caption' => $label,
00746                         'align' => $align
00747                 );
00748                 if ( $framed ) {
00749                         $frameParams['framed'] = true;
00750                 }
00751                 if ( $manualthumb ) {
00752                         $frameParams['manualthumb'] = $manualthumb;
00753                 }
00754                 return self::makeThumbLink2( $title, $file, $frameParams, $params );
00755         }
00756 
00766         public static function makeThumbLink2( Title $title, $file, $frameParams = array(),
00767                 $handlerParams = array(), $time = false, $query = "" )
00768         {
00769                 global $wgStylePath, $wgContLang;
00770                 $exists = $file && $file->exists();
00771 
00772                 # Shortcuts
00773                 $fp =& $frameParams;
00774                 $hp =& $handlerParams;
00775 
00776                 $page = isset( $hp['page'] ) ? $hp['page'] : false;
00777                 if ( !isset( $fp['align'] ) ) $fp['align'] = 'right';
00778                 if ( !isset( $fp['alt'] ) ) $fp['alt'] = '';
00779                 if ( !isset( $fp['title'] ) ) $fp['title'] = '';
00780                 if ( !isset( $fp['caption'] ) ) $fp['caption'] = '';
00781 
00782                 if ( empty( $hp['width'] ) ) {
00783                         // Reduce width for upright images when parameter 'upright' is used
00784                         $hp['width'] = isset( $fp['upright'] ) ? 130 : 180;
00785                 }
00786                 $thumb = false;
00787 
00788                 if ( !$exists ) {
00789                         $outerWidth = $hp['width'] + 2;
00790                 } else {
00791                         if ( isset( $fp['manualthumb'] ) ) {
00792                                 # Use manually specified thumbnail
00793                                 $manual_title = Title::makeTitleSafe( NS_FILE, $fp['manualthumb'] );
00794                                 if ( $manual_title ) {
00795                                         $manual_img = wfFindFile( $manual_title );
00796                                         if ( $manual_img ) {
00797                                                 $thumb = $manual_img->getUnscaledThumb( $hp );
00798                                         } else {
00799                                                 $exists = false;
00800                                         }
00801                                 }
00802                         } elseif ( isset( $fp['framed'] ) ) {
00803                                 // Use image dimensions, don't scale
00804                                 $thumb = $file->getUnscaledThumb( $hp );
00805                         } else {
00806                                 # Do not present an image bigger than the source, for bitmap-style images
00807                                 # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
00808                                 $srcWidth = $file->getWidth( $page );
00809                                 if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
00810                                         $hp['width'] = $srcWidth;
00811                                 }
00812                                 $thumb = $file->transform( $hp );
00813                         }
00814 
00815                         if ( $thumb ) {
00816                                 $outerWidth = $thumb->getWidth() + 2;
00817                         } else {
00818                                 $outerWidth = $hp['width'] + 2;
00819                         }
00820                 }
00821 
00822                 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
00823                 # So we don't need to pass it here in $query. However, the URL for the
00824                 # zoom icon still needs it, so we make a unique query for it. See bug 14771
00825                 $url = $title->getLocalURL( $query );
00826                 if ( $page ) {
00827                         $url = wfAppendQuery( $url, 'page=' . urlencode( $page ) );
00828                 }
00829 
00830                 $s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
00831                 if ( !$exists ) {
00832                         $s .= self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
00833                         $zoomIcon = '';
00834                 } elseif ( !$thumb ) {
00835                         $s .= wfMessage( 'thumbnail_error', '' )->escaped();
00836                         $zoomIcon = '';
00837                 } else {
00838                         $params = array(
00839                                 'alt' => $fp['alt'],
00840                                 'title' => $fp['title'],
00841                                 'img-class' => ( isset( $fp['class'] ) && $fp['class'] !== '' ) ? $fp['class'] . ' thumbimage' : 'thumbimage'
00842                         );
00843                         $params = self::getImageLinkMTOParams( $fp, $query ) + $params;
00844                         $s .= $thumb->toHtml( $params );
00845                         if ( isset( $fp['framed'] ) ) {
00846                                 $zoomIcon = "";
00847                         } else {
00848                                 $zoomIcon = Html::rawElement( 'div', array( 'class' => 'magnify' ),
00849                                         Html::rawElement( 'a', array(
00850                                                 'href' => $url,
00851                                                 'class' => 'internal',
00852                                                 'title' => wfMessage( 'thumbnail-more' )->text() ),
00853                                                 Html::element( 'img', array(
00854                                                         'src' => $wgStylePath . '/common/images/magnify-clip' . ( $wgContLang->isRTL() ? '-rtl' : '' ) . '.png',
00855                                                         'width' => 15,
00856                                                         'height' => 11,
00857                                                         'alt' => "" ) ) ) );
00858                         }
00859                 }
00860                 $s .= '  <div class="thumbcaption">' . $zoomIcon . $fp['caption'] . "</div></div></div>";
00861                 return str_replace( "\n", ' ', $s );
00862         }
00863 
00875         public static function makeBrokenImageLinkObj( $title, $label = '', $query = '', $unused1 = '', $unused2 = '', $time = false ) {
00876                 global $wgEnableUploads, $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
00877                 if ( ! $title instanceof Title ) {
00878                         return "<!-- ERROR -->" . htmlspecialchars( $label );
00879                 }
00880                 wfProfileIn( __METHOD__ );
00881                 if ( $label == '' ) {
00882                         $label = $title->getPrefixedText();
00883                 }
00884                 $encLabel = htmlspecialchars( $label );
00885                 $currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
00886 
00887                 if ( ( $wgUploadMissingFileUrl || $wgUploadNavigationUrl || $wgEnableUploads ) && !$currentExists ) {
00888                         $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
00889 
00890                         if ( $redir ) {
00891                                 wfProfileOut( __METHOD__ );
00892                                 return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
00893                         }
00894 
00895                         $href = self::getUploadUrl( $title, $query );
00896 
00897                         wfProfileOut( __METHOD__ );
00898                         return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
00899                                 htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
00900                                 $encLabel . '</a>';
00901                 } else {
00902                         wfProfileOut( __METHOD__ );
00903                         return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
00904                 }
00905         }
00906 
00914         protected static function getUploadUrl( $destFile, $query = '' ) {
00915                 global $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
00916                 $q = 'wpDestFile=' . $destFile->getPartialUrl();
00917                 if ( $query != '' )
00918                         $q .= '&' . $query;
00919 
00920                 if ( $wgUploadMissingFileUrl ) {
00921                         return wfAppendQuery( $wgUploadMissingFileUrl, $q );
00922                 } elseif( $wgUploadNavigationUrl ) {
00923                         return wfAppendQuery( $wgUploadNavigationUrl, $q );
00924                 } else {
00925                         $upload = SpecialPage::getTitleFor( 'Upload' );
00926                         return $upload->getLocalUrl( $q );
00927                 }
00928         }
00929 
00938         public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
00939                 $img = wfFindFile( $title, array( 'time' => $time ) );
00940                 return self::makeMediaLinkFile( $title, $img, $html );
00941         }
00942 
00954         public static function makeMediaLinkFile( Title $title, $file, $html = '' ) {
00955                 if ( $file && $file->exists() ) {
00956                         $url = $file->getURL();
00957                         $class = 'internal';
00958                 } else {
00959                         $url = self::getUploadUrl( $title );
00960                         $class = 'new';
00961                 }
00962                 $alt = htmlspecialchars( $title->getText(), ENT_QUOTES );
00963                 if ( $html == '' ) {
00964                         $html = $alt;
00965                 }
00966                 $u = htmlspecialchars( $url );
00967                 return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$html}</a>";
00968         }
00969 
00977         public static function specialLink( $name, $key = '' ) {
00978                 if ( $key == '' ) {
00979                         $key = strtolower( $name );
00980                 }
00981 
00982                 return self::linkKnown( SpecialPage::getTitleFor( $name ) , wfMessage( $key )->text() );
00983         }
00984 
00994         public static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) {
00995                 $class = "external";
00996                 if ( $linktype ) {
00997                         $class .= " $linktype";
00998                 }
00999                 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
01000                         $class .= " {$attribs['class']}";
01001                 }
01002                 $attribs['class'] = $class;
01003 
01004                 if ( $escape ) {
01005                         $text = htmlspecialchars( $text );
01006                 }
01007                 $link = '';
01008                 $success = wfRunHooks( 'LinkerMakeExternalLink',
01009                         array( &$url, &$text, &$link, &$attribs, $linktype ) );
01010                 if ( !$success ) {
01011                         wfDebug( "Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true );
01012                         return $link;
01013                 }
01014                 $attribs['href'] = $url;
01015                 return Html::rawElement( 'a', $attribs, $text );
01016         }
01017 
01026         public static function userLink( $userId, $userName, $altUserName = false ) {
01027                 if ( $userId == 0 ) {
01028                         $page = SpecialPage::getTitleFor( 'Contributions', $userName );
01029                 } else {
01030                         $page = Title::makeTitle( NS_USER, $userName );
01031                 }
01032 
01033                 return self::link(
01034                         $page,
01035                         htmlspecialchars( $altUserName !== false ? $altUserName : $userName ),
01036                         array( 'class' => 'mw-userlink' )
01037                 );
01038         }
01039 
01051         public static function userToolLinks(
01052                 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null
01053         ) {
01054                 global $wgUser, $wgDisableAnonTalk, $wgLang;
01055                 $talkable = !( $wgDisableAnonTalk && 0 == $userId );
01056                 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
01057                 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
01058 
01059                 $items = array();
01060                 if ( $talkable ) {
01061                         $items[] = self::userTalkLink( $userId, $userText );
01062                 }
01063                 if ( $userId ) {
01064                         // check if the user has an edit
01065                         $attribs = array();
01066                         if ( $redContribsWhenNoEdits ) {
01067                                 $count = !is_null( $edits ) ? $edits : User::edits( $userId );
01068                                 if ( $count == 0 ) {
01069                                         $attribs['class'] = 'new';
01070                                 }
01071                         }
01072                         $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
01073 
01074                         $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
01075                 }
01076                 if ( $blockable && $wgUser->isAllowed( 'block' ) ) {
01077                         $items[] = self::blockLink( $userId, $userText );
01078                 }
01079 
01080                 if ( $addEmailLink && $wgUser->canSendEmail() ) {
01081                         $items[] = self::emailLink( $userId, $userText );
01082                 }
01083 
01084                 wfRunHooks( 'UserToolLinksEdit', array( $userId, $userText, &$items ) );
01085 
01086                 if ( $items ) {
01087                         return wfMessage( 'word-separator' )->plain()
01088                                 . '<span class="mw-usertoollinks">'
01089                                 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
01090                                 . '</span>';
01091                 } else {
01092                         return '';
01093                 }
01094         }
01095 
01103         public static function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
01104                 return self::userToolLinks( $userId, $userText, true, 0, $edits );
01105         }
01106 
01107 
01113         public static function userTalkLink( $userId, $userText ) {
01114                 $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
01115                 $userTalkLink = self::link( $userTalkPage, wfMessage( 'talkpagelinktext' )->escaped() );
01116                 return $userTalkLink;
01117         }
01118 
01124         public static function blockLink( $userId, $userText ) {
01125                 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
01126                 $blockLink = self::link( $blockPage, wfMessage( 'blocklink' )->escaped() );
01127                 return $blockLink;
01128         }
01129 
01135         public static function emailLink( $userId, $userText ) {
01136                 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
01137                 $emailLink = self::link( $emailPage, wfMessage( 'emaillink' )->escaped() );
01138                 return $emailLink;
01139         }
01140 
01147         public static function revUserLink( $rev, $isPublic = false ) {
01148                 if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
01149                         $link = wfMessage( 'rev-deleted-user' )->escaped();
01150                 } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
01151                         $link = self::userLink( $rev->getUser( Revision::FOR_THIS_USER ),
01152                                 $rev->getUserText( Revision::FOR_THIS_USER ) );
01153                 } else {
01154                         $link = wfMessage( 'rev-deleted-user' )->escaped();
01155                 }
01156                 if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
01157                         return '<span class="history-deleted">' . $link . '</span>';
01158                 }
01159                 return $link;
01160         }
01161 
01168         public static function revUserTools( $rev, $isPublic = false ) {
01169                 if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
01170                         $link = wfMessage( 'rev-deleted-user' )->escaped();
01171                 } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
01172                         $userId = $rev->getUser( Revision::FOR_THIS_USER );
01173                         $userText = $rev->getUserText( Revision::FOR_THIS_USER );
01174                         $link = self::userLink( $userId, $userText )
01175                                 . wfMessage( 'word-separator' )->plain()
01176                                 . self::userToolLinks( $userId, $userText );
01177                 } else {
01178                         $link = wfMessage( 'rev-deleted-user' )->escaped();
01179                 }
01180                 if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
01181                         return ' <span class="history-deleted">' . $link . '</span>';
01182                 }
01183                 return $link;
01184         }
01185 
01203         public static function formatComment( $comment, $title = null, $local = false ) {
01204                 wfProfileIn( __METHOD__ );
01205 
01206                 # Sanitize text a bit:
01207                 $comment = str_replace( "\n", " ", $comment );
01208                 # Allow HTML entities (for bug 13815)
01209                 $comment = Sanitizer::escapeHtmlAllowEntities( $comment );
01210 
01211                 # Render autocomments and make links:
01212                 $comment = self::formatAutocomments( $comment, $title, $local );
01213                 $comment = self::formatLinksInComment( $comment, $title, $local );
01214 
01215                 wfProfileOut( __METHOD__ );
01216                 return $comment;
01217         }
01218 
01222         static $autocommentTitle;
01223         static $autocommentLocal;
01224 
01237         private static function formatAutocomments( $comment, $title = null, $local = false ) {
01238                 // Bah!
01239                 self::$autocommentTitle = $title;
01240                 self::$autocommentLocal = $local;
01241                 $comment = preg_replace_callback(
01242                         '!(.*)/\*\s*(.*?)\s*\*/(.*)!',
01243                         array( 'Linker', 'formatAutocommentsCallback' ),
01244                         $comment );
01245                 self::$autocommentTitle = null;
01246                 self::$autocommentLocal = null;
01247                 return $comment;
01248         }
01249 
01254         private static function formatAutocommentsCallback( $match ) {
01255                 global $wgLang;
01256                 $title = self::$autocommentTitle;
01257                 $local = self::$autocommentLocal;
01258 
01259                 $pre = $match[1];
01260                 $auto = $match[2];
01261                 $post = $match[3];
01262                 $comment = null;
01263                 wfRunHooks( 'FormatAutocomments', array( &$comment, $pre, $auto, $post, $title, $local ) );
01264                 if ( $comment === null ) {
01265                         $link = '';
01266                         if ( $title ) {
01267                                 $section = $auto;
01268 
01269                                 # Remove links that a user may have manually put in the autosummary
01270                                 # This could be improved by copying as much of Parser::stripSectionName as desired.
01271                                 $section = str_replace( '[[:', '', $section );
01272                                 $section = str_replace( '[[', '', $section );
01273                                 $section = str_replace( ']]', '', $section );
01274 
01275                                 $section = Sanitizer::normalizeSectionNameWhitespace( $section ); # bug 22784
01276                                 if ( $local ) {
01277                                         $sectionTitle = Title::newFromText( '#' . $section );
01278                                 } else {
01279                                         $sectionTitle = Title::makeTitleSafe( $title->getNamespace(),
01280                                                 $title->getDBkey(), $section );
01281                                 }
01282                                 if ( $sectionTitle ) {
01283                                         $link = self::link( $sectionTitle,
01284                                                 $wgLang->getArrow(), array(), array(),
01285                                                 'noclasses' );
01286                                 } else {
01287                                         $link = '';
01288                                 }
01289                         }
01290                         if ( $pre ) {
01291                                 # written summary $presep autocomment (summary /* section */)
01292                                 $pre .= wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped();
01293                         }
01294                         if ( $post ) {
01295                                 # autocomment $postsep written summary (/* section */ summary)
01296                                 $auto .= wfMessage( 'colon-separator' )->inContentLanguage()->escaped();
01297                         }
01298                         $auto = '<span class="autocomment">' . $auto . '</span>';
01299                         $comment = $pre . $link . $wgLang->getDirMark() . '<span dir="auto">' . $auto . $post . '</span>';
01300                 }
01301                 return $comment;
01302         }
01303 
01307         static $commentContextTitle;
01308         static $commentLocal;
01309 
01320         public static function formatLinksInComment( $comment, $title = null, $local = false ) {
01321                 self::$commentContextTitle = $title;
01322                 self::$commentLocal = $local;
01323                 $html = preg_replace_callback(
01324                         '/
01325                                 \[\[
01326                                 :? # ignore optional leading colon
01327                                 ([^\]|]+) # 1. link target; page names cannot include ] or |
01328                                 (?:\|
01329                                         # 2. a pipe-separated substring; only the last is captured
01330                                         # Stop matching at | and ]] without relying on backtracking.
01331                                         ((?:]?[^\]|])*+)
01332                                 )*
01333                                 \]\]
01334                                 ([^[]*) # 3. link trail (the text up until the next link)
01335                         /x',
01336                         array( 'Linker', 'formatLinksInCommentCallback' ),
01337                         $comment );
01338                 self::$commentContextTitle = null;
01339                 self::$commentLocal = null;
01340                 return $html;
01341         }
01342 
01347         protected static function formatLinksInCommentCallback( $match ) {
01348                 global $wgContLang;
01349 
01350                 $medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
01351                 $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):';
01352 
01353                 $comment = $match[0];
01354 
01355                 # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
01356                 if ( strpos( $match[1], '%' ) !== false ) {
01357                         $match[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $match[1] ) );
01358                 }
01359 
01360                 # Handle link renaming [[foo|text]] will show link as "text"
01361                 if ( $match[2] != "" ) {
01362                         $text = $match[2];
01363                 } else {
01364                         $text = $match[1];
01365                 }
01366                 $submatch = array();
01367                 $thelink = null;
01368                 if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
01369                         # Media link; trail not supported.
01370                         $linkRegexp = '/\[\[(.*?)\]\]/';
01371                         $title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
01372                         if ( $title ) {
01373                                 $thelink = self::makeMediaLinkObj( $title, $text );
01374                         }
01375                 } else {
01376                         # Other kind of link
01377                         if ( preg_match( $wgContLang->linkTrail(), $match[3], $submatch ) ) {
01378                                 $trail = $submatch[1];
01379                         } else {
01380                                 $trail = "";
01381                         }
01382                         $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
01383                         if ( isset( $match[1][0] ) && $match[1][0] == ':' )
01384                                 $match[1] = substr( $match[1], 1 );
01385                         list( $inside, $trail ) = self::splitTrail( $trail );
01386 
01387                         $linkText = $text;
01388                         $linkTarget = self::normalizeSubpageLink( self::$commentContextTitle,
01389                                 $match[1], $linkText );
01390 
01391                         $target = Title::newFromText( $linkTarget );
01392                         if ( $target ) {
01393                                 if ( $target->getText() == '' && $target->getInterwiki() === ''
01394                                         && !self::$commentLocal && self::$commentContextTitle )
01395                                 {
01396                                         $newTarget = clone ( self::$commentContextTitle );
01397                                         $newTarget->setFragment( '#' . $target->getFragment() );
01398                                         $target = $newTarget;
01399                                 }
01400                                 $thelink = self::link(
01401                                         $target,
01402                                         $linkText . $inside
01403                                 ) . $trail;
01404                         }
01405                 }
01406                 if ( $thelink ) {
01407                         // If the link is still valid, go ahead and replace it in!
01408                         $comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 );
01409                 }
01410 
01411                 return $comment;
01412         }
01413 
01420         public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
01421                 # Valid link forms:
01422                 # Foobar -- normal
01423                 # :Foobar -- override special treatment of prefix (images, language links)
01424                 # /Foobar -- convert to CurrentPage/Foobar
01425                 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
01426                 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
01427                 # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage
01428 
01429                 wfProfileIn( __METHOD__ );
01430                 $ret = $target; # default return value is no change
01431 
01432                 # Some namespaces don't allow subpages,
01433                 # so only perform processing if subpages are allowed
01434                 if ( $contextTitle && MWNamespace::hasSubpages( $contextTitle->getNamespace() ) ) {
01435                         $hash = strpos( $target, '#' );
01436                         if ( $hash !== false ) {
01437                                 $suffix = substr( $target, $hash );
01438                                 $target = substr( $target, 0, $hash );
01439                         } else {
01440                                 $suffix = '';
01441                         }
01442                         # bug 7425
01443                         $target = trim( $target );
01444                         # Look at the first character
01445                         if ( $target != '' && $target[0] === '/' ) {
01446                                 # / at end means we don't want the slash to be shown
01447                                 $m = array();
01448                                 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
01449                                 if ( $trailingSlashes ) {
01450                                         $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
01451                                 } else {
01452                                         $noslash = substr( $target, 1 );
01453                                 }
01454 
01455                                 $ret = $contextTitle->getPrefixedText() . '/' . trim( $noslash ) . $suffix;
01456                                 if ( $text === '' ) {
01457                                         $text = $target . $suffix;
01458                                 } # this might be changed for ugliness reasons
01459                         } else {
01460                                 # check for .. subpage backlinks
01461                                 $dotdotcount = 0;
01462                                 $nodotdot = $target;
01463                                 while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
01464                                         ++$dotdotcount;
01465                                         $nodotdot = substr( $nodotdot, 3 );
01466                                 }
01467                                 if ( $dotdotcount > 0 ) {
01468                                         $exploded = explode( '/', $contextTitle->GetPrefixedText() );
01469                                         if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
01470                                                 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
01471                                                 # / at the end means don't show full path
01472                                                 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
01473                                                         $nodotdot = substr( $nodotdot, 0, -1 );
01474                                                         if ( $text === '' ) {
01475                                                                 $text = $nodotdot . $suffix;
01476                                                         }
01477                                                 }
01478                                                 $nodotdot = trim( $nodotdot );
01479                                                 if ( $nodotdot != '' ) {
01480                                                         $ret .= '/' . $nodotdot;
01481                                                 }
01482                                                 $ret .= $suffix;
01483                                         }
01484                                 }
01485                         }
01486                 }
01487 
01488                 wfProfileOut( __METHOD__ );
01489                 return $ret;
01490         }
01491 
01502         public static function commentBlock( $comment, $title = null, $local = false ) {
01503                 // '*' used to be the comment inserted by the software way back
01504                 // in antiquity in case none was provided, here for backwards
01505                 // compatability, acc. to brion -ævar
01506                 if ( $comment == '' || $comment == '*' ) {
01507                         return '';
01508                 } else {
01509                         $formatted = self::formatComment( $comment, $title, $local );
01510                         $formatted = wfMessage( 'parentheses' )->rawParams( $formatted )->escaped();
01511                         return " <span class=\"comment\">$formatted</span>";
01512                 }
01513         }
01514 
01524         public static function revComment( Revision $rev, $local = false, $isPublic = false ) {
01525                 if ( $rev->getRawComment() == "" ) {
01526                         return "";
01527                 }
01528                 if ( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
01529                         $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
01530                 } elseif ( $rev->userCan( Revision::DELETED_COMMENT ) ) {
01531                         $block = self::commentBlock( $rev->getComment( Revision::FOR_THIS_USER ),
01532                                 $rev->getTitle(), $local );
01533                 } else {
01534                         $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
01535                 }
01536                 if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
01537                         return " <span class=\"history-deleted\">$block</span>";
01538                 }
01539                 return $block;
01540         }
01541 
01546         public static function formatRevisionSize( $size ) {
01547                 if ( $size == 0 ) {
01548                         $stxt = wfMessage( 'historyempty' )->escaped();
01549                 } else {
01550                         $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
01551                         $stxt = wfMessage( 'parentheses' )->rawParams( $stxt )->escaped();
01552                 }
01553                 return "<span class=\"history-size\">$stxt</span>";
01554         }
01555 
01561         public static function tocIndent() {
01562                 return "\n<ul>";
01563         }
01564 
01570         public static function tocUnindent( $level ) {
01571                 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
01572         }
01573 
01579         public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
01580                 $classes = "toclevel-$level";
01581                 if ( $sectionIndex !== false ) {
01582                         $classes .= " tocsection-$sectionIndex";
01583                 }
01584                 return "\n<li class=\"$classes\"><a href=\"#" .
01585                         $anchor . '"><span class="tocnumber">' .
01586                         $tocnumber . '</span> <span class="toctext">' .
01587                         $tocline . '</span></a>';
01588         }
01589 
01596         public static function tocLineEnd() {
01597                 return "</li>\n";
01598         }
01599 
01607         public static function tocList( $toc, $lang = false ) {
01608                 $lang = wfGetLangObj( $lang );
01609                 $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
01610 
01611                 return
01612                    '<table id="toc" class="toc"><tr><td>'
01613                  . '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
01614                  . $toc
01615                  . "</ul>\n</td></tr></table>\n";
01616         }
01617 
01625         public static function generateTOC( $tree ) {
01626                 $toc = '';
01627                 $lastLevel = 0;
01628                 foreach ( $tree as $section ) {
01629                         if ( $section['toclevel'] > $lastLevel )
01630                                 $toc .= self::tocIndent();
01631                         elseif ( $section['toclevel'] < $lastLevel )
01632                                 $toc .= self::tocUnindent(
01633                                         $lastLevel - $section['toclevel'] );
01634                         else
01635                                 $toc .= self::tocLineEnd();
01636 
01637                         $toc .= self::tocLine( $section['anchor'],
01638                                 $section['line'], $section['number'],
01639                                 $section['toclevel'], $section['index'] );
01640                         $lastLevel = $section['toclevel'];
01641                 }
01642                 $toc .= self::tocLineEnd();
01643                 return self::tocList( $toc );
01644         }
01645 
01661         public static function makeHeadline( $level, $attribs, $anchor, $html, $link, $legacyAnchor = false ) {
01662                 $ret = "<h$level$attribs"
01663                         . $link
01664                         . " <span class=\"mw-headline\" id=\"$anchor\">$html</span>"
01665                         . "</h$level>";
01666                 if ( $legacyAnchor !== false ) {
01667                         $ret = "<div id=\"$legacyAnchor\"></div>$ret";
01668                 }
01669                 return $ret;
01670         }
01671 
01677         static function splitTrail( $trail ) {
01678                 global $wgContLang;
01679                 $regex = $wgContLang->linkTrail();
01680                 $inside = '';
01681                 if ( $trail !== '' ) {
01682                         $m = array();
01683                         if ( preg_match( $regex, $trail, $m ) ) {
01684                                 $inside = $m[1];
01685                                 $trail = $m[2];
01686                         }
01687                 }
01688                 return array( $inside, $trail );
01689         }
01690 
01706         public static function generateRollback( $rev, IContextSource $context = null ) {
01707                 if ( $context === null ) {
01708                         $context = RequestContext::getMain();
01709                 }
01710 
01711                 return '<span class="mw-rollback-link">'
01712                         . $context->msg( 'brackets' )->rawParams(
01713                                 self::buildRollbackLink( $rev, $context ) )->plain()
01714                         . '</span>';
01715         }
01716 
01724         public static function buildRollbackLink( $rev, IContextSource $context = null ) {
01725                 global $wgShowRollbackEditCount, $wgMiserMode;
01726                 
01727                 // To config which pages are effected by miser mode
01728                 $disableRollbackEditCountSpecialPage = array( 'Recentchanges', 'Watchlist' );
01729 
01730                 if ( $context === null ) {
01731                         $context = RequestContext::getMain();
01732                 }
01733 
01734                 $title = $rev->getTitle();
01735                 $query = array(
01736                         'action' => 'rollback',
01737                         'from' => $rev->getUserText(),
01738                         'token' => $context->getUser()->getEditToken( array( $title->getPrefixedText(), $rev->getUserText() ) ),
01739                 );
01740                 if ( $context->getRequest()->getBool( 'bot' ) ) {
01741                         $query['bot'] = '1';
01742                         $query['hidediff'] = '1'; // bug 15999
01743                 }
01744 
01745                 $disableRollbackEditCount = false;
01746                 if( $wgMiserMode ) {
01747                         foreach( $disableRollbackEditCountSpecialPage as $specialPage ) {
01748                                 if( $context->getTitle()->isSpecial( $specialPage ) ) {
01749                                         $disableRollbackEditCount = true;
01750                                         break;
01751                                 }
01752                         }
01753                 }
01754 
01755                 if( !$disableRollbackEditCount && is_int( $wgShowRollbackEditCount ) && $wgShowRollbackEditCount > 0 ) {
01756                         $dbr = wfGetDB( DB_SLAVE );
01757 
01758                         // Up to the value of $wgShowRollbackEditCount revisions are counted
01759                         $res = $dbr->select( 'revision',
01760                                 array( 'rev_id', 'rev_user_text' ),
01761                                 // $rev->getPage() returns null sometimes
01762                                 array( 'rev_page' => $rev->getTitle()->getArticleID() ),
01763                                 __METHOD__,
01764                                 array(  'USE INDEX' => 'page_timestamp',
01765                                         'ORDER BY' => 'rev_timestamp DESC',
01766                                         'LIMIT' => $wgShowRollbackEditCount + 1 )
01767                         );
01768 
01769                         $editCount = 0;
01770                         while( $row = $dbr->fetchObject( $res ) ) {
01771                                 if( $rev->getUserText() != $row->rev_user_text ) {
01772                                         break;
01773                                 }
01774                                 $editCount++;
01775                         }
01776 
01777                         if( $editCount > $wgShowRollbackEditCount ) {
01778                                 $editCount_output = $context->msg( 'rollbacklinkcount-morethan' )->numParams( $wgShowRollbackEditCount )->parse();
01779                         } else {
01780                                 $editCount_output = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
01781                         }
01782 
01783                         return self::link(
01784                                 $title,
01785                                 $editCount_output,
01786                                 array( 'title' => $context->msg( 'tooltip-rollback' )->text() ),
01787                                 $query,
01788                                 array( 'known', 'noclasses' )
01789                         );
01790                 } else {
01791                         return self::link(
01792                                 $title,
01793                                 $context->msg( 'rollbacklink' )->escaped(),
01794                                 array( 'title' => $context->msg( 'tooltip-rollback' )->text() ),
01795                                 $query,
01796                                 array( 'known', 'noclasses' )
01797                         );
01798                 }
01799         }
01800 
01810         public static function formatTemplates( $templates, $preview = false, $section = false ) {
01811                 wfProfileIn( __METHOD__ );
01812 
01813                 $outText = '';
01814                 if ( count( $templates ) > 0 ) {
01815                         # Do a batch existence check
01816                         $batch = new LinkBatch;
01817                         foreach ( $templates as $title ) {
01818                                 $batch->addObj( $title );
01819                         }
01820                         $batch->execute();
01821 
01822                         # Construct the HTML
01823                         $outText = '<div class="mw-templatesUsedExplanation">';
01824                         if ( $preview ) {
01825                                 $outText .= wfMessage( 'templatesusedpreview' )->numParams( count( $templates ) )
01826                                         ->parseAsBlock();
01827                         } elseif ( $section ) {
01828                                 $outText .= wfMessage( 'templatesusedsection' )->numParams( count( $templates ) )
01829                                         ->parseAsBlock();
01830                         } else {
01831                                 $outText .= wfMessage( 'templatesused' )->numParams( count( $templates ) )
01832                                         ->parseAsBlock();
01833                         }
01834                         $outText .= "</div><ul>\n";
01835 
01836                         usort( $templates, 'Title::compare' );
01837                         foreach ( $templates as $titleObj ) {
01838                                 $r = $titleObj->getRestrictions( 'edit' );
01839                                 if ( in_array( 'sysop', $r ) ) {
01840                                         $protected = wfMessage( 'template-protected' )->parse();
01841                                 } elseif ( in_array( 'autoconfirmed', $r ) ) {
01842                                         $protected = wfMessage( 'template-semiprotected' )->parse();
01843                                 } else {
01844                                         $protected = '';
01845                                 }
01846                                 if ( $titleObj->quickUserCan( 'edit' ) ) {
01847                                         $editLink = self::link(
01848                                                 $titleObj,
01849                                                 wfMessage( 'editlink' )->text(),
01850                                                 array(),
01851                                                 array( 'action' => 'edit' )
01852                                         );
01853                                 } else {
01854                                         $editLink = self::link(
01855                                                 $titleObj,
01856                                                 wfMessage( 'viewsourcelink' )->text(),
01857                                                 array(),
01858                                                 array( 'action' => 'edit' )
01859                                         );
01860                                 }
01861                                 $outText .= '<li>' . self::link( $titleObj ) . ' (' . $editLink . ') ' . $protected . '</li>';
01862                         }
01863                         $outText .= '</ul>';
01864                 }
01865                 wfProfileOut( __METHOD__  );
01866                 return $outText;
01867         }
01868 
01876         public static function formatHiddenCategories( $hiddencats ) {
01877                 wfProfileIn( __METHOD__ );
01878 
01879                 $outText = '';
01880                 if ( count( $hiddencats ) > 0 ) {
01881                         # Construct the HTML
01882                         $outText = '<div class="mw-hiddenCategoriesExplanation">';
01883                         $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
01884                         $outText .= "</div><ul>\n";
01885 
01886                         foreach ( $hiddencats as $titleObj ) {
01887                                 $outText .= '<li>' . self::link( $titleObj, null, array(), array(), 'known' ) . "</li>\n"; # If it's hidden, it must exist - no need to check with a LinkBatch
01888                         }
01889                         $outText .= '</ul>';
01890                 }
01891                 wfProfileOut( __METHOD__  );
01892                 return $outText;
01893         }
01894 
01902         public static function formatSize( $size ) {
01903                 global $wgLang;
01904                 return htmlspecialchars( $wgLang->formatSize( $size ) );
01905         }
01906 
01919         public static function titleAttrib( $name, $options = null ) {
01920                 wfProfileIn( __METHOD__ );
01921 
01922                 $message = wfMessage( "tooltip-$name" );
01923 
01924                 if ( !$message->exists() ) {
01925                         $tooltip = false;
01926                 } else {
01927                         $tooltip = $message->text();
01928                         # Compatibility: formerly some tooltips had [alt-.] hardcoded
01929                         $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
01930                         # Message equal to '-' means suppress it.
01931                         if (  $tooltip == '-' ) {
01932                                 $tooltip = false;
01933                         }
01934                 }
01935 
01936                 if ( $options == 'withaccess' ) {
01937                         $accesskey = self::accesskey( $name );
01938                         if ( $accesskey !== false ) {
01939                                 if ( $tooltip === false || $tooltip === '' ) {
01940                                         $tooltip = "[$accesskey]";
01941                                 } else {
01942                                         $tooltip .= " [$accesskey]";
01943                                 }
01944                         }
01945                 }
01946 
01947                 wfProfileOut( __METHOD__ );
01948                 return $tooltip;
01949         }
01950 
01951         static $accesskeycache;
01952 
01963         public static function accesskey( $name ) {
01964                 if ( isset( self::$accesskeycache[$name] ) ) {
01965                         return self::$accesskeycache[$name];
01966                 }
01967                 wfProfileIn( __METHOD__ );
01968 
01969                 $message = wfMessage( "accesskey-$name" );
01970 
01971                 if ( !$message->exists() ) {
01972                         $accesskey = false;
01973                 } else {
01974                         $accesskey = $message->plain();
01975                         if ( $accesskey === '' || $accesskey === '-' ) {
01976                                 # @todo FIXME: Per standard MW behavior, a value of '-' means to suppress the
01977                                 # attribute, but this is broken for accesskey: that might be a useful
01978                                 # value.
01979                                 $accesskey = false;
01980                         }
01981                 }
01982 
01983                 wfProfileOut( __METHOD__ );
01984                 return self::$accesskeycache[$name] = $accesskey;
01985         }
01986 
02000         public static function getRevDeleteLink( User $user, Revision $rev, Title $title ) {
02001                 $canHide = $user->isAllowed( 'deleterevision' );
02002                 if ( !$canHide && !( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
02003                         return '';
02004                 }
02005 
02006                 if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
02007                         return Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
02008                 } else {
02009                         if ( $rev->getId() ) {
02010                                 // RevDelete links using revision ID are stable across
02011                                 // page deletion and undeletion; use when possible.
02012                                 $query = array(
02013                                         'type'   => 'revision',
02014                                         'target' => $title->getPrefixedDBkey(),
02015                                         'ids'    => $rev->getId()
02016                                 );
02017                         } else {
02018                                 // Older deleted entries didn't save a revision ID.
02019                                 // We have to refer to these by timestamp, ick!
02020                                 $query = array(
02021                                         'type'   => 'archive',
02022                                         'target' => $title->getPrefixedDBkey(),
02023                                         'ids'    => $rev->getTimestamp()
02024                                 );
02025                         }
02026                         return Linker::revDeleteLink( $query,
02027                                 $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
02028                 }
02029         }
02030 
02041         public static function revDeleteLink( $query = array(), $restricted = false, $delete = true ) {
02042                 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
02043                 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
02044                 $html = wfMessage( $msgKey )->escaped();
02045                 $tag = $restricted ? 'strong' : 'span';
02046                 $link = self::link( $sp, $html, array(), $query, array( 'known', 'noclasses' ) );
02047                 return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), wfMessage( 'parentheses' )->rawParams( $link )->escaped() );
02048         }
02049 
02058         public static function revDeleteLinkDisabled( $delete = true ) {
02059                 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
02060                 $html = wfMessage( $msgKey )->escaped();
02061                 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
02062                 return Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), $htmlParentheses );
02063         }
02064 
02065         /* Deprecated methods */
02066 
02081         static function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
02082                 wfDeprecated( __METHOD__, '1.16' );
02083                 
02084                 $nt = Title::newFromText( $title );
02085                 if ( $nt instanceof Title ) {
02086                         return self::makeBrokenLinkObj( $nt, $text, $query, $trail );
02087                 } else {
02088                         wfDebug( 'Invalid title passed to self::makeBrokenLink(): "' . $title . "\"\n" );
02089                         return $text == '' ? $title : $text;
02090                 }
02091         }
02092 
02110         static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
02111                 # wfDeprecated( __METHOD__, '1.16' ); // See r105985 and it's revert. Somewhere still used.
02112                 
02113                 wfProfileIn( __METHOD__ );
02114                 $query = wfCgiToArray( $query );
02115                 list( $inside, $trail ) = self::splitTrail( $trail );
02116                 if ( $text === '' ) {
02117                         $text = self::linkText( $nt );
02118                 }
02119 
02120                 $ret = self::link( $nt, "$prefix$text$inside", array(), $query ) . $trail;
02121 
02122                 wfProfileOut( __METHOD__ );
02123                 return $ret;
02124         }
02125 
02142         static function makeKnownLinkObj(
02143                 $title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = ''
02144         ) {
02145                 # wfDeprecated( __METHOD__, '1.16' ); // See r105985 and it's revert. Somewhere still used.
02146                 
02147                 wfProfileIn( __METHOD__ );
02148 
02149                 if ( $text == '' ) {
02150                         $text = self::linkText( $title );
02151                 }
02152                 $attribs = Sanitizer::mergeAttributes(
02153                         Sanitizer::decodeTagAttributes( $aprops ),
02154                         Sanitizer::decodeTagAttributes( $style )
02155                 );
02156                 $query = wfCgiToArray( $query );
02157                 list( $inside, $trail ) = self::splitTrail( $trail );
02158 
02159                 $ret = self::link( $title, "$prefix$text$inside", $attribs, $query,
02160                         array( 'known', 'noclasses' ) ) . $trail;
02161 
02162                 wfProfileOut( __METHOD__ );
02163                 return $ret;
02164         }
02165 
02180         static function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
02181                 wfDeprecated( __METHOD__, '1.16' );
02182                 
02183                 wfProfileIn( __METHOD__ );
02184 
02185                 list( $inside, $trail ) = self::splitTrail( $trail );
02186                 if ( $text === '' ) {
02187                         $text = self::linkText( $title );
02188                 }
02189 
02190                 $ret = self::link( $title, "$prefix$text$inside", array(),
02191                         wfCgiToArray( $query ), 'broken' ) . $trail;
02192 
02193                 wfProfileOut( __METHOD__ );
02194                 return $ret;
02195         }
02196 
02212         static function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
02213                 wfDeprecated( __METHOD__, '1.16' );
02214                 
02215                 if ( $colour != '' ) {
02216                         $style = self::getInternalLinkAttributesObj( $nt, $text, $colour );
02217                 } else {
02218                         $style = '';
02219                 }
02220                 return self::makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style );
02221         }
02222 
02227         public static function tooltipAndAccesskeyAttribs( $name ) {
02228                 # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
02229                 # no attribute" instead of "output '' as value for attribute", this
02230                 # would be three lines.
02231                 $attribs = array(
02232                         'title' => self::titleAttrib( $name, 'withaccess' ),
02233                         'accesskey' => self::accesskey( $name )
02234                 );
02235                 if ( $attribs['title'] === false ) {
02236                         unset( $attribs['title'] );
02237                 }
02238                 if ( $attribs['accesskey'] === false ) {
02239                         unset( $attribs['accesskey'] );
02240                 }
02241                 return $attribs;
02242         }
02243 
02248         public static function tooltip( $name, $options = null ) {
02249                 # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
02250                 # no attribute" instead of "output '' as value for attribute", this
02251                 # would be two lines.
02252                 $tooltip = self::titleAttrib( $name, $options );
02253                 if ( $tooltip === false ) {
02254                         return '';
02255                 }
02256                 return Xml::expandAttributes( array(
02257                         'title' => $tooltip
02258                 ) );
02259         }
02260 }
02261 
02265 class DummyLinker {
02266 
02275         public function __call( $fname, $args ) {
02276                 return call_user_func_array( array( 'Linker', $fname ), $args );
02277         }
02278 }