MediaWiki r19095 - Code Review

Jump to: navigation, search
Repository:MediaWiki
Revision:r19094‎ | r19095 (on ViewVC)‎ | r19096 >
Date:23:32, 10 January 2007
Author:werdna
Status:old
Tags:
Comment:
Merge from branches/werdna/restrictions-separation (forked at r18959): * Branch page_restrictions column out into its own table, also creating a 'cascading protection' feature, which automagically disallows edits to pages transcluded into a page protected with this new option. Various other code tidiness fixes and refactoring in the log messages of branches/werdna/restrictions-separation. REQUIRES DATABASE SCHEMA UPGRADE
Modified paths:

Diff [purge]

Index: trunk/phase3/maintenance/updaters.inc
===================================================================
--- trunk/phase3/maintenance/updaters.inc	(revision 19094)
+++ trunk/phase3/maintenance/updaters.inc	(revision 19095)
@@ -37,6 +37,7 @@
 	array( 'filearchive',   'patch-filearchive.sql' ),
 	array( 'redirect',      'patch-redirect.sql' ),
 	array( 'querycachetwo',	'patch-querycachetwo.sql' ),
+#	array( 'page_restrictions', 'patch-page_restrictions.sql' ),
 );
 
 $wgNewFields = array(
@@ -905,6 +906,8 @@
 	
 	do_backlinking_indices_update(); flush();
 
+	do_restrictions_update(); flush ();
+
 	echo "Deleting old default messages..."; flush();
 	deleteDefaultMessages();
 	echo "Done\n"; flush();
@@ -925,6 +928,70 @@
 	}
 }
 
+function do_restrictions_update() {
+	# Adding page_restrictions table, obsoleting page.page_restrictions.
+	#  Migrating old restrictions to new table
+	# -- Andrew Garrett, January 2007.
+
+	global $wgDatabase;
+
+	$name = 'page_restrictions';
+	$patch = 'patch-page_restrictions.sql';
+
+	if ( $wgDatabase->tableExists( $name ) ) {
+		echo "...$name table already exists.\n";
+	} else {
+		echo "Creating $name table...";
+		dbsource( archive($patch), $wgDatabase );
+		echo "ok\n";
+
+		echo "Migrating old restrictions to new table...";
+
+		$res = $wgDatabase->select( 'page', array( 'page_id', 'page_restrictions' ), array("page_restrictions!=''", "page_restrictions!='edit=:move='"), __METHOD__ );
+
+		$count = 0;
+
+		while ($row = $wgDatabase->fetchObject($res) ) {
+			$count = ($count + 1) % 100;
+
+			if ($count == 0) {
+				if ( function_exists( 'wfWaitForSlaves' ) ) {
+					wfWaitForSlaves( 10 );
+				} else {
+					sleep( 1 );
+				}
+			}
+
+			# Figure out what the restrictions are..
+			$id = $row->page_id;
+			$flatterrestrictions = $row->page_restrictions;
+
+			$flatrestrictions = explode( ':', $flatterrestrictions );
+
+			$restrictions = array ();
+
+			foreach( $flatrestrictions as $restriction ) {
+				$thisrestriction = explode('=', $restriction);
+
+				$restriction_type = $thisrestriction[0];
+				$restriction_level = $thisrestriction[1];
+
+				$restrictions[$restriction_type] = $restriction_level;
+
+				if ($restriction_level != '') {
+
+					$wgDatabase->insert( 'page_restrictions', array ( 'pr_page' => $id,
+												'pr_type' => $restriction_type,
+												'pr_level' => $restriction_level,
+												'pr_cascade' => 0 ), __METHOD );
+				}
+			}
+		}
+		print "ok\n";
+	}
+	
+}
+
 function do_postgres_updates() {
 	global $wgDatabase, $wgVersion, $wgDBmwschema;
 
Index: trunk/phase3/maintenance/tables.sql
===================================================================
--- trunk/phase3/maintenance/tables.sql	(revision 19094)
+++ trunk/phase3/maintenance/tables.sql	(revision 19095)
@@ -1075,4 +1075,27 @@
 
 ) TYPE=InnoDB;
 
