From: Aryeh Gregor Date: Wed, 30 Jul 2008 21:02:28 +0000 (+0000) Subject: New Linker::link() function, intended to replace Linker::make*Link*() functions.... X-Git-Tag: 1.31.0-rc.0~46266 X-Git-Url: http://git.cyclocoop.org/fichier?a=commitdiff_plain;h=868c45880172b9ad72da8498d6069be7b9f35521;p=lhc%2Fweb%2Fwiklou.git New Linker::link() function, intended to replace Linker::make*Link*() functions. Cleaner interface and more cleanly written. Convert a bunch of stuff to use it. All parser tests pass (except the usual 17). --- diff --git a/includes/Linker.php b/includes/Linker.php index b232ce876b..c7958740fa 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -21,6 +21,7 @@ class Linker { * @deprecated */ function postParseLinkColour( $s = null ) { + wfDeprecated( __METHOD__ ); return null; } @@ -130,6 +131,148 @@ class Linker { return $colour; } + /** + * This function returns an HTML link to the given target. It serves a few purposes: + * 1) If $target is a Title, the correct URL to link to will be figured out automatically. + * 2) It automatically adds the usual classes for various types of link targets: "new" for red links, "extern" for external links, etc. + * 3) It escapes all attribute values safely so there's no risk of XSS. + * 4) It provides a default tooltip if the target is a Title (the page name of the target). + * + * @param $target Title Can currently only be a Title, but this may change. + * @param $text string The HTML contents of the element, i.e., the link text. This is raw HTML and will not be escaped. If null, defaults to the page name of the Title or Image, or the text of the URL if $target is a URL. + * @param $query array The query string to append to the URL you're linking to, in key => value array form. Useful mainly for Titles and Images. Query keys and values will be URL-encoded. + * @param $customAttribs array A key => value array of extra HTML attributes, such as title and class. (href is ignored.) Classes will be merged with the default classes, while other attributes will replace default attributes. All passed attribute values will be HTML-escaped. A false attribute value means to suppress that attribute. + * @param $options mixed String or array of strings: + * 'known': Page is known to exist, so don't check if it does. + * 'broken': Page is known not to exist, so don't check if it does. + * 'noclasses': Don't add any classes automatically (includes "new", "stub", "mw-redirect"). Only use the class attribute provided, if any. + * @return string HTML attribute + */ + public function link( $target, $text = null, $customAttribs = array(), $query = array(), $options = array() ) { + wfProfileIn( __METHOD__ ); + if( !($target instanceof Title) ) { + throw new MWException( 'Linker::link passed invalid target' ); + } + $options = (array)$options; + + # Normalize the Title if it's a special page + if( $target->getNamespace() == NS_SPECIAL ) { + list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $target->getDBkey() ); + if( $name ) { + $target = SpecialPage::getTitleFor( $name, $subpage ); + } + } + + # If we don't know whether the page exists, let's find out. + if( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) { + if( $target->getNamespace() == NS_SPECIAL ) { + if( SpecialPage::exists( $target->getDbKey() ) ) { + $options []= 'known'; + } else { + $options []= 'broken'; + } + } elseif( $target->isAlwaysKnown() or + ($target->getPrefixedText() == '' and $target->getFragment() != '') + or $target->exists() ) { + $options []= 'known'; + } else { + # Either it exists + $options []= 'broken'; + } + } + + # Note: we want the href attribute first, for prettiness. + $attribs = array( 'href' => $this->linkUrl( $target, $query, $options ) ); + $attribs = array_merge( + $attribs, + $this->linkAttribs( $target, $customAttribs, $options ) + ); + if( is_null( $text ) ) { + $text = $this->linkText( $target, $options ); + } + + $ret = Xml::element( 'a', $attribs, $text, false ); + + wfProfileOut( __METHOD__ ); + return $ret; + } + + private function linkUrl( $target, $query, $options ) { + # If it's a broken link, add the appropriate query pieces. This over- + # writes the default action! + if( in_array( 'broken', $options ) ) { + $query['action'] = 'edit'; + $query['redlink'] = '1'; + } + + $queryString = array(); + foreach( $query as $key => $val ) { + $queryString []= urlencode( $key ) . '=' . urlencode( $val ); + } + $queryString = implode( '&', $queryString ); + + if( $target->isExternal() ) { + return $target->getFullURL( $queryString ); + } + return $target->getLocalURL( $queryString ); + } + + private function linkAttribs( $target, $attribs, $options ) { + global $wgUser; + $defaults = array(); + + # First get a default title attribute. + if( in_array( 'known', $options ) ) { + $defaults['title'] = $target->getPrefixedText(); + } else { + $defaults['title'] = wfMsg( 'red-link-title', $target->getPrefixedText() ); + } + + if( !in_array( 'noclasses', $options ) ) { + # Now build the classes. This is the bulk of what we're doing. + $classes = array(); + + if( in_array( 'broken', $options ) ) { + $classes []= 'new'; + } + + # Note that redirects never count as stubs here. + if ( $target->isRedirect() ) { + $classes []= 'mw-redirect'; + } elseif( $target->isContentPage() ) { + $threshold = $wgUser->getOption( 'stubthreshold' ); + if( $threshold > 0 and $target->getLength() < $threshold ) { + $classes []= 'stub'; + } + } + if( $classes != array() ) { + $defaults['class'] = implode( ' ', $classes ); + } + } + + # Finally, merge the custom attribs with the default ones, and iterate + # over that, deleting all "false" attributes. + if( !empty( $attribs['class'] ) and !empty( $defaults['class'] ) ) { + $attribs['class'] .= ' '.$defaults['class']; + } + $ret = array(); + foreach( array_merge( $defaults, $attribs ) as $key => $val ) { + if( $key != 'href' and $val !== false ) { + $ret[$key] = $val; + } + } + return $ret; + } + + private function linkText( $target, $options ) { + # If the target is just a fragment, with no title, we return the frag- + # ment text. Otherwise, we return the title text itself. + if( $target->getPrefixedText() === '' and $target->getFragment() !== '' ) { + return htmlspecialchars( $target->getFragment() ); + } + return htmlspecialchars( $target->getPrefixedText() ); + } + /** * This function is a shortcut to makeLinkObj(Title::newFromText($title),...). Do not call * it if you already have a title object handy. See makeLinkObj for further documentation. @@ -465,6 +608,7 @@ class Linker { /** Obsolete alias */ function makeImage( $url, $alt = '' ) { + wfDeprecated( __METHOD__ ); return $this->makeExternalImage( $url, $alt ); } @@ -563,7 +707,7 @@ class Linker { global $wgContLang, $wgUser, $wgThumbLimits, $wgThumbUpright; if ( $file && !$file->allowInlineDisplay() ) { wfDebug( __METHOD__.': '.$title->getPrefixedDBkey()." does not allow inline display\n" ); - return $this->makeKnownLinkObj( $title ); + return $this->link( $title ); } // Shortcuts @@ -878,13 +1022,11 @@ class Linker { function userLink( $userId, $userText ) { $encName = htmlspecialchars( $userText ); if( $userId == 0 ) { - $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText ); - return $this->makeKnownLinkObj( $contribsPage, - $encName); + $page = SpecialPage::getTitleFor( 'Contributions', $userText ); } else { - $userPage = Title::makeTitle( NS_USER, $userText ); - return $this->makeLinkObj( $userPage, $encName ); + $page = Title::makeTitle( NS_USER, $userText ); } + return $this->link( $page, $encName ); } /** @@ -908,15 +1050,16 @@ class Linker { } if( $userId ) { // check if the user has an edit + $attribs = array(); if( $redContribsWhenNoEdits ) { $count = !is_null($edits) ? $edits : User::edits( $userId ); - $style = ($count == 0) ? " class='new'" : ''; - } else { - $style = ''; + if( $count == 0 ) { + $attribs['class'] = 'new'; + } } $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText ); - $items[] = $this->makeKnownLinkObj( $contribsPage, wfMsgHtml( 'contribslink' ), '', '', '', '', $style ); + $items[] = $this->link( $contribsPage, wfMsgHtml( 'contribslink' ), $attribs ); } if( $blockable && $wgUser->isAllowed( 'block' ) ) { $items[] = $this->blockLink( $userId, $userText ); @@ -948,7 +1091,7 @@ class Linker { */ function userTalkLink( $userId, $userText ) { $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText ); - $userTalkLink = $this->makeLinkObj( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) ); + $userTalkLink = $this->link( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) ); return $userTalkLink; } @@ -960,8 +1103,7 @@ class Linker { */ function blockLink( $userId, $userText ) { $blockPage = SpecialPage::getTitleFor( 'Blockip', $userText ); - $blockLink = $this->makeKnownLinkObj( $blockPage, - wfMsgHtml( 'blocklink' ) ); + $blockLink = $this->link( $blockPage, wfMsgHtml( 'blocklink' ) ); return $blockLink; } @@ -1074,7 +1216,7 @@ class Linker { $sectionTitle = wfClone( $title ); $sectionTitle->mFragment = $section; } - $link = $this->makeKnownLinkObj( $sectionTitle, wfMsgForContent( 'sectionlink' ) ); + $link = $this->link( $sectionTitle, wfMsgForContent( 'sectionlink' ) ); } $auto = $link . $auto; if( $pre ) { @@ -1260,7 +1402,7 @@ class Linker { public function editSectionLinkForOther( $title, $section ) { wfDeprecated( __METHOD__ ); $title = Title::newFromText( $title ); - return $this->doEditSectionLink( $title, $section, '', 'EditSectionLinkForOther' ); + return $this->doEditSectionLink( $title, $section ); } /** @@ -1268,9 +1410,14 @@ class Linker { * @param $section Integer: section number. * @param $hint Link String: title, or default if omitted or empty */ - public function editSectionLink( Title $nt, $section, $hint='' ) { + public function editSectionLink( Title $nt, $section, $hint = '' ) { wfDeprecated( __METHOD__ ); - return $this->doEditSectionLink( $nt, $section, $hint, 'EditSectionLink' ); + if( $hint === '' ) { + # No way to pass an actual empty $hint here! The new interface al- + # lows this, so we have to do this for compatibility. + $hint = null; + } + return $this->doEditSectionLink( $nt, $section, $hint ); } /** @@ -1285,22 +1432,24 @@ class Linker { * and wrapped in the 'editsectionhint' message * @return string HTML to use for edit link */ - public function doEditSectionLink( Title $nt, $section, $tooltip='' ) { + public function doEditSectionLink( Title $nt, $section, $tooltip = null ) { + $attribs = array(); + if( !is_null( $tooltip ) ) { + $attribs['title'] = wfMsg( 'editsectionhint', $tooltip ); + } + $url = $this->link( $nt, wfMsg('editsection'), + $attribs, + array( 'action' => 'edit', 'section' => $section ), + array( 'noclasses', 'known' ) + ); + + # Run the old hook. This takes up half of the function . . . hopefully + # we can rid of it someday. $attribs = ''; if( $tooltip ) { $attribs = wfMsgHtml( 'editsectionhint', htmlspecialchars( $tooltip ) ); $attribs = " title=\"$attribs\""; } - - $url = $this->makeKnownLinkObj( - $nt, - htmlspecialchars(wfMsg('editsection')), - "action=edit§ion=$section", - '', '', '', $attribs - ); - - # Run the old hook. This takes up half of the function . . . hopefully - # we can rid of it someday. $result = null; wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $url, &$result ) ); if( !is_null( $result ) ) { @@ -1388,14 +1537,14 @@ class Linker { public function buildRollbackLink( $rev ) { global $wgRequest, $wgUser; $title = $rev->getTitle(); - $extra = $wgRequest->getBool( 'bot' ) ? '&bot=1' : ''; - $extra .= '&token=' . urlencode( $wgUser->editToken( array( $title->getPrefixedText(), - $rev->getUserText() ) ) ); - return $this->makeKnownLinkObj( - $title, - wfMsgHtml( 'rollbacklink' ), - 'action=rollback&from=' . urlencode( $rev->getUserText() ) . $extra - ); + $query = array( 'action' => 'rollback' ); + if( $wgRequest->getBool( 'bot' ) ) { + $query['bot'] = '1'; + } + $query['token'] = $wgUser->editToken( array( $title->getPrefixedText(), + $rev->getUserText() ) ); + return $this->link( $title, wfMsgHtml( 'rollbacklink' ), array(), + $query, 'known' ); } /** @@ -1443,7 +1592,7 @@ class Linker { } else { $protected = ''; } - $outText .= '
  • ' . $sk->makeLinkObj( $titleObj ) . ' ' . $protected . '
  • '; + $outText .= '
  • ' . $sk->link( $titleObj ) . ' ' . $protected . '
  • '; } $outText .= ''; } @@ -1472,7 +1621,7 @@ class Linker { $outText .= ''; } diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php index 28b1c27576..d71dee41cd 100644 --- a/includes/Sanitizer.php +++ b/includes/Sanitizer.php @@ -1301,7 +1301,7 @@ class Sanitizer { return $out; } - static function cleanUrl( $url, $hostname=true ) { + static function cleanUrl( $url ) { # Normalize any HTML entities in input. They will be # re-escaped by makeExternalLink(). $url = Sanitizer::decodeCharReferences( $url );