// We should instead work with the Revision object when we need it...
$this->mContent = $revision->userCan( Revision::DELETED_TEXT ) ? $revision->getRawText() : "";
//$this->mContent = $revision->getText();
+ $this->mContent = $revision->revText(); // Loads if user is allowed
$this->mUser = $revision->getUser();
$this->mUserText = $revision->getUserText();
$result = $dbw->affectedRows() != 0;
if ($result) {
- // FIXME: Should the result from updateRedirectOn() be returned instead?
$this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
}
*
* @param boolean $noRedir Add redirect=no
* @param string $sectionAnchor section to redirect to, including "#"
+ * @param string $extraq, extra query params
*/
function doRedirect( $noRedir = false, $sectionAnchor = '', $extraq = '' ) {
global $wgOut;
* @return bool true on success
*/
function updateRestrictions( $limit = array(), $reason = '', $cascade = 0, $expiry = null ) {
- global $wgUser, $wgRestrictionTypes, $wgContLang;
+ global $wgUser, $wgRestrictionTypes, $wgContLang, $wgGroupPermissions;
$id = $this->mTitle->getArticleID();
if( !$wgUser->isAllowed( 'protect' ) || wfReadOnly() || $id == 0 ) {
# If nothing's changed, do nothing
if( $changed ) {
- global $wgGroupPermissions;
if( wfRunHooks( 'ArticleProtect', array( &$this, &$wgUser, $limit, $reason ) ) ) {
$dbw = wfGetDB( DB_MASTER );
$confirm = $wgRequest->wasPosted() &&
$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
$reason = $wgRequest->getText( 'wpReason' );
+ # Flag to hide all contents of the archived revisions
+ $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('deleterevision');
# This code desperately needs to be totally rewritten
}
if( $confirm ) {
- $this->doDelete( $reason );
+ $this->doDelete( $reason, $suppress );
if( $wgRequest->getCheck( 'wpWatch' ) ) {
$this->doWatch();
} elseif( $this->mTitle->userIsWatching() ) {
$delcom = htmlspecialchars( wfMsg( 'deletecomment' ) );
$token = htmlspecialchars( $wgUser->editToken() );
$watch = Xml::checkLabel( wfMsg( 'watchthis' ), 'wpWatch', 'wpWatch', $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching(), array( 'tabindex' => '2' ) );
-
+ if ( $wgUser->isAllowed( 'deleterevision' ) ) {
+ $supress = "<tr><td> </td><td>";
+ $supress .= Xml::checkLabel( wfMsg( 'revdelete-suppress' ), 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '2' ) );
+ $supress .= "</td></tr>";
+ } else {
+ $supress = '';
+ }
+
$wgOut->addHTML( "
<form id='deleteconfirm' method='post' action=\"{$formaction}\">
<table border='0'>
<input type='text' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" tabindex=\"1\" />
</td>
</tr>
+ $supress
<tr>
<td> </td>
<td>$watch</td>
/**
* Perform a deletion and output success or failure messages
*/
- function doDelete( $reason ) {
+ function doDelete( $reason, $suppress = false ) {
global $wgOut, $wgUser;
wfDebug( __METHOD__."\n" );
if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) {
- if ( $this->doDeleteArticle( $reason ) ) {
+ if ( $this->doDeleteArticle( $reason, $suppress ) ) {
$deleted = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
$wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
$wgOut->returnToMain( false );
wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason));
} else {
- $wgOut->showFatalError( wfMsg( 'cannotdelete' ) );
+ $wgOut->showFatalError( wfMsg( 'cannotdelete' ).'<br/>'.wfMsg('cannotdelete-merge') );
}
}
}
* Deletes the article with database consistency, writes logs, purges caches
* Returns success
*/
- function doDeleteArticle( $reason ) {
+ function doDeleteArticle( $reason, $suppress = false ) {
global $wgUseSquid, $wgDeferredUpdateList;
global $wgUseTrackbacks;
if ( $t == '' || $id == 0 ) {
return false;
}
+ // Do not fuck up histories by merging them in annoying, unrevertable ways
+ // This page id should match any deleted ones (excepting NULL values)
+ $otherpages = $dbw->selectField( 'archive', 'COUNT(*)',
+ array('ar_namespace' => $ns, 'ar_title' => $t,
+ 'ar_page_id IS NOT NULL', "ar_page_id != $id" ),
+ __METHOD__ );
+ if( $otherpages )
+ return false;
$u = new SiteStatsUpdate( 0, 1, -(int)$this->isCountable( $this->getContent() ), -1 );
array_push( $wgDeferredUpdateList, $u );
+ // Bitfields to further supress the content
+ if ( $suppress ) {
+ $bitfield = 0;
+ // This should be 15...
+ $bitfield |= Revision::DELETED_TEXT;
+ $bitfield |= Revision::DELETED_COMMENT;
+ $bitfield |= Revision::DELETED_USER;
+ $bitfield |= Revision::DELETED_RESTRICTED;
+ } else {
+ $bitfield = 'rev_deleted';
+ }
+
// For now, shunt the revision data into the archive table.
// Text is *not* removed from the text table; bulk storage
// is left intact to avoid breaking block-compression or
'ar_flags' => '\'\'', // MySQL's "strict mode"...
'ar_len' => 'rev_len',
'ar_page_id' => 'page_id',
+ 'ar_deleted' => $bitfield
), array(
'page_id' => $id,
'page_id = rev_page'
# Clear caches
Article::onArticleDelete( $this->mTitle );
- # Log the deletion
- $log = new LogPage( 'delete' );
+ # Log the deletion, if the page was suppressed, log it at Oversight instead
+ $logtype = ($suppress) ? 'oversight' : 'delete';
+ $log = new LogPage( $logtype );
$log->addEntry( 'delete', $this->mTitle, $reason );
# Clear the cached article id so the interface doesn't act like we exist
);
}
- # Get the edit summary
$target = Revision::newFromId( $s->rev_id );
+ # Revision *must* be public and we don't well handle deleted edits on top
+ if ( $target->isDeleted(REVISION::DELETED_TEXT) ) {
+ $wgOut->setPageTitle( wfMsg('rollbackfailed') );
+ $wgOut->addHTML( wfMsg( 'missingarticle' ) );
+ }
+ # Get the edit summary
if( empty( $summary ) )
$summary = wfMsgForContent( 'revertpage', $target->getUserText(), $from );
? wfMsg( 'diff' )
: $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=next&oldid='.$oldid );
- $userlinks = $sk->userLink( $revision->getUser(), $revision->getUserText() )
- . $sk->userToolLinks( $revision->getUser(), $revision->getUserText() );
+ $cdel='';
+ if( $wgUser->isAllowed( 'deleterevision' ) ) {
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+ if( $revision->isCurrent() ) {
+ // We don't handle top deleted edits too well
+ $cdel = wfMsgHtml('rev-delundel');
+ } else if( !$revision->userCan( Revision::DELETED_RESTRICTED ) ) {
+ // If revision was hidden from sysops
+ $cdel = wfMsgHtml('rev-delundel');
+ } else {
+ $cdel = $sk->makeKnownLinkObj( $revdel,
+ wfMsgHtml('rev-delundel'),
+ 'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) .
+ '&oldid=' . urlencode( $oldid ) );
+ // Bolden oversighted content
+ if( $revision->isDeleted( Revision::DELETED_RESTRICTED ) )
+ $cdel = "<strong>$cdel</strong>";
+ }
+ $cdel = "(<small>$cdel</small>) ";
+ }
+
+ $userlinks = $sk->revUserTools( $revision, true );
$m = wfMsg( 'revision-info-current' );
$infomsg = $current && !wfEmptyMsg( 'revision-info-current', $m ) && $m != '-'
: 'revision-info';
$r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" . wfMsg( $infomsg, $td, $userlinks ) . "</div>\n" .
- "\n\t\t\t\t<div id=\"mw-revision-nav\">" . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
+
+ "\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
$wgOut->setSubtitle( $r );
}
: $nothing;
$f .= $bot ? '<span class="bot">' . $this->message['boteditletter'] . '</span>' : $nothing;
$f .= $patrolled ? '<span class="unpatrolled">!</span>' : $nothing;
- return $f;
+ return "<tt>$f</tt>";
}
/**
}
}
+ /**
+ * int $field one of DELETED_* bitfield constants
+ * @return bool
+ */
+ function isDeleted( $rc, $field ) {
+ return ($rc->mAttribs['rc_deleted'] & $field) == $field;
+ }
+
+ /**
+ * Determine if the current user is allowed to view a particular
+ * field of this revision, if it's marked as deleted.
+ * @param int $field
+ * @return bool
+ */
+ function userCan( $rc, $field ) {
+ if( ( $rc->mAttribs['rc_deleted'] & $field ) == $field ) {
+ global $wgUser;
+ $permission = ( $rc->mAttribs['rc_deleted'] & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED
+ ? 'hiderevision'
+ : 'deleterevision';
+ wfDebug( "Checking for $permission due to $field match on $rc->mAttribs['rc_deleted']\n" );
+ return $wgUser->isAllowed( $permission );
+ } else {
+ return true;
+ }
+ }
function insertMove( &$s, $rc ) {
# Diff
$s .= '(' . $this->skin->makeKnownLinkObj($title, $logname ) . ')';
}
-
function insertDiffHist(&$s, &$rc, $unpatrolled) {
# Diff link
- if( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) {
+ if( !$this->userCan($rc,Revision::DELETED_TEXT) ) {
+ $diffLink = $this->message['diff'];
+ } else if( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) {
$diffLink = $this->message['diff'];
} else {
$rcidparam = $unpatrolled
$params = ( $unpatrolled && $rc->mAttribs['rc_type'] == RC_NEW )
? 'rcid='.$rc->mAttribs['rc_id']
: '';
- $articlelink = ' '. $this->skin->makeKnownLinkObj( $rc->getTitle(), '', $params );
+ if( $this->isDeleted($rc,Revision::DELETED_TEXT) ) {
+ $articlelink = $this->skin->makeKnownLinkObj( $rc->getTitle(), '', $params );
+ $articlelink = '<span class="history-deleted">'.$articlelink.'</span>';
+ } else {
+ $articlelink = ' '. $this->skin->makeKnownLinkObj( $rc->getTitle(), '', $params );
+ }
if( $watched )
$articlelink = "<strong class=\"mw-watched\">{$articlelink}</strong>";
global $wgContLang;
/** Insert links to user page, user talk page and eventually a blocking link */
function insertUserRelatedLinks(&$s, &$rc) {
- $s .= $this->skin->userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
- $s .= $this->skin->userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+ if ( $this->isDeleted($rc,Revision::DELETED_USER) ) {
+ $s .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-user') . '</span>';
+ } else {
+ $s .= $this->skin->userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+ $s .= $this->skin->userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+ }
+ }
+
+ /** insert a formatted action */
+ function insertAction(&$s, &$rc) {
+ # Add comment
+ if( $rc->mAttribs['rc_type'] == RC_LOG ) {
+ // log action
+ if ( $this->isDeleted($rc,LogViewer::DELETED_ACTION) ) {
+ $s .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+ } else {
+ $s .= ' ' . LogPage::actionText( $rc->mAttribs['rc_log_type'], $rc->mAttribs['rc_log_action'],
+ $rc->getTitle(), $this->skin, LogPage::extractParams($rc->mAttribs['rc_params']), true, true );
+ }
+ }
}
/** insert a formatted comment */
function insertComment(&$s, &$rc) {
# Add comment
if( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) {
- $s .= $this->skin->commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
+ // log comment
+ if ( $this->isDeleted($rc,Revision::DELETED_COMMENT) ) {
+ $s .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-comment') . '</span>';
+ } else {
+ $s .= $this->skin->commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
+ }
}
}
$s .= '<li>';
- // moved pages
+ // Moved pages
if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
$this->insertMove( $s, $rc );
- // log entries
- } elseif ( $rc_namespace == NS_SPECIAL ) {
+ // Log entries
+ } elseif( $rc_log_type !='' ) {
+ $logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
+ $this->insertLog( $s, $logtitle, $rc_log_type );
+ // Log entries (old format) or log targets, and special pages
+ } elseif( $rc_namespace == NS_SPECIAL ) {
list( $specialName, $specialSubpage ) = SpecialPage::resolveAliasWithSubpage( $rc_title );
if ( $specialName == 'Log' ) {
$this->insertLog( $s, $rc->getTitle(), $specialSubpage );
} else {
wfDebug( "Unexpected special page in recentchanges\n" );
}
- // all other stuff
+ // Log entries
} else {
wfProfileIn($fname.'-page');
}
$this->insertUserRelatedLinks($s,$rc);
+ $this->insertAction($s, $rc);
$this->insertComment($s, $rc);
-
- $s .= rtrim(' ' . $this->numberofWatchingusers($rc->numberofWatchingusers));
+
+ # Mark revision as deleted
+ if ( !$rc_log_type && $this->isDeleted($rc,Revision::DELETED_TEXT) )
+ $s .= ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
+ if($rc->numberofWatchingusers > 0) {
+ $s .= ' ' . wfMsg('number_of_watching_users_RCview', $wgContLang->formatNum($rc->numberofWatchingusers));
+ }
$s .= "</li>\n";
$rc->unpatrolled = false;
}
+ $showrev=true;
# Make article link
if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
$msg = ( $rc_type == RC_MOVE ) ? "1movedto2" : "1movedto2_redir";
$clink = wfMsg( $msg, $this->skin->makeKnownLinkObj( $rc->getTitle(), '', 'redirect=no' ),
$this->skin->makeKnownLinkObj( $rc->getMovedToTitle(), '' ) );
- } elseif( $rc_namespace == NS_SPECIAL ) {
+ } else if( $rc_namespace == NS_SPECIAL ) {
+ // Log entries (old format) and special pages
list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $rc_title );
if ( $specialName == 'Log' ) {
# Log updates, etc
wfDebug( "Unexpected special page in recentchanges\n" );
$clink = '';
}
- } elseif( $rc->unpatrolled && $rc_type == RC_NEW ) {
+ } elseif ( $rc_log_type !='' ) {
+ // Log entries
+ $logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
+ $logname = LogPage::logName( $rc_log_type );
+ $clink = '(' . $this->skin->makeKnownLinkObj($logtitle, $logname ) . ')';
+ } if ( $this->isDeleted($rc,Revision::DELETED_TEXT) ) {
+ $clink = '<span class="history-deleted">' . $this->skin->makeKnownLinkObj( $rc->getTitle(), '' ) . '</span>';
+ if ( !ChangesList::userCan($rc,Revision::DELETED_TEXT) )
+ $showrev=false;
+ } else if( $rc->unpatrolled && $rc_type == RC_NEW ) {
# Unpatrolled new page, give rc_id in query
$clink = $this->skin->makeKnownLinkObj( $rc->getTitle(), '', "rcid={$rc_id}" );
} else {
$querydiff = $curIdEq."&diff=$rc_this_oldid&oldid=$rc_last_oldid$rcIdQuery";
$aprops = ' tabindex="'.$baseRC->counter.'"';
$curLink = $this->skin->makeKnownLinkObj( $rc->getTitle(), $this->message['cur'], $querycur, '' ,'', $aprops );
- if( $rc_type == RC_NEW || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
+ if ( !$showrev ) {
+ $curLink = $this->message['cur'];
+ $diffLink = $this->message['diff'];
+ } else if( $rc_type == RC_NEW || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
if( $rc_type != RC_NEW ) {
$curLink = $this->message['cur'];
}
}
# Make "last" link
- if( $rc_last_oldid == 0 || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
+ if ( !$showrev ) {
+ $lastLink = $this->message['last'];
+ } else if( $rc_last_oldid == 0 || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
$lastLink = $this->message['last'];
} else {
$lastLink = $this->skin->makeKnownLinkObj( $rc->getTitle(), $this->message['last'],
- $curIdEq.'&diff='.$rc_this_oldid.'&oldid='.$rc_last_oldid . $rcIdQuery );
+ $curIdEq.'&diff='.$rc_this_oldid.'&oldid='.$rc_last_oldid . $rcIdQuery );
+ }
+
+ # Make user links
+ if ( $this->isDeleted($rc,Revision::DELETED_USER) ) {
+ $rc->userlink = ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-user') . '</span>';
+ } else {
+ $rc->userlink = $this->skin->userLink( $rc_user, $rc_user_text );
+ $rc->usertalklink = $this->skin->userToolLinks( $rc_user, $rc_user_text );
}
-
- $rc->userlink = $this->skin->userLink( $rc_user, $rc_user_text );
$rc->lastlink = $lastLink;
$rc->curlink = $curLink;
$rc->difflink = $diffLink;
- $rc->usertalklink = $this->skin->userToolLinks( $rc_user, $rc_user_text );
-
# Put accumulated information into the cache, for later display
# Page moves go on their own line
$title = $rc->getTitle();
*/
function recentChangesBlockGroup( $block ) {
global $wgLang, $wgContLang, $wgRCShowChangedSize;
- $r = '';
+ $r = '<table cellpadding="0" cellspacing="0"><tr>';
# Collate list of users
$isnew = false;
+ $namehidden = true;
$unpatrolled = false;
$userlinks = array();
foreach( $block as $rcObj ) {
if( $rcObj->mAttribs['rc_new'] ) {
$isnew = true;
}
+ // if all log actions to this page were hidden, then don't
+ // give the name of the affected page for this block
+ if( !($rcObj->mAttribs['rc_deleted'] & LogViewer::DELETED_ACTION) ) {
+ $namehidden = false;
+ }
$u = $rcObj->userlink;
if( !isset( $userlinks[$u] ) ) {
$userlinks[$u] = 0;
$toggleLink = "javascript:toggleVisibility('$rci','$rcm','$rcl')";
$tl = '<span id="'.$rcm.'"><a href="'.$toggleLink.'">' . $this->sideArrow() . '</a></span>';
$tl .= '<span id="'.$rcl.'" style="display:none"><a href="'.$toggleLink.'">' . $this->downArrow() . '</a></span>';
- $r .= $tl;
+ $r .= '<td valign="top">'.$tl;
# Main line
- $r .= '<tt>';
- $r .= $this->recentChangesFlags( $isnew, false, $unpatrolled, ' ', $bot );
+ $r .= ' '.$this->recentChangesFlags( $isnew, false, $unpatrolled, ' ', $bot );
# Timestamp
- $r .= ' '.$block[0]->timestamp.' </tt>';
+ $r .= ' '.$block[0]->timestamp.' </td><td>';
# Article link
- $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
+ if ( $namehidden )
+ $r .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+ else
+ $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
$r .= $wgContLang->getDirMark();
$curIdEq = 'curid=' . $block[0]->mAttribs['rc_cur_id'];
$currentRevision = $block[0]->mAttribs['rc_this_oldid'];
if( $block[0]->mAttribs['rc_type'] != RC_LOG ) {
# Changes
-
$n = count($block);
static $nchanges = array();
if ( !isset( $nchanges[$n] ) ) {
$r .= ' (';
- if( $isnew ) {
+ if( !ChangesList::userCan($rcObj,Revision::DELETED_TEXT) ) {
+ $r .= $nchanges[$n];
+ } else if( $isnew ) {
$r .= $nchanges[$n];
} else {
$r .= $this->skin->makeKnownLinkObj( $block[0]->getTitle(),
$nchanges[$n], $curIdEq."&diff=$currentRevision&oldid=$oldid" );
}
- $r .= ') . . ';
-
if( $wgRCShowChangedSize ) {
# Character difference
$chardiff = $rcObj->getCharacterDifference( $block[ count( $block ) - 1 ]->mAttribs['rc_old_len'],
$block[0]->mAttribs['rc_new_len'] );
if( $chardiff == '' ) {
- $r .= ' (';
+ $r .= ') ';
} else {
$r .= ' ' . $chardiff. ' . . ';
}
- }
+ }
# History
$r .= '(' . $this->skin->makeKnownLinkObj( $block[0]->getTitle(),
}
$r .= $users;
-
- $r .= $this->numberofWatchingusers($block[0]->numberofWatchingusers);
- $r .= "<br />\n";
+ $r .=$this->numberofWatchingusers($block[0]->numberofWatchingusers);
+
+ $r .= "</td></tr></table>\n";
# Sub-entries
- $r .= '<div id="'.$rci.'" style="display:none">';
+ $r .= '<div id="'.$rci.'" style="display:none; font-size:95%;"><table cellpadding="0" cellspacing="0">';
foreach( $block as $rcObj ) {
# Get rc_xxxx variables
// FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( $rcObj->mAttribs );
- $r .= $this->spacerArrow();
- $r .= '<tt> ';
+ #$r .= '<tr><td valign="top">'.$this->spacerArrow();
+ $r .= '<tr><td valign="top">'.$this->spacerIndent();
+ $r .= ' ';
$r .= $this->recentChangesFlags( $rc_new, $rc_minor, $rcObj->unpatrolled, ' ', $rc_bot );
- $r .= ' </tt>';
+ $r .= ' </td><td valign="top">';
$o = '';
if( $rc_this_oldid != 0 ) {
$o = 'oldid='.$rc_this_oldid;
}
+ # Revision link
if( $rc_type == RC_LOG ) {
- $link = $rcObj->timestamp;
+ $link = $rcObj->timestamp.' ';
+ } else if( !ChangesList::userCan($rcObj,Revision::DELETED_TEXT) ) {
+ $link = '<span class="history-deleted">'.$rcObj->timestamp.'</span> ';
} else {
$link = $this->skin->makeKnownLinkObj( $rcObj->getTitle(), $rcObj->timestamp, $curIdEq.'&'.$o );
+ if( $this->isDeleted($rcObj,Revision::DELETED_TEXT) )
+ $link = '<span class="history-deleted">'.$link.'</span> ';
}
- $link = '<tt>'.$link.'</tt>';
-
$r .= $link;
- $r .= ' (';
- $r .= $rcObj->curlink;
- $r .= '; ';
- $r .= $rcObj->lastlink;
- $r .= ') . . ';
+
+ if ( !$rc_log_type ) {
+ $r .= ' (';
+ $r .= $rcObj->curlink;
+ $r .= '; ';
+ $r .= $rcObj->lastlink;
+ $r .= ')';
+ } else {
+ $logname = LogPage::logName( $rc_log_type );
+ $logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
+ $r .= '(' . $this->skin->makeKnownLinkObj($logtitle, $logname ) . ')';
+ }
+ $r .= ' . . ';
# Character diff
if( $wgRCShowChangedSize ) {
$r .= ( $rcObj->getCharacterDifference() == '' ? '' : $rcObj->getCharacterDifference() . ' . . ' ) ;
}
-
+ # User links
$r .= $rcObj->userlink;
$r .= $rcObj->usertalklink;
- $r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() );
- $r .= "<br />\n";
+ // log action
+ parent::insertAction($r, $rcObj);
+ // log comment
+ parent::insertComment($r, $rcObj);
+ # Mark revision as deleted
+ if ( !$rc_log_type && $this->isDeleted($rcObj,Revision::DELETED_TEXT) )
+ $s .= ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
+
+ $r .= "</td></tr>\n";
}
- $r .= "</div>\n";
+ $r .= "</table></div>\n";
$this->rcCacheIndex++;
return $r;
* @access private
*/
function spacerArrow() {
+ //FIXME: problems with FF 1.5x
return $this->arrow( '', ' ' );
}
+
+ /**
+ * Generate HTML for the equivilant of a spacer image for tables
+ * @return string HTML <td> tag
+ * @access private
+ */
+ function spacerColumn() {
+ return '<td width="12"></td>';
+ }
+
+ // Adds a few spaces
+ function spacerIndent() {
+ return ' ';
+ }
/**
* Enhanced RC ungrouped line.
extract( $rcObj->mAttribs );
$curIdEq = 'curid='.$rc_cur_id;
- $r = '';
-
- # Spacer image
- $r .= $this->spacerArrow();
+ $r = '<table cellspacing="0" cellpadding="0"><tr><td>';
+ # spacerArrow() causes issues in FF
+ $r .= $this->spacerColumn();
+ $r .= '<td valign="top">';
+
# Flag and Timestamp
- $r .= '<tt>';
-
if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
- $r .= ' ';
+ $r .= ' ';
} else {
- $r .= $this->recentChangesFlags( $rc_type == RC_NEW, $rc_minor, $rcObj->unpatrolled, ' ', $rc_bot );
+ $r .= ' '.$this->recentChangesFlags( $rc_type == RC_NEW, $rc_minor, $rcObj->unpatrolled, ' ', $rc_bot );
}
- $r .= ' '.$rcObj->timestamp.' </tt>';
-
+ $r .= ' '.$rcObj->timestamp.' </td><td>';
+
# Article link
- $r .= $this->maybeWatchedLink( $rcObj->link, $rcObj->watched );
-
- # Diff
- $r .= ' ('. $rcObj->difflink .'; ';
-
- # Hist
- $r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), wfMsg( 'hist' ), $curIdEq.'&action=history' ) . ') . . ';
-
+ if ( $rc_log_type !='' ) {
+ $logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
+ $logname = LogPage::logName( $rc_log_type );
+ $r .= '(' . $this->skin->makeKnownLinkObj($logtitle, $logname ) . ')';
+ // All other stuff
+ } else {
+ $r .= $this->maybeWatchedLink( $rcObj->link, $rcObj->watched );
+ }
+ if ( $rc_type != RC_LOG ) {
+ # Diff
+ $r .= ' ('. $rcObj->difflink .'; ';
+ # Hist
+ $r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), wfMsg( 'hist' ), $curIdEq.'&action=history' ) . ')';
+ }
+ $r .= ' . . ';
+
# Character diff
if( $wgRCShowChangedSize ) {
$r .= ( $rcObj->getCharacterDifference() == '' ? '' : ' ' . $rcObj->getCharacterDifference() . ' . . ' ) ;
}
# User/talk
- $r .= $rcObj->userlink . $rcObj->usertalklink;
+ $r .= ' '.$rcObj->userlink . $rcObj->usertalklink;
# Comment
if( $rc_type != RC_MOVE && $rc_type != RC_MOVE_OVER_REDIRECT ) {
- $r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() );
+ // log action
+ if ( $this->isDeleted($rcObj,LogViewer::DELETED_ACTION) ) {
+ $r .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+ } else {
+ $r .= ' ' . LogPage::actionText( $rc_log_type, $rc_log_action, $rcObj->getTitle(), $this->skin, LogPage::extractParams($rc_params), true, true );
+ }
+ // log comment
+ if ( $this->isDeleted($rcObj,LogViewer::DELETED_COMMENT) ) {
+ $r .= ' <span class="history-deleted">' . wfMsg('rev-deleted-comment') . '</span>';
+ } else {
+ $r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() );
+ }
}
$r .= $this->numberofWatchingusers($rcObj->numberofWatchingusers);
- $r .= "<br />\n";
+ $r .= "</td></tr></table>\n";
return $r;
}
* $wgFileStore['deleted']['directory'] = '/var/wiki/private/deleted';
*
*/
+// For deleted images, gererally were all versions of the image are discarded
$wgFileStore = array();
$wgFileStore['deleted']['directory'] = false;// Defaults to $wgUploadDirectory/deleted
$wgFileStore['deleted']['url'] = null; // Private
$wgFileStore['deleted']['hash'] = 3; // 3-level subdirectory split
+// For revisions of images marked as hidden
+// These are kept even if $wgSaveDeletedFiles is set to false
+$wgFileStore['hidden']['directory'] = false;// Defaults to $wgUploadDirectory/hidden
+$wgFileStore['hidden']['url'] = null; // Private
+$wgFileStore['hidden']['hash'] = 3; // 3-level subdirectory split
/**#@+
* File repository structures
$wgGroupPermissions['sysop']['block'] = true;
$wgGroupPermissions['sysop']['createaccount'] = true;
$wgGroupPermissions['sysop']['delete'] = true;
+$wgGroupPermissions['sysop']['browsearchive'] = true; // can see the deleted page list
$wgGroupPermissions['sysop']['deletedhistory'] = true; // can view deleted history entries, but not see or restore the text
$wgGroupPermissions['sysop']['editinterface'] = true;
$wgGroupPermissions['sysop']['editusercssjs'] = true;
$wgGroupPermissions['sysop']['autoconfirmed'] = true;
$wgGroupPermissions['sysop']['upload_by_url'] = true;
$wgGroupPermissions['sysop']['ipblock-exempt'] = true;
+$wgGroupPermissions['sysop']['deleterevision'] = true;
$wgGroupPermissions['sysop']['blockemail'] = true;
+$wgGroupPermissions['sysop']['mergehistory'] = true;
// Permission to change users' group assignments
$wgGroupPermissions['bureaucrat']['userrights'] = true;
-// Experimental permissions, not ready for production use
-//$wgGroupPermissions['sysop']['deleterevision'] = true;
-//$wgGroupPermissions['bureaucrat']['hiderevision'] = true;
+// To hide usernames
+$wgGroupPermissions['oversight']['hideuser'] = true;
+// To see hidden revs and unhide revs hidden from Sysops
+$wgGroupPermissions['oversight']['hiderevision'] = true;
+// For private log access
+$wgGroupPermissions['oversight']['oversight'] = true;
+
+$wgAllowLogDeletion = false;
/**
* The developer group is deprecated, but can be activated if need be
'move',
'import',
'patrol',
+ 'merge',
+ 'oversight',
+);
+
+/**
+ * This restricts log access to those who have a certain right
+ * Users without this will not see it in the option menu and can not view it
+ * Restricted logs are not added to recent changes
+ * Logs should remain non-transcludable
+ */
+$wgLogRestrictions = array(
+ 'oversight' => 'oversight'
);
/**
'move' => 'movelogpage',
'import' => 'importlogpage',
'patrol' => 'patrol-log-page',
+ 'merge' => 'mergelog',
+ 'oversight' => 'oversightlog',
);
/**
'move' => 'movelogpagetext',
'import' => 'importlogpagetext',
'patrol' => 'patrol-log-header',
+ 'merge' => 'mergelogpagetext',
+ 'oversight' => 'overlogpagetext',
);
/**
* Extensions with custom log types may add to this array.
*/
$wgLogActions = array(
- 'block/block' => 'blocklogentry',
- 'block/unblock' => 'unblocklogentry',
- 'protect/protect' => 'protectedarticle',
- 'protect/modify' => 'modifiedarticleprotection',
- 'protect/unprotect' => 'unprotectedarticle',
- 'rights/rights' => 'rightslogentry',
- 'delete/delete' => 'deletedarticle',
- 'delete/restore' => 'undeletedarticle',
- 'delete/revision' => 'revdelete-logentry',
- 'upload/upload' => 'uploadedimage',
- 'upload/overwrite' => 'overwroteimage',
- 'upload/revert' => 'uploadedimage',
- 'move/move' => '1movedto2',
- 'move/move_redir' => '1movedto2_redir',
- 'import/upload' => 'import-logentry-upload',
- 'import/interwiki' => 'import-logentry-interwiki',
+ 'block/block' => 'blocklogentry',
+ 'block/unblock' => 'unblocklogentry',
+ 'protect/protect' => 'protectedarticle',
+ 'protect/modify' => 'modifiedarticleprotection',
+ 'protect/unprotect' => 'unprotectedarticle',
+ 'rights/rights' => 'rightslogentry',
+ 'delete/delete' => 'deletedarticle',
+ 'delete/restore' => 'undeletedarticle',
+ 'delete/revision' => 'revdelete-logentry',
+ 'delete/event' => 'logdelete-logentry',
+ 'upload/upload' => 'uploadedimage',
+ 'upload/overwrite' => 'overwroteimage',
+ 'upload/revert' => 'uploadedimage',
+ 'move/move' => '1movedto2',
+ 'move/move_redir' => '1movedto2_redir',
+ 'import/upload' => 'import-logentry-upload',
+ 'import/interwiki' => 'import-logentry-interwiki',
+ 'merge/merge' => 'pagemerge-logentry',
+ 'oversight/revision' => 'revdelete-logentry',
+ 'oversight/file' => 'revdelete-logentry',
+ 'oversight/event' => 'logdelete-logentry',
+ 'oversight/delete' => 'suppressedarticle',
+ 'oversight/block' => 'blocklogentry',
);
/**
# Show diff between revision $old and the previous one.
# Get previous one from DB.
#
- $this->mNewid = intval($old);
+ $this->mNewid = intval($old);
$this->mOldid = $this->mTitle->getPreviousRevisionID( $this->mNewid );
$this->mNewid = 0;
}
+ } else if( 'cur' === $new ) {
+ # Show diff between revision $old and the current one.
+ # Get previous one from DB.
+ #
+ $this->mNewid = $this->mTitle->getLatestRevID();
+
+ $this->mOldid = intval($old);
} else {
$this->mOldid = intval($old);
$this->mNewid = intval($new);
$newminor = wfElement( 'span', array( 'class' => 'minor' ),
wfMsg( 'minoreditletter') ) . ' ';
}
+
+ $rdel = ''; $ldel = '';
+ if( $wgUser->isAllowed( 'deleterevision' ) ) {
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+ if( !$this->mOldRev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ // If revision was hidden from sysops
+ $ldel = wfMsgHtml('rev-delundel');
+ } else {
+ $ldel = $sk->makeKnownLinkObj( $revdel,
+ wfMsgHtml('rev-delundel'),
+ 'target=' . urlencode( $this->mOldRev->mTitle->getPrefixedDbkey() ) .
+ '&oldid=' . urlencode( $this->mOldRev->getId() ) );
+ // Bolden oversighted content
+ if( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) )
+ $ldel = "<strong>$ldel</strong>";
+ }
+ $ldel = " <tt>(<small>$ldel</small>)</tt> ";
+ // We don't currently handle well changing the top revision's settings
+ if( $this->mNewRev->isCurrent() ) {
+ // If revision was hidden from sysops
+ $rdel = wfMsgHtml('rev-delundel');
+ } else if( !$this->mNewRev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ // If revision was hidden from sysops
+ $rdel = wfMsgHtml('rev-delundel');
+ } else {
+ $rdel = $sk->makeKnownLinkObj( $revdel,
+ wfMsgHtml('rev-delundel'),
+ 'target=' . urlencode( $this->mNewRev->mTitle->getPrefixedDbkey() ) .
+ '&oldid=' . urlencode( $this->mNewRev->getId() ) );
+ // Bolden oversighted content
+ if( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) )
+ $rdel = "<strong>$rdel</strong>";
+ }
+ $rdel = " <tt>(<small>$rdel</small>)</tt> ";
+ }
- $oldHeader = '<div id="mw-diff-otitle1"><strong>' . $this->mOldtitle . '</strong></div>' .
- '<div id="mw-diff-otitle2">' . $sk->revUserTools( $this->mOldRev ) . "</div>" .
- '<div id="mw-diff-otitle3">' . $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly ) . "</div>" .
- '<div id="mw-diff-otitle4">' . $prevlink . '</div>';
- $newHeader = '<div id="mw-diff-ntitle1"><strong>' .$this->mNewtitle . '</strong></div>' .
- '<div id="mw-diff-ntitle2">' . $sk->revUserTools( $this->mNewRev ) . " $rollback</div>" .
- '<div id="mw-diff-ntitle3">' . $newminor . $sk->revComment( $this->mNewRev, !$diffOnly ) . "</div>" .
+ $oldHeader = '<div id="mw-diff-otitle1"><strong>'.$this->mOldtitle.'</strong></div>' .
+ '<div id="mw-diff-otitle2">' . $sk->revUserTools( $this->mOldRev, true ) . "</div>" .
+ '<div id="mw-diff-otitle3">' . $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly, true ) . $ldel . "</div>" .
+ '<div id="mw-diff-otitle4">' . $prevlink .'</div>';
+ $newHeader = '<div id="mw-diff-ntitle1"><strong>'.$this->mNewtitle.'</strong></div>' .
+ '<div id="mw-diff-ntitle2">' . $sk->revUserTools( $this->mNewRev, true ) . " $rollback</div>" .
+ '<div id="mw-diff-ntitle3">' . $newminor . $sk->revComment( $this->mNewRev, !$diffOnly, true ) . $rdel . "</div>" .
'<div id="mw-diff-ntitle4">' . $nextlink . $patrol . '</div>';
$this->showDiff( $oldHeader, $newHeader );
$wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" );
#add deleted rev tag if needed
- if ( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
+ if( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
$wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) );
+ } else if( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
+ $wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) );
}
if( !$this->mNewRev->isCurrent() ) {
} // don't try to load but save the result
}
- #loadtext is permission safe, this just clears out the diff
+ // Loadtext is permission safe, this just clears out the diff
if ( !$this->loadText() ) {
wfProfileOut( $fname );
return false;
} else if ( $this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) {
- return '';
+ return '';
} else if ( $this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
- return '';
+ return '';
}
$difftext = $this->generateDiffBody( $this->mOldtext, $this->mNewtext );
// Save to cache for 7 days
- if ( $key !== false && $difftext !== false ) {
+ // Only do this for public revs, otherwise an admin can view the diff and a non-admin can nab it!
+ if ( $this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
+ wfIncrStats( 'diff_uncacheable' );
+ } else if ( $this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
+ wfIncrStats( 'diff_uncacheable' );
+ } else if ( $key !== false && $difftext !== false ) {
wfIncrStats( 'diff_cache_miss' );
$wgMemc->set( $key, $difftext, 7*86400 );
} else {
/**
* Add the header to a diff body
*/
- function addHeader( $diff, $otitle, $ntitle, $multi = '' ) {
+ static function addHeader( $diff, $otitle, $ntitle, $multi = '' ) {
global $wgOut;
-
- if ( $this->mOldRev && $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
- $otitle = '<span class="history-deleted">'.$otitle.'</span>';
- }
- if ( $this->mNewRev && $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
- $ntitle = '<span class="history-deleted">'.$ntitle.'</span>';
- }
+
$header = "
<table class='diff'>
<col class='diff-marker' />
: Revision::newFromTitle( $this->mTitle );
if( !$this->mNewRev instanceof Revision )
return false;
-
+
// Update the new revision ID in case it was 0 (makes life easier doing UI stuff)
$this->mNewid = $this->mNewRev->getId();
-
+
// Set assorted variables
$timestamp = $wgLang->timeanddate( $this->mNewRev->getTimestamp(), true );
$this->mNewPage = $this->mNewRev->getTitle();
$this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>"
. " (<a href='$newEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
}
+ if ( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) {
+ $this->mNewtitle = "<span class='history-deleted'>{$this->mPagetitle}</span>";
+ } else if ( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) {
+ $this->mNewtitle = '<span class="history-deleted">'.$this->mNewtitle.'</span>';
+ }
// Load the old revision object
$this->mOldRev = false;
$t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true );
$oldLink = $this->mOldPage->escapeLocalUrl( 'oldid=' . $this->mOldid );
$oldEdit = $this->mOldPage->escapeLocalUrl( 'action=edit&oldid=' . $this->mOldid );
- $this->mOldtitle = "<a href='$oldLink'>" . htmlspecialchars( wfMsg( 'revisionasof', $t ) )
- . "</a> (<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
+ $this->mOldPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $t ) );
+ $this->mOldtitle = "<a href='$oldLink'>{$this->mOldPagetitle}</a>"
+ . "(<a href='$oldEdit'>" . htmlspecialchars( wfMsg( 'editold' ) ) . "</a>)";
// Add an "undo" link
$newUndo = $this->mNewPage->escapeLocalUrl( 'action=edit&undoafter=' . $this->mOldid . '&undo=' . $this->mNewid);
- $this->mNewtitle .= " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)";
+ if ( $this->mNewRev->userCan(Revision::DELETED_TEXT) )
+ $this->mNewtitle .= " (<a href='$newUndo'>" . htmlspecialchars( wfMsg( 'editundo' ) ) . "</a>)";
+
+ if ( !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) {
+ $this->mOldtitle = "<span class='history-deleted'>{$this->mOldPagetitle}</span>";
+ } else if ( $this->mOldRev->isDeleted(Revision::DELETED_TEXT) ) {
+ $this->mOldtitle = '<span class="history-deleted">'.$this->mOldtitle.'</span>';
+ }
}
return true;
return false;
}
if ( $this->mOldRev ) {
- // FIXME: permission tests
$this->mOldtext = $this->mOldRev->revText();
if ( $this->mOldtext === false ) {
return false;
}
if ( isset( $this->mArticle ) && isset( $this->mArticle->mRevision ) ) {
// Let sysop know that this will make private content public if saved
- if( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
+
+ if( !$this->mArticle->mRevision->userCan( Revision::DELETED_TEXT ) ) {
+ $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) );
+ } else if( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
$wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) );
}
+
if( !$this->mArticle->mRevision->isCurrent() ) {
$this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() );
$wgOut->addWikiText( wfMsg( 'editingold' ) );
return;
}
- $this->oldimage = $wgRequest->getText( 'oldimage', false );
+ # Use revision delete
+ # $this->oldimage = $wgRequest->getText( 'oldimage', false );
+
+ $this->oldimage = false;
$token = $wgRequest->getText( 'wpEditToken' );
+ # Flag to hide all contents of the archived revisions
+ $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('deleterevision');
+
if( $this->oldimage && !$this->isValidOldSpec() ) {
$wgOut->showUnexpectedValueError( 'oldimage', htmlspecialchars( $this->oldimage ) );
return;
if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $token, $this->oldimage ) ) {
$comment = $wgRequest->getText( 'wpReason' );
if( $this->oldimage ) {
- $status = $this->file->deleteOld( $this->oldimage, $comment );
+ $status = $this->file->deleteOld( $this->oldimage, $comment, $suppress );
if( $status->ok ) {
// Need to do a log item
$log = new LogPage( 'delete' );
$log->addEntry( 'delete', $this->title, $logComment );
}
} else {
- $status = $this->file->delete( $comment );
+ $status = $this->file->delete( $comment, $suppress );
if( $status->ok ) {
// Need to delete the associated article
$article = new Article( $this->title );
if ( $line ) {
$list = new ImageHistoryList( $sk, $this->img );
+ // Our top image
$file = $this->repo->newFileFromRow( $line );
$dims = $file->getDimensionsString();
$s = $list->beginImageHistoryList() .
$list->imageHistoryLine( true, wfTimestamp(TS_MW, $line->img_timestamp),
- $this->mTitle->getDBkey(), $line->img_user,
- $line->img_user_text, $line->img_size, $line->img_description,
- $dims
+ $this->mTitle->getDBkey(), $line->img_user,
+ $line->img_user_text, $line->img_size, $line->img_description, $dims,
+ $line->oi_deleted, $line->img_sha1
);
-
+ // old image versions
while ( $line = $this->img->nextHistoryLine() ) {
$file = $this->repo->newFileFromRow( $line );
$dims = $file->getDimensionsString();
$s .= $list->imageHistoryLine( false, $line->oi_timestamp,
$line->oi_archive_name, $line->oi_user,
$line->oi_user_text, $line->oi_size, $line->oi_description,
- $dims
+ $dims, $line->oi_deleted, $line->oi_sha1
);
}
$s .= $list->endImageHistoryList();
. $wgOut->parse( wfMsgNoTrans( 'filehist-help' ) )
. Xml::openElement( 'table', array( 'class' => 'filehistory' ) ) . "\n"
. '<tr><td></td>'
- . ( $this->img->isLocal() && $wgUser->isAllowed( 'delete' ) ? '<td></td>' : '' )
+ . ( $this->img->isLocal() && $wgUser->isAllowed( 'deleterevision' ) ? '<td></td>' : '' )
. '<th>' . wfMsgHtml( 'filehist-datetime' ) . '</th>'
. '<th>' . wfMsgHtml( 'filehist-user' ) . '</th>'
. '<th>' . wfMsgHtml( 'filehist-dimensions' ) . '</th>'
return "</table>\n";
}
- public function imageHistoryLine( $iscur, $timestamp, $img, $user, $usertext, $size, $description, $dims ) {
- global $wgUser, $wgLang, $wgContLang;
+ public function imageHistoryLine( $iscur, $timestamp, $img, $user, $usertext, $size, $description, $dims,
+ $deleted, $sha1 ) {
+ global $wgUser, $wgLang, $wgContLang, $wgTitle;
$local = $this->img->isLocal();
- $row = '';
+ $row = '<td>';
// Deletion link
- if( $local && $wgUser->isAllowed( 'delete' ) ) {
- $row .= '<td>';
+ if( $iscur && $local && $wgUser->isAllowed( 'delete' ) ) {
$q = array();
$q[] = 'action=delete';
- if( !$iscur )
- $q[] = 'oldimage=' . urlencode( $img );
+ $q[] = 'image=' . $this->title->getPartialUrl();
$row .= '(' . $this->skin->makeKnownLinkObj(
$this->title,
wfMsgHtml( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' ),
implode( '&', $q )
) . ')';
- $row .= '</td>';
+ $row .= '</td><td>';
}
+ if( !$iscur && $local && $wgUser->isAllowed( 'deleterevision' ) ) {
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+ if( !$this->userCan($deleted,Image::DELETED_RESTRICTED) ) {
+ // If file was hidden from sysops
+ $del = wfMsgHtml( 'rev-delundel' );
+ } else {
+ // If the file was hidden, link to sha-1
+ list($ts,$name) = explode('!',$img,2);
+ $del = $this->skin->makeKnownLinkObj( $revdel, wfMsg( 'rev-delundel' ),
+ 'target=' . urlencode( $wgTitle->getPrefixedText() ) .
+ '&oldimage=' . urlencode( $ts ) );
+ // Bolden oversighted content
+ if( $this->isDeleted($deleted,Image::DELETED_RESTRICTED) )
+ $del = "<strong>$del</strong>";
+ }
+ $row .= "<tt>(<small>$del</small>)</tt></td><td> ";
+ }
+
// Reversion link/current indicator
- $row .= '<td>';
if( $iscur ) {
- $row .= '(' . wfMsgHtml( 'filehist-current' ) . ')';
+ $row .= ' (' . wfMsgHtml( 'filehist-current' ) . ')';
+ } elseif( $this->isDeleted($deleted,Image::DELETED_FILE) ) {
+ $row .= '(' . wfMsgHtml('filehist-revert') . ')';
} elseif( $local && $wgUser->isLoggedIn() && $this->title->userCan( 'edit' ) ) {
$q = array();
$q[] = 'action=revert';
// Date/time and image link
$row .= '<td>';
- $url = $iscur ? $this->img->getUrl() : $this->img->getArchiveUrl( $img );
- $row .= Xml::element(
- 'a',
- array( 'href' => $url ),
- $wgLang->timeAndDate( $timestamp, true )
- );
+ if( !$this->userCan($deleted,Image::DELETED_FILE) ) {
+ # Don't link to unviewable files
+ $row .= '<span class="history-deleted">' . $wgLang->timeAndDate( $timestamp, true ) . '</span>';
+ } else if( $this->isDeleted($deleted,Image::DELETED_FILE) ) {
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+ # Make a link to review the image
+ $url = $this->skin->makeKnownLinkObj( $revdel, $wgLang->timeAndDate( $timestamp, true ),
+ "target=".$wgTitle->getPrefixedText()."&file=$sha1.".$this->img->getExtension() );
+ $row .= '<span class="history-deleted">'.$url.'</span>';
+ } else {
+ $url = $iscur ? $this->img->getUrl() : $this->img->getArchiveUrl( $img );
+ $row .= Xml::element( 'a',
+ array( 'href' => $url ),
+ $wgLang->timeAndDate( $timestamp, true ) );
+ }
+
$row .= '</td>';
// Uploading user
$row .= '<td>';
if( $local ) {
- $row .= $this->skin->userLink( $user, $usertext ) . $this->skin->userToolLinks( $user, $usertext );
+ // Hide deleted usernames
+ if( $this->isDeleted($deleted,Image::DELETED_USER) )
+ $row .= '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
+ else
+ $row .= $this->skin->userLink( $user, $usertext ) . $this->skin->userToolLinks( $user, $usertext );
} else {
$row .= htmlspecialchars( $usertext );
}
// File size
$row .= '<td class="mw-imagepage-filesize">' . $this->skin->formatSize( $size ) . '</td>';
- // Comment
- $row .= '<td>' . $this->skin->formatComment( $description, $this->title ) . '</td>';
+ // Don't show deleted descriptions
+ if ( $this->isDeleted($deleted,Image::DELETED_COMMENT) )
+ $row .= '<td><span class="history-deleted">' . wfMsgHtml('rev-deleted-comment') . '</span></td>';
+ else
+ $row .= '<td>' . $this->skin->commentBlock( $description, $this->title ) . '</td>';
return "<tr>{$row}</tr>\n";
}
+
+ /**
+ * int $field one of DELETED_* bitfield constants
+ * for file or revision rows
+ * @param int $bitfield
+ * @param int $field
+ * @return bool
+ */
+ function isDeleted( $bitfield, $field ) {
+ return ($bitfield & $field) == $field;
+ }
+
+ /**
+ * Determine if the current user is allowed to view a particular
+ * field of this FileStore image file, if it's marked as deleted.
+ * @param int $bitfield
+ * @param int $field
+ * @return bool
+ */
+ function userCan( $bitfield, $field ) {
+ if( ($bitfield & $field) == $field ) {
+ // images
+ global $wgUser;
+ $permission = ( $bitfield & File::DELETED_RESTRICTED ) == File::DELETED_RESTRICTED
+ ? 'hiderevision'
+ : 'deleterevision';
+ wfDebug( "Checking for $permission due to $field match on $bitfield\n" );
+ return $wgUser->isAllowed( $permission );
+ } else {
+ return true;
+ }
+ }
}
* @param boolean $thumb shows image as thumbnail in a frame
* @param string $manualthumb image name for the manual thumbnail
* @param string $valign vertical alignment: baseline, sub, super, top, text-top, middle, bottom, text-bottom
+ * @param string $time, timestamp (for image versioning)
* @return string
*/
function makeImageLinkObj( $title, $label, $alt, $align = '', $handlerParams = array(), $framed = false,
/**
* Generate a user link if the current user is allowed to view it
* @param $rev Revision object.
+ * @param $isPublic, bool, show only if all users can see it
* @return string HTML
*/
- function revUserLink( $rev ) {
- if( $rev->userCan( Revision::DELETED_USER ) ) {
+ function revUserLink( $rev, $isPublic = false ) {
+ if( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
+ $link = wfMsgHtml( 'rev-deleted-user' );
+ } else if( $rev->userCan( Revision::DELETED_USER ) ) {
$link = $this->userLink( $rev->getRawUser(), $rev->getRawUserText() );
} else {
$link = wfMsgHtml( 'rev-deleted-user' );
}
return $link;
}
+
+ /**
+ * Generate a user link if the current user is allowed to view it
+ * @param $event, log row item.
+ * @param $isPublic, bool, show only if all users can see it
+ * @return string HTML
+ */
+ function logUserLink( $event, $isPublic = false ) {
+ if( LogViewer::isDeleted( $event, LogViewer::DELETED_USER ) && $isPublic ) {
+ $link = wfMsgHtml( 'rev-deleted-user' );
+ } else if( LogViewer::userCan( $event, LogViewer::DELETED_USER ) ) {
+ if ( isset($event->user_name) ) {
+ $link = $this->userLink( $event->log_user, $event->user_name );
+ } else {
+ $user = $event->log_user;
+ $link = $this->userLink( $event->log_user, User::whoIs( $user ) );
+ }
+ } else {
+ $link = wfMsgHtml( 'rev-deleted-user' );
+ }
+ if( LogViewer::isDeleted( $event, LogViewer::DELETED_USER ) ) {
+ return '<span class="history-deleted">' . $link . '</span>';
+ }
+ return $link;
+ }
/**
* Generate a user tool link cluster if the current user is allowed to view it
* @param $rev Revision object.
+ * @param $isPublic, bool, show only if all users can see it
* @return string HTML
*/
- function revUserTools( $rev ) {
- if( $rev->userCan( Revision::DELETED_USER ) ) {
+ function revUserTools( $rev, $isPublic = false ) {
+ if( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
+ $link = wfMsgHtml( 'rev-deleted-user' );
+ } else if( $rev->userCan( Revision::DELETED_USER ) ) {
$link = $this->userLink( $rev->getRawUser(), $rev->getRawUserText() ) .
- ' ' .
- $this->userToolLinks( $rev->getRawUser(), $rev->getRawUserText() );
+ ' ' . $this->userToolLinks( $rev->getRawUser(), $rev->getRawUserText() );
} else {
$link = wfMsgHtml( 'rev-deleted-user' );
}
if( $rev->isDeleted( Revision::DELETED_USER ) ) {
+ return ' <span class="history-deleted">' . $link . '</span>';
+ }
+ return " $link";
+ }
+
+ /**
+ * Generate a user tool link cluster if the current user is allowed to view it
+ * @param $event, log item.
+ * @param $isPublic, bool, show only if all users can see it
+ * @return string HTML
+ */
+ function logUserTools( $event, $isPublic = false ) {
+ if( LogViewer::isDeleted( $event, LogViewer::DELETED_USER ) && $isPublic ) {
+ $link = wfMsgHtml( 'rev-deleted-user' );
+ } else if( LogViewer::userCan( $event, LogViewer::DELETED_USER ) ) {
+ if( isset($event->user_name) ) {
+ $link = $this->userLink( $event->log_user, $event->user_name ) .
+ $this->userToolLinksRedContribs( $event->log_user, $event->user_name );
+ } else {
+ $link = $this->userLink( $event->log_user, $event->user_name ) .
+ $this->userToolLinksRedContribs( $event->log_user, User::whoIs($event->log_user) );
+ }
+ } else {
+ $link = wfMsgHtml( 'rev-deleted-user' );
+ }
+ if( LogViewer::isDeleted( $event, LogViewer::DELETED_USER ) ) {
return '<span class="history-deleted">' . $link . '</span>';
}
return $link;
*
* @param Revision $rev
* @param bool $local Whether section links should refer to local page
+ * @param $isPublic, show only if all users can see it
* @return string HTML
*/
- function revComment( Revision $rev, $local = false ) {
- if( $rev->userCan( Revision::DELETED_COMMENT ) ) {
+ function revComment( Revision $rev, $local = false, $isPublic = false ) {
+ if( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
+ $block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
+ } else if( $rev->userCan( Revision::DELETED_COMMENT ) ) {
$block = $this->commentBlock( $rev->getRawComment(), $rev->getTitle(), $local );
} else {
- $block = " <span class=\"comment\">" .
- wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
+ $block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
}
if( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
return " <span class=\"history-deleted\">$block</span>";
}
return $block;
}
+
+ /**
+ * Wrap and format the given event's comment block, if the current
+ * user is allowed to view it.
+ *
+ * @param Revision $rev
+ * @return string HTML
+ */
+ function logComment( $event, $isPublic = false ) {
+ if( LogViewer::isDeleted( $event, LogViewer::DELETED_COMMENT ) && $isPublic ) {
+ $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
+ } else if( LogViewer::userCan( $event, LogViewer::DELETED_COMMENT ) ) {
+ $block = $this->commentBlock( LogViewer::getRawComment( $event ) );
+ } else {
+ $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
+ }
+ if( LogViewer::isDeleted( $event, LogViewer::DELETED_COMMENT ) ) {
+ return "<span class=\"history-deleted\">$block</span>";
+ }
+ return $block;
+ }
/** @todo document */
function tocIndent() {
function saveContent() {
if( wfReadOnly() ) return false;
- global $wgUser;
+ global $wgUser, $wgLogRestrictions;
$fname = 'LogPage::saveContent';
$dbw = wfGetDB( DB_MASTER );
'log_comment' => $this->comment,
'log_params' => $this->params
);
-
- # log_id doesn't exist on Wikimedia servers yet, and it's a tricky
- # schema update to do. Hack it for now to ignore the field on MySQL.
- if ( !is_null( $log_id ) ) {
- $data['log_id'] = $log_id;
- }
$dbw->insert( 'logging', $data, $fname );
+ $newId = $dbw->insertId();
# And update recentchanges
- if ( $this->updateRecentChanges ) {
- $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
- $rcComment = $this->getRcComment();
- RecentChange::notifyLog( $now, $titleObj, $wgUser, $rcComment, '',
- $this->type, $this->action, $this->target, $this->comment, $this->params );
+ if( $this->updateRecentChanges ) {
+ # Don't add private logs to RC!
+ if( !isset($wgLogRestrictions[$this->type]) || $wgLogRestrictions[$this->type]=='*' ) {
+ $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
+ $rcComment = $this->getRcComment();
+ RecentChange::notifyLog( $now, $titleObj, $wgUser, $rcComment, '',
+ $this->type, $this->action, $this->target, $this->comment, $this->params, $newId );
+ }
}
return true;
}
*/
static function logHeader( $type ) {
global $wgLogHeaders;
- return wfMsg( $wgLogHeaders[$type] );
+ return wfMsgHtml( $wgLogHeaders[$type] );
}
/**
$text = $wgContLang->ucfirst( $title->getText() );
$titleLink = $skin->makeLinkObj( Title::makeTitle( NS_USER, $text ) );
break;
+ case 'merge':
+ $titleLink = $skin->makeLinkObj( $title, $title->getPrefixedText(), 'redirect=no' );
+ $params[0] = $skin->makeLinkObj( Title::newFromText( $params[0] ), htmlspecialchars( $params[0] ) );
+ $params[1] = $wgLang->timeanddate( $params[1] );
+ break;
default:
$titleLink = $skin->makeLinkObj( $title );
}
}
} else {
array_unshift( $params, $titleLink );
- if ( $key == 'block/block' ) {
+ if ( $key == 'block/block' || $key == 'oversight/block' ) {
if ( $skin ) {
$params[1] = '<span title="' . htmlspecialchars( $params[1] ). '">' . $wgLang->translateBlockExpiry( $params[1] ) . '</span>';
} else {
$this->mTitle =& $article->mTitle;
$this->mNotificationTimestamp = NULL;
$this->mSkin = $wgUser->getSkin();
+ $this->preCacheMessages();
+ }
+
+ /**
+ * As we use the same small set of messages in various methods and that
+ * they are called often, we call them once and save them in $this->message
+ */
+ function preCacheMessages() {
+ // Precache various messages
+ if( !isset( $this->message ) ) {
+ foreach( explode(' ', 'cur last rev-delundel' ) as $msg ) {
+ $this->message[$msg] = wfMsgExt( $msg, array( 'escape') );
+ }
+ }
}
/**
$arbitrary = $this->diffButtons( $rev, $firstInList, $counter );
$link = $this->revLink( $rev );
- $user = $this->mSkin->userLink( $rev->getUser(), $rev->getUserText() )
- . $this->mSkin->userToolLinks( $rev->getUser(), $rev->getUserText() );
-
$s .= "($curlink) ($lastlink) $arbitrary";
if( $wgUser->isAllowed( 'deleterevision' ) ) {
$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
if( $firstInList ) {
- // We don't currently handle well changing the top revision's settings
- $del = wfMsgHtml( 'rev-delundel' );
+ // We don't currently handle well changing the top revision's settings
+ $del = $this->message['rev-delundel'];
} else if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
// If revision was hidden from sysops
- $del = wfMsgHtml( 'rev-delundel' );
+ $del = $this->message['rev-delundel'];
} else {
$del = $this->mSkin->makeKnownLinkObj( $revdel,
- wfMsg( 'rev-delundel' ),
+ $this->message['rev-delundel'],
'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) .
'&oldid=' . urlencode( $rev->getId() ) );
+ // Bolden oversighted content
+ if( $rev->isDeleted( Revision::DELETED_RESTRICTED ) )
+ $del = "<strong>$del</strong>";
}
- $s .= " (<small>$del</small>) ";
+ $s .= " <tt>(<small>$del</small>)</tt> ";
}
$s .= " $link";
- #getUser is safe, but this avoids making the invalid untargeted contribs links
- if( $row->rev_deleted & Revision::DELETED_USER ) {
- $user = '<span class="history-deleted">' . wfMsg('rev-deleted-user') . '</span>';
- }
- $s .= " <span class='history-user'>$user</span>";
-
+ $s .= ' '.$this->mSkin->revUserTools( $rev, true);
+
if( $row->rev_minor_edit ) {
$s .= ' ' . wfElement( 'span', array( 'class' => 'minor' ), wfMsg( 'minoreditletter') );
}
}
#add blurb about text having been deleted
if( $row->rev_deleted & Revision::DELETED_TEXT ) {
- $s .= ' ' . wfMsgHtml( 'deletedrev' );
+ $s .= ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
}
$tools = array();
/** @todo document */
function curLink( $rev, $latest ) {
- $cur = wfMsgExt( 'cur', array( 'escape') );
+ $cur = $this->message['cur'];
if( $latest || !$rev->userCan( Revision::DELETED_TEXT ) ) {
return $cur;
} else {
/** @todo document */
function lastLink( $rev, $next, $counter ) {
- $last = wfMsgExt( 'last', array( 'escape' ) );
+ $last = $this->message['last'];
if ( is_null( $next ) ) {
# Probably no next row
return $last;
* rc_patrolled boolean whether or not someone has marked this edit as patrolled
* rc_old_len integer byte length of the text before the edit
* rc_new_len the same after the edit
+ * rc_deleted partial deletion
+ * rc_logid the log_id value for this log entry (or zero)
+ * rc_log_type the log type (or null)
+ * rc_log_action the log action (or null)
+ * rc_params log params
*
* mExtra:
* prefixedDBkey prefixed db key, used by external app via msg queue
'rc_patrolled' => 0,
'rc_new' => 0, # obsolete
'rc_old_len' => $oldSize,
- 'rc_new_len' => $newSize
+ 'rc_new_len' => $newSize,
+ 'rc_deleted' => 0,
+ 'rc_logid' => 0,
+ 'rc_log_type' => null,
+ 'rc_log_action' => '',
+ 'rc_params' => ''
);
$rc->mExtra = array(
public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot = 'default',
$ip='', $size = 0, $newId = 0 )
{
- if ( !$ip ) {
+ if( !$ip ) {
$ip = wfGetIP();
- if ( !$ip ) {
- $ip = '';
- }
+ if( !$ip ) $ip = '';
}
if ( $bot === 'default' ) {
$bot = $user->isAllowed( 'bot' );
'rc_moved_to_title' => '',
'rc_ip' => $ip,
'rc_patrolled' => 0,
- 'rc_new' => 1, # obsolete
+ 'rc_new' => 1, # obsolete
'rc_old_len' => 0,
- 'rc_new_len' => $size
+ 'rc_new_len' => $size,
+ 'rc_deleted' => 0,
+ 'rc_logid' => 0,
+ 'rc_log_type' => null,
+ 'rc_log_action' => '',
+ 'rc_params' => ''
);
$rc->mExtra = array(
# Makes an entry in the database corresponding to a rename
public static function notifyMove( $timestamp, &$oldTitle, &$newTitle, &$user, $comment, $ip='', $overRedir = false )
{
- if ( !$ip ) {
+ if( !$ip ) {
$ip = wfGetIP();
- if ( !$ip ) {
- $ip = '';
- }
+ if( !$ip ) $ip = '';
}
$rc = new RecentChange;
'rc_patrolled' => 1,
'rc_old_len' => NULL,
'rc_new_len' => NULL,
+ 'rc_deleted' => 0,
+ 'rc_logid' => 0, # notifyMove not used anymore
+ 'rc_log_type' => null,
+ 'rc_log_action' => '',
+ 'rc_params' => ''
);
$rc->mExtra = array(
RecentChange::notifyMove( $timestamp, $oldTitle, $newTitle, $user, $comment, $ip, true );
}
- # A log entry is different to an edit in that previous revisions are
- # not kept
+ # A log entry is different to an edit in that previous revisions are not kept
public static function notifyLog( $timestamp, &$title, &$user, $comment, $ip='',
- $type, $action, $target, $logComment, $params )
+ $type, $action, $target, $logComment, $params, $newId=0 )
{
- if ( !$ip ) {
+ if( !$ip ) {
$ip = wfGetIP();
- if ( !$ip ) {
- $ip = '';
- }
+ if( !$ip ) $ip = '';
}
$rc = new RecentChange;
$rc->mAttribs = array(
'rc_timestamp' => $timestamp,
'rc_cur_time' => $timestamp,
- 'rc_namespace' => $title->getNamespace(),
- 'rc_title' => $title->getDBkey(),
+ 'rc_namespace' => $target->getNamespace(),
+ 'rc_title' => $target->getDBkey(),
'rc_type' => RC_LOG,
'rc_minor' => 0,
- 'rc_cur_id' => $title->getArticleID(),
+ 'rc_cur_id' => $target->getArticleID(),
'rc_user' => $user->getID(),
'rc_user_text' => $user->getName(),
- 'rc_comment' => $comment,
+ 'rc_comment' => $logComment,
'rc_this_oldid' => 0,
'rc_last_oldid' => 0,
'rc_bot' => $user->isAllowed( 'bot' ) ? 1 : 0,
'rc_new' => 0, # obsolete
'rc_old_len' => NULL,
'rc_new_len' => NULL,
+ 'rc_deleted' => 0,
+ 'rc_logid' => $newId,
+ 'rc_log_type' => $type,
+ 'rc_log_action' => $action,
+ 'rc_params' => $params
);
$rc->mExtra = array(
'prefixedDBkey' => $title->getPrefixedDBkey(),
'rc_new' => $row->page_is_new, # obsolete
'rc_old_len' => $row->rc_old_len,
'rc_new_len' => $row->rc_new_len,
+ 'rc_deleted' => $row->rc_deleted,
+ 'rc_logid' => $row->rc_logid,
+ 'rc_log_type' => $row->rc_log_type,
+ 'rc_log_action' => $row->rc_log_action,
+ 'rc_params' => $row->rc_params
);
$this->mExtra = array();
* @param bool $minor
* @return Revision
*/
- function newNullRevision( &$dbw, $pageId, $summary, $minor ) {
+ static function newNullRevision( &$dbw, $pageId, $summary, $minor ) {
$fname = 'Revision::newNullRevision';
wfProfileIn( $fname );
if ( empty( $wgFileStore['deleted']['directory'] ) ) {
$wgFileStore['deleted']['directory'] = "{$wgUploadDirectory}/deleted";
}
-
+if ( empty( $wgFileStore['hidden']['directory'] ) ) {
+ $wgFileStore['hidden']['directory'] = "{$wgUploadDirectory}/hidden";
+}
/**
* Initialise $wgLocalFileRepo from backwards-compatible settings
'transformVia404' => !$wgGenerateThumbnailOnParse,
'initialCapital' => $wgCapitalLinks,
'deletedDir' => $wgFileStore['deleted']['directory'],
- 'deletedHashLevels' => $wgFileStore['deleted']['hash']
+ 'deletedHashLevels' => $wgFileStore['deleted']['hash'],
+ 'hiddenDir' => $wgFileStore['hidden']['directory'],
+ 'hiddenHashLevels' => $wgFileStore['hidden']['hash']
);
}
/**
</td>
</tr>
");
- // Allow some users to hide name from block log, blocklist and listusers
- if ( $wgUser->isAllowed( 'hideuser' ) ) {
+
+ global $wgSysopEmailBans;
+ if ( $wgSysopEmailBans && $wgUser->isAllowed( 'blockemail' ) ) {
$wgOut->addHTML("
<tr>
<td> </td>
<td>
- " . wfCheckLabel( wfMsgHtml( 'ipbhidename' ),
- 'wpHideName', 'wpHideName', $this->BlockHideName,
- array( 'tabindex' => '9' ) ) . "
+ " . wfCheckLabel( wfMsgHtml( 'ipbemailban' ),
+ 'wpEmailBan', 'wpEmailBan', $this->BlockEmail,
+ array( 'tabindex' => '10' )) . "
</td>
</tr>
");
}
- global $wgSysopEmailBans;
-
- if ( $wgSysopEmailBans && $wgUser->isAllowed( 'blockemail' ) ) {
+ // Allow some users to hide name from block log, blocklist and listusers
+ if ( $wgUser->isAllowed( 'hideuser' ) ) {
$wgOut->addHTML("
<tr id='wpEnableEmailBan'>
<td> </td>
<td>
- " . wfCheckLabel( wfMsgHtml( 'ipbemailban' ),
- 'wpEmailBan', 'wpEmailBan', $this->BlockEmail,
- array( 'tabindex' => '10' )) . "
+ " . wfCheckLabel( wfMsgHtml( 'ipbhidename' ),
+ 'wpHideName', 'wpHideName', $this->BlockHideName,
+ array( 'tabindex' => '9' ) ) . "
</td>
</tr>
");
}
+
$wgOut->addHTML("
<tr>
<td style='padding-top: 1em'> </td>
}
$histlink='('.$sk->makeKnownLinkObj( $page, $this->messages['hist'], 'action=history' ) . ')';
- $comment = $wgContLang->getDirMark() . $sk->revComment( $rev );
+ $comment = $wgContLang->getDirMark() . $sk->revComment( $rev, false, true );
$d = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true );
if( $this->target == 'newbies' ) {
$titleObj = SpecialPage::getTitleFor( "Ipblocklist" );
$unblocklink = ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&id=' . urlencode( $block->mId ) ) . ')';
}
-
+
$comment = $sk->commentBlock( $block->mReason );
-
+
$s = "{$line} $comment";
if ( $block->mHideName )
$s = '<span class="history-deleted">' . $s . '</span>';
-
+
wfProfileOut( __METHOD__ );
return "<li>$s $unblocklink</li>\n";
}
$this->db = wfGetDB( DB_SLAVE );
$this->setupQuery( $request );
}
+
+ /**
+ * Returns a row of log data
+ * @param Title $title
+ * @param integer $logid, optional
+ * @private
+ */
+ function newRowFromID( $logid ) {
+ $fname = 'LogReader::newFromTitle';
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $row = $dbr->selectRow( 'logging', array('*'),
+ array('log_id' => intval($logid) ),
+ $fname );
+
+ return $row;
+ }
/**
* Basic setup and applies the limiting factors from the WebRequest object.
/**
* Set the log reader to return only entries of the given type.
+ * Type restrictions enforced here
* @param string $type A log type ('upload', 'delete', etc)
* @private
*/
function limitType( $type ) {
+ global $wgLogRestrictions, $wgUser;
+ // Reset the array, clears extra "where" clauses when $par is used
+ $this->whereClauses = $hiddenLogs = array();
+ // Exclude logs this user can't see
+ if( isset($wgLogRestrictions) ) {
+ if( isset($wgLogRestrictions[$type]) && !$wgUser->isAllowed( $wgLogRestrictions[$type] ) )
+ return false;
+ // Don't show private logs to unpriviledged users or
+ // when not specifically requested.
+ foreach( $wgLogRestrictions as $logtype => $right ) {
+ if( !$wgUser->isAllowed( $right ) || empty($type) ) {
+ $safetype = $this->db->strencode( $logtype );
+ $hiddenLogs[] = "'$safetype'";
+ }
+ }
+ if( !empty($hiddenLogs) ) {
+ $this->whereClauses[] = 'log_type NOT IN('.implode(',',$hiddenLogs).')';
+ }
+ }
+
if( empty( $type ) ) {
return false;
}
* @private
*/
function getQuery() {
+ global $wgAllowLogDeletion;
+
$logging = $this->db->tableName( "logging" );
$sql = "SELECT /*! STRAIGHT_JOIN */ log_type, log_action, log_timestamp,
- log_user, user_name,
- log_namespace, log_title, page_id,
- log_comment, log_params FROM $logging ";
+ log_user, user_name, log_namespace, log_title, page_id,
+ log_comment, log_params, log_deleted ";
+ if( $wgAllowLogDeletion )
+ $sql .= ", log_id ";
+
+ $sql .= "FROM $logging ";
if( !empty( $this->joinClauses ) ) {
$sql .= implode( ' ', $this->joinClauses );
}
$this->db->freeResult( $res );
return $ret;
}
-
+
}
/**
* @addtogroup SpecialPage
*/
class LogViewer {
+ const DELETED_ACTION = 1;
+ const DELETED_COMMENT = 2;
+ const DELETED_USER = 4;
+ const DELETED_RESTRICTED = 8;
+
const NO_ACTION_LINK = 1;
-
/**
* @var LogReader $reader
*/
global $wgUser;
$this->skin = $wgUser->getSkin();
$this->reader =& $reader;
+ $this->preCacheMessages();
$this->flags = $flags;
}
+
+ /**
+ * As we use the same small set of messages in various methods and that
+ * they are called often, we call them once and save them in $this->message
+ */
+ function preCacheMessages() {
+ // Precache various messages
+ if( !isset( $this->message ) ) {
+ foreach( explode(' ', 'viewpagelogs revhistory filehist rev-delundel' ) as $msg ) {
+ $this->message[$msg] = wfMsgExt( $msg, array( 'escape') );
+ }
+ }
+ }
/**
* Take over the whole output page in $wgOut with the log display.
$this->showError( $wgOut );
}
}
+
+ /**
+ * Fetch event's user id if it's available to all users
+ * @return int
+ */
+ static function getUser( $event ) {
+ if( $this->isDeleted( $event, Revision::DELETED_USER ) ) {
+ return 0;
+ } else {
+ return $event->log_user;
+ }
+ }
+
+ /**
+ * Fetch event's user id without regard for the current user's permissions
+ * @return string
+ */
+ static function getRawUser( $event ) {
+ return $event->log_user;
+ }
+
+ /**
+ * Fetch event's username if it's available to all users
+ * @return string
+ */
+ static function getUserText( $event ) {
+ if( $this->isDeleted( $event, Revision::DELETED_USER ) ) {
+ return "";
+ } else {
+ if ( isset($event->user_name) ) {
+ return $event->user_name;
+ } else {
+ return User::whoIs( $event->log_user );
+ }
+ }
+ }
+
+ /**
+ * Fetch event's username without regard for view restrictions
+ * @return string
+ */
+ static function getRawUserText( $event ) {
+ if ( isset($event->user_name) ) {
+ return $event->user_name;
+ } else {
+ return User::whoIs( $event->log_user );
+ }
+ }
+
+ /**
+ * Fetch event comment if it's available to all users
+ * @return string
+ */
+ static function getComment( $event ) {
+ if( $this->isDeleted( $event, Revision::DELETED_COMMENT ) ) {
+ return "";
+ } else {
+ return $event->log_comment;
+ }
+ }
+
+ /**
+ * Fetch event comment without regard for the current user's permissions
+ * @return string
+ */
+ static function getRawComment( $event ) {
+ return $event->log_comment;
+ }
+
+ /**
+ * Returns the title of the page associated with this entry.
+ * @return Title
+ */
+ static function getTitle( $event ) {
+ return Title::makeTitle( $event->log_namespace, $event->log_title );
+ }
/**
* Load the data from the linked LogReader
} else {
$linkCache->addBadLinkObj( $title );
}
-
- $userLink = $this->skin->userLink( $s->log_user, $s->user_name ) . $this->skin->userToolLinksRedContribs( $s->log_user, $s->user_name );
- $comment = $wgContLang->getDirMark() . $this->skin->commentBlock( $s->log_comment );
+ // User links
+ $userLink = $this->skin->logUserTools( $s, true );
+ // Comment
+ if( $s->log_action == 'create2' ) {
+ $comment = ''; // Suppress from old account creations, useless and can contain incorrect links
+ } else if( $s->log_deleted & self::DELETED_COMMENT ) {
+ $comment = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-comment') . '</span>';
+ } else {
+ $comment = $wgContLang->getDirMark() . $this->skin->commentBlock( $s->log_comment );
+ }
+
$paramArray = LogPage::extractParams( $s->log_params );
+ $revert = ''; $del = '';
+
+ // Some user can hide log items and have review links
+ if( $wgUser->isAllowed( 'deleterevision' ) ) {
+ $del = $this->showhideLinks( $s, $title );
+ }
+
+ // Show restore/unprotect/unblock
+ $revert = $this->showReviewLinks( $s, $title, $paramArray );
+
+ // Event description
+ if ( $s->log_deleted & self::DELETED_ACTION )
+ $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+ else
+ $action = LogPage::actionText( $s->log_type, $s->log_action, $title, $this->skin, $paramArray, true );
+
+ return "<li><tt>$del</tt> $time $userLink $action $comment $revert</li>";
+ }
+
+ /**
+ * @param $s, row object
+ * @param $s, title object
+ * @private
+ */
+ function showhideLinks( $s, $title ) {
+ global $wgAllowLogDeletion;
+
+ if( !$wgAllowLogDeletion )
+ return "";
+
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+ // If event was hidden from sysops
+ if( !self::userCan( $s, Revision::DELETED_RESTRICTED ) ) {
+ $del = $this->message['rev-delundel'];
+ } else if( $s->log_type == 'oversight' ) {
+ return ''; // No one should be hiding from the oversight log
+ } else {
+ $del = $this->skin->makeKnownLinkObj( $revdel, $this->message['rev-delundel'], 'logid='.$s->log_id );
+ // Bolden oversighted content
+ if( self::isDeleted( $s, Revision::DELETED_RESTRICTED ) )
+ $del = "<strong>$del</strong>";
+ }
+ return "(<small>$del</small>)";
+ }
+
+ /**
+ * @param $s, row object
+ * @param $title, title object
+ * @param $s, param array
+ * @private
+ */
+ function showReviewLinks( $s, $title, $paramArray ) {
+ global $wgUser;
+
$revert = '';
- // show revertmove link
- if ( !( $this->flags & self::NO_ACTION_LINK ) ) {
- if ( $s->log_type == 'move' && isset( $paramArray[0] ) ) {
- $destTitle = Title::newFromText( $paramArray[0] );
- if ( $destTitle ) {
- $revert = '(' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Movepage' ),
- wfMsg( 'revertmove' ),
- 'wpOldTitle=' . urlencode( $destTitle->getPrefixedDBkey() ) .
- '&wpNewTitle=' . urlencode( $title->getPrefixedDBkey() ) .
- '&wpReason=' . urlencode( wfMsgForContent( 'revertmove' ) ) .
- '&wpMovetalk=0' ) . ')';
- }
- // show undelete link
- } elseif ( $s->log_action == 'delete' && $wgUser->isAllowed( 'delete' ) ) {
- $revert = '(' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Undelete' ),
- wfMsg( 'undeletebtn' ) ,
- 'target='. urlencode( $title->getPrefixedDBkey() ) ) . ')';
+ // Don't give away the page name
+ if( self::isDeleted($s,self::DELETED_ACTION) )
+ return $revert;
- // show unblock link
- } elseif ( $s->log_action == 'block' && $wgUser->isAllowed( 'block' ) ) {
- $revert = '(' . $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Ipblocklist' ),
- wfMsg( 'unblocklink' ),
- 'action=unblock&ip=' . urlencode( $s->log_title ) ) . ')';
- // show change protection link
- } elseif ( ( $s->log_action == 'protect' || $s->log_action == 'modify' ) && $wgUser->isAllowed( 'protect' ) ) {
- $revert = '(' . $skin->makeKnownLinkObj( $title, wfMsg( 'protect_change' ), 'action=unprotect' ) . ')';
- // show user tool links for self created users
- // TODO: The extension should be handling this, get it out of core!
- } elseif ( $s->log_action == 'create2' ) {
- if( isset( $paramArray[0] ) ) {
- $revert = $this->skin->userToolLinks( $paramArray[0], $s->log_title, true );
- } else {
- # Fall back to a blue contributions link
- $revert = $this->skin->userToolLinks( 1, $s->log_title );
+ if( $this->flags & self::NO_ACTION_LINK ) {
+ return $revert;
+ }
+ // Show revertmove link
+ if( $s->log_type == 'move' && isset( $paramArray[0] ) ) {
+ $destTitle = Title::newFromText( $paramArray[0] );
+ if ( $destTitle ) {
+ $revert = $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Movepage' ),
+ wfMsg( 'revertmove' ),
+ 'wpOldTitle=' . urlencode( $destTitle->getPrefixedDBkey() ) .
+ '&wpNewTitle=' . urlencode( $title->getPrefixedDBkey() ) .
+ '&wpReason=' . urlencode( wfMsgForContent( 'revertmove' ) ) .
+ '&wpMovetalk=0' );
+ }
+ // show undelete link
+ } else if( $s->log_action == 'delete' && $wgUser->isAllowed( 'delete' ) ) {
+ $revert = $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Undelete' ),
+ wfMsg( 'undeletebtn' ) ,
+ 'target='. urlencode( $title->getPrefixedDBkey() ) );
+ // show unblock link
+ } elseif( $s->log_action == 'block' && $wgUser->isAllowed( 'block' ) ) {
+ $revert = $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Ipblocklist' ),
+ wfMsg( 'unblocklink' ),
+ 'action=unblock&ip=' . urlencode( $s->log_title ) );
+ // show change protection link
+ } elseif ( ( $s->log_action == 'protect' || $s->log_action == 'modify' ) && $wgUser->isAllowed( 'protect' ) ) {
+ $revert = $this->skin->makeKnownLinkObj( $title, wfMsg( 'protect_change' ), 'action=unprotect' );
+ // show user tool links for self created users
+ // TODO: The extension should be handling this, get it out of core!
+ } elseif ( $s->log_action == 'create2' ) {
+ if( isset( $paramArray[0] ) ) {
+ $revert = $this->skin->userToolLinks( $paramArray[0], $s->log_title, true );
+ } else {
+ # Fall back to a blue contributions link
+ $revert = $this->skin->userToolLinks( 1, $s->log_title );
+ }
+ // If an edit was hidden from a page give a review link to the history
+ } elseif ( $s->log_action == 'merge' ) {
+ $merge = SpecialPage::getTitleFor( 'Mergehistory' );
+ $revert = $this->skin->makeKnownLinkObj( $merge, wfMsg('revertmerge'),
+ wfArrayToCGI( array('target' => $paramArray[0], 'dest' => $title->getPrefixedText() ) ) );
+ // If an edit was hidden from a page give a review link to the history
+ } else if( ($s->log_action=='revision') && $wgUser->isAllowed( 'deleterevision' ) ) {
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+ // Different revision types use different URL params...
+ $subtype = isset($paramArray[2]) ? $paramArray[1] : '';
+ // Link to each hidden object ID, $paramArray[1] is the url param.
+ // Don't number by IDs because of their size.
+ // We may often just have one, in which case it's easier to not...
+ $Ids = explode( ',', $paramArray[2] );
+ if( count($Ids) == 1 ) {
+ $revert = $this->skin->makeKnownLinkObj( $revdel, wfMsgHtml('revdel-restore'),
+ wfArrayToCGI( array('target' => $paramArray[0], $paramArray[1] => $Ids[0] ) ) );
+ } else {
+ $revert .= wfMsgHtml('revdel-restore').':';
+ foreach( $Ids as $n => $id ) {
+ $revert .= ' '.$this->skin->makeKnownLinkObj( $revdel, '#'.($n+1),
+ wfArrayToCGI( array('target' => $paramArray[0], $paramArray[1] => $id ) ) );
+ }
+ }
+ // Hidden log items, give review link
+ } else if( ($s->log_action=='event') && $wgUser->isAllowed( 'deleterevision' ) ) {
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+ $revert .= wfMsgHtml('revdel-restore');
+ $Ids = explode( ',', $paramArray[0] );
+ if( count($Ids) == 1 ) {
+ $revert = $this->skin->makeKnownLinkObj( $revdel, wfMsgHtml('revdel-restore'),
+ wfArrayToCGI( array('logid' => $Ids[0] ) ) );
+ } else {
+ foreach( $Ids as $n => $id ) {
+ $revert .= $this->skin->makeKnownLinkObj( $revdel, '#'.($n+1),
+ wfArrayToCGI( array('logid' => $id ) ) );
}
- # Suppress $comment from old entries, not needed and can contain incorrect links
- $comment = '';
}
}
-
- $action = LogPage::actionText( $s->log_type, $s->log_action, $title, $this->skin, $paramArray, true );
- $out = "<li>$time $userLink $action $comment $revert</li>\n";
- return $out;
+ $revert = ($revert == '') ? "" : " ($revert) ";
+ return $revert;
}
/**
* @private
*/
function getTypeMenu() {
+ global $wgLogRestrictions, $wgUser;
+
$out = "<select name='type'>\n";
$validTypes = LogPage::validTypes();
// Third pass generates sorted XHTML content
foreach( $m as $text => $type ) {
$selected = ($type == $this->reader->queryType());
- $out .= Xml::option( $text, $type, $selected ) . "\n";
+ // Restricted types
+ if ( isset($wgLogRestrictions[$type]) ) {
+ if ( $wgUser->isAllowed( $wgLogRestrictions[$type] ) ) {
+ $out .= Xml::option( $text, $type, $selected ) . "\n";
+ }
+ } else {
+ $out .= Xml::option( $text, $type, $selected ) . "\n";
+ }
}
$out .= '</select>';
$this->numResults < $limit);
$out->addHTML( '<p>' . $html . '</p>' );
}
+
+ /**
+ * Determine if the current user is allowed to view a particular
+ * field of this event, if it's marked as deleted.
+ * @param int $field
+ * @return bool
+ */
+ public static function userCan( $event, $field ) {
+ if( ( $event->log_deleted & $field ) == $field ) {
+ global $wgUser;
+ $permission = ( $event->log_deleted & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED
+ ? 'hiderevision'
+ : 'deleterevision';
+ wfDebug( "Checking for $permission due to $field match on $event->log_deleted\n" );
+ return $wgUser->isAllowed( $permission );
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * int $field one of DELETED_* bitfield constants
+ * @return bool
+ */
+ public static function isDeleted( $event, $field ) {
+ return ($event->log_deleted & $field) == $field;
+ }
}
+/**
+ * Aliases for backwards compatibility with 1.6
+ */
+define( 'MW_LOG_DELETED_ACTION', LogViewer::DELETED_ACTION );
+define( 'MW_LOG_DELETED_USER', LogViewer::DELETED_USER );
+define( 'MW_LOG_DELETED_COMMENT', LogViewer::DELETED_COMMENT );
+define( 'MW_LOG_DELETED_RESTRICTED', LogViewer::DELETED_RESTRICTED );
--- /dev/null
+<?php\r
+\r
+/**\r
+ * Special page allowing users with the appropriate permissions to \r
+ * merge article histories, with some restrictions\r
+ *\r
+ * @addtogroup SpecialPage\r
+ */\r
+\r
+/**\r
+ * Constructor\r
+ */\r
+function wfSpecialMergehistory( $par ) {\r
+ global $wgRequest;\r
+\r
+ $form = new MergehistoryForm( $wgRequest, $par );\r
+ $form->execute();\r
+}\r
+\r
+/**\r
+ * The HTML form for Special:MergeHistory, which allows users with the appropriate\r
+ * permissions to view and restore deleted content.\r
+ * @addtogroup SpecialPage\r
+ */\r
+class MergehistoryForm {\r
+ var $mAction, $mTarget, $mDest, $mTimestamp, $mTargetID, $mDestID, $mComment;\r
+ var $mTargetObj, $mDestObj;\r
+\r
+ function MergehistoryForm( $request, $par = "" ) {\r
+ global $wgUser;\r
+ \r
+ $this->mAction = $request->getVal( 'action' );\r
+ $this->mTarget = $request->getVal( 'target' );\r
+ $this->mDest = $request->getVal( 'dest' );\r
+ \r
+ $this->mTargetID = intval( $request->getVal( 'targetID' ) );\r
+ $this->mDestID = intval( $request->getVal( 'destID' ) );\r
+ $this->mTimestamp = $request->getVal( 'mergepoint' );\r
+ $this->mComment = $request->getText( 'wpComment' );\r
+ \r
+ $this->mMerge = $request->wasPosted() && $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) );\r
+ // target page\r
+ if( $this->mTarget !== "" ) {\r
+ $this->mTargetObj = Title::newFromURL( $this->mTarget );\r
+ } else {\r
+ $this->mTargetObj = NULL;\r
+ }\r
+ # Destination\r
+ if( $this->mDest !== "" ) {\r
+ $this->mDestObj = Title::newFromURL( $this->mDest );\r
+ } else {\r
+ $this->mDestObj = NULL;\r
+ } \r
+ \r
+ $this->preCacheMessages();\r
+ }\r
+ \r
+ /**\r
+ * As we use the same small set of messages in various methods and that\r
+ * they are called often, we call them once and save them in $this->message\r
+ */\r
+ function preCacheMessages() {\r
+ // Precache various messages\r
+ if( !isset( $this->message ) ) {\r
+ $this->message['last'] = wfMsgExt( 'last', array( 'escape') );\r
+ }\r
+ }\r
+\r
+ function execute() {\r
+ global $wgOut, $wgUser;\r
+ \r
+ $wgOut->setPagetitle( wfMsgHtml( "mergehistory" ) );\r
+ \r
+ if( $this->mTargetID && $this->mDestID && $this->mAction=="submit" && $this->mMerge ) {\r
+ return $this->merge();\r
+ }\r
+ \r
+ if( is_object($this->mTargetObj) && is_object($this->mDestObj) ) {\r
+ return $this->showHistory();\r
+ }\r
+ \r
+ return $this->showMergeForm();\r
+ }\r
+\r
+ function showMergeForm() {\r
+ global $wgOut, $wgScript;\r
+ \r
+ $wgOut->addWikiText( wfMsg( 'mergehistory-header' ) );\r
+ \r
+ $wgOut->addHtml(\r
+ Xml::openElement( 'form', array(\r
+ 'method' => 'get',\r
+ 'action' => $wgScript ) ) .\r
+ '<fieldset>' .\r
+ Xml::element( 'legend', array(),\r
+ wfMsg( 'mergehistory-box' ) ) .\r
+ Xml::hidden( 'title',\r
+ SpecialPage::getTitleFor( 'Mergehistory' )->getPrefixedDbKey() ) .\r
+ Xml::openElement( 'table' ) .\r
+ "<tr>\r
+ <td>".Xml::Label( wfMsg( 'mergehistory-from' ), 'target' )."</td>\r
+ <td>".Xml::input( 'target', 30, $this->mTarget, array('id'=>'target') )."</td>\r
+ </tr><tr>\r
+ <td>".Xml::Label( wfMsg( 'mergehistory-into' ), 'dest' )."</td>\r
+ <td>".Xml::input( 'dest', 30, $this->mDest, array('id'=>'dest') )."</td>\r
+ </tr><tr><td>" .\r
+ Xml::submitButton( wfMsg( 'mergehistory-go' ) ) .\r
+ "</td></tr>" .\r
+ Xml::closeElement( 'table' ) .\r
+ '</fieldset>' .\r
+ '</form>' );\r
+ }\r
+\r
+ private function showHistory() {\r
+ global $wgLang, $wgContLang, $wgUser, $wgOut;\r
+\r
+ $this->sk = $wgUser->getSkin();\r
+ \r
+ $wgOut->setPagetitle( wfMsg( "mergehistory" ) );\r
+ \r
+ $this->showMergeForm();\r
+ \r
+ # List all stored revisions\r
+ $revisions = new MergeHistoryPager( $this, array(), $this->mTargetObj, $this->mDestObj );\r
+ $haveRevisions = $revisions && $revisions->getNumRows() > 0;\r
+\r
+ $titleObj = SpecialPage::getTitleFor( "Mergehistory" );\r
+ $action = $titleObj->getLocalURL( "action=submit" );\r
+ # Start the form here\r
+ $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'merge' ) );\r
+ $wgOut->addHtml( $top );\r
+\r
+ if( $haveRevisions ) {\r
+ # Format the user-visible controls (comment field, submission button)\r
+ # in a nice little table\r
+ $align = $wgContLang->isRtl() ? 'left' : 'right';\r
+ $table =\r
+ Xml::openElement( 'fieldset' ) .\r
+ Xml::openElement( 'table' ) .\r
+ "<tr>\r
+ <td colspan='2'>" .\r
+ wfMsgExt( 'mergehistory-merge', array('parseinline'),\r
+ $this->mTargetObj->getPrefixedText(), $this->mDestObj->getPrefixedText() ) .\r
+ "</td>\r
+ </tr>\r
+ <tr>\r
+ <td align='$align'>" .\r
+ Xml::label( wfMsg( 'undeletecomment' ), 'wpComment' ) .\r
+ "</td>\r
+ <td>" .\r
+ Xml::input( 'wpComment', 50, $this->mComment ) .\r
+ "</td>\r
+ </tr>\r
+ <tr>\r
+ <td> </td>\r
+ <td>" .\r
+ Xml::submitButton( wfMsg( 'mergehistory-submit' ), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) .\r
+ "</td>\r
+ </tr>" .\r
+ Xml::closeElement( 'table' ) .\r
+ Xml::closeElement( 'fieldset' );\r
+\r
+ $wgOut->addHtml( $table );\r
+ }\r
+\r
+ $wgOut->addHTML( "<h2 id=\"mergehistory\">" . wfMsgHtml( "mergehistory-list" ) . "</h2>\n" );\r
+\r
+ if( $haveRevisions ) {\r
+ $wgOut->addHTML( $revisions->getNavigationBar() );\r
+ $wgOut->addHTML( "<ul>" );\r
+ $wgOut->addHTML( $revisions->getBody() );\r
+ $wgOut->addHTML( "</ul>" );\r
+ $wgOut->addHTML( $revisions->getNavigationBar() );\r
+ } else {\r
+ $wgOut->addWikiText( wfMsg( "mergehistory-empty" ) );\r
+ }\r
+\r
+ # Show relevant lines from the deletion log:\r
+ $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'merge' ) ) . "</h2>\n" );\r
+ $logViewer = new LogViewer(\r
+ new LogReader(\r
+ new FauxRequest(\r
+ array( 'page' => $this->mTargetObj->getPrefixedText(),\r
+ 'type' => 'merge' ) ) ) );\r
+ $logViewer->showList( $wgOut );\r
+ \r
+ # Slip in the hidden controls here\r
+ # When we submit, go by page ID to avoid some nasty but unlikely collisions.\r
+ # Such would happen if a page was renamed after the form loaded, but before submit\r
+ $misc = Xml::hidden( 'targetID', $this->mTargetObj->getArticleID() );\r
+ $misc .= Xml::hidden( 'destID', $this->mDestObj->getArticleID() );\r
+ $misc .= Xml::hidden( 'target', $this->mTarget );\r
+ $misc .= Xml::hidden( 'dest', $this->mDest );\r
+ $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() );\r
+ $misc .= Xml::closeElement( 'form' );\r
+ $wgOut->addHtml( $misc );\r
+\r
+ return true;\r
+ }\r
+ \r
+ function formatRevisionRow( $row ) {\r
+ global $wgUser, $wgLang;\r
+ \r
+ $rev = new Revision( $row );\r
+ \r
+ $stxt = ''; \r
+ $last = $this->message['last'];\r
+ \r
+ $ts = wfTimestamp( TS_MW, $row->rev_timestamp );\r
+ $checkBox = wfRadio( "mergepoint", $ts, false );\r
+ \r
+ $pageLink = $this->sk->makeKnownLinkObj( $rev->getTitle(), $wgLang->timeanddate( $ts ), 'oldid=' . $rev->getID() );\r
+ if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {\r
+ $pageLink = '<span class="history-deleted">' . $pageLink . '</span>';\r
+ }\r
+ \r
+ # Last link\r
+ if( !$rev->userCan( Revision::DELETED_TEXT ) )\r
+ $last = $this->message['last'];\r
+ else if( isset($this->prevId[$row->rev_id]) )\r
+ $last = $this->sk->makeKnownLinkObj( $rev->getTitle(), $this->message['last'], \r
+ "&diff=" . $row->rev_id . "&oldid=" . $this->prevId[$row->rev_id] );\r
+ \r
+ $userLink = $this->sk->userLink( $rev->getUser(), $rev->getUserText() )\r
+ . $this->sk->userToolLinks( $rev->getUser(), $rev->getUserText() );\r
+ \r
+ if(!is_null($size = $row->rev_len)) {\r
+ if($size == 0)\r
+ $stxt = wfMsgHtml('historyempty');\r
+ else\r
+ $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) );\r
+ }\r
+ $comment = $this->sk->revComment( $rev );\r
+ \r
+ return "<li>$checkBox ($last) $pageLink . . $userLink $stxt $comment</li>";\r
+ }\r
+\r
+ /**\r
+ * Fetch revision text link if it's available to all users\r
+ * @return string\r
+ */\r
+ function getPageLink( $row, $titleObj, $ts, $target ) {\r
+ global $wgLang;\r
+ \r
+ if( !$this->userCan($row, Revision::DELETED_TEXT) ) {\r
+ return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>';\r
+ } else {\r
+ $link = $this->sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ), "target=$target×tamp=$ts" );\r
+ if( $this->isDeleted($row, Revision::DELETED_TEXT) )\r
+ $link = '<span class="history-deleted">' . $link . '</span>';\r
+ return $link;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Fetch revision's user id if it's available to this user\r
+ * @return string\r
+ */\r
+ function getUser( $row ) { \r
+ if( !$this->userCan($row, Revision::DELETED_USER) ) {\r
+ return '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';\r
+ } else {\r
+ $link = $this->sk->userLink( $row->rev_user, $row->rev_user_text ) . $this->sk->userToolLinks( $row->rev_user, $row->rev_user_text );\r
+ if( $this->isDeleted($row, Revision::DELETED_USER) )\r
+ $link = '<span class="history-deleted">' . $link . '</span>';\r
+ return $link;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Fetch revision comment if it's available to this user\r
+ * @return string\r
+ */\r
+ function getComment( $row ) {\r
+ if( !$this->userCan($row, Revision::DELETED_COMMENT) ) {\r
+ return '<span class="history-deleted"><span class="comment">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span></span>';\r
+ } else {\r
+ $link = $this->sk->commentBlock( $row->rev_comment );\r
+ if( $this->isDeleted($row, Revision::DELETED_COMMENT) )\r
+ $link = '<span class="history-deleted">' . $link . '</span>';\r
+ return $link;\r
+ }\r
+ }\r
+\r
+ function merge() {\r
+ global $wgOut, $wgUser;\r
+ # Get the titles directly from the IDs, in case the target page params \r
+ # were spoofed. The queries are done based on the IDs, so it's best to \r
+ # keep it consistent...\r
+ $targetTitle = Title::newFromID( $this->mTargetID );\r
+ $destTitle = Title::newFromID( $this->mDestID );\r
+ if( is_null($targetTitle) || is_null($destTitle) )\r
+ return false; // validate these\r
+ # Verify that this timestamp is valid\r
+ # Must be older than the destination page\r
+ $dbw = wfGetDB( DB_MASTER );\r
+ $maxtimestamp = $dbw->selectField( 'revision', 'MIN(rev_timestamp)',\r
+ array('rev_page' => $this->mDestID ),\r
+ __METHOD__ );\r
+ # Destination page must exist with revisions\r
+ if( !$maxtimestamp ) {\r
+ $wgOut->addHtml( wfMsg('mergehistory-fail') );\r
+ return false;\r
+ }\r
+ # Leave the latest version no matter what\r
+ $lasttime = $dbw->selectField( array('page','revision'), \r
+ 'rev_timestamp',\r
+ array('page_id' => $this->mTargetID, 'page_latest = rev_id' ),\r
+ __METHOD__ );\r
+ # Take the most restrictive of the twain\r
+ $maxtimestamp = ($lasttime < $maxtimestamp) ? $lasttime : $maxtimestamp;\r
+ if( $this->mTimestamp && $this->mTimestamp >= $maxtimestamp ) {\r
+ $wgOut->addHtml( wfMsg('mergehistory-fail') );\r
+ return false;\r
+ }\r
+ # Update the revisions\r
+ if( $this->mTimestamp )\r
+ $timewhere = "rev_timestamp <= {$this->mTimestamp}";\r
+ else\r
+ $timewhere = '1 = 1';\r
+ \r
+ $dbw->update( 'revision',\r
+ array( 'rev_page' => $this->mDestID ),\r
+ array( 'rev_page' => $this->mTargetID, \r
+ "rev_timestamp < {$maxtimestamp}",\r
+ $timewhere ),\r
+ __METHOD__ );\r
+ # Check if this did anything\r
+ $count = $dbw->affectedRows();\r
+ if( !$count ) {\r
+ $wgOut->addHtml( wfMsg('mergehistory-fail') );\r
+ return false;\r
+ }\r
+ # Update our logs\r
+ $log = new LogPage( 'merge' );\r
+ $log->addEntry( 'merge', $targetTitle, $this->mComment, \r
+ array($destTitle->getPrefixedText(),$this->mTimestamp) );\r
+ \r
+ $wgOut->addHtml( wfMsgExt( 'mergehistory-success', array('parseinline'),\r
+ $targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count ) );\r
+ \r
+ wfRunHooks( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) );\r
+ \r
+ return true;\r
+ }\r
+}\r
+\r
+class MergeHistoryPager extends ReverseChronologicalPager {\r
+ public $mForm, $mConds;\r
+\r
+ function __construct( $form, $conds = array(), $title, $title2 ) {\r
+ $this->mForm = $form;\r
+ $this->mConds = $conds;\r
+ $this->title = $title;\r
+ $this->articleID = $title->getArticleID();\r
+ \r
+ $dbw = wfGetDB( DB_SLAVE );\r
+ $maxtimestamp = $dbw->selectField('revision', 'MIN(rev_timestamp)',\r
+ array('rev_page' => $title2->getArticleID() ),\r
+ __METHOD__ );\r
+ $maxtimestamp = $maxtimestamp ? $maxtimestamp : 0;\r
+ $this->maxTimestamp = $maxtimestamp;\r
+ \r
+ parent::__construct();\r
+ }\r
+ \r
+ function getStartBody() {\r
+ wfProfileIn( __METHOD__ );\r
+ # Do a link batch query\r
+ $this->mResult->seek( 0 );\r
+ $batch = new LinkBatch();\r
+ # Give some pointers to make (last) links\r
+ $this->mForm->prevId = array();\r
+ while( $row = $this->mResult->fetchObject() ) {\r
+ $batch->addObj( Title::makeTitleSafe( NS_USER, $row->rev_user_text ) );\r
+ $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->rev_user_text ) );\r
+ \r
+ $rev_id = isset($rev_id) ? $rev_id : $row->rev_id;\r
+ if( $rev_id > $row->rev_id )\r
+ $this->mForm->prevId[$rev_id] = $row->rev_id;\r
+ else if( $rev_id < $row->rev_id )\r
+ $this->mForm->prevId[$row->rev_id] = $rev_id;\r
+ \r
+ $rev_id = $row->rev_id;\r
+ }\r
+ \r
+ $batch->execute();\r
+ $this->mResult->seek( 0 );\r
+\r
+ wfProfileOut( __METHOD__ );\r
+ return '';\r
+ }\r
+ \r
+ function formatRow( $row ) {\r
+ $block = new Block;\r
+ return $this->mForm->formatRevisionRow( $row );\r
+ }\r
+\r
+ function getQueryInfo() {\r
+ $conds = $this->mConds;\r
+ $conds['rev_page'] = $this->articleID;\r
+ $conds[] = "rev_timestamp < {$this->maxTimestamp}";\r
+ # Skip the latest one, as that could cause problems\r
+ if( $page = $this->title->getLatestRevID() )\r
+ $conds[] = "rev_id != {$page}";\r
+ \r
+ return array(\r
+ 'tables' => array('revision'),\r
+ 'fields' => array( 'rev_minor_edit', 'rev_timestamp', 'rev_user', 'rev_user_text', 'rev_comment', \r
+ 'rev_id', 'rev_page', 'rev_text_id', 'rev_len', 'rev_deleted' ),\r
+ 'conds' => $conds\r
+ );\r
+ }\r
+\r
+ function getIndexField() {\r
+ return 'rev_timestamp';\r
+ }\r
+}\r
'Log' => array( 'SpecialPage', 'Log' ),
'Blockip' => array( 'SpecialPage', 'Blockip', 'block' ),
'Undelete' => array( 'SpecialPage', 'Undelete', 'deletedhistory' ),
- 'Import' => array( 'SpecialPage', "Import", 'import' ),
+ 'Import' => array( 'SpecialPage', 'Import', 'import' ),
'Lockdb' => array( 'SpecialPage', 'Lockdb', 'siteadmin' ),
'Unlockdb' => array( 'SpecialPage', 'Unlockdb', 'siteadmin' ),
'Userrights' => array( 'SpecialPage', 'Userrights', 'userrights' ),
'Mytalk' => array( 'SpecialMytalk' ),
'Mycontributions' => array( 'SpecialMycontributions' ),
'Listadmins' => array( 'SpecialRedirectToSpecial', 'Listadmins', 'Listusers', 'sysop' ),
+ 'MergeHistory' => array( 'SpecialPage', 'Mergehistory', 'mergehistory' ),
);
static public $mAliases;
}
return $pages;
}
+
+ /**
+ * Return categorised listable log pages which are available
+ * for the current user, but not for everyone
+ * @static
+ */
+ static function getRestrictedLogs() {
+ global $wgUser, $wgLogRestrictions, $wgLogNames;
+
+ $pages = array();
+
+ if ( isset($wgLogRestrictions) ) {
+ foreach ( $wgLogRestrictions as $type => $restriction ) {
+ $page = SpecialPage::getTitleFor( 'Log', $type );
+ if ( $restriction !='' && $restriction !='*' && $wgUser->isAllowed( $restriction ) ) {
+ $name = wfMsgHtml( $wgLogNames[$type] );
+ $pages[$name] = $page;
+ }
+ }
+ }
+ return $pages;
+ }
/**
* Execute a special page path.
rcFormatDiff( $obj ),
$title->getFullURL(),
$obj->rc_timestamp,
- $obj->rc_user_text,
+ ($obj->rc_deleted & Revision::DELETED_USER) ? wfMsgHtml('rev-deleted-user') : $obj->rc_user_text,
$talkpage->getFullURL()
);
$feed->outItem( $item );
return rcFormatDiffRow( $titleObj,
$row->rc_last_oldid, $row->rc_this_oldid,
$timestamp,
- $row->rc_comment );
+ ($row->rc_deleted & Revision::DELETED_COMMENT) ? wfMsgHtml('rev-deleted-comment') : $row->rc_comment,
+ ($row->rc_deleted & LogViewer::DELETED_ACTION) ? wfMsgHtml('rev-deleted-event') : $row->rc_actiontext );
}
-function rcFormatDiffRow( $title, $oldid, $newid, $timestamp, $comment ) {
+function rcFormatDiffRow( $title, $oldid, $newid, $timestamp, $comment, $actiontext='' ) {
global $wgFeedDiffCutoff, $wgContLang, $wgUser;
$fname = 'rcFormatDiff';
wfProfileIn( $fname );
$skin = $wgUser->getSkin();
+ # log enties
+ if( $actiontext ) $comment = "$actiontext $comment";
$completeText = '<p>' . $skin->formatComment( $comment ) . "</p>\n";
//NOTE: Check permissions for anonymous users, not current user.
<?php
/**
- * Not quite ready for production use yet; need to fix up the restricted mode,
- * and provide for preservation across delete/undelete of the page.
+ * Special page allowing users with the appropriate permissions to view
+ * and hide revisions. Log items can also be hidden.
*
- * To try this out, set up extra permissions something like:
- * $wgGroupPermissions['sysop']['deleterevision'] = true;
- * $wgGroupPermissions['bureaucrat']['hiderevision'] = true;
+ * @addtogroup SpecialPage
*/
function wfSpecialRevisiondelete( $par = null ) {
- global $wgOut, $wgRequest;
-
- $target = $wgRequest->getVal( 'target' );
- $oldid = $wgRequest->getIntArray( 'oldid' );
-
+ global $wgOut, $wgRequest, $wgUser, $wgAllowLogDeletion;
+ # Handle our many different possible input types
+ $target = $wgRequest->getText( 'target' );
+ $oldid = $wgRequest->getArray( 'oldid' );
+ $artimestamp = $wgRequest->getArray( 'artimestamp' );
+ $logid = $wgAllowLogDeletion ? $wgRequest->getArray( 'logid' ) : '';
+ $image = $wgRequest->getArray( 'oldimage' );
+ $fileid = $wgRequest->getArray( 'fileid' );
+ # For reviewing deleted files...
+ $file = $wgRequest->getVal( 'file' );
+ # If this is a revision, then we need a target page
$page = Title::newFromUrl( $target );
-
- if( is_null( $page ) ) {
- $wgOut->showErrorPage( 'notargettitle', 'notargettext' );
+ if( is_null($page) && is_null($logid) ) {
+ $wgOut->addWikiText( wfMsgHtml( 'undelete-header' ) );
return;
}
+ # Only one target set at a time please!
+ $inputs = !empty($file) + !empty($oldid) + !empty($logid) + !empty($artimestamp) +
+ !empty($fileid) + !empty($image);
- if( is_null( $oldid ) ) {
+ if( $inputs > 1 || $inputs==0 ) {
$wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
return;
}
-
- $form = new RevisionDeleteForm( $wgRequest );
+ # Either submit or create our form
+ $form = new RevisionDeleteForm( $page, $oldid, $logid, $artimestamp, $fileid, $image, $file );
if( $wgRequest->wasPosted() ) {
$form->submit( $wgRequest );
- } else {
- $form->show( $wgRequest );
+ } else if( $oldid || $artimestamp ) {
+ $form->showRevs( $wgRequest );
+ } else if( $fileid || $image ) {
+ $form->showImages( $wgRequest );
+ } else if( $logid ) {
+ $form->showEvents( $wgRequest );
+ }
+ # Show relevant lines from the deletion log
+ # This will show even if said ID does not exist...might be helpful
+ if( !is_null($page) ) {
+ $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
+ $logViewer = new LogViewer(
+ new LogReader(
+ new FauxRequest(
+ array( 'page' => $page->getPrefixedText(), 'type' => 'delete' ) ) ) );
+ $logViewer->showList( $wgOut );
}
}
class RevisionDeleteForm {
/**
* @param Title $page
- * @param int $oldid
+ * @param array $oldids
+ * @param array $logids
+ * @param array $artimestamps
+ * @param array $fileids
+ * @param array $oldimages
+ * @param string $file
*/
- function __construct( $request ) {
+ function __construct( $page, $oldids=null, $logids=null, $artimestamps=null, $fileids=null, $oldimages=null, $file=null ) {
global $wgUser;
-
- $target = $request->getVal( 'target' );
- $this->page = Title::newFromUrl( $target );
-
- $this->revisions = $request->getIntArray( 'oldid', array() );
-
+
+ $this->page = $page;
$this->skin = $wgUser->getSkin();
+
+ // For reviewing deleted files
+ if( $file ) {
+ $oimage = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $page, $file );
+ $oimage->load();
+ // Check if user is allowed to see this file
+ if( !$oimage->userCan(File::DELETED_FILE) ) {
+ $wgOut->permissionRequired( 'hiderevision' );
+ return false;
+ } else {
+ return $this->showFile( $file );
+ }
+ }
+ // At this point, we should only have one of these
+ if( $oldids ) {
+ $this->revisions = $oldids;
+ $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT );
+ $this->deletetype='oldid';
+ } else if( $artimestamps ) {
+ $this->archrevs = $artimestamps;
+ $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT );
+ $this->deletetype='artimestamp';
+ } else if( $oldimages ) {
+ $this->ofiles = $oldimages;
+ $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE );
+ $this->deletetype='oldimage';
+ } else if( $fileids ) {
+ $this->afiles = $fileids;
+ $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE );
+ $this->deletetype='fileid';
+ } else if( $logids ) {
+ $this->events = $logids;
+ $hide_content_name = array( 'revdelete-hide-name', 'wpHideName', LogViewer::DELETED_ACTION );
+ $this->deletetype='logid';
+ }
+ // Our checkbox messages depends one what we are doing,
+ // e.g. we don't hide "text" for logs or images
$this->checks = array(
- array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT ),
+ $hide_content_name,
array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ),
array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ),
array( 'revdelete-hide-restricted', 'wpHideRestricted', Revision::DELETED_RESTRICTED ) );
}
/**
+ * Show a deleted file version requested by the visitor.
+ */
+ function showFile( $key ) {
+ global $wgOut, $wgRequest;
+ $wgOut->disable();
+
+ # We mustn't allow the output to be Squid cached, otherwise
+ # if an admin previews a deleted image, and it's cached, then
+ # a user without appropriate permissions can toddle off and
+ # nab the image, and Squid will serve it
+ $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
+ $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
+ $wgRequest->response()->header( 'Pragma: no-cache' );
+
+ $store = FileStore::get( 'hidden' );
+ $store->stream( $key );
+ }
+
+ /**
+ * This lets a user set restrictions for live and archived revisions
+ * @param WebRequest $request
+ */
+ function showRevs( $request ) {
+ global $wgOut, $wgUser, $action;
+
+ $UserAllowed = true;
+
+ $count = ($this->deletetype=='oldid') ?
+ count($this->revisions) : count($this->archrevs);
+ $wgOut->addWikiText( wfMsgExt( 'revdelete-selected', array('parsemag'),
+ $this->page->getPrefixedText(), $count ) );
+
+ $bitfields = 0;
+ $wgOut->addHtml( "<ul>" );
+
+ $where = $revObjs = array();
+ $dbr = wfGetDB( DB_SLAVE );
+ // Live revisions...
+ if( $this->deletetype=='oldid' ) {
+ // Run through and pull all our data in one query
+ foreach( $this->revisions as $revid ) {
+ $where[] = intval($revid);
+ }
+ $whereClause = 'rev_id IN(' . implode(',',$where) . ')';
+ $result = $dbr->select( 'revision', '*',
+ array( 'rev_page' => $this->page->getArticleID(),
+ $whereClause ),
+ __METHOD__ );
+ while( $row = $dbr->fetchObject( $result ) ) {
+ $revObjs[$row->rev_id] = new Revision( $row );
+ }
+ foreach( $this->revisions as $revid ) {
+ // Hiding top revisison is bad
+ if( !is_object($revObjs[$revid]) || $revObjs[$revid]->isCurrent() ) {
+ $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+ return;
+ } else if( !$revObjs[$revid]->userCan(Revision::DELETED_RESTRICTED) ) {
+ // If a rev is hidden from sysops
+ if( $action != 'submit') {
+ $wgOut->permissionRequired( 'hiderevision' );
+ return;
+ }
+ $UserAllowed = false;
+ }
+ $wgOut->addHtml( $this->historyLine( $revObjs[$revid] ) );
+ $bitfields |= $revObjs[$revid]->mDeleted;
+ }
+ // The archives...
+ } else {
+ // Run through and pull all our data in one query
+ foreach( $this->archrevs as $timestamp ) {
+ $where[] = $dbr->addQuotes( $timestamp );
+ }
+ $whereClause = 'ar_timestamp IN(' . implode(',',$where) . ')';
+ $result = $dbr->select( 'archive', '*',
+ array( 'ar_namespace' => $this->page->getNamespace(),
+ 'ar_title' => $this->page->getDBKey(),
+ $whereClause ),
+ __METHOD__ );
+ while( $row = $dbr->fetchObject( $result ) ) {
+ $revObjs[$row->ar_timestamp] = new Revision( array(
+ 'page' => $this->page->getArticleId(),
+ 'id' => $row->ar_rev_id,
+ 'text' => $row->ar_text_id,
+ 'comment' => $row->ar_comment,
+ 'user' => $row->ar_user,
+ 'user_text' => $row->ar_user_text,
+ 'timestamp' => $row->ar_timestamp,
+ 'minor_edit' => $row->ar_minor_edit,
+ 'text_id' => $row->ar_text_id,
+ 'deleted' => $row->ar_deleted,
+ 'len' => $row->ar_len) );
+ }
+ foreach( $this->archrevs as $timestamp ) {
+ if( !is_object($revObjs[$timestamp]) ) {
+ $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+ return;
+ }
+ }
+ foreach( $revObjs as $rev ) {
+ if( !$rev->userCan(Revision::DELETED_RESTRICTED) ) {
+ //if a rev is hidden from sysops
+ if( $action != 'submit') {
+ $wgOut->permissionRequired( 'hiderevision' );
+ return;
+ }
+ $UserAllowed = false;
+ }
+ $wgOut->addHtml( $this->historyLine( $rev ) );
+ $bitfields |= $rev->mDeleted;
+ }
+ }
+ $wgOut->addHtml( "</ul>" );
+
+ $wgOut->addWikiText( wfMsgHtml( 'revdelete-text' ) );
+ //Normal sysops can always see what they did, but can't always change it
+ if( !$UserAllowed ) return;
+
+ $items = array(
+ wfInputLabel( wfMsgHtml( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
+ wfSubmitButton( wfMsgHtml( 'revdelete-submit' ) ) );
+ $hidden = array(
+ wfHidden( 'wpEditToken', $wgUser->editToken() ),
+ wfHidden( 'target', $this->page->getPrefixedText() ),
+ wfHidden( 'type', $this->deletetype ) );
+ if( $this->deletetype=='oldid' ) {
+ foreach( $revObjs as $rev )
+ $hidden[] = wfHidden( 'oldid[]', $rev->getID() );
+ } else {
+ foreach( $revObjs as $rev )
+ $hidden[] = wfHidden( 'artimestamp[]', $rev->getTimestamp() );
+ }
+ $special = SpecialPage::getTitleFor( 'Revisiondelete' );
+ $wgOut->addHtml( wfElement( 'form', array(
+ 'method' => 'post',
+ 'action' => $special->getLocalUrl( 'action=submit' ) ),
+ null ) );
+
+ $wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'revdelete-legend' ) . '</legend>' );
+ // FIXME: all items checked for just one rev are checked, even if not set for the others
+ foreach( $this->checks as $item ) {
+ list( $message, $name, $field ) = $item;
+ $wgOut->addHtml( "<div>" .
+ wfCheckLabel( wfMsgHtml( $message), $name, $name, $bitfields & $field ) .
+ "</div>\n" );
+ }
+ $wgOut->addHtml( '</fieldset>' );
+ foreach( $items as $item ) {
+ $wgOut->addHtml( '<p>' . $item . '</p>' );
+ }
+ foreach( $hidden as $item ) {
+ $wgOut->addHtml( $item );
+ }
+
+ $wgOut->addHtml( '</form>' );
+ }
+
+ /**
+ * This lets a user set restrictions for archived images
+ * @param WebRequest $request
+ */
+ function showImages( $request ) {
+ global $wgOut, $wgUser, $action;
+
+ $UserAllowed = true;
+
+ $count = ($this->deletetype=='oldimage') ? count($this->ofiles) : count($this->afiles);
+ $wgOut->addWikiText( wfMsgExt( 'revdelete-selected', array('parsemag'), $this->page->getPrefixedText(), $count ) );
+
+ $bitfields = 0;
+ $wgOut->addHtml( "<ul>" );
+
+ $where = $filesObjs = array();
+ $dbr = wfGetDB( DB_SLAVE );
+ // Live old revisions...
+ if( $this->deletetype=='oldimage' ) {
+ // Run through and pull all our data in one query
+ foreach( $this->ofiles as $timestamp ) {
+ $where[] = $dbr->addQuotes( $timestamp.'!'.$this->page->getDbKey() );
+ }
+ $whereClause = 'oi_archive_name IN(' . implode(',',$where) . ')';
+ $result = $dbr->select( 'oldimage', '*',
+ array( 'oi_name' => $this->page->getDbKey(),
+ $whereClause ),
+ __METHOD__ );
+ while( $row = $dbr->fetchObject( $result ) ) {
+ $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
+ $filesObjs[$row->oi_archive_name]->user = $row->oi_user;
+ $filesObjs[$row->oi_archive_name]->userText = $row->oi_user_text;
+ }
+ // Check through our images
+ foreach( $this->ofiles as $timestamp ) {
+ $archivename = $timestamp.'!'.$this->page->getDbKey();
+ if( !isset($filesObjs[$archivename]) ) {
+ $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+ return;
+ }
+ }
+ foreach( $filesObjs as $file ) {
+ if( !isset($file) ) {
+ $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+ return;
+ } else if( !$file->userCan(File::DELETED_RESTRICTED) ) {
+ // If a rev is hidden from sysops
+ if( $action != 'submit' ) {
+ $wgOut->permissionRequired( 'hiderevision' );
+ return;
+ }
+ $UserAllowed = false;
+ }
+ // Inject history info
+ $wgOut->addHtml( $this->uploadLine( $file ) );
+ $bitfields |= $file->deleted;
+ }
+ // Archived files...
+ } else {
+ // Run through and pull all our data in one query
+ foreach( $this->afiles as $id ) {
+ $where[] = intval($id);
+ }
+ $whereClause = 'fa_id IN(' . implode(',',$where) . ')';
+ $result = $dbr->select( 'filearchive', '*',
+ array( 'fa_name' => $this->page->getDbKey(),
+ $whereClause ),
+ __METHOD__ );
+ while( $row = $dbr->fetchObject( $result ) ) {
+ $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row );
+ }
+
+ foreach( $this->afiles as $fileid ) {
+ if( !isset($filesObjs[$fileid]) ) {
+ $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+ return;
+ } else if( !$filesObjs[$fileid]->userCan(File::DELETED_RESTRICTED) ) {
+ // If a rev is hidden from sysops
+ if( $action != 'submit' ) {
+ $wgOut->permissionRequired( 'hiderevision' );
+ return;
+ }
+ $UserAllowed = false;
+ }
+ // Inject history info
+ $wgOut->addHtml( $this->uploadLine( $filesObjs[$fileid] ) );
+ $bitfields |= $filesObjs[$fileid]->deleted;
+ }
+ }
+ $wgOut->addHtml( "</ul>" );
+
+ $wgOut->addWikiText( wfMsgHtml( 'revdelete-text' ) );
+ //Normal sysops can always see what they did, but can't always change it
+ if( !$UserAllowed ) return;
+
+ $items = array(
+ wfInputLabel( wfMsgHtml( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
+ wfSubmitButton( wfMsgHtml( 'revdelete-submit' ) ) );
+ $hidden = array(
+ wfHidden( 'wpEditToken', $wgUser->editToken() ),
+ wfHidden( 'target', $this->page->getPrefixedText() ),
+ wfHidden( 'type', $this->deletetype ) );
+ if( $this->deletetype=='oldimage' ) {
+ foreach( $this->ofiles as $filename )
+ $hidden[] = wfHidden( 'oldimage[]', $filename );
+ } else {
+ foreach( $this->afiles as $fileid )
+ $hidden[] = wfHidden( 'fileid[]', $fileid );
+ }
+ $special = SpecialPage::getTitleFor( 'Revisiondelete' );
+ $wgOut->addHtml( wfElement( 'form', array(
+ 'method' => 'post',
+ 'action' => $special->getLocalUrl( 'action=submit' ) ),
+ null ) );
+
+ $wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'revdelete-legend' ) . '</legend>' );
+ // FIXME: all items checked for just one file are checked, even if not set for the others
+ foreach( $this->checks as $item ) {
+ list( $message, $name, $field ) = $item;
+ $wgOut->addHtml( '<div>' .
+ wfCheckLabel( wfMsgHtml( $message), $name, $name, $bitfields & $field ) .
+ '</div>' );
+ }
+ $wgOut->addHtml( '</fieldset>' );
+ foreach( $items as $item ) {
+ $wgOut->addHtml( '<p>' . $item . '</p>' );
+ }
+ foreach( $hidden as $item ) {
+ $wgOut->addHtml( $item );
+ }
+
+ $wgOut->addHtml( '</form>' );
+ }
+
+ /**
+ * This lets a user set restrictions for log items
* @param WebRequest $request
*/
- function show( $request ) {
- global $wgOut, $wgUser;
+ function showEvents( $request ) {
+ global $wgOut, $wgUser, $action;
- $wgOut->addWikiText( wfMsg( 'revdelete-selected', $this->page->getPrefixedText() ) );
+ $UserAllowed = true;
+ $wgOut->addWikiText( wfMsgExt( 'logdelete-selected', array('parsemag'), count($this->events) ) );
+ $bitfields = 0;
$wgOut->addHtml( "<ul>" );
- foreach( $this->revisions as $revid ) {
- $rev = Revision::newFromTitle( $this->page, $revid );
- if( !isset( $rev ) ) {
+
+ $where = $logRows = array();
+ $dbr = wfGetDB( DB_SLAVE );
+ // Run through and pull all our data in one query
+ foreach( $this->events as $logid ) {
+ $where[] = intval($logid);
+ }
+ $whereClause = 'log_id IN(' . implode(',',$where) . ')';
+ $result = $dbr->select( 'logging', '*',
+ array( $whereClause ),
+ __METHOD__ );
+ while( $row = $dbr->fetchObject( $result ) ) {
+ $logRows[$row->log_id] = $row;
+ }
+ foreach( $this->events as $logid ) {
+ // Don't hide from oversight log!!!
+ if( !isset( $logRows[$logid] ) || $logRows[$logid]->log_type=='oversight' ) {
$wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
return;
+ } else if( !LogViewer::userCan( $logRows[$logid],Revision::DELETED_RESTRICTED) ) {
+ // If an event is hidden from sysops
+ if( $action != 'submit') {
+ $wgOut->permissionRequired( 'hiderevision' );
+ return;
+ }
+ $UserAllowed = false;
}
- $wgOut->addHtml( $this->historyLine( $rev ) );
- $bitfields[] = $rev->mDeleted; // FIXME
+ $wgOut->addHtml( $this->logLine( $logRows[$logid] ) );
+ $bitfields |= $logRows[$logid]->log_deleted;
}
$wgOut->addHtml( "</ul>" );
-
- $wgOut->addWikiText( wfMsg( 'revdelete-text' ) );
+
+ $wgOut->addWikiText( wfMsgHtml( 'revdelete-text' ) );
+ //Normal sysops can always see what they did, but can't always change it
+ if( !$UserAllowed ) return;
$items = array(
- wfInputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
- wfSubmitButton( wfMsg( 'revdelete-submit' ) ) );
+ wfInputLabel( wfMsgHtml( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
+ wfSubmitButton( wfMsgHtml( 'revdelete-submit' ) ) );
$hidden = array(
wfHidden( 'wpEditToken', $wgUser->editToken() ),
- wfHidden( 'target', $this->page->getPrefixedText() ) );
- foreach( $this->revisions as $revid ) {
- $hidden[] = wfHidden( 'oldid[]', $revid );
+ wfHidden( 'type', $this->deletetype ) );
+ foreach( $this->events as $logid ) {
+ $hidden[] = wfHidden( 'logid[]', $logid );
}
$special = SpecialPage::getTitleFor( 'Revisiondelete' );
null ) );
$wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'revdelete-legend' ) . '</legend>' );
+ // FIXME: all items checked for just on event are checked, even if not set for the others
foreach( $this->checks as $item ) {
list( $message, $name, $field ) = $item;
$wgOut->addHtml( '<div>' .
- wfCheckLabel( wfMsg( $message), $name, $name, $rev->isDeleted( $field ) ) .
+ wfCheckLabel( wfMsgHtml( $message), $name, $name, $bitfields & $field ) .
'</div>' );
}
$wgOut->addHtml( '</fieldset>' );
function historyLine( $rev ) {
global $wgContLang;
$date = $wgContLang->timeanddate( $rev->getTimestamp() );
+
+ $difflink=''; $del = '';
+ // Live revisions
+ if( $this->deletetype=='oldid' ) {
+ $difflink = '(' . $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml('diff'),
+ 'diff=' . $rev->getId() . '&oldid=prev' ) . ')';
+ $revlink = $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() );
+ } else {
+ // Archived revisions
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ $target = $this->page->getPrefixedText();
+ $revlink = $this->skin->makeLinkObj( $undelete, $date, "target=$target×tamp=" . $rev->getTimestamp() );
+ }
+
+ if( $rev->isDeleted(Revision::DELETED_TEXT) ) {
+ $revlink = '<span class="history-deleted">'.$revlink.'</span>';
+ $del = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
+ if( !$rev->userCan(Revision::DELETED_TEXT) ) {
+ $revlink = '<span class="history-deleted">'.$date.'</span>';
+ }
+ }
+
return
- "<li>" .
- $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() ) .
- " " .
- $this->skin->revUserLink( $rev ) .
- " " .
- $this->skin->revComment( $rev ) .
- "</li>";
+ "<li> $difflink $revlink " . $this->skin->revUserLink( $rev ) . " " . $this->skin->revComment( $rev ) . "$del</li>";
+ }
+
+ /**
+ * @param File $file
+ * This can work for old or archived revisions
+ * @returns string
+ */
+ function uploadLine( $file ) {
+ global $wgContLang, $wgTitle;
+
+ $target = $this->page->getPrefixedText();
+ $date = $wgContLang->timeanddate( $file->timestamp, true );
+
+ $del = '';
+ // Special:Undelete for viewing archived images
+ if( $this->deletetype=='fileid' ) {
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ $pageLink = $this->skin->makeKnownLinkObj( $undelete, $date, "target=$target&file=$file->key" );
+ // Revisiondelete for viewing images
+ } else {
+ # Hidden files...
+ if( $file->isDeleted(File::DELETED_FILE) ) {
+ $del = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
+ if( !$file->userCan(File::DELETED_FILE) ) {
+ $pageLink = $date;
+ } else {
+ $pageLink = $this->skin->makeKnownLinkObj( $wgTitle, $date,
+ "target=$target&file=$file->sha1.".$file->getExtension() );
+ }
+ $pageLink = '<span class="history-deleted">' . $pageLink . '</span>';
+ # Regular files...
+ } else {
+ $url = $file->getUrlRel();
+ $pageLink = "<a href=\"{$url}\">{$date}</a>";
+ }
+ }
+
+ $data = wfMsgHtml( 'widthheight',
+ $wgContLang->formatNum( $file->width ),
+ $wgContLang->formatNum( $file->height ) ) .
+ ' (' . wfMsgHtml( 'nbytes', $wgContLang->formatNum( $file->size ) ) . ')';
+
+ return "<li> $pageLink " . $this->fileUserLink( $file ) . " $data " . $this->fileComment( $file ) . "$del</li>";
+ }
+
+ /**
+ * @param Array $event row
+ * @returns string
+ */
+ function logLine( $event ) {
+ global $wgContLang;
+
+ $date = $wgContLang->timeanddate( $event->log_timestamp );
+ $paramArray = LogPage::extractParams( $event->log_params );
+
+ if( !LogViewer::userCan($event,LogViewer::DELETED_ACTION) ) {
+ $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+ } else {
+ $title = Title::makeTitle( $event->log_namespace, $event->log_title );
+ $action = LogPage::actionText( $event->log_type, $event->log_action, $title, $this->skin, $paramArray, true, true );
+ if( $event->log_deleted & LogViewer::DELETED_ACTION )
+ $action = '<span class="history-deleted">' . $action . '</span>';
+ }
+ return
+ "<li>$date" . " " . $this->skin->logUserLink( $event ) . " $action " . $this->skin->logComment( $event ) . "</li>";
+ }
+
+ /**
+ * Generate a user link if the current user is allowed to view it
+ * @param ArchivedFile $file
+ * @param $isPublic, bool, show only if all users can see it
+ * @return string HTML
+ */
+ function fileUserLink( $file, $isPublic = false ) {
+ if( $file->isDeleted( File::DELETED_USER ) && $isPublic ) {
+ $link = wfMsgHtml( 'rev-deleted-user' );
+ } else if( $file->userCan( File::DELETED_USER ) ) {
+ $link = $this->skin->userLink( $file->user, $file->userText );
+ } else {
+ $link = wfMsgHtml( 'rev-deleted-user' );
+ }
+ if( $file->isDeleted( File::DELETED_USER ) ) {
+ return '<span class="history-deleted">' . $link . '</span>';
+ }
+ return $link;
+ }
+
+ /**
+ * Generate a user tool link cluster if the current user is allowed to view it
+ * @param ArchivedFile $file
+ * @param $isPublic, bool, show only if all users can see it
+ * @return string HTML
+ */
+ function fileUserTools( $file, $isPublic = false ) {
+ if( $file->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
+ $link = wfMsgHtml( 'rev-deleted-user' );
+ } else if( $file->userCan( Revision::DELETED_USER ) ) {
+ $link = $this->skin->userLink( $file->user, $file->userText ) .
+ $this->userToolLinks( $file->user, $file->userText );
+ } else {
+ $link = wfMsgHtml( 'rev-deleted-user' );
+ }
+ if( $file->isDeleted( Revision::DELETED_USER ) ) {
+ return '<span class="history-deleted">' . $link . '</span>';
+ }
+ return $link;
+ }
+
+ /**
+ * Wrap and format the given file's comment block, if the current
+ * user is allowed to view it.
+ *
+ * @param ArchivedFile $file
+ * @return string HTML
+ */
+ function fileComment( $file, $isPublic = false ) {
+ if( $file->isDeleted( File::DELETED_COMMENT ) && $isPublic ) {
+ $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
+ } else if( $file->userCan( File::DELETED_COMMENT ) ) {
+ $block = $this->skin->commentBlock( $file->description );
+ } else {
+ $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
+ }
+ if( $file->isDeleted( File::DELETED_COMMENT ) ) {
+ return "<span class=\"history-deleted\">$block</span>";
+ }
+ return $block;
}
/**
function submit( $request ) {
$bitfield = $this->extractBitfield( $request );
$comment = $request->getText( 'wpReason' );
- if( $this->save( $bitfield, $comment ) ) {
- return $this->success( $request );
- } else {
- return $this->show( $request );
+
+ $this->target = $request->getText( 'target' );
+ $this->title = Title::newFromURL( $this->target );
+
+ if( $this->save( $bitfield, $comment, $this->title ) ) {
+ $this->success( $request );
+ } else if( $request->getCheck( 'oldid' ) || $request->getCheck( 'artimestamp' ) ) {
+ return $this->showRevs( $request );
+ } else if( $request->getCheck( 'logid' ) ) {
+ return $this->showLogs( $request );
+ } else if( $request->getCheck( 'oldimage' ) || $request->getCheck( 'fileid' ) ) {
+ return $this->showImages( $request );
}
}
function success( $request ) {
global $wgOut;
- $wgOut->addWikiText( 'woo' );
+
+ $wgOut->setPagetitle( wfMsgHtml( 'actioncomplete' ) );
+ # Give a link to the log for this page
+ $logtitle = SpecialPage::getTitleFor( 'Log' );
+ $loglink = $this->skin->makeKnownLinkObj( $logtitle, wfMsgHtml( 'viewpagelogs' ),
+ wfArrayToCGI( array('page' => $this->target ) ) );
+ # Give a link to the page history
+ $histlink = $this->skin->makeKnownLinkObj( $this->title, wfMsgHtml( 'revhistory' ),
+ wfArrayToCGI( array('action' => 'history' ) ) );
+ # Link to deleted edits
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ $dellink = $this->skin->makeKnownLinkObj( $undelete, wfMsgHtml( 'undeleterevs' ),
+ wfArrayToCGI( array('target' => $this->target) ) );
+ # Logs themselves don't have histories or archived revisions
+ if( !is_null($this->title) && $this->title->getNamespace() > -1)
+ $wgOut->setSubtitle( '<p>'.$histlink.' / '.$loglink.' / '.$dellink.'</p>' );
+
+ if( $this->deletetype=='logid' ) {
+ $wgOut->addWikiText( wfMsgHtml('logdelete-success'), false );
+ $this->showEvents( $request );
+ } else if( $this->deletetype=='oldid' || $this->deletetype=='artimestamp' ) {
+ $wgOut->addWikiText( wfMsgHtml('revdelete-success'), false );
+ $this->showRevs( $request );
+ } else if( $this->deletetype=='fileid' ) {
+ $wgOut->addWikiText( wfMsgHtml('revdelete-success'), false );
+ $this->showImages( $request );
+ } else if( $this->deletetype=='oldimage' ) {
+ $this->showImages( $request );
+ }
}
/**
return $bitfield;
}
- function save( $bitfield, $reason ) {
+ function save( $bitfield, $reason, $title ) {
$dbw = wfGetDB( DB_MASTER );
+
+ // Don't allow simply locking the interface for no reason
+ if( $bitfield == Revision::DELETED_RESTRICTED )
+ $bitfield = 0;
+
$deleter = new RevisionDeleter( $dbw );
- $deleter->setVisibility( $this->revisions, $bitfield, $reason );
+ // By this point, only one of the below should be set
+ if( isset($this->revisions) ) {
+ return $deleter->setRevVisibility( $title, $this->revisions, $bitfield, $reason );
+ } else if( isset($this->archrevs) ) {
+ return $deleter->setArchiveVisibility( $title, $this->archrevs, $bitfield, $reason );
+ } else if( isset($this->ofiles) ) {
+ return $deleter->setOldImgVisibility( $title, $this->ofiles, $bitfield, $reason );
+ } else if( isset($this->afiles) ) {
+ return $deleter->setArchFileVisibility( $title, $this->afiles, $bitfield, $reason );
+ } else if( isset($this->events) ) {
+ return $deleter->setEventVisibility( $this->events, $bitfield, $reason );
+ }
}
}
*/
class RevisionDeleter {
function __construct( $db ) {
- $this->db = $db;
+ $this->dbw = $db;
}
/**
+ * @param $title, the page these events apply to
* @param array $items list of revision ID numbers
* @param int $bitfield new rev_deleted value
* @param string $comment Comment for log records
*/
- function setVisibility( $items, $bitfield, $comment ) {
- $pages = array();
+ function setRevVisibility( $title, $items, $bitfield, $comment ) {
+ global $wgOut;
+ $userAllowedAll = $success = true;
+ $revIDs = array();
+ $revCount = 0;
+ // Run through and pull all our data in one query
+ foreach( $items as $revid ) {
+ $where[] = intval($revid);
+ }
+ $whereClause = 'rev_id IN(' . implode(',',$where) . ')';
+ $result = $this->dbw->select( 'revision', '*',
+ array( 'rev_page' => $title->getArticleID(),
+ $whereClause ),
+ __METHOD__ );
+ while( $row = $this->dbw->fetchObject( $result ) ) {
+ $revObjs[$row->rev_id] = new Revision( $row );
+ }
// To work!
foreach( $items as $revid ) {
- $rev = Revision::newFromId( $revid );
- if( !isset( $rev ) ) {
- return false;
+ if( !isset($revObjs[$revid]) || $revObjs[$revid]->isCurrent() ) {
+ $success = false;
+ continue; // Must exist
+ } else if( !$revObjs[$revid]->userCan(Revision::DELETED_RESTRICTED) ) {
+ $userAllowedAll=false;
+ continue;
+ }
+ // For logging, maintain a count of revisions
+ if( $revObjs[$revid]->mDeleted != $bitfield ) {
+ $revCount++;
+ $revIDs[]=$revid;
+
+ $this->updateRevision( $revObjs[$revid], $bitfield );
+ $this->updateRecentChangesEdits( $revObjs[$revid], $bitfield, false );
+ }
+ }
+ // Clear caches...
+ // Don't log or touch if nothing changed
+ if( $revCount > 0 ) {
+ $this->updatePage( $title );
+ $this->updateLog( $title, $revCount, $bitfield, $revObjs[$revid]->mDeleted,
+ $comment, $title, 'oldid', $revIDs );
+ }
+ // Where all revs allowed to be set?
+ if( !$userAllowedAll ) {
+ //FIXME: still might be confusing???
+ $wgOut->permissionRequired( 'hiderevision' );
+ return false;
+ }
+
+ return $success;
+ }
+
+ /**
+ * @param $title, the page these events apply to
+ * @param array $items list of revision ID numbers
+ * @param int $bitfield new rev_deleted value
+ * @param string $comment Comment for log records
+ */
+ function setArchiveVisibility( $title, $items, $bitfield, $comment ) {
+ global $wgOut;
+
+ $userAllowedAll = $success = true;
+ $count = 0;
+ $Id_set = array();
+ // Run through and pull all our data in one query
+ foreach( $items as $timestamp ) {
+ $where[] = $this->dbw->addQuotes( $timestamp );
+ }
+ $whereClause = 'ar_timestamp IN(' . implode(',',$where) . ')';
+ $result = $this->dbw->select( 'archive', '*',
+ array( 'ar_namespace' => $title->getNamespace(),
+ 'ar_title' => $title->getDBKey(),
+ $whereClause ),
+ __METHOD__ );
+ while( $row = $this->dbw->fetchObject( $result ) ) {
+ $revObjs[$row->ar_timestamp] = new Revision( array(
+ 'page' => $title->getArticleId(),
+ 'id' => $row->ar_rev_id,
+ 'text' => $row->ar_text_id,
+ 'comment' => $row->ar_comment,
+ 'user' => $row->ar_user,
+ 'user_text' => $row->ar_user_text,
+ 'timestamp' => $row->ar_timestamp,
+ 'minor_edit' => $row->ar_minor_edit,
+ 'text_id' => $row->ar_text_id,
+ 'deleted' => $row->ar_deleted,
+ 'len' => $row->ar_len) );
+ }
+ // To work!
+ foreach( $items as $timestamp ) {
+ // This will only select the first revision with this timestamp.
+ // Since they are all selected/deleted at once, we can just check the
+ // permissions of one. UPDATE is done via timestamp, so all revs are set.
+ if( !is_object($revObjs[$timestamp]) ) {
+ $success = false;
+ continue; // Must exist
+ } else if( !$revObjs[$timestamp]->userCan(Revision::DELETED_RESTRICTED) ) {
+ $userAllowedAll=false;
+ continue;
+ }
+ // Which revisions did we change anything about?
+ if( $revObjs[$timestamp]->mDeleted != $bitfield ) {
+ $Id_set[]=$timestamp;
+ $count++;
+
+ $this->updateArchive( $revObjs[$timestamp], $bitfield );
+ }
+ }
+ // For logging, maintain a count of revisions
+ if( $count > 0 ) {
+ $this->updateLog( $title, $count, $bitfield, $revObjs[$timestamp]->mDeleted,
+ $comment, $title, 'artimestamp', $Id_set );
+ }
+ // Where all revs allowed to be set?
+ if( !$userAllowedAll ) {
+ $wgOut->permissionRequired( 'hiderevision' );
+ return false;
+ }
+
+ return $success;
+ }
+
+ /**
+ * @param $title, the page these events apply to
+ * @param array $items list of revision ID numbers
+ * @param int $bitfield new rev_deleted value
+ * @param string $comment Comment for log records
+ */
+ function setOldImgVisibility( $title, $items, $bitfield, $comment ) {
+ global $wgOut;
+
+ $userAllowedAll = $success = true;
+ $count = 0;
+ $set = array();
+ // Run through and pull all our data in one query
+ foreach( $items as $timestamp ) {
+ $where[] = $this->dbw->addQuotes( $timestamp.'!'.$title->getDbKey() );
+ }
+ $whereClause = 'oi_archive_name IN(' . implode(',',$where) . ')';
+ $result = $this->dbw->select( 'oldimage', '*',
+ array( 'oi_name' => $title->getDbKey(),
+ $whereClause ),
+ __METHOD__ );
+ while( $row = $this->dbw->fetchObject( $result ) ) {
+ $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
+ $filesObjs[$row->oi_archive_name]->user = $row->oi_user;
+ $filesObjs[$row->oi_archive_name]->userText = $row->oi_user_text;
+ }
+ // To work!
+ foreach( $items as $timestamp ) {
+ $archivename = $timestamp.'!'.$title->getDbKey();
+ if( !isset($filesObjs[$archivename]) ) {
+ $success = false;
+ continue; // Must exist
+ } else if( !$filesObjs[$archivename]->userCan(File::DELETED_RESTRICTED) ) {
+ $userAllowedAll=false;
+ continue;
}
- $this->updateRevision( $rev, $bitfield );
- $this->updateRecentChanges( $rev, $bitfield );
- // For logging, maintain a count of revisions per page
- $pageid = $rev->getPage();
- if( isset( $pages[$pageid] ) ) {
- $pages[$pageid]++;
+ $transaction = true;
+ // Which revisions did we change anything about?
+ if( $filesObjs[$archivename]->deleted != $bitfield ) {
+ $count++;
+
+ $this->dbw->begin();
+ $this->updateOldFiles( $filesObjs[$archivename], $bitfield );
+ // If this image is currently hidden...
+ if( $filesObjs[$archivename]->deleted & File::DELETED_FILE ) {
+ if( $bitfield & File::DELETED_FILE ) {
+ # Leave it alone if we are not changing this...
+ $set[]=$name;
+ $transaction = true;
+ } else {
+ # We are moving this out
+ $transaction = $this->makeOldImagePublic( $filesObjs[$archivename] );
+ $set[]=$transaction;
+ }
+ // Is it just now becoming hidden?
+ } else if( $bitfield & File::DELETED_FILE ) {
+ $transaction = $this->makeOldImagePrivate( $filesObjs[$archivename] );
+ $set[]=$transaction;
+ } else {
+ $set[]=$name;
+ }
+ // If our file operations fail, then revert back the db
+ if( $transaction==false ) {
+ $this->dbw->rollback();
+ return false;
+ }
+ $this->dbw->commit();
+ // Purge page/history
+ $filesObjs[$archivename]->purgeCache();
+ $filesObjs[$archivename]->purgeHistory();
+ // Invalidate cache for all pages using this file
+ $update = new HTMLCacheUpdate( $oimage->getTitle(), 'imagelinks' );
+ $update->doUpdate();
+ }
+ }
+
+ // Log if something was changed
+ if( $count > 0 ) {
+ $this->updateLog( $title, $count, $bitfield, $filesObjs[$archivename]->deleted,
+ $comment, $title, 'oldimage', $set );
+ }
+ // Where all revs allowed to be set?
+ if( !$userAllowedAll ) {
+ $wgOut->permissionRequired( 'hiderevision' );
+ return false;
+ }
+
+ return $success;
+ }
+
+ /**
+ * @param $title, the page these events apply to
+ * @param array $items list of revision ID numbers
+ * @param int $bitfield new rev_deleted value
+ * @param string $comment Comment for log records
+ */
+ function setArchFileVisibility( $title, $items, $bitfield, $comment ) {
+ global $wgOut;
+
+ $userAllowedAll = $success = true;
+ $count = 0;
+ $Id_set = array();
+
+ // Run through and pull all our data in one query
+ foreach( $items as $id ) {
+ $where[] = intval($id);
+ }
+ $whereClause = 'fa_id IN(' . implode(',',$where) . ')';
+ $result = $this->dbw->select( 'filearchive', '*',
+ array( 'fa_name' => $title->getDbKey(),
+ $whereClause ),
+ __METHOD__ );
+ while( $row = $this->dbw->fetchObject( $result ) ) {
+ $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row );
+ }
+ // To work!
+ foreach( $items as $fileid ) {
+ if( !isset($filesObjs[$fileid]) ) {
+ $success = false;
+ continue; // Must exist
+ } else if( !$filesObjs[$fileid]->userCan(File::DELETED_RESTRICTED) ) {
+ $userAllowedAll=false;
+ continue;
+ }
+ // Which revisions did we change anything about?
+ if( $filesObjs[$fileid]->deleted != $bitfield ) {
+ $Id_set[]=$fileid;
+ $count++;
+
+ $this->updateArchFiles( $filesObjs[$fileid], $bitfield );
+ }
+ }
+ // Log if something was changed
+ if( $count > 0 ) {
+ $this->updateLog( $title, $count, $bitfield, $comment,
+ $filesObjs[$fileid]->deleted, $title, 'fileid', $Id_set );
+ }
+ // Where all revs allowed to be set?
+ if( !$userAllowedAll ) {
+ $wgOut->permissionRequired( 'hiderevision' );
+ return false;
+ }
+
+ return $success;
+ }
+
+ /**
+ * @param $title, the page these events apply to
+ * @param array $items list of log ID numbers
+ * @param int $bitfield new log_deleted value
+ * @param string $comment Comment for log records
+ */
+ function setEventVisibility( $items, $bitfield, $comment ) {
+ global $wgOut;
+
+ $userAllowedAll = $success = true;
+ $logs_count = array();
+ $logs_Ids = array();
+
+ // Run through and pull all our data in one query
+ foreach( $items as $logid ) {
+ $where[] = intval($logid);
+ }
+ $whereClause = 'log_id IN(' . implode(',',$where) . ')';
+ $result = $this->dbw->select( 'logging', '*',
+ array( $whereClause ),
+ __METHOD__ );
+ while( $row = $this->dbw->fetchObject( $result ) ) {
+ $logRows[$row->log_id] = $row;
+ }
+ // To work!
+ foreach( $items as $logid ) {
+ if( !isset($logRows[$logid]) ) {
+ $success = false;
+ continue; // Must exist
+ } else if( !LogViewer::userCan($logRows[$logid], Revision::DELETED_RESTRICTED)
+ || $logRows[$logid]->log_type=='oversight' ) {
+ // Don't hide from oversight log!!!
+ $userAllowedAll=false;
+ continue;
+ }
+ $logtype = $logRows[$logid]->log_type;
+ // For logging, maintain a count of events per log type
+ if( !isset( $logs_count[$logtype] ) ) {
+ $logs_count[$logtype]=0;
+ $logs_Ids[$logtype]=array();
+ }
+ // Which logs did we change anything about?
+ if( $logRows[$logid]->log_deleted != $bitfield ) {
+ $logs_Ids[$logtype][]=$logid;
+ $logs_count[$logtype]++;
+
+ $this->updateLogs( $logRows[$logid], $bitfield );
+ $this->updateRecentChangesLog( $logRows[$logid], $bitfield, true );
+ }
+ }
+ foreach( $logs_count as $logtype => $count ) {
+ //Don't log or touch if nothing changed
+ if( $count > 0 ) {
+ $target = SpecialPage::getTitleFor( 'Log', $logtype );
+ $this->updateLog( $target, $count, $bitfield, $logRows[$logid]->log_deleted,
+ $comment, $target, 'logid', $logs_Ids[$logtype] );
+ }
+ }
+ // Where all revs allowed to be set?
+ if( !$userAllowedAll ) {
+ $wgOut->permissionRequired( 'hiderevision' );
+ return false;
+ }
+
+ return $success;
+ }
+
+ /**
+ * Moves an image to a safe private location
+ * Caller is responsible for clearing caches
+ * @param File $oimage
+ * @returns string, timestamp on success, false on failure
+ */
+ function makeOldImagePrivate( $oimage ) {
+ global $wgFileStore, $wgUseSquid;
+
+ $transaction = new FSTransaction();
+ if( !FileStore::lock() ) {
+ wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
+ return false;
+ }
+ $oldpath = $oimage->getArchivePath() . DIRECTORY_SEPARATOR . $oimage->archive_name;
+ // Dupe the file into the file store
+ if( file_exists( $oldpath ) ) {
+ // Is our directory configured?
+ if( $store = FileStore::get( 'hidden' ) ) {
+ if( !$oimage->sha1 )
+ $oimage->upgradeRow();
+
+ $key = $oimage->sha1.'.'.$oimage->getExtension();
+ $transaction->add( $store->insert( $key, $oldpath, FileStore::DELETE_ORIGINAL ) );
} else {
- $pages[$pageid] = 1;
+ $group = null;
+ $key = null;
+ $transaction = false; // Return an error and do nothing
}
+ } else {
+ wfDebug( __METHOD__." deleting already-missing '$oldpath'; moving on to database\n" );
+ $group = null;
+ $key = '';
+ $transaction = new FSTransaction(); // empty
+ }
+
+ if( $transaction === false ) {
+ // Fail to restore?
+ wfDebug( __METHOD__.": import to file store failed, aborting\n" );
+ throw new MWException( "Could not archive and delete file $oldpath" );
+ return false;
}
- // Clear caches...
- foreach( $pages as $pageid => $count ) {
- $title = Title::newFromId( $pageid );
- $this->updatePage( $title );
- $this->updateLog( $title, $count, $bitfield, $comment );
+ wfDebug( __METHOD__.": set db items, applying file transactions\n" );
+ $transaction->commit();
+ FileStore::unlock();
+
+ $m = explode('!',$oimage->archive_name,2);
+ $timestamp = $m[0];
+
+ return $timestamp;
+ }
+
+ /**
+ * Moves an image from a safe private location
+ * Caller is responsible for clearing caches
+ * @param File $oimage
+ * @returns string, timestamp on success, false on failure
+ */
+ function makeOldImagePublic( $oimage ) {
+ global $wgFileStore;
+
+ $transaction = new FSTransaction();
+ if( !FileStore::lock() ) {
+ wfDebug( __METHOD__." could not acquire filestore lock\n" );
+ return false;
+ }
+
+ $store = FileStore::get( 'hidden' );
+ if( !$store ) {
+ wfDebug( __METHOD__.": skipping row with no file.\n" );
+ return false;
+ }
+
+ $key = $oimage->sha1.'.'.$oimage->getExtension();
+ $destDir = $oimage->getArchivePath();
+ if( !is_dir( $destDir ) ) {
+ wfMkdirParents( $destDir );
+ }
+ $destPath = $destDir . DIRECTORY_SEPARATOR . $oimage->archive_name;
+ // Check if any other stored revisions use this file;
+ // if so, we shouldn't remove the file from the hidden
+ // archives so they will still work.
+ $useCount = $this->dbw->selectField( 'oldimage','COUNT(*)',
+ array( 'oi_sha1' => $oimage->sha1,
+ 'oi_deleted & '.File::DELETED_FILE => File::DELETED_FILE ),
+ __METHOD__ );
+
+ if( $useCount == 0 ) {
+ wfDebug( __METHOD__.": nothing else using {$oimage->sha1}, will deleting after\n" );
+ $flags = FileStore::DELETE_ORIGINAL;
+ } else {
+ $flags = 0;
+ }
+ $transaction->add( $store->export( $key, $destPath, $flags ) );
+
+ wfDebug( __METHOD__.": set db items, applying file transactions\n" );
+ $transaction->commit();
+ FileStore::unlock();
+
+ $m = explode('!',$oimage->archive_name,2);
+ $timestamp = $m[0];
+
+ return $timestamp;
+ }
+
+ /**
+ * Moves an image from a safe private location to deleted archives
+ * Groups should be 'deleted' and 'hidden'
+ * @param File $oimage
+ * @param string $group1, old group
+ * @param string $group2, new group
+ * @returns bool, success
+ */
+ function moveImageFromFileRepos( $oimage, $group1, $group2 ) {
+ global $wgFileStore;
+
+ $transaction = new FSTransaction();
+ if( !FileStore::lock() ) {
+ wfDebug( __METHOD__." could not acquire filestore lock\n" );
+ return false;
}
+ $storeOld = FileStore::get( $group1 );
+ if( !$storeOld ) {
+ wfDebug( __METHOD__.": skipping row with no file.\n" );
+ return false;
+ }
+ $key = $oimage->sha1.'.'.$oimage->getExtension();
+
+ $oldPath = $storeOld->filePath( $key );
+ // Check if any other stored revisions use this file;
+ // if so, we shouldn't remove the file from the hidden
+ // archives so they will still work.
+ if( $group1=='hidden' ) {
+ $useCount = $this->dbw->selectField( 'oldimage','COUNT(*)',
+ array( 'oi_sha1' => $oimage->sha1 ),
+ __METHOD__ );
+ } else if( $group1=='deleted' ) {
+ $useCount = $this->dbw->selectField( 'filearchive','COUNT(*)',
+ array( 'fa_storage_key' => $key, 'fa_storage_group' => 'deleted' ),
+ __METHOD__ );
+ }
+
+ if( $useCount == 0 ) {
+ wfDebug( __METHOD__.": nothing else using $key, will deleting after\n" );
+ $flags = FileStore::DELETE_ORIGINAL;
+ } else {
+ $flags = 0;
+ }
+
+ $storeNew = FileStore::get( $group2 );
+ $transaction->add( $storeNew->insert( $key, $oldPath, $flags ) );
+
+ wfDebug( __METHOD__.": set db items, applying file transactions\n" );
+ $transaction->commit();
+ FileStore::unlock();
+
return true;
}
* @param int $bitfield new rev_deleted bitfield value
*/
function updateRevision( $rev, $bitfield ) {
- $this->db->update( 'revision',
+ $this->dbw->update( 'revision',
array( 'rev_deleted' => $bitfield ),
array( 'rev_id' => $rev->getId() ),
'RevisionDeleter::updateRevision' );
}
+ /**
+ * Update the revision's rev_deleted field
+ * @param Revision $rev
+ * @param int $bitfield new rev_deleted bitfield value
+ */
+ function updateArchive( $rev, $bitfield ) {
+ $this->dbw->update( 'archive',
+ array( 'ar_deleted' => $bitfield ),
+ array( 'ar_rev_id' => $rev->getId() ),
+ 'RevisionDeleter::updateArchive' );
+ }
+
+ /**
+ * Update the images's oi_deleted field
+ * @param File $oimage
+ * @param int $bitfield new rev_deleted bitfield value
+ */
+ function updateOldFiles( $oimage, $bitfield ) {
+ $this->dbw->update( 'oldimage',
+ array( 'oi_deleted' => $bitfield ),
+ array( 'oi_archive_name' => $oimage->archive_name ),
+ 'RevisionDeleter::updateOldFiles' );
+ }
+
+ /**
+ * Update the images's fa_deleted field
+ * @param ArchivedFile $file
+ * @param int $bitfield new rev_deleted bitfield value
+ */
+ function updateArchFiles( $file, $bitfield ) {
+ $this->dbw->update( 'filearchive',
+ array( 'fa_deleted' => $bitfield ),
+ array( 'fa_id' => $file->id ),
+ 'RevisionDeleter::updateArchFiles' );
+ }
+
+ /**
+ * Update the logging log_deleted field
+ * @param Row $event
+ * @param int $bitfield new rev_deleted bitfield value
+ */
+ function updateLogs( $event, $bitfield ) {
+ $this->dbw->update( 'logging',
+ array( 'log_deleted' => $bitfield ),
+ array( 'log_id' => $event->log_id ),
+ 'RevisionDeleter::updateLogs' );
+ }
+
/**
* Update the revision's recentchanges record if fields have been hidden
* @param Revision $rev
* @param int $bitfield new rev_deleted bitfield value
*/
- function updateRecentChanges( $rev, $bitfield ) {
- $this->db->update( 'recentchanges',
- array(
- 'rc_user' => ($bitfield & Revision::DELETED_USER) ? 0 : $rev->getUser(),
- 'rc_user_text' => ($bitfield & Revision::DELETED_USER) ? wfMsg( 'rev-deleted-user' ) : $rev->getUserText(),
- 'rc_comment' => ($bitfield & Revision::DELETED_COMMENT) ? wfMsg( 'rev-deleted-comment' ) : $rev->getComment() ),
- array(
- 'rc_this_oldid' => $rev->getId() ),
- 'RevisionDeleter::updateRecentChanges' );
+ function updateRecentChangesEdits( $rev, $bitfield ) {
+ $this->dbw->update( 'recentchanges',
+ array( 'rc_deleted' => $bitfield,
+ 'rc_patrolled' => 1 ),
+ array( 'rc_this_oldid' => $rev->getId() ),
+ 'RevisionDeleter::updateRecentChangesEdits' );
+ }
+
+ /**
+ * Update the revision's recentchanges record if fields have been hidden
+ * @param Row $event
+ * @param int $bitfield new rev_deleted bitfield value
+ */
+ function updateRecentChangesLog( $event, $bitfield ) {
+ $this->dbw->update( 'recentchanges',
+ array( 'rc_deleted' => $bitfield,
+ 'rc_patrolled' => 1 ),
+ array( 'rc_logid' => $event->log_id ),
+ 'RevisionDeleter::updateRecentChangesLog' );
}
/**
*/
function updatePage( $title ) {
$title->invalidateCache();
+ $title->purgeSquid();
+
+ // Extensions that require referencing previous revisions may need this
+ wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$title ) );
}
/**
* Record a log entry on the action
- * @param Title $title
+ * @param Title $title, page where item was removed from
* @param int $count the number of revisions altered for this page
- * @param int $bitfield the new rev_deleted value
+ * @param int $nbitfield the new _deleted value
+ * @param int $obitfield the old _deleted value
* @param string $comment
+ * @param Title $target, the relevant page
+ * @param string $param, URL param
+ * @param Array $items
*/
- function updateLog( $title, $count, $bitfield, $comment ) {
- $log = new LogPage( 'delete' );
- $reason = "changed $count revisions to $bitfield";
- $reason .= ": $comment";
- $log->addEntry( 'revision', $title, $reason );
+ function updateLog( $title, $count, $nbitfield, $obitfield, $comment, $target, $param, $items = array() ) {
+ // Put things hidden from sysops in the oversight log
+ $logtype = ( ($nbitfield | $obitfield) & Revision::DELETED_RESTRICTED ) ? 'oversight' : 'delete';
+ $log = new LogPage( $logtype );
+ // FIXME: do this better
+ if( $param=='logid' ) {
+ $params = array( implode( ',', $items) );
+ $reason = wfMsgExt('logdelete-logaction', array('parsemag'), $count, $nbitfield );
+ if($comment) $reason .= ": $comment";
+ $log->addEntry( 'event', $title, $reason, $params );
+ } else {
+ // Add params for effected page and ids
+ $params = array( $target->getPrefixedText(), $param, implode( ',', $items) );
+ $reason = wfMsgExt('revdelete-logaction', array('parsemag'), $count, $nbitfield );
+ if($comment) $reason .= ": $comment";
+ $log->addEntry( 'revision', $title, $reason, $params );
+ }
}
}
-
-
$sk = $wgUser->getSkin();
/** Pages available to all */
- wfSpecialSpecialpages_gen( SpecialPage::getRegularPages(), 'spheading', $sk );
+ wfSpecialSpecialpages_gen( SpecialPage::getRegularPages(), 'spheading', $sk, false );
/** Restricted special pages */
- wfSpecialSpecialpages_gen( SpecialPage::getRestrictedPages(), 'restrictedpheading', $sk );
+ wfSpecialSpecialpages_gen( SpecialPage::getRestrictedPages(), 'restrictedpheading', $sk, false );
+
+ /** Restricted logs */
+ wfSpecialSpecialpages_gen( SpecialPage::getRestrictedLogs(), 'restrictedlheading', $sk, true );
}
/**
* @param $pages the list of pages
* @param $heading header to be used
* @param $sk skin object ???
+ * @param $islog, is this for a list of log types?
*/
-function wfSpecialSpecialpages_gen($pages,$heading,$sk) {
- global $wgOut, $wgSortSpecialPages;
+function wfSpecialSpecialpages_gen( $pages, $heading, $sk, $islog=false ) {
+ global $wgOut, $wgUser, $wgSortSpecialPages;
if( count( $pages ) == 0 ) {
# Yeah, that was pointless. Thanks for coming.
/** Put them into a sortable array */
$sortedPages = array();
- foreach ( $pages as $page ) {
- if ( $page->isListed() ) {
- $sortedPages[$page->getDescription()] = $page->getTitle();
+ if( $islog ) {
+ $sortedPages = $pages;
+ } else {
+ foreach ( $pages as $page ) {
+ if ( $page->isListed() ) {
+ $sortedPages[$page->getDescription()] = $page->getTitle();
+ }
}
}
array(
'ar_namespace',
'ar_title',
- 'COUNT(*) AS count',
+ 'COUNT(*) AS count'
),
$condition,
__METHOD__,
);
}
- /**
- * List the revisions of the given page. Returns result wrapper with
- * (ar_minor_edit, ar_timestamp, ar_user, ar_user_text, ar_comment) fields.
- *
- * @return ResultWrapper
- */
- function listRevisions() {
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'archive',
- array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment', 'ar_len' ),
- array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey() ),
- 'PageArchive::listRevisions',
- array( 'ORDER BY' => 'ar_timestamp DESC' ) );
- $ret = $dbr->resultObject( $res );
- return $ret;
- }
-
/**
* List the deleted file revisions for this page, if it's a file page.
* Returns a result wrapper with various filearchive fields, or null
array(
'fa_id',
'fa_name',
+ 'fa_archive_name',
'fa_storage_key',
+ 'fa_storage_group',
'fa_size',
'fa_width',
'fa_height',
+ 'fa_bits',
+ 'fa_metadata',
+ 'fa_media_type',
+ 'fa_major_mime',
+ 'fa_minor_mime',
'fa_description',
'fa_user',
'fa_user_text',
- 'fa_timestamp' ),
+ 'fa_timestamp',
+ 'fa_deleted' ),
array( 'fa_name' => $this->title->getDbKey() ),
__METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' ) );
$rev = $this->getRevision( $timestamp );
return $rev ? $rev->getText() : null;
}
+
+ function getRevisionConds( $timestamp, $id ) {
+ if( $id ) {
+ $id = intval($id);
+ return "ar_rev_id=$id";
+ } else if( $timestamp ) {
+ return "ar_timestamp=$timestamp";
+ } else {
+ return 'ar_rev_id=0';
+ }
+ }
/**
* Return a Revision object containing data for the deleted revision.
- * Note that the result *may* or *may not* have a null page ID.
- * @param string $timestamp
+ * Note that the result *may* have a null page ID.
+ * @param string $timestamp or $id
* @return Revision
*/
- function getRevision( $timestamp ) {
+ function getRevision( $timestamp, $id=null ) {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'archive',
array(
'ar_minor_edit',
'ar_flags',
'ar_text_id',
+ 'ar_deleted',
'ar_len' ),
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDbkey(),
- 'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
+ $this->getRevisionConds( $dbr->timestamp($timestamp), $id ) ),
__METHOD__ );
if( $row ) {
return new Revision( array(
'user_text' => $row->ar_user_text,
'timestamp' => $row->ar_timestamp,
'minor_edit' => $row->ar_minor_edit,
- 'text_id' => $row->ar_text_id ) );
+ 'text_id' => $row->ar_text_id,
+ 'deleted' => $row->ar_deleted,
+ 'len' => $row->ar_len) );
} else {
return null;
}
* Restore the given (or all) text and file revisions for the page.
* Once restored, the items will be removed from the archive tables.
* The deletion log will be updated with an undeletion notice.
+ * Use -1 for the one of the timestamps to only restore files or text
*
- * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
+ * @param string $pagetimestamp, restore all revisions since this time
* @param string $comment
- * @param array $fileVersions
+ * @param string $filetimestamp, restore all revision from this time on
+ * @param bool $Unsuppress
*
* @return true on success.
*/
- function undelete( $timestamps, $comment = '', $fileVersions = array() ) {
+ function undelete( $pagetimestamp = 0, $comment = '', $filetimestamp = 0, $Unsuppress = false) {
// If both the set of text revisions and file revisions are empty,
// restore everything. Otherwise, just restore the requested items.
- $restoreAll = empty( $timestamps ) && empty( $fileVersions );
+ $restoreAll = ($pagetimestamp==0 && $filetimestamp==0);
- $restoreText = $restoreAll || !empty( $timestamps );
- $restoreFiles = $restoreAll || !empty( $fileVersions );
+ $restoreText = ($restoreAll || $pagetimestamp );
+ $restoreFiles = ($restoreAll || $filetimestamp );
+
+ if( $restoreText && $pagetimestamp >= 0 ) {
+ $textRestored = $this->undeleteRevisions( $pagetimestamp, $Unsuppress );
+ } else {
+ $textRestored = 0;
+ }
- if( $restoreFiles && $this->title->getNamespace() == NS_IMAGE ) {
+ if( $restoreFiles && $filetimestamp >= 0 && $this->title->getNamespace()==NS_IMAGE ) {
$img = wfLocalFile( $this->title );
- $this->fileStatus = $img->restore( $fileVersions );
+ $this->fileStatus = $img->restore( $filetimestamp, $Unsuppress );
$filesRestored = $this->fileStatus->successCount;
} else {
$filesRestored = 0;
}
-
- if( $restoreText ) {
- $textRestored = $this->undeleteRevisions( $timestamps );
- } else {
- $textRestored = 0;
- }
// Touch the log!
global $wgContLang;
$log = new LogPage( 'delete' );
if( $textRestored && $filesRestored ) {
- $reason = wfMsgForContent( 'undeletedrevisions-files',
+ $reason = wfMsgExt( 'undeletedrevisions-files', array('parsemag'),
$wgContLang->formatNum( $textRestored ),
$wgContLang->formatNum( $filesRestored ) );
} elseif( $textRestored ) {
- $reason = wfMsgForContent( 'undeletedrevisions',
+ $reason = wfMsgExt( 'undeletedrevisions', array('parsemag'),
$wgContLang->formatNum( $textRestored ) );
} elseif( $filesRestored ) {
- $reason = wfMsgForContent( 'undeletedfiles',
+ $reason = wfMsgExt( 'undeletedfiles', array('parsemag'),
$wgContLang->formatNum( $filesRestored ) );
} else {
wfDebug( "Undelete: nothing undeleted...\n" );
if( trim( $comment ) != '' )
$reason .= ": {$comment}";
- $log->addEntry( 'restore', $this->title, $reason );
+ $log->addEntry( 'restore', $this->title, $reason, array($pagetimestamp,$filetimestamp) );
if ( $this->fileStatus && !$this->fileStatus->ok ) {
return false;
* to the cur/old tables. If the page currently exists, all revisions will
* be stuffed into old, otherwise the most recent will go into cur.
*
- * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
+ * @param string $timestamps, restore all revisions since this time
* @param string $comment
* @param array $fileVersions
+ * @param bool $Unsuppress, remove all ar_deleted/fa_deleted restrictions of seletected revs
*
* @return int number of revisions restored
*/
- private function undeleteRevisions( $timestamps ) {
- $restoreAll = empty( $timestamps );
+ private function undeleteRevisions( $timestamp, $Unsuppress = false ) {
+ $restoreAll = ($timestamp==0);
$dbw = wfGetDB( DB_MASTER );
+ $makepage = false; // Do we need to make a new page?
# Does this page already exist? We'll have to update it...
$article = new Article( $this->title );
$options = 'FOR UPDATE';
$page = $dbw->selectRow( 'page',
array( 'page_id', 'page_latest' ),
- array( 'page_namespace' => $this->title->getNamespace(),
- 'page_title' => $this->title->getDBkey() ),
+ array( 'page_namespace' => $this->title->getNamespace(),
+ 'page_title' => $this->title->getDBkey() ),
__METHOD__,
$options );
+
if( $page ) {
# Page already exists. Import the history, and if necessary
# we'll update the latest revision field in the record.
$newid = 0;
$pageId = $page->page_id;
$previousRevId = $page->page_latest;
+ # Get the time span of this page
+ $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
+ array( 'rev_id' => $previousRevId ),
+ __METHOD__ );
+
+ if( $previousTimestamp === false ) {
+ wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" );
+ return false;
+ }
+ # Do not fuck up histories by merging them in annoying, unrevertable ways
+ # This page id should match any deleted ones (excepting NULL values)
+ # We can allow restoration into redirect pages with no edit history
+ $otherpages = $dbw->selectField( 'archive', 'COUNT(*)',
+ array( 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey(),
+ 'ar_page_id IS NOT NULL', "ar_page_id != $pageId" ),
+ __METHOD__,
+ array('LIMIT' => 1) );
+ if( $otherpages && !$this->title->isValidRestoreOverTarget() ) {
+ return false;
+ }
+
} else {
# Have to create a new article...
- $newid = $article->insertOn( $dbw );
- $pageId = $newid;
+ $makepage = true;
$previousRevId = 0;
+ $previousTimestamp = 0;
}
- if( $restoreAll ) {
- $oldones = '1 = 1'; # All revisions...
- } else {
- $oldts = implode( ',',
- array_map( array( &$dbw, 'addQuotes' ),
- array_map( array( &$dbw, 'timestamp' ),
- $timestamps ) ) );
-
- $oldones = "ar_timestamp IN ( {$oldts} )";
+ $conditions = array(
+ 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey() );
+ if( $timestamp ) {
+ $conditions[] = "ar_timestamp >= {$timestamp}";
}
/**
- * Restore each revision...
+ * Select each archived revision...
*/
$result = $dbw->select( 'archive',
/* fields */ array(
'ar_minor_edit',
'ar_flags',
'ar_text_id',
+ 'ar_deleted',
'ar_len' ),
- /* WHERE */ array(
- 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- $oldones ),
+ /* WHERE */
+ $conditions,
__METHOD__,
/* options */ array(
'ORDER BY' => 'ar_timestamp' )
);
- if( $dbw->numRows( $result ) < count( $timestamps ) ) {
- wfDebug( __METHOD__.": couldn't find all requested rows\n" );
- return false;
+ $ret = $dbw->resultObject( $result );
+
+ $rev_count = $dbw->numRows( $result );
+ if( $rev_count ) {
+ # We need to seek around as just using DESC in the ORDER BY
+ # would leave the revisions inserted in the wrong order
+ $first = $ret->fetchObject();
+ $ret->seek( $rev_count - 1 );
+ $last = $ret->fetchObject();
+ // We don't handle well changing the top revision's settings
+ if( !$Unsuppress && $last->ar_deleted && $last->ar_timestamp > $previousTimestamp ) {
+ wfDebug( __METHOD__.": restoration would result in a deleted top revision\n" );
+ return false;
+ }
+ $ret->seek( 0 );
}
+ if( $makepage ) {
+ $newid = $article->insertOn( $dbw );
+ $pageId = $newid;
+ }
+
$revision = null;
$restored = 0;
-
- while( $row = $dbw->fetchObject( $result ) ) {
+
+ while( $row = $ret->fetchObject() ) {
if( $row->ar_text_id ) {
// Revision was deleted in 1.5+; text is in
// the regular text table, use the reference.
'timestamp' => $row->ar_timestamp,
'minor_edit' => $row->ar_minor_edit,
'text_id' => $row->ar_text_id,
- 'len' => $row->ar_len
+ 'deleted' => $Unsuppress ? 0 : $row->ar_deleted,
+ 'len' => $row->ar_len
) );
$revision->insertOn( $dbw );
$restored++;
}
-
+
+ # If there were any revisions restored...
if( $revision ) {
// Attach the latest revision to the page...
$wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId );
// Update site stats, link tables, etc
$article->createUpdates( $revision );
}
-
+
if( $newid ) {
wfRunHooks( 'ArticleUndelete', array( &$this->title, true ) );
Article::onArticleCreate( $this->title );
wfRunHooks( 'ArticleUndelete', array( &$this->title, false ) );
Article::onArticleEdit( $this->title );
}
- } else {
- # Something went terribly wrong!
}
# Now that it's safely stored, take it out of the archive
$dbw->delete( 'archive',
- /* WHERE */ array(
- 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- $oldones ),
+ /* WHERE */
+ $conditions,
+ __METHOD__ );
+ # Update any revision left to reflect the page they belong to.
+ # If a page was deleted, and a new one created over it, then deleted,
+ # selective restore acts as a way to seperate the two. Nevertheless, we
+ # still want the rest to be restorable, in case some mistake was made.
+ $dbw->update( 'archive',
+ array( 'ar_page_id' => $newid ),
+ array( 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey() ),
__METHOD__ );
return $restored;
$time = $request->getVal( 'timestamp' );
$this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
$this->mFile = $request->getVal( 'file' );
+ $this->mDiff = $request->getVal( 'diff' );
+ $this->mOldid = $request->getVal( 'oldid' );
- $posted = $request->wasPosted() &&
- $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) );
+ $posted = $request->wasPosted() && $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) );
$this->mRestore = $request->getCheck( 'restore' ) && $posted;
$this->mPreview = $request->getCheck( 'preview' ) && $posted;
$this->mComment = $request->getText( 'wpComment' );
+ $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'oversight' );
if( $par != "" ) {
$this->mTarget = $par;
+ $_GET['target'] = $par; // hack for Pager
}
- if ( $wgUser->isAllowed( 'delete' ) && !$wgUser->isBlocked() ) {
+ if( $wgUser->isAllowed( 'delete' ) && !$wgUser->isBlocked() ) {
$this->mAllowed = true;
} else {
$this->mAllowed = false;
$this->mTimestamp = '';
$this->mRestore = false;
}
- if ( $this->mTarget !== "" ) {
+ if( $this->mTarget !== "" ) {
$this->mTargetObj = Title::newFromURL( $this->mTarget );
} else {
$this->mTargetObj = NULL;
}
if( $this->mRestore ) {
- $timestamps = array();
- $this->mFileVersions = array();
- foreach( $_REQUEST as $key => $val ) {
- $matches = array();
- if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
- array_push( $timestamps, $matches[1] );
- }
-
- if( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) {
- $this->mFileVersions[] = intval( $matches[1] );
- }
+ $this->mFileTimestamp = $request->getVal('imgrestorepoint');
+ $this->mPageTimestamp = $request->getVal('restorepoint');
+ }
+ $this->preCacheMessages();
+ }
+
+ /**
+ * As we use the same small set of messages in various methods and that
+ * they are called often, we call them once and save them in $this->message
+ */
+ function preCacheMessages() {
+ // Precache various messages
+ if( !isset( $this->message ) ) {
+ foreach( explode(' ', 'last rev-delundel' ) as $msg ) {
+ $this->message[$msg] = wfMsgExt( $msg, array( 'escape') );
}
- rsort( $timestamps );
- $this->mTargetTimestamp = $timestamps;
}
}
function execute() {
- global $wgOut;
- if ( $this->mAllowed ) {
- $wgOut->setPagetitle( wfMsg( "undeletepage" ) );
+ global $wgOut, $wgUser;
+ if( $this->mAllowed ) {
+ $wgOut->setPagetitle( wfMsgHtml( "undeletepage" ) );
} else {
- $wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) );
+ $wgOut->setPagetitle( wfMsgHtml( "viewdeletedpage" ) );
}
if( is_null( $this->mTargetObj ) ) {
- $this->showSearchForm();
+ # Not all users can just browse every deleted page from the list
+ if( $wgUser->isAllowed( 'browsearchive' ) ) {
+ $this->showSearchForm();
- # List undeletable articles
- if( $this->mSearchPrefix ) {
- $result = PageArchive::listPagesByPrefix(
- $this->mSearchPrefix );
- $this->showList( $result );
+ # List undeletable articles
+ if( $this->mSearchPrefix ) {
+ $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
+ $this->showList( $result );
+ }
+ } else {
+ $wgOut->addWikiText( wfMsgHtml( 'undelete-header' ) );
}
return;
}
if( $this->mTimestamp !== '' ) {
return $this->showRevision( $this->mTimestamp );
}
+
+ if( $this->mDiff && $this->mOldid )
+ return $this->showDiff( $this->mDiff, $this->mOldid );
+
if( $this->mFile !== null ) {
- return $this->showFile( $this->mFile );
+ $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile );
+ // Check if user is allowed to see this file
+ if( !$file->userCan( File::DELETED_FILE ) ) {
+ $wgOut->permissionRequired( 'hiderevision' );
+ return false;
+ } else {
+ return $this->showFile( $this->mFile );
+ }
}
+
if( $this->mRestore && $this->mAction == "submit" ) {
return $this->undelete();
}
'</form>' );
}
- /* private */ function showList( $result ) {
+ // Generic list of deleted pages
+ private function showList( $result ) {
global $wgLang, $wgContLang, $wgUser, $wgOut;
if( $result->numRows() == 0 ) {
return true;
}
- /* private */ function showRevision( $timestamp ) {
+ private function showRevision( $timestamp ) {
global $wgLang, $wgUser, $wgOut;
$self = SpecialPage::getTitleFor( 'Undelete' );
$skin = $wgUser->getSkin();
$rev = $archive->getRevision( $timestamp );
if( !$rev ) {
- $wgOut->addWikiTexT( wfMsg( 'undeleterevision-missing' ) );
+ $wgOut->addWikiText( wfMsg( 'undeleterevision-missing' ) );
return;
}
+ if( $rev->isDeleted(Revision::DELETED_TEXT) ) {
+ if( !$rev->userCan(Revision::DELETED_TEXT) ) {
+ $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) );
+ return;
+ } else {
+ $wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) );
+ $wgOut->addHTML( '<br/>' );
+ // and we are allowed to see...
+ }
+ }
+
$wgOut->setPageTitle( wfMsg( 'undeletepage' ) );
- $link = $skin->makeKnownLinkObj(
- $self,
+ $link = $skin->makeKnownLinkObj( $self,
htmlspecialchars( $this->mTargetObj->getPrefixedText() ),
'target=' . $this->mTargetObj->getPrefixedUrl()
);
if( $this->mPreview ) {
$wgOut->addHtml( "<hr />\n" );
- $wgOut->addWikiTextTitleTidy( $rev->getText(), $this->mTargetObj, false );
+ $wgOut->addWikiTextTitleTidy( $rev->revText(), $this->mTargetObj, false );
}
$wgOut->addHtml(
'readonly' => 'readonly',
'cols' => intval( $wgUser->getOption( 'cols' ) ),
'rows' => intval( $wgUser->getOption( 'rows' ) ) ),
- $rev->getText() . "\n" ) .
+ $rev->revText() . "\n" ) .
wfOpenElement( 'div' ) .
wfOpenElement( 'form', array(
'method' => 'post',
wfCloseElement( 'form' ) .
wfCloseElement( 'div' ) );
}
+
+ /**
+ * Show the changes between two deleted revisions
+ */
+ private function showDiff( $newid, $oldid ) {
+ global $wgOut, $wgUser, $wgLang;
+
+ if( is_null($this->mTargetObj) )
+ return;
+ $skin = $wgUser->getSkin();
+
+ $archive = new PageArchive( $this->mTargetObj );
+ $oldRev = $archive->getRevision( null, $oldid );
+ $newRev = $archive->getRevision( null, $newid );
+
+ if( !$oldRev || !$newRev )
+ return;
+
+ $oldTitle = $this->mTargetObj->getPrefixedText();
+ $wgOut->addHtml( "<center><h3>$oldTitle</h3></center>" );
+
+ $oldminor = $newminor = '';
+
+ if($oldRev->mMinorEdit == 1) {
+ $oldminor = wfElement( 'span', array( 'class' => 'minor' ),
+ wfMsg( 'minoreditletter') ) . ' ';
+ }
+
+ if($newRev->mMinorEdit == 1) {
+ $newminor = wfElement( 'span', array( 'class' => 'minor' ),
+ wfMsg( 'minoreditletter') ) . ' ';
+ }
+
+ $ot = $wgLang->timeanddate( $oldRev->getTimestamp(), true );
+ $nt = $wgLang->timeanddate( $newRev->getTimestamp(), true );
+ $oldHeader = htmlspecialchars( wfMsg( 'revisionasof', $ot ) ) . "<br />" .
+ $skin->revUserTools( $oldRev, true ) . "<br />" .
+ $oldminor . $skin->revComment( $oldRev, false ) . "<br />";
+ $newHeader = htmlspecialchars( wfMsg( 'revisionasof', $nt ) ) . "<br />" .
+ $skin->revUserTools( $newRev, true ) . " <br />" .
+ $newminor . $skin->revComment( $newRev, false ) . "<br />";
+
+ $otext = $oldRev->revText();
+ $ntext = $newRev->revText();
+
+ $wgOut->addStyle( 'common/diff.css' );
+ $wgOut->addHtml(
+ "<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'>" . $oldHeader . "</td>" .
+ "<td colspan='2' width='50%' align='center' class='diff-ntitle'>" . $newHeader . "</td>" .
+ "</tr>" .
+ DifferenceEngine::generateDiffBody( $otext, $ntext ) .
+ "</table>" .
+ "</div>\n" );
+
+ return true;
+ }
/**
* Show a deleted file version requested by the visitor.
*/
- function showFile( $key ) {
+ private function showFile( $key ) {
global $wgOut, $wgRequest;
$wgOut->disable();
$store->stream( $key );
}
- /* private */ function showHistory() {
+ private function showHistory() {
global $wgLang, $wgContLang, $wgUser, $wgOut;
- $sk = $wgUser->getSkin();
- if ( $this->mAllowed ) {
+ $this->sk = $wgUser->getSkin();
+ if( $this->mAllowed ) {
$wgOut->setPagetitle( wfMsg( "undeletepage" ) );
} else {
$wgOut->setPagetitle( wfMsg( 'viewdeletedpage' ) );
}
+
+ $wgOut->addWikiText( wfMsgHtml( 'undeletepagetitle', $this->mTargetObj->getPrefixedText()) );
$archive = new PageArchive( $this->mTargetObj );
- /*
- $text = $archive->getLastRevisionText();
- if( is_null( $text ) ) {
- $wgOut->addWikiText( wfMsg( "nohistory" ) );
- return;
- }
- */
- if ( $this->mAllowed ) {
- $wgOut->addWikiText( wfMsg( "undeletehistory" ) );
+
+ if( $this->mAllowed ) {
+ $wgOut->addWikiText( '<p>' . wfMsgHtml( "undeletehistory" ) . '</p>' );
+ $wgOut->addHtml( '<p>' . wfMsgHtml( "undeleterevdel" ) . '</p>' );
} else {
- $wgOut->addWikiText( wfMsg( "undeletehistorynoadmin" ) );
+ $wgOut->addWikiText( wfMsgHtml( "undeletehistorynoadmin" ) );
}
# List all stored revisions
- $revisions = $archive->listRevisions();
+ $revisions = new UndeleteRevisionsPager( $this, array(), $this->mTargetObj );
$files = $archive->listFiles();
- $haveRevisions = $revisions && $revisions->numRows() > 0;
+ $haveRevisions = $revisions && $revisions->getNumRows() > 0;
$haveFiles = $files && $files->numRows() > 0;
# Batch existence check on user and talk pages
- if( $haveRevisions ) {
- $batch = new LinkBatch();
- while( $row = $revisions->fetchObject() ) {
- $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) );
- $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) );
- }
- $batch->execute();
- $revisions->seek( 0 );
- }
if( $haveFiles ) {
$batch = new LinkBatch();
while( $row = $files->fetchObject() ) {
$files->seek( 0 );
}
- if ( $this->mAllowed ) {
+ if( $this->mAllowed ) {
$titleObj = SpecialPage::getTitleFor( "Undelete" );
$action = $titleObj->getLocalURL( "action=submit" );
# Start the form here
$wgOut->addHtml( $top );
}
- # Show relevant lines from the deletion log:
- $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
- $logViewer = new LogViewer(
- new LogReader(
- new FauxRequest(
- array(
- 'page' => $this->mTargetObj->getPrefixedText(),
- 'type' => 'delete'
- )
- )
- ), LogViewer::NO_ACTION_LINK
- );
- $logViewer->showList( $wgOut );
-
if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
# Format the user-visible controls (comment field, submission button)
# in a nice little table
<td>" .
Xml::submitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) .
Xml::element( 'input', array( 'type' => 'reset', 'value' => wfMsg( 'undeletereset' ), 'id' => 'mw-undelete-reset' ) ) .
+ Xml::openElement( 'p' ) .
+ Xml::check( 'wpUnsuppress', $this->mUnsuppress, array('id' => 'mw-undelete-unsupress') ) . ' ' .
+ Xml::label( wfMsgHtml('revdelete-unsuppress'), 'mw-undelete-unsupress' ) .
+ Xml::closeElement( 'p' ) .
"</td>
</tr>" .
Xml::closeElement( 'table' ) .
$wgOut->addHtml( $table );
}
- $wgOut->addHTML( "<h2>" . htmlspecialchars( wfMsg( "history" ) ) . "</h2>\n" );
+ $wgOut->addHTML( "<h2 id=\"pagehistory\">" . wfMsgHtml( "history" ) . "</h2>\n" );
if( $haveRevisions ) {
- # The page's stored (deleted) history:
- $wgOut->addHTML("<ul>");
- $target = urlencode( $this->mTarget );
- while( $row = $revisions->fetchObject() ) {
- $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
- if ( $this->mAllowed ) {
- $checkBox = Xml::check( "ts$ts" );
- $pageLink = $sk->makeKnownLinkObj( $titleObj,
- $wgLang->timeanddate( $ts, true ),
- "target=$target×tamp=$ts" );
- } else {
- $checkBox = '';
- $pageLink = $wgLang->timeanddate( $ts, true );
- }
- $userLink = $sk->userLink( $row->ar_user, $row->ar_user_text ) . $sk->userToolLinks( $row->ar_user, $row->ar_user_text );
- $stxt = '';
- if (!is_null($size = $row->ar_len)) {
- if ($size == 0) {
- $stxt = wfMsgHtml('historyempty');
- } else {
- $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) );
- }
- }
- $comment = $sk->commentBlock( $row->ar_comment );
- $wgOut->addHTML( "<li>$checkBox $pageLink . . $userLink $stxt $comment</li>\n" );
-
- }
- $revisions->free();
- $wgOut->addHTML("</ul>");
+ $wgOut->addHTML( '<p>' . wfMsgHtml( "restorepoint" ) . '</p>' );
+ $wgOut->addHTML( $revisions->getNavigationBar() );
+ $wgOut->addHTML( "<ul>" );
+ $wgOut->addHTML( "<li>" . wfRadio( "restorepoint", -1, false ) . " " . wfMsgHtml('restorenone') . "</li>" );
+ $wgOut->addHTML( $revisions->getBody() );
+ $wgOut->addHTML( "</ul>" );
+ $wgOut->addHTML( $revisions->getNavigationBar() );
} else {
$wgOut->addWikiText( wfMsg( "nohistory" ) );
}
-
if( $haveFiles ) {
- $wgOut->addHtml( "<h2>" . wfMsgHtml( 'filehist' ) . "</h2>\n" );
+ $wgOut->addHtml( "<h2 id=\"filehistory\">" . wfMsgHtml( 'filehist' ) . "</h2>\n" );
+ $wgOut->addHTML( wfMsgHtml( "restorepoint" ) );
$wgOut->addHtml( "<ul>" );
+ $wgOut->addHTML( "<li>" . wfRadio( "imgrestorepoint", -1, false ) . " " . wfMsgHtml('restorenone') . "</li>" );
while( $row = $files->fetchObject() ) {
+ $file = ArchivedFile::newFromRow( $row );
+
$ts = wfTimestamp( TS_MW, $row->fa_timestamp );
- if ( $this->mAllowed && $row->fa_storage_key ) {
- $checkBox = Xml::check( "fileid" . $row->fa_id );
+ if( $this->mAllowed && $row->fa_storage_key ) {
+ $checkBox = wfRadio( "imgrestorepoint", $ts, false );
$key = urlencode( $row->fa_storage_key );
$target = urlencode( $this->mTarget );
- $pageLink = $sk->makeKnownLinkObj( $titleObj,
- $wgLang->timeanddate( $ts, true ),
- "target=$target&file=$key" );
+ $pageLink = $this->getFileLink( $file, $titleObj, $ts, $target, $key );
} else {
$checkBox = '';
$pageLink = $wgLang->timeanddate( $ts, true );
}
- $userLink = $sk->userLink( $row->fa_user, $row->fa_user_text ) . $sk->userToolLinks( $row->fa_user, $row->fa_user_text );
+ $userLink = $this->getFileUser( $file );
$data =
wfMsgHtml( 'widthheight',
$wgLang->formatNum( $row->fa_width ),
' (' .
wfMsgHtml( 'nbytes', $wgLang->formatNum( $row->fa_size ) ) .
')';
- $comment = $sk->commentBlock( $row->fa_description );
- $wgOut->addHTML( "<li>$checkBox $pageLink . . $userLink $data $comment</li>\n" );
+ $comment = $this->getFileComment( $file );
+ $rd='';
+ if( $wgUser->isAllowed( 'deleterevision' ) ) {
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+ if( !$file->userCan(File::DELETED_RESTRICTED ) ) {
+ // If revision was hidden from sysops
+ $del = $this->message['rev-delundel'];
+ } else {
+ $del = $this->sk->makeKnownLinkObj( $revdel,
+ $this->message['rev-delundel'],
+ 'target=' . urlencode( $this->mTarget ) .
+ '&fileid=' . urlencode( $row->fa_id ) );
+ // Bolden oversighted content
+ if( $file->isDeleted( File::DELETED_RESTRICTED ) )
+ $del = "<strong>$del</strong>";
+ }
+ $rd = "<tt>(<small>$del</small>)</tt>";
+ }
+ $wgOut->addHTML( "<li>$checkBox $rd $pageLink . . $userLink $data $comment</li>\n" );
}
$files->free();
$wgOut->addHTML( "</ul>" );
}
- if ( $this->mAllowed ) {
+ # Show relevant lines from the deletion log:
+ $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
+ $logViewer = new LogViewer(
+ new LogReader(
+ new FauxRequest(
+ array( 'page' => $this->mTargetObj->getPrefixedText(),
+ 'type' => 'delete' ) ) ) );
+ $logViewer->showList( $wgOut );
+
+ if( $this->mAllowed ) {
# Slip in the hidden controls here
$misc = Xml::hidden( 'target', $this->mTarget );
$misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() );
return true;
}
+
+ function formatRevisionRow( $row ) {
+ global $wgUser, $wgLang;
+
+ $rev = new Revision( array(
+ 'page' => $this->mTargetObj->getArticleId(),
+ 'id' => $row->ar_rev_id,
+ 'comment' => $row->ar_comment,
+ 'user' => $row->ar_user,
+ 'user_text' => $row->ar_user_text,
+ 'timestamp' => $row->ar_timestamp,
+ 'minor_edit' => $row->ar_minor_edit,
+ 'text_id' => $row->ar_text_id,
+ 'deleted' => $row->ar_deleted,
+ 'len' => $row->ar_len) );
+
+ $stxt = '';
+ $last = $this->message['last'];
+
+ if( $this->mAllowed ) {
+ $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
+ $checkBox = wfRadio( "restorepoint", $ts, false );
+ $titleObj = SpecialPage::getTitleFor( "Undelete" );
+ $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $this->mTarget );
+ # Last link
+ if( !$rev->userCan( Revision::DELETED_TEXT ) )
+ $last = $this->message['last'];
+ else if( isset($this->prevId[$row->ar_rev_id]) )
+ $last = $this->sk->makeKnownLinkObj( $titleObj, $this->message['last'], "target=" . $this->mTarget .
+ "&diff=" . $row->ar_rev_id . "&oldid=" . $this->prevId[$row->ar_rev_id] );
+ } else {
+ $checkBox = '';
+ $pageLink = $wgLang->timeanddate( $ts, true );
+ }
+ $userLink = $this->sk->revUserTools( $rev );
+
+ if(!is_null($size = $row->ar_len)) {
+ if($size == 0)
+ $stxt = wfMsgHtml('historyempty');
+ else
+ $stxt = wfMsgHtml('historysize', $wgLang->formatNum( $size ) );
+ }
+ $comment = $this->sk->revComment( $rev );
+ $revd='';
+ if( $wgUser->isAllowed( 'deleterevision' ) ) {
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+ if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ // If revision was hidden from sysops
+ $del = $this->message['rev-delundel'];
+ } else {
+ $del = $this->sk->makeKnownLinkObj( $revdel,
+ $this->message['rev-delundel'],
+ 'target=' . urlencode( $this->mTarget ) .
+ '&artimestamp=' . urlencode( $row->ar_timestamp ) );
+ // Bolden oversighted content
+ if( $rev->isDeleted( Revision::DELETED_RESTRICTED ) )
+ $del = "<strong>$del</strong>";
+ }
+ $revd = "<tt>(<small>$del</small>)</tt>";
+ }
+
+ return "<li>$checkBox $revd ($last) $pageLink . . $userLink $stxt $comment</li>";
+ }
+
+ /**
+ * Fetch revision text link if it's available to all users
+ * @return string
+ */
+ function getPageLink( $rev, $titleObj, $ts, $target ) {
+ global $wgLang;
+
+ if( !$rev->userCan(Revision::DELETED_TEXT) ) {
+ return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>';
+ } else {
+ $link = $this->sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ), "target=$target×tamp=$ts" );
+ if( $rev->isDeleted(Revision::DELETED_TEXT) )
+ $link = '<span class="history-deleted">' . $link . '</span>';
+ return $link;
+ }
+ }
+
+ /**
+ * Fetch image view link if it's available to all users
+ * @return string
+ */
+ function getFileLink( $file, $titleObj, $ts, $target, $key ) {
+ global $wgLang;
+
+ if( !$file->userCan(File::DELETED_FILE) ) {
+ return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>';
+ } else {
+ $link = $this->sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ), "target=$target&file=$key" );
+ if( $file->isDeleted(File::DELETED_FILE) )
+ $link = '<span class="history-deleted">' . $link . '</span>';
+ return $link;
+ }
+ }
+
+ /**
+ * Fetch file's user id if it's available to this user
+ * @return string
+ */
+ function getFileUser( $file ) {
+ if( !$file->userCan(File::DELETED_USER) ) {
+ return '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
+ } else {
+ $link = $this->sk->userLink( $file->user, $file->userText ) .
+ $this->sk->userToolLinks( $file->user, $file->userText );
+ if( $file->isDeleted(File::DELETED_USER) )
+ $link = '<span class="history-deleted">' . $link . '</span>';
+ return $link;
+ }
+ }
+
+ /**
+ * Fetch file upload comment if it's available to this user
+ * @return string
+ */
+ function getFileComment( $file ) {
+ if( !$file->userCan(File::DELETED_COMMENT) ) {
+ return '<span class="history-deleted"><span class="comment">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span></span>';
+ } else {
+ $link = $this->sk->commentBlock( $file->description );
+ if( $file->isDeleted(File::DELETED_COMMENT) )
+ $link = '<span class="history-deleted">' . $link . '</span>';
+ return $link;
+ }
+ }
function undelete() {
global $wgOut, $wgUser;
$archive = new PageArchive( $this->mTargetObj );
$ok = $archive->undelete(
- $this->mTargetTimestamp,
+ $this->mPageTimestamp,
$this->mComment,
- $this->mFileVersions );
-
+ $this->mFileTimestamp,
+ $this->mUnsuppress );
if( $ok ) {
$skin = $wgUser->getSkin();
- $link = $skin->makeKnownLinkObj( $this->mTargetObj );
+ $link = $skin->makeKnownLinkObj( $this->mTargetObj, $this->mTargetObj->getPrefixedText(), 'redirect=no' );
$wgOut->addHtml( wfMsgWikiHtml( 'undeletedpage', $link ) );
} else {
$wgOut->showFatalError( wfMsg( "cannotundelete" ) );
+ $wgOut->addHtml( '<p>' . wfMsgHtml( "undeleterevdel" ) . '</p>' );
}
// Show file deletion warnings and errors
}
}
+class UndeleteRevisionsPager extends ReverseChronologicalPager {
+ public $mForm, $mConds;
+ function __construct( $form, $conds = array(), $title ) {
+ $this->mForm = $form;
+ $this->mConds = $conds;
+ $this->title = $title;
+ parent::__construct();
+ }
+
+ function getStartBody() {
+ wfProfileIn( __METHOD__ );
+ # Do a link batch query
+ $this->mResult->seek( 0 );
+ $batch = new LinkBatch();
+ # Give some pointers to make (last) links
+ $this->mForm->prevId = array();
+ while( $row = $this->mResult->fetchObject() ) {
+ $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) );
+ $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) );
+
+ $rev_id = isset($rev_id) ? $rev_id : $row->ar_rev_id;
+ if( $rev_id > $row->ar_rev_id )
+ $this->mForm->prevId[$rev_id] = $row->ar_rev_id;
+ else if( $rev_id < $row->ar_rev_id )
+ $this->mForm->prevId[$row->ar_rev_id] = $rev_id;
+
+ $rev_id = $row->ar_rev_id;
+ }
+
+ $batch->execute();
+ $this->mResult->seek( 0 );
+
+ wfProfileOut( __METHOD__ );
+ return '';
+ }
+
+ function formatRow( $row ) {
+ $block = new Block;
+ return $this->mForm->formatRevisionRow( $row );
+ }
+
+ function getQueryInfo() {
+ $conds = $this->mConds;
+ $conds['ar_namespace'] = $this->title->getNamespace();
+ $conds['ar_title'] = $this->title->getDBkey();
+ return array(
+ 'tables' => array('archive'),
+ 'fields' => array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment',
+ 'ar_rev_id', 'ar_text_id', 'ar_len', 'ar_deleted' ),
+ 'conds' => $conds
+ );
+ }
+
+ function getIndexField() {
+ return 'ar_timestamp';
+ }
+}
$andLatest='';
$limitWatchlist = 'LIMIT ' . intval( $wgUser->getOption( 'wllimit' ) );
} else {
- $andLatest= 'AND rc_this_oldid=page_latest';
+ # Top log Ids for a page are not stored
+ $andLatest = 'AND (rc_this_oldid=page_latest OR rc_type=' . RC_LOG . ') ';
$limitWatchlist = '';
}
$count = floor( $count / 2 );
return( $count );
-}
\ No newline at end of file
+}
return $row === false;
}
+ /**
+ * Checks if the deleted history of another page can be merged into the same title as $this
+ * - Selects for update, so don't call it unless you mean business
+ */
+ public function isValidRestoreOverTarget() {
+
+ $fname = 'Title::isValidRestoreOverTarget';
+ $dbw = wfGetDB( DB_MASTER );
+
+ # Is it a redirect?
+ $page_is_redirect = $dbw->selectField( 'page', 'page_is_redirect',
+ array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ),
+ $fname, 'FOR UPDATE' );
+
+ if ( !$page_is_redirect ) {
+ # Not a redirect
+ wfDebug( __METHOD__ . ": not a redirect\n" );
+ return false;
+ }
+
+ # Does the article have a history?
+ $row = $dbw->selectRow( array('page','revision'),
+ array( 'rev_id' ),
+ array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform,
+ 'page_id=rev_page AND page_latest != rev_id'
+ ), $fname, 'FOR UPDATE'
+ );
+
+ # Return true if there was no history
+ return $row === false;
+ }
+
/**
* Can this title be added to a user's watchlist?
*
*/
class ArchivedFile
{
+ function ArchivedFile( $title, $id=0, $key='' ) {
+ if( !is_object( $title ) ) {
+ throw new MWException( 'ArchivedFile constructor given bogus title.' );
+ }
+ $this->id = -1;
+ $this->title = $title;
+ $this->name = $title->getDBKey();
+ $this->group = '';
+ $this->key = '';
+ $this->size = 0;
+ $this->bits = 0;
+ $this->width = 0;
+ $this->height = 0;
+ $this->metaData = '';
+ $this->mime = "unknown/unknown";
+ $this->type = '';
+ $this->description = '';
+ $this->user = 0;
+ $this->userText = '';
+ $this->timestamp = NULL;
+ $this->deleted = 0;
+ # BC, load if these are specified
+ if( $id || $key ) {
+ $this->load();
+ }
+ }
+
/**
- * Returns a file object from the filearchive table
- * @param $title, the corresponding image page title
- * @param $id, the image id, a unique key
- * @param $key, optional storage key
+ * Loads a file object from the filearchive table
* @return ResultWrapper
*/
- function ArchivedFile( $title, $id=0, $key='' ) {
- if( !is_object( $title ) ) {
+ function load() {
+ if( !is_object( $this->title ) ) {
throw new MWException( 'ArchivedFile constructor given bogus title.' );
}
- $conds = ($id) ? "fa_id = $id" : "fa_storage_key = '$key'";
- if( $title->getNamespace() == NS_IMAGE ) {
+ $conds = ($this->id) ? "fa_id = {$this->id}" : "fa_storage_key = '{$this->key}'";
+ if( $this->title->getNamespace() == NS_IMAGE ) {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'filearchive',
array(
'fa_id',
'fa_name',
+ 'fa_archive_name',
'fa_storage_key',
'fa_storage_group',
'fa_size',
'fa_timestamp',
'fa_deleted' ),
array(
- 'fa_name' => $title->getDbKey(),
+ 'fa_name' => $this->title->getDBKey(),
$conds ),
__METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' ) );
$row = $ret->fetchObject();
// initialize fields for filestore image object
- $this->mId = intval($row->fa_id);
- $this->mName = $row->fa_name;
- $this->mGroup = $row->fa_storage_group;
- $this->mKey = $row->fa_storage_key;
- $this->mSize = $row->fa_size;
- $this->mBits = $row->fa_bits;
- $this->mWidth = $row->fa_width;
- $this->mHeight = $row->fa_height;
- $this->mMetaData = $row->fa_metadata;
- $this->mMime = "$row->fa_major_mime/$row->fa_minor_mime";
- $this->mType = $row->fa_media_type;
- $this->mDescription = $row->fa_description;
- $this->mUser = $row->fa_user;
- $this->mUserText = $row->fa_user_text;
- $this->mTimestamp = $row->fa_timestamp;
- $this->mDeleted = $row->fa_deleted;
+ $this->id = intval($row->fa_id);
+ $this->name = $row->fa_name;
+ $this->archive_name = $row->fa_archive_name;
+ $this->group = $row->fa_storage_group;
+ $this->key = $row->fa_storage_key;
+ $this->size = $row->fa_size;
+ $this->bits = $row->fa_bits;
+ $this->width = $row->fa_width;
+ $this->height = $row->fa_height;
+ $this->metaData = $row->fa_metadata;
+ $this->mime = "$row->fa_major_mime/$row->fa_minor_mime";
+ $this->type = $row->fa_media_type;
+ $this->description = $row->fa_description;
+ $this->user = $row->fa_user;
+ $this->userText = $row->fa_user_text;
+ $this->timestamp = $row->fa_timestamp;
+ $this->deleted = $row->fa_deleted;
} else {
throw new MWException( 'This title does not correspond to an image page.' );
return;
return true;
}
+ /**
+ * Loads a file object from the filearchive table
+ * @return ResultWrapper
+ */
+ public static function newFromRow( $row ) {
+ $file = new ArchivedFile( Title::makeTitle( NS_IMAGE, $row->fa_name ) );
+
+ $file->id = intval($row->fa_id);
+ $file->name = $row->fa_name;
+ $file->archive_name = $row->fa_archive_name;
+ $file->group = $row->fa_storage_group;
+ $file->key = $row->fa_storage_key;
+ $file->size = $row->fa_size;
+ $file->bits = $row->fa_bits;
+ $file->width = $row->fa_width;
+ $file->height = $row->fa_height;
+ $file->metaData = $row->fa_metadata;
+ $file->mime = "$row->fa_major_mime/$row->fa_minor_mime";
+ $file->type = $row->fa_media_type;
+ $file->description = $row->fa_description;
+ $file->user = $row->fa_user;
+ $file->userText = $row->fa_user_text;
+ $file->timestamp = $row->fa_timestamp;
+ $file->deleted = $row->fa_deleted;
+
+ return $file;
+ }
+
/**
* int $field one of DELETED_* bitfield constants
* for file or revision rows
* @return bool
*/
function isDeleted( $field ) {
- return ($this->mDeleted & $field) == $field;
+ return ($this->deleted & $field) == $field;
}
/**
* @return bool
*/
function userCan( $field ) {
- if( isset($this->mDeleted) && ($this->mDeleted & $field) == $field ) {
+ if( isset($this->deleted) && ($this->deleted & $field) == $field ) {
// images
global $wgUser;
- $permission = ( $this->mDeleted & File::DELETED_RESTRICTED ) == File::DELETED_RESTRICTED
+ $permission = ( $this->deleted & File::DELETED_RESTRICTED ) == File::DELETED_RESTRICTED
? 'hiderevision'
: 'deleterevision';
- wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
+ wfDebug( "Checking for $permission due to $field match on $this->deleted\n" );
return $wgUser->isAllowed( $permission );
} else {
return true;
}
}
}
-
-
*/
class FSRepo extends FileRepo {
- var $directory, $deletedDir, $url, $hashLevels, $deletedHashLevels;
+ var $directory, $deletedDir, $hiddenDir, $url, $hashLevels, $deletedHashLevels;
var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
var $oldFileFactory = false;
var $pathDisclosureProtection = 'simple';
$this->hashLevels = isset( $info['hashLevels'] ) ? $info['hashLevels'] : 2;
$this->deletedHashLevels = isset( $info['deletedHashLevels'] ) ?
$info['deletedHashLevels'] : $this->hashLevels;
+ $this->hiddenHashLevels = isset( $info['hiddenHashLevels'] ) ?
+ $info['hiddenHashLevels'] : $this->hashLevels;
$this->deletedDir = isset( $info['deletedDir'] ) ? $info['deletedDir'] : false;
+ $this->hiddenDir = isset( $info['hiddenDir'] ) ? $info['hiddenDir'] : false;
}
/**
return $this->directory;
case 'temp':
return "{$this->directory}/temp";
+ case 'hidden':
+ return $this->hiddenDir;
case 'deleted':
return $this->deletedDir;
default:
return $this->url;
case 'temp':
return "{$this->url}/temp";
+ case 'hidden':
+ return false; // no public URL
case 'deleted':
return false; // no public URL
default:
$status->error( 'filedeleteerror', $srcPath );
$good = false;
}
- } else{
+ } else {
if ( !@rename( $srcPath, $archivePath ) ) {
$status->error( 'filerenameerror', $srcPath, $archivePath );
$good = false;
return $this->getPath() && file_exists( $this->path );
}
+ /**
+ * Returns true if file exists in the repository and can be included in a page.
+ * It would be unsafe to include private images, making public thumbnails inadvertently
+ *
+ * @return boolean Whether file exists in the repository and is includable.
+ * @public
+ */
+ function isVisible() {
+ return $this->exists();
+ }
+
function getTransformScript() {
if ( !isset( $this->transformScript ) ) {
$this->transformScript = false;
* STUB
* Overridden by LocalFile
*/
- function purgeCache( $archiveFiles = array() ) {}
+ function purgeCache() {}
/**
* Purge the file description page, but don't go after
*
* May throw database exceptions on error.
*
- * @param $versions set of record ids of deleted items to restore,
- * or empty to restore all revisions.
+ * @param string $timestamp, restore all revisions since this time
* @return the number of file revisions restored if successful,
* or false on failure
* STUB
* Overridden by LocalFile
*/
- function restore( $versions=array(), $Unsuppress=false ) {
+ function restore( $timestamp = 0, $Unsuppress=false ) {
$this->readOnlyError();
}
*/
abstract class FileRepo {
const DELETE_SOURCE = 1;
+ const FIND_PRIVATE = 1;
const OVERWRITE = 2;
const OVERWRITE_SAME = 4;
*
* @param mixed $time 14-character timestamp, or false for the current version
*/
- function findFile( $title, $time = false ) {
+ function findFile( $title, $time = false, $flags = 0 ) {
# First try the current version of the file to see if it precedes the timestamp
$img = $this->newFile( $title );
- if ( !$img ) {
- return false;
- }
- if ( $img->exists() && ( !$time || $img->getTimestamp() <= $time ) ) {
+ # Check if the image exists and is of the specified timestamp
+ if ( $img->exists() && ( !$time || $img->getTimestamp()==$time ) ) {
return $img;
}
# Now try an old version of the file
$img = $this->newFile( $title, $time );
if ( $img->exists() ) {
- return $img;
+ if( !$img->isDeleted(File::DELETED_FILE) ) {
+ return $img;
+ } else if( ($flags & FileRepo::FIND_PRIVATE) && $img->userCan(File::DELETED_FILE) ) {
+ return $img;
+ }
}
+ return false;
}
/**
$sha1, # SHA-1 base 36 content hash
$dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
$upgraded, # Whether the row was upgraded on load
- $locked; # True if the image row is locked
+ $locked, # True if the image row is locked
+ $deleted; # Bitfield akin to rev_deleted
/**#@-*/
$this->$name = $value;
}
$this->fileExists = true;
- // Check for rows from a previous schema, quietly upgrade them
- $this->maybeUpgradeRow();
+ // Check if the file is hidden...
+ if( $this->isDeleted(File::DELETED_FILE) ) {
+ $this->fileExists = false; // treat as not existing
+ } else {
+ // Check for rows from a previous schema, quietly upgrade them
+ $this->maybeUpgradeRow();
+ }
}
/**
$this->historyRes = $dbr->select( 'image',
array(
'*',
- "'' AS oi_archive_name"
+ "'' AS oi_archive_name",
+ '0 as oi_deleted',
+ 'img_sha1'
),
array( 'img_name' => $this->title->getDBkey() ),
__METHOD__
'oi_media_type' => 'img_media_type',
'oi_major_mime' => 'img_major_mime',
'oi_minor_mime' => 'img_minor_mime',
- 'oi_sha1' => 'img_sha1',
+ 'oi_sha1' => 'img_sha1'
), array( 'img_name' => $this->getName() ), __METHOD__
);
* @param $reason
* @return FileRepoStatus object.
*/
- function delete( $reason ) {
+ function delete( $reason, $suppress=false ) {
$this->lock();
- $batch = new LocalFileDeleteBatch( $this, $reason );
+ $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
$batch->addCurrent();
# Get old version relative paths
* @throws MWException or FSException on database or filestore failure
* @return FileRepoStatus object.
*/
- function deleteOld( $archiveName, $reason ) {
+ function deleteOld( $archiveName, $reason, $suppress=false ) {
$this->lock();
- $batch = new LocalFileDeleteBatch( $this, $reason );
+ $batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
$batch->addOld( $archiveName );
$status = $batch->execute();
$this->unlock();
return $status;
}
- /**
+ /*
* Restore all or specified deleted revisions to the given file.
* Permissions and logging are left to the caller.
*
* May throw database exceptions on error.
*
- * @param $versions set of record ids of deleted items to restore,
- * or empty to restore all revisions.
+ * @param string $timestamp, restore all revisions since this time
* @return FileRepoStatus
*/
- function restore( $versions = array(), $unsuppress = false ) {
+ function restore( $timestamp = 0, $unsuppress = false ) {
$batch = new LocalFileRestoreBatch( $this );
- if ( !$versions ) {
+ if ( !$timestamp ) {
$batch->addAll();
} else {
- $batch->addIds( $versions );
+ $batch->addAll( $timestamp );
}
$status = $batch->execute();
if ( !$status->ok ) {
var $file, $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch;
var $status;
- function __construct( File $file, $reason = '' ) {
+ function __construct( File $file, $reason = '', $suppress=false ) {
$this->file = $file;
$this->reason = $reason;
+ $this->suppress = $suppress;
$this->status = $file->repo->newGood();
}
$dotExt = $ext === '' ? '' : ".$ext";
$encExt = $dbw->addQuotes( $dotExt );
list( $oldRels, $deleteCurrent ) = $this->getOldRels();
+
+ // Bitfields to further suppress the content
+ if ( $this->suppress ) {
+ $bitfield = 0;
+ // This should be 15...
+ $bitfield |= Revision::DELETED_TEXT;
+ $bitfield |= Revision::DELETED_COMMENT;
+ $bitfield |= Revision::DELETED_USER;
+ $bitfield |= Revision::DELETED_RESTRICTED;
+ }
if ( $deleteCurrent ) {
$concat = $dbw->buildConcat( array( "img_sha1", $encExt ) );
'fa_deleted_user' => $encUserId,
'fa_deleted_timestamp' => $encTimestamp,
'fa_deleted_reason' => $encReason,
- 'fa_deleted' => 0,
+ 'fa_deleted' => $this->suppress ? $bitfield : 0,
'fa_name' => 'img_name',
'fa_archive_name' => 'NULL',
'fa_deleted_user' => $encUserId,
'fa_deleted_timestamp' => $encTimestamp,
'fa_deleted_reason' => $encReason,
- 'fa_deleted' => 0,
+ 'fa_deleted' => $this->suppress ? $bitfield : 'oi_deleted',
'fa_name' => 'oi_name',
'fa_archive_name' => 'oi_archive_name',
wfProfileIn( __METHOD__ );
$this->file->lock();
-
+ // Use revisiondelete to handle private files
+ $privateFiles = array();
+ list( $oldRels, $deleteCurrent ) = $this->getOldRels();
+ $dbw = $this->file->repo->getMasterDB();
+ $revisionDeleter = new RevisionDeleter( $dbw );
+ if( !empty( $oldRels ) ) {
+ $res = $dbw->select( 'oldimage',
+ array( 'oi_archive_name', 'oi_sha1' ),
+ array( 'oi_name' => $this->file->getName(),
+ 'oi_archive_name IN (' . $dbw->makeList( array_keys( $oldRels ) ) . ')',
+ 'oi_deleted & '.File::DELETED_FILE => File::DELETED_FILE ),
+ __METHOD__ );
+ while( $row = $dbw->fetchObject( $res ) ) {
+ $title = $this->file->getTitle();
+ $oimage = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $title, $row->oi_archive_name );
+ $oimage->sha1 = $row->oi_sha1;
+ $privateFiles[$row->oi_archive_name] = $oimage;
+ }
+ }
// Prepare deletion batch
$hashes = $this->getHashes();
$this->deletionBatch = array();
$dotExt = $ext === '' ? '' : ".$ext";
foreach ( $this->srcRels as $name => $srcRel ) {
// Skip files that have no hash (missing source)
- if ( isset( $hashes[$name] ) ) {
+ // Move private files using revisiondelete
+ if ( isset($hashes[$name]) && !array_key_exists($name,$privateFiles) ) {
$hash = $hashes[$name];
$key = $hash . $dotExt;
$dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
$this->file->unlockAndRollback();
return $this->status;
}
+
+ // Delete image/oldimage rows
+ $this->doDBDeletes();
+
+ // Move private files to deletion archives
+ $revisionDeleter = new RevisionDeleter( $dbw );
+ foreach( $privateFiles as $name => $oimage ) {
+ $ok = $revisionDeleter->moveImageFromFileRepos( $oimage, 'hidden', 'deleted' );
+ if( $ok )
+ $status->successCount++;
+ else
+ $status->failCount++;
+ }
// Purge squid
if ( $wgUseSquid ) {
SquidUpdate::purge( $urls );
}
- // Delete image/oldimage rows
- $this->doDBDeletes();
-
// Commit and return
$this->file->unlock();
wfProfileOut( __METHOD__ );
* Helper class for file undeletion
*/
class LocalFileRestoreBatch {
- var $file, $cleanupBatch, $ids, $all, $unsuppress = false;
+ var $file, $cleanupBatch, $ids, $all;
- function __construct( File $file ) {
+ function __construct( File $file, $unsuppress = false ) {
$this->file = $file;
$this->cleanupBatch = $this->ids = array();
$this->ids = array();
+ $this->unsuppress = $unsuppress;
}
/**
/**
* Add all revisions of the file
+ * Can be all from $timestamp if given
*/
- function addAll() {
+ function addAll( $timestamp = false ) {
$this->all = true;
+ $this->timestamp = $timestamp;
}
/**
$dbw = $this->file->repo->getMasterDB();
$status = $this->file->repo->newGood();
+ $revisionDeleter = new RevisionDeleter( $dbw );
+ $privateFiles = array();
+
// Fetch all or selected archived revisions for the file,
// sorted from the most recent to the oldest.
$conditions = array( 'fa_name' => $this->file->getName() );
if( !$this->all ) {
$conditions[] = 'fa_id IN (' . $dbw->makeList( $this->ids ) . ')';
+ } else if( $this->timestamp ) {
+ $conditions[] = "fa_timestamp >= {$this->timestamp}";
}
$result = $dbw->select( 'filearchive', '*',
$archiveNames = array();
while( $row = $dbw->fetchObject( $result ) ) {
$idsPresent[] = $row->fa_id;
- if ( $this->unsuppress ) {
- // Currently, fa_deleted flags fall off upon restore, lets be careful about this
- } else if ( ($row->fa_deleted & Revision::DELETED_RESTRICTED) && !$wgUser->isAllowed('hiderevision') ) {
- // Skip restoring file revisions that the user cannot restore
- continue;
- }
+
if ( $row->fa_name != $this->file->getName() ) {
$status->error( 'undelete-filename-mismatch', $wgLang->timeanddate( $row->fa_timestamp ) );
$status->failCount++;
}
if ( $first && !$exists ) {
+ // The live (current) version cannot be hidden!
+ if( $row->fa_deleted ) {
+ $this->file->unlock();
+ return $status;
+ }
// This revision will be published as the new current version
$destRel = $this->file->getRel();
$insertCurrent = array(
'oi_media_type' => $props['media_type'],
'oi_major_mime' => $props['major_mime'],
'oi_minor_mime' => $props['minor_mime'],
- 'oi_deleted' => $row->fa_deleted,
+ 'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
'oi_sha1' => $sha1 );
}
$deleteIds[] = $row->fa_id;
- $storeBatch[] = array( $deletedUrl, 'public', $destRel );
- $this->cleanupBatch[] = $row->fa_storage_key;
+ // Use revisiondelete to handle private files
+ if( $row->fa_deleted & File::DELETED_FILE ) {
+ $title = $this->file->getTitle();
+ $oimage = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $title, $archiveName );
+ $oimage->sha1 = $sha1;
+ $privateFiles[$archiveName] = $oimage;
+ } else {
+ $storeBatch[] = array( $deletedUrl, 'public', $destRel );
+ $this->cleanupBatch[] = $row->fa_storage_key;
+ }
$first = false;
}
unset( $result );
array( 'fa_id IN (' . $dbw->makeList( $deleteIds ) . ')' ),
__METHOD__ );
}
+
+ // Immediatly move to private files to hidden directory
+ $revisionDeleter = new RevisionDeleter( $dbw );
+ foreach ( $privateFiles as $oimage ) {
+ $ok = $revisionDeleter->moveImageFromFileRepos( $oimage, 'deleted', 'hidden' );
+ if( $ok )
+ $status->successCount++;
+ else
+ $status->failCount++;
+ }
if( $status->successCount > 0 ) {
if( !$exists ) {
function isOld() {
return true;
}
+
+ function isVisible() {
+ return $this->exists() && !$this->isDeleted(File::DELETED_FILE);
+ }
/**
* Try to load file metadata from memcached. Returns true on success.
function getCacheFields( $prefix = 'img_' ) {
$fields = parent::getCacheFields( $prefix );
$fields[] = $prefix . 'archive_name';
-
- // XXX: Temporary hack before schema update
- //$fields = array_diff( $fields, array(
- // 'oi_media_type', 'oi_major_mime', 'oi_minor_mime', 'oi_metadata' ) );
+ $fields[] = $prefix . 'deleted';
+
return $fields;
}
);
wfProfileOut( __METHOD__ );
}
-}
+
+ /**
+ * int $field one of DELETED_* bitfield constants
+ * for file or revision rows
+ * @return bool
+ */
+ function isDeleted( $field ) {
+ return ($this->deleted & $field) == $field;
+ }
+
+ /**
+ * Determine if the current user is allowed to view a particular
+ * field of this FileStore image file, if it's marked as deleted.
+ * @param int $field
+ * @return bool
+ */
+ function userCan( $field ) {
+ if( isset($this->deleted) && ($this->deleted & $field) == $field ) {
+ global $wgUser;
+ $permission = ( $this->deleted & File::DELETED_RESTRICTED ) == File::DELETED_RESTRICTED
+ ? 'hiderevision'
+ : 'deleterevision';
+ wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
+ return $wgUser->isAllowed( $permission );
+ } else {
+ return true;
+ }
+ }
+}
'formerror' => 'Error: could not submit form',
'badarticleerror' => 'This action cannot be performed on this page.',
'cannotdelete' => 'Could not delete the page or file specified. (It may have already been deleted by someone else.)',
+'cannotdelete-merge' => 'Pages cannot be deleted if a different page already has archived revisions under the same title. This can
+happen if you move a page over another one and then delete it.',
'badtitle' => 'Bad title',
'badtitletext' => 'The requested page title was invalid, empty, or an incorrectly linked inter-language or inter-wiki title. It may contain one or more characters which cannot be used in titles.',
'perfdisabled' => 'Sorry! This feature has been temporarily disabled because it slows the database down to the point that no one can use the wiki.',
</div>',
'rev-delundel' => 'show/hide',
'revisiondelete' => 'Delete/undelete revisions',
-'revdelete-nooldid-title' => 'No target revision',
-'revdelete-nooldid-text' => 'You have not specified target revision or revisions to perform this function on.',
-'revdelete-selected' => "{{PLURAL:$2|Selected revision|Selected revisions}} of '''$1:'''",
-'logdelete-selected' => "{{PLURAL:$2|Selected log event|Selected log events}} for '''$1:'''",
+'revdelete-nooldid-title' => 'Invalid target revision',
+'revdelete-nooldid-text' => 'You have either not specified a target revision(s) to perform this
+function, the specified revision does not exist, or you are attempting to hide the current revision.',
+'revdelete-selected' => "{{PLURAL:$2|Selected revision|Selected revisions}} of [[:$1]]:",
+'logdelete-selected' => "{{PLURAL:$1|Selected log event|Selected log events}}:",
'revdelete-text' => 'Deleted revisions and events will still appear in the page history and logs,
but parts of their content will be inaccessible to the public.
'revdelete-hide-name' => 'Hide action and target',
'revdelete-hide-comment' => 'Hide edit comment',
'revdelete-hide-user' => "Hide editor's username/IP",
-'revdelete-hide-restricted' => 'Apply these restrictions to sysops as well as others',
+'revdelete-hide-restricted' => 'Apply these restrictions to Sysops and lock this interface',
'revdelete-suppress' => 'Suppress data from sysops as well as others',
'revdelete-hide-image' => 'Hide file content',
'revdelete-unsuppress' => 'Remove restrictions on restored revisions',
'revdelete-logentry' => 'changed revision visibility of [[$1]]',
'logdelete-logentry' => 'changed event visibility of [[$1]]',
'revdelete-logaction' => '$1 {{PLURAL:$1|revision|revisions}} set to mode $2',
-'logdelete-logaction' => '$1 {{PLURAL:$1|event|events}} to [[$3]] set to mode $2',
-'revdelete-success' => 'Revision visibility successfully set.',
-'logdelete-success' => 'Event visibility successfully set.',
+'logdelete-logaction' => '$1 {{PLURAL:$1|event|events}} set to mode $2',
+'revdelete-success' => "'''Revision visibility successfully set.'''",
+'logdelete-success' => "'''Log visibility successfully set.'''",
+'revdel-restore' => 'Change visiblity',
# Oversight log
-'oversightlog' => 'Oversight log',
-'overlogpagetext' => 'Below is a list of the most recent deletions and blocks involving content
-hidden from Sysops. See the [[Special:Ipblocklist|IP block list]] for the list of currently operational bans and blocks.',
+'oversightlog' => 'Suppression log',
+'overlogpagetext' => 'Below is a list of the most recent deletions and blocks involving items
+hidden from Sysops. Automatically blocked IP addresses are not listed. See the [[Special:Ipblocklist|IP block list]]
+for the list of currently operational bans and blocks.
+
+Blocked users listed here can cannot edit their talk pages and thus can only communicate via email. Their accounts
+will remain hidden only as long as they are blocked.',
+
+# History merging
+'mergehistory' => 'Merge page histories',
+'mergehistory-header' => 'This page lets you merge revisions of the history of one source page into a newer page.
+Please make sure that this change will maintain historical page continuity.
+
+At least the current revision of the source page must be left.',
+'mergehistory-box' => 'Merge revisions of two pages:',
+'mergehistory-from' => 'Source page:',
+'mergehistory-into' => 'Destination page:',
+'mergehistory-list' => 'Mergeable edit history',
+'mergehistory-merge' => 'The following revisions of [[:$1|$1]] can be merged into [[:$2|$2]]. Use the radio
+button column to merge in only the revisions created at or before the specified time. Note that you will have to
+reselect any options if you use the navigation links.',
+'mergehistory-go' => 'Show mergeable edits',
+'mergehistory-submit' => 'Merge revisions',
+'mergehistory-empty' => 'No revisions can be merged',
+'mergehistory-success' => '$3 revisions of [[:$1]] successfully merged into [[:$2]].',
+'mergehistory-fail' => 'Unable to perform history merge, please recheck the page and time parameters.',
+
+'mergelog' => 'Merge log',
+'pagemerge-logentry' => 'merged $1 into $2 (revisions up to $3)',
+'revertmerge' => 'Unmerge',
+'mergelogpagetext' => 'Below is a list of the most recent merges of one page history into another.',
# Diffs
'history-title' => 'Revision history of "$1"',
'grouppage-sysop' => '{{ns:project}}:Administrators',
'grouppage-bureaucrat' => '{{ns:project}}:Bureaucrats',
+'oversight' => 'Oversight',
+'group-oversight' => 'Oversights',
+'group-oversight-member' => 'Oversight',
+'grouppage-oversight' => '{{ns:project}}:Oversight',
+
# User rights log
'rightslog' => 'User rights log',
'rightslogtext' => 'This is a log of changes to user rights.',
'specialpages-summary' => '', # only translate this message to other languages if you have to change it
'spheading' => 'Special pages for all users',
'restrictedpheading' => 'Restricted special pages',
+'restrictedlheading' => 'Restricted logs',
'rclsub' => '(to pages linked from "$1")',
'newpages' => 'New pages',
'newpages-summary' => '', # only translate this message to other languages if you have to change it
'specialloguserlabel' => 'User:',
'speciallogtitlelabel' => 'Title:',
'log' => 'Logs',
-'all-logs-page' => 'All logs',
+'all-logs-page' => 'All public logs',
'log-search-legend' => 'Search for logs',
'log-search-submit' => 'Go',
-'alllogstext' => 'Combined display of all available logs of {{SITENAME}}.
+'alllogstext' => 'Combined display of all available public logs of {{SITENAME}}.
You can narrow down the view by selecting a log type, the user name, or the affected page.',
'logempty' => 'No matching items in log.',
'log-title-wildcard' => 'Search titles starting with this text',
'deletedtext' => '"$1" has been deleted.
See $2 for a record of recent deletions.',
'deletedarticle' => 'deleted "[[$1]]"',
+'suppressedarticle' => 'suppressed "[[$1]]"',
'dellogpage' => 'Deletion log',
'dellogpagetext' => 'Below is a list of the most recent deletions.',
'deletionlog' => 'deletion log',
'sessionfailure' => 'There seems to be a problem with your login session;
this action has been canceled as a precaution against session hijacking.
Please hit "back" and reload the page you came from, then try again.',
+
'protectlogpage' => 'Protection log',
'protectlogtext' => 'Below is a list of page locks and unlocks. See the [[Special:Protectedpages|protected pages list]] for the list of currently operational page protections.',
'protectedarticle' => 'protected "[[$1]]"',
'unprotectedarticle' => 'unprotected "[[$1]]"',
'protectsub' => '(Setting protection level for "$1")',
'confirmprotect' => 'Confirm protection',
+'protect-fileonly' => 'Apply edit restrictions to file uploads only',
'protectcomment' => 'Comment:',
'protectexpiry' => 'Expires:',
'protect_expiry_invalid' => 'Expiry time is invalid.',
# Restrictions (nouns)
'restriction-edit' => 'Edit',
'restriction-move' => 'Move',
+'restriction-upload' => 'Upload',
# Restriction levels
'restriction-level-sysop' => 'full protected',
# Undelete
'undelete' => 'View deleted pages',
+'undeleterevs' => 'Deleted revisions',
'undeletepage' => 'View and restore deleted pages',
'viewdeletedpage' => 'View deleted pages',
+'undeletepagetitle' => '\'\'\'The following consists of deleted revisions of [[:$1]]\'\'\'.',
'undeletepagetext' => 'The following pages have been deleted but are still in the archive and
can be restored. The archive may be periodically cleaned out.',
-'undeleteextrahelp' => "To restore the entire page, leave all checkboxes deselected and
-click '''''Restore'''''. To perform a selective restoration, check the boxes corresponding to the
-revisions to be restored, and click '''''Restore'''''. Clicking '''''Reset''''' will clear the
-comment field and all checkboxes.",
+'undeleteextrahelp' => "To restore the entire page, leave all radios deselected and click '''''Restore'''''.
+To perform a selective restoration, check the desired restore point below and click '''''Restore'''''.
+Clicking '''''Reset''''' will reset this form. Note that you will have to reselect any options if you
+use the navigation links.",
'undeleterevisions' => '$1 {{PLURAL:$1|revision|revisions}} archived',
'undeletehistory' => 'If you restore the page, all revisions will be restored to the history.
If a new page with the same name has been created since the deletion, the restored
revisions will appear in the prior history, and the current revision of the live page
-will not be automatically replaced. Also note that restrictions on file revisions are lost upon restoration',
-'undeleterevdel' => "Undeletion will not be performed if it will result in the top page revision being
-partially deleted. In such cases, you must uncheck or unhide the newest deleted revisions. Revisions of files
-that you don't have permission to view will not be restored.",
+will not be automatically replaced.',
+'undeleterevdel' => 'Undeletion will not be performed if either it would result in the top page/image revision
+being restricted. Histories of different pages cannot be merged unless the live page is a redirect with no edit history.',
'undeletehistorynoadmin' => 'This article has been deleted. The reason for deletion is
shown in the summary below, along with details of the users who had edited this page
before deletion. The actual text of these deleted revisions is only available to administrators.',
+'restorepoint' => 'Use the radio button column to restore only revisions from the specified time onwards.',
+'restorenone' => '(select this button to restore none of these revisions)',
+
'undelete-revision' => 'Deleted revision of $1 (as of $2) by $3:',
'undeleterevision-missing' => 'Invalid or missing revision. You may have a bad link, or the
revision may have been restored or removed from the archive.',
{
$fname = 'rebuildRecentChangesTablePass1';
$dbw = wfGetDB( DB_MASTER );
- extract( $dbw->tableNames( 'recentchanges', 'cur', 'old' ) );
$dbw->delete( 'recentchanges', '*' );
'rc_this_oldid' => 'rev_id',
'rc_last_oldid' => 0, // is this ok?
'rc_type' => $dbw->conditional( 'page_is_new != 0', RC_NEW, RC_EDIT ),
+ 'rc_deleted' => 'rev_deleted'
), array(
'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $cutoff ) ),
'rev_page=page_id'
}
function rebuildRecentChangesTablePass3()
+{
+ $fname = 'rebuildRecentChangesTablePass3';
+ $dbw = wfGetDB( DB_MASTER );
+
+ print( "Loading from user, page, and logging tables...\n" );
+
+ global $wgRCMaxAge, $wgLogRestrictions;
+
+ // Exclude non-public logs
+ $avoidLogs = array();
+ if( isset($wgLogRestrictions) ) {
+ foreach ( $wgLogRestrictions as $logtype => $right ) {
+ // Do not show private logs when not specifically requested
+ if ( $right !='*' ) {
+ $safetype =$dbw->strencode( $logtype );
+ $avoidLogs[] = "'$safetype'";
+ }
+ }
+ }
+ // Some logs don't go in RC. This can't really detect all of those ... :(
+ $avoidLogs[] = "'patrol'"; // hack...we don't want this here
+
+ if( !empty($avoidLogs) ) {
+ $skipLogs = 'log_type NOT IN(' . implode(',',$avoidLogs) . ')';
+ } else {
+ $skipLogs = '1 = 1';
+ }
+
+ $cutoff = time() - $wgRCMaxAge;
+ $dbw->insertSelect( 'recentchanges', array( 'logging', 'page', 'user' ),
+ array(
+ 'rc_timestamp' => 'log_timestamp',
+ 'rc_cur_time' => 'log_timestamp',
+ 'rc_user' => 'log_user',
+ 'rc_user_text' => 'user_name',
+ 'rc_namespace' => 'log_namespace',
+ 'rc_title' => 'log_title',
+ 'rc_comment' => 'log_comment',
+ 'rc_minor' => 0,
+ 'rc_bot' => 0,
+ 'rc_new' => 0,
+ 'rc_cur_id' => 0,
+ 'rc_this_oldid' => 0,
+ 'rc_last_oldid' => 0,
+ 'rc_type' => RC_LOG,
+ 'rc_cur_id' => 'page_id',
+ 'rc_log_type' => 'log_type',
+ 'rc_log_action' => 'log_action',
+ 'rc_logid' => 'log_id',
+ 'rc_params' => 'log_params',
+ 'rc_deleted' => 'log_deleted'
+ ), array(
+ 'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $cutoff ) ),
+ 'log_user=user_id',
+ 'log_namespace=page_namespace',
+ 'log_title=page_title',
+ $skipLogs
+ ), $fname,
+ array(), // INSERT options
+ array( 'ORDER BY' => 'log_timestamp DESC', 'LIMIT' => 5000 ) // SELECT options
+ );
+}
+
+function rebuildRecentChangesTablePass4()
{
global $wgGroupPermissions, $wgUseRCPatrol;
rebuildRecentChangesTablePass1();
rebuildRecentChangesTablePass2();
-rebuildRecentChangesTablePass3(); // flag bot edits
+rebuildRecentChangesTablePass3(); // logs entries
+rebuildRecentChangesTablePass4(); // flag bot edits
print "Done.\n";
exit();