+--- Used for storing page restrictions (i.e. protection levels)
+CREATE TABLE /*$wgDBprefix*/page_restrictions (
+  -- Page to apply restrictions to (Foreign Key to page).
+  pr_page int(8) NOT NULL,
+  -- The protection type (edit, move, etc)
+  pr_type varchar(255) NOT NULL,
+  -- The protection level (Sysop, autoconfirmed, etc)
+  pr_level varchar(255) NOT NULL,
+  -- Whether or not to cascade the protection down to pages transcluded.
+  pr_cascade tinyint(4) NOT NULL,
+  -- Field for future support of per-user restriction.
+  pr_user int(8) NULL,
+  -- Field for future support of time-limited protection.
+  pr_expiry char(14) binary NULL,
+
+  PRIMARY KEY  (pr_page,pr_type),
+
+  KEY pr_page (pr_page),
+  KEY pr_typelevel (pr_type,pr_level),
+  KEY pr_level (pr_level),
+  KEY pr_cascade (pr_cascade)
+) TYPE=InnoDB;
+
 -- vim: sw=2 sts=2 et
Index: trunk/phase3/includes/ProtectionForm.php
===================================================================
--- trunk/phase3/includes/ProtectionForm.php	(revision 19094)
+++ trunk/phase3/includes/ProtectionForm.php	(revision 19095)
@@ -25,6 +25,7 @@
 class ProtectionForm {
 	var $mRestrictions = array();
 	var $mReason = '';
+	var $mCascade = false;
 
 	function ProtectionForm( &$article ) {
 		global $wgRequest, $wgUser;
@@ -38,6 +39,7 @@
 				// but the db allows multiples separated by commas.
 				$this->mRestrictions[$action] = implode( '', $this->mTitle->getRestrictions( $action ) );
 			}
+			$this->mCascade = $this->mTitle->getRestrictionCascadingFlags() & 1;
 		}
 
 		// The form will be available in read-only to show levels.
@@ -48,6 +50,7 @@
 
 		if( $wgRequest->wasPosted() ) {
 			$this->mReason = $wgRequest->getText( 'mwProtect-reason' );
+			$this->mCascade = $wgRequest->getBool( 'mwProtect-cascade' );
 			foreach( $wgRestrictionTypes as $action ) {
 				$val = $wgRequest->getVal( "mwProtect-level-$action" );
 				if( isset( $val ) && in_array( $val, $wgRestrictionLevels ) ) {
@@ -101,7 +104,7 @@
 			throw new FatalError( wfMsg( 'sessionfailure' ) );
 		}
 
-		$ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $this->mReason );
+		$ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $this->mReason, $this->mCascade );
 		if( !$ok ) {
 			throw new FatalError( "Unknown error at restriction save time." );
 		}
@@ -148,6 +151,11 @@
 		$out .= "</tbody>\n";
 		$out .= "</table>\n";
 
+		global $wgEnableCascadingProtection;
+
+		if ($wgEnableCascadingProtection)
+			$out .= $this->buildCascadeInput();
+
 		if( !$this->disabled ) {
 			$out .= "<table>\n";
 			$out .= "<tbody>\n";
@@ -205,6 +213,13 @@
 				'id' => $id ) );
 	}
 
