r38254 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r38253‎ | r38254 | r38255 >
Date:21:02, 30 July 2008
Author:simetrical
Status:old
Tags:
Comment:
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).
Modified paths:
  • /trunk/phase3/includes/Linker.php (modified) (history)
  • /trunk/phase3/includes/Sanitizer.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/Linker.php
@@ -21,6 +21,7 @@
2222 * @deprecated
2323 */
2424 function postParseLinkColour( $s = null ) {
 25+ wfDeprecated( __METHOD__ );
2526 return null;
2627 }
2728
@@ -131,6 +132,148 @@
132133 }
133134
134135 /**
 136+ * This function returns an HTML link to the given target. It serves a few purposes:
 137+ * 1) If $target is a Title, the correct URL to link to will be figured out automatically.
 138+ * 2) It automatically adds the usual classes for various types of link targets: "new" for red links, "extern" for external links, etc.
 139+ * 3) It escapes all attribute values safely so there's no risk of XSS.
 140+ * 4) It provides a default tooltip if the target is a Title (the page name of the target).
 141+ *
 142+ * @param $target Title Can currently only be a Title, but this may change.
 143+ * @param $text string The HTML contents of the <a> 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.
 144+ * @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.
 145+ * @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.
 146+ * @param $options mixed String or array of strings:
 147+ * 'known': Page is known to exist, so don't check if it does.
 148+ * 'broken': Page is known not to exist, so don't check if it does.
 149+ * 'noclasses': Don't add any classes automatically (includes "new", "stub", "mw-redirect"). Only use the class attribute provided, if any.
 150+ * @return string HTML <a> attribute
 151+ */
 152+ public function link( $target, $text = null, $customAttribs = array(), $query = array(), $options = array() ) {
 153+ wfProfileIn( __METHOD__ );
 154+ if( !($target instanceof Title) ) {
 155+ throw new MWException( 'Linker::link passed invalid target' );
 156+ }
 157+ $options = (array)$options;
 158+
 159+ # Normalize the Title if it's a special page
 160+ if( $target->getNamespace() == NS_SPECIAL ) {
 161+ list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $target->getDBkey() );
 162+ if( $name ) {
 163+ $target = SpecialPage::getTitleFor( $name, $subpage );
 164+ }
 165+ }
 166+
 167+ # If we don't know whether the page exists, let's find out.
 168+ if( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) {
 169+ if( $target->getNamespace() == NS_SPECIAL ) {
 170+ if( SpecialPage::exists( $target->getDbKey() ) ) {
 171+ $options []= 'known';
 172+ } else {
 173+ $options []= 'broken';
 174+ }
 175+ } elseif( $target->isAlwaysKnown() or
 176+ ($target->getPrefixedText() == '' and $target->getFragment() != '')
 177+ or $target->exists() ) {
 178+ $options []= 'known';
 179+ } else {
 180+ # Either it exists
 181+ $options []= 'broken';
 182+ }
 183+ }
 184+
 185+ # Note: we want the href attribute first, for prettiness.
 186+ $attribs = array( 'href' => $this->linkUrl( $target, $query, $options ) );
 187+ $attribs = array_merge(
 188+ $attribs,
 189+ $this->linkAttribs( $target, $customAttribs, $options )
 190+ );
 191+ if( is_null( $text ) ) {
 192+ $text = $this->linkText( $target, $options );
 193+ }
 194+
 195+ $ret = Xml::element( 'a', $attribs, $text, false );
 196+
 197+ wfProfileOut( __METHOD__ );
 198+ return $ret;
 199+ }
 200+
 201+ private function linkUrl( $target, $query, $options ) {
 202+ # If it's a broken link, add the appropriate query pieces. This over-
 203+ # writes the default action!
 204+ if( in_array( 'broken', $options ) ) {
 205+ $query['action'] = 'edit';
 206+ $query['redlink'] = '1';
 207+ }
 208+
 209+ $queryString = array();
 210+ foreach( $query as $key => $val ) {
 211+ $queryString []= urlencode( $key ) . '=' . urlencode( $val );
 212+ }
 213+ $queryString = implode( '&', $queryString );
 214+
 215+ if( $target->isExternal() ) {
 216+ return $target->getFullURL( $queryString );
 217+ }
 218+ return $target->getLocalURL( $queryString );
 219+ }
 220+
 221+ private function linkAttribs( $target, $attribs, $options ) {
 222+ global $wgUser;
 223+ $defaults = array();
 224+
 225+ # First get a default title attribute.
 226+ if( in_array( 'known', $options ) ) {
 227+ $defaults['title'] = $target->getPrefixedText();
 228+ } else {
 229+ $defaults['title'] = wfMsg( 'red-link-title', $target->getPrefixedText() );
 230+ }
 231+
 232+ if( !in_array( 'noclasses', $options ) ) {
 233+ # Now build the classes. This is the bulk of what we're doing.
 234+ $classes = array();
 235+
 236+ if( in_array( 'broken', $options ) ) {
 237+ $classes []= 'new';
 238+ }
 239+
 240+ # Note that redirects never count as stubs here.
 241+ if ( $target->isRedirect() ) {
 242+ $classes []= 'mw-redirect';
 243+ } elseif( $target->isContentPage() ) {
 244+ $threshold = $wgUser->getOption( 'stubthreshold' );
 245+ if( $threshold > 0 and $target->getLength() < $threshold ) {
 246+ $classes []= 'stub';
 247+ }
 248+ }
 249+ if( $classes != array() ) {
 250+ $defaults['class'] = implode( ' ', $classes );
 251+ }
 252+ }
 253+
 254+ # Finally, merge the custom attribs with the default ones, and iterate
 255+ # over that, deleting all "false" attributes.
 256+ if( !empty( $attribs['class'] ) and !empty( $defaults['class'] ) ) {
 257+ $attribs['class'] .= ' '.$defaults['class'];
 258+ }
 259+ $ret = array();
 260+ foreach( array_merge( $defaults, $attribs ) as $key => $val ) {
 261+ if( $key != 'href' and $val !== false ) {
 262+ $ret[$key] = $val;
 263+ }
 264+ }
 265+ return $ret;
 266+ }
 267+
 268+ private function linkText( $target, $options ) {
 269+ # If the target is just a fragment, with no title, we return the frag-
 270+ # ment text. Otherwise, we return the title text itself.
 271+ if( $target->getPrefixedText() === '' and $target->getFragment() !== '' ) {
 272+ return htmlspecialchars( $target->getFragment() );
 273+ }
 274+ return htmlspecialchars( $target->getPrefixedText() );
 275+ }
 276+
 277+ /**
135278 * This function is a shortcut to makeLinkObj(Title::newFromText($title),...). Do not call
136279 * it if you already have a title object handy. See makeLinkObj for further documentation.
137280 *
@@ -465,6 +608,7 @@
466609
467610 /** Obsolete alias */
468611 function makeImage( $url, $alt = '' ) {
 612+ wfDeprecated( __METHOD__ );
469613 return $this->makeExternalImage( $url, $alt );
470614 }
471615
@@ -563,7 +707,7 @@
564708 global $wgContLang, $wgUser, $wgThumbLimits, $wgThumbUpright;
565709 if ( $file && !$file->allowInlineDisplay() ) {
566710 wfDebug( __METHOD__.': '.$title->getPrefixedDBkey()." does not allow inline display\n" );
567 - return $this->makeKnownLinkObj( $title );
 711+ return $this->link( $title );
568712 }
569713
570714 // Shortcuts
@@ -878,13 +1022,11 @@
8791023 function userLink( $userId, $userText ) {
8801024 $encName = htmlspecialchars( $userText );
8811025 if( $userId == 0 ) {
882 - $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
883 - return $this->makeKnownLinkObj( $contribsPage,
884 - $encName);
 1026+ $page = SpecialPage::getTitleFor( 'Contributions', $userText );
8851027 } else {
886 - $userPage = Title::makeTitle( NS_USER, $userText );
887 - return $this->makeLinkObj( $userPage, $encName );
 1028+ $page = Title::makeTitle( NS_USER, $userText );
8881029 }
 1030+ return $this->link( $page, $encName );
8891031 }
8901032
8911033 /**
@@ -908,15 +1050,16 @@
9091051 }
9101052 if( $userId ) {
9111053 // check if the user has an edit
 1054+ $attribs = array();
9121055 if( $redContribsWhenNoEdits ) {
9131056 $count = !is_null($edits) ? $edits : User::edits( $userId );
914 - $style = ($count == 0) ? " class='new'" : '';
915 - } else {
916 - $style = '';
 1057+ if( $count == 0 ) {
 1058+ $attribs['class'] = 'new';
 1059+ }
9171060 }
9181061 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
9191062
920 - $items[] = $this->makeKnownLinkObj( $contribsPage, wfMsgHtml( 'contribslink' ), '', '', '', '', $style );
 1063+ $items[] = $this->link( $contribsPage, wfMsgHtml( 'contribslink' ), $attribs );
9211064 }
9221065 if( $blockable && $wgUser->isAllowed( 'block' ) ) {
9231066 $items[] = $this->blockLink( $userId, $userText );
@@ -948,7 +1091,7 @@
9491092 */
9501093 function userTalkLink( $userId, $userText ) {
9511094 $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
952 - $userTalkLink = $this->makeLinkObj( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) );
 1095+ $userTalkLink = $this->link( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) );
