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