* Added a configuration variable allowing the "break out of framesets" feature to be switched on and off. Off by default -- there's all sorts of flashy frameset gadgets around, outright abuse is more rare, especially for small default installs.
* Refactored URL fragment handling.
* Made Title::secureAndSplit() slightly more legible.
* Refactored makeGlobalVariablesScript() -- it's not particularly convenient or maintainable to pass all global variables through the template array. The array is there for BC, not flexibility.
* Enable QueryPage classes to override list formatting
* (bug 5485) Show number of intervening revisions in diff view
* (bug 8100) Fix XHTML validity in Taiwanese localization
+* Added redirect to section feature. Use it wisely.
+* Added a configuration variable allowing the "break out of framesets" feature
+ to be switched on and off ($wgBreakFrames). Off by default.
+
== Languages updated ==
$redir = $sk->makeKnownLinkObj( $this->mRedirectedFrom, '', 'redirect=no' );
$s = wfMsg( 'redirectedfrom', $redir );
$wgOut->setSubtitle( $s );
+
+ // Set the fragment if one was specified in the redirect
+ if ( strval( $this->mTitle->getFragment() ) != '' ) {
+ $fragment = Xml::escapeJsString( $this->mTitle->getFragmentForURL() );
+ $wgOut->addInlineScript( "redirectToFragment(\"$fragment\");" );
+ }
$wasRedirected = true;
}
} elseif ( !empty( $rdfrom ) ) {
if( !$wasRedirected && $this->isCurrent() ) {
$wgOut->setSubtitle( wfMsgHtml( 'redirectpagesub' ) );
}
- $link = $sk->makeLinkObj( $rt );
+ $link = $sk->makeLinkObj( $rt, $rt->getFullText() );
$wgOut->addHTML( '<img src="'.$imageUrl.'" alt="#REDIRECT" />' .
'<span class="redirectText">'.$link.'</span>' );
public static function getRedirectAutosummary( $text ) {
$rt = Title::newFromRedirect( $text );
if( is_object( $rt ) )
- return wfMsgForContent( 'autoredircomment', $rt->getPrefixedText() );
+ return wfMsgForContent( 'autoredircomment', $rt->getFullText() );
else
return '';
}
"$IP/maintenance/parserTests.txt",
);
+/**
+ * Break out of framesets. This can be used to prevent external sites from
+ * framing your site with ads.
+ */
+$wgBreakFrames = false;
+
+/**
+ * Break frameset exception list
+ */
+$wgBreakFramesExceptions = array(
+ 'babelfish.altavista.com',
+ 'translate.google.com',
+);
+
?>
$trail = $m[2];
}
}
-
- # Check for anchors, normalize the anchor
-
- $parts = explode( '#', $u, 2 );
- if ( count( $parts ) == 2 ) {
- $anchor = urlencode( Sanitizer::decodeCharReferences( str_replace(' ', '_', $parts[1] ) ) );
- $replacearray = array(
- '%3A' => ':',
- '%' => '.'
- );
- $u = $parts[0] . '#' .
- str_replace( array_keys( $replacearray ),
- array_values( $replacearray ),
- $anchor );
- }
-
$t = "<a href=\"{$u}\"{$style}>{$text}{$inside}</a>";
wfProfileOut( $fname );
$text = htmlspecialchars( $nt->getFragment() );
}
}
- $anchor = urlencode( Sanitizer::decodeCharReferences( str_replace( ' ', '_', $nt->getFragment() ) ) );
- $replacearray = array(
- '%3A' => ':',
- '%' => '.'
- );
- $u .= '#' . str_replace(array_keys($replacearray),array_values($replacearray),$anchor);
+ $u .= $nt->getFragmentForURL();
}
if ( $text == '' ) {
$text = htmlspecialchars( $nt->getPrefixedText() );
function addMeta( $name, $val ) { array_push( $this->mMetatags, array( $name, $val ) ); }
function addKeyword( $text ) { array_push( $this->mKeywords, $text ); }
function addScript( $script ) { $this->mScripts .= $script; }
+
+ /**
+ * Add a self-contained script tag with the given contents
+ * @param string $script JavaScript text, no <script> tags
+ */
+ function addInlineScript( $script ) {
+ global $wgJsMimeType;
+ $this->mScripts .= "<script type=\"$wgJsMimeType\"><!--\n$script\n--></script>";
+ }
+
function getScript() { return $this->mScripts; }
function setETag($tag) { $this->mETag = $tag; }
$out->out( "\n</body></html>" );
}
- static function makeGlobalVariablesScript( $data ) {
- $r = '<script type= "' . $data['jsmimetype'] . '">
- var skin = "' . Xml::escapeJsString( $data['skinname'] ) . '";
- var stylepath = "' . Xml::escapeJsString( $data['stylepath'] ) . '";
-
- var wgArticlePath = "' . Xml::escapeJsString( $data['articlepath'] ) . '";
- var wgScriptPath = "' . Xml::escapeJsString( $data['scriptpath'] ) . '";
- var wgServer = "' . Xml::escapeJsString( $data['serverurl'] ) . '";
-
- var wgCanonicalNamespace = "' . Xml::escapeJsString( $data['nscanonical'] ) . '";
- var wgNamespaceNumber = ' . (int)$data['nsnumber'] . ';
- var wgPageName = "' . Xml::escapeJsString( $data['titleprefixeddbkey'] ) . '";
- var wgTitle = "' . Xml::escapeJsString( $data['titletext'] ) . '";
- var wgArticleId = ' . (int)$data['articleid'] . ';
- var wgCurRevisionId = ' . ( int ) $data['currevisionid'] . ';
- var wgIsArticle = ' . ( $data['isarticle'] ? 'true' : 'false' ) . ';
-
- var wgUserName = ' . ( $data['username'] == NULL ? 'null' : ( '"' . Xml::escapeJsString( $data['username'] ) . '"' ) ) . ';
- var wgUserLanguage = "' . Xml::escapeJsString( $data['userlang'] ) . '";
- var wgContentLanguage = "' . Xml::escapeJsString( $data['lang'] ) . '";
- </script>
- ';
+ static function makeVariablesScript( $data ) {
+ global $wgJsMimeType;
+
+ $r = "<script type= \"$wgJsMimeType\"><!--\n";
+ foreach ( $data as $name => $value ) {
+ $encValue = Xml::encodeJsVar( $value );
+ $r .= "var $name = $encValue;\n";
+ }
+ $r .= "--></script>\n";
return $r;
}
- function getHeadScripts() {
- global $wgStylePath, $wgUser, $wgAllowUserJs, $wgJsMimeType, $wgStyleVersion;
+ /**
+ * Make a <script> tag containing global variables
+ * @param array $data Associative array containing one element:
+ * skinname => the skin name
+ * The odd calling convention is for backwards compatibility
+ */
+ static function makeGlobalVariablesScript( $data ) {
+ global $wgStylePath, $wgUser;
global $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang, $wgLang;
global $wgTitle, $wgCanonicalNamespaceNames, $wgOut, $wgArticle;
+ global $wgBreakFrames, $wgBreakFramesExceptions;
$ns = $wgTitle->getNamespace();
$nsname = isset( $wgCanonicalNamespaceNames[ $ns ] ) ? $wgCanonicalNamespaceNames[ $ns ] : $wgTitle->getNsText();
$vars = array(
- 'jsmimetype' => $wgJsMimeType,
- 'skinname' => $this->getSkinName(),
+ 'skin' => $data['skinname'],
'stylepath' => $wgStylePath,
- 'articlepath' => $wgArticlePath,
- 'scriptpath' => $wgScriptPath,
- 'serverurl' => $wgServer,
- 'nscanonical' => $nsname,
- 'nsnumber' => $wgTitle->getNamespace(),
- 'titleprefixeddbkey' => $wgTitle->getPrefixedDBKey(),
- 'titletext' => $wgTitle->getText(),
- 'articleid' => $wgTitle->getArticleId(),
- 'currevisionid' => isset( $wgArticle ) ? $wgArticle->getLatest() : 0,
- 'isarticle' => $wgOut->isArticle(),
- 'username' => $wgUser->isAnon() ? NULL : $wgUser->getName(),
- 'userlang' => $wgLang->getCode(),
- 'lang' => $wgContLang->getCode(),
+ 'wgArticlePath' => $wgArticlePath,
+ 'wgScriptPath' => $wgScriptPath,
+ 'wgServer' => $wgServer,
+ 'wgCanonicalNamespace' => $nsname,
+ 'wgNamespaceNumber' => $wgTitle->getNamespace(),
+ 'wgPageName' => $wgTitle->getPrefixedDBKey(),
+ 'wgTitle' => $wgTitle->getText(),
+ 'wgArticleId' => $wgTitle->getArticleId(),
+ 'wgIsArticle' => $wgOut->isArticle(),
+ 'wgUserName' => $wgUser->isAnon() ? NULL : $wgUser->getName(),
+ 'wgUserLanguage' => $wgLang->getCode(),
+ 'wgContentLanguage' => $wgContLang->getCode(),
+ 'wgBreakFrames' => $wgBreakFrames,
+ 'wgBreakFramesExceptions' => $wgBreakFramesExceptions,
+ 'wgCurRevisionId' => isset( $wgArticle ) ? $wgArticle->getLatest() : 0,
);
- $r = self::makeGlobalVariablesScript( $vars );
+ return self::makeVariablesScript( $vars );
+ }
+
+ function getHeadScripts() {
+ global $wgStylePath, $wgUser, $wgAllowUserJs, $wgJsMimeType, $wgStyleVersion;
+
+ $r = self::makeGlobalVariablesScript( array( 'skinname' => $this->getSkinName() ) );
$r .= "<script type=\"{$wgJsMimeType}\" src=\"{$wgStylePath}/common/wikibits.js?$wgStyleVersion\"></script>\n";
global $wgUseSiteJs;
}
}
+ /**
+ * Escape a text fragment, say from a link, for a URL
+ */
+ static function escapeFragmentForURL( $fragment ) {
+ $fragment = trim( str_replace( ' ', '_', $fragment ), '_' );
+ $fragment = urlencode( Sanitizer::decodeCharReferences( $fragment ) );
+ $replaceArray = array(
+ '%3A' => ':',
+ '%' => '.'
+ );
+ return strtr( $fragment, $replaceArray );
+ }
+
#----------------------------------------------------------------------------
# Other stuff
#----------------------------------------------------------------------------
*/
function getInterwiki() { return $this->mInterwiki; }
/**
- * Get the Title fragment (i.e. the bit after the #)
+ * Get the Title fragment (i.e. the bit after the #) in text form
* @return string
* @access public
*/
function getFragment() { return $this->mFragment; }
+ /**
+ * Get the fragment in URL form, including the "#" character if there is one
+ *
+ * @return string
+ * @access public
+ */
+ function getFragmentForURL() {
+ if ( $this->mFragment == '' ) {
+ return '';
+ } else {
+ return '#' . Title::escapeFragmentForURL( $this->mFragment );
+ }
+ }
/**
* Get the default namespace index, for when there is no namespace
* @return int
}
# Finally, add the fragment.
- if ( '' != $this->mFragment ) {
- $url .= '#' . $this->mFragment;
- }
+ $url .= $this->getFragmentForURL();
wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
return $url;
# Clean up whitespace
#
- $t = preg_replace( '/[ _]+/', '_', $this->mDbkeyform );
- $t = trim( $t, '_' );
+ $dbkey = preg_replace( '/[ _]+/', '_', $this->mDbkeyform );
+ $dbkey = trim( $dbkey, '_' );
- if ( '' == $t ) {
+ if ( '' == $dbkey ) {
return false;
}
- if( false !== strpos( $t, UTF8_REPLACEMENT ) ) {
+ if( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
# Contained illegal UTF-8 sequences or forbidden Unicode chars.
return false;
}
- $this->mDbkeyform = $t;
+ $this->mDbkeyform = $dbkey;
# Initial colon indicates main namespace rather than specified default
# but should not create invalid {ns,title} pairs such as {0,Project:Foo}
- if ( ':' == $t{0} ) {
+ if ( ':' == $dbkey{0} ) {
$this->mNamespace = NS_MAIN;
- $t = substr( $t, 1 ); # remove the colon but continue processing
+ $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
}
# Namespace or interwiki prefix
$firstPass = true;
do {
$m = array();
- if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $t, $m ) ) {
+ if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $dbkey, $m ) ) {
$p = $m[1];
$lowerNs = $wgContLang->lc( $p );
if ( $ns = Namespace::getCanonicalIndex( $lowerNs ) ) {
# Canonical namespace
- $t = $m[2];
+ $dbkey = $m[2];
$this->mNamespace = $ns;
} elseif ( $ns = $wgContLang->getNsIndex( $lowerNs )) {
# Ordinary namespace
- $t = $m[2];
+ $dbkey = $m[2];
$this->mNamespace = $ns;
} elseif( $this->getInterwikiLink( $p ) ) {
if( !$firstPass ) {
}
# Interwiki link
- $t = $m[2];
+ $dbkey = $m[2];
$this->mInterwiki = $wgContLang->lc( $p );
# Redundant interwiki prefix to the local wiki
if ( 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) ) {
- if( $t == '' ) {
+ if( $dbkey == '' ) {
# Can't have an empty self-link
return false;
}
# If there's an initial colon after the interwiki, that also
# resets the default namespace
- if ( $t !== '' && $t[0] == ':' ) {
+ if ( $dbkey !== '' && $dbkey[0] == ':' ) {
$this->mNamespace = NS_MAIN;
- $t = substr( $t, 1 );
+ $dbkey = substr( $dbkey, 1 );
}
}
# If there's no recognized interwiki or namespace,
}
break;
} while( true );
- $r = $t;
# We already know that some pages won't be in the database!
#
if ( '' != $this->mInterwiki || NS_SPECIAL == $this->mNamespace ) {
$this->mArticleID = 0;
}
- $f = strstr( $r, '#' );
- if ( false !== $f ) {
- $this->mFragment = substr( $f, 1 );
- $r = substr( $r, 0, strlen( $r ) - strlen( $f ) );
+ $fragment = strstr( $dbkey, '#' );
+ if ( false !== $fragment ) {
+ $this->setFragment( $fragment );
+ $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
# remove whitespace again: prevents "Foo_bar_#"
# becoming "Foo_bar_"
- $r = preg_replace( '/_*$/', '', $r );
+ $dbkey = preg_replace( '/_*$/', '', $dbkey );
}
# Reject illegal characters.
#
- if( preg_match( $rxTc, $r ) ) {
+ if( preg_match( $rxTc, $dbkey ) ) {
return false;
}
* often be unreachable due to the way web browsers deal
* with 'relative' URLs. Forbid them explicitly.
*/
- if ( strpos( $r, '.' ) !== false &&
- ( $r === '.' || $r === '..' ||
- strpos( $r, './' ) === 0 ||
- strpos( $r, '../' ) === 0 ||
- strpos( $r, '/./' ) !== false ||
- strpos( $r, '/../' ) !== false ) )
+ if ( strpos( $dbkey, '.' ) !== false &&
+ ( $dbkey === '.' || $dbkey === '..' ||
+ strpos( $dbkey, './' ) === 0 ||
+ strpos( $dbkey, '../' ) === 0 ||
+ strpos( $dbkey, '/./' ) !== false ||
+ strpos( $dbkey, '/../' ) !== false ) )
{
return false;
}
- # We shouldn't need to query the DB for the size.
- #$maxSize = $dbr->textFieldSize( 'page', 'page_title' );
- if ( strlen( $r ) > 255 ) {
+ /**
+ * Limit the size of titles to 255 bytes.
+ * This is typically the size of the underlying database field.
+ * We make an exception for special pages, which don't need to be stored
+ * in the database, and may edge over 255 bytes due to subpage syntax
+ * for long titles, e.g. [[Special:Block/Long name]]
+ */
+ if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) ||
+ strlen( $dbkey ) > 512 )
+ {
return false;
}
* site might be case-sensitive.
*/
if( $wgCapitalLinks && $this->mInterwiki == '') {
- $t = $wgContLang->ucfirst( $r );
- } else {
- $t = $r;
+ $dbkey = $wgContLang->ucfirst( $dbkey );
}
/**
* "empty" local links can only be self-links
* with a fragment identifier.
*/
- if( $t == '' &&
+ if( $dbkey == '' &&
$this->mInterwiki == '' &&
$this->mNamespace != NS_MAIN ) {
return false;
}
// Any remaining initial :s are illegal.
- if ( $t !== '' && ':' == $t{0} ) {
+ if ( $dbkey !== '' && ':' == $dbkey{0} ) {
return false;
}
# Fill fields
- $this->mDbkeyform = $t;
- $this->mUrlform = wfUrlencode( $t );
+ $this->mDbkeyform = $dbkey;
+ $this->mUrlform = wfUrlencode( $dbkey );
- $this->mTextform = str_replace( '_', ' ', $t );
+ $this->mTextform = str_replace( '_', ' ', $dbkey );
return true;
}
+ /**
+ * Set the fragment for this title
+ * This is kind of bad, since except for this rarely-used function, Title objects
+ * are immutable. The reason this is here is because it's better than setting the
+ * members directly, which is what Linker::formatComment was doing previously.
+ *
+ * @param string $fragment text
+ * @access kind of public
+ */
+ function setFragment( $fragment ) {
+ $this->mFragment = trim( str_replace( '_', ' ', substr( $fragment, 1 ) ), ' ' );
+ }
+
/**
* Get a Title object associated with the talk page of this article
* @return Title the object for the talk page
return strtr( $string, $pairs );
}
+ /**
+ * Encode a variable of unknown type to JavaScript.
+ * Doesn't support hashtables just yet.
+ */
+ public static function encodeJsVar( $value ) {
+ if ( is_bool( $value ) ) {
+ $s = $value ? 'true' : 'false';
+ } elseif ( is_null( $value ) ) {
+ $s = 'null';
+ } elseif ( is_int( $value ) ) {
+ $s = $value;
+ } elseif ( is_array( $value ) ) {
+ $s = '[';
+ foreach ( $value as $name => $elt ) {
+ if ( $s != '[' ) {
+ $s .= ', ';
+ }
+ $s .= self::encodeJsVar( $elt );
+ }
+ $s .= ']';
+ } else {
+ $s = '"' . self::escapeJsString( $value ) . '"';
+ }
+ return $s;
+ }
+
+
/**
* Check if a string is well-formed XML.
* Must include the surrounding tag.
document.write('<link rel="stylesheet" type="text/css" href="'+stylepath+'/'+skin+'/KHTMLFixes.css">');
}
}
-// Un-trap us from framesets
-if (window.top != window) {
- window.top.location = window.location;
+
+if (wgBreakFrames) {
+ // Un-trap us from framesets
+ if (window.top != window && wgBreakFramesExceptions.indexOf(window.top.location.hostname) == -1) {
+ window.top.location = window.location;
+ }
}
// for enhanced RecentChanges
}
}
+function redirectToFragment(fragment) {
+ if (is_gecko) {
+ // Mozilla needs to wait until after load, otherwise the window doesn't scroll
+ addOnloadHook(function () {
+ if (window.location.hash == "")
+ window.location.hash = fragment;
+ });
+ } else {
+ if (window.location.hash == "")
+ window.location.hash = fragment;
+ }
+}
function runOnloadHook() {
// don't run anything below this for non-dom browsers