Index: trunk/phase3/includes/Linker.php |
— | — | @@ -21,6 +21,7 @@ |
22 | 22 | * @deprecated |
23 | 23 | */ |
24 | 24 | function postParseLinkColour( $s = null ) { |
| 25 | + wfDeprecated( __METHOD__ ); |
25 | 26 | return null; |
26 | 27 | } |
27 | 28 | |
— | — | @@ -131,6 +132,148 @@ |
132 | 133 | } |
133 | 134 | |
134 | 135 | /** |
| 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 | + /** |
135 | 278 | * This function is a shortcut to makeLinkObj(Title::newFromText($title),...). Do not call |
136 | 279 | * it if you already have a title object handy. See makeLinkObj for further documentation. |
137 | 280 | * |
— | — | @@ -465,6 +608,7 @@ |
466 | 609 | |
467 | 610 | /** Obsolete alias */ |
468 | 611 | function makeImage( $url, $alt = '' ) { |
| 612 | + wfDeprecated( __METHOD__ ); |
469 | 613 | return $this->makeExternalImage( $url, $alt ); |
470 | 614 | } |
471 | 615 | |
— | — | @@ -563,7 +707,7 @@ |
564 | 708 | global $wgContLang, $wgUser, $wgThumbLimits, $wgThumbUpright; |
565 | 709 | if ( $file && !$file->allowInlineDisplay() ) { |
566 | 710 | wfDebug( __METHOD__.': '.$title->getPrefixedDBkey()." does not allow inline display\n" ); |
567 | | - return $this->makeKnownLinkObj( $title ); |
| 711 | + return $this->link( $title ); |
568 | 712 | } |
569 | 713 | |
570 | 714 | // Shortcuts |
— | — | @@ -878,13 +1022,11 @@ |
879 | 1023 | function userLink( $userId, $userText ) { |
880 | 1024 | $encName = htmlspecialchars( $userText ); |
881 | 1025 | if( $userId == 0 ) { |
882 | | - $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText ); |
883 | | - return $this->makeKnownLinkObj( $contribsPage, |
884 | | - $encName); |
| 1026 | + $page = SpecialPage::getTitleFor( 'Contributions', $userText ); |
885 | 1027 | } else { |
886 | | - $userPage = Title::makeTitle( NS_USER, $userText ); |
887 | | - return $this->makeLinkObj( $userPage, $encName ); |
| 1028 | + $page = Title::makeTitle( NS_USER, $userText ); |
888 | 1029 | } |
| 1030 | + return $this->link( $page, $encName ); |
889 | 1031 | } |
890 | 1032 | |
891 | 1033 | /** |
— | — | @@ -908,15 +1050,16 @@ |
909 | 1051 | } |
910 | 1052 | if( $userId ) { |
911 | 1053 | // check if the user has an edit |
| 1054 | + $attribs = array(); |
912 | 1055 | if( $redContribsWhenNoEdits ) { |
913 | 1056 | $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 | + } |
917 | 1060 | } |
918 | 1061 | $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText ); |
919 | 1062 | |
920 | | - $items[] = $this->makeKnownLinkObj( $contribsPage, wfMsgHtml( 'contribslink' ), '', '', '', '', $style ); |
| 1063 | + $items[] = $this->link( $contribsPage, wfMsgHtml( 'contribslink' ), $attribs ); |
921 | 1064 | } |
922 | 1065 | if( $blockable && $wgUser->isAllowed( 'block' ) ) { |
923 | 1066 | $items[] = $this->blockLink( $userId, $userText ); |
— | — | @@ -948,7 +1091,7 @@ |
949 | 1092 | */ |
950 | 1093 | function userTalkLink( $userId, $userText ) { |
951 | 1094 | $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText ); |
952 | | - $userTalkLink = $this->makeLinkObj( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) ); |
| 1095 | + $userTalkLink = $this->link( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) ); |
953 | 1096 | return $userTalkLink; |
954 | 1097 | } |
955 | 1098 | |
— | — | @@ -960,8 +1103,7 @@ |
961 | 1104 | */ |
962 | 1105 | function blockLink( $userId, $userText ) { |
963 | 1106 | $blockPage = SpecialPage::getTitleFor( 'Blockip', $userText ); |
964 | | - $blockLink = $this->makeKnownLinkObj( $blockPage, |
965 | | - wfMsgHtml( 'blocklink' ) ); |
| 1107 | + $blockLink = $this->link( $blockPage, wfMsgHtml( 'blocklink' ) ); |
966 | 1108 | return $blockLink; |
967 | 1109 | } |
968 | 1110 | |
— | — | @@ -1074,7 +1216,7 @@ |
1075 | 1217 | $sectionTitle = wfClone( $title ); |
1076 | 1218 | $sectionTitle->mFragment = $section; |
1077 | 1219 | } |
1078 | | - $link = $this->makeKnownLinkObj( $sectionTitle, wfMsgForContent( 'sectionlink' ) ); |
| 1220 | + $link = $this->link( $sectionTitle, wfMsgForContent( 'sectionlink' ) ); |
1079 | 1221 | } |
1080 | 1222 | $auto = $link . $auto; |
1081 | 1223 | if( $pre ) { |
— | — | @@ -1260,7 +1402,7 @@ |
1261 | 1403 | public function editSectionLinkForOther( $title, $section ) { |
1262 | 1404 | wfDeprecated( __METHOD__ ); |
1263 | 1405 | $title = Title::newFromText( $title ); |
1264 | | - return $this->doEditSectionLink( $title, $section, '', 'EditSectionLinkForOther' ); |
| 1406 | + return $this->doEditSectionLink( $title, $section ); |
1265 | 1407 | } |
1266 | 1408 | |
1267 | 1409 | /** |
— | — | @@ -1268,9 +1410,14 @@ |
1269 | 1411 | * @param $section Integer: section number. |
1270 | 1412 | * @param $hint Link String: title, or default if omitted or empty |
1271 | 1413 | */ |
1272 | | - public function editSectionLink( Title $nt, $section, $hint='' ) { |
| 1414 | + public function editSectionLink( Title $nt, $section, $hint = '' ) { |
1273 | 1415 | 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 ); |
1275 | 1422 | } |
1276 | 1423 | |
1277 | 1424 | /** |
— | — | @@ -1285,22 +1432,24 @@ |
1286 | 1433 | * and wrapped in the 'editsectionhint' message |
1287 | 1434 | * @return string HTML to use for edit link |
1288 | 1435 | */ |
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. |
1290 | 1449 | $attribs = ''; |
1291 | 1450 | if( $tooltip ) { |
1292 | 1451 | $attribs = wfMsgHtml( 'editsectionhint', htmlspecialchars( $tooltip ) ); |
1293 | 1452 | $attribs = " title=\"$attribs\""; |
1294 | 1453 | } |
1295 | | - |
1296 | | - $url = $this->makeKnownLinkObj( |
1297 | | - $nt, |
1298 | | - htmlspecialchars(wfMsg('editsection')), |
1299 | | - "action=edit§ion=$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. |
1305 | 1454 | $result = null; |
1306 | 1455 | wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $url, &$result ) ); |
1307 | 1456 | if( !is_null( $result ) ) { |
— | — | @@ -1388,14 +1537,14 @@ |
1389 | 1538 | public function buildRollbackLink( $rev ) { |
1390 | 1539 | global $wgRequest, $wgUser; |
1391 | 1540 | $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' ); |
1400 | 1549 | } |
1401 | 1550 | |
1402 | 1551 | /** |
— | — | @@ -1443,7 +1592,7 @@ |
1444 | 1593 | } else { |
1445 | 1594 | $protected = ''; |
1446 | 1595 | } |
1447 | | - $outText .= '<li>' . $sk->makeLinkObj( $titleObj ) . ' ' . $protected . '</li>'; |
| 1596 | + $outText .= '<li>' . $sk->link( $titleObj ) . ' ' . $protected . '</li>'; |
1448 | 1597 | } |
1449 | 1598 | $outText .= '</ul>'; |
1450 | 1599 | } |
— | — | @@ -1472,7 +1621,7 @@ |
1473 | 1622 | $outText .= '</div><ul>'; |
1474 | 1623 | |
1475 | 1624 | 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 |
1477 | 1626 | } |
1478 | 1627 | $outText .= '</ul>'; |
1479 | 1628 | } |
Index: trunk/phase3/includes/Sanitizer.php |
— | — | @@ -1301,7 +1301,7 @@ |
1302 | 1302 | return $out; |
1303 | 1303 | } |
1304 | 1304 | |
1305 | | - static function cleanUrl( $url, $hostname=true ) { |
| 1305 | + static function cleanUrl( $url ) { |
1306 | 1306 | # Normalize any HTML entities in input. They will be |
1307 | 1307 | # re-escaped by makeExternalLink(). |
1308 | 1308 | $url = Sanitizer::decodeCharReferences( $url ); |