Extension:Oversight 2

The Oversight extension 2 adds a user class that allows revisions to be hidden from all users, and unhidden. This extension differs from the original in that it allows revisions to be unhidden from the front end by users with the appropriate right, instead of requiring DB access. It can unhide revisions that were hidden with the original extension, and provides this functionality to MediaWiki installations from before the inclusion of RevisionDelete in the core.

Usage
Users with the Oversight class will have an additional 'hide revision' tab when viewing an old revision, edit difference, or deleted revision. The edit can be hidden from all users by clicking this tab, adding a reason, and confirming. The most recent edit to a page cannot be hidden unless the page is deleted; to hide it, you must first revert or delete the edit.

Note that hiding revisions may create misleading edit differences, since any changes made in hidden revisions will seem to have been made by the next visible edit. In situations where good content is added in hidden revisions, there may be no simple solution.

The hidings can be viewed and undone from a private log.

Download instructions
Please cut and paste the code found below and place it in. Note: $IP stands for the root directory of your MediaWiki installation, the same directory that holds LocalSettings.php.

Installation
To install this extension, add the following to LocalSettings.php:

Add the required table to the database, by running this sql code:

HideRevision_body.php:
" .				" \n" );		}		$dbr->freeResult( $result );	} }

class UnHideRevisionForm extends SpecialPage { function __construct { wfLoadExtensionMessages( 'HideRevision' ); parent::__construct( 'UnHideRevision', 'unhiderevision' ); }

function execute( $par ) { global $wgUser, $wgRequest;

$this->setHeaders;

if( !$this->userCanExecute( $wgUser ) ){ $this->displayRestrictionError; return; }

$this->outputHeader;

// For live revisions $this->mRevisions = (array)$wgRequest->getIntArray( 'revision' );

// For deleted/archived revisions $this->mTarget = Title::newFromUrl( $wgRequest->getVal( 'target' ) ); $this->mTimestamps = (array)$wgRequest->getArray( 'timestamp' ); if( is_null( $this->mTarget ) ) { // title and timestamps must go together $this->mTimestamps = array; }

$this->mPopulated = !empty( $this->mRevisions ) || !empty( $this->mTimestamps );

$this->mReason = $wgRequest->getText( 'wpReason' );

$submitted = $wgRequest->wasPosted && $wgRequest->getVal( 'action' ) == 'submit' && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ); $dbw = wfGetDB( DB_MASTER ); $dbw->begin; $id = $wgRequest->getIntOrNull("revisions") ? $wgRequest->getIntOrNull("revisions") : $this->mRevisions[0]; $res = $dbw->select('hidden',array('unhidden_by_user','hidden_page'),array('hidden_rev_id' => $id)); $result = array; while($resul = $dbw->fetchRow($res))$result = $resul; global $wgOut; if($result['unhidden_by_user'] || !$result['hidden_page'] && $id){$this->mPopulated = false;$wgOut->addHtml("Invalied revision: $id.

"); }		if( $this->mPopulated && $submitted ) {			$this->submit;		} elseif( $this->mPopulated ) {			$this->showForm;		} else {			$this->showEmpty;		}	}

/**	 * If no revisions are specified, prompt for a revision id	 */ function showEmpty { global $wgOut, $wgUser; $special = Title::makeTitle( NS_SPECIAL, 'UnHideRevision' );

$wgOut->addHtml(			Xml::openElement( 'form', array(				'action' => $special->getLocalUrl,				'method' => 'post' ) ).

// Visible fields Xml::inputLabel( wfMsgHTML( 'unhiderevision-prompt' ), 'revision[]', 'wpRevision', 10 ). " " .			Xml::inputLabel( wfMsgHTML( 'unhiderevision-reason' ), 'wpReason', 'wpReason', 60 ). " " .			Xml::submitButton( wfMsgHTML( 'unhiderevision-continue' ) ).

Xml::closeElement( 'form' ) );	}

/**	 * Once a set of revisions have been selected, * list them and request a reason/comment for confirmation. */	function showForm { global $wgOut, $wgUser; $special = Title::makeTitle( NS_SPECIAL, 'UnHideRevision' );

$wgOut->addWikiText( wfMsg( 'unhiderevision-text' ) ); $wgOut->addHtml(			$this->revisionList.

$this->archiveList.

Xml::openElement( 'form', array( 'action' => $special->getLocalUrl( 'action=submit' ), 'method' => 'post' ) ).

// Visible fields " " .			Xml::inputLabel( wfMsgHTML( 'unhiderevision-reason' ), 'wpReason', 'wpReason', 60, $this->mReason ). " " .			Xml::submitButton( wfMsgHTML( 'unhiderevision-submit' ) ).

// Hidden fields $this->revisionFields. Xml::hidden( 'wpEditToken', $wgUser->editToken ).

Xml::closeElement( 'form' ) );	}

function revisionList { if( !$this->mRevisions ) { return ''; }		$dbr = wfGetDB( DB_SLAVE ); $result = $dbr->select(			array( 'page', 'revision' ),			'*, 0 AS rc_id, 1 AS rc_patrolled, 0 AS counter, 0 AS rc_old_len, 0 AS rc_new_len,			NULL AS rc_log_action, 0 AS rc_deleted, 0 AS rc_logid, NULL AS rc_log_type, \'\' AS rc_params',			array( 'rev_id' => $this->mRevisions, 'rev_page=page_id', ),			__METHOD__ );

return $this->makeList( $dbr->resultObject( $result ) ); }

function makeList( $resultSet ) { global $IP, $wgUser; require_once( "$IP/includes/ChangesList.php" ); $changes = ChangesList::newFromUser( $wgUser );

$skin = $wgUser->getSkin;

$out = $changes->beginRecentChangesList; while( $row = $resultSet->fetchObject ) { $rc = RecentChange::newFromCurRow( $row ); $rc->counter = 0; // ??? $out .= $changes->recentChangesLine( $rc ); }		$out .= $changes->endRecentChangesList;

$resultSet->free; return $out; }

function archiveList { if( !$this->mTarget || !$this->mTimestamps ) { return ''; }

$dbr = wfGetDB( DB_SLAVE ); $result = $dbr->select(			array( 'archive' ),			array( 'ar_namespace AS page_namespace', 'ar_title AS page_title', 'ar_comment AS rev_comment', 'ar_user AS rev_user', 'ar_user_text AS rev_user_text', 'ar_timestamp AS rev_timestamp', 'ar_minor_edit AS rev_minor_edit', 'ar_rev_id AS rev_id', '0 AS rc_id', '1 AS rc_patrolled', '0 AS counter', '0 AS page_id', '0 AS page_is_new', '0 AS rc_old_len', '0 AS rc_new_len', '0 AS rc_deleted', '0 AS rc_logid', 'NULL AS rc_log_type', 'NULL AS rc_log_action', '"" AS rc_params' ),			array( 'ar_namespace' => $this->mTarget->getNamespace, 'ar_title' => $this->mTarget->getDBkey, 'ar_timestamp' => $this->mTimestamps, ),			__METHOD__ );

return $this->makeList( $dbr->resultObject( $result ) ); }

function revisionFields { $out = ''; foreach( $this->mRevisions as $id ) { $out .= Xml::hidden( 'revision[]', $id ); }		if( $this->mTarget ) { $out .= Xml::hidden( 'target', $this->mTarget->getPrefixedDbKey ); }		foreach( $this->mTimestamps as $timestamp ) { $out .= Xml::hidden( 'timestamp[]', wfTimestamp( TS_MW, $timestamp ) ); }		return $out; }

/**	 * Handle submission of deletion form */	function submit { global $wgOut; if( !$this->mPopulated ) { $wgOut->addWikiText( wfMsg( 'unhiderevision-norevisions' ) ); $this->showForm; } elseif( empty( $this->mReason ) ) { $wgOut->addWikiText( wfMsg( 'unhiderevision-noreason' ) ); $this->showForm; } else { $dbw = wfGetDB( DB_MASTER ); $success = $this->hideRevisions( $dbw ); $wgOut->addWikiText( '* ' . implode( "\n* ", $success ) ); }	}

/**	 * Go kill the revisions and return status information. * @param $dbw database * @return array of wikitext strings with success/failure messages */	function hideRevisions( $dbw ) { // Live revisions foreach( $this->mRevisions as $id ) { $success[] = wfMsgHTML( 'unhiderevision-status', $id,				wfMsgHTML( $this->hideRevision( $dbw, $id ) ) ); }

// Archived revisions /*foreach( $this->mTimestamps as $timestamp ) { global $wgLang; $success[] = wfMsgHTML( 'unhiderevision-archive-status',				$wgLang->timeanddate( $timestamp ),				wfMsgHTML( $this->hideArchivedRevision( $dbw, $timestamp ) ) ); }*/		return $success; }

/**	 * Actually go in the database and kill things. * @return message key string for success or failure message */	function hideRevision( $dbw, $id ) { global $wgUser;

$dbw->begin; /*		$rev = Revision::newFromId( $id ); if( is_null( $rev ) ) { $dbw->rollback; return 'unhiderevision-error-missing'; }		$title = $rev->getTitle;*/ // Our tasks: // Copy revision to "hidden" table $this->InsertRevision( $dbw, $id );

if( $dbw->affectedRows != 1 ) { $dbw->rollback; return 'unhiderevision-error-delete'; }

// Remove from "revision" $keys = array('rev_page','rev_id','rev_text_id','rev_comment','rev_user','rev_user_text','rev_timestamp','rev_minor_edit','rev_deleted'); $gets = array('hidden_page','hidden_rev_id','hidden_text_id','hidden_comment','hidden_user','hidden_user_text','hidden_timestamp','hidden_minor_edit','hidden_deleted'); $infos = $dbw->select('hidden',$gets,array("hidden_rev_id" => $id)) or die('no data'); while( $row = $dbw->fetchRow( $infos ) )$values = $row; foreach($values as $key => $value)if($keys[$key])$pairs["$keys[$key]"]=$value;

$res=$dbw->insert('revision',$pairs) or die("Can't insert diff"); // Remove from "recentchanges" // The page ID is used to get us a relatively usable index /*$dbw->insert( 'recentchanges',			array( 'rc_cur_id'    => $rev->getPage, 'rc_this_oldid' => $id ),			__METHOD__ );*/

// Invalidate cache of page history

// Done with all database pieces; commit! $dbw->commit;

// Also purge remote proxies. // Ideally this would be built into the above, but squid code is // old crappy style. global $wgUseSquid; if ( $wgUseSquid ) { // Send purge $update = SquidUpdate::newSimplePurge( $title ); $update->doUpdate; }

return 'unhiderevision-success'; }

function insertRevision( $dbw, $rev ) { global $wgUser; return $dbw->update( 'hidden',			array( 'unhidden_by_user'     => $wgUser->getId, 'unhidden_on_timestamp' => $dbw->timestamp, 'unhidden_reason'      => $this->mReason, ),			array( 'hidden_rev_id'    => $rev, 'unhidden_by_user' => null, ) );	} }