9531096 return $userTalkLink;
9541097 }
9551098
@@ -960,8 +1103,7 @@
9611104 */
9621105 function blockLink( $userId, $userText ) {
9631106 $blockPage = SpecialPage::getTitleFor( 'Blockip', $userText );
964 - $blockLink = $this->makeKnownLinkObj( $blockPage,
965 - wfMsgHtml( 'blocklink' ) );
 1107+ $blockLink = $this->link( $blockPage, wfMsgHtml( 'blocklink' ) );
9661108 return $blockLink;
9671109 }
9681110
@@ -1074,7 +1216,7 @@
10751217 $sectionTitle = wfClone( $title );
10761218 $sectionTitle->mFragment = $section;
10771219 }
1078 - $link = $this->makeKnownLinkObj( $sectionTitle, wfMsgForContent( 'sectionlink' ) );
 1220+ $link = $this->link( $sectionTitle, wfMsgForContent( 'sectionlink' ) );
10791221 }
10801222 $auto = $link . $auto;
10811223 if( $pre ) {
@@ -1260,7 +1402,7 @@
12611403 public function editSectionLinkForOther( $title, $section ) {
12621404 wfDeprecated( __METHOD__ );
12631405 $title = Title::newFromText( $title );
1264 - return $this->doEditSectionLink( $title, $section, '', 'EditSectionLinkForOther' );
 1406+ return $this->doEditSectionLink( $title, $section );
12651407 }
12661408
12671409 /**
@@ -1268,9 +1410,14 @@
12691411 * @param $section Integer: section number.
12701412 * @param $hint Link String: title, or default if omitted or empty
12711413 */
1272 - public function editSectionLink( Title $nt, $section, $hint='' ) {
 1414+ public function editSectionLink( Title $nt, $section, $hint = '' ) {
12731415 wfDeprecated( __METHOD__ );
1274 - return $this->doEditSectionLink( $nt, $section, $hint, 'EditSectionLink' );
 1416+ if( $hint === '' ) {
 1417+ # No way to pass an actual empty $hint here! The new interface al-
 1418+ # lows this, so we have to do this for compatibility.
 1419+ $hint = null;
 1420+ }
 1421+ return $this->doEditSectionLink( $nt, $section, $hint );
12751422 }
12761423
12771424 /**
@@ -1285,22 +1432,24 @@
12861433 * and wrapped in the 'editsectionhint' message
12871434 * @return string HTML to use for edit link
12881435 */
1289 - public function doEditSectionLink( Title $nt, $section, $tooltip='' ) {
 1436+ public function doEditSectionLink( Title $nt, $section, $tooltip = null ) {
 1437+ $attribs = array();
 1438+ if( !is_null( $tooltip ) ) {
 1439+ $attribs['title'] = wfMsg( 'editsectionhint', $tooltip );
 1440+ }
 1441+ $url = $this->link( $nt, wfMsg('editsection'),
 1442+ $attribs,
 1443+ array( 'action' => 'edit', 'section' => $section ),
 1444+ array( 'noclasses', 'known' )
 1445+ );
 1446+
 1447+ # Run the old hook. This takes up half of the function . . . hopefully
 1448+ # we can rid of it someday.
12901449 $attribs = '';
12911450 if( $tooltip ) {
12921451 $attribs = wfMsgHtml( 'editsectionhint', htmlspecialchars( $tooltip ) );
12931452 $attribs = " title=\"$attribs\"";
12941453 }
1295 -
1296 - $url = $this->makeKnownLinkObj(
1297 - $nt,
1298 - htmlspecialchars(wfMsg('editsection')),
1299 - "action=edit&section=$section",
1300 - '', '', '', $attribs
1301 - );
1302 -
1303 - # Run the old hook. This takes up half of the function . . . hopefully
1304 - # we can rid of it someday.
13051454 $result = null;
13061455 wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $url, &$result ) );
13071456 if( !is_null( $result ) ) {
@@ -1388,14 +1537,14 @@
13891538 public function buildRollbackLink( $rev ) {
13901539 global $wgRequest, $wgUser;
13911540 $title = $rev->getTitle();
1392 - $extra = $wgRequest->getBool( 'bot' ) ? '&bot=1' : '';
1393 - $extra .= '&token=' . urlencode( $wgUser->editToken( array( $title->getPrefixedText(),
1394 - $rev->getUserText() ) ) );
1395 - return $this->makeKnownLinkObj(
1396 - $title,
1397 - wfMsgHtml( 'rollbacklink' ),
1398 - 'action=rollback&from=' . urlencode( $rev->getUserText() ) . $extra
1399 - );
 1541+ $query = array( 'action' => 'rollback' );
 1542+ if( $wgRequest->getBool( 'bot' ) ) {
 1543+ $query['bot'] = '1';
 1544+ }
 1545+ $query['token'] = $wgUser->editToken( array( $title->getPrefixedText(),
 1546+ $rev->getUserText() ) );
 1547+ return $this->link( $title, wfMsgHtml( 'rollbacklink' ), array(),
 1548+ $query, 'known' );
14001549 }
14011550
14021551 /**
@@ -1443,7 +1592,7 @@
14441593 } else {
14451594 $protected = '';
14461595 }
1447 - $outText .= '<li>' . $sk->makeLinkObj( $titleObj ) . ' ' . $protected . '</li>';
 1596+ $outText .= '<li>' . $sk->link( $titleObj ) . ' ' . $protected . '</li>';
14481597 }
14491598 $outText .= '</ul>';
14501599 }
@@ -1472,7 +1621,7 @@
14731622 $outText .= '</div><ul>';
14741623
14751624 foreach ( $hiddencats as $titleObj ) {
1476 - $outText .= '<li>' . $sk->makeKnownLinkObj( $titleObj ) . '</li>'; # If it's hidden, it must exist - no need to check with a LinkBatch
 1625+ $outText .= '<li>' . $sk->link( $titleObj, null, array(), array(), 'known' ) . '</li>'; # If it's hidden, it must exist - no need to check with a LinkBatch
14771626 }
14781627 $outText .= '</ul>';
14791628 }
Index: trunk/phase3/includes/Sanitizer.php
@@ -1301,7 +1301,7 @@
13021302 return $out;
13031303 }
13041304
1305 - static function cleanUrl( $url, $hostname=true ) {
 1305+ static function cleanUrl( $url ) {
13061306 # Normalize any HTML entities in input. They will be
13071307 # re-escaped by makeExternalLink().
13081308 $url = Sanitizer::decodeCharReferences( $url );

Status & tagging log