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 ==
'page_id',
'page_namespace',
'page_title',
- 'page_restrictions',
'page_counter',
'page_is_redirect',
'page_is_new',
$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 );
$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.
$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 );
'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(),
* @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();
$updated = Article::flattenRestrictions( $limit );
$changed = ( $current != $updated );
+ $changed = $changed || ($this->mTitle->getRestrictionCascadingFlags() != $cascade);
$protect = ( $updated != '' );
# If nothing's changed, do nothing
$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 );
}
), __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__);
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 );
+ }
+
}
?>
*/
$wgDisableQueryPageUpdate = false;
+/**
+ * Set this to false to disable cascading protection
+ */
+$wgEnableCascadingProtection = true;
+
?>
$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);
}
* @param string $text
* @param Article $article
* @param bool $cache
+ * @deprecated Use Article::outputWikitext
*/
public function addPrimaryWikiText( $text, $article, $cache = true ) {
global $wgParser, $wgUser;
class ProtectionForm {
var $mRestrictions = array();
var $mReason = '';
+ var $mCascade = false;
function ProtectionForm( &$article ) {
global $wgRequest, $wgUser;
// 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.
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 ) ) {
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." );
}
$out .= "</tbody>\n";
$out .= "</table>\n";
+ global $wgEnableCascadingProtection;
+
+ if ($wgEnableCascadingProtection)
+ $out .= $this->buildCascadeInput();
+
if( !$this->disabled ) {
$out .= "<table>\n";
$out .= "<tbody>\n";
'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',
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" );
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
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' ) {
return ( $wgUser->isAllowed('editinterface') or preg_match('/^'.preg_quote($wgUser->getName(), '/').'\//', $this->mTextform) );
}
+ /**
+ * 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
*
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]
'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',
) 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
array( 'filearchive', 'patch-filearchive.sql' ),
array( 'redirect', 'patch-redirect.sql' ),
array( 'querycachetwo', 'patch-querycachetwo.sql' ),
+# array( 'page_restrictions', 'patch-page_restrictions.sql' ),
);
$wgNewFields = array(
do_backlinking_indices_update(); flush();
+ do_restrictions_update(); flush ();
+
echo "Deleting old default messages..."; flush();
deleteDefaultMessages();
echo "Done\n"; flush();
}
}
+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;