MediaWiki r18220 - Code Review

Jump to: navigation, search
Repository:MediaWiki
Revision:r18219‎ | r18220 (on ViewVC)‎ | r18221 >
Date:06:09, 8 December 2006
Author:tstarling
Status:old
Tags:
Comment:
* 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. 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.
Modified paths:

Diff [purge]

Index: trunk/phase3/skins/common/wikibits.js
===================================================================
--- trunk/phase3/skins/common/wikibits.js	(revision 18219)
+++ trunk/phase3/skins/common/wikibits.js	(revision 18220)
@@ -47,9 +47,12 @@
 		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
@@ -843,6 +846,18 @@
 	}
 }
 
+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
Index: trunk/phase3/includes/Xml.php
===================================================================
--- trunk/phase3/includes/Xml.php	(revision 18219)
+++ trunk/phase3/includes/Xml.php	(revision 18220)
@@ -255,6 +255,33 @@
 	}
 
 	/**
+	 * 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.
 	 *
Index: trunk/phase3/includes/Article.php
===================================================================
--- trunk/phase3/includes/Article.php	(revision 18219)
+++ trunk/phase3/includes/Article.php	(revision 18220)
@@ -691,6 +691,12 @@
 				$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 ) ) {
@@ -778,7 +784,7 @@
 				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>' );
@@ -2666,7 +2672,7 @@
 	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 '';
 	}
Index: trunk/phase3/includes/Linker.php
===================================================================
--- trunk/phase3/includes/Linker.php	(revision 18219)
+++ trunk/phase3/includes/Linker.php	(revision 18220)
@@ -213,22 +213,6 @@
 					$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 );
@@ -307,12 +291,7 @@
 					$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() );
Index: trunk/phase3/includes/OutputPage.php
===================================================================
--- trunk/phase3/includes/OutputPage.php	(revision 18219)
+++ trunk/phase3/includes/OutputPage.php	(revision 18220)
@@ -72,6 +72,16 @@
 	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; }
Index: trunk/phase3/includes/Title.php
===================================================================
--- trunk/phase3/includes/Title.php	(revision 18219)
+++ trunk/phase3/includes/Title.php	(revision 18220)
@@ -567,6 +567,19 @@
 		}
 	}
 
+	/**
+	 * 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
 #----------------------------------------------------------------------------
@@ -639,12 +652,25 @@
 	 */
 	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
 	 * @access public
@@ -803,9 +829,7 @@
 		}
 
 		# Finally, add the fragment.
-		if ( '' != $this->mFragment ) {
-			$url .= '#' . $this->mFragment;
-		}
+		$url .= $this->getFragmentForURL();
 
 		wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
 		return $url;
@@ -1442,41 +1466,41 @@
 
 		# 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 ) {
@@ -1486,12 +1510,12 @@
 					}
 
 					# 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;
 						}
@@ -1503,9 +1527,9 @@
 
 					# 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,
@@ -1513,25 +1537,24 @@
 			}
 			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;
 		}
 
@@ -1540,19 +1563,26 @@
 		 * 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;
 		}
 
@@ -1565,9 +1595,7 @@
 		 * site might be case-sensitive.
 		 */
 		if( $wgCapitalLinks && $this->mInterwiki == '') {
-			$t = $wgContLang->ucfirst( $r );
-		} else {
-			$t = $r;
+			$dbkey = $wgContLang->ucfirst( $dbkey );
 		}
 
 		/**
@@ -1575,27 +1603,40 @@
 		 * "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
 	 * @access public
Index: trunk/phase3/includes/DefaultSettings.php
===================================================================
--- trunk/phase3/includes/DefaultSettings.php	(revision 18219)
+++ trunk/phase3/includes/DefaultSettings.php	(revision 18220)
@@ -2334,4 +2334,18 @@
 	"$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',
+);
+
 ?>
Index: trunk/phase3/includes/Skin.php
===================================================================
--- trunk/phase3/includes/Skin.php	(revision 18219)
+++ trunk/phase3/includes/Skin.php	(revision 18220)
@@ -270,61 +270,62 @@
 		$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'] ) . '";
+	static function makeVariablesScript( $data ) {
+		global $wgJsMimeType;
 
-			var wgArticlePath = "' . Xml::escapeJsString( $data['articlepath'] ) . '";
-			var wgScriptPath = "' . Xml::escapeJsString( $data['scriptpath'] ) . '";
-			var wgServer = "' . Xml::escapeJsString( $data['serverurl'] ) . '";
+		$r = "<script type= \"$wgJsMimeType\"><!--\n";
+		foreach ( $data as $name => $value ) {
+			$encValue = Xml::encodeJsVar( $value );
+			$r .= "var $name = $encValue;\n";
+		}
+		$r .= "--></script>\n";
 
-			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>
-		';
-
 		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;
 		if ($wgUseSiteJs) {
Index: trunk/phase3/RELEASE-NOTES
===================================================================
--- trunk/phase3/RELEASE-NOTES	(revision 18219)
+++ trunk/phase3/RELEASE-NOTES	(revision 18220)
@@ -250,7 +250,11 @@
 * 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 ==
 
 * Bishnupriya Manipuri (bpy)
Personal tools
Namespaces
Variants
Views
Actions
Site
Support
Download
Development
Communication
Toolbox