MediaWiki  REL1_23
Linker.php
Go to the documentation of this file.
00001 <?php
00032 class Linker {
00033 
00037     const TOOL_LINKS_NOBLOCK = 1;
00038     const TOOL_LINKS_EMAIL = 2;
00039 
00049     static function getExternalLinkAttributes( $class = 'external' ) {
00050         wfDeprecated( __METHOD__, '1.18' );
00051         return self::getLinkAttributesInternal( '', $class );
00052     }
00053 
00064     static function getInterwikiLinkAttributes( $title, $unused = null, $class = 'external' ) {
00065         global $wgContLang;
00066 
00067         # @todo FIXME: We have a whole bunch of handling here that doesn't happen in
00068         # getExternalLinkAttributes, why?
00069         $title = urldecode( $title );
00070         $title = $wgContLang->checkTitleEncoding( $title );
00071         $title = preg_replace( '/[\\x00-\\x1f]/', ' ', $title );
00072 
00073         return self::getLinkAttributesInternal( $title, $class );
00074     }
00075 
00085     static function getInternalLinkAttributes( $title, $unused = null, $class = '' ) {
00086         $title = urldecode( $title );
00087         $title = str_replace( '_', ' ', $title );
00088         return self::getLinkAttributesInternal( $title, $class );
00089     }
00090 
00102     static function getInternalLinkAttributesObj( $nt, $unused = null, $class = '', $title = false ) {
00103         if ( $title === false ) {
00104             $title = $nt->getPrefixedText();
00105         }
00106         return self::getLinkAttributesInternal( $title, $class );
00107     }
00108 
00117     private static function getLinkAttributesInternal( $title, $class ) {
00118         $title = htmlspecialchars( $title );
00119         $class = htmlspecialchars( $class );
00120         $r = '';
00121         if ( $class != '' ) {
00122             $r .= " class=\"$class\"";
00123         }
00124         if ( $title != '' ) {
00125             $r .= " title=\"$title\"";
00126         }
00127         return $r;
00128     }
00129 
00137     public static function getLinkColour( $t, $threshold ) {
00138         $colour = '';
00139         if ( $t->isRedirect() ) {
00140             # Page is a redirect
00141             $colour = 'mw-redirect';
00142         } elseif ( $threshold > 0 && $t->isContentPage() &&
00143             $t->exists() && $t->getLength() < $threshold
00144         ) {
00145             # Page is a stub
00146             $colour = 'stub';
00147         }
00148         return $colour;
00149     }
00150 
00192     public static function link(
00193         $target, $html = null, $customAttribs = array(), $query = array(), $options = array()
00194     ) {
00195         wfProfileIn( __METHOD__ );
00196         if ( !$target instanceof Title ) {
00197             wfProfileOut( __METHOD__ );
00198             return "<!-- ERROR -->$html";
00199         }
00200 
00201         if ( is_string( $query ) ) {
00202             // some functions withing core using this still hand over query strings
00203             wfDeprecated( __METHOD__ . ' with parameter $query as string (should be array)', '1.20' );
00204             $query = wfCgiToArray( $query );
00205         }
00206         $options = (array)$options;
00207 
00208         $dummy = new DummyLinker; // dummy linker instance for bc on the hooks
00209 
00210         $ret = null;
00211         if ( !wfRunHooks( 'LinkBegin', array( $dummy, $target, &$html,
00212         &$customAttribs, &$query, &$options, &$ret ) ) ) {
00213             wfProfileOut( __METHOD__ );
00214             return $ret;
00215         }
00216 
00217         # Normalize the Title if it's a special page
00218         $target = self::normaliseSpecialPage( $target );
00219 
00220         # If we don't know whether the page exists, let's find out.
00221         wfProfileIn( __METHOD__ . '-checkPageExistence' );
00222         if ( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) {
00223             if ( $target->isKnown() ) {
00224                 $options[] = 'known';
00225             } else {
00226                 $options[] = 'broken';
00227             }
00228         }
00229         wfProfileOut( __METHOD__ . '-checkPageExistence' );
00230 
00231         $oldquery = array();
00232         if ( in_array( "forcearticlepath", $options ) && $query ) {
00233             $oldquery = $query;
00234             $query = array();
00235         }
00236 
00237         # Note: we want the href attribute first, for prettiness.
00238         $attribs = array( 'href' => self::linkUrl( $target, $query, $options ) );
00239         if ( in_array( 'forcearticlepath', $options ) && $oldquery ) {
00240             $attribs['href'] = wfAppendQuery( $attribs['href'], $oldquery );
00241         }
00242 
00243         $attribs = array_merge(
00244             $attribs,
00245             self::linkAttribs( $target, $customAttribs, $options )
00246         );
00247         if ( is_null( $html ) ) {
00248             $html = self::linkText( $target );
00249         }
00250 
00251         $ret = null;
00252         if ( wfRunHooks( 'LinkEnd', array( $dummy, $target, $options, &$html, &$attribs, &$ret ) ) ) {
00253             $ret = Html::rawElement( 'a', $attribs, $html );
00254         }
00255 
00256         wfProfileOut( __METHOD__ );
00257         return $ret;
00258     }
00259 
00264     public static function linkKnown(
00265         $target, $html = null, $customAttribs = array(),
00266         $query = array(), $options = array( 'known', 'noclasses' )
00267     ) {
00268         return self::link( $target, $html, $customAttribs, $query, $options );
00269     }
00270 
00279     private static function linkUrl( $target, $query, $options ) {
00280         wfProfileIn( __METHOD__ );
00281         # We don't want to include fragments for broken links, because they
00282         # generally make no sense.
00283         if ( in_array( 'broken', $options ) && $target->hasFragment() ) {
00284             $target = clone $target;
00285             $target->setFragment( '' );
00286         }
00287 
00288         # If it's a broken link, add the appropriate query pieces, unless
00289         # there's already an action specified, or unless 'edit' makes no sense
00290         # (i.e., for a nonexistent special page).
00291         if ( in_array( 'broken', $options ) && empty( $query['action'] )
00292             && !$target->isSpecialPage() ) {
00293             $query['action'] = 'edit';
00294             $query['redlink'] = '1';
00295         }
00296 
00297         if ( in_array( 'http', $options ) ) {
00298             $proto = PROTO_HTTP;
00299         } elseif ( in_array( 'https', $options ) ) {
00300             $proto = PROTO_HTTPS;
00301         } else {
00302             $proto = PROTO_RELATIVE;
00303         }
00304 
00305         $ret = $target->getLinkURL( $query, false, $proto );
00306         wfProfileOut( __METHOD__ );
00307         return $ret;
00308     }
00309 
00319     private static function linkAttribs( $target, $attribs, $options ) {
00320         wfProfileIn( __METHOD__ );
00321         global $wgUser;
00322         $defaults = array();
00323 
00324         if ( !in_array( 'noclasses', $options ) ) {
00325             wfProfileIn( __METHOD__ . '-getClasses' );
00326             # Now build the classes.
00327             $classes = array();
00328 
00329             if ( in_array( 'broken', $options ) ) {
00330                 $classes[] = 'new';
00331             }
00332 
00333             if ( $target->isExternal() ) {
00334                 $classes[] = 'extiw';
00335             }
00336 
00337             if ( !in_array( 'broken', $options ) ) { # Avoid useless calls to LinkCache (see r50387)
00338                 $colour = self::getLinkColour( $target, $wgUser->getStubThreshold() );
00339                 if ( $colour !== '' ) {
00340                     $classes[] = $colour; # mw-redirect or stub
00341                 }
00342             }
00343             if ( $classes != array() ) {
00344                 $defaults['class'] = implode( ' ', $classes );
00345             }
00346             wfProfileOut( __METHOD__ . '-getClasses' );
00347         }
00348 
00349         # Get a default title attribute.
00350         if ( $target->getPrefixedText() == '' ) {
00351             # A link like [[#Foo]].  This used to mean an empty title
00352             # attribute, but that's silly.  Just don't output a title.
00353         } elseif ( in_array( 'known', $options ) ) {
00354             $defaults['title'] = $target->getPrefixedText();
00355         } else {
00356             $defaults['title'] = wfMessage( 'red-link-title', $target->getPrefixedText() )->text();
00357         }
00358 
00359         # Finally, merge the custom attribs with the default ones, and iterate
00360         # over that, deleting all "false" attributes.
00361         $ret = array();
00362         $merged = Sanitizer::mergeAttributes( $defaults, $attribs );
00363         foreach ( $merged as $key => $val ) {
00364             # A false value suppresses the attribute, and we don't want the
00365             # href attribute to be overridden.
00366             if ( $key != 'href' and $val !== false ) {
00367                 $ret[$key] = $val;
00368             }
00369         }
00370         wfProfileOut( __METHOD__ );
00371         return $ret;
00372     }
00373 
00381     private static function linkText( $target ) {
00382         // We might be passed a non-Title by make*LinkObj().  Fail gracefully.
00383         if ( !$target instanceof Title ) {
00384             return '';
00385         }
00386 
00387         // If the target is just a fragment, with no title, we return the fragment
00388         // text.  Otherwise, we return the title text itself.
00389         if ( $target->getPrefixedText() === '' && $target->hasFragment() ) {
00390             return htmlspecialchars( $target->getFragment() );
00391         }
00392         return htmlspecialchars( $target->getPrefixedText() );
00393     }
00394 
00409     public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) {
00410         if ( $html == '' ) {
00411             $html = htmlspecialchars( $nt->getPrefixedText() );
00412         }
00413         list( $inside, $trail ) = self::splitTrail( $trail );
00414         return "<strong class=\"selflink\">{$prefix}{$html}{$inside}</strong>{$trail}";
00415     }
00416 
00427     public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
00428         global $wgContLang;
00429 
00430         // First we check whether the namespace exists or not.
00431         if ( MWNamespace::exists( $namespace ) ) {
00432             if ( $namespace == NS_MAIN ) {
00433                 $name = $context->msg( 'blanknamespace' )->text();
00434             } else {
00435                 $name = $wgContLang->getFormattedNsText( $namespace );
00436             }
00437             return $context->msg( 'invalidtitle-knownnamespace', $namespace, $name, $title )->text();
00438         } else {
00439             return $context->msg( 'invalidtitle-unknownnamespace', $namespace, $title )->text();
00440         }
00441     }
00442 
00447     static function normaliseSpecialPage( Title $title ) {
00448         if ( $title->isSpecialPage() ) {
00449             list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
00450             if ( !$name ) {
00451                 return $title;
00452             }
00453             $ret = SpecialPage::getTitleFor( $name, $subpage, $title->getFragment() );
00454             return $ret;
00455         } else {
00456             return $title;
00457         }
00458     }
00459 
00468     private static function fnamePart( $url ) {
00469         $basename = strrchr( $url, '/' );
00470         if ( false === $basename ) {
00471             $basename = $url;
00472         } else {
00473             $basename = substr( $basename, 1 );
00474         }
00475         return $basename;
00476     }
00477 
00487     public static function makeExternalImage( $url, $alt = '' ) {
00488         if ( $alt == '' ) {
00489             $alt = self::fnamePart( $url );
00490         }
00491         $img = '';
00492         $success = wfRunHooks( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
00493         if ( !$success ) {
00494             wfDebug( "Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}\n", true );
00495             return $img;
00496         }
00497         return Html::element( 'img',
00498             array(
00499                 'src' => $url,
00500                 'alt' => $alt ) );
00501     }
00502 
00539     public static function makeImageLink( /*Parser*/ $parser, Title $title, $file, $frameParams = array(),
00540         $handlerParams = array(), $time = false, $query = "", $widthOption = null
00541     ) {
00542         $res = null;
00543         $dummy = new DummyLinker;
00544         if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$dummy, &$title,
00545             &$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) {
00546             return $res;
00547         }
00548 
00549         if ( $file && !$file->allowInlineDisplay() ) {
00550             wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display\n" );
00551             return self::link( $title );
00552         }
00553 
00554         // Shortcuts
00555         $fp =& $frameParams;
00556         $hp =& $handlerParams;
00557 
00558         // Clean up parameters
00559         $page = isset( $hp['page'] ) ? $hp['page'] : false;
00560         if ( !isset( $fp['align'] ) ) {
00561             $fp['align'] = '';
00562         }
00563         if ( !isset( $fp['alt'] ) ) {
00564             $fp['alt'] = '';
00565         }
00566         if ( !isset( $fp['title'] ) ) {
00567             $fp['title'] = '';
00568         }
00569         if ( !isset( $fp['class'] ) ) {
00570             $fp['class'] = '';
00571         }
00572 
00573         $prefix = $postfix = '';
00574 
00575         if ( 'center' == $fp['align'] ) {
00576             $prefix = '<div class="center">';
00577             $postfix = '</div>';
00578             $fp['align'] = 'none';
00579         }
00580         if ( $file && !isset( $hp['width'] ) ) {
00581             if ( isset( $hp['height'] ) && $file->isVectorized() ) {
00582                 // If its a vector image, and user only specifies height
00583                 // we don't want it to be limited by its "normal" width.
00584                 global $wgSVGMaxSize;
00585                 $hp['width'] = $wgSVGMaxSize;
00586             } else {
00587                 $hp['width'] = $file->getWidth( $page );
00588             }
00589 
00590             if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
00591                 global $wgThumbLimits, $wgThumbUpright;
00592                 if ( $widthOption === null || !isset( $wgThumbLimits[$widthOption] ) ) {
00593                     $widthOption = User::getDefaultOption( 'thumbsize' );
00594                 }
00595 
00596                 // Reduce width for upright images when parameter 'upright' is used
00597                 if ( isset( $fp['upright'] ) && $fp['upright'] == 0 ) {
00598                     $fp['upright'] = $wgThumbUpright;
00599                 }
00600                 // 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
00601                 $prefWidth = isset( $fp['upright'] ) ?
00602                     round( $wgThumbLimits[$widthOption] * $fp['upright'], -1 ) :
00603                     $wgThumbLimits[$widthOption];
00604 
00605                 // Use width which is smaller: real image width or user preference width
00606                 // Unless image is scalable vector.
00607                 if ( !isset( $hp['height'] ) && ( $hp['width'] <= 0 ||
00608                         $prefWidth < $hp['width'] || $file->isVectorized() ) ) {
00609                     $hp['width'] = $prefWidth;
00610                 }
00611             }
00612         }
00613 
00614         if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) ) {
00615             # Create a thumbnail. Alignment depends on the writing direction of
00616             # the page content language (right-aligned for LTR languages,
00617             # left-aligned for RTL languages)
00618             #
00619             # If a thumbnail width has not been provided, it is set
00620             # to the default user option as specified in Language*.php
00621             if ( $fp['align'] == '' ) {
00622                 if ( $parser instanceof Parser ) {
00623                     $fp['align'] = $parser->getTargetLanguage()->alignEnd();
00624                 } else {
00625                     # backwards compatibility, remove with makeImageLink2()
00626                     global $wgContLang;
00627                     $fp['align'] = $wgContLang->alignEnd();
00628                 }
00629             }
00630             return $prefix . self::makeThumbLink2( $title, $file, $fp, $hp, $time, $query ) . $postfix;
00631         }
00632 
00633         if ( $file && isset( $fp['frameless'] ) ) {
00634             $srcWidth = $file->getWidth( $page );
00635             # For "frameless" option: do not present an image bigger than the source (for bitmap-style images)
00636             # This is the same behavior as the "thumb" option does it already.
00637             if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
00638                 $hp['width'] = $srcWidth;
00639             }
00640         }
00641 
00642         if ( $file && isset( $hp['width'] ) ) {
00643             # Create a resized image, without the additional thumbnail features
00644             $thumb = $file->transform( $hp );
00645         } else {
00646             $thumb = false;
00647         }
00648 
00649         if ( !$thumb ) {
00650             $s = self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
00651         } else {
00652             self::processResponsiveImages( $file, $thumb, $hp );
00653             $params = array(
00654                 'alt' => $fp['alt'],
00655                 'title' => $fp['title'],
00656                 'valign' => isset( $fp['valign'] ) ? $fp['valign'] : false,
00657                 'img-class' => $fp['class'] );
00658             if ( isset( $fp['border'] ) ) {
00659                 $params['img-class'] .= ( $params['img-class'] !== '' ? ' ' : '' ) . 'thumbborder';
00660             }
00661             $params = self::getImageLinkMTOParams( $fp, $query, $parser ) + $params;
00662 
00663             $s = $thumb->toHtml( $params );
00664         }
00665         if ( $fp['align'] != '' ) {
00666             $s = "<div class=\"float{$fp['align']}\">{$s}</div>";
00667         }
00668         return str_replace( "\n", ' ', $prefix . $s . $postfix );
00669     }
00670 
00676     public static function makeImageLink2( Title $title, $file, $frameParams = array(),
00677         $handlerParams = array(), $time = false, $query = "", $widthOption = null ) {
00678         return self::makeImageLink( null, $title, $file, $frameParams,
00679             $handlerParams, $time, $query, $widthOption );
00680     }
00681 
00689     private static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
00690         $mtoParams = array();
00691         if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
00692             $mtoParams['custom-url-link'] = $frameParams['link-url'];
00693             if ( isset( $frameParams['link-target'] ) ) {
00694                 $mtoParams['custom-target-link'] = $frameParams['link-target'];
00695             }
00696             if ( $parser ) {
00697                 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
00698                 foreach ( $extLinkAttrs as $name => $val ) {
00699                     // Currently could include 'rel' and 'target'
00700                     $mtoParams['parser-extlink-' . $name] = $val;
00701                 }
00702             }
00703         } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
00704             $mtoParams['custom-title-link'] = self::normaliseSpecialPage( $frameParams['link-title'] );
00705         } elseif ( !empty( $frameParams['no-link'] ) ) {
00706             // No link
00707         } else {
00708             $mtoParams['desc-link'] = true;
00709             $mtoParams['desc-query'] = $query;
00710         }
00711         return $mtoParams;
00712     }
00713 
00726     public static function makeThumbLinkObj( Title $title, $file, $label = '', $alt,
00727         $align = 'right', $params = array(), $framed = false, $manualthumb = ""
00728     ) {
00729         $frameParams = array(
00730             'alt' => $alt,
00731             'caption' => $label,
00732             'align' => $align
00733         );
00734         if ( $framed ) {
00735             $frameParams['framed'] = true;
00736         }
00737         if ( $manualthumb ) {
00738             $frameParams['manualthumb'] = $manualthumb;
00739         }
00740         return self::makeThumbLink2( $title, $file, $frameParams, $params );
00741     }
00742 
00752     public static function makeThumbLink2( Title $title, $file, $frameParams = array(),
00753         $handlerParams = array(), $time = false, $query = ""
00754     ) {
00755         global $wgStylePath, $wgContLang;
00756         $exists = $file && $file->exists();
00757 
00758         # Shortcuts
00759         $fp =& $frameParams;
00760         $hp =& $handlerParams;
00761 
00762         $page = isset( $hp['page'] ) ? $hp['page'] : false;
00763         if ( !isset( $fp['align'] ) ) {
00764             $fp['align'] = 'right';
00765         }
00766         if ( !isset( $fp['alt'] ) ) {
00767             $fp['alt'] = '';
00768         }
00769         if ( !isset( $fp['title'] ) ) {
00770             $fp['title'] = '';
00771         }
00772         if ( !isset( $fp['caption'] ) ) {
00773             $fp['caption'] = '';
00774         }
00775 
00776         if ( empty( $hp['width'] ) ) {
00777             // Reduce width for upright images when parameter 'upright' is used
00778             $hp['width'] = isset( $fp['upright'] ) ? 130 : 180;
00779         }
00780         $thumb = false;
00781         $noscale = false;
00782         $manualthumb = false;
00783 
00784         if ( !$exists ) {
00785             $outerWidth = $hp['width'] + 2;
00786         } else {
00787             if ( isset( $fp['manualthumb'] ) ) {
00788                 # Use manually specified thumbnail
00789                 $manual_title = Title::makeTitleSafe( NS_FILE, $fp['manualthumb'] );
00790                 if ( $manual_title ) {
00791                     $manual_img = wfFindFile( $manual_title );
00792                     if ( $manual_img ) {
00793                         $thumb = $manual_img->getUnscaledThumb( $hp );
00794                         $manualthumb = true;
00795                     } else {
00796                         $exists = false;
00797                     }
00798                 }
00799             } elseif ( isset( $fp['framed'] ) ) {
00800                 // Use image dimensions, don't scale
00801                 $thumb = $file->getUnscaledThumb( $hp );
00802                 $noscale = true;
00803             } else {
00804                 # Do not present an image bigger than the source, for bitmap-style images
00805                 # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
00806                 $srcWidth = $file->getWidth( $page );
00807                 if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
00808                     $hp['width'] = $srcWidth;
00809                 }
00810                 $thumb = $file->transform( $hp );
00811             }
00812 
00813             if ( $thumb ) {
00814                 $outerWidth = $thumb->getWidth() + 2;
00815             } else {
00816                 $outerWidth = $hp['width'] + 2;
00817             }
00818         }
00819 
00820         # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
00821         # So we don't need to pass it here in $query. However, the URL for the
00822         # zoom icon still needs it, so we make a unique query for it. See bug 14771
00823         $url = $title->getLocalURL( $query );
00824         if ( $page ) {
00825             $url = wfAppendQuery( $url, array( 'page' => $page ) );
00826         }
00827         if ( $manualthumb
00828             && !isset( $fp['link-title'] )
00829             && !isset( $fp['link-url'] )
00830             && !isset( $fp['no-link'] ) ) {
00831             $fp['link-url'] = $url;
00832         }
00833 
00834         $s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
00835         if ( !$exists ) {
00836             $s .= self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
00837             $zoomIcon = '';
00838         } elseif ( !$thumb ) {
00839             $s .= wfMessage( 'thumbnail_error', '' )->escaped();
00840             $zoomIcon = '';
00841         } else {
00842             if ( !$noscale && !$manualthumb ) {
00843                 self::processResponsiveImages( $file, $thumb, $hp );
00844             }
00845             $params = array(
00846                 'alt' => $fp['alt'],
00847                 'title' => $fp['title'],
00848                 'img-class' => ( isset( $fp['class'] ) && $fp['class'] !== '' ? $fp['class'] . ' ' : '' ) . 'thumbimage'
00849             );
00850             $params = self::getImageLinkMTOParams( $fp, $query ) + $params;
00851             $s .= $thumb->toHtml( $params );
00852             if ( isset( $fp['framed'] ) ) {
00853                 $zoomIcon = "";
00854             } else {
00855                 $zoomIcon = Html::rawElement( 'div', array( 'class' => 'magnify' ),
00856                     Html::rawElement( 'a', array(
00857                         'href' => $url,
00858                         'class' => 'internal',
00859                         'title' => wfMessage( 'thumbnail-more' )->text() ),
00860                         Html::element( 'img', array(
00861                             'src' => $wgStylePath . '/common/images/magnify-clip' . ( $wgContLang->isRTL() ? '-rtl' : '' ) . '.png',
00862                             'width' => 15,
00863                             'height' => 11,
00864                             'alt' => "" ) ) ) );
00865             }
00866         }
00867         $s .= '  <div class="thumbcaption">' . $zoomIcon . $fp['caption'] . "</div></div></div>";
00868         return str_replace( "\n", ' ', $s );
00869     }
00870 
00879     public static function processResponsiveImages( $file, $thumb, $hp ) {
00880         global $wgResponsiveImages;
00881         if ( $wgResponsiveImages ) {
00882             $hp15 = $hp;
00883             $hp15['width'] = round( $hp['width'] * 1.5 );
00884             $hp20 = $hp;
00885             $hp20['width'] = $hp['width'] * 2;
00886             if ( isset( $hp['height'] ) ) {
00887                 $hp15['height'] = round( $hp['height'] * 1.5 );
00888                 $hp20['height'] = $hp['height'] * 2;
00889             }
00890 
00891             $thumb15 = $file->transform( $hp15 );
00892             $thumb20 = $file->transform( $hp20 );
00893             if ( $thumb15 && $thumb15->getUrl() !== $thumb->getUrl() ) {
00894                 $thumb->responsiveUrls['1.5'] = $thumb15->getUrl();
00895             }
00896             if ( $thumb20 && $thumb20->getUrl() !== $thumb->getUrl() ) {
00897                 $thumb->responsiveUrls['2'] = $thumb20->getUrl();
00898             }
00899         }
00900     }
00901 
00913     public static function makeBrokenImageLinkObj( $title, $label = '', $query = '', $unused1 = '', $unused2 = '', $time = false ) {
00914         global $wgEnableUploads, $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
00915         if ( ! $title instanceof Title ) {
00916             return "<!-- ERROR -->" . htmlspecialchars( $label );
00917         }
00918         wfProfileIn( __METHOD__ );
00919         if ( $label == '' ) {
00920             $label = $title->getPrefixedText();
00921         }
00922         $encLabel = htmlspecialchars( $label );
00923         $currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
00924 
00925         if ( ( $wgUploadMissingFileUrl || $wgUploadNavigationUrl || $wgEnableUploads ) && !$currentExists ) {
00926             $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
00927 
00928             if ( $redir ) {
00929                 wfProfileOut( __METHOD__ );
00930                 return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
00931             }
00932 
00933             $href = self::getUploadUrl( $title, $query );
00934 
00935             wfProfileOut( __METHOD__ );
00936             return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
00937                 htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
00938                 $encLabel . '</a>';
00939         }
00940 
00941         wfProfileOut( __METHOD__ );
00942         return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) );
00943     }
00944 
00952     protected static function getUploadUrl( $destFile, $query = '' ) {
00953         global $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
00954         $q = 'wpDestFile=' . $destFile->getPartialURL();
00955         if ( $query != '' ) {
00956             $q .= '&' . $query;
00957         }
00958 
00959         if ( $wgUploadMissingFileUrl ) {
00960             return wfAppendQuery( $wgUploadMissingFileUrl, $q );
00961         } elseif ( $wgUploadNavigationUrl ) {
00962             return wfAppendQuery( $wgUploadNavigationUrl, $q );
00963         } else {
00964             $upload = SpecialPage::getTitleFor( 'Upload' );
00965             return $upload->getLocalURL( $q );
00966         }
00967     }
00968 
00977     public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
00978         $img = wfFindFile( $title, array( 'time' => $time ) );
00979         return self::makeMediaLinkFile( $title, $img, $html );
00980     }
00981 
00993     public static function makeMediaLinkFile( Title $title, $file, $html = '' ) {
00994         if ( $file && $file->exists() ) {
00995             $url = $file->getURL();
00996             $class = 'internal';
00997         } else {
00998             $url = self::getUploadUrl( $title );
00999             $class = 'new';
01000         }
01001         $alt = htmlspecialchars( $title->getText(), ENT_QUOTES );
01002         if ( $html == '' ) {
01003             $html = $alt;
01004         }
01005         $u = htmlspecialchars( $url );
01006         return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$html}</a>";
01007     }
01008 
01016     public static function specialLink( $name, $key = '' ) {
01017         if ( $key == '' ) {
01018             $key = strtolower( $name );
01019         }
01020 
01021         return self::linkKnown( SpecialPage::getTitleFor( $name ), wfMessage( $key )->text() );
01022     }
01023 
01034     public static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array(), $title = null ) {
01035         global $wgTitle;
01036         $class = "external";
01037         if ( $linktype ) {
01038             $class .= " $linktype";
01039         }
01040         if ( isset( $attribs['class'] ) && $attribs['class'] ) {
01041             $class .= " {$attribs['class']}";
01042         }
01043         $attribs['class'] = $class;
01044 
01045         if ( $escape ) {
01046             $text = htmlspecialchars( $text );
01047         }
01048 
01049         if ( !$title ) {
01050             $title = $wgTitle;
01051         }
01052         $attribs['rel'] = Parser::getExternalLinkRel( $url, $title );
01053         $link = '';
01054         $success = wfRunHooks( 'LinkerMakeExternalLink',
01055             array( &$url, &$text, &$link, &$attribs, $linktype ) );
01056         if ( !$success ) {
01057             wfDebug( "Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true );
01058             return $link;
01059         }
01060         $attribs['href'] = $url;
01061         return Html::rawElement( 'a', $attribs, $text );
01062     }
01063 
01072     public static function userLink( $userId, $userName, $altUserName = false ) {
01073         if ( $userId == 0 ) {
01074             $page = SpecialPage::getTitleFor( 'Contributions', $userName );
01075             if ( $altUserName === false ) {
01076                 $altUserName = IP::prettifyIP( $userName );
01077             }
01078         } else {
01079             $page = Title::makeTitle( NS_USER, $userName );
01080         }
01081 
01082         return self::link(
01083             $page,
01084             htmlspecialchars( $altUserName !== false ? $altUserName : $userName ),
01085             array( 'class' => 'mw-userlink' )
01086         );
01087     }
01088 
01100     public static function userToolLinks(
01101         $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null
01102     ) {
01103         global $wgUser, $wgDisableAnonTalk, $wgLang;
01104         $talkable = !( $wgDisableAnonTalk && 0 == $userId );
01105         $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
01106         $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
01107 
01108         $items = array();
01109         if ( $talkable ) {
01110             $items[] = self::userTalkLink( $userId, $userText );
01111         }
01112         if ( $userId ) {
01113             // check if the user has an edit
01114             $attribs = array();
01115             if ( $redContribsWhenNoEdits ) {
01116                 if ( intval( $edits ) === 0 && $edits !== 0 ) {
01117                     $user = User::newFromId( $userId );
01118                     $edits = $user->getEditCount();
01119                 }
01120                 if ( $edits === 0 ) {
01121                     $attribs['class'] = 'new';
01122                 }
01123             }
01124             $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
01125 
01126             $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
01127         }
01128         if ( $blockable && $wgUser->isAllowed( 'block' ) ) {
01129             $items[] = self::blockLink( $userId, $userText );
01130         }
01131 
01132         if ( $addEmailLink && $wgUser->canSendEmail() ) {
01133             $items[] = self::emailLink( $userId, $userText );
01134         }
01135 
01136         wfRunHooks( 'UserToolLinksEdit', array( $userId, $userText, &$items ) );
01137 
01138         if ( $items ) {
01139             return wfMessage( 'word-separator' )->plain()
01140                 . '<span class="mw-usertoollinks">'
01141                 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
01142                 . '</span>';
01143         } else {
01144             return '';
01145         }
01146     }
01147 
01155     public static function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
01156         return self::userToolLinks( $userId, $userText, true, 0, $edits );
01157     }
01158 
01164     public static function userTalkLink( $userId, $userText ) {
01165         $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
01166         $userTalkLink = self::link( $userTalkPage, wfMessage( 'talkpagelinktext' )->escaped() );
01167         return $userTalkLink;
01168     }
01169 
01175     public static function blockLink( $userId, $userText ) {
01176         $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
01177         $blockLink = self::link( $blockPage, wfMessage( 'blocklink' )->escaped() );
01178         return $blockLink;
01179     }
01180 
01186     public static function emailLink( $userId, $userText ) {
01187         $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
01188         $emailLink = self::link( $emailPage, wfMessage( 'emaillink' )->escaped() );
01189         return $emailLink;
01190     }
01191 
01198     public static function revUserLink( $rev, $isPublic = false ) {
01199         if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
01200             $link = wfMessage( 'rev-deleted-user' )->escaped();
01201         } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
01202             $link = self::userLink( $rev->getUser( Revision::FOR_THIS_USER ),
01203                 $rev->getUserText( Revision::FOR_THIS_USER ) );
01204         } else {
01205             $link = wfMessage( 'rev-deleted-user' )->escaped();
01206         }
01207         if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
01208             return '<span class="history-deleted">' . $link . '</span>';
01209         }
01210         return $link;
01211     }
01212 
01219     public static function revUserTools( $rev, $isPublic = false ) {
01220         if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
01221             $link = wfMessage( 'rev-deleted-user' )->escaped();
01222         } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
01223             $userId = $rev->getUser( Revision::FOR_THIS_USER );
01224             $userText = $rev->getUserText( Revision::FOR_THIS_USER );
01225             $link = self::userLink( $userId, $userText )
01226                 . wfMessage( 'word-separator' )->plain()
01227                 . self::userToolLinks( $userId, $userText );
01228         } else {
01229             $link = wfMessage( 'rev-deleted-user' )->escaped();
01230         }
01231         if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
01232             return ' <span class="history-deleted">' . $link . '</span>';
01233         }
01234         return $link;
01235     }
01236 
01254     public static function formatComment( $comment, $title = null, $local = false ) {
01255         wfProfileIn( __METHOD__ );
01256 
01257         # Sanitize text a bit:
01258         $comment = str_replace( "\n", " ", $comment );
01259         # Allow HTML entities (for bug 13815)
01260         $comment = Sanitizer::escapeHtmlAllowEntities( $comment );
01261 
01262         # Render autocomments and make links:
01263         $comment = self::formatAutocomments( $comment, $title, $local );
01264         $comment = self::formatLinksInComment( $comment, $title, $local );
01265 
01266         wfProfileOut( __METHOD__ );
01267         return $comment;
01268     }
01269 
01273     static $autocommentTitle;
01274     static $autocommentLocal;
01275 
01289     private static function formatAutocomments( $comment, $title = null, $local = false ) {
01290         // Bah!
01291         self::$autocommentTitle = $title;
01292         self::$autocommentLocal = $local;
01293         $comment = preg_replace_callback(
01294             '!(.*)/\*\s*(.*?)\s*\*/(.*)!',
01295             array( 'Linker', 'formatAutocommentsCallback' ),
01296             $comment );
01297         self::$autocommentTitle = null;
01298         self::$autocommentLocal = null;
01299         return $comment;
01300     }
01301 
01307     private static function formatAutocommentsCallback( $match ) {
01308         global $wgLang;
01309         $title = self::$autocommentTitle;
01310         $local = self::$autocommentLocal;
01311 
01312         $pre = $match[1];
01313         $auto = $match[2];
01314         $post = $match[3];
01315         $comment = null;
01316         wfRunHooks( 'FormatAutocomments', array( &$comment, $pre, $auto, $post, $title, $local ) );
01317         if ( $comment === null ) {
01318             $link = '';
01319             if ( $title ) {
01320                 $section = $auto;
01321 
01322                 # Remove links that a user may have manually put in the autosummary
01323                 # This could be improved by copying as much of Parser::stripSectionName as desired.
01324                 $section = str_replace( '[[:', '', $section );
01325                 $section = str_replace( '[[', '', $section );
01326                 $section = str_replace( ']]', '', $section );
01327 
01328                 $section = Sanitizer::normalizeSectionNameWhitespace( $section ); # bug 22784
01329                 if ( $local ) {
01330                     $sectionTitle = Title::newFromText( '#' . $section );
01331                 } else {
01332                     $sectionTitle = Title::makeTitleSafe( $title->getNamespace(),
01333                         $title->getDBkey(), $section );
01334                 }
01335                 if ( $sectionTitle ) {
01336                     $link = self::link( $sectionTitle,
01337                         $wgLang->getArrow(), array(), array(),
01338                         'noclasses' );
01339                 } else {
01340                     $link = '';
01341                 }
01342             }
01343             if ( $pre ) {
01344                 # written summary $presep autocomment (summary /* section */)
01345                 $pre .= wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped();
01346             }
01347             if ( $post ) {
01348                 # autocomment $postsep written summary (/* section */ summary)
01349                 $auto .= wfMessage( 'colon-separator' )->inContentLanguage()->escaped();
01350             }
01351             $auto = '<span class="autocomment">' . $auto . '</span>';
01352             $comment = $pre . $link . $wgLang->getDirMark() . '<span dir="auto">' . $auto . $post . '</span>';
01353         }
01354         return $comment;
01355     }
01356 
01360     static $commentContextTitle;
01361     static $commentLocal;
01362 
01373     public static function formatLinksInComment( $comment, $title = null, $local = false ) {
01374         self::$commentContextTitle = $title;
01375         self::$commentLocal = $local;
01376         $html = preg_replace_callback(
01377             '/
01378                 \[\[
01379                 :? # ignore optional leading colon
01380                 ([^\]|]+) # 1. link target; page names cannot include ] or |
01381                 (?:\|
01382                     # 2. a pipe-separated substring; only the last is captured
01383                     # Stop matching at | and ]] without relying on backtracking.
01384                     ((?:]?[^\]|])*+)
01385                 )*
01386                 \]\]
01387                 ([^[]*) # 3. link trail (the text up until the next link)
01388             /x',
01389             array( 'Linker', 'formatLinksInCommentCallback' ),
01390             $comment );
01391         self::$commentContextTitle = null;
01392         self::$commentLocal = null;
01393         return $html;
01394     }
01395 
01400     protected static function formatLinksInCommentCallback( $match ) {
01401         global $wgContLang;
01402 
01403         $medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
01404         $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):';
01405 
01406         $comment = $match[0];
01407 
01408         # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
01409         if ( strpos( $match[1], '%' ) !== false ) {
01410             $match[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $match[1] ) );
01411         }
01412 
01413         # Handle link renaming [[foo|text]] will show link as "text"
01414         if ( $match[2] != "" ) {
01415             $text = $match[2];
01416         } else {
01417             $text = $match[1];
01418         }
01419         $submatch = array();
01420         $thelink = null;
01421         if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
01422             # Media link; trail not supported.
01423             $linkRegexp = '/\[\[(.*?)\]\]/';
01424             $title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
01425             if ( $title ) {
01426                 $thelink = self::makeMediaLinkObj( $title, $text );
01427             }
01428         } else {
01429             # Other kind of link
01430             if ( preg_match( $wgContLang->linkTrail(), $match[3], $submatch ) ) {
01431                 $trail = $submatch[1];
01432             } else {
01433                 $trail = "";
01434             }
01435             $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
01436             if ( isset( $match[1][0] ) && $match[1][0] == ':' ) {
01437                 $match[1] = substr( $match[1], 1 );
01438             }
01439             list( $inside, $trail ) = self::splitTrail( $trail );
01440 
01441             $linkText = $text;
01442             $linkTarget = self::normalizeSubpageLink( self::$commentContextTitle,
01443                 $match[1], $linkText );
01444 
01445             $target = Title::newFromText( $linkTarget );
01446             if ( $target ) {
01447                 if ( $target->getText() == '' && !$target->isExternal()
01448                     && !self::$commentLocal && self::$commentContextTitle
01449                 ) {
01450                     $newTarget = clone ( self::$commentContextTitle );
01451                     $newTarget->setFragment( '#' . $target->getFragment() );
01452                     $target = $newTarget;
01453                 }
01454                 $thelink = self::link(
01455                     $target,
01456                     $linkText . $inside
01457                 ) . $trail;
01458             }
01459         }
01460         if ( $thelink ) {
01461             // If the link is still valid, go ahead and replace it in!
01462             $comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 );
01463         }
01464 
01465         return $comment;
01466     }
01467 
01474     public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
01475         # Valid link forms:
01476         # Foobar -- normal
01477         # :Foobar -- override special treatment of prefix (images, language links)
01478         # /Foobar -- convert to CurrentPage/Foobar
01479         # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
01480         # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
01481         # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage
01482 
01483         wfProfileIn( __METHOD__ );
01484         $ret = $target; # default return value is no change
01485 
01486         # Some namespaces don't allow subpages,
01487         # so only perform processing if subpages are allowed
01488         if ( $contextTitle && MWNamespace::hasSubpages( $contextTitle->getNamespace() ) ) {
01489             $hash = strpos( $target, '#' );
01490             if ( $hash !== false ) {
01491                 $suffix = substr( $target, $hash );
01492                 $target = substr( $target, 0, $hash );
01493             } else {
01494                 $suffix = '';
01495             }
01496             # bug 7425
01497             $target = trim( $target );
01498             # Look at the first character
01499             if ( $target != '' && $target[0] === '/' ) {
01500                 # / at end means we don't want the slash to be shown
01501                 $m = array();
01502                 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
01503                 if ( $trailingSlashes ) {
01504                     $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
01505                 } else {
01506                     $noslash = substr( $target, 1 );
01507                 }
01508 
01509                 $ret = $contextTitle->getPrefixedText() . '/' . trim( $noslash ) . $suffix;
01510                 if ( $text === '' ) {
01511                     $text = $target . $suffix;
01512                 } # this might be changed for ugliness reasons
01513             } else {
01514                 # check for .. subpage backlinks
01515                 $dotdotcount = 0;
01516                 $nodotdot = $target;
01517                 while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
01518                     ++$dotdotcount;
01519                     $nodotdot = substr( $nodotdot, 3 );
01520                 }
01521                 if ( $dotdotcount > 0 ) {
01522                     $exploded = explode( '/', $contextTitle->getPrefixedText() );
01523                     if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
01524                         $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
01525                         # / at the end means don't show full path
01526                         if ( substr( $nodotdot, -1, 1 ) === '/' ) {
01527                             $nodotdot = substr( $nodotdot, 0, -1 );
01528                             if ( $text === '' ) {
01529                                 $text = $nodotdot . $suffix;
01530                             }
01531                         }
01532                         $nodotdot = trim( $nodotdot );
01533                         if ( $nodotdot != '' ) {
01534                             $ret .= '/' . $nodotdot;
01535                         }
01536                         $ret .= $suffix;
01537                     }
01538                 }
01539             }
01540         }
01541 
01542         wfProfileOut( __METHOD__ );
01543         return $ret;
01544     }
01545 
01556     public static function commentBlock( $comment, $title = null, $local = false ) {
01557         // '*' used to be the comment inserted by the software way back
01558         // in antiquity in case none was provided, here for backwards
01559         // compatibility, acc. to brion -ævar
01560         if ( $comment == '' || $comment == '*' ) {
01561             return '';
01562         } else {
01563             $formatted = self::formatComment( $comment, $title, $local );
01564             $formatted = wfMessage( 'parentheses' )->rawParams( $formatted )->escaped();
01565             return " <span class=\"comment\">$formatted</span>";
01566         }
01567     }
01568 
01578     public static function revComment( Revision $rev, $local = false, $isPublic = false ) {
01579         if ( $rev->getRawComment() == "" ) {
01580             return "";
01581         }
01582         if ( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
01583             $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
01584         } elseif ( $rev->userCan( Revision::DELETED_COMMENT ) ) {
01585             $block = self::commentBlock( $rev->getComment( Revision::FOR_THIS_USER ),
01586                 $rev->getTitle(), $local );
01587         } else {
01588             $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
01589         }
01590         if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
01591             return " <span class=\"history-deleted\">$block</span>";
01592         }
01593         return $block;
01594     }
01595 
01600     public static function formatRevisionSize( $size ) {
01601         if ( $size == 0 ) {
01602             $stxt = wfMessage( 'historyempty' )->escaped();
01603         } else {
01604             $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
01605             $stxt = wfMessage( 'parentheses' )->rawParams( $stxt )->escaped();
01606         }
01607         return "<span class=\"history-size\">$stxt</span>";
01608     }
01609 
01615     public static function tocIndent() {
01616         return "\n<ul>";
01617     }
01618 
01624     public static function tocUnindent( $level ) {
01625         return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
01626     }
01627 
01633     public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
01634         $classes = "toclevel-$level";
01635         if ( $sectionIndex !== false ) {
01636             $classes .= " tocsection-$sectionIndex";
01637         }
01638         return "\n<li class=\"$classes\"><a href=\"#" .
01639             $anchor . '"><span class="tocnumber">' .
01640             $tocnumber . '</span> <span class="toctext">' .
01641             $tocline . '</span></a>';
01642     }
01643 
01650     public static function tocLineEnd() {
01651         return "</li>\n";
01652     }
01653 
01661     public static function tocList( $toc, $lang = false ) {
01662         $lang = wfGetLangObj( $lang );
01663         $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
01664 
01665         return '<div id="toc" class="toc">'
01666             . '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
01667             . $toc
01668             . "</ul>\n</div>\n";
01669     }
01670 
01678     public static function generateTOC( $tree ) {
01679         $toc = '';
01680         $lastLevel = 0;
01681         foreach ( $tree as $section ) {
01682             if ( $section['toclevel'] > $lastLevel ) {
01683                 $toc .= self::tocIndent();
01684             } elseif ( $section['toclevel'] < $lastLevel ) {
01685                 $toc .= self::tocUnindent(
01686                     $lastLevel - $section['toclevel'] );
01687             } else {
01688                 $toc .= self::tocLineEnd();
01689             }
01690 
01691             $toc .= self::tocLine( $section['anchor'],
01692                 $section['line'], $section['number'],
01693                 $section['toclevel'], $section['index'] );
01694             $lastLevel = $section['toclevel'];
01695         }
01696         $toc .= self::tocLineEnd();
01697         return self::tocList( $toc );
01698     }
01699 
01715     public static function makeHeadline( $level, $attribs, $anchor, $html, $link, $legacyAnchor = false ) {
01716         $ret = "<h$level$attribs"
01717             . "<span class=\"mw-headline\" id=\"$anchor\">$html</span>"
01718             . $link
01719             . "</h$level>";
01720         if ( $legacyAnchor !== false ) {
01721             $ret = "<div id=\"$legacyAnchor\"></div>$ret";
01722         }
01723         return $ret;
01724     }
01725 
01731     static function splitTrail( $trail ) {
01732         global $wgContLang;
01733         $regex = $wgContLang->linkTrail();
01734         $inside = '';
01735         if ( $trail !== '' ) {
01736             $m = array();
01737             if ( preg_match( $regex, $trail, $m ) ) {
01738                 $inside = $m[1];
01739                 $trail = $m[2];
01740             }
01741         }
01742         return array( $inside, $trail );
01743     }
01744 
01770     public static function generateRollback( $rev, IContextSource $context = null, $options = array( 'verify' ) ) {
01771         if ( $context === null ) {
01772             $context = RequestContext::getMain();
01773         }
01774         $editCount = false;
01775         if ( in_array( 'verify', $options ) ) {
01776             $editCount = self::getRollbackEditCount( $rev, true );
01777             if ( $editCount === false ) {
01778                 return '';
01779             }
01780         }
01781 
01782         $inner = self::buildRollbackLink( $rev, $context, $editCount );
01783 
01784         if ( !in_array( 'noBrackets', $options ) ) {
01785             $inner = $context->msg( 'brackets' )->rawParams( $inner )->plain();
01786         }
01787 
01788         return '<span class="mw-rollback-link">' . $inner . '</span>';
01789     }
01790 
01806     public static function getRollbackEditCount( $rev, $verify ) {
01807         global $wgShowRollbackEditCount;
01808         if ( !is_int( $wgShowRollbackEditCount ) || !$wgShowRollbackEditCount > 0 ) {
01809             // Nothing has happened, indicate this by returning 'null'
01810             return null;
01811         }
01812 
01813         $dbr = wfGetDB( DB_SLAVE );
01814 
01815         // Up to the value of $wgShowRollbackEditCount revisions are counted
01816         $res = $dbr->select(
01817             'revision',
01818             array( 'rev_user_text', 'rev_deleted' ),
01819             // $rev->getPage() returns null sometimes
01820             array( 'rev_page' => $rev->getTitle()->getArticleID() ),
01821             __METHOD__,
01822             array(
01823                 'USE INDEX' => array( 'revision' => 'page_timestamp' ),
01824                 'ORDER BY' => 'rev_timestamp DESC',
01825                 'LIMIT' => $wgShowRollbackEditCount + 1
01826             )
01827         );
01828 
01829         $editCount = 0;
01830         $moreRevs = false;
01831         foreach ( $res as $row ) {
01832             if ( $rev->getRawUserText() != $row->rev_user_text ) {
01833                 if ( $verify && ( $row->rev_deleted & Revision::DELETED_TEXT || $row->rev_deleted & Revision::DELETED_USER ) ) {
01834                     // If the user or the text of the revision we might rollback to is deleted in some way we can't rollback
01835                     // Similar to the sanity checks in WikiPage::commitRollback
01836                     return false;
01837                 }
01838                 $moreRevs = true;
01839                 break;
01840             }
01841             $editCount++;
01842         }
01843 
01844         if ( $verify && $editCount <= $wgShowRollbackEditCount && !$moreRevs ) {
01845             // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
01846             // and there weren't any other revisions. That means that the current user is the only
01847             // editor, so we can't rollback
01848             return false;
01849         }
01850         return $editCount;
01851     }
01852 
01861     public static function buildRollbackLink( $rev, IContextSource $context = null, $editCount = false ) {
01862         global $wgShowRollbackEditCount, $wgMiserMode;
01863 
01864         // To config which pages are effected by miser mode
01865         $disableRollbackEditCountSpecialPage = array( 'Recentchanges', 'Watchlist' );
01866 
01867         if ( $context === null ) {
01868             $context = RequestContext::getMain();
01869         }
01870 
01871         $title = $rev->getTitle();
01872         $query = array(
01873             'action' => 'rollback',
01874             'from' => $rev->getUserText(),
01875             'token' => $context->getUser()->getEditToken( array( $title->getPrefixedText(), $rev->getUserText() ) ),
01876         );
01877         if ( $context->getRequest()->getBool( 'bot' ) ) {
01878             $query['bot'] = '1';
01879             $query['hidediff'] = '1'; // bug 15999
01880         }
01881 
01882         $disableRollbackEditCount = false;
01883         if ( $wgMiserMode ) {
01884             foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
01885                 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
01886                     $disableRollbackEditCount = true;
01887                     break;
01888                 }
01889             }
01890         }
01891 
01892         if ( !$disableRollbackEditCount && is_int( $wgShowRollbackEditCount ) && $wgShowRollbackEditCount > 0 ) {
01893             if ( !is_numeric( $editCount ) ) {
01894                 $editCount = self::getRollbackEditCount( $rev, false );
01895             }
01896 
01897             if ( $editCount > $wgShowRollbackEditCount ) {
01898                 $editCount_output = $context->msg( 'rollbacklinkcount-morethan' )->numParams( $wgShowRollbackEditCount )->parse();
01899             } else {
01900                 $editCount_output = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
01901             }
01902 
01903             return self::link(
01904                 $title,
01905                 $editCount_output,
01906                 array( 'title' => $context->msg( 'tooltip-rollback' )->text() ),
01907                 $query,
01908                 array( 'known', 'noclasses' )
01909             );
01910         } else {
01911             return self::link(
01912                 $title,
01913                 $context->msg( 'rollbacklink' )->escaped(),
01914                 array( 'title' => $context->msg( 'tooltip-rollback' )->text() ),
01915                 $query,
01916                 array( 'known', 'noclasses' )
01917             );
01918         }
01919     }
01920 
01936     public static function formatTemplates( $templates, $preview = false, $section = false, $more = null ) {
01937         global $wgLang;
01938         wfProfileIn( __METHOD__ );
01939 
01940         $outText = '';
01941         if ( count( $templates ) > 0 ) {
01942             # Do a batch existence check
01943             $batch = new LinkBatch;
01944             foreach ( $templates as $title ) {
01945                 $batch->addObj( $title );
01946             }
01947             $batch->execute();
01948 
01949             # Construct the HTML
01950             $outText = '<div class="mw-templatesUsedExplanation">';
01951             if ( $preview ) {
01952                 $outText .= wfMessage( 'templatesusedpreview' )->numParams( count( $templates ) )
01953                     ->parseAsBlock();
01954             } elseif ( $section ) {
01955                 $outText .= wfMessage( 'templatesusedsection' )->numParams( count( $templates ) )
01956                     ->parseAsBlock();
01957             } else {
01958                 $outText .= wfMessage( 'templatesused' )->numParams( count( $templates ) )
01959                     ->parseAsBlock();
01960             }
01961             $outText .= "</div><ul>\n";
01962 
01963             usort( $templates, 'Title::compare' );
01964             foreach ( $templates as $titleObj ) {
01965                 $protected = '';
01966                 $restrictions = $titleObj->getRestrictions( 'edit' );
01967                 if ( $restrictions ) {
01968                     // Check backwards-compatible messages
01969                     $msg = null;
01970                     if ( $restrictions === array( 'sysop' ) ) {
01971                         $msg = wfMessage( 'template-protected' );
01972                     } elseif ( $restrictions === array( 'autoconfirmed' ) ) {
01973                         $msg = wfMessage( 'template-semiprotected' );
01974                     }
01975                     if ( $msg && !$msg->isDisabled() ) {
01976                         $protected = $msg->parse();
01977                     } else {
01978                         // Construct the message from restriction-level-*
01979                         // e.g. restriction-level-sysop, restriction-level-autoconfirmed
01980                         $msgs = array();
01981                         foreach ( $restrictions as $r ) {
01982                             $msgs[] = wfMessage( "restriction-level-$r" )->parse();
01983                         }
01984                         $protected = wfMessage( 'parentheses' )
01985                             ->rawParams( $wgLang->commaList( $msgs ) )->escaped();
01986                     }
01987                 }
01988                 if ( $titleObj->quickUserCan( 'edit' ) ) {
01989                     $editLink = self::link(
01990                         $titleObj,
01991                         wfMessage( 'editlink' )->text(),
01992                         array(),
01993                         array( 'action' => 'edit' )
01994                     );
01995                 } else {
01996                     $editLink = self::link(
01997                         $titleObj,
01998                         wfMessage( 'viewsourcelink' )->text(),
01999                         array(),
02000                         array( 'action' => 'edit' )
02001                     );
02002                 }
02003                 $outText .= '<li>' . self::link( $titleObj )
02004                     . wfMessage( 'word-separator' )->escaped()
02005                     . wfMessage( 'parentheses' )->rawParams( $editLink )->escaped()
02006                     . wfMessage( 'word-separator' )->escaped()
02007                     . $protected . '</li>';
02008             }
02009 
02010             if ( $more instanceof Title ) {
02011                 $outText .= '<li>' . self::link( $more, wfMessage( 'moredotdotdot' ) ) . '</li>';
02012             } elseif ( $more ) {
02013                 $outText .= "<li>$more</li>";
02014             }
02015 
02016             $outText .= '</ul>';
02017         }
02018         wfProfileOut( __METHOD__ );
02019         return $outText;
02020     }
02021 
02029     public static function formatHiddenCategories( $hiddencats ) {
02030         wfProfileIn( __METHOD__ );
02031 
02032         $outText = '';
02033         if ( count( $hiddencats ) > 0 ) {
02034             # Construct the HTML
02035             $outText = '<div class="mw-hiddenCategoriesExplanation">';
02036             $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
02037             $outText .= "</div><ul>\n";
02038 
02039             foreach ( $hiddencats as $titleObj ) {
02040                 $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
02041             }
02042             $outText .= '</ul>';
02043         }
02044         wfProfileOut( __METHOD__ );
02045         return $outText;
02046     }
02047 
02055     public static function formatSize( $size ) {
02056         global $wgLang;
02057         return htmlspecialchars( $wgLang->formatSize( $size ) );
02058     }
02059 
02072     public static function titleAttrib( $name, $options = null ) {
02073         wfProfileIn( __METHOD__ );
02074 
02075         $message = wfMessage( "tooltip-$name" );
02076 
02077         if ( !$message->exists() ) {
02078             $tooltip = false;
02079         } else {
02080             $tooltip = $message->text();
02081             # Compatibility: formerly some tooltips had [alt-.] hardcoded
02082             $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
02083             # Message equal to '-' means suppress it.
02084             if ( $tooltip == '-' ) {
02085                 $tooltip = false;
02086             }
02087         }
02088 
02089         if ( $options == 'withaccess' ) {
02090             $accesskey = self::accesskey( $name );
02091             if ( $accesskey !== false ) {
02092                 if ( $tooltip === false || $tooltip === '' ) {
02093                     $tooltip = wfMessage( 'brackets', $accesskey )->escaped();
02094                 } else {
02095                     $tooltip .= wfMessage( 'word-separator' )->escaped();
02096                     $tooltip .= wfMessage( 'brackets', $accesskey )->escaped();
02097                 }
02098             }
02099         }
02100 
02101         wfProfileOut( __METHOD__ );
02102         return $tooltip;
02103     }
02104 
02105     static $accesskeycache;
02106 
02117     public static function accesskey( $name ) {
02118         if ( isset( self::$accesskeycache[$name] ) ) {
02119             return self::$accesskeycache[$name];
02120         }
02121         wfProfileIn( __METHOD__ );
02122 
02123         $message = wfMessage( "accesskey-$name" );
02124 
02125         if ( !$message->exists() ) {
02126             $accesskey = false;
02127         } else {
02128             $accesskey = $message->plain();
02129             if ( $accesskey === '' || $accesskey === '-' ) {
02130                 # @todo FIXME: Per standard MW behavior, a value of '-' means to suppress the
02131                 # attribute, but this is broken for accesskey: that might be a useful
02132                 # value.
02133                 $accesskey = false;
02134             }
02135         }
02136 
02137         wfProfileOut( __METHOD__ );
02138         self::$accesskeycache[$name] = $accesskey;
02139         return self::$accesskeycache[$name];
02140     }
02141 
02155     public static function getRevDeleteLink( User $user, Revision $rev, Title $title ) {
02156         $canHide = $user->isAllowed( 'deleterevision' );
02157         if ( !$canHide && !( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
02158             return '';
02159         }
02160 
02161         if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
02162             return Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
02163         } else {
02164             if ( $rev->getId() ) {
02165                 // RevDelete links using revision ID are stable across
02166                 // page deletion and undeletion; use when possible.
02167                 $query = array(
02168                     'type' => 'revision',
02169                     'target' => $title->getPrefixedDBkey(),
02170                     'ids' => $rev->getId()
02171                 );
02172             } else {
02173                 // Older deleted entries didn't save a revision ID.
02174                 // We have to refer to these by timestamp, ick!
02175                 $query = array(
02176                     'type' => 'archive',
02177                     'target' => $title->getPrefixedDBkey(),
02178                     'ids' => $rev->getTimestamp()
02179                 );
02180             }
02181             return Linker::revDeleteLink( $query,
02182                 $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
02183         }
02184     }
02185 
02196     public static function revDeleteLink( $query = array(), $restricted = false, $delete = true ) {
02197         $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
02198         $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
02199         $html = wfMessage( $msgKey )->escaped();
02200         $tag = $restricted ? 'strong' : 'span';
02201         $link = self::link( $sp, $html, array(), $query, array( 'known', 'noclasses' ) );
02202         return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), wfMessage( 'parentheses' )->rawParams( $link )->escaped() );
02203     }
02204 
02213     public static function revDeleteLinkDisabled( $delete = true ) {
02214         $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
02215         $html = wfMessage( $msgKey )->escaped();
02216         $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
02217         return Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), $htmlParentheses );
02218     }
02219 
02220     /* Deprecated methods */
02221 
02238     static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
02239         wfDeprecated( __METHOD__, '1.21' );
02240 
02241         wfProfileIn( __METHOD__ );
02242         $query = wfCgiToArray( $query );
02243         list( $inside, $trail ) = self::splitTrail( $trail );
02244         if ( $text === '' ) {
02245             $text = self::linkText( $nt );
02246         }
02247 
02248         $ret = self::link( $nt, "$prefix$text$inside", array(), $query ) . $trail;
02249 
02250         wfProfileOut( __METHOD__ );
02251         return $ret;
02252     }
02253 
02270     static function makeKnownLinkObj(
02271         $title, $text = '', $query = '', $trail = '', $prefix = '', $aprops = '', $style = ''
02272     ) {
02273         wfDeprecated( __METHOD__, '1.21' );
02274 
02275         wfProfileIn( __METHOD__ );
02276 
02277         if ( $text == '' ) {
02278             $text = self::linkText( $title );
02279         }
02280         $attribs = Sanitizer::mergeAttributes(
02281             Sanitizer::decodeTagAttributes( $aprops ),
02282             Sanitizer::decodeTagAttributes( $style )
02283         );
02284         $query = wfCgiToArray( $query );
02285         list( $inside, $trail ) = self::splitTrail( $trail );
02286 
02287         $ret = self::link( $title, "$prefix$text$inside", $attribs, $query,
02288             array( 'known', 'noclasses' ) ) . $trail;
02289 
02290         wfProfileOut( __METHOD__ );
02291         return $ret;
02292     }
02293 
02298     public static function tooltipAndAccesskeyAttribs( $name ) {
02299         # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
02300         # no attribute" instead of "output '' as value for attribute", this
02301         # would be three lines.
02302         $attribs = array(
02303             'title' => self::titleAttrib( $name, 'withaccess' ),
02304             'accesskey' => self::accesskey( $name )
02305         );
02306         if ( $attribs['title'] === false ) {
02307             unset( $attribs['title'] );
02308         }
02309         if ( $attribs['accesskey'] === false ) {
02310             unset( $attribs['accesskey'] );
02311         }
02312         return $attribs;
02313     }
02314 
02319     public static function tooltip( $name, $options = null ) {
02320         # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
02321         # no attribute" instead of "output '' as value for attribute", this
02322         # would be two lines.
02323         $tooltip = self::titleAttrib( $name, $options );
02324         if ( $tooltip === false ) {
02325             return '';
02326         }
02327         return Xml::expandAttributes( array(
02328             'title' => $tooltip
02329         ) );
02330     }
02331 }
02332 
02336 class DummyLinker {
02337 
02346     public function __call( $fname, $args ) {
02347         return call_user_func_array( array( 'Linker', $fname ), $args );
02348     }
02349 }