+	function buildCascadeInput() {
+		$id = 'mwProtect-cascade';
+		$ci = wfCheckLabel( wfMsg( 'protect-cascade' ), $id, $id, $this->mCascade, array ());
+		
+		return $ci;
+	}
+
 	function buildSubmit() {
 		return wfElement( 'input', array(
 			'type' => 'submit',
Index: trunk/phase3/includes/Article.php
===================================================================
--- trunk/phase3/includes/Article.php	(revision 19094)
+++ trunk/phase3/includes/Article.php	(revision 19095)
@@ -252,7 +252,6 @@
 				'page_id',
 				'page_namespace',
 				'page_title',
-				'page_restrictions',
 				'page_counter',
 				'page_is_redirect',
 				'page_is_new',
@@ -305,8 +304,6 @@
 			$lc->addGoodLinkObj( $data->page_id, $this->mTitle );
 
 			$this->mTitle->mArticleID = $data->page_id;
-			$this->mTitle->loadRestrictions( $data->page_restrictions );
-			$this->mTitle->mRestrictionsLoaded = true;
 
 			$this->mCounter     = $data->page_counter;
 			$this->mTouched     = wfTimestamp( TS_MW, $data->page_touched );
@@ -795,7 +792,7 @@
 				$wgOut->addParserOutputNoText( $parseout );
 			} else if ( $pcache ) {
 				# Display content and save to parser cache
-				$wgOut->addPrimaryWikiText( $text, $this );
+				$this->outputWikiText( $text );
 			} else {
 				# Display content, don't attempt to save to parser cache
 				# Don't show section-edit links on old revisions... this way lies madness.
@@ -803,7 +800,7 @@
 					$oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
 				}
 				# Display content and don't save to parser cache
-				$wgOut->addPrimaryWikiText( $text, $this, false );
+				$this->outputWikiText( $text, false );
 
 				if( !$this->isCurrent() ) {
 					$wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
@@ -970,7 +967,6 @@
 			'page_namespace'    => $this->mTitle->getNamespace(),
 			'page_title'        => $this->mTitle->getDBkey(),
 			'page_counter'      => 0,
-			'page_restrictions' => $restrictions,
 			'page_is_redirect'  => 0, # Will set this shortly...
 			'page_is_new'       => 1,
 			'page_random'       => wfRandom(),
@@ -1635,7 +1631,7 @@
 	 * @param string $reason
 	 * @return bool true on success
 	 */
-	function updateRestrictions( $limit = array(), $reason = '' ) {
+	function updateRestrictions( $limit = array(), $reason = '', $cascade = 0 ) {
 		global $wgUser, $wgRestrictionTypes, $wgContLang;
 		
 		$id = $this->mTitle->getArticleID();
@@ -1653,6 +1649,7 @@
 		$updated = Article::flattenRestrictions( $limit );
 		
 		$changed = ( $current != $updated );
+		$changed = $changed || ($this->mTitle->getRestrictionCascadingFlags() != $cascade);
 		$protect = ( $updated != '' );
 		
 		# If nothing's changed, do nothing
@@ -1669,23 +1666,32 @@
 					$comment .= " [$updated]";
 				$nullRevision = Revision::newNullRevision( $dbw, $id, $comment, true );
 				$nullRevId = $nullRevision->insertOn( $dbw );
+
+				# Update restrictions table
+				foreach( $limit as $action => $restrictions ) {
+					if ($restrictions != '' ) {
+						$dbw->replace( 'page_restrictions', array( 'pr_pagetype'),
+							array( 'pr_page' => $id, 'pr_type' => $action
+								, 'pr_level' => $restrictions, 'pr_cascade' => $cascade ), __METHOD__  );
+					} else {
+						$dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
+							'pr_type' => $action ), __METHOD__ );
+					}
+				}
 			
-				# Update page record
-				$dbw->update( 'page',
-					array( /* SET */
-						'page_touched' => $dbw->timestamp(),
-						'page_restrictions' => $updated,
-						'page_latest' => $nullRevId
-					), array( /* WHERE */
-						'page_id' => $id
-					), 'Article::protect'
-				);
 				wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser, $limit, $reason ) );
 	
 				# Update the protection log
 				$log = new LogPage( 'protect' );
+
+				$cascade_description = '';
+
+				if ($cascade) {
+					$cascade_description = ' ['.wfMsg('protect-summary-cascade').']';
+				}
+
 				if( $protect ) {
-					$log->addEntry( 'protect', $this->mTitle, trim( $reason . " [$updated]" ) );
+					$log->addEntry( 'protect', $this->mTitle, trim( $reason . " [$updated]$cascade_description" ) );
 				} else {
 					$log->addEntry( 'unprotect', $this->mTitle, $reason );
 				}
@@ -2010,6 +2016,9 @@
 			), __METHOD__
 		);
 
+		# Delete restrictions for it
+		$dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
+
 		# Now that it's safely backed up, delete it
 		$dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__);
 
@@ -2785,6 +2794,67 @@
 
 		return $summary;
 	}
