MediaWiki  master
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 
00195         public static function link(
00196                 $target, $html = null, $customAttribs = array(), $query = array(), $options = array()
00197         ) {
00198                 wfProfileIn( __METHOD__ );
00199                 if ( !$target instanceof Title ) {
00200                         wfProfileOut( __METHOD__ );
00201                         return "<!-- ERROR -->$html";
00202                 }
00203 
00204                 if( is_string( $query ) ) {
00205                         // some functions withing core using this still hand over query strings
00206                         wfDeprecated( __METHOD__ . ' with parameter $query as string (should be array)', '1.20' );
00207                         $query = wfCgiToArray( $query );
00208                 }
00209                 $options = (array)$options;
00210 
00211                 $dummy = new DummyLinker; // dummy linker instance for bc on the hooks
00212 
00213                 $ret = null;
00214                 if ( !wfRunHooks( 'LinkBegin', array( $dummy, $target, &$html,
00215                 &$customAttribs, &$query, &$options, &$ret ) ) ) {
00216                         wfProfileOut( __METHOD__ );
00217                         return $ret;
00218                 }
00219 
00220                 # Normalize the Title if it's a special page
00221                 $target = self::normaliseSpecialPage( $target );
00222 
00223                 # If we don't know whether the page exists, let's find out.
00224                 wfProfileIn( __METHOD__ . '-checkPageExistence' );
00225                 if ( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) {
00226                         if ( $target->isKnown() ) {
00227                                 $options[] = 'known';
00228                         } else {
00229                                 $options[] = 'broken';
00230                         }
00231                 }
00232                 wfProfileOut( __METHOD__ . '-checkPageExistence' );
00233 
00234                 $oldquery = array();
00235                 if ( in_array( "forcearticlepath", $options ) && $query ) {
00236                         $oldquery = $query;
00237                         $query = array();
00238                 }
00239 
00240                 # Note: we want the href attribute first, for prettiness.
00241                 $attribs = array( 'href' => self::linkUrl( $target, $query, $options ) );
00242                 if ( in_array( 'forcearticlepath', $options ) && $oldquery ) {
00243                         $attribs['href'] = wfAppendQuery( $attribs['href'], wfArrayToCgi( $oldquery ) );
00244                 }
00245 
00246                 $attribs = array_merge(
00247                         $attribs,
00248                         self::linkAttribs( $target, $customAttribs, $options )
00249                 );
00250                 if ( is_null( $html ) ) {
00251                         $html = self::linkText( $target );
00252                 }
00253 
00254                 $ret = null;
00255                 if ( wfRunHooks( 'LinkEnd', array( $dummy, $target, $options, &$html, &$attribs, &$ret ) ) ) {
00256                         $ret = Html::rawElement( 'a', $attribs, $html );
00257                 }
00258 
00259                 wfProfileOut( __METHOD__ );
00260                 return $ret;
00261         }
00262 
00267         public static function linkKnown(
00268                 $target, $html = null, $customAttribs = array(),
00269                 $query = array(), $options = array( 'known', 'noclasses' ) )
00270         {
00271                 return self::link( $target, $html, $customAttribs, $query, $options );
00272         }
00273 
00282         private static function linkUrl( $target, $query, $options ) {
00283                 wfProfileIn( __METHOD__ );
00284                 # We don't want to include fragments for broken links, because they
00285                 # generally make no sense.
00286                 if ( in_array( 'broken', $options ) && $target->mFragment !== '' ) {
00287                         $target = clone $target;
00288                         $target->mFragment = '';
00289                 }
00290 
00291                 # If it's a broken link, add the appropriate query pieces, unless
00292                 # there's already an action specified, or unless 'edit' makes no sense
00293                 # (i.e., for a nonexistent special page).
00294                 if ( in_array( 'broken', $options ) && empty( $query['action'] )
00295                         && !$target->isSpecialPage() ) {
00296                         $query['action'] = 'edit';
00297                         $query['redlink'] = '1';
00298                 }
00299 
00300                 if ( in_array( 'http', $options ) ) {
00301                         $proto = PROTO_HTTP;
00302                 } elseif ( in_array( 'https', $options ) ) {
00303                         $proto = PROTO_HTTPS;
00304                 } else {
00305                         $proto = PROTO_RELATIVE;
00306                 }
00307 
00308                 $ret = $target->getLinkURL( $query, false, $proto );
00309                 wfProfileOut( __METHOD__ );
00310                 return $ret;
00311         }
00312 
00322         private static function linkAttribs( $target, $attribs, $options ) {
00323                 wfProfileIn( __METHOD__ );
00324                 global $wgUser;
00325                 $defaults = array();
00326 
00327                 if ( !in_array( 'noclasses', $options ) ) {
00328                         wfProfileIn( __METHOD__ . '-getClasses' );
00329                         # Now build the classes.
00330                         $classes = array();
00331 
00332                         if ( in_array( 'broken', $options ) ) {
00333                                 $classes[] = 'new';
00334                         }
00335 
00336                         if ( $target->isExternal() ) {
00337                                 $classes[] = 'extiw';
00338                         }
00339 
00340                         if ( !in_array( 'broken', $options ) ) { # Avoid useless calls to LinkCache (see r50387)
00341                                 $colour = self::getLinkColour( $target, $wgUser->getStubThreshold() );
00342                                 if ( $colour !== '' ) {
00343                                         $classes[] = $colour; # mw-redirect or stub
00344                                 }
00345                         }
00346                         if ( $classes != array() ) {
00347                                 $defaults['class'] = implode( ' ', $classes );
00348                         }
00349                         wfProfileOut( __METHOD__ . '-getClasses' );
00350                 }
00351 
00352                 # Get a default title attribute.
00353                 if ( $target->getPrefixedText() == '' ) {
00354                         # A link like [[#Foo]].  This used to mean an empty title
00355                         # attribute, but that's silly.  Just don't output a title.
00356                 } elseif ( in_array( 'known', $options ) ) {
00357                         $defaults['title'] = $target->getPrefixedText();
00358                 } else {
00359                         $defaults['title'] = wfMessage( 'red-link-title', $target->getPrefixedText() )->text();
00360                 }
00361 
00362                 # Finally, merge the custom attribs with the default ones, and iterate
00363                 # over that, deleting all "false" attributes.
00364                 $ret = array();
00365                 $merged = Sanitizer::mergeAttributes( $defaults, $attribs );
00366                 foreach ( $merged as $key => $val ) {
00367                         # A false value suppresses the attribute, and we don't want the
00368                         # href attribute to be overridden.
00369                         if ( $key != 'href' and $val !== false ) {
00370                                 $ret[$key] = $val;
00371                         }
00372                 }
00373                 wfProfileOut( __METHOD__ );
00374                 return $ret;
00375         }
00376 
00384         private static function linkText( $target ) {
00385                 # We might be passed a non-Title by make*LinkObj().  Fail gracefully.
00386                 if ( !$target instanceof Title ) {
00387                         return '';
00388                 }
00389 
00390                 # If the target is just a fragment, with no title, we return the frag-
00391                 # ment text.  Otherwise, we return the title text itself.
00392                 if ( $target->getPrefixedText() === '' && $target->getFragment() !== '' ) {
00393                         return htmlspecialchars( $target->getFragment() );
00394                 }
00395                 return htmlspecialchars( $target->getPrefixedText() );
00396         }
00397 
00411         static function makeSizeLinkObj( $size, $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
00412                 global $wgUser;
00413                 wfDeprecated( __METHOD__, '1.17' );
00414 
00415                 $threshold = $wgUser->getStubThreshold();
00416                 $colour = ( $size < $threshold ) ? 'stub' : '';
00417                 // @todo FIXME: Replace deprecated makeColouredLinkObj by link()
00418                 return self::makeColouredLinkObj( $nt, $colour, $text, $query, $trail, $prefix );
00419         }
00420 
00435         public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) {
00436                 if ( $html == '' ) {
00437                         $html = htmlspecialchars( $nt->getPrefixedText() );
00438                 }
00439                 list( $inside, $trail ) = self::splitTrail( $trail );
00440                 return "<strong class=\"selflink\">{$prefix}{$html}{$inside}</strong>{$trail}";
00441         }
00442 
00453         public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
00454                 global $wgContLang;
00455 
00456                 // First we check whether the namespace exists or not.
00457                 if ( MWNamespace::exists( $namespace ) ) {
00458                         if ( $namespace == NS_MAIN ) {
00459                                 $name = $context->msg( 'blanknamespace' )->text();
00460                         } else {
00461                                 $name = $wgContLang->getFormattedNsText( $namespace );
00462                         }
00463                         return $context->msg( 'invalidtitle-knownnamespace', $namespace, $name, $title )->text();
00464                 } else {
00465                         return $context->msg( 'invalidtitle-unknownnamespace', $namespace, $title )->text();
00466                 }
00467         }
00468 
00473         static function normaliseSpecialPage( Title $title ) {
00474                 if ( $title->isSpecialPage() ) {
00475                         list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
00476                         if ( !$name ) {
00477                                 return $title;
00478                         }
00479                         $ret = SpecialPage::getTitleFor( $name, $subpage );
00480                         $ret->mFragment = $title->getFragment();
00481                         return $ret;
00482                 } else {
00483                         return $title;
00484                 }
00485         }
00486 
00495         private static function fnamePart( $url ) {
00496                 $basename = strrchr( $url, '/' );
00497                 if ( false === $basename ) {
00498                         $basename = $url;
00499                 } else {
00500                         $basename = substr( $basename, 1 );
00501                 }
00502                 return $basename;
00503         }
00504 
00514         public static function makeExternalImage( $url, $alt = '' ) {
00515                 if ( $alt == '' ) {
00516                         $alt = self::fnamePart( $url );
00517                 }
00518                 $img = '';
00519                 $success = wfRunHooks( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
00520                 if ( !$success ) {
00521                         wfDebug( "Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}\n", true );
00522                         return $img;
00523                 }
00524                 return Html::element( 'img',
00525                         array(
00526                                 'src' => $url,
00527                                 'alt' => $alt ) );
00528         }
00529 
00566         public static function makeImageLink( /*Parser*/ $parser, Title $title, $file, $frameParams = array(),
00567                 $handlerParams = array(), $time = false, $query = "", $widthOption = null )
00568         {
00569                 $res = null;
00570                 $dummy = new DummyLinker;
00571                 if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$dummy, &$title,
00572                         &$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) {
00573                         return $res;
00574                 }
00575 
00576                 if ( $file && !$file->allowInlineDisplay() ) {
00577                         wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display\n" );
00578                         return self::link( $title );
00579                 }
00580 
00581                 // Shortcuts
00582                 $fp =& $frameParams;
00583                 $hp =& $handlerParams;
00584 
00585                 // Clean up parameters
00586                 $page = isset( $hp['page'] ) ? $hp['page'] : false;
00587                 if ( !isset( $fp['align'] ) ) {
00588                         $fp['align'] = '';
00589                 }
00590                 if ( !isset( $fp['alt'] ) ) {
00591                         $fp['alt'] = '';
00592                 }
00593                 if ( !isset( $fp['title'] ) ) {
00594                         $fp['title'] = '';
00595                 }
00596                 if ( !isset( $fp['class'] ) ) {
00597                         $fp['class'] = '';
00598                 }
00599 
00600                 $prefix = $postfix = '';
00601 
00602                 if ( 'center' == $fp['align'] ) {
00603                         $prefix  = '<div class="center">';
00604                         $postfix = '</div>';
00605                         $fp['align']   = 'none';
00606                 }
00607                 if ( $file && !isset( $hp['width'] ) ) {
00608                         if ( isset( $hp['height'] ) && $file->isVectorized() ) {
00609                                 // If its a vector image, and user only specifies height
00610                                 // we don't want it to be limited by its "normal" width.
00611                                 global $wgSVGMaxSize;
00612                                 $hp['width'] = $wgSVGMaxSize;
00613                         } else {
00614                                 $hp['width'] = $file->getWidth( $page );
00615                         }
00616 
00617                         if ( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
00618                                 global $wgThumbLimits, $wgThumbUpright;
00619                                 if ( !isset( $widthOption ) || !isset( $wgThumbLimits[$widthOption] ) ) {
00620                                         $widthOption = User::getDefaultOption( 'thumbsize' );
00621                                 }
00622 
00623                                 // Reduce width for upright images when parameter 'upright' is used
00624                                 if ( isset( $fp['upright'] ) && $fp['upright'] == 0 ) {
00625                                         $fp['upright'] = $wgThumbUpright;
00626                                 }
00627                                 // 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
00628                                 $prefWidth = isset( $fp['upright'] ) ?
00629                                         round( $wgThumbLimits[$widthOption] * $fp['upright'], -1 ) :
00630                                         $wgThumbLimits[$widthOption];
00631 
00632                                 // Use width which is smaller: real image width or user preference width
00633                                 // Unless image is scalable vector.
00634                                 if ( !isset( $hp['height'] ) && ( $hp['width'] <= 0 ||
00635                                                 $prefWidth < $hp['width'] || $file->isVectorized() ) ) {
00636                                         $hp['width'] = $prefWidth;
00637                                 }
00638                         }
00639                 }
00640 
00641                 if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) ) {
00642                         # Create a thumbnail. Alignment depends on the writing direction of
00643                         # the page content language (right-aligned for LTR languages,
00644                         # left-aligned for RTL languages)
00645                         #
00646                         # If a thumbnail width has not been provided, it is set
00647                         # to the default user option as specified in Language*.php
00648                         if ( $fp['align'] == '' ) {
00649                                 if( $parser instanceof Parser ) {
00650                                         $fp['align'] = $parser->getTargetLanguage()->alignEnd();
00651                                 } else {
00652                                         # backwards compatibility, remove with makeImageLink2()
00653                                         global $wgContLang;
00654                                         $fp['align'] = $wgContLang->alignEnd();
00655                                 }
00656                         }
00657                         return $prefix . self::makeThumbLink2( $title, $file, $fp, $hp, $time, $query ) . $postfix;
00658                 }
00659 
00660                 if ( $file && isset( $fp['frameless'] ) ) {
00661                         $srcWidth = $file->getWidth( $page );
00662                         # For "frameless" option: do not present an image bigger than the source (for bitmap-style images)
00663                         # This is the same behaviour as the "thumb" option does it already.
00664                         if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
00665                                 $hp['width'] = $srcWidth;
00666                         }
00667                 }
00668 
00669                 if ( $file && isset( $hp['width'] ) ) {
00670                         # Create a resized image, without the additional thumbnail features
00671                         $thumb = $file->transform( $hp );
00672                 } else {
00673                         $thumb = false;
00674                 }
00675 
00676                 if ( !$thumb ) {
00677                         $s = self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
00678                 } else {
00679                         self::processResponsiveImages( $file, $thumb, $hp );
00680                         $params = array(
00681                                 'alt' => $fp['alt'],
00682                                 'title' => $fp['title'],
00683                                 'valign' => isset( $fp['valign'] ) ? $fp['valign'] : false,
00684                                 'img-class' => $fp['class'] );
00685                         if ( isset( $fp['border'] ) ) {
00686                                 $params['img-class'] .= ( $params['img-class'] !== '' ) ? ' thumbborder' : 'thumbborder';
00687                         }
00688                         $params = self::getImageLinkMTOParams( $fp, $query, $parser ) + $params;
00689 
00690                         $s = $thumb->toHtml( $params );
00691                 }
00692                 if ( $fp['align'] != '' ) {
00693                         $s = "<div class=\"float{$fp['align']}\">{$s}</div>";
00694                 }
00695                 return str_replace( "\n", ' ', $prefix . $s . $postfix );
00696         }
00697 
00703         public static function makeImageLink2( Title $title, $file, $frameParams = array(),
00704                 $handlerParams = array(), $time = false, $query = "", $widthOption = null ) {
00705                 return self::makeImageLink( null, $title, $file, $frameParams,
00706                         $handlerParams, $time, $query, $widthOption );
00707         }
00708 
00716         private static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
00717                 $mtoParams = array();
00718                 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
00719                         $mtoParams['custom-url-link'] = $frameParams['link-url'];
00720                         if ( isset( $frameParams['link-target'] ) ) {
00721                                 $mtoParams['custom-target-link'] = $frameParams['link-target'];
00722                         }
00723                         if ( $parser ) {
00724                                 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
00725                                 foreach ( $extLinkAttrs as $name => $val ) {
00726                                         // Currently could include 'rel' and 'target'
00727                                         $mtoParams['parser-extlink-'.$name] = $val;
00728                                 }
00729                         }
00730                 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
00731                         $mtoParams['custom-title-link'] = self::normaliseSpecialPage( $frameParams['link-title'] );
00732                 } elseif ( !empty( $frameParams['no-link'] ) ) {
00733                         // No link
00734                 } else {
00735                         $mtoParams['desc-link'] = true;
00736                         $mtoParams['desc-query'] = $query;
00737                 }
00738                 return $mtoParams;
00739         }
00740 
00753         public static function makeThumbLinkObj( Title $title, $file, $label = '', $alt,
00754                 $align = 'right', $params = array(), $framed = false , $manualthumb = "" )
00755         {
00756                 $frameParams = array(
00757                         'alt' => $alt,
00758                         'caption' => $label,
00759                         'align' => $align
00760                 );
00761                 if ( $framed ) {
00762                         $frameParams['framed'] = true;
00763                 }
00764                 if ( $manualthumb ) {
00765                         $frameParams['manualthumb'] = $manualthumb;
00766                 }
00767                 return self::makeThumbLink2( $title, $file, $frameParams, $params );
00768         }
00769 
00779         public static function makeThumbLink2( Title $title, $file, $frameParams = array(),
00780                 $handlerParams = array(), $time = false, $query = "" )
00781         {
00782                 global $wgStylePath, $wgContLang;
00783                 $exists = $file && $file->exists();
00784 
00785                 # Shortcuts
00786                 $fp =& $frameParams;
00787                 $hp =& $handlerParams;
00788 
00789                 $page = isset( $hp['page'] ) ? $hp['page'] : false;
00790                 if ( !isset( $fp['align'] ) ) $fp['align'] = 'right';
00791                 if ( !isset( $fp['alt'] ) ) $fp['alt'] = '';
00792                 if ( !isset( $fp['title'] ) ) $fp['title'] = '';
00793                 if ( !isset( $fp['caption'] ) ) $fp['caption'] = '';
00794 
00795                 if ( empty( $hp['width'] ) ) {
00796                         // Reduce width for upright images when parameter 'upright' is used
00797                         $hp['width'] = isset( $fp['upright'] ) ? 130 : 180;
00798                 }
00799                 $thumb = false;
00800                 $noscale = false;
00801 
00802                 if ( !$exists ) {
00803                         $outerWidth = $hp['width'] + 2;
00804                 } else {
00805                         if ( isset( $fp['manualthumb'] ) ) {
00806                                 # Use manually specified thumbnail
00807                                 $manual_title = Title::makeTitleSafe( NS_FILE, $fp['manualthumb'] );
00808                                 if ( $manual_title ) {
00809                                         $manual_img = wfFindFile( $manual_title );
00810                                         if ( $manual_img ) {
00811                                                 $thumb = $manual_img->getUnscaledThumb( $hp );
00812                                         } else {
00813                                                 $exists = false;
00814                                         }
00815                                 }
00816                         } elseif ( isset( $fp['framed'] ) ) {
00817                                 // Use image dimensions, don't scale
00818                                 $thumb = $file->getUnscaledThumb( $hp );
00819                                 $noscale = true;
00820                         } else {
00821                                 # Do not present an image bigger than the source, for bitmap-style images
00822                                 # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
00823                                 $srcWidth = $file->getWidth( $page );
00824                                 if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
00825                                         $hp['width'] = $srcWidth;
00826                                 }
00827                                 $thumb = $file->transform( $hp );
00828                         }
00829 
00830                         if ( $thumb ) {
00831                                 $outerWidth = $thumb->getWidth() + 2;
00832                         } else {
00833                                 $outerWidth = $hp['width'] + 2;
00834                         }
00835                 }
00836 
00837                 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
00838                 # So we don't need to pass it here in $query. However, the URL for the
00839                 # zoom icon still needs it, so we make a unique query for it. See bug 14771
00840                 $url = $title->getLocalURL( $query );
00841                 if ( $page ) {
00842                         $url = wfAppendQuery( $url, 'page=' . urlencode( $page ) );
00843                 }
00844 
00845                 $s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
00846                 if ( !$exists ) {
00847                         $s .= self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
00848                         $zoomIcon = '';
00849                 } elseif ( !$thumb ) {
00850                         $s .= wfMessage( 'thumbnail_error', '' )->escaped();
00851                         $zoomIcon = '';
00852                 } else {
00853                         if ( !$noscale ) {
00854                                 self::processResponsiveImages( $file, $thumb, $hp );
00855                         }
00856                         $params = array(
00857                                 'alt' => $fp['alt'],
00858                                 'title' => $fp['title'],
00859                                 'img-class' => ( isset( $fp['class'] ) && $fp['class'] !== '' ) ? $fp['class'] . ' thumbimage' : 'thumbimage'
00860                         );
00861                         $params = self::getImageLinkMTOParams( $fp, $query ) + $params;
00862                         $s .= $thumb->toHtml( $params );
00863                         if ( isset( $fp['framed'] ) ) {
00864                                 $zoomIcon = "";
00865                         } else {
00866                                 $zoomIcon = Html::rawElement( 'div', array( 'class' => 'magnify' ),
00867                                         Html::rawElement( 'a', array(
00868                                                 'href' => $url,
00869                                                 'class' => 'internal',
00870                                                 'title' => wfMessage( 'thumbnail-more' )->text() ),
00871                                                 Html::element( 'img', array(
00872                                                         'src' => $wgStylePath . '/common/images/magnify-clip' . ( $wgContLang->isRTL() ? '-rtl' : '' ) . '.png',
00873                                                         'width' => 15,
00874                                                         'height' => 11,
00875                                                         'alt' => "" ) ) ) );
00876                         }
00877                 }
00878                 $s .= '  <div class="thumbcaption">' . $zoomIcon . $fp['caption'] . "</div></div></div>";
00879                 return str_replace( "\n", ' ', $s );
00880         }
00881 
00890         protected static function processResponsiveImages( $file, $thumb, $hp ) {
00891                 global $wgResponsiveImages;
00892                 if ( $wgResponsiveImages ) {
00893                         $hp15 = $hp;
00894                         $hp15['width'] = round( $hp['width'] * 1.5 );
00895                         $hp20 = $hp;
00896                         $hp20['width'] = $hp['width'] * 2;
00897                         if ( isset( $hp['height'] ) ) {
00898                                 $hp15['height'] = round( $hp['height'] * 1.5 );
00899                                 $hp20['height'] = $hp['height'] * 2;
00900                         }
00901 
00902                         $thumb15 = $file->transform( $hp15 );
00903                         $thumb20 = $file->transform( $hp20 );
00904                         if ( $thumb15->url !== $thumb->url ) {
00905                                 $thumb->responsiveUrls['1.5'] = $thumb15->url;
00906                         }
00907                         if ( $thumb20->url !== $thumb->url ) {
00908                                 $thumb->responsiveUrls['2'] = $thumb20->url;
00909                         }
00910                 }
00911         }
00912 
00924         public static function makeBrokenImageLinkObj( $title, $label = '', $query = '', $unused1 = '', $unused2 = '', $time = false ) {
00925                 global $wgEnableUploads, $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
00926                 if ( ! $title instanceof Title ) {
00927                         return "<!-- ERROR -->" . htmlspecialchars( $label );
00928                 }
00929                 wfProfileIn( __METHOD__ );
00930                 if ( $label == '' ) {
00931                         $label = $title->getPrefixedText();
00932                 }
00933                 $encLabel = htmlspecialchars( $label );
00934                 $currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
00935 
00936                 if ( ( $wgUploadMissingFileUrl || $wgUploadNavigationUrl || $wgEnableUploads ) && !$currentExists ) {
00937                         $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
00938 
00939                         if ( $redir ) {
00940                                 wfProfileOut( __METHOD__ );
00941                                 return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
00942                         }
00943 
00944                         $href = self::getUploadUrl( $title, $query );
00945 
00946                         wfProfileOut( __METHOD__ );
00947                         return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
00948                                 htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
00949                                 $encLabel . '</a>';
00950                 }
00951 
00952                 wfProfileOut( __METHOD__ );
00953                 return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
00954         }
00955 
00963         protected static function getUploadUrl( $destFile, $query = '' ) {
00964                 global $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
00965                 $q = 'wpDestFile=' . $destFile->getPartialUrl();
00966                 if ( $query != '' )
00967                         $q .= '&' . $query;
00968 
00969                 if ( $wgUploadMissingFileUrl ) {
00970                         return wfAppendQuery( $wgUploadMissingFileUrl, $q );
00971                 } elseif( $wgUploadNavigationUrl ) {
00972                         return wfAppendQuery( $wgUploadNavigationUrl, $q );
00973                 } else {
00974                         $upload = SpecialPage::getTitleFor( 'Upload' );
00975                         return $upload->getLocalUrl( $q );
00976                 }
00977         }
00978 
00987         public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
00988                 $img = wfFindFile( $title, array( 'time' => $time ) );
00989                 return self::makeMediaLinkFile( $title, $img, $html );
00990         }
00991 
01003         public static function makeMediaLinkFile( Title $title, $file, $html = '' ) {
01004                 if ( $file && $file->exists() ) {
01005                         $url = $file->getURL();
01006                         $class = 'internal';
01007                 } else {
01008                         $url = self::getUploadUrl( $title );
01009                         $class = 'new';
01010                 }
01011                 $alt = htmlspecialchars( $title->getText(), ENT_QUOTES );
01012                 if ( $html == '' ) {
01013                         $html = $alt;
01014                 }
01015                 $u = htmlspecialchars( $url );
01016                 return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$html}</a>";
01017         }
01018 
01026         public static function specialLink( $name, $key = '' ) {
01027                 if ( $key == '' ) {
01028                         $key = strtolower( $name );
01029                 }
01030 
01031                 return self::linkKnown( SpecialPage::getTitleFor( $name ) , wfMessage( $key )->text() );
01032         }
01033 
01044         public static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array(), $title = null ) {
01045                 global $wgTitle;
01046                 $class = "external";
01047                 if ( $linktype ) {
01048                         $class .= " $linktype";
01049                 }
01050                 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
01051                         $class .= " {$attribs['class']}";
01052                 }
01053                 $attribs['class'] = $class;
01054 
01055                 if ( $escape ) {
01056                         $text = htmlspecialchars( $text );
01057                 }
01058 
01059                 if ( !$title ) {
01060                         $title = $wgTitle;
01061                 }
01062                 $attribs['rel'] = Parser::getExternalLinkRel( $url, $title );
01063                 $link = '';
01064                 $success = wfRunHooks( 'LinkerMakeExternalLink',
01065                         array( &$url, &$text, &$link, &$attribs, $linktype ) );
01066                 if ( !$success ) {
01067                         wfDebug( "Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true );
01068                         return $link;
01069                 }
01070                 $attribs['href'] = $url;
01071                 return Html::rawElement( 'a', $attribs, $text );
01072         }
01073 
01082         public static function userLink( $userId, $userName, $altUserName = false ) {
01083                 if ( $userId == 0 ) {
01084                         $page = SpecialPage::getTitleFor( 'Contributions', $userName );
01085                         if ( $altUserName === false ) {
01086                                 $altUserName = IP::prettifyIP( $userName );
01087                         }
01088                 } else {
01089                         $page = Title::makeTitle( NS_USER, $userName );
01090                 }
01091 
01092                 return self::link(
01093                         $page,
01094                         htmlspecialchars( $altUserName !== false ? $altUserName : $userName ),
01095                         array( 'class' => 'mw-userlink' )
01096                 );
01097         }
01098 
01110         public static function userToolLinks(
01111                 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null
01112         ) {
01113                 global $wgUser, $wgDisableAnonTalk, $wgLang;
01114                 $talkable = !( $wgDisableAnonTalk && 0 == $userId );
01115                 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
01116                 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
01117 
01118                 $items = array();
01119                 if ( $talkable ) {
01120                         $items[] = self::userTalkLink( $userId, $userText );
01121                 }
01122                 if ( $userId ) {
01123                         // check if the user has an edit
01124                         $attribs = array();
01125                         if ( $redContribsWhenNoEdits ) {
01126                                 if ( intval( $edits ) === 0 && $edits !== 0 ) {
01127                                         $user = User::newFromId( $userId );
01128                                         $edits = $user->getEditCount();
01129                                 }
01130                                 if ( $edits === 0 ) {
01131                                         $attribs['class'] = 'new';
01132                                 }
01133                         }
01134                         $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
01135 
01136                         $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
01137                 }
01138                 if ( $blockable && $wgUser->isAllowed( 'block' ) ) {
01139                         $items[] = self::blockLink( $userId, $userText );
01140                 }
01141 
01142                 if ( $addEmailLink && $wgUser->canSendEmail() ) {
01143                         $items[] = self::emailLink( $userId, $userText );
01144                 }
01145 
01146                 wfRunHooks( 'UserToolLinksEdit', array( $userId, $userText, &$items ) );
01147 
01148                 if ( $items ) {
01149                         return wfMessage( 'word-separator' )->plain()
01150                                 . '<span class="mw-usertoollinks">'
01151                                 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
01152                                 . '</span>';
01153                 } else {
01154                         return '';
01155                 }
01156         }
01157 
01165         public static function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
01166                 return self::userToolLinks( $userId, $userText, true, 0, $edits );
01167         }
01168 
01169 
01175         public static function userTalkLink( $userId, $userText ) {
01176                 $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
01177                 $userTalkLink = self::link( $userTalkPage, wfMessage( 'talkpagelinktext' )->escaped() );
01178                 return $userTalkLink;
01179         }
01180 
01186         public static function blockLink( $userId, $userText ) {
01187                 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
01188                 $blockLink = self::link( $blockPage, wfMessage( 'blocklink' )->escaped() );
01189                 return $blockLink;
01190         }
01191 
01197         public static function emailLink( $userId, $userText ) {
01198                 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
01199                 $emailLink = self::link( $emailPage, wfMessage( 'emaillink' )->escaped() );
01200                 return $emailLink;
01201         }
01202 
01209         public static function revUserLink( $rev, $isPublic = false ) {
01210                 if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
01211                         $link = wfMessage( 'rev-deleted-user' )->escaped();
01212                 } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
01213                         $link = self::userLink( $rev->getUser( Revision::FOR_THIS_USER ),
01214                                 $rev->getUserText( Revision::FOR_THIS_USER ) );
01215                 } else {
01216                         $link = wfMessage( 'rev-deleted-user' )->escaped();
01217                 }
01218                 if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
01219                         return '<span class="history-deleted">' . $link . '</span>';
01220                 }
01221                 return $link;
01222         }
01223 
01230         public static function revUserTools( $rev, $isPublic = false ) {
01231                 if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
01232                         $link = wfMessage( 'rev-deleted-user' )->escaped();
01233                 } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
01234                         $userId = $rev->getUser( Revision::FOR_THIS_USER );
01235                         $userText = $rev->getUserText( Revision::FOR_THIS_USER );
01236                         $link = self::userLink( $userId, $userText )
01237                                 . wfMessage( 'word-separator' )->plain()
01238                                 . self::userToolLinks( $userId, $userText );
01239                 } else {
01240                         $link = wfMessage( 'rev-deleted-user' )->escaped();
01241                 }
01242                 if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
01243                         return ' <span class="history-deleted">' . $link . '</span>';
01244                 }
01245                 return $link;
01246         }
01247 
01265         public static function formatComment( $comment, $title = null, $local = false ) {
01266                 wfProfileIn( __METHOD__ );
01267 
01268                 # Sanitize text a bit:
01269                 $comment = str_replace( "\n", " ", $comment );
01270                 # Allow HTML entities (for bug 13815)
01271                 $comment = Sanitizer::escapeHtmlAllowEntities( $comment );
01272 
01273                 # Render autocomments and make links:
01274                 $comment = self::formatAutocomments( $comment, $title, $local );
01275                 $comment = self::formatLinksInComment( $comment, $title, $local );
01276 
01277                 wfProfileOut( __METHOD__ );
01278                 return $comment;
01279         }
01280 
01284         static $autocommentTitle;
01285         static $autocommentLocal;
01286 
01300         private static function formatAutocomments( $comment, $title = null, $local = false ) {
01301                 // Bah!
01302                 self::$autocommentTitle = $title;
01303                 self::$autocommentLocal = $local;
01304                 $comment = preg_replace_callback(
01305                         '!(.*)/\*\s*(.*?)\s*\*/(.*)!',
01306                         array( 'Linker', 'formatAutocommentsCallback' ),
01307                         $comment );
01308                 self::$autocommentTitle = null;
01309                 self::$autocommentLocal = null;
01310                 return $comment;
01311         }
01312 
01318         private static function formatAutocommentsCallback( $match ) {
01319                 global $wgLang;
01320                 $title = self::$autocommentTitle;
01321                 $local = self::$autocommentLocal;
01322 
01323                 $pre = $match[1];
01324                 $auto = $match[2];
01325                 $post = $match[3];
01326                 $comment = null;
01327                 wfRunHooks( 'FormatAutocomments', array( &$comment, $pre, $auto, $post, $title, $local ) );
01328                 if ( $comment === null ) {
01329                         $link = '';
01330                         if ( $title ) {
01331                                 $section = $auto;
01332 
01333                                 # Remove links that a user may have manually put in the autosummary
01334                                 # This could be improved by copying as much of Parser::stripSectionName as desired.
01335                                 $section = str_replace( '[[:', '', $section );
01336                                 $section = str_replace( '[[', '', $section );
01337                                 $section = str_replace( ']]', '', $section );
01338 
01339                                 $section = Sanitizer::normalizeSectionNameWhitespace( $section ); # bug 22784
01340                                 if ( $local ) {
01341                                         $sectionTitle = Title::newFromText( '#' . $section );
01342                                 } else {
01343                                         $sectionTitle = Title::makeTitleSafe( $title->getNamespace(),
01344                                                 $title->getDBkey(), $section );
01345                                 }
01346                                 if ( $sectionTitle ) {
01347                                         $link = self::link( $sectionTitle,
01348                                                 $wgLang->getArrow(), array(), array(),
01349                                                 'noclasses' );
01350                                 } else {
01351                                         $link = '';
01352                                 }
01353                         }
01354                         if ( $pre ) {
01355                                 # written summary $presep autocomment (summary /* section */)
01356                                 $pre .= wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped();
01357                         }
01358                         if ( $post ) {
01359                                 # autocomment $postsep written summary (/* section */ summary)
01360                                 $auto .= wfMessage( 'colon-separator' )->inContentLanguage()->escaped();
01361                         }
01362                         $auto = '<span class="autocomment">' . $auto . '</span>';
01363                         $comment = $pre . $link . $wgLang->getDirMark() . '<span dir="auto">' . $auto . $post . '</span>';
01364                 }
01365                 return $comment;
01366         }
01367 
01371         static $commentContextTitle;
01372         static $commentLocal;
01373 
01384         public static function formatLinksInComment( $comment, $title = null, $local = false ) {
01385                 self::$commentContextTitle = $title;
01386                 self::$commentLocal = $local;
01387                 $html = preg_replace_callback(
01388                         '/
01389                                 \[\[
01390                                 :? # ignore optional leading colon
01391                                 ([^\]|]+) # 1. link target; page names cannot include ] or |
01392                                 (?:\|
01393                                         # 2. a pipe-separated substring; only the last is captured
01394                                         # Stop matching at | and ]] without relying on backtracking.
01395                                         ((?:]?[^\]|])*+)
01396                                 )*
01397                                 \]\]
01398                                 ([^[]*) # 3. link trail (the text up until the next link)
01399                         /x',
01400                         array( 'Linker', 'formatLinksInCommentCallback' ),
01401                         $comment );
01402                 self::$commentContextTitle = null;
01403                 self::$commentLocal = null;
01404                 return $html;
01405         }
01406 
01411         protected static function formatLinksInCommentCallback( $match ) {
01412                 global $wgContLang;
01413 
01414                 $medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
01415                 $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):';
01416 
01417                 $comment = $match[0];
01418 
01419                 # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
01420                 if ( strpos( $match[1], '%' ) !== false ) {
01421                         $match[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $match[1] ) );
01422                 }
01423 
01424                 # Handle link renaming [[foo|text]] will show link as "text"
01425                 if ( $match[2] != "" ) {
01426                         $text = $match[2];
01427                 } else {
01428                         $text = $match[1];
01429                 }
01430                 $submatch = array();
01431                 $thelink = null;
01432                 if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
01433                         # Media link; trail not supported.
01434                         $linkRegexp = '/\[\[(.*?)\]\]/';
01435                         $title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
01436                         if ( $title ) {
01437                                 $thelink = self::makeMediaLinkObj( $title, $text );
01438                         }
01439                 } else {
01440                         # Other kind of link
01441                         if ( preg_match( $wgContLang->linkTrail(), $match[3], $submatch ) ) {
01442                                 $trail = $submatch[1];
01443                         } else {
01444                                 $trail = "";
01445                         }
01446                         $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
01447                         if ( isset( $match[1][0] ) && $match[1][0] == ':' )
01448                                 $match[1] = substr( $match[1], 1 );
01449                         list( $inside, $trail ) = self::splitTrail( $trail );
01450 
01451                         $linkText = $text;
01452                         $linkTarget = self::normalizeSubpageLink( self::$commentContextTitle,
01453                                 $match[1], $linkText );
01454 
01455                         $target = Title::newFromText( $linkTarget );
01456                         if ( $target ) {
01457                                 if ( $target->getText() == '' && $target->getInterwiki() === ''
01458                                         && !self::$commentLocal && self::$commentContextTitle )
01459                                 {
01460                                         $newTarget = clone ( self::$commentContextTitle );
01461                                         $newTarget->setFragment( '#' . $target->getFragment() );
01462                                         $target = $newTarget;
01463                                 }
01464                                 $thelink = self::link(
01465                                         $target,
01466                                         $linkText . $inside
01467                                 ) . $trail;
01468                         }
01469                 }
01470                 if ( $thelink ) {
01471                         // If the link is still valid, go ahead and replace it in!
01472                         $comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 );
01473                 }
01474 
01475                 return $comment;
01476         }
01477 
01484         public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
01485                 # Valid link forms:
01486                 # Foobar -- normal
01487                 # :Foobar -- override special treatment of prefix (images, language links)
01488                 # /Foobar -- convert to CurrentPage/Foobar
01489                 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
01490                 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
01491                 # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage
01492 
01493                 wfProfileIn( __METHOD__ );
01494                 $ret = $target; # default return value is no change
01495 
01496                 # Some namespaces don't allow subpages,
01497                 # so only perform processing if subpages are allowed
01498                 if ( $contextTitle && MWNamespace::hasSubpages( $contextTitle->getNamespace() ) ) {
01499                         $hash = strpos( $target, '#' );
01500                         if ( $hash !== false ) {
01501                                 $suffix = substr( $target, $hash );
01502                                 $target = substr( $target, 0, $hash );
01503                         } else {
01504                                 $suffix = '';
01505                         }
01506                         # bug 7425
01507                         $target = trim( $target );
01508                         # Look at the first character
01509                         if ( $target != '' && $target[0] === '/' ) {
01510                                 # / at end means we don't want the slash to be shown
01511                                 $m = array();
01512                                 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
01513                                 if ( $trailingSlashes ) {
01514                                         $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
01515                                 } else {
01516                                         $noslash = substr( $target, 1 );
01517                                 }
01518 
01519                                 $ret = $contextTitle->getPrefixedText() . '/' . trim( $noslash ) . $suffix;
01520                                 if ( $text === '' ) {
01521                                         $text = $target . $suffix;
01522                                 } # this might be changed for ugliness reasons
01523                         } else {
01524                                 # check for .. subpage backlinks
01525                                 $dotdotcount = 0;
01526                                 $nodotdot = $target;
01527                                 while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
01528                                         ++$dotdotcount;
01529                                         $nodotdot = substr( $nodotdot, 3 );
01530                                 }
01531                                 if ( $dotdotcount > 0 ) {
01532                                         $exploded = explode( '/', $contextTitle->GetPrefixedText() );
01533                                         if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
01534                                                 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
01535                                                 # / at the end means don't show full path
01536                                                 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
01537                                                         $nodotdot = substr( $nodotdot, 0, -1 );
01538                                                         if ( $text === '' ) {
01539                                                                 $text = $nodotdot . $suffix;
01540                                                         }
01541                                                 }
01542                                                 $nodotdot = trim( $nodotdot );
01543                                                 if ( $nodotdot != '' ) {
01544                                                         $ret .= '/' . $nodotdot;
01545                                                 }
01546                                                 $ret .= $suffix;
01547                                         }
01548                                 }
01549                         }
01550                 }
01551 
01552                 wfProfileOut( __METHOD__ );
01553                 return $ret;
01554         }
01555 
01566         public static function commentBlock( $comment, $title = null, $local = false ) {
01567                 // '*' used to be the comment inserted by the software way back
01568                 // in antiquity in case none was provided, here for backwards
01569                 // compatability, acc. to brion -ævar
01570                 if ( $comment == '' || $comment == '*' ) {
01571                         return '';
01572                 } else {
01573                         $formatted = self::formatComment( $comment, $title, $local );
01574                         $formatted = wfMessage( 'parentheses' )->rawParams( $formatted )->escaped();
01575                         return " <span class=\"comment\">$formatted</span>";
01576                 }
01577         }
01578 
01588         public static function revComment( Revision $rev, $local = false, $isPublic = false ) {
01589                 if ( $rev->getRawComment() == "" ) {
01590                         return "";
01591                 }
01592                 if ( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
01593                         $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
01594                 } elseif ( $rev->userCan( Revision::DELETED_COMMENT ) ) {
01595                         $block = self::commentBlock( $rev->getComment( Revision::FOR_THIS_USER ),
01596                                 $rev->getTitle(), $local );
01597                 } else {
01598                         $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
01599                 }
01600                 if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
01601                         return " <span class=\"history-deleted\">$block</span>";
01602                 }
01603                 return $block;
01604         }
01605 
01610         public static function formatRevisionSize( $size ) {
01611                 if ( $size == 0 ) {
01612                         $stxt = wfMessage( 'historyempty' )->escaped();
01613                 } else {
01614                         $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
01615                         $stxt = wfMessage( 'parentheses' )->rawParams( $stxt )->escaped();
01616                 }
01617                 return "<span class=\"history-size\">$stxt</span>";
01618         }
01619 
01625         public static function tocIndent() {
01626                 return "\n<ul>";
01627         }
01628 
01634         public static function tocUnindent( $level ) {
01635                 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
01636         }
01637 
01643         public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
01644                 $classes = "toclevel-$level";
01645                 if ( $sectionIndex !== false ) {
01646                         $classes .= " tocsection-$sectionIndex";
01647                 }
01648                 return "\n<li class=\"$classes\"><a href=\"#" .
01649                         $anchor . '"><span class="tocnumber">' .
01650                         $tocnumber . '</span> <span class="toctext">' .
01651                         $tocline . '</span></a>';
01652         }
01653 
01660         public static function tocLineEnd() {
01661                 return "</li>\n";
01662         }
01663 
01671         public static function tocList( $toc, $lang = false ) {
01672                 $lang = wfGetLangObj( $lang );
01673                 $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
01674 
01675                 return
01676                    '<table id="toc" class="toc"><tr><td>'
01677                  . '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
01678                  . $toc
01679                  . "</ul>\n</td></tr></table>\n";
01680         }
01681 
01689         public static function generateTOC( $tree ) {
01690                 $toc = '';
01691                 $lastLevel = 0;
01692                 foreach ( $tree as $section ) {
01693                         if ( $section['toclevel'] > $lastLevel )
01694                                 $toc .= self::tocIndent();
01695                         elseif ( $section['toclevel'] < $lastLevel )
01696                                 $toc .= self::tocUnindent(
01697                                         $lastLevel - $section['toclevel'] );
01698                         else
01699                                 $toc .= self::tocLineEnd();
01700 
01701                         $toc .= self::tocLine( $section['anchor'],
01702                                 $section['line'], $section['number'],
01703                                 $section['toclevel'], $section['index'] );
01704                         $lastLevel = $section['toclevel'];
01705                 }
01706                 $toc .= self::tocLineEnd();
01707                 return self::tocList( $toc );
01708         }
01709 
01725         public static function makeHeadline( $level, $attribs, $anchor, $html, $link, $legacyAnchor = false ) {
01726                 $ret = "<h$level$attribs"
01727                         . $link
01728                         . " <span class=\"mw-headline\" id=\"$anchor\">$html</span>"
01729                         . "</h$level>";
01730                 if ( $legacyAnchor !== false ) {
01731                         $ret = "<div id=\"$legacyAnchor\"></div>$ret";
01732                 }
01733                 return $ret;
01734         }
01735 
01741         static function splitTrail( $trail ) {
01742                 global $wgContLang;
01743                 $regex = $wgContLang->linkTrail();
01744                 $inside = '';
01745                 if ( $trail !== '' ) {
01746                         $m = array();
01747                         if ( preg_match( $regex, $trail, $m ) ) {
01748                                 $inside = $m[1];
01749                                 $trail = $m[2];
01750                         }
01751                 }
01752                 return array( $inside, $trail );
01753         }
01754 
01780         public static function generateRollback( $rev, IContextSource $context = null, $options = array( 'verify' ) ) {
01781                 if ( $context === null ) {
01782                         $context = RequestContext::getMain();
01783                 }
01784                 $editCount = false;
01785                 if ( in_array( 'verify', $options ) ) {
01786                         $editCount = self::getRollbackEditCount( $rev, true );
01787                         if ( $editCount === false ) {
01788                                 return '';
01789                         }
01790                 }
01791 
01792                 $inner = self::buildRollbackLink( $rev, $context, $editCount );
01793 
01794                 if ( !in_array( 'noBrackets', $options ) ) {
01795                         $inner = $context->msg( 'brackets' )->rawParams( $inner )->plain();
01796                 }
01797 
01798                 return '<span class="mw-rollback-link">' . $inner . '</span>';
01799         }
01800 
01816         public static function getRollbackEditCount( $rev, $verify ) {
01817                 global $wgShowRollbackEditCount;
01818                 if ( !is_int( $wgShowRollbackEditCount ) || !$wgShowRollbackEditCount > 0 ) {
01819                         // Nothing has happened, indicate this by returning 'null'
01820                         return null;
01821                 }
01822 
01823                 $dbr = wfGetDB( DB_SLAVE );
01824 
01825                 // Up to the value of $wgShowRollbackEditCount revisions are counted
01826                 $res = $dbr->select(
01827                         'revision',
01828                         array( 'rev_user_text', 'rev_deleted' ),
01829                         // $rev->getPage() returns null sometimes
01830                         array( 'rev_page' => $rev->getTitle()->getArticleID() ),
01831                         __METHOD__,
01832                         array(
01833                                 'USE INDEX' => array( 'revision' => 'page_timestamp' ),
01834                                 'ORDER BY' => 'rev_timestamp DESC',
01835                                 'LIMIT' => $wgShowRollbackEditCount + 1
01836                         )
01837                 );
01838 
01839                 $editCount = 0;
01840                 $moreRevs = false;
01841                 foreach ( $res as $row ) {
01842                         if ( $rev->getRawUserText() != $row->rev_user_text ) {
01843                                 if ( $verify && ( $row->rev_deleted & Revision::DELETED_TEXT || $row->rev_deleted & Revision::DELETED_USER ) ) {
01844                                         // If the user or the text of the revision we might rollback to is deleted in some way we can't rollback
01845                                         // Similar to the sanity checks in WikiPage::commitRollback
01846                                         return false;
01847                                 }
01848                                 $moreRevs = true;
01849                                 break;
01850                         }
01851                         $editCount++;
01852                 }
01853 
01854                 if ( $verify && $editCount <= $wgShowRollbackEditCount && !$moreRevs ) {
01855                         // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
01856                         // and there weren't any other revisions. That means that the current user is the only
01857                         // editor, so we can't rollback
01858                         return false;
01859                 }
01860                 return $editCount;
01861         }
01862 
01871         public static function buildRollbackLink( $rev, IContextSource $context = null, $editCount = false ) {
01872                 global $wgShowRollbackEditCount, $wgMiserMode;
01873 
01874                 // To config which pages are effected by miser mode
01875                 $disableRollbackEditCountSpecialPage = array( 'Recentchanges', 'Watchlist' );
01876 
01877                 if ( $context === null ) {
01878                         $context = RequestContext::getMain();
01879                 }
01880 
01881                 $title = $rev->getTitle();
01882                 $query = array(
01883                         'action' => 'rollback',
01884                         'from' => $rev->getUserText(),
01885                         'token' => $context->getUser()->getEditToken( array( $title->getPrefixedText(), $rev->getUserText() ) ),
01886                 );
01887                 if ( $context->getRequest()->getBool( 'bot' ) ) {
01888                         $query['bot'] = '1';
01889                         $query['hidediff'] = '1'; // bug 15999
01890                 }
01891 
01892                 $disableRollbackEditCount = false;
01893                 if( $wgMiserMode ) {
01894                         foreach( $disableRollbackEditCountSpecialPage as $specialPage ) {
01895                                 if( $context->getTitle()->isSpecial( $specialPage ) ) {
01896                                         $disableRollbackEditCount = true;
01897                                         break;
01898                                 }
01899                         }
01900                 }
01901 
01902                 if( !$disableRollbackEditCount && is_int( $wgShowRollbackEditCount ) && $wgShowRollbackEditCount > 0 ) {
01903                         if ( !is_numeric( $editCount ) ) {
01904                                 $editCount = self::getRollbackEditCount( $rev, false );
01905                         }
01906 
01907                         if( $editCount > $wgShowRollbackEditCount ) {
01908                                 $editCount_output = $context->msg( 'rollbacklinkcount-morethan' )->numParams( $wgShowRollbackEditCount )->parse();
01909                         } else {
01910                                 $editCount_output = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
01911                         }
01912 
01913                         return self::link(
01914                                 $title,
01915                                 $editCount_output,
01916                                 array( 'title' => $context->msg( 'tooltip-rollback' )->text() ),
01917                                 $query,
01918                                 array( 'known', 'noclasses' )
01919                         );
01920                 } else {
01921                         return self::link(
01922                                 $title,
01923                                 $context->msg( 'rollbacklink' )->escaped(),
01924                                 array( 'title' => $context->msg( 'tooltip-rollback' )->text() ),
01925                                 $query,
01926                                 array( 'known', 'noclasses' )
01927                         );
01928                 }
01929         }
01930 
01946         public static function formatTemplates( $templates, $preview = false, $section = false, $more = null ) {
01947                 wfProfileIn( __METHOD__ );
01948 
01949                 $outText = '';
01950                 if ( count( $templates ) > 0 ) {
01951                         # Do a batch existence check
01952                         $batch = new LinkBatch;
01953                         foreach ( $templates as $title ) {
01954                                 $batch->addObj( $title );
01955                         }
01956                         $batch->execute();
01957 
01958                         # Construct the HTML
01959                         $outText = '<div class="mw-templatesUsedExplanation">';
01960                         if ( $preview ) {
01961                                 $outText .= wfMessage( 'templatesusedpreview' )->numParams( count( $templates ) )
01962                                         ->parseAsBlock();
01963                         } elseif ( $section ) {
01964                                 $outText .= wfMessage( 'templatesusedsection' )->numParams( count( $templates ) )
01965                                         ->parseAsBlock();
01966                         } else {
01967                                 $outText .= wfMessage( 'templatesused' )->numParams( count( $templates ) )
01968                                         ->parseAsBlock();
01969                         }
01970                         $outText .= "</div><ul>\n";
01971 
01972                         usort( $templates, 'Title::compare' );
01973                         foreach ( $templates as $titleObj ) {
01974                                 $r = $titleObj->getRestrictions( 'edit' );
01975                                 if ( in_array( 'sysop', $r ) ) {
01976                                         $protected = wfMessage( 'template-protected' )->parse();
01977                                 } elseif ( in_array( 'autoconfirmed', $r ) ) {
01978                                         $protected = wfMessage( 'template-semiprotected' )->parse();
01979                                 } else {
01980                                         $protected = '';
01981                                 }
01982                                 if ( $titleObj->quickUserCan( 'edit' ) ) {
01983                                         $editLink = self::link(
01984                                                 $titleObj,
01985                                                 wfMessage( 'editlink' )->text(),
01986                                                 array(),
01987                                                 array( 'action' => 'edit' )
01988                                         );
01989                                 } else {
01990                                         $editLink = self::link(
01991                                                 $titleObj,
01992                                                 wfMessage( 'viewsourcelink' )->text(),
01993                                                 array(),
01994                                                 array( 'action' => 'edit' )
01995                                         );
01996                                 }
01997                                 $outText .= '<li>' . self::link( $titleObj )
01998                                         . wfMessage( 'word-separator' )->escaped()
01999                                         . wfMessage( 'parentheses' )->rawParams( $editLink )->escaped()
02000                                         . wfMessage( 'word-separator' )->escaped()
02001                                         . $protected . '</li>';
02002                         }
02003 
02004                         if ( $more instanceof Title ) {
02005                                 $outText .= '<li>' . self::link( $more, wfMessage( 'moredotdotdot' ) ) . '</li>';
02006                         } elseif ( $more ) {
02007                                 $outText .= "<li>$more</li>";
02008                         }
02009 
02010                         $outText .= '</ul>';
02011                 }
02012                 wfProfileOut( __METHOD__ );
02013                 return $outText;
02014         }
02015 
02023         public static function formatHiddenCategories( $hiddencats ) {
02024                 wfProfileIn( __METHOD__ );
02025 
02026                 $outText = '';
02027                 if ( count( $hiddencats ) > 0 ) {
02028                         # Construct the HTML
02029                         $outText = '<div class="mw-hiddenCategoriesExplanation">';
02030                         $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
02031                         $outText .= "</div><ul>\n";
02032 
02033                         foreach ( $hiddencats as $titleObj ) {
02034                                 $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
02035                         }
02036                         $outText .= '</ul>';
02037                 }
02038                 wfProfileOut( __METHOD__ );
02039                 return $outText;
02040         }
02041 
02049         public static function formatSize( $size ) {
02050                 global $wgLang;
02051                 return htmlspecialchars( $wgLang->formatSize( $size ) );
02052         }
02053 
02066         public static function titleAttrib( $name, $options = null ) {
02067                 wfProfileIn( __METHOD__ );
02068 
02069                 $message = wfMessage( "tooltip-$name" );
02070 
02071                 if ( !$message->exists() ) {
02072                         $tooltip = false;
02073                 } else {
02074                         $tooltip = $message->text();
02075                         # Compatibility: formerly some tooltips had [alt-.] hardcoded
02076                         $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
02077                         # Message equal to '-' means suppress it.
02078                         if (  $tooltip == '-' ) {
02079                                 $tooltip = false;
02080                         }
02081                 }
02082 
02083                 if ( $options == 'withaccess' ) {
02084                         $accesskey = self::accesskey( $name );
02085                         if ( $accesskey !== false ) {
02086                                 if ( $tooltip === false || $tooltip === '' ) {
02087                                         $tooltip = "[$accesskey]";
02088                                 } else {
02089                                         $tooltip .= " [$accesskey]";
02090                                 }
02091                         }
02092                 }
02093 
02094                 wfProfileOut( __METHOD__ );
02095                 return $tooltip;
02096         }
02097 
02098         static $accesskeycache;
02099 
02110         public static function accesskey( $name ) {
02111                 if ( isset( self::$accesskeycache[$name] ) ) {
02112                         return self::$accesskeycache[$name];
02113                 }
02114                 wfProfileIn( __METHOD__ );
02115 
02116                 $message = wfMessage( "accesskey-$name" );
02117 
02118                 if ( !$message->exists() ) {
02119                         $accesskey = false;
02120                 } else {
02121                         $accesskey = $message->plain();
02122                         if ( $accesskey === '' || $accesskey === '-' ) {
02123                                 # @todo FIXME: Per standard MW behavior, a value of '-' means to suppress the
02124                                 # attribute, but this is broken for accesskey: that might be a useful
02125                                 # value.
02126                                 $accesskey = false;
02127                         }
02128                 }
02129 
02130                 wfProfileOut( __METHOD__ );
02131                 return self::$accesskeycache[$name] = $accesskey;
02132         }
02133 
02147         public static function getRevDeleteLink( User $user, Revision $rev, Title $title ) {
02148                 $canHide = $user->isAllowed( 'deleterevision' );
02149                 if ( !$canHide && !( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
02150                         return '';
02151                 }
02152 
02153                 if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
02154                         return Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
02155                 } else {
02156                         if ( $rev->getId() ) {
02157                                 // RevDelete links using revision ID are stable across
02158                                 // page deletion and undeletion; use when possible.
02159                                 $query = array(
02160                                         'type'   => 'revision',
02161                                         'target' => $title->getPrefixedDBkey(),
02162                                         'ids'    => $rev->getId()
02163                                 );
02164                         } else {
02165                                 // Older deleted entries didn't save a revision ID.
02166                                 // We have to refer to these by timestamp, ick!
02167                                 $query = array(
02168                                         'type'   => 'archive',
02169                                         'target' => $title->getPrefixedDBkey(),
02170                                         'ids'    => $rev->getTimestamp()
02171                                 );
02172                         }
02173                         return Linker::revDeleteLink( $query,
02174                                 $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
02175                 }
02176         }
02177 
02188         public static function revDeleteLink( $query = array(), $restricted = false, $delete = true ) {
02189                 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
02190                 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
02191                 $html = wfMessage( $msgKey )->escaped();
02192                 $tag = $restricted ? 'strong' : 'span';
02193                 $link = self::link( $sp, $html, array(), $query, array( 'known', 'noclasses' ) );
02194                 return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), wfMessage( 'parentheses' )->rawParams( $link )->escaped() );
02195         }
02196 
02205         public static function revDeleteLinkDisabled( $delete = true ) {
02206                 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
02207                 $html = wfMessage( $msgKey )->escaped();
02208                 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
02209                 return Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), $htmlParentheses );
02210         }
02211 
02212         /* Deprecated methods */
02213 
02228         static function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
02229                 wfDeprecated( __METHOD__, '1.16' );
02230 
02231                 $nt = Title::newFromText( $title );
02232                 if ( $nt instanceof Title ) {
02233                         return self::makeBrokenLinkObj( $nt, $text, $query, $trail );
02234                 } else {
02235                         wfDebug( 'Invalid title passed to self::makeBrokenLink(): "' . $title . "\"\n" );
02236                         return $text == '' ? $title : $text;
02237                 }
02238         }
02239 
02257         static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
02258                 wfDeprecated( __METHOD__, '1.21' );
02259 
02260                 wfProfileIn( __METHOD__ );
02261                 $query = wfCgiToArray( $query );
02262                 list( $inside, $trail ) = self::splitTrail( $trail );
02263                 if ( $text === '' ) {
02264                         $text = self::linkText( $nt );
02265                 }
02266 
02267                 $ret = self::link( $nt, "$prefix$text$inside", array(), $query ) . $trail;
02268 
02269                 wfProfileOut( __METHOD__ );
02270                 return $ret;
02271         }
02272 
02289         static function makeKnownLinkObj(
02290                 $title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = ''
02291         ) {
02292                 wfDeprecated( __METHOD__, '1.21' );
02293 
02294                 wfProfileIn( __METHOD__ );
02295 
02296                 if ( $text == '' ) {
02297                         $text = self::linkText( $title );
02298                 }
02299                 $attribs = Sanitizer::mergeAttributes(
02300                         Sanitizer::decodeTagAttributes( $aprops ),
02301                         Sanitizer::decodeTagAttributes( $style )
02302                 );
02303                 $query = wfCgiToArray( $query );
02304                 list( $inside, $trail ) = self::splitTrail( $trail );
02305 
02306                 $ret = self::link( $title, "$prefix$text$inside", $attribs, $query,
02307                         array( 'known', 'noclasses' ) ) . $trail;
02308 
02309                 wfProfileOut( __METHOD__ );
02310                 return $ret;
02311         }
02312 
02327         static function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
02328                 wfDeprecated( __METHOD__, '1.16' );
02329 
02330                 wfProfileIn( __METHOD__ );
02331 
02332                 list( $inside, $trail ) = self::splitTrail( $trail );
02333                 if ( $text === '' ) {
02334                         $text = self::linkText( $title );
02335                 }
02336 
02337                 $ret = self::link( $title, "$prefix$text$inside", array(),
02338                         wfCgiToArray( $query ), 'broken' ) . $trail;
02339 
02340                 wfProfileOut( __METHOD__ );
02341                 return $ret;
02342         }
02343 
02359         static function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
02360                 wfDeprecated( __METHOD__, '1.16' );
02361 
02362                 if ( $colour != '' ) {
02363                         $style = self::getInternalLinkAttributesObj( $nt, $text, $colour );
02364                 } else {
02365                         $style = '';
02366                 }
02367                 return self::makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style );
02368         }
02369 
02374         public static function tooltipAndAccesskeyAttribs( $name ) {
02375                 # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
02376                 # no attribute" instead of "output '' as value for attribute", this
02377                 # would be three lines.
02378                 $attribs = array(
02379                         'title' => self::titleAttrib( $name, 'withaccess' ),
02380                         'accesskey' => self::accesskey( $name )
02381                 );
02382                 if ( $attribs['title'] === false ) {
02383                         unset( $attribs['title'] );
02384                 }
02385                 if ( $attribs['accesskey'] === false ) {
02386                         unset( $attribs['accesskey'] );
02387                 }
02388                 return $attribs;
02389         }
02390 
02395         public static function tooltip( $name, $options = null ) {
02396                 # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
02397                 # no attribute" instead of "output '' as value for attribute", this
02398                 # would be two lines.
02399                 $tooltip = self::titleAttrib( $name, $options );
02400                 if ( $tooltip === false ) {
02401                         return '';
02402                 }
02403                 return Xml::expandAttributes( array(
02404                         'title' => $tooltip
02405                 ) );
02406         }
02407 }
02408 
02412 class DummyLinker {
02413 
02422         public function __call( $fname, $args ) {
02423                 return call_user_func_array( array( 'Linker', $fname ), $args );
02424         }
02425 }