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