+
+	/**
+	 * Add the primary page-view wikitext to the output buffer
+	 * Saves the text into the parser cache if possible.
+	 *
+	 * @param string  $text
+	 * @param Article $article
+	 * @param bool    $cache
+	 */
+	public function outputWikiText( $text, $cache = true ) {
+		global $wgParser, $wgUser, $wgOut;
+
+		$article = $this;
+
+		$popts = $wgOut->parserOptions();
+		$popts->setTidy(true);
+		$parserOutput = $wgParser->parse( $text, $article->mTitle,
+			$popts, true, true, $this->mRevisionId );
+		$popts->setTidy(false);
+		if ( $cache && $article && $parserOutput->getCacheTime() != -1 ) {
+			$parserCache =& ParserCache::singleton();
+			$parserCache->save( $parserOutput, $article, $wgUser );
+		}
+
+		if ( !wfReadOnly() ) {
+
+			# Get templates from templatelinks
+			$tlTemplates_titles = $this->getUsedTemplates();
+
+			$tlTemplates = array ();
+			foreach( $tlTemplates_titles as $template_title) {
+				$tlTemplates[] = $template_title->getDBkey();
+			}
+
+			# Get templates from parser output.
+			$poTemplates_allns = $parserOutput->getTemplates();
+
+			$poTemplates = array ();
+			foreach ( $poTemplates_allns as $ns_templates ) {
+				$poTemplates = array_merge( $poTemplates, $ns_templates );
+			}
+
+			# Get the diff
+			$templates_diff = array_diff( $poTemplates, $tlTemplates );
+
+			if ( count( $templates_diff ) > 0 ) {
+				# Whee, link updates time.
+				$u = new LinksUpdate( $this->mTitle, $parserOutput );
+	
+				$dbw =& wfGetDb( DB_MASTER );
+				$dbw->begin();
+	
+				$u->doUpdate();
+
+				$dbw->commit();
+			}
+		}
+
+		$wgOut->addParserOutput( $parserOutput );
+	}
+
 }
 
 ?>
Index: trunk/phase3/includes/OutputPage.php
===================================================================
--- trunk/phase3/includes/OutputPage.php	(revision 19094)
+++ trunk/phase3/includes/OutputPage.php	(revision 19095)
@@ -315,14 +315,26 @@
 		$this->addWikiTextTitle($text, $title, $linestart);
 	}
 
