MediaWiki
REL1_19
|
00001 <?php 00009 class Linker { 00010 00014 const TOOL_LINKS_NOBLOCK = 1; 00015 const TOOL_LINKS_EMAIL = 2; 00016 00025 static function getExternalLinkAttributes( $class = 'external' ) { 00026 wfDeprecated( __METHOD__, '1.18' ); 00027 return self::getLinkAttributesInternal( '', $class ); 00028 } 00029 00040 static function getInterwikiLinkAttributes( $title, $unused = null, $class = 'external' ) { 00041 global $wgContLang; 00042 00043 # @todo FIXME: We have a whole bunch of handling here that doesn't happen in 00044 # getExternalLinkAttributes, why? 00045 $title = urldecode( $title ); 00046 $title = $wgContLang->checkTitleEncoding( $title ); 00047 $title = preg_replace( '/[\\x00-\\x1f]/', ' ', $title ); 00048 00049 return self::getLinkAttributesInternal( $title, $class ); 00050 } 00051 00061 static function getInternalLinkAttributes( $title, $unused = null, $class = '' ) { 00062 $title = urldecode( $title ); 00063 $title = str_replace( '_', ' ', $title ); 00064 return self::getLinkAttributesInternal( $title, $class ); 00065 } 00066 00077 static function getInternalLinkAttributesObj( $nt, $unused = null, $class = '', $title = false ) { 00078 if ( $title === false ) { 00079 $title = $nt->getPrefixedText(); 00080 } 00081 return self::getLinkAttributesInternal( $title, $class ); 00082 } 00083 00092 private static function getLinkAttributesInternal( $title, $class ) { 00093 $title = htmlspecialchars( $title ); 00094 $class = htmlspecialchars( $class ); 00095 $r = ''; 00096 if ( $class != '' ) { 00097 $r .= " class=\"$class\""; 00098 } 00099 if ( $title != '' ) { 00100 $r .= " title=\"$title\""; 00101 } 00102 return $r; 00103 } 00104 00112 public static function getLinkColour( $t, $threshold ) { 00113 $colour = ''; 00114 if ( $t->isRedirect() ) { 00115 # Page is a redirect 00116 $colour = 'mw-redirect'; 00117 } elseif ( $threshold > 0 && 00118 $t->exists() && $t->getLength() < $threshold && 00119 $t->isContentPage() ) { 00120 # Page is a stub 00121 $colour = 'stub'; 00122 } 00123 return $colour; 00124 } 00125 00168 public static function link( 00169 $target, $html = null, $customAttribs = array(), $query = array(), $options = array() 00170 ) { 00171 wfProfileIn( __METHOD__ ); 00172 if ( !$target instanceof Title ) { 00173 wfProfileOut( __METHOD__ ); 00174 return "<!-- ERROR -->$html"; 00175 } 00176 $options = (array)$options; 00177 00178 $dummy = new DummyLinker; // dummy linker instance for bc on the hooks 00179 00180 $ret = null; 00181 if ( !wfRunHooks( 'LinkBegin', array( $dummy, $target, &$html, 00182 &$customAttribs, &$query, &$options, &$ret ) ) ) { 00183 wfProfileOut( __METHOD__ ); 00184 return $ret; 00185 } 00186 00187 # Normalize the Title if it's a special page 00188 $target = self::normaliseSpecialPage( $target ); 00189 00190 # If we don't know whether the page exists, let's find out. 00191 wfProfileIn( __METHOD__ . '-checkPageExistence' ); 00192 if ( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) { 00193 if ( $target->isKnown() ) { 00194 $options[] = 'known'; 00195 } else { 00196 $options[] = 'broken'; 00197 } 00198 } 00199 wfProfileOut( __METHOD__ . '-checkPageExistence' ); 00200 00201 $oldquery = array(); 00202 if ( in_array( "forcearticlepath", $options ) && $query ) { 00203 $oldquery = $query; 00204 $query = array(); 00205 } 00206 00207 # Note: we want the href attribute first, for prettiness. 00208 $attribs = array( 'href' => self::linkUrl( $target, $query, $options ) ); 00209 if ( in_array( 'forcearticlepath', $options ) && $oldquery ) { 00210 $attribs['href'] = wfAppendQuery( $attribs['href'], wfArrayToCgi( $oldquery ) ); 00211 } 00212 00213 $attribs = array_merge( 00214 $attribs, 00215 self::linkAttribs( $target, $customAttribs, $options ) 00216 ); 00217 if ( is_null( $html ) ) { 00218 $html = self::linkText( $target ); 00219 } 00220 00221 $ret = null; 00222 if ( wfRunHooks( 'LinkEnd', array( $dummy, $target, $options, &$html, &$attribs, &$ret ) ) ) { 00223 $ret = Html::rawElement( 'a', $attribs, $html ); 00224 } 00225 00226 wfProfileOut( __METHOD__ ); 00227 return $ret; 00228 } 00229 00233 public static function linkKnown( 00234 $target, $html = null, $customAttribs = array(), 00235 $query = array(), $options = array( 'known', 'noclasses' ) ) 00236 { 00237 return self::link( $target, $html, $customAttribs, $query, $options ); 00238 } 00239 00247 private static function linkUrl( $target, $query, $options ) { 00248 wfProfileIn( __METHOD__ ); 00249 # We don't want to include fragments for broken links, because they 00250 # generally make no sense. 00251 if ( in_array( 'broken', $options ) && $target->mFragment !== '' ) { 00252 $target = clone $target; 00253 $target->mFragment = ''; 00254 } 00255 00256 # If it's a broken link, add the appropriate query pieces, unless 00257 # there's already an action specified, or unless 'edit' makes no sense 00258 # (i.e., for a nonexistent special page). 00259 if ( in_array( 'broken', $options ) && empty( $query['action'] ) 00260 && !$target->isSpecialPage() ) { 00261 $query['action'] = 'edit'; 00262 $query['redlink'] = '1'; 00263 } 00264 $ret = $target->getLinkURL( $query ); 00265 wfProfileOut( __METHOD__ ); 00266 return $ret; 00267 } 00268 00278 private static function linkAttribs( $target, $attribs, $options ) { 00279 wfProfileIn( __METHOD__ ); 00280 global $wgUser; 00281 $defaults = array(); 00282 00283 if ( !in_array( 'noclasses', $options ) ) { 00284 wfProfileIn( __METHOD__ . '-getClasses' ); 00285 # Now build the classes. 00286 $classes = array(); 00287 00288 if ( in_array( 'broken', $options ) ) { 00289 $classes[] = 'new'; 00290 } 00291 00292 if ( $target->isExternal() ) { 00293 $classes[] = 'extiw'; 00294 } 00295 00296 if ( !in_array( 'broken', $options ) ) { # Avoid useless calls to LinkCache (see r50387) 00297 $colour = self::getLinkColour( $target, $wgUser->getStubThreshold() ); 00298 if ( $colour !== '' ) { 00299 $classes[] = $colour; # mw-redirect or stub 00300 } 00301 } 00302 if ( $classes != array() ) { 00303 $defaults['class'] = implode( ' ', $classes ); 00304 } 00305 wfProfileOut( __METHOD__ . '-getClasses' ); 00306 } 00307 00308 # Get a default title attribute. 00309 if ( $target->getPrefixedText() == '' ) { 00310 # A link like [[#Foo]]. This used to mean an empty title 00311 # attribute, but that's silly. Just don't output a title. 00312 } elseif ( in_array( 'known', $options ) ) { 00313 $defaults['title'] = $target->getPrefixedText(); 00314 } else { 00315 $defaults['title'] = wfMsg( 'red-link-title', $target->getPrefixedText() ); 00316 } 00317 00318 # Finally, merge the custom attribs with the default ones, and iterate 00319 # over that, deleting all "false" attributes. 00320 $ret = array(); 00321 $merged = Sanitizer::mergeAttributes( $defaults, $attribs ); 00322 foreach ( $merged as $key => $val ) { 00323 # A false value suppresses the attribute, and we don't want the 00324 # href attribute to be overridden. 00325 if ( $key != 'href' and $val !== false ) { 00326 $ret[$key] = $val; 00327 } 00328 } 00329 wfProfileOut( __METHOD__ ); 00330 return $ret; 00331 } 00332 00340 private static function linkText( $target ) { 00341 # We might be passed a non-Title by make*LinkObj(). Fail gracefully. 00342 if ( !$target instanceof Title ) { 00343 return ''; 00344 } 00345 00346 # If the target is just a fragment, with no title, we return the frag- 00347 # ment text. Otherwise, we return the title text itself. 00348 if ( $target->getPrefixedText() === '' && $target->getFragment() !== '' ) { 00349 return htmlspecialchars( $target->getFragment() ); 00350 } 00351 return htmlspecialchars( $target->getPrefixedText() ); 00352 } 00353 00367 static function makeSizeLinkObj( $size, $nt, $text = '', $query = '', $trail = '', $prefix = '' ) { 00368 global $wgUser; 00369 wfDeprecated( __METHOD__, '1.17' ); 00370 00371 $threshold = $wgUser->getStubThreshold(); 00372 $colour = ( $size < $threshold ) ? 'stub' : ''; 00373 // @todo FIXME: Replace deprecated makeColouredLinkObj by link() 00374 return self::makeColouredLinkObj( $nt, $colour, $text, $query, $trail, $prefix ); 00375 } 00376 00386 public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) { 00387 if ( $html == '' ) { 00388 $html = htmlspecialchars( $nt->getPrefixedText() ); 00389 } 00390 list( $inside, $trail ) = self::splitTrail( $trail ); 00391 return "<strong class=\"selflink\">{$prefix}{$html}{$inside}</strong>{$trail}"; 00392 } 00393 00398 static function normaliseSpecialPage( Title $title ) { 00399 if ( $title->isSpecialPage() ) { 00400 list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); 00401 if ( !$name ) { 00402 return $title; 00403 } 00404 $ret = SpecialPage::getTitleFor( $name, $subpage ); 00405 $ret->mFragment = $title->getFragment(); 00406 return $ret; 00407 } else { 00408 return $title; 00409 } 00410 } 00411 00420 private static function fnamePart( $url ) { 00421 $basename = strrchr( $url, '/' ); 00422 if ( false === $basename ) { 00423 $basename = $url; 00424 } else { 00425 $basename = substr( $basename, 1 ); 00426 } 00427 return $basename; 00428 } 00429 00439 public static function makeExternalImage( $url, $alt = '' ) { 00440 if ( $alt == '' ) { 00441 $alt = self::fnamePart( $url ); 00442 } 00443 $img = ''; 00444 $success = wfRunHooks( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) ); 00445 if ( !$success ) { 00446 wfDebug( "Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}\n", true ); 00447 return $img; 00448 } 00449 return Html::element( 'img', 00450 array( 00451 'src' => $url, 00452 'alt' => $alt ) ); 00453 } 00454 00488 public static function makeImageLink2( Title $title, $file, $frameParams = array(), 00489 $handlerParams = array(), $time = false, $query = "", $widthOption = null ) 00490 { 00491 $res = null; 00492 $dummy = new DummyLinker; 00493 if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$dummy, &$title, 00494 &$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) { 00495 return $res; 00496 } 00497 00498 if ( $file && !$file->allowInlineDisplay() ) { 00499 wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display\n" ); 00500 return self::link( $title ); 00501 } 00502 00503 // Shortcuts 00504 $fp =& $frameParams; 00505 $hp =& $handlerParams; 00506 00507 // Clean up parameters 00508 $page = isset( $hp['page'] ) ? $hp['page'] : false; 00509 if ( !isset( $fp['align'] ) ) { 00510 $fp['align'] = ''; 00511 } 00512 if ( !isset( $fp['alt'] ) ) { 00513 $fp['alt'] = ''; 00514 } 00515 if ( !isset( $fp['title'] ) ) { 00516 $fp['title'] = ''; 00517 } 00518 00519 $prefix = $postfix = ''; 00520 00521 if ( 'center' == $fp['align'] ) { 00522 $prefix = '<div class="center">'; 00523 $postfix = '</div>'; 00524 $fp['align'] = 'none'; 00525 } 00526 if ( $file && !isset( $hp['width'] ) ) { 00527 if ( isset( $hp['height'] ) && $file->isVectorized() ) { 00528 // If its a vector image, and user only specifies height 00529 // we don't want it to be limited by its "normal" width. 00530 global $wgSVGMaxSize; 00531 $hp['width'] = $wgSVGMaxSize; 00532 } else { 00533 $hp['width'] = $file->getWidth( $page ); 00534 } 00535 00536 if ( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) { 00537 global $wgThumbLimits, $wgThumbUpright; 00538 if ( !isset( $widthOption ) || !isset( $wgThumbLimits[$widthOption] ) ) { 00539 $widthOption = User::getDefaultOption( 'thumbsize' ); 00540 } 00541 00542 // Reduce width for upright images when parameter 'upright' is used 00543 if ( isset( $fp['upright'] ) && $fp['upright'] == 0 ) { 00544 $fp['upright'] = $wgThumbUpright; 00545 } 00546 // 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 00547 $prefWidth = isset( $fp['upright'] ) ? 00548 round( $wgThumbLimits[$widthOption] * $fp['upright'], -1 ) : 00549 $wgThumbLimits[$widthOption]; 00550 00551 // Use width which is smaller: real image width or user preference width 00552 // Unless image is scalable vector. 00553 if ( !isset( $hp['height'] ) && ( $hp['width'] <= 0 || 00554 $prefWidth < $hp['width'] || $file->isVectorized() ) ) { 00555 $hp['width'] = $prefWidth; 00556 } 00557 } 00558 } 00559 00560 if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) ) { 00561 global $wgContLang; 00562 # Create a thumbnail. Alignment depends on language 00563 # writing direction, # right aligned for left-to-right- 00564 # languages ("Western languages"), left-aligned 00565 # for right-to-left-languages ("Semitic languages") 00566 # 00567 # If thumbnail width has not been provided, it is set 00568 # to the default user option as specified in Language*.php 00569 if ( $fp['align'] == '' ) { 00570 $fp['align'] = $wgContLang->alignEnd(); 00571 } 00572 return $prefix . self::makeThumbLink2( $title, $file, $fp, $hp, $time, $query ) . $postfix; 00573 } 00574 00575 if ( $file && isset( $fp['frameless'] ) ) { 00576 $srcWidth = $file->getWidth( $page ); 00577 # For "frameless" option: do not present an image bigger than the source (for bitmap-style images) 00578 # This is the same behaviour as the "thumb" option does it already. 00579 if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) { 00580 $hp['width'] = $srcWidth; 00581 } 00582 } 00583 00584 if ( $file && isset( $hp['width'] ) ) { 00585 # Create a resized image, without the additional thumbnail features 00586 $thumb = $file->transform( $hp ); 00587 } else { 00588 $thumb = false; 00589 } 00590 00591 if ( !$thumb ) { 00592 $s = self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true ); 00593 } else { 00594 $params = array( 00595 'alt' => $fp['alt'], 00596 'title' => $fp['title'], 00597 'valign' => isset( $fp['valign'] ) ? $fp['valign'] : false , 00598 'img-class' => isset( $fp['border'] ) ? 'thumbborder' : false ); 00599 $params = self::getImageLinkMTOParams( $fp, $query ) + $params; 00600 00601 $s = $thumb->toHtml( $params ); 00602 } 00603 if ( $fp['align'] != '' ) { 00604 $s = "<div class=\"float{$fp['align']}\">{$s}</div>"; 00605 } 00606 return str_replace( "\n", ' ', $prefix . $s . $postfix ); 00607 } 00608 00615 private static function getImageLinkMTOParams( $frameParams, $query = '' ) { 00616 $mtoParams = array(); 00617 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) { 00618 $mtoParams['custom-url-link'] = $frameParams['link-url']; 00619 if ( isset( $frameParams['link-target'] ) ) { 00620 $mtoParams['custom-target-link'] = $frameParams['link-target']; 00621 } 00622 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) { 00623 $mtoParams['custom-title-link'] = self::normaliseSpecialPage( $frameParams['link-title'] ); 00624 } elseif ( !empty( $frameParams['no-link'] ) ) { 00625 // No link 00626 } else { 00627 $mtoParams['desc-link'] = true; 00628 $mtoParams['desc-query'] = $query; 00629 } 00630 return $mtoParams; 00631 } 00632 00644 public static function makeThumbLinkObj( Title $title, $file, $label = '', $alt, 00645 $align = 'right', $params = array(), $framed = false , $manualthumb = "" ) 00646 { 00647 $frameParams = array( 00648 'alt' => $alt, 00649 'caption' => $label, 00650 'align' => $align 00651 ); 00652 if ( $framed ) { 00653 $frameParams['framed'] = true; 00654 } 00655 if ( $manualthumb ) { 00656 $frameParams['manualthumb'] = $manualthumb; 00657 } 00658 return self::makeThumbLink2( $title, $file, $frameParams, $params ); 00659 } 00660 00670 public static function makeThumbLink2( Title $title, $file, $frameParams = array(), 00671 $handlerParams = array(), $time = false, $query = "" ) 00672 { 00673 global $wgStylePath, $wgContLang; 00674 $exists = $file && $file->exists(); 00675 00676 # Shortcuts 00677 $fp =& $frameParams; 00678 $hp =& $handlerParams; 00679 00680 $page = isset( $hp['page'] ) ? $hp['page'] : false; 00681 if ( !isset( $fp['align'] ) ) $fp['align'] = 'right'; 00682 if ( !isset( $fp['alt'] ) ) $fp['alt'] = ''; 00683 if ( !isset( $fp['title'] ) ) $fp['title'] = ''; 00684 if ( !isset( $fp['caption'] ) ) $fp['caption'] = ''; 00685 00686 if ( empty( $hp['width'] ) ) { 00687 // Reduce width for upright images when parameter 'upright' is used 00688 $hp['width'] = isset( $fp['upright'] ) ? 130 : 180; 00689 } 00690 $thumb = false; 00691 00692 if ( !$exists ) { 00693 $outerWidth = $hp['width'] + 2; 00694 } else { 00695 if ( isset( $fp['manualthumb'] ) ) { 00696 # Use manually specified thumbnail 00697 $manual_title = Title::makeTitleSafe( NS_FILE, $fp['manualthumb'] ); 00698 if ( $manual_title ) { 00699 $manual_img = wfFindFile( $manual_title ); 00700 if ( $manual_img ) { 00701 $thumb = $manual_img->getUnscaledThumb( $hp ); 00702 } else { 00703 $exists = false; 00704 } 00705 } 00706 } elseif ( isset( $fp['framed'] ) ) { 00707 // Use image dimensions, don't scale 00708 $thumb = $file->getUnscaledThumb( $hp ); 00709 } else { 00710 # Do not present an image bigger than the source, for bitmap-style images 00711 # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour 00712 $srcWidth = $file->getWidth( $page ); 00713 if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) { 00714 $hp['width'] = $srcWidth; 00715 } 00716 $thumb = $file->transform( $hp ); 00717 } 00718 00719 if ( $thumb ) { 00720 $outerWidth = $thumb->getWidth() + 2; 00721 } else { 00722 $outerWidth = $hp['width'] + 2; 00723 } 00724 } 00725 00726 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs 00727 # So we don't need to pass it here in $query. However, the URL for the 00728 # zoom icon still needs it, so we make a unique query for it. See bug 14771 00729 $url = $title->getLocalURL( $query ); 00730 if ( $page ) { 00731 $url = wfAppendQuery( $url, 'page=' . urlencode( $page ) ); 00732 } 00733 00734 $s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">"; 00735 if ( !$exists ) { 00736 $s .= self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true ); 00737 $zoomIcon = ''; 00738 } elseif ( !$thumb ) { 00739 $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) ); 00740 $zoomIcon = ''; 00741 } else { 00742 $params = array( 00743 'alt' => $fp['alt'], 00744 'title' => $fp['title'], 00745 'img-class' => 'thumbimage' ); 00746 $params = self::getImageLinkMTOParams( $fp, $query ) + $params; 00747 $s .= $thumb->toHtml( $params ); 00748 if ( isset( $fp['framed'] ) ) { 00749 $zoomIcon = ""; 00750 } else { 00751 $zoomIcon = Html::rawElement( 'div', array( 'class' => 'magnify' ), 00752 Html::rawElement( 'a', array( 00753 'href' => $url, 00754 'class' => 'internal', 00755 'title' => wfMsg( 'thumbnail-more' ) ), 00756 Html::element( 'img', array( 00757 'src' => $wgStylePath . '/common/images/magnify-clip' . ( $wgContLang->isRTL() ? '-rtl' : '' ) . '.png', 00758 'width' => 15, 00759 'height' => 11, 00760 'alt' => "" ) ) ) ); 00761 } 00762 } 00763 $s .= ' <div class="thumbcaption">' . $zoomIcon . $fp['caption'] . "</div></div></div>"; 00764 return str_replace( "\n", ' ', $s ); 00765 } 00766 00778 public static function makeBrokenImageLinkObj( $title, $label = '', $query = '', $unused1 = '', $unused2 = '', $time = false ) { 00779 global $wgEnableUploads, $wgUploadMissingFileUrl, $wgUploadNavigationUrl; 00780 if ( ! $title instanceof Title ) { 00781 return "<!-- ERROR -->" . htmlspecialchars( $label ); 00782 } 00783 wfProfileIn( __METHOD__ ); 00784 if ( $label == '' ) { 00785 $label = $title->getPrefixedText(); 00786 } 00787 $encLabel = htmlspecialchars( $label ); 00788 $currentExists = $time ? ( wfFindFile( $title ) != false ) : false; 00789 00790 if ( ( $wgUploadMissingFileUrl || $wgUploadNavigationUrl || $wgEnableUploads ) && !$currentExists ) { 00791 $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title ); 00792 00793 if ( $redir ) { 00794 wfProfileOut( __METHOD__ ); 00795 return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) ); 00796 } 00797 00798 $href = self::getUploadUrl( $title, $query ); 00799 00800 wfProfileOut( __METHOD__ ); 00801 return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' . 00802 htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' . 00803 $encLabel . '</a>'; 00804 } else { 00805 wfProfileOut( __METHOD__ ); 00806 return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) ); 00807 } 00808 } 00809 00817 protected static function getUploadUrl( $destFile, $query = '' ) { 00818 global $wgUploadMissingFileUrl, $wgUploadNavigationUrl; 00819 $q = 'wpDestFile=' . $destFile->getPartialUrl(); 00820 if ( $query != '' ) 00821 $q .= '&' . $query; 00822 00823 if ( $wgUploadMissingFileUrl ) { 00824 return wfAppendQuery( $wgUploadMissingFileUrl, $q ); 00825 } elseif( $wgUploadNavigationUrl ) { 00826 return wfAppendQuery( $wgUploadNavigationUrl, $q ); 00827 } else { 00828 $upload = SpecialPage::getTitleFor( 'Upload' ); 00829 return $upload->getLocalUrl( $q ); 00830 } 00831 } 00832 00841 public static function makeMediaLinkObj( $title, $html = '', $time = false ) { 00842 $img = wfFindFile( $title, array( 'time' => $time ) ); 00843 return self::makeMediaLinkFile( $title, $img, $html ); 00844 } 00845 00857 public static function makeMediaLinkFile( Title $title, $file, $html = '' ) { 00858 if ( $file && $file->exists() ) { 00859 $url = $file->getURL(); 00860 $class = 'internal'; 00861 } else { 00862 $url = self::getUploadUrl( $title ); 00863 $class = 'new'; 00864 } 00865 $alt = htmlspecialchars( $title->getText(), ENT_QUOTES ); 00866 if ( $html == '' ) { 00867 $html = $alt; 00868 } 00869 $u = htmlspecialchars( $url ); 00870 return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$html}</a>"; 00871 } 00872 00880 public static function specialLink( $name, $key = '' ) { 00881 if ( $key == '' ) { 00882 $key = strtolower( $name ); 00883 } 00884 00885 return self::linkKnown( SpecialPage::getTitleFor( $name ) , wfMsg( $key ) ); 00886 } 00887 00896 public static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) { 00897 $class = "external"; 00898 if ( $linktype ) { 00899 $class .= " $linktype"; 00900 } 00901 if ( isset( $attribs['class'] ) && $attribs['class'] ) { 00902 $class .= " {$attribs['class']}"; 00903 } 00904 $attribs['class'] = $class; 00905 00906 if ( $escape ) { 00907 $text = htmlspecialchars( $text ); 00908 } 00909 $link = ''; 00910 $success = wfRunHooks( 'LinkerMakeExternalLink', 00911 array( &$url, &$text, &$link, &$attribs, $linktype ) ); 00912 if ( !$success ) { 00913 wfDebug( "Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true ); 00914 return $link; 00915 } 00916 $attribs['href'] = $url; 00917 return Html::rawElement( 'a', $attribs, $text ); 00918 } 00919 00928 public static function userLink( $userId, $userName, $altUserName = false ) { 00929 if ( $userId == 0 ) { 00930 $page = SpecialPage::getTitleFor( 'Contributions', $userName ); 00931 } else { 00932 $page = Title::makeTitle( NS_USER, $userName ); 00933 } 00934 00935 return self::link( 00936 $page, 00937 htmlspecialchars( $altUserName !== false ? $altUserName : $userName ), 00938 array( 'class' => 'mw-userlink' ) 00939 ); 00940 } 00941 00953 public static function userToolLinks( 00954 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null 00955 ) { 00956 global $wgUser, $wgDisableAnonTalk, $wgLang; 00957 $talkable = !( $wgDisableAnonTalk && 0 == $userId ); 00958 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK ); 00959 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId; 00960 00961 $items = array(); 00962 if ( $talkable ) { 00963 $items[] = self::userTalkLink( $userId, $userText ); 00964 } 00965 if ( $userId ) { 00966 // check if the user has an edit 00967 $attribs = array(); 00968 if ( $redContribsWhenNoEdits ) { 00969 $count = !is_null( $edits ) ? $edits : User::edits( $userId ); 00970 if ( $count == 0 ) { 00971 $attribs['class'] = 'new'; 00972 } 00973 } 00974 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText ); 00975 00976 $items[] = self::link( $contribsPage, wfMsgHtml( 'contribslink' ), $attribs ); 00977 } 00978 if ( $blockable && $wgUser->isAllowed( 'block' ) ) { 00979 $items[] = self::blockLink( $userId, $userText ); 00980 } 00981 00982 if ( $addEmailLink && $wgUser->canSendEmail() ) { 00983 $items[] = self::emailLink( $userId, $userText ); 00984 } 00985 00986 wfRunHooks( 'UserToolLinksEdit', array( $userId, $userText, &$items ) ); 00987 00988 if ( $items ) { 00989 return ' <span class="mw-usertoollinks">(' . $wgLang->pipeList( $items ) . ')</span>'; 00990 } else { 00991 return ''; 00992 } 00993 } 00994 01001 public static function userToolLinksRedContribs( $userId, $userText, $edits = null ) { 01002 return self::userToolLinks( $userId, $userText, true, 0, $edits ); 01003 } 01004 01005 01011 public static function userTalkLink( $userId, $userText ) { 01012 $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText ); 01013 $userTalkLink = self::link( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) ); 01014 return $userTalkLink; 01015 } 01016 01022 public static function blockLink( $userId, $userText ) { 01023 $blockPage = SpecialPage::getTitleFor( 'Block', $userText ); 01024 $blockLink = self::link( $blockPage, wfMsgHtml( 'blocklink' ) ); 01025 return $blockLink; 01026 } 01027 01033 public static function emailLink( $userId, $userText ) { 01034 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText ); 01035 $emailLink = self::link( $emailPage, wfMsgHtml( 'emaillink' ) ); 01036 return $emailLink; 01037 } 01038 01045 public static function revUserLink( $rev, $isPublic = false ) { 01046 if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) { 01047 $link = wfMsgHtml( 'rev-deleted-user' ); 01048 } elseif ( $rev->userCan( Revision::DELETED_USER ) ) { 01049 $link = self::userLink( $rev->getUser( Revision::FOR_THIS_USER ), 01050 $rev->getUserText( Revision::FOR_THIS_USER ) ); 01051 } else { 01052 $link = wfMsgHtml( 'rev-deleted-user' ); 01053 } 01054 if ( $rev->isDeleted( Revision::DELETED_USER ) ) { 01055 return '<span class="history-deleted">' . $link . '</span>'; 01056 } 01057 return $link; 01058 } 01059 01066 public static function revUserTools( $rev, $isPublic = false ) { 01067 if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) { 01068 $link = wfMsgHtml( 'rev-deleted-user' ); 01069 } elseif ( $rev->userCan( Revision::DELETED_USER ) ) { 01070 $userId = $rev->getUser( Revision::FOR_THIS_USER ); 01071 $userText = $rev->getUserText( Revision::FOR_THIS_USER ); 01072 $link = self::userLink( $userId, $userText ) . 01073 ' ' . self::userToolLinks( $userId, $userText ); 01074 } else { 01075 $link = wfMsgHtml( 'rev-deleted-user' ); 01076 } 01077 if ( $rev->isDeleted( Revision::DELETED_USER ) ) { 01078 return ' <span class="history-deleted">' . $link . '</span>'; 01079 } 01080 return $link; 01081 } 01082 01099 public static function formatComment( $comment, $title = null, $local = false ) { 01100 wfProfileIn( __METHOD__ ); 01101 01102 # Sanitize text a bit: 01103 $comment = str_replace( "\n", " ", $comment ); 01104 # Allow HTML entities (for bug 13815) 01105 $comment = Sanitizer::escapeHtmlAllowEntities( $comment ); 01106 01107 # Render autocomments and make links: 01108 $comment = self::formatAutocomments( $comment, $title, $local ); 01109 $comment = self::formatLinksInComment( $comment, $title, $local ); 01110 01111 wfProfileOut( __METHOD__ ); 01112 return $comment; 01113 } 01114 01118 static $autocommentTitle; 01119 static $autocommentLocal; 01120 01133 private static function formatAutocomments( $comment, $title = null, $local = false ) { 01134 // Bah! 01135 self::$autocommentTitle = $title; 01136 self::$autocommentLocal = $local; 01137 $comment = preg_replace_callback( 01138 '!(.*)/\*\s*(.*?)\s*\*/(.*)!', 01139 array( 'Linker', 'formatAutocommentsCallback' ), 01140 $comment ); 01141 self::$autocommentTitle = null; 01142 self::$autocommentLocal = null; 01143 return $comment; 01144 } 01145 01150 private static function formatAutocommentsCallback( $match ) { 01151 global $wgLang; 01152 $title = self::$autocommentTitle; 01153 $local = self::$autocommentLocal; 01154 01155 $pre = $match[1]; 01156 $auto = $match[2]; 01157 $post = $match[3]; 01158 $link = ''; 01159 if ( $title ) { 01160 $section = $auto; 01161 01162 # Remove links that a user may have manually put in the autosummary 01163 # This could be improved by copying as much of Parser::stripSectionName as desired. 01164 $section = str_replace( '[[:', '', $section ); 01165 $section = str_replace( '[[', '', $section ); 01166 $section = str_replace( ']]', '', $section ); 01167 01168 $section = Sanitizer::normalizeSectionNameWhitespace( $section ); # bug 22784 01169 if ( $local ) { 01170 $sectionTitle = Title::newFromText( '#' . $section ); 01171 } else { 01172 $sectionTitle = Title::makeTitleSafe( $title->getNamespace(), 01173 $title->getDBkey(), $section ); 01174 } 01175 if ( $sectionTitle ) { 01176 $link = self::link( $sectionTitle, 01177 $wgLang->getArrow(), array(), array(), 01178 'noclasses' ); 01179 } else { 01180 $link = ''; 01181 } 01182 } 01183 if ( $pre ) { 01184 # written summary $presep autocomment (summary /* section */) 01185 $pre .= wfMsgExt( 'autocomment-prefix', array( 'escapenoentities', 'content' ) ); 01186 } 01187 if ( $post ) { 01188 # autocomment $postsep written summary (/* section */ summary) 01189 $auto .= wfMsgExt( 'colon-separator', array( 'escapenoentities', 'content' ) ); 01190 } 01191 $auto = '<span class="autocomment">' . $auto . '</span>'; 01192 $comment = $pre . $link . $wgLang->getDirMark() . '<span dir="auto">' . $auto . $post . '</span>'; 01193 return $comment; 01194 } 01195 01199 static $commentContextTitle; 01200 static $commentLocal; 01201 01212 public static function formatLinksInComment( $comment, $title = null, $local = false ) { 01213 self::$commentContextTitle = $title; 01214 self::$commentLocal = $local; 01215 $html = preg_replace_callback( 01216 '/ 01217 \[\[ 01218 :? # ignore optional leading colon 01219 ([^\]|]+) # 1. link target; page names cannot include ] or | 01220 (?:\| 01221 # 2. a pipe-separated substring; only the last is captured 01222 # Stop matching at | and ]] without relying on backtracking. 01223 ((?:]?[^\]|])*+) 01224 )* 01225 \]\] 01226 ([^[]*) # 3. link trail (the text up until the next link) 01227 /x', 01228 array( 'Linker', 'formatLinksInCommentCallback' ), 01229 $comment ); 01230 self::$commentContextTitle = null; 01231 self::$commentLocal = null; 01232 return $html; 01233 } 01234 01239 protected static function formatLinksInCommentCallback( $match ) { 01240 global $wgContLang; 01241 01242 $medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|'; 01243 $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):'; 01244 01245 $comment = $match[0]; 01246 01247 # fix up urlencoded title texts (copied from Parser::replaceInternalLinks) 01248 if ( strpos( $match[1], '%' ) !== false ) { 01249 $match[1] = str_replace( array( '<', '>' ), array( '<', '>' ), rawurldecode( $match[1] ) ); 01250 } 01251 01252 # Handle link renaming [[foo|text]] will show link as "text" 01253 if ( $match[2] != "" ) { 01254 $text = $match[2]; 01255 } else { 01256 $text = $match[1]; 01257 } 01258 $submatch = array(); 01259 $thelink = null; 01260 if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) { 01261 # Media link; trail not supported. 01262 $linkRegexp = '/\[\[(.*?)\]\]/'; 01263 $title = Title::makeTitleSafe( NS_FILE, $submatch[1] ); 01264 if ( $title ) { 01265 $thelink = self::makeMediaLinkObj( $title, $text ); 01266 } 01267 } else { 01268 # Other kind of link 01269 if ( preg_match( $wgContLang->linkTrail(), $match[3], $submatch ) ) { 01270 $trail = $submatch[1]; 01271 } else { 01272 $trail = ""; 01273 } 01274 $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/'; 01275 if ( isset( $match[1][0] ) && $match[1][0] == ':' ) 01276 $match[1] = substr( $match[1], 1 ); 01277 list( $inside, $trail ) = self::splitTrail( $trail ); 01278 01279 $linkText = $text; 01280 $linkTarget = self::normalizeSubpageLink( self::$commentContextTitle, 01281 $match[1], $linkText ); 01282 01283 $target = Title::newFromText( $linkTarget ); 01284 if ( $target ) { 01285 if ( $target->getText() == '' && $target->getInterwiki() === '' 01286 && !self::$commentLocal && self::$commentContextTitle ) 01287 { 01288 $newTarget = clone ( self::$commentContextTitle ); 01289 $newTarget->setFragment( '#' . $target->getFragment() ); 01290 $target = $newTarget; 01291 } 01292 $thelink = self::link( 01293 $target, 01294 $linkText . $inside 01295 ) . $trail; 01296 } 01297 } 01298 if ( $thelink ) { 01299 // If the link is still valid, go ahead and replace it in! 01300 $comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 ); 01301 } 01302 01303 return $comment; 01304 } 01305 01312 public static function normalizeSubpageLink( $contextTitle, $target, &$text ) { 01313 # Valid link forms: 01314 # Foobar -- normal 01315 # :Foobar -- override special treatment of prefix (images, language links) 01316 # /Foobar -- convert to CurrentPage/Foobar 01317 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text 01318 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage 01319 # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage 01320 01321 wfProfileIn( __METHOD__ ); 01322 $ret = $target; # default return value is no change 01323 01324 # Some namespaces don't allow subpages, 01325 # so only perform processing if subpages are allowed 01326 if ( $contextTitle && MWNamespace::hasSubpages( $contextTitle->getNamespace() ) ) { 01327 $hash = strpos( $target, '#' ); 01328 if ( $hash !== false ) { 01329 $suffix = substr( $target, $hash ); 01330 $target = substr( $target, 0, $hash ); 01331 } else { 01332 $suffix = ''; 01333 } 01334 # bug 7425 01335 $target = trim( $target ); 01336 # Look at the first character 01337 if ( $target != '' && $target[0] === '/' ) { 01338 # / at end means we don't want the slash to be shown 01339 $m = array(); 01340 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m ); 01341 if ( $trailingSlashes ) { 01342 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) ); 01343 } else { 01344 $noslash = substr( $target, 1 ); 01345 } 01346 01347 $ret = $contextTitle->getPrefixedText() . '/' . trim( $noslash ) . $suffix; 01348 if ( $text === '' ) { 01349 $text = $target . $suffix; 01350 } # this might be changed for ugliness reasons 01351 } else { 01352 # check for .. subpage backlinks 01353 $dotdotcount = 0; 01354 $nodotdot = $target; 01355 while ( strncmp( $nodotdot, "../", 3 ) == 0 ) { 01356 ++$dotdotcount; 01357 $nodotdot = substr( $nodotdot, 3 ); 01358 } 01359 if ( $dotdotcount > 0 ) { 01360 $exploded = explode( '/', $contextTitle->GetPrefixedText() ); 01361 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page 01362 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) ); 01363 # / at the end means don't show full path 01364 if ( substr( $nodotdot, -1, 1 ) === '/' ) { 01365 $nodotdot = substr( $nodotdot, 0, -1 ); 01366 if ( $text === '' ) { 01367 $text = $nodotdot . $suffix; 01368 } 01369 } 01370 $nodotdot = trim( $nodotdot ); 01371 if ( $nodotdot != '' ) { 01372 $ret .= '/' . $nodotdot; 01373 } 01374 $ret .= $suffix; 01375 } 01376 } 01377 } 01378 } 01379 01380 wfProfileOut( __METHOD__ ); 01381 return $ret; 01382 } 01383 01394 public static function commentBlock( $comment, $title = null, $local = false ) { 01395 // '*' used to be the comment inserted by the software way back 01396 // in antiquity in case none was provided, here for backwards 01397 // compatability, acc. to brion -ævar 01398 if ( $comment == '' || $comment == '*' ) { 01399 return ''; 01400 } else { 01401 $formatted = self::formatComment( $comment, $title, $local ); 01402 return " <span class=\"comment\" dir=\"auto\">($formatted)</span>"; 01403 } 01404 } 01405 01415 public static function revComment( Revision $rev, $local = false, $isPublic = false ) { 01416 if ( $rev->getRawComment() == "" ) { 01417 return ""; 01418 } 01419 if ( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) { 01420 $block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>"; 01421 } elseif ( $rev->userCan( Revision::DELETED_COMMENT ) ) { 01422 $block = self::commentBlock( $rev->getComment( Revision::FOR_THIS_USER ), 01423 $rev->getTitle(), $local ); 01424 } else { 01425 $block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>"; 01426 } 01427 if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) { 01428 return " <span class=\"history-deleted\">$block</span>"; 01429 } 01430 return $block; 01431 } 01432 01437 public static function formatRevisionSize( $size ) { 01438 if ( $size == 0 ) { 01439 $stxt = wfMsgExt( 'historyempty', 'parsemag' ); 01440 } else { 01441 global $wgLang; 01442 $stxt = wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $size ) ); 01443 $stxt = "($stxt)"; 01444 } 01445 $stxt = htmlspecialchars( $stxt ); 01446 return "<span class=\"history-size\">$stxt</span>"; 01447 } 01448 01454 public static function tocIndent() { 01455 return "\n<ul>"; 01456 } 01457 01463 public static function tocUnindent( $level ) { 01464 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 ); 01465 } 01466 01472 public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) { 01473 $classes = "toclevel-$level"; 01474 if ( $sectionIndex !== false ) { 01475 $classes .= " tocsection-$sectionIndex"; 01476 } 01477 return "\n<li class=\"$classes\"><a href=\"#" . 01478 $anchor . '"><span class="tocnumber">' . 01479 $tocnumber . '</span> <span class="toctext">' . 01480 $tocline . '</span></a>'; 01481 } 01482 01488 public static function tocLineEnd() { 01489 return "</li>\n"; 01490 } 01491 01499 public static function tocList( $toc, $lang = false ) { 01500 $title = wfMsgExt( 'toc', array( 'language' => $lang, 'escape' ) ); 01501 return 01502 '<table id="toc" class="toc"><tr><td>' 01503 . '<div id="toctitle"><h2>' . $title . "</h2></div>\n" 01504 . $toc 01505 . "</ul>\n</td></tr></table>\n"; 01506 } 01507 01515 public static function generateTOC( $tree ) { 01516 $toc = ''; 01517 $lastLevel = 0; 01518 foreach ( $tree as $section ) { 01519 if ( $section['toclevel'] > $lastLevel ) 01520 $toc .= self::tocIndent(); 01521 elseif ( $section['toclevel'] < $lastLevel ) 01522 $toc .= self::tocUnindent( 01523 $lastLevel - $section['toclevel'] ); 01524 else 01525 $toc .= self::tocLineEnd(); 01526 01527 $toc .= self::tocLine( $section['anchor'], 01528 $section['line'], $section['number'], 01529 $section['toclevel'], $section['index'] ); 01530 $lastLevel = $section['toclevel']; 01531 } 01532 $toc .= self::tocLineEnd(); 01533 return self::tocList( $toc ); 01534 } 01535 01551 public static function makeHeadline( $level, $attribs, $anchor, $html, $link, $legacyAnchor = false ) { 01552 $ret = "<h$level$attribs" 01553 . $link 01554 . " <span class=\"mw-headline\" id=\"$anchor\">$html</span>" 01555 . "</h$level>"; 01556 if ( $legacyAnchor !== false ) { 01557 $ret = "<div id=\"$legacyAnchor\"></div>$ret"; 01558 } 01559 return $ret; 01560 } 01561 01566 static function splitTrail( $trail ) { 01567 global $wgContLang; 01568 $regex = $wgContLang->linkTrail(); 01569 $inside = ''; 01570 if ( $trail !== '' ) { 01571 $m = array(); 01572 if ( preg_match( $regex, $trail, $m ) ) { 01573 $inside = $m[1]; 01574 $trail = $m[2]; 01575 } 01576 } 01577 return array( $inside, $trail ); 01578 } 01579 01593 public static function generateRollback( $rev ) { 01594 return '<span class="mw-rollback-link">[' 01595 . self::buildRollbackLink( $rev ) 01596 . ']</span>'; 01597 } 01598 01605 public static function buildRollbackLink( $rev ) { 01606 global $wgRequest, $wgUser; 01607 $title = $rev->getTitle(); 01608 $query = array( 01609 'action' => 'rollback', 01610 'from' => $rev->getUserText(), 01611 'token' => $wgUser->getEditToken( array( $title->getPrefixedText(), $rev->getUserText() ) ), 01612 ); 01613 if ( $wgRequest->getBool( 'bot' ) ) { 01614 $query['bot'] = '1'; 01615 $query['hidediff'] = '1'; // bug 15999 01616 } 01617 return self::link( 01618 $title, 01619 wfMsgHtml( 'rollbacklink' ), 01620 array( 'title' => wfMsg( 'tooltip-rollback' ) ), 01621 $query, 01622 array( 'known', 'noclasses' ) 01623 ); 01624 } 01625 01635 public static function formatTemplates( $templates, $preview = false, $section = false ) { 01636 wfProfileIn( __METHOD__ ); 01637 01638 $outText = ''; 01639 if ( count( $templates ) > 0 ) { 01640 # Do a batch existence check 01641 $batch = new LinkBatch; 01642 foreach ( $templates as $title ) { 01643 $batch->addObj( $title ); 01644 } 01645 $batch->execute(); 01646 01647 # Construct the HTML 01648 $outText = '<div class="mw-templatesUsedExplanation">'; 01649 if ( $preview ) { 01650 $outText .= wfMsgExt( 'templatesusedpreview', array( 'parse' ), count( $templates ) ); 01651 } elseif ( $section ) { 01652 $outText .= wfMsgExt( 'templatesusedsection', array( 'parse' ), count( $templates ) ); 01653 } else { 01654 $outText .= wfMsgExt( 'templatesused', array( 'parse' ), count( $templates ) ); 01655 } 01656 $outText .= "</div><ul>\n"; 01657 01658 usort( $templates, array( 'Title', 'compare' ) ); 01659 foreach ( $templates as $titleObj ) { 01660 $r = $titleObj->getRestrictions( 'edit' ); 01661 if ( in_array( 'sysop', $r ) ) { 01662 $protected = wfMsgExt( 'template-protected', array( 'parseinline' ) ); 01663 } elseif ( in_array( 'autoconfirmed', $r ) ) { 01664 $protected = wfMsgExt( 'template-semiprotected', array( 'parseinline' ) ); 01665 } else { 01666 $protected = ''; 01667 } 01668 if ( $titleObj->quickUserCan( 'edit' ) ) { 01669 $editLink = self::link( 01670 $titleObj, 01671 wfMsg( 'editlink' ), 01672 array(), 01673 array( 'action' => 'edit' ) 01674 ); 01675 } else { 01676 $editLink = self::link( 01677 $titleObj, 01678 wfMsg( 'viewsourcelink' ), 01679 array(), 01680 array( 'action' => 'edit' ) 01681 ); 01682 } 01683 $outText .= '<li>' . self::link( $titleObj ) . ' (' . $editLink . ') ' . $protected . '</li>'; 01684 } 01685 $outText .= '</ul>'; 01686 } 01687 wfProfileOut( __METHOD__ ); 01688 return $outText; 01689 } 01690 01698 public static function formatHiddenCategories( $hiddencats ) { 01699 global $wgLang; 01700 wfProfileIn( __METHOD__ ); 01701 01702 $outText = ''; 01703 if ( count( $hiddencats ) > 0 ) { 01704 # Construct the HTML 01705 $outText = '<div class="mw-hiddenCategoriesExplanation">'; 01706 $outText .= wfMsgExt( 'hiddencategories', array( 'parse' ), $wgLang->formatnum( count( $hiddencats ) ) ); 01707 $outText .= "</div><ul>\n"; 01708 01709 foreach ( $hiddencats as $titleObj ) { 01710 $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 01711 } 01712 $outText .= '</ul>'; 01713 } 01714 wfProfileOut( __METHOD__ ); 01715 return $outText; 01716 } 01717 01725 public static function formatSize( $size ) { 01726 global $wgLang; 01727 return htmlspecialchars( $wgLang->formatSize( $size ) ); 01728 } 01729 01742 public static function titleAttrib( $name, $options = null ) { 01743 wfProfileIn( __METHOD__ ); 01744 01745 $message = wfMessage( "tooltip-$name" ); 01746 01747 if ( !$message->exists() ) { 01748 $tooltip = false; 01749 } else { 01750 $tooltip = $message->text(); 01751 # Compatibility: formerly some tooltips had [alt-.] hardcoded 01752 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip ); 01753 # Message equal to '-' means suppress it. 01754 if ( $tooltip == '-' ) { 01755 $tooltip = false; 01756 } 01757 } 01758 01759 if ( $options == 'withaccess' ) { 01760 $accesskey = self::accesskey( $name ); 01761 if ( $accesskey !== false ) { 01762 if ( $tooltip === false || $tooltip === '' ) { 01763 $tooltip = "[$accesskey]"; 01764 } else { 01765 $tooltip .= " [$accesskey]"; 01766 } 01767 } 01768 } 01769 01770 wfProfileOut( __METHOD__ ); 01771 return $tooltip; 01772 } 01773 01774 static $accesskeycache; 01775 01786 public static function accesskey( $name ) { 01787 if ( isset( self::$accesskeycache[$name] ) ) { 01788 return self::$accesskeycache[$name]; 01789 } 01790 wfProfileIn( __METHOD__ ); 01791 01792 $message = wfMessage( "accesskey-$name" ); 01793 01794 if ( !$message->exists() ) { 01795 $accesskey = false; 01796 } else { 01797 $accesskey = $message->plain(); 01798 if ( $accesskey === '' || $accesskey === '-' ) { 01799 # @todo FIXME: Per standard MW behavior, a value of '-' means to suppress the 01800 # attribute, but this is broken for accesskey: that might be a useful 01801 # value. 01802 $accesskey = false; 01803 } 01804 } 01805 01806 wfProfileOut( __METHOD__ ); 01807 return self::$accesskeycache[$name] = $accesskey; 01808 } 01809 01823 public static function getRevDeleteLink( User $user, Revision $rev, Title $title ) { 01824 $canHide = $user->isAllowed( 'deleterevision' ); 01825 if ( !$canHide && !( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) { 01826 return ''; 01827 } 01828 01829 if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) { 01830 return Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops 01831 } else { 01832 if ( $rev->getId() ) { 01833 // RevDelete links using revision ID are stable across 01834 // page deletion and undeletion; use when possible. 01835 $query = array( 01836 'type' => 'revision', 01837 'target' => $title->getPrefixedDBkey(), 01838 'ids' => $rev->getId() 01839 ); 01840 } else { 01841 // Older deleted entries didn't save a revision ID. 01842 // We have to refer to these by timestamp, ick! 01843 $query = array( 01844 'type' => 'archive', 01845 'target' => $title->getPrefixedDBkey(), 01846 'ids' => $rev->getTimestamp() 01847 ); 01848 } 01849 return Linker::revDeleteLink( $query, 01850 $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide ); 01851 } 01852 } 01853 01864 public static function revDeleteLink( $query = array(), $restricted = false, $delete = true ) { 01865 $sp = SpecialPage::getTitleFor( 'Revisiondelete' ); 01866 $html = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' ); 01867 $tag = $restricted ? 'strong' : 'span'; 01868 $link = self::link( $sp, $html, array(), $query, array( 'known', 'noclasses' ) ); 01869 return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), "($link)" ); 01870 } 01871 01880 public static function revDeleteLinkDisabled( $delete = true ) { 01881 $html = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' ); 01882 return Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), "($html)" ); 01883 } 01884 01885 /* Deprecated methods */ 01886 01900 static function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) { 01901 wfDeprecated( __METHOD__, '1.16' ); 01902 01903 $nt = Title::newFromText( $title ); 01904 if ( $nt instanceof Title ) { 01905 return self::makeBrokenLinkObj( $nt, $text, $query, $trail ); 01906 } else { 01907 wfDebug( 'Invalid title passed to self::makeBrokenLink(): "' . $title . "\"\n" ); 01908 return $text == '' ? $title : $text; 01909 } 01910 } 01911 01928 static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) { 01929 # wfDeprecated( __METHOD__, '1.16' ); // See r105985 and it's revert. Somewhere still used. 01930 01931 wfProfileIn( __METHOD__ ); 01932 $query = wfCgiToArray( $query ); 01933 list( $inside, $trail ) = self::splitTrail( $trail ); 01934 if ( $text === '' ) { 01935 $text = self::linkText( $nt ); 01936 } 01937 01938 $ret = self::link( $nt, "$prefix$text$inside", array(), $query ) . $trail; 01939 01940 wfProfileOut( __METHOD__ ); 01941 return $ret; 01942 } 01943 01960 static function makeKnownLinkObj( 01961 $title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = '' 01962 ) { 01963 # wfDeprecated( __METHOD__, '1.16' ); // See r105985 and it's revert. Somewhere still used. 01964 01965 wfProfileIn( __METHOD__ ); 01966 01967 if ( $text == '' ) { 01968 $text = self::linkText( $title ); 01969 } 01970 $attribs = Sanitizer::mergeAttributes( 01971 Sanitizer::decodeTagAttributes( $aprops ), 01972 Sanitizer::decodeTagAttributes( $style ) 01973 ); 01974 $query = wfCgiToArray( $query ); 01975 list( $inside, $trail ) = self::splitTrail( $trail ); 01976 01977 $ret = self::link( $title, "$prefix$text$inside", $attribs, $query, 01978 array( 'known', 'noclasses' ) ) . $trail; 01979 01980 wfProfileOut( __METHOD__ ); 01981 return $ret; 01982 } 01983 01997 static function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) { 01998 wfDeprecated( __METHOD__, '1.16' ); 01999 02000 wfProfileIn( __METHOD__ ); 02001 02002 list( $inside, $trail ) = self::splitTrail( $trail ); 02003 if ( $text === '' ) { 02004 $text = self::linkText( $title ); 02005 } 02006 02007 $ret = self::link( $title, "$prefix$text$inside", array(), 02008 wfCgiToArray( $query ), 'broken' ) . $trail; 02009 02010 wfProfileOut( __METHOD__ ); 02011 return $ret; 02012 } 02013 02028 static function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) { 02029 wfDeprecated( __METHOD__, '1.16' ); 02030 02031 if ( $colour != '' ) { 02032 $style = self::getInternalLinkAttributesObj( $nt, $text, $colour ); 02033 } else { 02034 $style = ''; 02035 } 02036 return self::makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style ); 02037 } 02038 02042 public static function tooltipAndAccesskeyAttribs( $name ) { 02043 # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output 02044 # no attribute" instead of "output '' as value for attribute", this 02045 # would be three lines. 02046 $attribs = array( 02047 'title' => self::titleAttrib( $name, 'withaccess' ), 02048 'accesskey' => self::accesskey( $name ) 02049 ); 02050 if ( $attribs['title'] === false ) { 02051 unset( $attribs['title'] ); 02052 } 02053 if ( $attribs['accesskey'] === false ) { 02054 unset( $attribs['accesskey'] ); 02055 } 02056 return $attribs; 02057 } 02058 02062 public static function tooltip( $name, $options = null ) { 02063 # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output 02064 # no attribute" instead of "output '' as value for attribute", this 02065 # would be two lines. 02066 $tooltip = self::titleAttrib( $name, $options ); 02067 if ( $tooltip === false ) { 02068 return ''; 02069 } 02070 return Xml::expandAttributes( array( 02071 'title' => $tooltip 02072 ) ); 02073 } 02074 } 02075 02079 class DummyLinker { 02080 02088 public function __call( $fname, $args ) { 02089 return call_user_func_array( array( 'Linker', $fname ), $args ); 02090 } 02091 }