MediaWiki  REL1_22
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'], $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['manualthumb'] ) || 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'] ) ) {
00790             $fp['align'] = 'right';
00791         }
00792         if ( !isset( $fp['alt'] ) ) {
00793             $fp['alt'] = '';
00794         }
00795         if ( !isset( $fp['title'] ) ) {
00796             $fp['title'] = '';
00797         }
00798         if ( !isset( $fp['caption'] ) ) {
00799             $fp['caption'] = '';
00800         }
00801 
00802         if ( empty( $hp['width'] ) ) {
00803             // Reduce width for upright images when parameter 'upright' is used
00804             $hp['width'] = isset( $fp['upright'] ) ? 130 : 180;
00805         }
00806         $thumb = false;
00807         $noscale = false;
00808         $manualthumb = false;
00809 
00810         if ( !$exists ) {
00811             $outerWidth = $hp['width'] + 2;
00812         } else {
00813             if ( isset( $fp['manualthumb'] ) ) {
00814                 # Use manually specified thumbnail
00815                 $manual_title = Title::makeTitleSafe( NS_FILE, $fp['manualthumb'] );
00816                 if ( $manual_title ) {
00817                     $manual_img = wfFindFile( $manual_title );
00818                     if ( $manual_img ) {
00819                         $thumb = $manual_img->getUnscaledThumb( $hp );
00820                         $manualthumb = true;
00821                     } else {
00822                         $exists = false;
00823                     }
00824                 }
00825             } elseif ( isset( $fp['framed'] ) ) {
00826                 // Use image dimensions, don't scale
00827                 $thumb = $file->getUnscaledThumb( $hp );
00828                 $noscale = true;
00829             } else {
00830                 # Do not present an image bigger than the source, for bitmap-style images
00831                 # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
00832                 $srcWidth = $file->getWidth( $page );
00833                 if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
00834                     $hp['width'] = $srcWidth;
00835                 }
00836                 $thumb = $file->transform( $hp );
00837             }
00838 
00839             if ( $thumb ) {
00840                 $outerWidth = $thumb->getWidth() + 2;
00841             } else {
00842                 $outerWidth = $hp['width'] + 2;
00843             }
00844         }
00845 
00846         # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
00847         # So we don't need to pass it here in $query. However, the URL for the
00848         # zoom icon still needs it, so we make a unique query for it. See bug 14771
00849         $url = $title->getLocalURL( $query );
00850         if ( $page ) {
00851             $url = wfAppendQuery( $url, array( 'page' => $page ) );
00852         }
00853         if ( $manualthumb &&
00854              !isset( $fp['link-title'] ) &&
00855              !isset( $fp['link-url'] ) &&
00856              !isset( $fp['no-link'] ) ) {
00857             $fp['link-url'] = $url;
00858         }
00859 
00860         $s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
00861         if ( !$exists ) {
00862             $s .= self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
00863             $zoomIcon = '';
00864         } elseif ( !$thumb ) {
00865             $s .= wfMessage( 'thumbnail_error', '' )->escaped();
00866             $zoomIcon = '';
00867         } else {
00868             if ( !$noscale && !$manualthumb ) {
00869                 self::processResponsiveImages( $file, $thumb, $hp );
00870             }
00871             $params = array(
00872                 'alt' => $fp['alt'],
00873                 'title' => $fp['title'],
00874                 'img-class' => ( isset( $fp['class'] ) && $fp['class'] !== '' ) ? $fp['class'] . ' thumbimage' : 'thumbimage'
00875             );
00876             $params = self::getImageLinkMTOParams( $fp, $query ) + $params;
00877             $s .= $thumb->toHtml( $params );
00878             if ( isset( $fp['framed'] ) ) {
00879                 $zoomIcon = "";
00880             } else {
00881                 $zoomIcon = Html::rawElement( 'div', array( 'class' => 'magnify' ),
00882                     Html::rawElement( 'a', array(
00883                         'href' => $url,
00884                         'class' => 'internal',
00885                         'title' => wfMessage( 'thumbnail-more' )->text() ),
00886                         Html::element( 'img', array(
00887                             'src' => $wgStylePath . '/common/images/magnify-clip' . ( $wgContLang->isRTL() ? '-rtl' : '' ) . '.png',
00888                             'width' => 15,
00889                             'height' => 11,
00890                             'alt' => "" ) ) ) );
00891             }
00892         }
00893         $s .= '  <div class="thumbcaption">' . $zoomIcon . $fp['caption'] . "</div></div></div>";
00894         return str_replace( "\n", ' ', $s );
00895     }
00896 
00905     protected static function processResponsiveImages( $file, $thumb, $hp ) {
00906         global $wgResponsiveImages;
00907         if ( $wgResponsiveImages ) {
00908             $hp15 = $hp;
00909             $hp15['width'] = round( $hp['width'] * 1.5 );
00910             $hp20 = $hp;
00911             $hp20['width'] = $hp['width'] * 2;
00912             if ( isset( $hp['height'] ) ) {
00913                 $hp15['height'] = round( $hp['height'] * 1.5 );
00914                 $hp20['height'] = $hp['height'] * 2;
00915             }
00916 
00917             $thumb15 = $file->transform( $hp15 );
00918             $thumb20 = $file->transform( $hp20 );
00919             if ( $thumb15->url !== $thumb->url ) {
00920                 $thumb->responsiveUrls['1.5'] = $thumb15->url;
00921             }
00922             if ( $thumb20->url !== $thumb->url ) {
00923                 $thumb->responsiveUrls['2'] = $thumb20->url;
00924             }
00925         }
00926     }
00927 
00939     public static function makeBrokenImageLinkObj( $title, $label = '', $query = '', $unused1 = '', $unused2 = '', $time = false ) {
00940         global $wgEnableUploads, $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
00941         if ( ! $title instanceof Title ) {
00942             return "<!-- ERROR -->" . htmlspecialchars( $label );
00943         }
00944         wfProfileIn( __METHOD__ );
00945         if ( $label == '' ) {
00946             $label = $title->getPrefixedText();
00947         }
00948         $encLabel = htmlspecialchars( $label );
00949         $currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
00950 
00951         if ( ( $wgUploadMissingFileUrl || $wgUploadNavigationUrl || $wgEnableUploads ) && !$currentExists ) {
00952             $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
00953 
00954             if ( $redir ) {
00955                 wfProfileOut( __METHOD__ );
00956                 return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
00957             }
00958 
00959             $href = self::getUploadUrl( $title, $query );
00960 
00961             wfProfileOut( __METHOD__ );
00962             return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
00963                 htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
00964                 $encLabel . '</a>';
00965         }
00966 
00967         wfProfileOut( __METHOD__ );
00968         return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
00969     }
00970 
00978     protected static function getUploadUrl( $destFile, $query = '' ) {
00979         global $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
00980         $q = 'wpDestFile=' . $destFile->getPartialURL();
00981         if ( $query != '' ) {
00982             $q .= '&' . $query;
00983         }
00984 
00985         if ( $wgUploadMissingFileUrl ) {
00986             return wfAppendQuery( $wgUploadMissingFileUrl, $q );
00987         } elseif ( $wgUploadNavigationUrl ) {
00988             return wfAppendQuery( $wgUploadNavigationUrl, $q );
00989         } else {
00990             $upload = SpecialPage::getTitleFor( 'Upload' );
00991             return $upload->getLocalURL( $q );
00992         }
00993     }
00994 
01003     public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
01004         $img = wfFindFile( $title, array( 'time' => $time ) );
01005         return self::makeMediaLinkFile( $title, $img, $html );
01006     }
01007 
01019     public static function makeMediaLinkFile( Title $title, $file, $html = '' ) {
01020         if ( $file && $file->exists() ) {
01021             $url = $file->getURL();
01022             $class = 'internal';
01023         } else {
01024             $url = self::getUploadUrl( $title );
01025             $class = 'new';
01026         }
01027         $alt = htmlspecialchars( $title->getText(), ENT_QUOTES );
01028         if ( $html == '' ) {
01029             $html = $alt;
01030         }
01031         $u = htmlspecialchars( $url );
01032         return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$html}</a>";
01033     }
01034 
01042     public static function specialLink( $name, $key = '' ) {
01043         if ( $key == '' ) {
01044             $key = strtolower( $name );
01045         }
01046 
01047         return self::linkKnown( SpecialPage::getTitleFor( $name ), wfMessage( $key )->text() );
01048     }
01049 
01060     public static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array(), $title = null ) {
01061         global $wgTitle;
01062         $class = "external";
01063         if ( $linktype ) {
01064             $class .= " $linktype";
01065         }
01066         if ( isset( $attribs['class'] ) && $attribs['class'] ) {
01067             $class .= " {$attribs['class']}";
01068         }
01069         $attribs['class'] = $class;
01070 
01071         if ( $escape ) {
01072             $text = htmlspecialchars( $text );
01073         }
01074 
01075         if ( !$title ) {
01076             $title = $wgTitle;
01077         }
01078         $attribs['rel'] = Parser::getExternalLinkRel( $url, $title );
01079         $link = '';
01080         $success = wfRunHooks( 'LinkerMakeExternalLink',
01081             array( &$url, &$text, &$link, &$attribs, $linktype ) );
01082         if ( !$success ) {
01083             wfDebug( "Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true );
01084             return $link;
01085         }
01086         $attribs['href'] = $url;
01087         return Html::rawElement( 'a', $attribs, $text );
01088     }
01089 
01098     public static function userLink( $userId, $userName, $altUserName = false ) {
01099         if ( $userId == 0 ) {
01100             $page = SpecialPage::getTitleFor( 'Contributions', $userName );
01101             if ( $altUserName === false ) {
01102                 $altUserName = IP::prettifyIP( $userName );
01103             }
01104         } else {
01105             $page = Title::makeTitle( NS_USER, $userName );
01106         }
01107 
01108         return self::link(
01109             $page,
01110             htmlspecialchars( $altUserName !== false ? $altUserName : $userName ),
01111             array( 'class' => 'mw-userlink' )
01112         );
01113     }
01114 
01126     public static function userToolLinks(
01127         $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null
01128     ) {
01129         global $wgUser, $wgDisableAnonTalk, $wgLang;
01130         $talkable = !( $wgDisableAnonTalk && 0 == $userId );
01131         $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
01132         $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
01133 
01134         $items = array();
01135         if ( $talkable ) {
01136             $items[] = self::userTalkLink( $userId, $userText );
01137         }
01138         if ( $userId ) {
01139             // check if the user has an edit
01140             $attribs = array();
01141             if ( $redContribsWhenNoEdits ) {
01142                 if ( intval( $edits ) === 0 && $edits !== 0 ) {
01143                     $user = User::newFromId( $userId );
01144                     $edits = $user->getEditCount();
01145                 }
01146                 if ( $edits === 0 ) {
01147                     $attribs['class'] = 'new';
01148                 }
01149             }
01150             $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
01151 
01152             $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
01153         }
01154         if ( $blockable && $wgUser->isAllowed( 'block' ) ) {
01155             $items[] = self::blockLink( $userId, $userText );
01156         }
01157 
01158         if ( $addEmailLink && $wgUser->canSendEmail() ) {
01159             $items[] = self::emailLink( $userId, $userText );
01160         }
01161 
01162         wfRunHooks( 'UserToolLinksEdit', array( $userId, $userText, &$items ) );
01163 
01164         if ( $items ) {
01165             return wfMessage( 'word-separator' )->plain()
01166                 . '<span class="mw-usertoollinks">'
01167                 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
01168                 . '</span>';
01169         } else {
01170             return '';
01171         }
01172     }
01173 
01181     public static function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
01182         return self::userToolLinks( $userId, $userText, true, 0, $edits );
01183     }
01184 
01190     public static function userTalkLink( $userId, $userText ) {
01191         $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
01192         $userTalkLink = self::link( $userTalkPage, wfMessage( 'talkpagelinktext' )->escaped() );
01193         return $userTalkLink;
01194     }
01195 
01201     public static function blockLink( $userId, $userText ) {
01202         $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
01203         $blockLink = self::link( $blockPage, wfMessage( 'blocklink' )->escaped() );
01204         return $blockLink;
01205     }
01206 
01212     public static function emailLink( $userId, $userText ) {
01213         $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
01214         $emailLink = self::link( $emailPage, wfMessage( 'emaillink' )->escaped() );
01215         return $emailLink;
01216     }
01217 
01224     public static function revUserLink( $rev, $isPublic = false ) {
01225         if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
01226             $link = wfMessage( 'rev-deleted-user' )->escaped();
01227         } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
01228             $link = self::userLink( $rev->getUser( Revision::FOR_THIS_USER ),
01229                 $rev->getUserText( Revision::FOR_THIS_USER ) );
01230         } else {
01231             $link = wfMessage( 'rev-deleted-user' )->escaped();
01232         }
01233         if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
01234             return '<span class="history-deleted">' . $link . '</span>';
01235         }
01236         return $link;
01237     }
01238 
01245     public static function revUserTools( $rev, $isPublic = false ) {
01246         if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
01247             $link = wfMessage( 'rev-deleted-user' )->escaped();
01248         } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
01249             $userId = $rev->getUser( Revision::FOR_THIS_USER );
01250             $userText = $rev->getUserText( Revision::FOR_THIS_USER );
01251             $link = self::userLink( $userId, $userText )
01252                 . wfMessage( 'word-separator' )->plain()
01253                 . self::userToolLinks( $userId, $userText );
01254         } else {
01255             $link = wfMessage( 'rev-deleted-user' )->escaped();
01256         }
01257         if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
01258             return ' <span class="history-deleted">' . $link . '</span>';
01259         }
01260         return $link;
01261     }
01262 
01280     public static function formatComment( $comment, $title = null, $local = false ) {
01281         wfProfileIn( __METHOD__ );
01282 
01283         # Sanitize text a bit:
01284         $comment = str_replace( "\n", " ", $comment );
01285         # Allow HTML entities (for bug 13815)
01286         $comment = Sanitizer::escapeHtmlAllowEntities( $comment );
01287 
01288         # Render autocomments and make links:
01289         $comment = self::formatAutocomments( $comment, $title, $local );
01290         $comment = self::formatLinksInComment( $comment, $title, $local );
01291 
01292         wfProfileOut( __METHOD__ );
01293         return $comment;
01294     }
01295 
01299     static $autocommentTitle;
01300     static $autocommentLocal;
01301 
01315     private static function formatAutocomments( $comment, $title = null, $local = false ) {
01316         // Bah!
01317         self::$autocommentTitle = $title;
01318         self::$autocommentLocal = $local;
01319         $comment = preg_replace_callback(
01320             '!(.*)/\*\s*(.*?)\s*\*/(.*)!',
01321             array( 'Linker', 'formatAutocommentsCallback' ),
01322             $comment );
01323         self::$autocommentTitle = null;
01324         self::$autocommentLocal = null;
01325         return $comment;
01326     }
01327 
01333     private static function formatAutocommentsCallback( $match ) {
01334         global $wgLang;
01335         $title = self::$autocommentTitle;
01336         $local = self::$autocommentLocal;
01337 
01338         $pre = $match[1];
01339         $auto = $match[2];
01340         $post = $match[3];
01341         $comment = null;
01342         wfRunHooks( 'FormatAutocomments', array( &$comment, $pre, $auto, $post, $title, $local ) );
01343         if ( $comment === null ) {
01344             $link = '';
01345             if ( $title ) {
01346                 $section = $auto;
01347 
01348                 # Remove links that a user may have manually put in the autosummary
01349                 # This could be improved by copying as much of Parser::stripSectionName as desired.
01350                 $section = str_replace( '[[:', '', $section );
01351                 $section = str_replace( '[[', '', $section );
01352                 $section = str_replace( ']]', '', $section );
01353 
01354                 $section = Sanitizer::normalizeSectionNameWhitespace( $section ); # bug 22784
01355                 if ( $local ) {
01356                     $sectionTitle = Title::newFromText( '#' . $section );
01357                 } else {
01358                     $sectionTitle = Title::makeTitleSafe( $title->getNamespace(),
01359                         $title->getDBkey(), $section );
01360                 }
01361                 if ( $sectionTitle ) {
01362                     $link = self::link( $sectionTitle,
01363                         $wgLang->getArrow(), array(), array(),
01364                         'noclasses' );
01365                 } else {
01366                     $link = '';
01367                 }
01368             }
01369             if ( $pre ) {
01370                 # written summary $presep autocomment (summary /* section */)
01371                 $pre .= wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped();
01372             }
01373             if ( $post ) {
01374                 # autocomment $postsep written summary (/* section */ summary)
01375                 $auto .= wfMessage( 'colon-separator' )->inContentLanguage()->escaped();
01376             }
01377             $auto = '<span class="autocomment">' . $auto . '</span>';
01378             $comment = $pre . $link . $wgLang->getDirMark() . '<span dir="auto">' . $auto . $post . '</span>';
01379         }
01380         return $comment;
01381     }
01382 
01386     static $commentContextTitle;
01387     static $commentLocal;
01388 
01399     public static function formatLinksInComment( $comment, $title = null, $local = false ) {
01400         self::$commentContextTitle = $title;
01401         self::$commentLocal = $local;
01402         $html = preg_replace_callback(
01403             '/
01404                 \[\[
01405                 :? # ignore optional leading colon
01406                 ([^\]|]+) # 1. link target; page names cannot include ] or |
01407                 (?:\|
01408                     # 2. a pipe-separated substring; only the last is captured
01409                     # Stop matching at | and ]] without relying on backtracking.
01410                     ((?:]?[^\]|])*+)
01411                 )*
01412                 \]\]
01413                 ([^[]*) # 3. link trail (the text up until the next link)
01414             /x',
01415             array( 'Linker', 'formatLinksInCommentCallback' ),
01416             $comment );
01417         self::$commentContextTitle = null;
01418         self::$commentLocal = null;
01419         return $html;
01420     }
01421 
01426     protected static function formatLinksInCommentCallback( $match ) {
01427         global $wgContLang;
01428 
01429         $medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
01430         $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):';
01431 
01432         $comment = $match[0];
01433 
01434         # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
01435         if ( strpos( $match[1], '%' ) !== false ) {
01436             $match[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $match[1] ) );
01437         }
01438 
01439         # Handle link renaming [[foo|text]] will show link as "text"
01440         if ( $match[2] != "" ) {
01441             $text = $match[2];
01442         } else {
01443             $text = $match[1];
01444         }
01445         $submatch = array();
01446         $thelink = null;
01447         if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
01448             # Media link; trail not supported.
01449             $linkRegexp = '/\[\[(.*?)\]\]/';
01450             $title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
01451             if ( $title ) {
01452                 $thelink = self::makeMediaLinkObj( $title, $text );
01453             }
01454         } else {
01455             # Other kind of link
01456             if ( preg_match( $wgContLang->linkTrail(), $match[3], $submatch ) ) {
01457                 $trail = $submatch[1];
01458             } else {
01459                 $trail = "";
01460             }
01461             $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
01462             if ( isset( $match[1][0] ) && $match[1][0] == ':' ) {
01463                 $match[1] = substr( $match[1], 1 );
01464             }
01465             list( $inside, $trail ) = self::splitTrail( $trail );
01466 
01467             $linkText = $text;
01468             $linkTarget = self::normalizeSubpageLink( self::$commentContextTitle,
01469                 $match[1], $linkText );
01470 
01471             $target = Title::newFromText( $linkTarget );
01472             if ( $target ) {
01473                 if ( $target->getText() == '' && $target->getInterwiki() === ''
01474                     && !self::$commentLocal && self::$commentContextTitle )
01475                 {
01476                     $newTarget = clone ( self::$commentContextTitle );
01477                     $newTarget->setFragment( '#' . $target->getFragment() );
01478                     $target = $newTarget;
01479                 }
01480                 $thelink = self::link(
01481                     $target,
01482                     $linkText . $inside
01483                 ) . $trail;
01484             }
01485         }
01486         if ( $thelink ) {
01487             // If the link is still valid, go ahead and replace it in!
01488             $comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 );
01489         }
01490 
01491         return $comment;
01492     }
01493 
01500     public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
01501         # Valid link forms:
01502         # Foobar -- normal
01503         # :Foobar -- override special treatment of prefix (images, language links)
01504         # /Foobar -- convert to CurrentPage/Foobar
01505         # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
01506         # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
01507         # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage
01508 
01509         wfProfileIn( __METHOD__ );
01510         $ret = $target; # default return value is no change
01511 
01512         # Some namespaces don't allow subpages,
01513         # so only perform processing if subpages are allowed
01514         if ( $contextTitle && MWNamespace::hasSubpages( $contextTitle->getNamespace() ) ) {
01515             $hash = strpos( $target, '#' );
01516             if ( $hash !== false ) {
01517                 $suffix = substr( $target, $hash );
01518                 $target = substr( $target, 0, $hash );
01519             } else {
01520                 $suffix = '';
01521             }
01522             # bug 7425
01523             $target = trim( $target );
01524             # Look at the first character
01525             if ( $target != '' && $target[0] === '/' ) {
01526                 # / at end means we don't want the slash to be shown
01527                 $m = array();
01528                 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
01529                 if ( $trailingSlashes ) {
01530                     $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
01531                 } else {
01532                     $noslash = substr( $target, 1 );
01533                 }
01534 
01535                 $ret = $contextTitle->getPrefixedText() . '/' . trim( $noslash ) . $suffix;
01536                 if ( $text === '' ) {
01537                     $text = $target . $suffix;
01538                 } # this might be changed for ugliness reasons
01539             } else {
01540                 # check for .. subpage backlinks
01541                 $dotdotcount = 0;
01542                 $nodotdot = $target;
01543                 while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
01544                     ++$dotdotcount;
01545                     $nodotdot = substr( $nodotdot, 3 );
01546                 }
01547                 if ( $dotdotcount > 0 ) {
01548                     $exploded = explode( '/', $contextTitle->getPrefixedText() );
01549                     if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
01550                         $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
01551                         # / at the end means don't show full path
01552                         if ( substr( $nodotdot, -1, 1 ) === '/' ) {
01553                             $nodotdot = substr( $nodotdot, 0, -1 );
01554                             if ( $text === '' ) {
01555                                 $text = $nodotdot . $suffix;
01556                             }
01557                         }
01558                         $nodotdot = trim( $nodotdot );
01559                         if ( $nodotdot != '' ) {
01560                             $ret .= '/' . $nodotdot;
01561                         }
01562                         $ret .= $suffix;
01563                     }
01564                 }
01565             }
01566         }
01567 
01568         wfProfileOut( __METHOD__ );
01569         return $ret;
01570     }
01571 
01582     public static function commentBlock( $comment, $title = null, $local = false ) {
01583         // '*' used to be the comment inserted by the software way back
01584         // in antiquity in case none was provided, here for backwards
01585         // compatibility, acc. to brion -ævar
01586         if ( $comment == '' || $comment == '*' ) {
01587             return '';
01588         } else {
01589             $formatted = self::formatComment( $comment, $title, $local );
01590             $formatted = wfMessage( 'parentheses' )->rawParams( $formatted )->escaped();
01591             return " <span class=\"comment\">$formatted</span>";
01592         }
01593     }
01594 
01604     public static function revComment( Revision $rev, $local = false, $isPublic = false ) {
01605         if ( $rev->getRawComment() == "" ) {
01606             return "";
01607         }
01608         if ( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
01609             $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
01610         } elseif ( $rev->userCan( Revision::DELETED_COMMENT ) ) {
01611             $block = self::commentBlock( $rev->getComment( Revision::FOR_THIS_USER ),
01612                 $rev->getTitle(), $local );
01613         } else {
01614             $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
01615         }
01616         if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
01617             return " <span class=\"history-deleted\">$block</span>";
01618         }
01619         return $block;
01620     }
01621 
01626     public static function formatRevisionSize( $size ) {
01627         if ( $size == 0 ) {
01628             $stxt = wfMessage( 'historyempty' )->escaped();
01629         } else {
01630             $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
01631             $stxt = wfMessage( 'parentheses' )->rawParams( $stxt )->escaped();
01632         }
01633         return "<span class=\"history-size\">$stxt</span>";
01634     }
01635 
01641     public static function tocIndent() {
01642         return "\n<ul>";
01643     }
01644 
01650     public static function tocUnindent( $level ) {
01651         return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
01652     }
01653 
01659     public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
01660         $classes = "toclevel-$level";
01661         if ( $sectionIndex !== false ) {
01662             $classes .= " tocsection-$sectionIndex";
01663         }
01664         return "\n<li class=\"$classes\"><a href=\"#" .
01665             $anchor . '"><span class="tocnumber">' .
01666             $tocnumber . '</span> <span class="toctext">' .
01667             $tocline . '</span></a>';
01668     }
01669 
01676     public static function tocLineEnd() {
01677         return "</li>\n";
01678     }
01679 
01687     public static function tocList( $toc, $lang = false ) {
01688         $lang = wfGetLangObj( $lang );
01689         $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
01690 
01691         return '<div id="toc" class="toc">'
01692             . '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
01693             . $toc
01694             . "</ul>\n</div>\n";
01695     }
01696 
01704     public static function generateTOC( $tree ) {
01705         $toc = '';
01706         $lastLevel = 0;
01707         foreach ( $tree as $section ) {
01708             if ( $section['toclevel'] > $lastLevel ) {
01709                 $toc .= self::tocIndent();
01710             } elseif ( $section['toclevel'] < $lastLevel ) {
01711                 $toc .= self::tocUnindent(
01712                     $lastLevel - $section['toclevel'] );
01713             } else {
01714                 $toc .= self::tocLineEnd();
01715             }
01716 
01717             $toc .= self::tocLine( $section['anchor'],
01718                 $section['line'], $section['number'],
01719                 $section['toclevel'], $section['index'] );
01720             $lastLevel = $section['toclevel'];
01721         }
01722         $toc .= self::tocLineEnd();
01723         return self::tocList( $toc );
01724     }
01725 
01741     public static function makeHeadline( $level, $attribs, $anchor, $html, $link, $legacyAnchor = false ) {
01742         $ret = "<h$level$attribs"
01743             . "<span class=\"mw-headline\" id=\"$anchor\">$html</span>"
01744             . $link
01745             . "</h$level>";
01746         if ( $legacyAnchor !== false ) {
01747             $ret = "<div id=\"$legacyAnchor\"></div>$ret";
01748         }
01749         return $ret;
01750     }
01751 
01757     static function splitTrail( $trail ) {
01758         global $wgContLang;
01759         $regex = $wgContLang->linkTrail();
01760         $inside = '';
01761         if ( $trail !== '' ) {
01762             $m = array();
01763             if ( preg_match( $regex, $trail, $m ) ) {
01764                 $inside = $m[1];
01765                 $trail = $m[2];
01766             }
01767         }
01768         return array( $inside, $trail );
01769     }
01770 
01796     public static function generateRollback( $rev, IContextSource $context = null, $options = array( 'verify' ) ) {
01797         if ( $context === null ) {
01798             $context = RequestContext::getMain();
01799         }
01800         $editCount = false;
01801         if ( in_array( 'verify', $options ) ) {
01802             $editCount = self::getRollbackEditCount( $rev, true );
01803             if ( $editCount === false ) {
01804                 return '';
01805             }
01806         }
01807 
01808         $inner = self::buildRollbackLink( $rev, $context, $editCount );
01809 
01810         if ( !in_array( 'noBrackets', $options ) ) {
01811             $inner = $context->msg( 'brackets' )->rawParams( $inner )->plain();
01812         }
01813 
01814         return '<span class="mw-rollback-link">' . $inner . '</span>';
01815     }
01816 
01832     public static function getRollbackEditCount( $rev, $verify ) {
01833         global $wgShowRollbackEditCount;
01834         if ( !is_int( $wgShowRollbackEditCount ) || !$wgShowRollbackEditCount > 0 ) {
01835             // Nothing has happened, indicate this by returning 'null'
01836             return null;
01837         }
01838 
01839         $dbr = wfGetDB( DB_SLAVE );
01840 
01841         // Up to the value of $wgShowRollbackEditCount revisions are counted
01842         $res = $dbr->select(
01843             'revision',
01844             array( 'rev_user_text', 'rev_deleted' ),
01845             // $rev->getPage() returns null sometimes
01846             array( 'rev_page' => $rev->getTitle()->getArticleID() ),
01847             __METHOD__,
01848             array(
01849                 'USE INDEX' => array( 'revision' => 'page_timestamp' ),
01850                 'ORDER BY' => 'rev_timestamp DESC',
01851                 'LIMIT' => $wgShowRollbackEditCount + 1
01852             )
01853         );
01854 
01855         $editCount = 0;
01856         $moreRevs = false;
01857         foreach ( $res as $row ) {
01858             if ( $rev->getRawUserText() != $row->rev_user_text ) {
01859                 if ( $verify && ( $row->rev_deleted & Revision::DELETED_TEXT || $row->rev_deleted & Revision::DELETED_USER ) ) {
01860                     // If the user or the text of the revision we might rollback to is deleted in some way we can't rollback
01861                     // Similar to the sanity checks in WikiPage::commitRollback
01862                     return false;
01863                 }
01864                 $moreRevs = true;
01865                 break;
01866             }
01867             $editCount++;
01868         }
01869 
01870         if ( $verify && $editCount <= $wgShowRollbackEditCount && !$moreRevs ) {
01871             // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
01872             // and there weren't any other revisions. That means that the current user is the only
01873             // editor, so we can't rollback
01874             return false;
01875         }
01876         return $editCount;
01877     }
01878 
01887     public static function buildRollbackLink( $rev, IContextSource $context = null, $editCount = false ) {
01888         global $wgShowRollbackEditCount, $wgMiserMode;
01889 
01890         // To config which pages are effected by miser mode
01891         $disableRollbackEditCountSpecialPage = array( 'Recentchanges', 'Watchlist' );
01892 
01893         if ( $context === null ) {
01894             $context = RequestContext::getMain();
01895         }
01896 
01897         $title = $rev->getTitle();
01898         $query = array(
01899             'action' => 'rollback',
01900             'from' => $rev->getUserText(),
01901             'token' => $context->getUser()->getEditToken( array( $title->getPrefixedText(), $rev->getUserText() ) ),
01902         );
01903         if ( $context->getRequest()->getBool( 'bot' ) ) {
01904             $query['bot'] = '1';
01905             $query['hidediff'] = '1'; // bug 15999
01906         }
01907 
01908         $disableRollbackEditCount = false;
01909         if ( $wgMiserMode ) {
01910             foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
01911                 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
01912                     $disableRollbackEditCount = true;
01913                     break;
01914                 }
01915             }
01916         }
01917 
01918         if ( !$disableRollbackEditCount && is_int( $wgShowRollbackEditCount ) && $wgShowRollbackEditCount > 0 ) {
01919             if ( !is_numeric( $editCount ) ) {
01920                 $editCount = self::getRollbackEditCount( $rev, false );
01921             }
01922 
01923             if ( $editCount > $wgShowRollbackEditCount ) {
01924                 $editCount_output = $context->msg( 'rollbacklinkcount-morethan' )->numParams( $wgShowRollbackEditCount )->parse();
01925             } else {
01926                 $editCount_output = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
01927             }
01928 
01929             return self::link(
01930                 $title,
01931                 $editCount_output,
01932                 array( 'title' => $context->msg( 'tooltip-rollback' )->text() ),
01933                 $query,
01934                 array( 'known', 'noclasses' )
01935             );
01936         } else {
01937             return self::link(
01938                 $title,
01939                 $context->msg( 'rollbacklink' )->escaped(),
01940                 array( 'title' => $context->msg( 'tooltip-rollback' )->text() ),
01941                 $query,
01942                 array( 'known', 'noclasses' )
01943             );
01944         }
01945     }
01946 
01962     public static function formatTemplates( $templates, $preview = false, $section = false, $more = null ) {
01963         global $wgLang;
01964         wfProfileIn( __METHOD__ );
01965 
01966         $outText = '';
01967         if ( count( $templates ) > 0 ) {
01968             # Do a batch existence check
01969             $batch = new LinkBatch;
01970             foreach ( $templates as $title ) {
01971                 $batch->addObj( $title );
01972             }
01973             $batch->execute();
01974 
01975             # Construct the HTML
01976             $outText = '<div class="mw-templatesUsedExplanation">';
01977             if ( $preview ) {
01978                 $outText .= wfMessage( 'templatesusedpreview' )->numParams( count( $templates ) )
01979                     ->parseAsBlock();
01980             } elseif ( $section ) {
01981                 $outText .= wfMessage( 'templatesusedsection' )->numParams( count( $templates ) )
01982                     ->parseAsBlock();
01983             } else {
01984                 $outText .= wfMessage( 'templatesused' )->numParams( count( $templates ) )
01985                     ->parseAsBlock();
01986             }
01987             $outText .= "</div><ul>\n";
01988 
01989             usort( $templates, 'Title::compare' );
01990             foreach ( $templates as $titleObj ) {
01991                 $protected = '';
01992                 $restrictions = $titleObj->getRestrictions( 'edit' );
01993                 if ( $restrictions ) {
01994                     // Check backwards-compatible messages
01995                     $msg = null;
01996                     if ( $restrictions === array( 'sysop' ) ) {
01997                         $msg = wfMessage( 'template-protected' );
01998                     } elseif ( $restrictions === array( 'autoconfirmed' ) ) {
01999                         $msg = wfMessage( 'template-semiprotected' );
02000                     }
02001                     if ( $msg && !$msg->isDisabled() ) {
02002                         $protected = $msg->parse();
02003                     } else {
02004                         // Construct the message from restriction-level-*
02005                         // e.g. restriction-level-sysop, restriction-level-autoconfirmed
02006                         $msgs = array();
02007                         foreach ( $restrictions as $r ) {
02008                             $msgs[] = wfMessage( "restriction-level-$r" )->parse();
02009                         }
02010                         $protected = wfMessage( 'parentheses' )
02011                             ->rawParams( $wgLang->commaList( $msgs ) )->escaped();
02012                     }
02013                 }
02014                 if ( $titleObj->quickUserCan( 'edit' ) ) {
02015                     $editLink = self::link(
02016                         $titleObj,
02017                         wfMessage( 'editlink' )->text(),
02018                         array(),
02019                         array( 'action' => 'edit' )
02020                     );
02021                 } else {
02022                     $editLink = self::link(
02023                         $titleObj,
02024                         wfMessage( 'viewsourcelink' )->text(),
02025                         array(),
02026                         array( 'action' => 'edit' )
02027                     );
02028                 }
02029                 $outText .= '<li>' . self::link( $titleObj )
02030                     . wfMessage( 'word-separator' )->escaped()
02031                     . wfMessage( 'parentheses' )->rawParams( $editLink )->escaped()
02032                     . wfMessage( 'word-separator' )->escaped()
02033                     . $protected . '</li>';
02034             }
02035 
02036             if ( $more instanceof Title ) {
02037                 $outText .= '<li>' . self::link( $more, wfMessage( 'moredotdotdot' ) ) . '</li>';
02038             } elseif ( $more ) {
02039                 $outText .= "<li>$more</li>";
02040             }
02041 
02042             $outText .= '</ul>';
02043         }
02044         wfProfileOut( __METHOD__ );
02045         return $outText;
02046     }
02047 
02055     public static function formatHiddenCategories( $hiddencats ) {
02056         wfProfileIn( __METHOD__ );
02057 
02058         $outText = '';
02059         if ( count( $hiddencats ) > 0 ) {
02060             # Construct the HTML
02061             $outText = '<div class="mw-hiddenCategoriesExplanation">';
02062             $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
02063             $outText .= "</div><ul>\n";
02064 
02065             foreach ( $hiddencats as $titleObj ) {
02066                 $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
02067             }
02068             $outText .= '</ul>';
02069         }
02070         wfProfileOut( __METHOD__ );
02071         return $outText;
02072     }
02073 
02081     public static function formatSize( $size ) {
02082         global $wgLang;
02083         return htmlspecialchars( $wgLang->formatSize( $size ) );
02084     }
02085 
02098     public static function titleAttrib( $name, $options = null ) {
02099         wfProfileIn( __METHOD__ );
02100 
02101         $message = wfMessage( "tooltip-$name" );
02102 
02103         if ( !$message->exists() ) {
02104             $tooltip = false;
02105         } else {
02106             $tooltip = $message->text();
02107             # Compatibility: formerly some tooltips had [alt-.] hardcoded
02108             $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
02109             # Message equal to '-' means suppress it.
02110             if ( $tooltip == '-' ) {
02111                 $tooltip = false;
02112             }
02113         }
02114 
02115         if ( $options == 'withaccess' ) {
02116             $accesskey = self::accesskey( $name );
02117             if ( $accesskey !== false ) {
02118                 if ( $tooltip === false || $tooltip === '' ) {
02119                     $tooltip = "[$accesskey]";
02120                 } else {
02121                     $tooltip .= " [$accesskey]";
02122                 }
02123             }
02124         }
02125 
02126         wfProfileOut( __METHOD__ );
02127         return $tooltip;
02128     }
02129 
02130     static $accesskeycache;
02131 
02142     public static function accesskey( $name ) {
02143         if ( isset( self::$accesskeycache[$name] ) ) {
02144             return self::$accesskeycache[$name];
02145         }
02146         wfProfileIn( __METHOD__ );
02147 
02148         $message = wfMessage( "accesskey-$name" );
02149 
02150         if ( !$message->exists() ) {
02151             $accesskey = false;
02152         } else {
02153             $accesskey = $message->plain();
02154             if ( $accesskey === '' || $accesskey === '-' ) {
02155                 # @todo FIXME: Per standard MW behavior, a value of '-' means to suppress the
02156                 # attribute, but this is broken for accesskey: that might be a useful
02157                 # value.
02158                 $accesskey = false;
02159             }
02160         }
02161 
02162         wfProfileOut( __METHOD__ );
02163         return self::$accesskeycache[$name] = $accesskey;
02164     }
02165 
02179     public static function getRevDeleteLink( User $user, Revision $rev, Title $title ) {
02180         $canHide = $user->isAllowed( 'deleterevision' );
02181         if ( !$canHide && !( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
02182             return '';
02183         }
02184 
02185         if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
02186             return Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
02187         } else {
02188             if ( $rev->getId() ) {
02189                 // RevDelete links using revision ID are stable across
02190                 // page deletion and undeletion; use when possible.
02191                 $query = array(
02192                     'type' => 'revision',
02193                     'target' => $title->getPrefixedDBkey(),
02194                     'ids' => $rev->getId()
02195                 );
02196             } else {
02197                 // Older deleted entries didn't save a revision ID.
02198                 // We have to refer to these by timestamp, ick!
02199                 $query = array(
02200                     'type' => 'archive',
02201                     'target' => $title->getPrefixedDBkey(),
02202                     'ids' => $rev->getTimestamp()
02203                 );
02204             }
02205             return Linker::revDeleteLink( $query,
02206                 $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
02207         }
02208     }
02209 
02220     public static function revDeleteLink( $query = array(), $restricted = false, $delete = true ) {
02221         $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
02222         $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
02223         $html = wfMessage( $msgKey )->escaped();
02224         $tag = $restricted ? 'strong' : 'span';
02225         $link = self::link( $sp, $html, array(), $query, array( 'known', 'noclasses' ) );
02226         return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), wfMessage( 'parentheses' )->rawParams( $link )->escaped() );
02227     }
02228 
02237     public static function revDeleteLinkDisabled( $delete = true ) {
02238         $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
02239         $html = wfMessage( $msgKey )->escaped();
02240         $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
02241         return Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), $htmlParentheses );
02242     }
02243 
02244     /* Deprecated methods */
02245 
02260     static function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
02261         wfDeprecated( __METHOD__, '1.16' );
02262 
02263         $nt = Title::newFromText( $title );
02264         if ( $nt instanceof Title ) {
02265             return self::makeBrokenLinkObj( $nt, $text, $query, $trail );
02266         } else {
02267             wfDebug( 'Invalid title passed to self::makeBrokenLink(): "' . $title . "\"\n" );
02268             return $text == '' ? $title : $text;
02269         }
02270     }
02271 
02289     static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
02290         wfDeprecated( __METHOD__, '1.21' );
02291 
02292         wfProfileIn( __METHOD__ );
02293         $query = wfCgiToArray( $query );
02294         list( $inside, $trail ) = self::splitTrail( $trail );
02295         if ( $text === '' ) {
02296             $text = self::linkText( $nt );
02297         }
02298 
02299         $ret = self::link( $nt, "$prefix$text$inside", array(), $query ) . $trail;
02300 
02301         wfProfileOut( __METHOD__ );
02302         return $ret;
02303     }
02304 
02321     static function makeKnownLinkObj(
02322         $title, $text = '', $query = '', $trail = '', $prefix = '', $aprops = '', $style = ''
02323     ) {
02324         wfDeprecated( __METHOD__, '1.21' );
02325 
02326         wfProfileIn( __METHOD__ );
02327 
02328         if ( $text == '' ) {
02329             $text = self::linkText( $title );
02330         }
02331         $attribs = Sanitizer::mergeAttributes(
02332             Sanitizer::decodeTagAttributes( $aprops ),
02333             Sanitizer::decodeTagAttributes( $style )
02334         );
02335         $query = wfCgiToArray( $query );
02336         list( $inside, $trail ) = self::splitTrail( $trail );
02337 
02338         $ret = self::link( $title, "$prefix$text$inside", $attribs, $query,
02339             array( 'known', 'noclasses' ) ) . $trail;
02340 
02341         wfProfileOut( __METHOD__ );
02342         return $ret;
02343     }
02344 
02359     static function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
02360         wfDeprecated( __METHOD__, '1.16' );
02361 
02362         wfProfileIn( __METHOD__ );
02363 
02364         list( $inside, $trail ) = self::splitTrail( $trail );
02365         if ( $text === '' ) {
02366             $text = self::linkText( $title );
02367         }
02368 
02369         $ret = self::link( $title, "$prefix$text$inside", array(),
02370             wfCgiToArray( $query ), 'broken' ) . $trail;
02371 
02372         wfProfileOut( __METHOD__ );
02373         return $ret;
02374     }
02375 
02391     static function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
02392         wfDeprecated( __METHOD__, '1.16' );
02393 
02394         if ( $colour != '' ) {
02395             $style = self::getInternalLinkAttributesObj( $nt, $text, $colour );
02396         } else {
02397             $style = '';
02398         }
02399         return self::makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style );
02400     }
02401 
02406     public static function tooltipAndAccesskeyAttribs( $name ) {
02407         # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
02408         # no attribute" instead of "output '' as value for attribute", this
02409         # would be three lines.
02410         $attribs = array(
02411             'title' => self::titleAttrib( $name, 'withaccess' ),
02412             'accesskey' => self::accesskey( $name )
02413         );
02414         if ( $attribs['title'] === false ) {
02415             unset( $attribs['title'] );
02416         }
02417         if ( $attribs['accesskey'] === false ) {
02418             unset( $attribs['accesskey'] );
02419         }
02420         return $attribs;
02421     }
02422 
02427     public static function tooltip( $name, $options = null ) {
02428         # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
02429         # no attribute" instead of "output '' as value for attribute", this
02430         # would be two lines.
02431         $tooltip = self::titleAttrib( $name, $options );
02432         if ( $tooltip === false ) {
02433             return '';
02434         }
02435         return Xml::expandAttributes( array(
02436             'title' => $tooltip
02437         ) );
02438     }
02439 }
02440 
02444 class DummyLinker {
02445 
02454     public function __call( $fname, $args ) {
02455         return call_user_func_array( array( 'Linker', $fname ), $args );
02456     }
02457 }