-	private function addWikiTextTitle($text, &$title, $linestart) {
+	function addWikiTextTitleTidy($text, &$title, $linestart = true) {
+		addWikiTextTitle( $text, $title, $linestart, true );
+	}
+
+	public function addWikiTextTitle($text, &$title, $linestart, $tidy = false) {
 		global $wgParser;
+
 		$fname = 'OutputPage:addWikiTextTitle';
 		wfProfileIn($fname);
+
 		wfIncrStats('pcache_not_possible');
-		$parserOutput = $wgParser->parse( $text, $title, $this->parserOptions(),
+
+		$popts = $this->parserOptions();
+		$popts->setTidy($tidy);
+
+		$parserOutput = $wgParser->parse( $text, $title, $popts,
 			$linestart, true, $this->mRevisionId );
+
 		$this->addParserOutput( $parserOutput );
+
 		wfProfileOut($fname);
 	}
 
@@ -366,6 +378,7 @@
 	 * @param string  $text
 	 * @param Article $article
 	 * @param bool    $cache
+	 * @deprecated Use Article::outputWikitext
 	 */
 	public function addPrimaryWikiText( $text, $article, $cache = true ) {
 		global $wgParser, $wgUser;
Index: trunk/phase3/includes/SpecialUndelete.php
===================================================================
--- trunk/phase3/includes/SpecialUndelete.php	(revision 19094)
+++ trunk/phase3/includes/SpecialUndelete.php	(revision 19095)
@@ -532,8 +532,7 @@
 		
 		if( $this->mPreview ) {
 			$wgOut->addHtml( "<hr />\n" );
-			$article = new Article ( $archive->title );  # OutputPage wants an Article obj
-			$wgOut->addPrimaryWikiText( $rev->getText(), $article, false );
+			$wgOut->addWikiTextTitle( $rev->getText(), $archive->title, false );
 		}
 		
 		$self = SpecialPage::getTitleFor( "Undelete" );
Index: trunk/phase3/includes/Title.php
===================================================================
--- trunk/phase3/includes/Title.php	(revision 19094)
+++ trunk/phase3/includes/Title.php	(revision 19095)
@@ -50,7 +50,7 @@
 	var $mArticleID;          # Article ID, fetched from the link cache on demand
 	var $mLatestID;         # ID of most recent revision
 	var $mRestrictions;       # Array of groups allowed to edit this article
-	                        # Only null or "sysop" are supported
+	var $mCascadeRestrictionFlags;
 	var $mRestrictionsLoaded; # Boolean for initialisation on demand
 	var $mPrefixedText;       # Text form including namespace/interwiki, initialised on demand
 	var $mDefaultNamespace;   # Namespace index when there is no namespace
@@ -1115,6 +1115,18 @@
 			return false;
 		}
 
+		if ( ( $this->isCascadeProtectedPage() ) || 
+				($this->getNamespace() == NS_IMAGE && $this->isCascadeProtectedImage() ) ) {
+			# We /could/ use the protection level on the source page, but it's fairly ugly
+			#  as we have to establish a precedence hierarchy for pages included by multiple
+			#  cascade-protected pages. So just restrict it to people with 'protect' permission,
+			#  as they could remove the protection anyway.
+			if ( !$wgUser->isAllowed('protect') ) {
+				wfProfileOut( $fname );
+				return false;
+			}
+		}
+
 		foreach( $this->getRestrictions($action) as $right ) {
 			// Backwards compatibility, rewrite sysop -> protect
 			if ( $right == 'sysop' ) {
@@ -1308,33 +1320,113 @@
 	}
 
 	/**
+	* Cascading protects: Check if the current image is protected due to a cascading restriction
+	*
+	* @return bool If the current page is protected due to a cascading restriction.
+	* @access public
+	*/
+	function isCascadeProtectedImage() {
+		global $wgEnableCascadingProtection;
+		if (!$wgEnableCascadingProtection)
+			return;
+
+		wfProfileIn(__METHOD__);
+
+		$dbr =& wfGetDb( DB_SLAVE );
+
+		$cols = array( 'il_to' );
+		$tables = array ('imagelinks', 'page_restrictions');
+		$where_clauses = array( 'il_to' => $this->getDBkey(), 'il_from=pr_page', 'pr_cascade' => 1 );
+
+		$res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__);
+
+		//die($dbr->numRows($res));
+
+		if ($dbr->numRows($res)) {
+			wfProfileOut(__METHOD__);
+			return true;
+		} else {
+			wfProfileOut(__METHOD__);
+			return false;
+		}
+	}
+
+	/**
+	* Cascading protects: Check if the current page is protected due to a cascading restriction.
+	*
+	* @return bool if the current page is protected due to a cascading restriction
+	* @access public
+	*/
+	function isCascadeProtectedPage() {
+		global $wgEnableCascadingProtection;
+		if (!$wgEnableCascadingProtection)
+			return;
+
+		wfProfileIn(__METHOD__);
+
+		$dbr =& wfGetDb( DB_SLAVE );
+
+		$cols = array( 'tl_namespace', 'tl_title'/*, 'pr_level', 'pr_type'*/ );
+		$tables = array ('templatelinks', 'page_restrictions');
+		$where_clauses = array( 'tl_namespace' => $this->getNamespace(), 'tl_title' => $this->getDBkey(), 'tl_from=pr_page', 'pr_cascade' => 1 );
+
+		$res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__);
+
+		if ($dbr->numRows($res)) {
+			wfProfileOut(__METHOD__);
+			return true;
+		} else {
+			wfProfileOut(__METHOD__);
+			return false;
+		}
+	}
+
+	function getRestrictionCascadingFlags() {
+		if (!$this->mRestrictionsLoaded) {
+			$this->loadRestrictions();
+		}
+
+		return $this->mCascadeRestrictionFlags;
+	}
+
+	/**
 	 * Loads a string into mRestrictions array
-	 * @param string $res restrictions in string format
+	 * @param resource $res restrictions as an SQL result.
 	 * @access public
 	 */
-	function loadRestrictions( $res ) {
-		$this->mRestrictions['edit'] = array();
-		$this->mRestrictions['move'] = array();
-		
-		if( !$res ) {
-			# No restrictions (page_restrictions blank)
+	function loadRestrictionsFromRow( $res ) {
+		$dbr =& wfGetDb( DB_SLAVE );
+
+		if (!$dbr->numRows( $res ) ) {
+			# No restrictions
 			$this->mRestrictionsLoaded = true;
 			return;
 		}
-	
-		foreach( explode( ':', trim( $res ) ) as $restrict ) {
-			$temp = explode( '=', trim( $restrict ) );
-			if(count($temp) == 1) {
-				// old format should be treated as edit/move restriction
-				$this->mRestrictions["edit"] = explode( ',', trim( $temp[0] ) );
-				$this->mRestrictions["move"] = explode( ',', trim( $temp[0] ) );
-			} else {
-				$this->mRestrictions[$temp[0]] = explode( ',', trim( $temp[1] ) );
-			}
+
+		$this->mRestrictions['edit'] = array();
+		$this->mRestrictions['move'] = array();
+
+		while ($row = $dbr->fetchObject( $res ) ) {
+			# Cycle through all the restrictions.
+
+			$this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
+
+			$this->mCascadeRestrictionFlags |= $row->pr_cascade;
 		}
+
 		$this->mRestrictionsLoaded = true;
 	}
 
+	function loadRestrictions() {
+		if( !$this->mRestrictionsLoaded ) {
+			$dbr =& wfGetDB( DB_SLAVE );
+		
+			$res = $dbr->select( 'page_restrictions', '*',
+				array ( 'pr_page' => $this->getArticleId() ), __METHOD__ );
+			$this->loadRestrictionsFromRow( $res );
+		}
+	}
+
 	/**
 	 * Accessor/initialisation for mRestrictions
 	 *
@@ -1345,9 +1437,7 @@
 	function getRestrictions( $action ) {
 		if( $this->exists() ) {
 			if( !$this->mRestrictionsLoaded ) {
-				$dbr =& wfGetDB( DB_SLAVE );
-				$res = $dbr->selectField( 'page', 'page_restrictions', array( 'page_id' => $this->getArticleId() ) );
-				$this->loadRestrictions( $res );
+				$this->loadRestrictions();
 			}
 			return isset( $this->mRestrictions[$action] )
 					? $this->mRestrictions[$action]
Index: trunk/phase3/includes/DefaultSettings.php
===================================================================
--- trunk/phase3/includes/DefaultSettings.php	(revision 19094)
+++ trunk/phase3/includes/DefaultSettings.php	(revision 19095)
@@ -2416,4 +2416,9 @@
  */
 $wgDisableQueryPageUpdate = false;
 
+/**
+ * Set this to false to disable cascading protection
+ */
+$wgEnableCascadingProtection = true;
+
 ?>
Index: trunk/phase3/languages/messages/MessagesEn.php
===================================================================
--- trunk/phase3/languages/messages/MessagesEn.php	(revision 19094)
+++ trunk/phase3/languages/messages/MessagesEn.php	(revision 19095)
@@ -1745,6 +1745,8 @@
 'protect-default' => '(default)',
 'protect-level-autoconfirmed' => 'Block unregistered users',
 'protect-level-sysop' => 'Sysops only',
+'protect-summary-cascade' => 'cascading',
+'protect-cascade' => 'Cascading protection - protect any pages transcluded in this page.',
 
 # restrictions (nouns)
 'restriction-edit' => 'Edit',
Index: trunk/phase3/RELEASE-NOTES
===================================================================
--- trunk/phase3/RELEASE-NOTES	(revision 19094)
+++ trunk/phase3/RELEASE-NOTES	(revision 19095)
@@ -39,6 +39,10 @@
   starts page output (http://lists.wikimedia.org/pipermail/wikitech-l/2007-January/028554.html)
 * Fix SpecialVersion->formatCredits input. Version and Url parameters should be null
   to be treated properly with isset.
+* Branch page_restrictions column out into its own table, also creating a "cascading protection"
+   feature, which automagically disallows edits to pages transcluded into a page protected with
+   this new option. Various other code tidiness fixes and refactoring in the log messages of
+   branches/werdna/restrictions-separation.
 
 == Languages updated ==
 

Follow-up revisions

Rev.Commit summaryAuthorDate
r19228* (bug 8632) Fix regression in page protection null edit update...brion12:03, 14 January 2007
r79752More ancient deprecated functions:...happy-melon20:40, 6 January 2011
Personal tools
Namespaces
Variants
Views
Actions
Site
Support
Download
Development
Communication
Toolbox