Extension:Oversight 2
From MediaWiki.org
|
Release status: beta |
|||
|---|---|---|---|
| Implementation | Special page, Database | ||
| Description | Adds a user class that allows hiding and unhiding revisions | ||
| Author(s) | Brion VIBBER and Shaiaqua | ||
| License | GPL | ||
| Download | see below | ||
|
|||
|
|||
|
check usage (experimental) |
|||
The Oversight extension adds a user class that allows revisions to be hidden from all users, and unhidden.
Contents |
[edit] 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.
[edit] Download instructions
Please cut and paste the code found below and place it in $IP/extensions/Oversight/HideRevision.php, $IP/extensions/Oversight/HideRevision_body.php, and $IP/extensions/Oversight/HideRevision.i18n.php. Note: $IP stands for the root directory of your MediaWiki installation, the same directory that holds LocalSettings.php.
[edit] Installation
To install this extension, add the following to LocalSettings.php:
$wgGroupPermissions['oversight']['oversight'] = true; $wgGroupPermissions['oversight']['hiderevision'] = true; $wgGroupPermissions['oversight']['unhiderevision'] = true; require_once("$IP/extensions/Oversight/HideRevision.php");
Add the required table to the database, by running this sql code:
CREATE TABLE IF NOT EXISTS `/*$wgDBprefix*/hidden` ( `hidden_page` int(11) UNSIGNED NOT NULL DEFAULT '0', `hidden_namespace` int(11) NOT NULL DEFAULT '0', `hidden_title` varchar(255) character SET latin1 collate latin1_bin NOT NULL DEFAULT '', `hidden_comment` tinyblob NOT NULL, `hidden_user` int(5) UNSIGNED NOT NULL DEFAULT '0', `hidden_user_text` varchar(255) character SET latin1 collate latin1_bin NOT NULL, `hidden_timestamp` char(14) character SET latin1 collate latin1_bin NOT NULL DEFAULT '', `hidden_minor_edit` tinyint(1) NOT NULL DEFAULT '0', `hidden_deleted` tinyint(1) NOT NULL DEFAULT '0', `hidden_rev_id` int(8) UNSIGNED DEFAULT NULL, `hidden_text_id` int(8) UNSIGNED DEFAULT NULL, `hidden_by_user` int(11) DEFAULT NULL, `hidden_on_timestamp` char(14) character SET latin1 collate latin1_bin DEFAULT NULL, `hidden_reason` text, `unhidden_by_user` int(11) DEFAULT NULL, `unhidden_on_timestamp` char(14) DEFAULT NULL, `unhidden_reason` text, KEY `page_title_timestamp` (`hidden_page`,`hidden_timestamp`), KEY `name_title_timestamp` (`hidden_namespace`,`hidden_title`,`hidden_timestamp`), KEY `hidden_on_timestamp` (`hidden_on_timestamp`), KEY `hidden_by_user` (`hidden_by_user`,`hidden_on_timestamp`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
[edit] Code
[edit] HideRevision.php:
<?php $wgAvailableRights[] = 'hiderevision'; $wgAvailableRights[] = 'unhiderevision'; $wgAvailableRights[] = 'oversight'; $wgExtensionCredits['specialpage'][] = array( 'name' => 'Oversight 2', 'author' => 'Brion Vibber and Shaiaqua', 'url' => 'http://www.mediawiki.org/wiki/Extension:Oversight_2', 'description' => 'Hide individual revisions from all users for legal reasons, etc., and unhide them.', ); $dir = dirname(__FILE__) . '/'; $wgExtensionMessagesFiles['HideRevision'] = $dir . 'HideRevision.i18n.php'; $wgExtensionMessagesFiles['UnHideRevision'] = $dir . 'HideRevision.i18n.php'; $wgExtensionMessagesFiles['Oversight'] = $dir . 'HideRevision.i18n.php'; $wgAutoloadClasses['HideRevisionForm'] = $dir . 'HideRevision_body.php'; $wgAutoloadClasses['UnHideRevisionForm'] = $dir . 'HideRevision_body.php'; $wgAutoloadClasses['SpecialOversight'] = $dir . 'HideRevision_body.php'; $wgSpecialPages['HideRevision'] = 'HideRevisionForm'; $wgSpecialPages['UnHideRevision'] = 'UnHideRevisionForm'; $wgSpecialPages['Oversight'] = 'SpecialOversight'; $wgHooks['ArticleViewHeader'][] = 'hrArticleViewHeaderHook'; $wgHooks['DiffViewHeader'][] = 'hrDiffViewHeaderHook'; $wgHooks['UndeleteShowRevision'][] = 'hrUndeleteShowRevisionHook'; /** * Hook for article view, giving us a chance to insert a removal * tab on old version views. */ function hrArticleViewHeaderHook( $article ) { $oldid = intval( $article->mOldId ); if( $oldid ) { hrInstallTab( $oldid ); } return true; } /** * Hook for diff view, giving us a chance to insert a removal * tab on old version views. */ function hrDiffViewHeaderHook( $diff, $oldRev, $newRev ) { if( !empty( $newRev ) && $newRev->getId() ) { hrInstallTab( $newRev->getId() ); } return true; } /** * Hook for deletion archive revision view, giving us a chance to * insert a removal tab for a deleted revision. */ function hrUndeleteShowRevisionHook( $title, $rev ) { hrInstallArchiveTab( $title, $rev->getTimestamp() ); return true; } class HideRevisionTabInstaller { function __construct( $linkParam ) { $this->mLinkParam = $linkParam; } function insertTab( $skin, &$content_actions ) { wfLoadExtensionMessages ('HideRevision'); $special = Title::makeTitle( NS_SPECIAL, 'HideRevision' ); $content_actions['hiderevision'] = array( 'class' => false, 'text' => wfMsgHTML( 'hiderevision-tab' ), 'href' => $special->getLocalUrl( $this->mLinkParam ) ); return true; } } /** * If the user is allowed, installs a tab hook on the skin * which links to a handy permanent removal thingy. */ function hrInstallTab( $id ) { global $wgUser; if( $wgUser->isAllowed( 'hiderevision' ) ) { global $wgHooks; $tab = new HideRevisionTabInstaller( 'revision[]=' . $id ); $wgHooks['SkinTemplateTabs'][] = array( $tab, 'insertTab' ); } } /** * If the user is allowed, installs a tab hook on the skin * which links to a handy permanent removal thingy for * archived (deleted) pages. */ function hrInstallArchiveTab( $target, $timestamp ) { global $wgUser; if( $wgUser->isAllowed( 'hiderevision' ) ) { global $wgHooks; $tab = new HideRevisionTabInstaller( 'target=' . $target->getPrefixedUrl() . '×tamp[]=' . $timestamp ); $wgHooks['SkinTemplateBuildContentActionUrlsAfterSpecialPage'][] = array( $tab, 'insertTab' ); } }
[edit] HideRevision_body.php:
<?php /** * Special page handler function for Special:HideRevision */ class HideRevisionForm extends SpecialPage { function __construct() { wfLoadExtensionMessages( 'HideRevision' ); parent::__construct( 'HideRevision', 'hiderevision' ); } 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' ) ); 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, 'HideRevision' ); $wgOut->addHtml( Xml::openElement( 'form', array( 'action' => $special->getLocalUrl(), 'method' => 'post' ) ) . // Visible fields Xml::inputLabel( wfMsgHTML( 'hiderevision-prompt' ), 'revision[]', 'wpRevision', 10 ) . "<br />" . Xml::inputLabel( wfMsgHTML( 'hiderevision-reason' ), 'wpReason', 'wpReason', 60 ) . "<br />" . Xml::submitButton( wfMsgHTML( 'hiderevision-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, 'HideRevision' ); $wgOut->addWikiText( wfMsg( 'hiderevision-text' ) ); $wgOut->addHtml( $this->revisionList() . $this->archiveList() . Xml::openElement( 'form', array( 'action' => $special->getLocalUrl( 'action=submit' ), 'method' => 'post' ) ) . // Visible fields "<br />" . Xml::inputLabel( wfMsgHTML( 'hiderevision-reason' ), 'wpReason', 'wpReason', 60, $this->mReason ) . "<br />" . Xml::submitButton( wfMsgHTML( 'hiderevision-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( 'hiderevision-norevisions' ) ); $this->showForm(); } elseif( empty( $this->mReason ) ) { $wgOut->addWikiText( wfMsg( 'hiderevision-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( 'hiderevision-status', $id, wfMsgHTML( $this->hideRevision( $dbw, $id ) ) ); } // Archived revisions foreach( $this->mTimestamps as $timestamp ) { global $wgLang; $success[] = wfMsgHTML( 'hiderevision-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 'hiderevision-error-missing'; } if( $rev->isCurrent() ) { $dbw->rollback(); return 'hiderevision-error-current'; } $title = $rev->getTitle(); // Our tasks: // Copy revision to "hidden" table $this->InsertRevision( $dbw, $title, $rev ); if( $dbw->affectedRows() != 1 ) { $dbw->rollback(); return 'hiderevision-error-delete'; } // Remove from "revision" $dbw->delete( 'revision', array( 'rev_id' => $id ), __FUNCTION__ ); // Remove from "recentchanges" // The page ID is used to get us a relatively usable index $dbw->delete( 'recentchanges', array( 'rc_cur_id' => $rev->getPage(), 'rc_this_oldid' => $id ), __METHOD__ ); // Invalidate cache of page history $title->invalidateCache(); // Done with all database pieces; commit! $dbw->immediateCommit(); // 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 'hiderevision-success'; } function hideArchivedRevision( $dbw, $timestamp ) { $archive = new PageArchive( $this->mTarget ); $rev = $archive->getRevision( $timestamp ); if( !$rev ) { $dbw->rollback(); return 'hiderevision-error-missing'; } $this->insertRevision( $dbw, $this->mTarget, $rev ); if( $dbw->affectedRows() != 1 ) { $dbw->rollback(); return 'hiderevision-error-delete'; } $dbw->delete( 'archive', array( 'ar_namespace' => $this->mTarget->getNamespace(), 'ar_title' => $this->mTarget->getDBkey(), 'ar_timestamp' => $timestamp ), __METHOD__ ); $dbw->commit(); return 'hiderevision-success'; } function insertRevision( $dbw, $title, $rev ) { global $wgUser; return $dbw->insert( 'hidden', array( 'hidden_page' => $rev->getPage(), 'hidden_namespace' => $title->getNamespace(), 'hidden_title' => $title->getDBkey(), 'hidden_rev_id' => $rev->getId(), 'hidden_text_id' => $rev->getTextId(), 'hidden_comment' => $rev->getRawComment(), 'hidden_user' => $rev->getRawUser(), 'hidden_user_text' => $rev->getRawUserText(), 'hidden_timestamp' => $dbw->timestamp( $rev->getTimestamp() ), 'hidden_minor_edit' => $rev->isMinor() ? 1 : 0, 'hidden_deleted' => $rev->mDeleted, // FIXME: private field access 'hidden_by_user' => $wgUser->getId(), 'hidden_on_timestamp' => $dbw->timestamp(), 'hidden_reason' => $this->mReason, ), __FUNCTION__, array( 'IGNORE' ) ); } } class SpecialOversight extends SpecialPage { function __construct(){ wfLoadExtensionMessages('HideRevision'); parent::__construct( 'Oversight', 'oversight' ); } /** * Special page handler function for Special:Oversight */ function execute( $par ) { global $wgRequest, $wgUser, $wgRCMaxAge; $this->setHeaders(); if( !$this->userCanExecute( $wgUser ) ){ $this->displayRestrictionError(); return; } $this->outputHeader(); $revision = $wgRequest->getIntOrNull( 'revision' ); if ( $wgRequest->getCheck( 'diff' ) && !is_null( $revision )) { $this->showDiff( $revision); } else if( is_null( $revision ) ) { $this->showList( time() - $wgRCMaxAge ); } else { $this->showRevision( $revision ); } } function showList( $from=null ) { global $wgOut; $dbr = wfGetDB( DB_SLAVE ); $fromTime = $dbr->timestamp( $from ); $result = $this->getRevisions( $dbr, array( 'hidden_on_timestamp >= ' . $dbr->addQuotes( $fromTime ) ) ); $wgOut->addWikiText( wfMsgNoTrans( 'oversight-header' ) ); $wgOut->addHtml( '<ul>' ); while( $row = $dbr->fetchObject( $result ) ) { $wgOut->addHtml( $this->listRow( $row ) ); } $wgOut->addHtml( '</ul>' ); $dbr->freeResult( $result ); } function getRevisions( $db, $condition ) { return $db->select( array( 'hidden', 'user' ), array( 'hidden_page as page_id', 'hidden_namespace as page_namespace', 'hidden_title as page_title', 'hidden_page as rev_page', 'hidden_comment as rev_comment', 'hidden_user as rev_user', 'hidden_user_text as rev_user_text', 'hidden_timestamp as rev_timestamp', 'hidden_minor_edit as rev_minor_edit', 'hidden_deleted as rev_deleted', 'hidden_rev_id as rev_id', 'hidden_text_id as rev_text_id', '0 as rev_len', 'hidden_by_user', 'hidden_on_timestamp', 'hidden_reason', 'user_name', '0 as page_is_new', '0 as rc_id', '1 as rc_patrolled', '0 as rc_old_len', '0 as rc_new_len', '0 as rc_params', 'NULL AS rc_log_action', '0 AS rc_deleted', '0 AS rc_logid', 'NULL AS rc_log_type', 'unhidden_by_user', 'unhidden_on_timestamp', 'unhidden_reason'), array_merge( $condition, array( 'hidden_by_user=user_id' ) ), __FUNCTION__, array( 'ORDER BY' => 'hidden_on_timestamp DESC' ) ); } function listRow( $row ) { global $wgUser, $wgLang; $skin = $wgUser->getSkin(); $self = $this->getTitle(); $userPage = Title::makeTitle( NS_USER, $row->user_name ); $victim = Title::makeTitle( $row->page_namespace, $row->page_title ); if($row->unhidden_by_user){$undid = $row->unhidden_by_user . ' ' . $wgLang->timeanddate( $row->unhidden_on_timestamp ) . " " . $skin->makeLinkObj( $userPage, htmlspecialchars( $userPage->getText() ) ) . " " . wfMsgHTML( 'oversight-log-unhiderev') . " " . $skin->commentBlock( $row->unhidden_reason ); $done = $skin->makeLinkObj( $victim , wfMsgHTML( 'diff' ), "&diff=".$row->rev_id). ") " ;} else{ $done = $row->unhidden_by_user . $skin->makeKnownLinkObj( $self, wfMsgHTML( 'oversight-view' ), 'revision=' . $row->rev_id ) . ") " . "(" . $skin->makeKnownLinkObj( $self, wfMsgHTML( 'diff' ), 'revision=' . $row->rev_id . '&diff=1') . ") " . '(' . $skin->makeKnownLink( 'Special:UnHideRevision', wfMsgHTML( 'editundo' ), 'revision=' . $row->rev_id ) . ") "; $undid =''; } return "<li>(" . $done . ' ' . $wgLang->timeanddate( $row->hidden_on_timestamp ) . " " . $skin->makeLinkObj( $userPage, htmlspecialchars( $userPage->getText() ) ) . " " . wfMsgHTML( 'oversight-log-hiderev', $skin->makeLinkObj( $victim ) ) . " " . $skin->commentBlock( $row->hidden_reason ) . $undid . "</li>\n"; } function showRevision( $revision ) { global $wgOut; $dbr = wfGetDB( DB_SLAVE ); $result = $this->getRevisions( $dbr, array( 'hidden_rev_id' => $revision ) ); while( $row = $dbr->fetchObject( $result ) ) { $info = $this->listRow( $row ); $list = $this->revisionInfo( $row ); $rev = new Revision( $row ); $text = $rev->getText(); $wgOut->addHtml( "<ul>" . $info . "</ul>\n" . $list ); if ( $text === false ) { $wgOut->addWikiText(wfmsg('hiderevision-error-missing')); } else { $wgOut->addHtml( "<div>" . Xml::openElement( 'textarea', array( 'cols' => 80, 'rows' => 25, 'wrap' => 'virtual', 'readonly' => 'readonly' ) ) . htmlspecialchars( $text ) . Xml::closeElement( 'textarea' ) . "</div>" ); } } $dbr->freeResult( $result ); } function revisionInfo( $row ) { global $wgUser; $changes = ChangesList::newFromUser( $wgUser ); $out = $changes->beginRecentChangesList(); $rc = RecentChange::newFromCurRow( $row ); $rc->counter = 0; // ??? $out .= $changes->recentChangesLine( $rc ); $out .= $changes->endRecentChangesList(); return $out; } function showDiff( $revision ){ global $wgOut; $dbr = wfGetDB( DB_SLAVE ); $result = $this->getRevisions( $dbr, array( 'hidden_rev_id' => $revision ) ); while( $row = $dbr->fetchObject( $result ) ) { $info = $this->listRow( $row ); $list = $this->revisionInfo( $row ); $rev = new Revision( $row ); $rev->mTitle = Title::makeTitle( $row->page_namespace, $row->page_title ); $prevId = $rev->mTitle->getPreviousRevisionID( $row->rev_id ); if ( $prevId ) { $prev = Revision::newFromTitle( $rev->mTitle, $prevId ); $otext = strval( $prev->getText()); } else { $wgOut->addHtml( "<ul>" . $info . "</ul>\n" . $list ); $wgOut->addWikiText( wfMsgNoTrans( 'oversight-nodiff' ) ); return; } $ntext = strval( $rev->getText()); $diffEngine = new DifferenceEngine(); $diffEngine->showDiffStyle(); $wgOut->addHtml( "<ul>" . $info . "</ul>\n" . $list . "<p><strong>" . wfMsgHTML('oversight-difference') . "</strong>" . "</p>" . "<div>" . "<table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>" . "<col class='diff-marker' />" . "<col class='diff-content' />" . "<col class='diff-marker' />" . "<col class='diff-content' />" . "<tr>" . "<td colspan='2' width='50%' align='center' class='diff-otitle'>" . wfMsgHTML('oversight-prev') . " (#$prevId)" . "</td>" . "<td colspan='2' width='50%' align='center' class='diff-ntitle'>" . wfMsgHTML('oversight-hidden') . "</td>" . "</tr>" . $diffEngine->generateDiffBody( $otext, $ntext ) . "</table>" . "</div>\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.<br /><br />"); } 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 ) . "<br />" . Xml::inputLabel( wfMsgHTML( 'unhiderevision-reason' ), 'wpReason', 'wpReason', 60 ) . "<br />" . 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 "<br />" . Xml::inputLabel( wfMsgHTML( 'unhiderevision-reason' ), 'wpReason', 'wpReason', 60, $this->mReason ) . "<br />" . 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->immediateCommit(); // 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, ) ); } }
[edit] HideRevision.i18n.php:
<?php $messages = array(); $messages['en'] = array( 'hiderevision' => 'Permanently hide revisions', 'hiderevision-desc' => 'Hide individual revisions from all users for legal reasons, etc.', 'group-oversight' => 'Oversighters', 'group-oversight-member' => 'Oversight', 'grouppage-oversight' => '{{ns:project}}:Oversight', 'right-oversight' => 'View a previously hidden revision', 'right-hiderevision' => 'Hide individual revisions', 'right-unhiderevision' => 'Unhide individual revisions', // Empty form 'hiderevision-prompt' => 'Revision number to remove:', 'hiderevision-continue' => 'Continue', // Confirmation form 'hiderevision-text' => "This should '''only''' be used for the following cases: * Inappropriate personal information *: ''home addresses and telephone numbers, social security numbers, etc'' '''Abuse of this system will result in loss of privileges.''' Removed items will not be visible to anyone through the web site, but the deletions are logged and can be restored if you make a mistake.", 'hiderevision-reason' => 'Reason (will be logged privately):', 'hiderevision-submit' => 'Hide this data permanently', // Tab displayed to allowed users on old revision display 'hiderevision-tab' => 'Hide revision', // Status & errors on action 'hiderevision-norevisions' => 'No revisions specified to delete.', 'hiderevision-noreason' => 'You must decribe the reason for this removal.', 'hiderevision-status' => 'Revision $1: $2', 'hiderevision-success' => 'Archived and deleted successfully.', 'hiderevision-error-missing' => 'Not found in database.', 'hiderevision-error-current' => 'Cannot delete the latest edit to a page. Revert this change first.', 'hiderevision-error-delete' => 'Could not archive; was it previously deleted?', 'hiderevision-archive-status' => 'Deleted revision from $1: $2', 'oversight-nodiff' => 'Unable to track changes as there is currently no previous revision for the page. Either: *The page was deleted *This hidden revision was the first revision', // Logging 'oversight-log-hiderev' => 'removed an edit from $1', 'oversight-log-unhiderev' => 'replaced this edit', // Oversight review page 'oversight' => 'Oversight', 'oversight-view' => 'details', 'oversight-difference' => '(Difference from previous remaining revision)', 'oversight-prev' => 'Last previous revision', 'oversight-hidden' => 'Hidden revision', 'oversight-header' => 'Below is a list of revisions recently permanently hidden from public view. Releasing this information can result in permanent loss of Oversight privileges.', //undo 'unhiderevision' => 'Unhide revisions', 'unhiderevision-prompt' => 'Revision number to replace:', 'unhiderevision-continue' => 'Continue', 'unhiderevision-text' => "This should '''only''' be used if you are '''''sure''''' the revision was deleted in error. '''Abuse of this system will result in loss of privileges.''' Replaced items will seem as if they were mostly untouched, but they are logged privately.", 'unhiderevision-reason' => 'Reason (will be logged privately):', 'unhiderevision-submit' => 'UnHide this data', 'unhiderevision-norevisions' => 'No revisions specified to undelete.', 'unhiderevision-noreason' => 'You must decribe the reason for this replacal.', 'unhiderevision-status' => 'Revision $1: $2', 'unhiderevision-success' => 'Replaced successfully.', 'unhiderevision-error-missing' => 'Not found in database.', 'unhiderevision-error-delete' => 'Could not replace; was it previously deleted?', 'unhiderevision-archive-status' => 'Replaced revision from $1: $2', );