MediaWiki  REL1_19
Linker.php
Go to the documentation of this file.
00001 <?php
00009 class Linker {
00010 
00014         const TOOL_LINKS_NOBLOCK = 1;
00015         const TOOL_LINKS_EMAIL   = 2;
00016 
00025         static function getExternalLinkAttributes( $class = 'external' ) {
00026                 wfDeprecated( __METHOD__, '1.18' );
00027                 return self::getLinkAttributesInternal( '', $class );
00028         }
00029 
00040         static function getInterwikiLinkAttributes( $title, $unused = null, $class = 'external' ) {
00041                 global $wgContLang;
00042 
00043                 # @todo FIXME: We have a whole bunch of handling here that doesn't happen in
00044                 # getExternalLinkAttributes, why?
00045                 $title = urldecode( $title );
00046                 $title = $wgContLang->checkTitleEncoding( $title );
00047                 $title = preg_replace( '/[\\x00-\\x1f]/', ' ', $title );
00048 
00049                 return self::getLinkAttributesInternal( $title, $class );
00050         }
00051 
00061         static function getInternalLinkAttributes( $title, $unused = null, $class = '' ) {
00062                 $title = urldecode( $title );
00063                 $title = str_replace( '_', ' ', $title );
00064                 return self::getLinkAttributesInternal( $title, $class );
00065         }
00066 
00077         static function getInternalLinkAttributesObj( $nt, $unused = null, $class = '', $title = false ) {
00078                 if ( $title === false ) {
00079                         $title = $nt->getPrefixedText();
00080                 }
00081                 return self::getLinkAttributesInternal( $title, $class );
00082         }
00083 
00092         private static function getLinkAttributesInternal( $title, $class ) {
00093                 $title = htmlspecialchars( $title );
00094                 $class = htmlspecialchars( $class );
00095                 $r = '';
00096                 if ( $class != '' ) {
00097                         $r .= " class=\"$class\"";
00098                 }
00099                 if ( $title != '' ) {
00100                         $r .= " title=\"$title\"";
00101                 }
00102                 return $r;
00103         }
00104 
00112         public static function getLinkColour( $t, $threshold ) {
00113                 $colour = '';
00114                 if ( $t->isRedirect() ) {
00115                         # Page is a redirect
00116                         $colour = 'mw-redirect';
00117                 } elseif ( $threshold > 0 &&
00118                            $t->exists() && $t->getLength() < $threshold &&
00119                            $t->isContentPage() ) {
00120                         # Page is a stub
00121                         $colour = 'stub';
00122                 }
00123                 return $colour;
00124         }
00125 
00168         public static function link(
00169                 $target, $html = null, $customAttribs = array(), $query = array(), $options = array()
00170         ) {
00171                 wfProfileIn( __METHOD__ );
00172                 if ( !$target instanceof Title ) {
00173                         wfProfileOut( __METHOD__ );
00174                         return "<!-- ERROR -->$html";
00175                 }
00176                 $options = (array)$options;
00177 
00178                 $dummy = new DummyLinker; // dummy linker instance for bc on the hooks
00179 
00180                 $ret = null;
00181                 if ( !wfRunHooks( 'LinkBegin', array( $dummy, $target, &$html,
00182                 &$customAttribs, &$query, &$options, &$ret ) ) ) {
00183                         wfProfileOut( __METHOD__ );
00184                         return $ret;
00185                 }
00186 
00187                 # Normalize the Title if it's a special page
00188                 $target = self::normaliseSpecialPage( $target );
00189 
00190                 # If we don't know whether the page exists, let's find out.
00191                 wfProfileIn( __METHOD__ . '-checkPageExistence' );
00192                 if ( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) {
00193                         if ( $target->isKnown() ) {
00194                                 $options[] = 'known';
00195                         } else {
00196                                 $options[] = 'broken';
00197                         }
00198                 }
00199                 wfProfileOut( __METHOD__ . '-checkPageExistence' );
00200 
00201                 $oldquery = array();
00202                 if ( in_array( "forcearticlepath", $options ) && $query ) {
00203                         $oldquery = $query;
00204                         $query = array();
00205                 }
00206 
00207                 # Note: we want the href attribute first, for prettiness.
00208                 $attribs = array( 'href' => self::linkUrl( $target, $query, $options ) );
00209                 if ( in_array( 'forcearticlepath', $options ) && $oldquery ) {
00210                         $attribs['href'] = wfAppendQuery( $attribs['href'], wfArrayToCgi( $oldquery ) );
00211                 }
00212 
00213                 $attribs = array_merge(
00214                         $attribs,
00215                         self::linkAttribs( $target, $customAttribs, $options )
00216                 );
00217                 if ( is_null( $html ) ) {
00218                         $html = self::linkText( $target );
00219                 }
00220 
00221                 $ret = null;
00222                 if ( wfRunHooks( 'LinkEnd', array( $dummy, $target, $options, &$html, &$attribs, &$ret ) ) ) {
00223                         $ret = Html::rawElement( 'a', $attribs, $html );
00224                 }
00225 
00226                 wfProfileOut( __METHOD__ );
00227                 return $ret;
00228         }
00229 
00233         public static function linkKnown(
00234                 $target, $html = null, $customAttribs = array(),
00235                 $query = array(), $options = array( 'known', 'noclasses' ) )
00236         {
00237                 return self::link( $target, $html, $customAttribs, $query, $options );
00238         }
00239 
00247         private static function linkUrl( $target, $query, $options ) {
00248                 wfProfileIn( __METHOD__ );
00249                 # We don't want to include fragments for broken links, because they
00250                 # generally make no sense.
00251                 if ( in_array( 'broken', $options ) && $target->mFragment !== '' ) {
00252                         $target = clone $target;
00253                         $target->mFragment = '';
00254                 }
00255 
00256                 # If it's a broken link, add the appropriate query pieces, unless
00257                 # there's already an action specified, or unless 'edit' makes no sense
00258                 # (i.e., for a nonexistent special page).
00259                 if ( in_array( 'broken', $options ) && empty( $query['action'] )
00260                         && !$target->isSpecialPage() ) {
00261                         $query['action'] = 'edit';
00262                         $query['redlink'] = '1';
00263                 }
00264                 $ret = $target->getLinkURL( $query );
00265                 wfProfileOut( __METHOD__ );
00266                 return $ret;
00267         }
00268 
00278         private static function linkAttribs( $target, $attribs, $options ) {
00279                 wfProfileIn( __METHOD__ );
00280                 global $wgUser;
00281                 $defaults = array();
00282 
00283                 if ( !in_array( 'noclasses', $options ) ) {
00284                         wfProfileIn( __METHOD__ . '-getClasses' );
00285                         # Now build the classes.
00286                         $classes = array();
00287 
00288                         if ( in_array( 'broken', $options ) ) {
00289                                 $classes[] = 'new';
00290                         }
00291 
00292                         if ( $target->isExternal() ) {
00293                                 $classes[] = 'extiw';
00294                         }
00295 
00296                         if ( !in_array( 'broken', $options ) ) { # Avoid useless calls to LinkCache (see r50387)
00297                                 $colour = self::getLinkColour( $target, $wgUser->getStubThreshold() );
00298                                 if ( $colour !== '' ) {
00299                                         $classes[] = $colour; # mw-redirect or stub
00300                                 }
00301                         }
00302                         if ( $classes != array() ) {
00303                                 $defaults['class'] = implode( ' ', $classes );
00304                         }
00305                         wfProfileOut( __METHOD__ . '-getClasses' );
00306                 }
00307 
00308                 # Get a default title attribute.
00309                 if ( $target->getPrefixedText() == '' ) {
00310                         # A link like [[#Foo]].  This used to mean an empty title
00311                         # attribute, but that's silly.  Just don't output a title.
00312                 } elseif ( in_array( 'known', $options ) ) {
00313                         $defaults['title'] = $target->getPrefixedText();
00314                 } else {
00315                         $defaults['title'] = wfMsg( 'red-link-title', $target->getPrefixedText() );
00316                 }
00317 
00318                 # Finally, merge the custom attribs with the default ones, and iterate
00319                 # over that, deleting all "false" attributes.
00320                 $ret = array();
00321                 $merged = Sanitizer::mergeAttributes( $defaults, $attribs );
00322                 foreach ( $merged as $key => $val ) {
00323                         # A false value suppresses the attribute, and we don't want the
00324                         # href attribute to be overridden.
00325                         if ( $key != 'href' and $val !== false ) {
00326                                 $ret[$key] = $val;
00327                         }
00328                 }
00329                 wfProfileOut( __METHOD__ );
00330                 return $ret;
00331         }
00332 
00340         private static function linkText( $target ) {
00341                 # We might be passed a non-Title by make*LinkObj().  Fail gracefully.
00342                 if ( !$target instanceof Title ) {
00343                         return '';
00344                 }
00345 
00346                 # If the target is just a fragment, with no title, we return the frag-
00347                 # ment text.  Otherwise, we return the title text itself.
00348                 if ( $target->getPrefixedText() === '' && $target->getFragment() !== '' ) {
00349                         return htmlspecialchars( $target->getFragment() );
00350                 }
00351                 return htmlspecialchars( $target->getPrefixedText() );
00352         }
00353 
00367         static function makeSizeLinkObj( $size, $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
00368                 global $wgUser;
00369                 wfDeprecated( __METHOD__, '1.17' );
00370 
00371                 $threshold = $wgUser->getStubThreshold();
00372                 $colour = ( $size < $threshold ) ? 'stub' : '';
00373                 // @todo FIXME: Replace deprecated makeColouredLinkObj by link()
00374                 return self::makeColouredLinkObj( $nt, $colour, $text, $query, $trail, $prefix );
00375         }
00376 
00386         public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) {
00387                 if ( $html == '' ) {
00388                         $html = htmlspecialchars( $nt->getPrefixedText() );
00389                 }
00390                 list( $inside, $trail ) = self::splitTrail( $trail );
00391                 return "<strong class=\"selflink\">{$prefix}{$html}{$inside}</strong>{$trail}";
00392         }
00393 
00398         static function normaliseSpecialPage( Title $title ) {
00399                 if ( $title->isSpecialPage() ) {
00400                         list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
00401                         if ( !$name ) {
00402                                 return $title;
00403                         }
00404                         $ret = SpecialPage::getTitleFor( $name, $subpage );
00405                         $ret->mFragment = $title->getFragment();
00406                         return $ret;
00407                 } else {
00408                         return $title;
00409                 }
00410         }
00411 
00420         private static function fnamePart( $url ) {
00421                 $basename = strrchr( $url, '/' );
00422                 if ( false === $basename ) {
00423                         $basename = $url;
00424                 } else {
00425                         $basename = substr( $basename, 1 );
00426                 }
00427                 return $basename;
00428         }
00429 
00439         public static function makeExternalImage( $url, $alt = '' ) {
00440                 if ( $alt == '' ) {
00441                         $alt = self::fnamePart( $url );
00442                 }
00443                 $img = '';
00444                 $success = wfRunHooks( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
00445                 if ( !$success ) {
00446                         wfDebug( "Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}\n", true );
00447                         return $img;
00448                 }
00449                 return Html::element( 'img',
00450                         array(
00451                                 'src' => $url,
00452                                 'alt' => $alt ) );
00453         }
00454 
00488         public static function makeImageLink2( Title $title, $file, $frameParams = array(),
00489                 $handlerParams = array(), $time = false, $query = "", $widthOption = null )
00490         {
00491                 $res = null;
00492                 $dummy = new DummyLinker;
00493                 if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$dummy, &$title,
00494                         &$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) {
00495                         return $res;
00496                 }
00497 
00498                 if ( $file && !$file->allowInlineDisplay() ) {
00499                         wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display\n" );
00500                         return self::link( $title );
00501                 }
00502 
00503                 // Shortcuts
00504                 $fp =& $frameParams;
00505                 $hp =& $handlerParams;
00506 
00507                 // Clean up parameters
00508                 $page = isset( $hp['page'] ) ? $hp['page'] : false;
00509                 if ( !isset( $fp['align'] ) ) {
00510                         $fp['align'] = '';
00511                 }
00512                 if ( !isset( $fp['alt'] ) ) {
00513                         $fp['alt'] = '';
00514                 }
00515                 if ( !isset( $fp['title'] ) ) {
00516                         $fp['title'] = '';
00517                 }
00518 
00519                 $prefix = $postfix = '';
00520 
00521                 if ( 'center' == $fp['align'] ) {
00522                         $prefix  = '<div class="center">';
00523                         $postfix = '</div>';
00524                         $fp['align']   = 'none';
00525                 }
00526                 if ( $file && !isset( $hp['width'] ) ) {
00527                         if ( isset( $hp['height'] ) && $file->isVectorized() ) {
00528                                 // If its a vector image, and user only specifies height
00529                                 // we don't want it to be limited by its "normal" width.
00530                                 global $wgSVGMaxSize;
00531                                 $hp['width'] = $wgSVGMaxSize;
00532                         } else {
00533                                 $hp['width'] = $file->getWidth( $page );
00534                         }
00535 
00536                         if ( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
00537                                 global $wgThumbLimits, $wgThumbUpright;
00538                                 if ( !isset( $widthOption ) || !isset( $wgThumbLimits[$widthOption] ) ) {
00539                                         $widthOption = User::getDefaultOption( 'thumbsize' );
00540                                 }
00541 
00542                                 // Reduce width for upright images when parameter 'upright' is used
00543                                 if ( isset( $fp['upright'] ) && $fp['upright'] == 0 ) {
00544                                         $fp['upright'] = $wgThumbUpright;
00545                                 }
00546                                 // 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
00547                                 $prefWidth = isset( $fp['upright'] ) ?
00548                                         round( $wgThumbLimits[$widthOption] * $fp['upright'], -1 ) :
00549                                         $wgThumbLimits[$widthOption];
00550 
00551                                 // Use width which is smaller: real image width or user preference width
00552                                 // Unless image is scalable vector.
00553                                 if ( !isset( $hp['height'] ) && ( $hp['width'] <= 0 ||
00554                                                 $prefWidth < $hp['width'] || $file->isVectorized() ) ) {
00555                                         $hp['width'] = $prefWidth;
00556                                 }
00557                         }
00558                 }
00559 
00560                 if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) ) {
00561                         global $wgContLang;
00562                         # Create a thumbnail. Alignment depends on language
00563                         # writing direction, # right aligned for left-to-right-
00564                         # languages ("Western languages"), left-aligned
00565                         # for right-to-left-languages ("Semitic languages")
00566                         #
00567                         # If  thumbnail width has not been provided, it is set
00568                         # to the default user option as specified in Language*.php
00569                         if ( $fp['align'] == '' ) {
00570                                 $fp['align'] = $wgContLang->alignEnd();
00571                         }
00572                         return $prefix . self::makeThumbLink2( $title, $file, $fp, $hp, $time, $query ) . $postfix;
00573                 }
00574 
00575                 if ( $file && isset( $fp['frameless'] ) ) {
00576                         $srcWidth = $file->getWidth( $page );
00577                         # For "frameless" option: do not present an image bigger than the source (for bitmap-style images)
00578                         # This is the same behaviour as the "thumb" option does it already.
00579                         if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
00580                                 $hp['width'] = $srcWidth;
00581                         }
00582                 }
00583 
00584                 if ( $file && isset( $hp['width'] ) ) {
00585                         # Create a resized image, without the additional thumbnail features
00586                         $thumb = $file->transform( $hp );
00587                 } else {
00588                         $thumb = false;
00589                 }
00590 
00591                 if ( !$thumb ) {
00592                         $s = self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
00593                 } else {
00594                         $params = array(
00595                                 'alt' => $fp['alt'],
00596                                 'title' => $fp['title'],
00597                                 'valign' => isset( $fp['valign'] ) ? $fp['valign'] : false ,
00598                                 'img-class' => isset( $fp['border'] ) ? 'thumbborder' : false );
00599                         $params = self::getImageLinkMTOParams( $fp, $query ) + $params;
00600 
00601                         $s = $thumb->toHtml( $params );
00602                 }
00603                 if ( $fp['align'] != '' ) {
00604                         $s = "<div class=\"float{$fp['align']}\">{$s}</div>";
00605                 }
00606                 return str_replace( "\n", ' ', $prefix . $s . $postfix );
00607         }
00608 
00615         private static function getImageLinkMTOParams( $frameParams, $query = '' ) {
00616                 $mtoParams = array();
00617                 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
00618                         $mtoParams['custom-url-link'] = $frameParams['link-url'];
00619                         if ( isset( $frameParams['link-target'] ) ) {
00620                                 $mtoParams['custom-target-link'] = $frameParams['link-target'];
00621                         }
00622                 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
00623                         $mtoParams['custom-title-link'] = self::normaliseSpecialPage( $frameParams['link-title'] );
00624                 } elseif ( !empty( $frameParams['no-link'] ) ) {
00625                         // No link
00626                 } else {
00627                         $mtoParams['desc-link'] = true;
00628                         $mtoParams['desc-query'] = $query;
00629                 }
00630                 return $mtoParams;
00631         }
00632 
00644         public static function makeThumbLinkObj( Title $title, $file, $label = '', $alt,
00645                 $align = 'right', $params = array(), $framed = false , $manualthumb = "" )
00646         {
00647                 $frameParams = array(
00648                         'alt' => $alt,
00649                         'caption' => $label,
00650                         'align' => $align
00651                 );
00652                 if ( $framed ) {
00653                         $frameParams['framed'] = true;
00654                 }
00655                 if ( $manualthumb ) {
00656                         $frameParams['manualthumb'] = $manualthumb;
00657                 }
00658                 return self::makeThumbLink2( $title, $file, $frameParams, $params );
00659         }
00660 
00670         public static function makeThumbLink2( Title $title, $file, $frameParams = array(),
00671                 $handlerParams = array(), $time = false, $query = "" )
00672         {
00673                 global $wgStylePath, $wgContLang;
00674                 $exists = $file && $file->exists();
00675 
00676                 # Shortcuts
00677                 $fp =& $frameParams;
00678                 $hp =& $handlerParams;
00679 
00680                 $page = isset( $hp['page'] ) ? $hp['page'] : false;
00681                 if ( !isset( $fp['align'] ) ) $fp['align'] = 'right';
00682                 if ( !isset( $fp['alt'] ) ) $fp['alt'] = '';
00683                 if ( !isset( $fp['title'] ) ) $fp['title'] = '';
00684                 if ( !isset( $fp['caption'] ) ) $fp['caption'] = '';
00685 
00686                 if ( empty( $hp['width'] ) ) {
00687                         // Reduce width for upright images when parameter 'upright' is used
00688                         $hp['width'] = isset( $fp['upright'] ) ? 130 : 180;
00689                 }
00690                 $thumb = false;
00691 
00692                 if ( !$exists ) {
00693                         $outerWidth = $hp['width'] + 2;
00694                 } else {
00695                         if ( isset( $fp['manualthumb'] ) ) {
00696                                 # Use manually specified thumbnail
00697                                 $manual_title = Title::makeTitleSafe( NS_FILE, $fp['manualthumb'] );
00698                                 if ( $manual_title ) {
00699                                         $manual_img = wfFindFile( $manual_title );
00700                                         if ( $manual_img ) {
00701                                                 $thumb = $manual_img->getUnscaledThumb( $hp );
00702                                         } else {
00703                                                 $exists = false;
00704                                         }
00705                                 }
00706                         } elseif ( isset( $fp['framed'] ) ) {
00707                                 // Use image dimensions, don't scale
00708                                 $thumb = $file->getUnscaledThumb( $hp );
00709                         } else {
00710                                 # Do not present an image bigger than the source, for bitmap-style images
00711                                 # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
00712                                 $srcWidth = $file->getWidth( $page );
00713                                 if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
00714                                         $hp['width'] = $srcWidth;
00715                                 }
00716                                 $thumb = $file->transform( $hp );
00717                         }
00718 
00719                         if ( $thumb ) {
00720                                 $outerWidth = $thumb->getWidth() + 2;
00721                         } else {
00722                                 $outerWidth = $hp['width'] + 2;
00723                         }
00724                 }
00725 
00726                 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
00727                 # So we don't need to pass it here in $query. However, the URL for the
00728                 # zoom icon still needs it, so we make a unique query for it. See bug 14771
00729                 $url = $title->getLocalURL( $query );
00730                 if ( $page ) {
00731                         $url = wfAppendQuery( $url, 'page=' . urlencode( $page ) );
00732                 }
00733 
00734                 $s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
00735                 if ( !$exists ) {
00736                         $s .= self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
00737                         $zoomIcon = '';
00738                 } elseif ( !$thumb ) {
00739                         $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
00740                         $zoomIcon = '';
00741                 } else {
00742                         $params = array(
00743                                 'alt' => $fp['alt'],
00744                                 'title' => $fp['title'],
00745                                 'img-class' => 'thumbimage' );
00746                         $params = self::getImageLinkMTOParams( $fp, $query ) + $params;
00747                         $s .= $thumb->toHtml( $params );
00748                         if ( isset( $fp['framed'] ) ) {
00749                                 $zoomIcon = "";
00750                         } else {
00751                                 $zoomIcon = Html::rawElement( 'div', array( 'class' => 'magnify' ),
00752                                         Html::rawElement( 'a', array(
00753                                                 'href' => $url,
00754                                                 'class' => 'internal',
00755                                                 'title' => wfMsg( 'thumbnail-more' ) ),
00756                                                 Html::element( 'img', array(
00757                                                         'src' => $wgStylePath . '/common/images/magnify-clip' . ( $wgContLang->isRTL() ? '-rtl' : '' ) . '.png',
00758                                                         'width' => 15,
00759                                                         'height' => 11,
00760                                                         'alt' => "" ) ) ) );
00761                         }
00762                 }
00763                 $s .= '  <div class="thumbcaption">' . $zoomIcon . $fp['caption'] . "</div></div></div>";
00764                 return str_replace( "\n", ' ', $s );
00765         }
00766 
00778         public static function makeBrokenImageLinkObj( $title, $label = '', $query = '', $unused1 = '', $unused2 = '', $time = false ) {
00779                 global $wgEnableUploads, $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
00780                 if ( ! $title instanceof Title ) {
00781                         return "<!-- ERROR -->" . htmlspecialchars( $label );
00782                 }
00783                 wfProfileIn( __METHOD__ );
00784                 if ( $label == '' ) {
00785                         $label = $title->getPrefixedText();
00786                 }
00787                 $encLabel = htmlspecialchars( $label );
00788                 $currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
00789 
00790                 if ( ( $wgUploadMissingFileUrl || $wgUploadNavigationUrl || $wgEnableUploads ) && !$currentExists ) {
00791                         $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
00792 
00793                         if ( $redir ) {
00794                                 wfProfileOut( __METHOD__ );
00795                                 return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
00796                         }
00797 
00798                         $href = self::getUploadUrl( $title, $query );
00799 
00800                         wfProfileOut( __METHOD__ );
00801                         return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
00802                                 htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
00803                                 $encLabel . '</a>';
00804                 } else {
00805                         wfProfileOut( __METHOD__ );
00806                         return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
00807                 }
00808         }
00809 
00817         protected static function getUploadUrl( $destFile, $query = '' ) {
00818                 global $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
00819                 $q = 'wpDestFile=' . $destFile->getPartialUrl();
00820                 if ( $query != '' )
00821                         $q .= '&' . $query;
00822 
00823                 if ( $wgUploadMissingFileUrl ) {
00824                         return wfAppendQuery( $wgUploadMissingFileUrl, $q );
00825                 } elseif( $wgUploadNavigationUrl ) {
00826                         return wfAppendQuery( $wgUploadNavigationUrl, $q );
00827                 } else {
00828                         $upload = SpecialPage::getTitleFor( 'Upload' );
00829                         return $upload->getLocalUrl( $q );
00830                 }
00831         }
00832 
00841         public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
00842                 $img = wfFindFile( $title, array( 'time' => $time ) );
00843                 return self::makeMediaLinkFile( $title, $img, $html );
00844         }
00845 
00857         public static function makeMediaLinkFile( Title $title, $file, $html = '' ) {
00858                 if ( $file && $file->exists() ) {
00859                         $url = $file->getURL();
00860                         $class = 'internal';
00861                 } else {
00862                         $url = self::getUploadUrl( $title );
00863                         $class = 'new';
00864                 }
00865                 $alt = htmlspecialchars( $title->getText(), ENT_QUOTES );
00866                 if ( $html == '' ) {
00867                         $html = $alt;
00868                 }
00869                 $u = htmlspecialchars( $url );
00870                 return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$html}</a>";
00871         }
00872 
00880         public static function specialLink( $name, $key = '' ) {
00881                 if ( $key == '' ) {
00882                         $key = strtolower( $name );
00883                 }
00884 
00885                 return self::linkKnown( SpecialPage::getTitleFor( $name ) , wfMsg( $key ) );
00886         }
00887 
00896         public static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) {
00897                 $class = "external";
00898                 if ( $linktype ) {
00899                         $class .= " $linktype";
00900                 }
00901                 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
00902                         $class .= " {$attribs['class']}";
00903                 }
00904                 $attribs['class'] = $class;
00905 
00906                 if ( $escape ) {
00907                         $text = htmlspecialchars( $text );
00908                 }
00909                 $link = '';
00910                 $success = wfRunHooks( 'LinkerMakeExternalLink',
00911                         array( &$url, &$text, &$link, &$attribs, $linktype ) );
00912                 if ( !$success ) {
00913                         wfDebug( "Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true );
00914                         return $link;
00915                 }
00916                 $attribs['href'] = $url;
00917                 return Html::rawElement( 'a', $attribs, $text );
00918         }
00919 
00928         public static function userLink( $userId, $userName, $altUserName = false ) {
00929                 if ( $userId == 0 ) {
00930                         $page = SpecialPage::getTitleFor( 'Contributions', $userName );
00931                 } else {
00932                         $page = Title::makeTitle( NS_USER, $userName );
00933                 }
00934 
00935                 return self::link(
00936                         $page,
00937                         htmlspecialchars( $altUserName !== false ? $altUserName : $userName ),
00938                         array( 'class' => 'mw-userlink' )
00939                 );
00940         }
00941 
00953         public static function userToolLinks(
00954                 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null
00955         ) {
00956                 global $wgUser, $wgDisableAnonTalk, $wgLang;
00957                 $talkable = !( $wgDisableAnonTalk && 0 == $userId );
00958                 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
00959                 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
00960 
00961                 $items = array();
00962                 if ( $talkable ) {
00963                         $items[] = self::userTalkLink( $userId, $userText );
00964                 }
00965                 if ( $userId ) {
00966                         // check if the user has an edit
00967                         $attribs = array();
00968                         if ( $redContribsWhenNoEdits ) {
00969                                 $count = !is_null( $edits ) ? $edits : User::edits( $userId );
00970                                 if ( $count == 0 ) {
00971                                         $attribs['class'] = 'new';
00972                                 }
00973                         }
00974                         $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
00975 
00976                         $items[] = self::link( $contribsPage, wfMsgHtml( 'contribslink' ), $attribs );
00977                 }
00978                 if ( $blockable && $wgUser->isAllowed( 'block' ) ) {
00979                         $items[] = self::blockLink( $userId, $userText );
00980                 }
00981 
00982                 if ( $addEmailLink && $wgUser->canSendEmail() ) {
00983                         $items[] = self::emailLink( $userId, $userText );
00984                 }
00985 
00986                 wfRunHooks( 'UserToolLinksEdit', array( $userId, $userText, &$items ) );
00987 
00988                 if ( $items ) {
00989                         return ' <span class="mw-usertoollinks">(' . $wgLang->pipeList( $items ) . ')</span>';
00990                 } else {
00991                         return '';
00992                 }
00993         }
00994 
01001         public static function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
01002                 return self::userToolLinks( $userId, $userText, true, 0, $edits );
01003         }
01004 
01005 
01011         public static function userTalkLink( $userId, $userText ) {
01012                 $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
01013                 $userTalkLink = self::link( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) );
01014                 return $userTalkLink;
01015         }
01016 
01022         public static function blockLink( $userId, $userText ) {
01023                 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
01024                 $blockLink = self::link( $blockPage, wfMsgHtml( 'blocklink' ) );
01025                 return $blockLink;
01026         }
01027 
01033         public static function emailLink( $userId, $userText ) {
01034                 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
01035                 $emailLink = self::link( $emailPage, wfMsgHtml( 'emaillink' ) );
01036                 return $emailLink;
01037         }
01038 
01045         public static function revUserLink( $rev, $isPublic = false ) {
01046                 if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
01047                         $link = wfMsgHtml( 'rev-deleted-user' );
01048                 } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
01049                         $link = self::userLink( $rev->getUser( Revision::FOR_THIS_USER ),
01050                                 $rev->getUserText( Revision::FOR_THIS_USER ) );
01051                 } else {
01052                         $link = wfMsgHtml( 'rev-deleted-user' );
01053                 }
01054                 if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
01055                         return '<span class="history-deleted">' . $link . '</span>';
01056                 }
01057                 return $link;
01058         }
01059 
01066         public static function revUserTools( $rev, $isPublic = false ) {
01067                 if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
01068                         $link = wfMsgHtml( 'rev-deleted-user' );
01069                 } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
01070                         $userId = $rev->getUser( Revision::FOR_THIS_USER );
01071                         $userText = $rev->getUserText( Revision::FOR_THIS_USER );
01072                         $link = self::userLink( $userId, $userText ) .
01073                                 ' ' . self::userToolLinks( $userId, $userText );
01074                 } else {
01075                         $link = wfMsgHtml( 'rev-deleted-user' );
01076                 }
01077                 if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
01078                         return ' <span class="history-deleted">' . $link . '</span>';
01079                 }
01080                 return $link;
01081         }
01082 
01099         public static function formatComment( $comment, $title = null, $local = false ) {
01100                 wfProfileIn( __METHOD__ );
01101 
01102                 # Sanitize text a bit:
01103                 $comment = str_replace( "\n", " ", $comment );
01104                 # Allow HTML entities (for bug 13815)
01105                 $comment = Sanitizer::escapeHtmlAllowEntities( $comment );
01106 
01107                 # Render autocomments and make links:
01108                 $comment = self::formatAutocomments( $comment, $title, $local );
01109                 $comment = self::formatLinksInComment( $comment, $title, $local );
01110 
01111                 wfProfileOut( __METHOD__ );
01112                 return $comment;
01113         }
01114 
01118         static $autocommentTitle;
01119         static $autocommentLocal;
01120 
01133         private static function formatAutocomments( $comment, $title = null, $local = false ) {
01134                 // Bah!
01135                 self::$autocommentTitle = $title;
01136                 self::$autocommentLocal = $local;
01137                 $comment = preg_replace_callback(
01138                         '!(.*)/\*\s*(.*?)\s*\*/(.*)!',
01139                         array( 'Linker', 'formatAutocommentsCallback' ),
01140                         $comment );
01141                 self::$autocommentTitle = null;
01142                 self::$autocommentLocal = null;
01143                 return $comment;
01144         }
01145 
01150         private static function formatAutocommentsCallback( $match ) {
01151                 global $wgLang;
01152                 $title = self::$autocommentTitle;
01153                 $local = self::$autocommentLocal;
01154 
01155                 $pre = $match[1];
01156                 $auto = $match[2];
01157                 $post = $match[3];
01158                 $link = '';
01159                 if ( $title ) {
01160                         $section = $auto;
01161 
01162                         # Remove links that a user may have manually put in the autosummary
01163                         # This could be improved by copying as much of Parser::stripSectionName as desired.
01164                         $section = str_replace( '[[:', '', $section );
01165                         $section = str_replace( '[[', '', $section );
01166                         $section = str_replace( ']]', '', $section );
01167 
01168                         $section = Sanitizer::normalizeSectionNameWhitespace( $section ); # bug 22784
01169                         if ( $local ) {
01170                                 $sectionTitle = Title::newFromText( '#' . $section );
01171                         } else {
01172                                 $sectionTitle = Title::makeTitleSafe( $title->getNamespace(),
01173                                         $title->getDBkey(), $section );
01174                         }
01175                         if ( $sectionTitle ) {
01176                                 $link = self::link( $sectionTitle,
01177                                         $wgLang->getArrow(), array(), array(),
01178                                         'noclasses' );
01179                         } else {
01180                                 $link = '';
01181                         }
01182                 }
01183                 if ( $pre ) {
01184                         # written summary $presep autocomment (summary /* section */)
01185                         $pre .= wfMsgExt( 'autocomment-prefix', array( 'escapenoentities', 'content' ) );
01186                 }
01187                 if ( $post ) {
01188                         # autocomment $postsep written summary (/* section */ summary)
01189                         $auto .= wfMsgExt( 'colon-separator', array( 'escapenoentities', 'content' ) );
01190                 }
01191                 $auto = '<span class="autocomment">' . $auto . '</span>';
01192                 $comment = $pre . $link . $wgLang->getDirMark() . '<span dir="auto">' . $auto . $post . '</span>';
01193                 return $comment;
01194         }
01195 
01199         static $commentContextTitle;
01200         static $commentLocal;
01201 
01212         public static function formatLinksInComment( $comment, $title = null, $local = false ) {
01213                 self::$commentContextTitle = $title;
01214                 self::$commentLocal = $local;
01215                 $html = preg_replace_callback(
01216                         '/
01217                                 \[\[
01218                                 :? # ignore optional leading colon
01219                                 ([^\]|]+) # 1. link target; page names cannot include ] or |
01220                                 (?:\|
01221                                         # 2. a pipe-separated substring; only the last is captured
01222                                         # Stop matching at | and ]] without relying on backtracking.
01223                                         ((?:]?[^\]|])*+)
01224                                 )*
01225                                 \]\]
01226                                 ([^[]*) # 3. link trail (the text up until the next link)
01227                         /x',
01228                         array( 'Linker', 'formatLinksInCommentCallback' ),
01229                         $comment );
01230                 self::$commentContextTitle = null;
01231                 self::$commentLocal = null;
01232                 return $html;
01233         }
01234 
01239         protected static function formatLinksInCommentCallback( $match ) {
01240                 global $wgContLang;
01241 
01242                 $medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
01243                 $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):';
01244 
01245                 $comment = $match[0];
01246 
01247                 # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
01248                 if ( strpos( $match[1], '%' ) !== false ) {
01249                         $match[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $match[1] ) );
01250                 }
01251 
01252                 # Handle link renaming [[foo|text]] will show link as "text"
01253                 if ( $match[2] != "" ) {
01254                         $text = $match[2];
01255                 } else {
01256                         $text = $match[1];
01257                 }
01258                 $submatch = array();
01259                 $thelink = null;
01260                 if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
01261                         # Media link; trail not supported.
01262                         $linkRegexp = '/\[\[(.*?)\]\]/';
01263                         $title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
01264                         if ( $title ) {
01265                                 $thelink = self::makeMediaLinkObj( $title, $text );
01266                         }
01267                 } else {
01268                         # Other kind of link
01269                         if ( preg_match( $wgContLang->linkTrail(), $match[3], $submatch ) ) {
01270                                 $trail = $submatch[1];
01271                         } else {
01272                                 $trail = "";
01273                         }
01274                         $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
01275                         if ( isset( $match[1][0] ) && $match[1][0] == ':' )
01276                                 $match[1] = substr( $match[1], 1 );
01277                         list( $inside, $trail ) = self::splitTrail( $trail );
01278 
01279                         $linkText = $text;
01280                         $linkTarget = self::normalizeSubpageLink( self::$commentContextTitle,
01281                                 $match[1], $linkText );
01282 
01283                         $target = Title::newFromText( $linkTarget );
01284                         if ( $target ) {
01285                                 if ( $target->getText() == '' && $target->getInterwiki() === ''
01286                                         && !self::$commentLocal && self::$commentContextTitle )
01287                                 {
01288                                         $newTarget = clone ( self::$commentContextTitle );
01289                                         $newTarget->setFragment( '#' . $target->getFragment() );
01290                                         $target = $newTarget;
01291                                 }
01292                                 $thelink = self::link(
01293                                         $target,
01294                                         $linkText . $inside
01295                                 ) . $trail;
01296                         }
01297                 }
01298                 if ( $thelink ) {
01299                         // If the link is still valid, go ahead and replace it in!
01300                         $comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 );
01301                 }
01302 
01303                 return $comment;
01304         }
01305 
01312         public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
01313                 # Valid link forms:
01314                 # Foobar -- normal
01315                 # :Foobar -- override special treatment of prefix (images, language links)
01316                 # /Foobar -- convert to CurrentPage/Foobar
01317                 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
01318                 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
01319                 # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage
01320 
01321                 wfProfileIn( __METHOD__ );
01322                 $ret = $target; # default return value is no change
01323 
01324                 # Some namespaces don't allow subpages,
01325                 # so only perform processing if subpages are allowed
01326                 if ( $contextTitle && MWNamespace::hasSubpages( $contextTitle->getNamespace() ) ) {
01327                         $hash = strpos( $target, '#' );
01328                         if ( $hash !== false ) {
01329                                 $suffix = substr( $target, $hash );
01330                                 $target = substr( $target, 0, $hash );
01331                         } else {
01332                                 $suffix = '';
01333                         }
01334                         # bug 7425
01335                         $target = trim( $target );
01336                         # Look at the first character
01337                         if ( $target != '' && $target[0] === '/' ) {
01338                                 # / at end means we don't want the slash to be shown
01339                                 $m = array();
01340                                 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
01341                                 if ( $trailingSlashes ) {
01342                                         $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
01343                                 } else {
01344                                         $noslash = substr( $target, 1 );
01345                                 }
01346 
01347                                 $ret = $contextTitle->getPrefixedText() . '/' . trim( $noslash ) . $suffix;
01348                                 if ( $text === '' ) {
01349                                         $text = $target . $suffix;
01350                                 } # this might be changed for ugliness reasons
01351                         } else {
01352                                 # check for .. subpage backlinks
01353                                 $dotdotcount = 0;
01354                                 $nodotdot = $target;
01355                                 while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
01356                                         ++$dotdotcount;
01357                                         $nodotdot = substr( $nodotdot, 3 );
01358                                 }
01359                                 if ( $dotdotcount > 0 ) {
01360                                         $exploded = explode( '/', $contextTitle->GetPrefixedText() );
01361                                         if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
01362                                                 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
01363                                                 # / at the end means don't show full path
01364                                                 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
01365                                                         $nodotdot = substr( $nodotdot, 0, -1 );
01366                                                         if ( $text === '' ) {
01367                                                                 $text = $nodotdot . $suffix;
01368                                                         }
01369                                                 }
01370                                                 $nodotdot = trim( $nodotdot );
01371                                                 if ( $nodotdot != '' ) {
01372                                                         $ret .= '/' . $nodotdot;
01373                                                 }
01374                                                 $ret .= $suffix;
01375                                         }
01376                                 }
01377                         }
01378                 }
01379 
01380                 wfProfileOut( __METHOD__ );
01381                 return $ret;
01382         }
01383 
01394         public static function commentBlock( $comment, $title = null, $local = false ) {
01395                 // '*' used to be the comment inserted by the software way back
01396                 // in antiquity in case none was provided, here for backwards
01397                 // compatability, acc. to brion -ævar
01398                 if ( $comment == '' || $comment == '*' ) {
01399                         return '';
01400                 } else {
01401                         $formatted = self::formatComment( $comment, $title, $local );
01402                         return " <span class=\"comment\" dir=\"auto\">($formatted)</span>";
01403                 }
01404         }
01405 
01415         public static function revComment( Revision $rev, $local = false, $isPublic = false ) {
01416                 if ( $rev->getRawComment() == "" ) {
01417                         return "";
01418                 }
01419                 if ( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
01420                         $block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
01421                 } elseif ( $rev->userCan( Revision::DELETED_COMMENT ) ) {
01422                         $block = self::commentBlock( $rev->getComment( Revision::FOR_THIS_USER ),
01423                                 $rev->getTitle(), $local );
01424                 } else {
01425                         $block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
01426                 }
01427                 if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
01428                         return " <span class=\"history-deleted\">$block</span>";
01429                 }
01430                 return $block;
01431         }
01432 
01437         public static function formatRevisionSize( $size ) {
01438                 if ( $size == 0 ) {
01439                         $stxt = wfMsgExt( 'historyempty', 'parsemag' );
01440                 } else {
01441                         global $wgLang;
01442                         $stxt = wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $size ) );
01443                         $stxt = "($stxt)";
01444                 }
01445                 $stxt = htmlspecialchars( $stxt );
01446                 return "<span class=\"history-size\">$stxt</span>";
01447         }
01448 
01454         public static function tocIndent() {
01455                 return "\n<ul>";
01456         }
01457 
01463         public static function tocUnindent( $level ) {
01464                 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
01465         }
01466 
01472         public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
01473                 $classes = "toclevel-$level";
01474                 if ( $sectionIndex !== false ) {
01475                         $classes .= " tocsection-$sectionIndex";
01476                 }
01477                 return "\n<li class=\"$classes\"><a href=\"#" .
01478                         $anchor . '"><span class="tocnumber">' .
01479                         $tocnumber . '</span> <span class="toctext">' .
01480                         $tocline . '</span></a>';
01481         }
01482 
01488         public static function tocLineEnd() {
01489                 return "</li>\n";
01490         }
01491 
01499         public static function tocList( $toc, $lang = false ) {
01500                 $title = wfMsgExt( 'toc', array( 'language' => $lang, 'escape' ) );
01501                 return
01502                    '<table id="toc" class="toc"><tr><td>'
01503                  . '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
01504                  . $toc
01505                  . "</ul>\n</td></tr></table>\n";
01506         }
01507 
01515         public static function generateTOC( $tree ) {
01516                 $toc = '';
01517                 $lastLevel = 0;
01518                 foreach ( $tree as $section ) {
01519                         if ( $section['toclevel'] > $lastLevel )
01520                                 $toc .= self::tocIndent();
01521                         elseif ( $section['toclevel'] < $lastLevel )
01522                                 $toc .= self::tocUnindent(
01523                                         $lastLevel - $section['toclevel'] );
01524                         else
01525                                 $toc .= self::tocLineEnd();
01526 
01527                         $toc .= self::tocLine( $section['anchor'],
01528                                 $section['line'], $section['number'],
01529                                 $section['toclevel'], $section['index'] );
01530                         $lastLevel = $section['toclevel'];
01531                 }
01532                 $toc .= self::tocLineEnd();
01533                 return self::tocList( $toc );
01534         }
01535 
01551         public static function makeHeadline( $level, $attribs, $anchor, $html, $link, $legacyAnchor = false ) {
01552                 $ret = "<h$level$attribs"
01553                         . $link
01554                         . " <span class=\"mw-headline\" id=\"$anchor\">$html</span>"
01555                         . "</h$level>";
01556                 if ( $legacyAnchor !== false ) {
01557                         $ret = "<div id=\"$legacyAnchor\"></div>$ret";
01558                 }
01559                 return $ret;
01560         }
01561 
01566         static function splitTrail( $trail ) {
01567                 global $wgContLang;
01568                 $regex = $wgContLang->linkTrail();
01569                 $inside = '';
01570                 if ( $trail !== '' ) {
01571                         $m = array();
01572                         if ( preg_match( $regex, $trail, $m ) ) {
01573                                 $inside = $m[1];
01574                                 $trail = $m[2];
01575                         }
01576                 }
01577                 return array( $inside, $trail );
01578         }
01579 
01593         public static function generateRollback( $rev ) {
01594                 return '<span class="mw-rollback-link">['
01595                         . self::buildRollbackLink( $rev )
01596                         . ']</span>';
01597         }
01598 
01605         public static function buildRollbackLink( $rev ) {
01606                 global $wgRequest, $wgUser;
01607                 $title = $rev->getTitle();
01608                 $query = array(
01609                         'action' => 'rollback',
01610                         'from' => $rev->getUserText(),
01611                         'token' => $wgUser->getEditToken( array( $title->getPrefixedText(), $rev->getUserText() ) ),
01612                 );
01613                 if ( $wgRequest->getBool( 'bot' ) ) {
01614                         $query['bot'] = '1';
01615                         $query['hidediff'] = '1'; // bug 15999
01616                 }
01617                 return self::link(
01618                         $title,
01619                         wfMsgHtml( 'rollbacklink' ),
01620                         array( 'title' => wfMsg( 'tooltip-rollback' ) ),
01621                         $query,
01622                         array( 'known', 'noclasses' )
01623                 );
01624         }
01625 
01635         public static function formatTemplates( $templates, $preview = false, $section = false ) {
01636                 wfProfileIn( __METHOD__ );
01637 
01638                 $outText = '';
01639                 if ( count( $templates ) > 0 ) {
01640                         # Do a batch existence check
01641                         $batch = new LinkBatch;
01642                         foreach ( $templates as $title ) {
01643                                 $batch->addObj( $title );
01644                         }
01645                         $batch->execute();
01646 
01647                         # Construct the HTML
01648                         $outText = '<div class="mw-templatesUsedExplanation">';
01649                         if ( $preview ) {
01650                                 $outText .= wfMsgExt( 'templatesusedpreview', array( 'parse' ), count( $templates ) );
01651                         } elseif ( $section ) {
01652                                 $outText .= wfMsgExt( 'templatesusedsection', array( 'parse' ), count( $templates ) );
01653                         } else {
01654                                 $outText .= wfMsgExt( 'templatesused', array( 'parse' ), count( $templates ) );
01655                         }
01656                         $outText .= "</div><ul>\n";
01657 
01658                         usort( $templates, array( 'Title', 'compare' ) );
01659                         foreach ( $templates as $titleObj ) {
01660                                 $r = $titleObj->getRestrictions( 'edit' );
01661                                 if ( in_array( 'sysop', $r ) ) {
01662                                         $protected = wfMsgExt( 'template-protected', array( 'parseinline' ) );
01663                                 } elseif ( in_array( 'autoconfirmed', $r ) ) {
01664                                         $protected = wfMsgExt( 'template-semiprotected', array( 'parseinline' ) );
01665                                 } else {
01666                                         $protected = '';
01667                                 }
01668                                 if ( $titleObj->quickUserCan( 'edit' ) ) {
01669                                         $editLink = self::link(
01670                                                 $titleObj,
01671                                                 wfMsg( 'editlink' ),
01672                                                 array(),
01673                                                 array( 'action' => 'edit' )
01674                                         );
01675                                 } else {
01676                                         $editLink = self::link(
01677                                                 $titleObj,
01678                                                 wfMsg( 'viewsourcelink' ),
01679                                                 array(),
01680                                                 array( 'action' => 'edit' )
01681                                         );
01682                                 }
01683                                 $outText .= '<li>' . self::link( $titleObj ) . ' (' . $editLink . ') ' . $protected . '</li>';
01684                         }
01685                         $outText .= '</ul>';
01686                 }
01687                 wfProfileOut( __METHOD__  );
01688                 return $outText;
01689         }
01690 
01698         public static function formatHiddenCategories( $hiddencats ) {
01699                 global $wgLang;
01700                 wfProfileIn( __METHOD__ );
01701 
01702                 $outText = '';
01703                 if ( count( $hiddencats ) > 0 ) {
01704                         # Construct the HTML
01705                         $outText = '<div class="mw-hiddenCategoriesExplanation">';
01706                         $outText .= wfMsgExt( 'hiddencategories', array( 'parse' ), $wgLang->formatnum( count( $hiddencats ) ) );
01707                         $outText .= "</div><ul>\n";
01708 
01709                         foreach ( $hiddencats as $titleObj ) {
01710                                 $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
01711                         }
01712                         $outText .= '</ul>';
01713                 }
01714                 wfProfileOut( __METHOD__  );
01715                 return $outText;
01716         }
01717 
01725         public static function formatSize( $size ) {
01726                 global $wgLang;
01727                 return htmlspecialchars( $wgLang->formatSize( $size ) );
01728         }
01729 
01742         public static function titleAttrib( $name, $options = null ) {
01743                 wfProfileIn( __METHOD__ );
01744 
01745                 $message = wfMessage( "tooltip-$name" );
01746 
01747                 if ( !$message->exists() ) {
01748                         $tooltip = false;
01749                 } else {
01750                         $tooltip = $message->text();
01751                         # Compatibility: formerly some tooltips had [alt-.] hardcoded
01752                         $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
01753                         # Message equal to '-' means suppress it.
01754                         if (  $tooltip == '-' ) {
01755                                 $tooltip = false;
01756                         }
01757                 }
01758 
01759                 if ( $options == 'withaccess' ) {
01760                         $accesskey = self::accesskey( $name );
01761                         if ( $accesskey !== false ) {
01762                                 if ( $tooltip === false || $tooltip === '' ) {
01763                                         $tooltip = "[$accesskey]";
01764                                 } else {
01765                                         $tooltip .= " [$accesskey]";
01766                                 }
01767                         }
01768                 }
01769 
01770                 wfProfileOut( __METHOD__ );
01771                 return $tooltip;
01772         }
01773 
01774         static $accesskeycache;
01775 
01786         public static function accesskey( $name ) {
01787                 if ( isset( self::$accesskeycache[$name] ) ) {
01788                         return self::$accesskeycache[$name];
01789                 }
01790                 wfProfileIn( __METHOD__ );
01791 
01792                 $message = wfMessage( "accesskey-$name" );
01793 
01794                 if ( !$message->exists() ) {
01795                         $accesskey = false;
01796                 } else {
01797                         $accesskey = $message->plain();
01798                         if ( $accesskey === '' || $accesskey === '-' ) {
01799                                 # @todo FIXME: Per standard MW behavior, a value of '-' means to suppress the
01800                                 # attribute, but this is broken for accesskey: that might be a useful
01801                                 # value.
01802                                 $accesskey = false;
01803                         }
01804                 }
01805 
01806                 wfProfileOut( __METHOD__ );
01807                 return self::$accesskeycache[$name] = $accesskey;
01808         }
01809 
01823         public static function getRevDeleteLink( User $user, Revision $rev, Title $title ) {
01824                 $canHide = $user->isAllowed( 'deleterevision' );
01825                 if ( !$canHide && !( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
01826                         return '';
01827                 }
01828 
01829                 if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
01830                         return Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
01831                 } else {
01832                         if ( $rev->getId() ) {
01833                                 // RevDelete links using revision ID are stable across
01834                                 // page deletion and undeletion; use when possible.
01835                                 $query = array(
01836                                         'type'   => 'revision',
01837                                         'target' => $title->getPrefixedDBkey(),
01838                                         'ids'    => $rev->getId()
01839                                 );
01840                         } else {
01841                                 // Older deleted entries didn't save a revision ID.
01842                                 // We have to refer to these by timestamp, ick!
01843                                 $query = array(
01844                                         'type'   => 'archive',
01845                                         'target' => $title->getPrefixedDBkey(),
01846                                         'ids'    => $rev->getTimestamp()
01847                                 );
01848                         }
01849                         return Linker::revDeleteLink( $query,
01850                                 $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
01851                 }
01852         }
01853 
01864         public static function revDeleteLink( $query = array(), $restricted = false, $delete = true ) {
01865                 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
01866                 $html = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' );
01867                 $tag = $restricted ? 'strong' : 'span';
01868                 $link = self::link( $sp, $html, array(), $query, array( 'known', 'noclasses' ) );
01869                 return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), "($link)" );
01870         }
01871 
01880         public static function revDeleteLinkDisabled( $delete = true ) {
01881                 $html = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' );
01882                 return Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), "($html)" );
01883         }
01884 
01885         /* Deprecated methods */
01886 
01900         static function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
01901                 wfDeprecated( __METHOD__, '1.16' );
01902                 
01903                 $nt = Title::newFromText( $title );
01904                 if ( $nt instanceof Title ) {
01905                         return self::makeBrokenLinkObj( $nt, $text, $query, $trail );
01906                 } else {
01907                         wfDebug( 'Invalid title passed to self::makeBrokenLink(): "' . $title . "\"\n" );
01908                         return $text == '' ? $title : $text;
01909                 }
01910         }
01911 
01928         static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
01929                 # wfDeprecated( __METHOD__, '1.16' ); // See r105985 and it's revert. Somewhere still used.
01930                 
01931                 wfProfileIn( __METHOD__ );
01932                 $query = wfCgiToArray( $query );
01933                 list( $inside, $trail ) = self::splitTrail( $trail );
01934                 if ( $text === '' ) {
01935                         $text = self::linkText( $nt );
01936                 }
01937 
01938                 $ret = self::link( $nt, "$prefix$text$inside", array(), $query ) . $trail;
01939 
01940                 wfProfileOut( __METHOD__ );
01941                 return $ret;
01942         }
01943 
01960         static function makeKnownLinkObj(
01961                 $title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = ''
01962         ) {
01963                 # wfDeprecated( __METHOD__, '1.16' ); // See r105985 and it's revert. Somewhere still used.
01964                 
01965                 wfProfileIn( __METHOD__ );
01966 
01967                 if ( $text == '' ) {
01968                         $text = self::linkText( $title );
01969                 }
01970                 $attribs = Sanitizer::mergeAttributes(
01971                         Sanitizer::decodeTagAttributes( $aprops ),
01972                         Sanitizer::decodeTagAttributes( $style )
01973                 );
01974                 $query = wfCgiToArray( $query );
01975                 list( $inside, $trail ) = self::splitTrail( $trail );
01976 
01977                 $ret = self::link( $title, "$prefix$text$inside", $attribs, $query,
01978                         array( 'known', 'noclasses' ) ) . $trail;
01979 
01980                 wfProfileOut( __METHOD__ );
01981                 return $ret;
01982         }
01983 
01997         static function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
01998                 wfDeprecated( __METHOD__, '1.16' );
01999                 
02000                 wfProfileIn( __METHOD__ );
02001 
02002                 list( $inside, $trail ) = self::splitTrail( $trail );
02003                 if ( $text === '' ) {
02004                         $text = self::linkText( $title );
02005                 }
02006 
02007                 $ret = self::link( $title, "$prefix$text$inside", array(),
02008                         wfCgiToArray( $query ), 'broken' ) . $trail;
02009 
02010                 wfProfileOut( __METHOD__ );
02011                 return $ret;
02012         }
02013 
02028         static function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
02029                 wfDeprecated( __METHOD__, '1.16' );
02030                 
02031                 if ( $colour != '' ) {
02032                         $style = self::getInternalLinkAttributesObj( $nt, $text, $colour );
02033                 } else {
02034                         $style = '';
02035                 }
02036                 return self::makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style );
02037         }
02038 
02042         public static function tooltipAndAccesskeyAttribs( $name ) {
02043                 # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
02044                 # no attribute" instead of "output '' as value for attribute", this
02045                 # would be three lines.
02046                 $attribs = array(
02047                         'title' => self::titleAttrib( $name, 'withaccess' ),
02048                         'accesskey' => self::accesskey( $name )
02049                 );
02050                 if ( $attribs['title'] === false ) {
02051                         unset( $attribs['title'] );
02052                 }
02053                 if ( $attribs['accesskey'] === false ) {
02054                         unset( $attribs['accesskey'] );
02055                 }
02056                 return $attribs;
02057         }
02058 
02062         public static function tooltip( $name, $options = null ) {
02063                 # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
02064                 # no attribute" instead of "output '' as value for attribute", this
02065                 # would be two lines.
02066                 $tooltip = self::titleAttrib( $name, $options );
02067                 if ( $tooltip === false ) {
02068                         return '';
02069                 }
02070                 return Xml::expandAttributes( array(
02071                         'title' => $tooltip
02072                 ) );
02073         }
02074 }
02075 
02079 class DummyLinker {
02080 
02088         public function __call( $fname, $args ) {
02089                 return call_user_func_array( array( 'Linker', $fname ), $args );
02090         }
02091 }