From: Brion Vibber Date: Thu, 16 Mar 2006 19:04:25 +0000 (+0000) Subject: * Further work on rev_deleted; changed to a bitfield with several data-hiding X-Git-Tag: 1.6.0~201 X-Git-Url: http://git.cyclocoop.org/%7B%24www_url%7Dadmin/compta/exercices/modifier.php?a=commitdiff_plain;h=103be1dda1e572d6af600228eff01b1a929ca7a6;p=lhc%2Fweb%2Fwiklou.git * Further work on rev_deleted; changed to a bitfield with several data-hiding options. Not yet ready for production use; Special:Revisiondelete is incomplete, and the flags are not preserved across page deletion/undeletion. To try it; add the 'deleterevision' permission to a privileged group. Also split some functions from GlobalFunctions to XmlFunctions.php, added some convenience functions for building form controls, some more Linker conveniences for formatting various types of standard link clusters. --- diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 18b94d21de..2c4c5d0f2b 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -685,6 +685,11 @@ fully support the editing toolbar, but was found to be too confusing. unnecessary hidden UI work when watch/unwatch is performed on edit * Fixed bogus master fallback in external storage * (bug 5246) Add speak:none to "hiddenStructure" class in main.css +* Further work on rev_deleted; changed to a bitfield with several data-hiding + options. Not yet ready for production use; Special:Revisiondelete is + incomplete, and the flags are not preserved across page deletion/undeletion. + To try it; add the 'deleterevision' permission to a privileged group. + === Caveats === diff --git a/includes/Article.php b/includes/Article.php index 18e5f8db78..f39ae2e7d7 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -495,7 +495,10 @@ class Article { } } - $this->mContent = $revision->getText(); + // FIXME: Horrible, horrible! This content-loading interface just plain sucks. + // We should instead work with the Revision object when we need it... + $this->mContent = $revision->userCan( MW_REV_DELETED_TEXT ) ? $revision->getRawText() : ""; + //$this->mContent = $revision->getText(); $this->mUser = $revision->getUser(); $this->mUserText = $revision->getUserText(); @@ -767,7 +770,7 @@ class Article { wfProfileOut( $fname ); return; } - + if ( empty( $oldid ) && $this->checkTouched() ) { $wgOut->setETag($parserCache->getETag($this, $wgUser)); @@ -846,7 +849,18 @@ class Article { if ( !empty( $oldid ) ) { $this->setOldSubtitle( isset($this->mOldId) ? $this->mOldId : $oldid ); - $wgOut->setRobotpolicy( 'noindex,follow' ); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); + if( $this->mRevision->isDeleted( MW_REV_DELETED_TEXT ) ) { + if( !$this->mRevision->userCan( MW_REV_DELETED_TEXT ) ) { + $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) ); + $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); + return; + } else { + $wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) ); + // and we are allowed to see... + } + } + } } if( !$outputDone ) { diff --git a/includes/ChangesList.php b/includes/ChangesList.php index 0f814f0c3a..97336ffc5c 100644 --- a/includes/ChangesList.php +++ b/includes/ChangesList.php @@ -181,8 +181,8 @@ class ChangesList { /** Insert links to user page, user talk page and eventually a blocking link */ function insertUserRelatedLinks(&$s, &$rc) { - $s .= $this->userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] ); - $s .= $this->userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] ); + $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 comment */ @@ -203,78 +203,6 @@ class ChangesList { ( !$wgOnlySysopsCanPatrol || $wgUser->isAllowed( 'patrol' ) ); } - /** - * Make user link (or user contributions for unregistered users) - * @param int $userId - * @param string $userText - * @return string HTML fragment - * @access private - */ - function userLink( $userId, $userText ) { - $encName = htmlspecialchars( $userText ); - if( $userId == 0 ) { - $contribsPage = Title::makeTitle( NS_SPECIAL, 'Contributions' ); - return $this->skin->makeKnownLinkObj( $contribsPage, - $encName, 'target=' . urlencode( $userText ) ); - } else { - $userPage = Title::makeTitle( NS_USER, $userText ); - return $this->skin->makeLinkObj( $userPage, $encName ); - } - } - - /** - * @param int $userId - * @param string $userText - * @return string HTML fragment with talk and/or block links - * @access private - */ - function userToolLinks( $userId, $userText ) { - global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans; - $talkable = !( $wgDisableAnonTalk && 0 == $userId ); - $blockable = ( $wgSysopUserBans || 0 == $userId ); - - $items = array(); - if( $talkable ) { - $items[] = $this->userTalkLink( $userId, $userText ); - } - if( $blockable && $wgUser->isAllowed( 'block' ) ) { - $items[] = $this->blockLink( $userId, $userText ); - } - - if( $items ) { - return ' (' . implode( ' | ', $items ) . ')'; - } else { - return ''; - } - } - - /** - * @param int $userId - * @param string $userText - * @return string HTML fragment with user talk link - * @access private - */ - function userTalkLink( $userId, $userText ) { - global $wgContLang; - $talkname = $wgContLang->getNsText( NS_TALK ); # use the shorter name - - $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText ); - $userTalkLink = $this->skin->makeLinkObj( $userTalkPage, $talkname ); - return $userTalkLink; - } - - /** - * @param int $userId - * @param string $userText - * @return string HTML fragment with block link - * @access private - */ - function blockLink( $userId, $userText ) { - $blockPage = Title::makeTitle( NS_SPECIAL, 'Blockip' ); - $blockLink = $this->skin->makeKnownLinkObj( $blockPage, - $this->message['blocklink'], 'ip=' . urlencode( $userText ) ); - return $blockLink; - } } @@ -429,13 +357,13 @@ class EnhancedChangesList extends ChangesList { $curIdEq.'&diff='.$rc_this_oldid.'&oldid='.$rc_last_oldid . $rcIdQuery ); } - $rc->userlink = $this->userLink( $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->userToolLinks( $rc_user, $rc_user_text ); + $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 diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 97d0617cef..1daf9e322b 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -849,6 +849,10 @@ $wgGroupPermissions['sysop']['autoconfirmed'] = 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; + /** * The developer group is deprecated, but can be activated if need be * to use the 'lockdb' and 'unlockdb' special pages. Those require diff --git a/includes/DifferenceEngine.php b/includes/DifferenceEngine.php index cdd9e576d7..c6170342a5 100644 --- a/includes/DifferenceEngine.php +++ b/includes/DifferenceEngine.php @@ -24,8 +24,6 @@ class DifferenceEngine { var $mOldid, $mNewid, $mTitle; var $mOldtitle, $mNewtitle, $mPagetitle; var $mOldtext, $mNewtext; - var $mOldUser, $mNewUser; - var $mOldComment, $mNewComment; var $mOldPage, $mNewPage; var $mRcidMarkPatrolled; var $mOldRev, $mNewRev; @@ -153,21 +151,11 @@ CONTROL; $talk = $wgContLang->getNsText( NS_TALK ); $contribs = wfMsg( 'contribslink' ); - $this->mOldComment = $sk->formatComment($this->mOldComment); - $this->mNewComment = $sk->formatComment($this->mNewComment); - - $oldUserLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER, $this->mOldUser ), $this->mOldUser ); - $newUserLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER, $this->mNewUser ), $this->mNewUser ); - $oldUTLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER_TALK, $this->mOldUser ), $talk ); - $newUTLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER_TALK, $this->mNewUser ), $talk ); - $oldContribs = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), $contribs, - 'target=' . urlencode($this->mOldUser) ); - $newContribs = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), $contribs, - 'target=' . urlencode($this->mNewUser) ); if ( $this->mNewRev->isCurrent() && $wgUser->isAllowed('rollback') ) { + $username = $this->mNewRev->getUserText(); $rollback = '   [' . $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'rollbacklink' ), - 'action=rollback&from=' . urlencode($this->mNewUser) . - '&token=' . urlencode( $wgUser->editToken( array( $this->mTitle->getPrefixedText(), $this->mNewUser ) ) ) ) . + 'action=rollback&from=' . urlencode( $username ) . + '&token=' . urlencode( $wgUser->editToken( array( $this->mTitle->getPrefixedText(), $username ) ) ) ) . ']'; } else { $rollback = ''; @@ -190,12 +178,14 @@ CONTROL; 'diff=next&oldid='.$this->mNewid, '', '', 'id="differences-nextlink"' ); } - $oldHeader = "{$this->mOldtitle}
$oldUserLink " . - "($oldUTLink | $oldContribs)
" . $this->mOldComment . - '
' . $prevlink; - $newHeader = "{$this->mNewtitle}
$newUserLink " . - "($newUTLink | $newContribs) $rollback
" . $this->mNewComment . - '
' . $nextlink . $patrol; + $oldHeader = "{$this->mOldtitle}
" . + $sk->revUserTools( $this->mOldRev ) . "
" . + $sk->revComment( $this->mOldRev ) . "
" . + $prevlink; + $newHeader = "{$this->mNewtitle}
" . + $sk->revUserTools( $this->mNewRev ) . " $rollback
" . + $sk->revComment( $this->mNewRev ) . "
" . + $nextlink . $patrol; $this->showDiff( $oldHeader, $newHeader ); $wgOut->addHTML( "

{$this->mPagetitle}

\n" ); @@ -255,19 +245,16 @@ CONTROL; # $sk = $wgUser->getSkin(); - $uTLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER_TALK, $this->mOldUser ), $wgLang->getNsText( NS_TALK ) ); - $userLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER, $this->mOldUser ), $this->mOldUser ); - $contribs = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), wfMsg( 'contribslink' ), - 'target=' . urlencode($this->mOldUser) ); $nextlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'nextdiff' ), 'diff=next&oldid='.$this->mNewid, '', '', 'id="differences-nextlink"' ); - $header = "
{$this->mOldtitle}
$userLink " . - "($uTLink | $contribs)
" . $this->mOldComment . - '
' . $nextlink. "
\n"; + $header = "
{$this->mOldtitle}
" . + $sk->revUserTools( $this->mNewRev ) . "
" . + $sk->revComment( $this->mNewRev ) . "
" . + $nextlink . "
\n"; $wgOut->addHTML( $header ); $wgOut->setSubtitle( wfMsg( 'difference' ) ); - $wgOut->setRobotpolicy( 'noindex,follow' ); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); # Show current revision @@ -289,7 +276,7 @@ CONTROL; global $wgOut; $diff = $this->getDiff( $otitle, $ntitle ); if ( $diff === false ) { - $wgOut->addWikitext( wfMsg( 'missingarticle', "$t" ) ); + $wgOut->addWikitext( wfMsg( 'missingarticle', "(fixme, bug)" ) ); return false; } else { $wgOut->addHTML( $diff ); @@ -510,9 +497,6 @@ CONTROL; $this->mNewtitle = "{$this->mPagetitle}"; } - $this->mNewUser = $this->mNewRev->getUserText(); - $this->mNewComment = $this->mNewRev->getComment(); - // Load the old revision object $this->mOldRev = false; if( $this->mOldid ) { @@ -539,10 +523,6 @@ CONTROL; $t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true ); $oldLink = $this->mOldPage->escapeLocalUrl( 'oldid=' . $this->mOldid ); $this->mOldtitle = "" . htmlspecialchars( wfMsg( 'revisionasof', $t ) ) . ''; - - - $this->mOldUser = $this->mOldRev->getUserText(); - $this->mOldComment = $this->mOldRev->getComment(); } return true; @@ -563,6 +543,7 @@ CONTROL; return false; } if ( $this->mOldRev ) { + // FIXME: permission tests $this->mOldtext = $this->mOldRev->getText(); if ( $this->mOldtext === false ) { return false; diff --git a/includes/Export.php b/includes/Export.php index 88ef6e3dbf..dee2e044c4 100644 --- a/includes/Export.php +++ b/includes/Export.php @@ -1,5 +1,5 @@ +# Copyright (C) 2003, 2005, 2006 Brion Vibber # http://www.mediawiki.org/ # # This program is free software; you can redistribute it and/or modify @@ -232,7 +232,7 @@ class XmlDumpWriter { * @return string */ function schemaVersion() { - return "0.3"; + return "0.3"; // FIXME: upgrade to 0.4 when updated XSD is ready, for the revision deletion bits } /** @@ -360,23 +360,31 @@ class XmlDumpWriter { $ts = wfTimestamp( TS_ISO_8601, $row->rev_timestamp ); $out .= " " . wfElement( 'timestamp', null, $ts ) . "\n"; - $out .= " \n"; - if( $row->rev_user ) { - $out .= " " . wfElementClean( 'username', null, strval( $row->rev_user_text ) ) . "\n"; - $out .= " " . wfElement( 'id', null, strval( $row->rev_user ) ) . "\n"; + if( $row->rev_deleted & MW_REV_DELETED_USER ) { + $out .= " " . wfElement( 'contributor', array( 'deleted' => 'deleted' ) ) . "\n"; } else { - $out .= " " . wfElementClean( 'ip', null, strval( $row->rev_user_text ) ) . "\n"; + $out .= " \n"; + if( $row->rev_user ) { + $out .= " " . wfElementClean( 'username', null, strval( $row->rev_user_text ) ) . "\n"; + $out .= " " . wfElement( 'id', null, strval( $row->rev_user ) ) . "\n"; + } else { + $out .= " " . wfElementClean( 'ip', null, strval( $row->rev_user_text ) ) . "\n"; + } + $out .= " \n"; } - $out .= " \n"; if( $row->rev_minor_edit ) { $out .= " \n"; } - if( $row->rev_comment != '' ) { + if( $row->rev_deleted & MW_REV_DELETED_COMMENT ) { + $out .= " " . wfElement( 'comment', array( 'deleted' => 'deleted' ) ) . "\n"; + } elseif( $row->rev_comment != '' ) { $out .= " " . wfElementClean( 'comment', null, strval( $row->rev_comment ) ) . "\n"; } - if( isset( $row->old_text ) ) { + if( $row->rev_deleted & MW_REV_DELETED_TEXT ) { + $out .= " " . wfElement( 'text', array( 'deleted' => 'deleted' ) ) . "\n"; + } elseif( isset( $row->old_text ) ) { // Raw text from the database may have invalid chars $text = strval( Revision::getRevisionText( $row ) ); $out .= " " . wfElementClean( 'text', diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 48374810e9..0644cf2683 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -30,6 +30,7 @@ require_once( 'DatabaseFunctions.php' ); require_once( 'UpdateClasses.php' ); require_once( 'LogPage.php' ); require_once( 'normal/UtfNormalUtil.php' ); +require_once( 'XmlFunctions.php' ); /** * Compatibility functions @@ -839,29 +840,6 @@ function wfQuotedPrintable( $string, $charset = '' ) { return $out; } -/** - * Returns an escaped string suitable for inclusion in a string literal - * for JavaScript source code. - * Illegal control characters are assumed not to be present. - * - * @param string $string - * @return string - */ -function wfEscapeJsString( $string ) { - // See ECMA 262 section 7.8.4 for string literal format - $pairs = array( - "\\" => "\\\\", - "\"" => "\\\"", - '\'' => '\\\'', - "\n" => "\\n", - "\r" => "\\r", - - # To avoid closing the element or CDATA section - "<" => "\\x3c", - ">" => "\\x3e", - ); - return strtr( $string, $pairs ); -} /** * @todo document @@ -872,15 +850,6 @@ function wfTime() { return (float)$st[0] + (float)$st[1]; } -/** - * Changes the first character to an HTML entity - */ -function wfHtmlEscapeFirst( $text ) { - $ord = ord($text); - $newText = substr($text, 1); - return "&#$ord;$newText"; -} - /** * Sets dest to source and returns the original value of dest * If source is NULL, it just returns the value, it doesn't set the variable @@ -1453,100 +1422,6 @@ function wfGetSiteNotice() { return( $siteNotice ); } -/** - * Format an XML element with given attributes and, optionally, text content. - * Element and attribute names are assumed to be ready for literal inclusion. - * Strings are assumed to not contain XML-illegal characters; special - * characters (<, >, &) are escaped but illegals are not touched. - * - * @param string $element - * @param array $attribs Name=>value pairs. Values will be escaped. - * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default) - * @return string - */ -function wfElement( $element, $attribs = null, $contents = '') { - $out = '<' . $element; - if( !is_null( $attribs ) ) { - foreach( $attribs as $name => $val ) { - $out .= ' ' . $name . '="' . htmlspecialchars( $val ) . '"'; - } - } - if( is_null( $contents ) ) { - $out .= '>'; - } else { - if( $contents == '' ) { - $out .= ' />'; - } else { - $out .= '>' . htmlspecialchars( $contents ) . ""; - } - } - return $out; -} - -/** - * Format an XML element as with wfElement(), but run text through the - * UtfNormal::cleanUp() validator first to ensure that no invalid UTF-8 - * is passed. - * - * @param string $element - * @param array $attribs Name=>value pairs. Values will be escaped. - * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default) - * @return string - */ -function wfElementClean( $element, $attribs = array(), $contents = '') { - if( $attribs ) { - $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs ); - } - if( $contents ) { - $contents = UtfNormal::cleanUp( $contents ); - } - return wfElement( $element, $attribs, $contents ); -} - -// Shortcuts -function wfOpenElement( $element, $attribs = null ) { return wfElement( $element, $attribs, null ); } -function wfCloseElement( $element ) { return ""; } - -/** - * Create a namespace selector - * - * @param mixed $selected The namespace which should be selected, default '' - * @param string $allnamespaces Value of a special item denoting all namespaces. Null to not include (default) - * @return Html string containing the namespace selector - */ -function &HTMLnamespaceselector($selected = '', $allnamespaces = null) { - global $wgContLang; - if( $selected !== '' ) { - if( is_null( $selected ) ) { - // No namespace selected; let exact match work without hitting Main - $selected = ''; - } else { - // Let input be numeric strings without breaking the empty match. - $selected = intval( $selected ); - } - } - $s = "\n"; - return $s; -} - /** Global singleton instance of MimeMagic. This is initialized on demand, * please always use the wfGetMimeMagic() function to get the instance. * @@ -1726,50 +1601,6 @@ function wfUrlProtocols() { } } -/** - * Check if a string is well-formed XML. - * Must include the surrounding tag. - * - * @param string $text - * @return bool - * - * @todo Error position reporting return - */ -function wfIsWellFormedXml( $text ) { - $parser = xml_parser_create( "UTF-8" ); - - # case folding violates XML standard, turn it off - xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false ); - - if( !xml_parse( $parser, $text, true ) ) { - $err = xml_error_string( xml_get_error_code( $parser ) ); - $position = xml_get_current_byte_index( $parser ); - //$fragment = $this->extractFragment( $html, $position ); - //$this->mXmlError = "$err at byte $position:\n$fragment"; - xml_parser_free( $parser ); - return false; - } - xml_parser_free( $parser ); - return true; -} - -/** - * Check if a string is a well-formed XML fragment. - * Wraps fragment in an bit and doctype, so it can be a fragment - * and can use HTML named entities. - * - * @param string $text - * @return bool - */ -function wfIsWellFormedXmlFragment( $text ) { - $html = - Sanitizer::hackDocType() . - '' . - $text . - ''; - return wfIsWellFormedXml( $html ); -} - /** * shell_exec() with time and memory limits mirrored from the PHP configuration, * if supported. diff --git a/includes/Linker.php b/includes/Linker.php index 75161bbb66..b512c4b3f4 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -736,6 +736,115 @@ class Linker { return ''.$text.''; } + /** + * Make user link (or user contributions for unregistered users) + * @param int $userId + * @param string $userText + * @return string HTML fragment + * @access private + */ + function userLink( $userId, $userText ) { + $encName = htmlspecialchars( $userText ); + if( $userId == 0 ) { + $contribsPage = Title::makeTitle( NS_SPECIAL, 'Contributions' ); + return $this->makeKnownLinkObj( $contribsPage, + $encName, 'target=' . urlencode( $userText ) ); + } else { + $userPage = Title::makeTitle( NS_USER, $userText ); + return $this->makeLinkObj( $userPage, $encName ); + } + } + + /** + * @param int $userId + * @param string $userText + * @return string HTML fragment with talk and/or block links + * @access private + */ + function userToolLinks( $userId, $userText ) { + global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans; + $talkable = !( $wgDisableAnonTalk && 0 == $userId ); + $blockable = ( $wgSysopUserBans || 0 == $userId ); + + $items = array(); + if( $talkable ) { + $items[] = $this->userTalkLink( $userId, $userText ); + } + if( $blockable && $wgUser->isAllowed( 'block' ) ) { + $items[] = $this->blockLink( $userId, $userText ); + } + + if( $items ) { + return ' (' . implode( ' | ', $items ) . ')'; + } else { + return ''; + } + } + + /** + * @param int $userId + * @param string $userText + * @return string HTML fragment with user talk link + * @access private + */ + function userTalkLink( $userId, $userText ) { + global $wgContLang; + $talkname = $wgContLang->getNsText( NS_TALK ); # use the shorter name + + $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText ); + $userTalkLink = $this->makeLinkObj( $userTalkPage, $talkname ); + return $userTalkLink; + } + + /** + * @param int $userId + * @param string $userText + * @return string HTML fragment with block link + * @access private + */ + function blockLink( $userId, $userText ) { + $blockPage = Title::makeTitle( NS_SPECIAL, 'Blockip' ); + $blockLink = $this->makeKnownLinkObj( $blockPage, + wfMsgHtml( 'blocklink' ), 'ip=' . urlencode( $userText ) ); + return $blockLink; + } + + /** + * Generate a user link if the current user is allowed to view it + * @param Revision $rev + * @return string HTML + */ + function revUserLink( $rev ) { + if( $rev->userCan( MW_REV_DELETED_USER ) ) { + $link = $this->userLink( $rev->getRawUser(), $rev->getRawUserText() ); + } else { + $link = wfMsgHtml( 'rev-deleted-user' ); + } + if( $rev->isDeleted( MW_REV_DELETED_USER ) ) { + return '' . $link . ''; + } + return $link; + } + + /** + * Generate a user tool link cluster if the current user is allowed to view it + * @param Revision $rev + * @return string HTML + */ + function revUserTools( $rev ) { + if( $rev->userCan( MW_REV_DELETED_USER ) ) { + $link = $this->userLink( $rev->getRawUser(), $rev->getRawUserText() ) . + ' ' . + $this->userToolLinks( $rev->getRawUser(), $rev->getRawUserText() ); + } else { + $link = wfMsgHtml( 'rev-deleted-user' ); + } + if( $rev->isDeleted( MW_REV_DELETED_USER ) ) { + return '' . $link . ''; + } + return $link; + } + /** * This function is called by all recent changes variants, by the page history, * and by the user contributions list. It is responsible for formatting edit @@ -823,24 +932,38 @@ class Linker { * * @param string $comment * @param Title $title - * @param bool $deleted * * @return string */ - function commentBlock( $comment, $title = NULL, $deleted = false ) { + function commentBlock( $comment, $title = NULL ) { // '*' used to be the comment inserted by the software way back // in antiquity in case none was provided, here for backwards // compatability, acc. to brion -ævar if( $comment == '' || $comment == '*' ) { return ''; } else { - if ( $deleted ) - return " (...)"; - else { - $formatted = $this->formatComment( $comment, $title ); - return " ($formatted)"; - } + $formatted = $this->formatComment( $comment, $title ); + return " ($formatted)"; + } + } + + /** + * Wrap and format the given revision's comment block, if the current + * user is allowed to view it. + * @param Revision $rev + * @return string HTML + */ + function revComment( $rev ) { + if( $rev->userCan( MW_REV_DELETED_COMMENT ) ) { + $block = $this->commentBlock( $rev->getRawComment(), $rev->getTitle() ); + } else { + $block = " " . + wfMsgHtml( 'rev-deleted-comment' ) . ""; + } + if( $rev->isDeleted( MW_REV_DELETED_COMMENT ) ) { + return " $block"; } + return $block; } /** @todo document */ diff --git a/includes/LogPage.php b/includes/LogPage.php index a1ad8c980b..b43bc8abaf 100644 --- a/includes/LogPage.php +++ b/includes/LogPage.php @@ -166,6 +166,7 @@ class LogPage { 'delete/delete' => 'deletedarticle', 'delete/restore' => 'undeletedarticle', + 'delete/revision' => 'revdelete-logentry', 'upload/upload' => 'uploadedimage', 'upload/revert' => 'uploadedimage', 'move/move' => '1movedto2', diff --git a/includes/PageHistory.php b/includes/PageHistory.php index ddfe444c81..594a547683 100644 --- a/includes/PageHistory.php +++ b/includes/PageHistory.php @@ -222,93 +222,101 @@ class PageHistory { /** @todo document */ function historyLine( $row, $next, $counter = '', $notificationtimestamp = false, $latest = false, $firstInList = false ) { - - if ( 0 == $row->rev_user ) { - $contribsPage =& Title::makeTitle( NS_SPECIAL, 'Contributions' ); - $ul = $this->mSkin->makeKnownLinkObj( $contribsPage, - htmlspecialchars( $row->rev_user_text ), - 'target=' . urlencode( $row->rev_user_text ) ); - } else { - $userPage =& Title::makeTitle( NS_USER, $row->rev_user_text ); - $ul = $this->mSkin->makeLinkObj( $userPage , htmlspecialchars( $row->rev_user_text ) ); - } + global $wgUser; + $rev = new Revision( $row ); $s = '
  • '; - /* This feature is not yet used according to schema */ - if( $row->rev_deleted ) { - $s .= ''; + $curlink = $this->curLink( $rev, $latest ); + $lastlink = $this->lastLink( $rev, $next, $counter ); + $arbitrary = $this->diffButtons( $rev, $firstInList, $counter ); + $link = $this->revLink( $rev ); + $user = $this->mSkin->revUserLink( $rev ); + + $s .= "($curlink) ($lastlink) $arbitrary"; + + if( $wgUser->isAllowed( 'deleterevision' ) ) { + $revdel = Title::makeTitle( NS_SPECIAL, 'Revisiondelete' ); + if( $firstInList ) { + // We don't currently handle well changing the top revision's settings + $del = wfMsgHtml( 'rev-delundel' ); + } else { + $del = $this->mSkin->makeKnownLinkObj( $revdel, + wfMsg( 'rev-delundel' ), + 'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) . + '&oldid=' . urlencode( $rev->getId() ) ); + } + $s .= "($del) "; } - $curlink = $this->curLink( $row, $latest ); - $lastlink = $this->lastLink( $row, $next, $counter ); - $arbitrary = $this->diffButtons( $row, $firstInList, $counter ); - $link = $this->revLink( $row ); - - $s .= "($curlink) ($lastlink) $arbitrary $link $ul"; + + $s .= " $link $user"; if( $row->rev_minor_edit ) { $s .= ' ' . wfElement( 'span', array( 'class' => 'minor' ), wfMsgHtml( 'minoreditletter') ); } - $s .= $this->mSkin->commentBlock( $row->rev_comment, $this->mTitle ); + $s .= $this->mSkin->revComment( $rev ); if ($notificationtimestamp && ($row->rev_timestamp >= $notificationtimestamp)) { $s .= ' ' . wfMsgHtml( 'updatedmarker' ) . ''; } - if( $row->rev_deleted ) { - $s .= ' ' . wfMsgHtml( 'deletedrev' ); + if( $row->rev_deleted & MW_REV_DELETED_TEXT ) { + $s .= ' ' . wfMsgHtml( 'deletedrev' ); } $s .= "
  • \n"; return $s; } - + /** @todo document */ - function revLink( $row ) { + function revLink( $rev ) { global $wgUser, $wgLang; - $date = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->rev_timestamp), true ); - if( $row->rev_deleted && !$wgUser->isAllowed( 'undelete' ) ) { - return $date; + $date = $wgLang->timeanddate( wfTimestamp(TS_MW, $rev->getTimestamp()), true ); + if( $rev->userCan( MW_REV_DELETED_TEXT ) ) { + $link = $this->mSkin->makeKnownLinkObj( + $this->mTitle, $date, "oldid=" . $rev->getId() ); } else { - return $this->mSkin->makeKnownLinkObj( - $this->mTitle, $date, "oldid={$row->rev_id}" ); + $link = $date; + } + if( $rev->isDeleted( MW_REV_DELETED_TEXT ) ) { + return '' . $link . ''; } + return $link; } /** @todo document */ - function curLink( $row, $latest ) { + function curLink( $rev, $latest ) { global $wgUser; $cur = wfMsgHtml( 'cur' ); - if( $latest - || ( $row->rev_deleted && !$wgUser->isAllowed( 'undelete' ) ) ) { + if( $latest || !$rev->userCan( MW_REV_DELETED_TEXT ) ) { return $cur; } else { return $this->mSkin->makeKnownLinkObj( $this->mTitle, $cur, 'diff=' . $this->getLatestID() . - "&oldid={$row->rev_id}" ); + "&oldid=" . $rev->getId() ); } } /** @todo document */ - function lastLink( $row, $next, $counter ) { + function lastLink( $rev, $next, $counter ) { global $wgUser; $last = htmlspecialchars( wfMsg( 'last' ) ); if( is_null( $next ) ) { - if( $row->rev_timestamp == $this->getEarliestOffset() ) { + if( $rev->getTimestamp() == $this->getEarliestOffset() ) { return $last; } else { // Cut off by paging; there are more behind us... return $this->mSkin->makeKnownLinkObj( $this->mTitle, $last, - "diff={$row->rev_id}&oldid=prev" ); + "diff=" . $rev->getId() . "&oldid=prev" ); } - } elseif( $row->rev_deleted && !$wgUser->isAllowed( 'undelete' ) ) { + } elseif( !$rev->userCan( MW_REV_DELETED_TEXT ) ) { return $last; } else { return $this->mSkin->makeKnownLinkObj( $this->mTitle, $last, - "diff={$row->rev_id}&oldid={$next->rev_id}" + "diff=" . $rev->getId() . "&oldid={$next->rev_id}" /*, '', '', @@ -317,17 +325,17 @@ class PageHistory { } /** @todo document */ - function diffButtons( $row, $firstInList, $counter ) { + function diffButtons( $rev, $firstInList, $counter ) { global $wgUser; if( $this->linesonpage > 1) { $radio = array( 'type' => 'radio', - 'value' => $row->rev_id, + 'value' => $rev->getId(), # do we really need to flood this on every item? # 'title' => wfMsgHtml( 'selectolderversionfordiff' ) ); - if( $row->rev_deleted && !$wgUser->isAllowed( 'undelete' ) ) { + if( !$rev->userCan( MW_REV_DELETED_TEXT ) ) { $radio['disabled'] = 'disabled'; } @@ -447,7 +455,7 @@ class PageHistory { $res = $dbr->select( 'revision', - array('rev_id', 'rev_user', 'rev_comment', 'rev_user_text', + array('rev_id', 'rev_page', 'rev_text_id', 'rev_user', 'rev_comment', 'rev_user_text', 'rev_timestamp', 'rev_minor_edit', 'rev_deleted'), array_merge(array("rev_page=$page_id"), $offsets), $fname, diff --git a/includes/RawPage.php b/includes/RawPage.php index 119f0c9d2f..d7de77c1ef 100644 --- a/includes/RawPage.php +++ b/includes/RawPage.php @@ -163,7 +163,7 @@ class RawPage { if ( $rev ) { $lastmod = wfTimestamp( TS_RFC2822, $rev->getTimestamp() ); header( "Last-modified: $lastmod" ); - $text = $rev->isDeleted() ? '' : $rev->getText(); + $text = $rev->getText(); } else $text = ''; } diff --git a/includes/Revision.php b/includes/Revision.php index 7908d56436..d0eabfc2b5 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -8,6 +8,13 @@ require_once( 'Database.php' ); require_once( 'Article.php' ); +/** @+ */ +define( 'MW_REV_DELETED_TEXT', 1 ); +define( 'MW_REV_DELETED_COMMENT', 2 ); +define( 'MW_REV_DELETED_USER', 4 ); +define( 'MW_REV_DELETED_RESTRICTED', 8 ); +/** @- */ + /** * @package MediaWiki * @todo document @@ -246,9 +253,14 @@ class Revision { $this->mTimestamp = $row->rev_timestamp; $this->mDeleted = intval( $row->rev_deleted ); - $this->mCurrent = ( $row->rev_id == $row->page_latest ); - $this->mTitle = Title::makeTitle( $row->page_namespace, - $row->page_title ); + if( isset( $row->page_latest ) ) { + $this->mCurrent = ( $row->rev_id == $row->page_latest ); + $this->mTitle = Title::makeTitle( $row->page_namespace, + $row->page_title ); + } else { + $this->mCurrent = false; + $this->mTitle = null; + } if( isset( $row->old_text ) ) { $this->mText = $this->getRevisionText( $row ); @@ -327,23 +339,62 @@ class Revision { } /** + * Fetch revision's user id if it's available to all users * @return int */ function getUser() { + if( $this->isDeleted( MW_REV_DELETED_USER ) ) { + return 0; + } else { + return $this->mUser; + } + } + + /** + * Fetch revision's user id without regard for the current user's permissions + * @return string + */ + function getRawUser() { return $this->mUser; } /** + * Fetch revision's username if it's available to all users * @return string */ function getUserText() { - return $this->mUserText; + if( $this->isDeleted( MW_REV_DELETED_USER ) ) { + return ""; + } else { + return $this->mUserText; + } } /** + * Fetch revision's username without regard for view restrictions + * @return string + */ + function getRawUserText() { + return $this->mUserText; + } + + /** + * Fetch revision comment if it's available to all users * @return string */ function getComment() { + if( $this->isDeleted( MW_REV_DELETED_COMMENT ) ) { + return ""; + } else { + return $this->mComment; + } + } + + /** + * Fetch revision comment without regard for the current user's permissions + * @return string + */ + function getRawComment() { return $this->mComment; } @@ -355,16 +406,30 @@ class Revision { } /** + * int $field one of MW_REV_DELETED_* bitfield constants * @return bool */ - function isDeleted() { - return (bool)$this->mDeleted; + function isDeleted( $field ) { + return ($this->mDeleted & $field) == $field; } /** + * Fetch revision text if it's available to all users * @return string */ function getText() { + if( $this->isDeleted( MW_REV_DELETED_TEXT ) ) { + return ""; + } else { + return $this->getRawText(); + } + } + + /** + * Fetch revision text without regard for view restrictions + * @return string + */ + function getRawText() { if( is_null( $this->mText ) ) { // Revision text is immutable. Load on demand: $this->mText = $this->loadText(); @@ -650,6 +715,28 @@ class Revision { wfProfileOut( $fname ); return $revision; } + + /** + * Determine if the current user is allowed to view a particular + * field of this revision, if it's marked as deleted. + * @param int $field one of MW_REV_DELETED_TEXT, + * MW_REV_DELETED_COMMENT, + * MW_REV_DELETED_USER + * @return bool + */ + function userCan( $field ) { + if( ( $this->mDeleted & $field ) == $field ) { + global $wgUser; + $permission = ( $this->mDeleted & MW_REV_DELETED_RESTRICTED ) == MW_REV_DELETED_RESTRICTED + ? 'hiderevision' + : 'deleterevision'; + wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" ); + return $wgUser->isAllowed( $permission ); + } else { + return true; + } + } } + ?> diff --git a/includes/SpecialContributions.php b/includes/SpecialContributions.php index 34b5d2e07d..f627ca6825 100644 --- a/includes/SpecialContributions.php +++ b/includes/SpecialContributions.php @@ -128,7 +128,7 @@ class contribs_finder { $use_index = $this->dbr->useIndexClause($index); $sql = "SELECT page_namespace,page_title,page_is_new,page_latest, - rev_id,rev_timestamp,rev_comment,rev_minor_edit,rev_user_text, + rev_id,rev_page,rev_text_id,rev_timestamp,rev_comment,rev_minor_edit,rev_user,rev_user_text, rev_deleted FROM $page,$revision $use_index WHERE page_id=rev_page AND $userCond $nscond $offsetQuery @@ -358,8 +358,10 @@ function ucListEdit( $sk, $row ) { } } - $page =& Title::makeTitle( $row->page_namespace, $row->page_title ); - $link = $sk->makeKnownLinkObj( $page, '' ); + $rev = new Revision( $row ); + + $page = Title::makeTitle( $row->page_namespace, $row->page_title ); + $link = $sk->makeKnownLinkObj( $page ); $difftext = $topmarktext = ''; if( $row->rev_id == $row->page_latest ) { $topmarktext .= '' . $messages['uctop'] . ''; @@ -379,15 +381,19 @@ function ucListEdit( $sk, $row ) { } } - if( $row->rev_deleted && !$wgUser->isAllowed( 'delete' ) ) { - $difftext = '(' . $messages['diff'] . ')'; - } else { + if( $rev->userCan( MW_REV_DELETED_TEXT ) ) { $difftext = '(' . $sk->makeKnownLinkObj( $page, $messages['diff'], 'diff=prev&oldid='.$row->rev_id ) . ')'; + } else { + $difftext = '(' . $messages['diff'] . ')'; } $histlink='('.$sk->makeKnownLinkObj( $page, $messages['hist'], 'action=history' ) . ')'; - $comment = $sk->commentBlock( $row->rev_comment, $page, (bool)$row->rev_deleted ); + $comment = $sk->revComment( $rev ); $d = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->rev_timestamp), true ); + + if( $rev->isDeleted( MW_REV_DELETED_TEXT ) ) { + $d = '' . $d . ''; + } if( $row->rev_minor_edit ) { $mflag = '' . $messages['minoreditletter'] . ' '; @@ -396,8 +402,8 @@ function ucListEdit( $sk, $row ) { } $ret = "{$d} {$histlink} {$difftext} {$mflag} {$link} {$comment} {$topmarktext}"; - if( $row->rev_deleted ) { - $ret = "$ret"; + if( $rev->isDeleted( MW_REV_DELETED_TEXT ) ) { + $ret .= ' ' . wfMsgHtml( 'deletedrev' ); } $ret = "
  • $ret
  • \n"; wfProfileOut( $fname ); diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php index 17388ac546..eb643411d2 100644 --- a/includes/SpecialPage.php +++ b/includes/SpecialPage.php @@ -77,7 +77,8 @@ $wgSpecialPages = array( 'Userrights' => new SpecialPage( 'Userrights', 'userrights' ), 'MIMEsearch' => new SpecialPage( 'MIMEsearch' ), 'Unwatchedpages' => new SpecialPage( 'Unwatchedpages', 'unwatchedpages' ), - 'Listredirects' => new SpecialPage( 'Listredirects' ) + 'Listredirects' => new SpecialPage( 'Listredirects' ), + 'Revisiondelete' => new SpecialPage( 'Revisiondelete', 'deleterevision' ), ); if( !$wgDisableCounters ) { diff --git a/includes/SpecialRevisiondelete.php b/includes/SpecialRevisiondelete.php new file mode 100644 index 0000000000..51ff68111b --- /dev/null +++ b/includes/SpecialRevisiondelete.php @@ -0,0 +1,258 @@ +getVal( 'target' ); + $oldid = $wgRequest->getInt( 'oldid' ); + + $sk = $wgUser->getSkin(); + $page = Title::newFromUrl( $target ); + + if( is_null( $page ) ) { + $wgOut->errorpage( 'notargettitle', 'notargettext' ); + return; + } + + $form = new RevisionDeleteForm( $wgRequest ); + if( $wgRequest->wasPosted() ) { + $form->submit( $wgRequest ); + } else { + $form->show( $wgRequest ); + } +} + +class RevisionDeleteForm { + /** + * @param Title $page + * @param int $oldid + */ + function __construct( $request ) { + global $wgUser; + + $target = $request->getVal( 'target' ); + $this->page = Title::newFromUrl( $target ); + + $this->revisions = $request->getIntArray( 'oldid', array() ); + + $this->skin = $wgUser->getSkin(); + $this->checks = array( + array( 'revdelete-hide-text', 'wpHideText', MW_REV_DELETED_TEXT ), + array( 'revdelete-hide-comment', 'wpHideComment', MW_REV_DELETED_COMMENT ), + array( 'revdelete-hide-user', 'wpHideUser', MW_REV_DELETED_USER ), + array( 'revdelete-hide-restricted', 'wpHideRestricted', MW_REV_DELETED_RESTRICTED ) ); + } + + /** + * @param WebRequest $request + */ + function show( $request ) { + global $wgOut, $wgUser; + + $first = $this->revisions[0]; + + $wgOut->addWikiText( wfMsg( 'revdelete-selected', $this->page->getPrefixedText() ) ); + + $wgOut->addHtml( "
      " ); + foreach( $this->revisions as $revid ) { + $rev = Revision::newFromTitle( $this->page, $revid ); + $wgOut->addHtml( $this->historyLine( $rev ) ); + $bitfields[] = $rev->mDeleted; // FIXME + } + $wgOut->addHtml( "
    " ); + + $wgOut->addWikiText( wfMsg( 'revdelete-text' ) ); + + $items = array( + wfInputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ), + wfSubmitButton( wfMsg( 'revdelete-submit' ) ) ); + $hidden = array( + wfHidden( 'wpEditToken', $wgUser->editToken() ), + wfHidden( 'target', $this->page->getPrefixedText() ) ); + foreach( $this->revisions as $revid ) { + $hidden[] = wfHidden( 'oldid[]', $revid ); + } + + $special = Title::makeTitle( NS_SPECIAL, 'Revisiondelete' ); + $wgOut->addHtml( wfElement( 'form', array( + 'method' => 'post', + 'action' => $special->getLocalUrl( 'action=submit' ) ) ) ); + + $wgOut->addHtml( '
    ' . wfMsgHtml( 'revdelete-legend' ) . '' ); + foreach( $this->checks as $item ) { + list( $message, $name, $field ) = $item; + $wgOut->addHtml( '
    ' . + wfCheckLabel( wfMsg( $message), $name, $name, $rev->isDeleted( $field ) ) . + '
    ' ); + } + $wgOut->addHtml( '
    ' ); + foreach( $items as $item ) { + $wgOut->addHtml( '

    ' . $item . '

    ' ); + } + foreach( $hidden as $item ) { + $wgOut->addHtml( $item ); + } + + $wgOut->addHtml( '' ); + } + + /** + * @param Revision $rev + * @returns string + */ + private function historyLine( $rev ) { + global $wgContLang; + $date = $wgContLang->timeanddate( $rev->getTimestamp() ); + return + "
  • " . + $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() ) . + " " . + $this->skin->revUserLink( $rev ) . + " " . + $this->skin->revComment( $rev ) . + "
  • "; + } + + /** + * @param WebRequest $request + */ + 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 ); + } + } + + function success( $request ) { + global $wgOut; + $wgOut->addWikiText( 'woo' ); + } + + /** + * Put together a rev_deleted bitfield from the submitted checkboxes + * @param WebRequest $request + * @return int + */ + function extractBitfield( $request ) { + $bitfield = 0; + foreach( $this->checks as $item ) { + list( $message, $name, $field ) = $item; + if( $request->getCheck( $name ) ) { + $bitfield |= $field; + } + } + return $bitfield; + } + + function save( $bitfield, $reason ) { + $dbw = wfGetDB( DB_MASTER ); + $deleter = new RevisionDeleter( $dbw ); + $ok = $deleter->setVisibility( $this->revisions, $bitfield, $reason ); + } +} + + +class RevisionDeleter { + function __construct( $db ) { + $this->db = $db; + } + + /** + * @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(); + + // To work! + foreach( $items as $revid ) { + $rev = Revision::newFromId( $revid ); + $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]++; + } else { + $pages[$pageid] = 1; + } + } + + // Clear caches... + foreach( $pages as $pageid => $count ) { + $title = Title::newFromId( $pageid ); + $this->updatePage( $title ); + $this->updateLog( $title, $count, $bitfield, $comment ); + } + + return true; + } + + /** + * Update the revision's rev_deleted field + * @param Revision $rev + * @param int $bitfield new rev_deleted bitfield value + */ + function updateRevision( $rev, $bitfield ) { + $this->db->update( 'revision', + array( 'rev_deleted' => $bitfield ), + array( 'rev_id' => $rev->getId() ), + 'RevisionDeleter::updateRevision' ); + } + + /** + * 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 & MW_REV_DELETED_USER) ? 0 : $rev->getUser(), + 'rc_user_text' => ($bitfield & MW_REV_DELETED_USER) ? wfMsg( 'rev-deleted-user' ) : $rev->getUserText(), + 'rc_comment' => ($bitfield & MW_REV_DELETED_COMMENT) ? wfMsg( 'rev-deleted-comment' ) : $rev->getComment() ), + array( + 'rc_this_oldid' => $rev->getId() ), + 'RevisionDeleter::updateRecentChanges' ); + } + + /** + * Touch the page's cache invalidation timestamp; this forces cached + * history views to refresh, so any newly hidden or shown fields will + * update properly. + * @param Title $title + */ + function updatePage( $title ) { + $title->invalidateCache(); + } + + /** + * Record a log entry on the action + * @param Title $title + * @param int $count the number of revisions altered for this page + * @param int $bitfield the new rev_deleted value + * @param string $comment + */ + function updateLog( $title, $count, $bitfield, $comment ) { + $log = new LogPage( 'delete' ); + $reason = "changed $count revisions to $bitfield"; + $reason .= ": $comment"; + $log->addEntry( 'revision', $title, $reason ); + } +} + +?> diff --git a/includes/WebRequest.php b/includes/WebRequest.php index 03939de08d..a0d8b13355 100644 --- a/includes/WebRequest.php +++ b/includes/WebRequest.php @@ -162,6 +162,24 @@ class WebRequest { return (array)$val; } } + + /** + * Fetch an array of integers, or return $default if it's not set. + * If source was scalar, will return an array with a single element. + * If no source and no default, returns NULL. + * If an array is returned, contents are guaranteed to be integers. + * + * @param string $name + * @param array $default option default (or NULL) + * @return array of ints + */ + function getIntArray( $name, $default = NULL ) { + $val = $this->getArray( $name, $default ); + if( is_array( $val ) ) { + $val = array_map( 'intval', $val ); + } + return $val; + } /** * Fetch an integer value from the input or return $default if not set. diff --git a/includes/XmlFunctions.php b/includes/XmlFunctions.php new file mode 100644 index 0000000000..78648031f8 --- /dev/null +++ b/includes/XmlFunctions.php @@ -0,0 +1,273 @@ +, &) are escaped but illegals are not touched. + * + * @param string $element + * @param array $attribs Name=>value pairs. Values will be escaped. + * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default) + * @return string + */ +function wfElement( $element, $attribs = null, $contents = '') { + $out = '<' . $element; + if( !is_null( $attribs ) ) { + foreach( $attribs as $name => $val ) { + $out .= ' ' . $name . '="' . htmlspecialchars( $val ) . '"'; + } + } + if( is_null( $contents ) ) { + $out .= '>'; + } else { + if( $contents == '' ) { + $out .= ' />'; + } else { + $out .= '>' . htmlspecialchars( $contents ) . ""; + } + } + return $out; +} + +/** + * Format an XML element as with wfElement(), but run text through the + * UtfNormal::cleanUp() validator first to ensure that no invalid UTF-8 + * is passed. + * + * @param string $element + * @param array $attribs Name=>value pairs. Values will be escaped. + * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default) + * @return string + */ +function wfElementClean( $element, $attribs = array(), $contents = '') { + if( $attribs ) { + $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs ); + } + if( $contents ) { + $contents = UtfNormal::cleanUp( $contents ); + } + return wfElement( $element, $attribs, $contents ); +} + +// Shortcuts +function wfOpenElement( $element, $attribs = null ) { return wfElement( $element, $attribs, null ); } +function wfCloseElement( $element ) { return ""; } + +/** + * Create a namespace selector + * + * @param mixed $selected The namespace which should be selected, default '' + * @param string $allnamespaces Value of a special item denoting all namespaces. Null to not include (default) + * @param bool $includehidden Include hidden namespaces? + * @return Html string containing the namespace selector + */ +function &HTMLnamespaceselector($selected = '', $allnamespaces = null, $includehidden=false) { + global $wgContLang; + if( $selected !== '' ) { + if( is_null( $selected ) ) { + // No namespace selected; let exact match work without hitting Main + $selected = ''; + } else { + // Let input be numeric strings without breaking the empty match. + $selected = intval( $selected ); + } + } + $s = "\n"; + return $s; +} + +function wfSpan( $text, $class, $attribs=array() ) { + return wfElement( 'span', array( 'class' => $class ) + $attribs, $text ); +} + +/** + * Convenience function to build an HTML text input field + * @return string HTML + */ +function wfInput( $name, $size=false, $value=false, $attribs=array() ) { + return wfElement( 'input', array( + 'name' => $name, + 'size' => $size, + 'value' => $value ) + $attribs ); +} + +/** + * Internal function for use in checkboxes and radio buttons and such. + * @return array + */ +function wfAttrib( $name, $present = true ) { + return $present ? array( $name => $name ) : array(); +} + +/** + * Convenience function to build an HTML checkbox + * @return string HTML + */ +function wfCheck( $name, $checked=false, $attribs=array() ) { + return wfElement( 'input', array( + 'name' => $name, + 'type' => 'checkbox', + 'value' => 1 ) + wfAttrib( 'checked', $checked ) + $attribs ); +} + +/** + * Convenience function to build an HTML radio button + * @return string HTML + */ +function wfRadio( $name, $value, $checked=false, $attribs=array() ) { + return wfElement( 'input', array( + 'name' => $name, + 'type' => 'radio', + 'value' => $value ) + wfAttrib( 'checked', $checked ) + $attribs ); +} + +/** + * Convenience function to build an HTML form label + * @return string HTML + */ +function wfLabel( $label, $id ) { + return wfElement( 'label', array( 'for' => $id ), $label ); +} + +/** + * Convenience function to build an HTML text input field with a label + * @return string HTML + */ +function wfInputLabel( $label, $name, $id, $size=false, $value=false, $attribs=array() ) { + return wfLabel( $label, $id ) . + ' ' . + wfInput( $name, $size, $value, array( 'id' => $id ) + $attribs ); +} + +/** + * Convenience function to build an HTML checkbox with a label + * @return string HTML + */ +function wfCheckLabel( $label, $name, $id, $checked=false, $attribs=array() ) { + return wfCheck( $name, $checked, array( 'id' => $id ) + $attribs ) . + ' ' . + wfLabel( $label, $id ); +} + +/** + * Convenience function to build an HTML radio button with a label + * @return string HTML + */ +function wfRadioLabel( $label, $name, $value, $id, $checked=false, $attribs=array() ) { + return wfRadio( $name, $checked, $value, array( 'id' => $id ) + $attribs ) . + ' ' . + wfLabel( $label, $id ); +} + +/** + * Convenience function to build an HTML submit button + * @param string $value Label text for the button + * @param array $attribs optional custom attributes + * @return string HTML + */ +function wfSubmitButton( $value, $attribs=array() ) { + return wfElement( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs ); +} + +/** + * Convenience function to build an HTML hidden form field + * @param string $value Label text for the button + * @param array $attribs optional custom attributes + * @return string HTML + */ +function wfHidden( $name, $value, $attribs=array() ) { + return wfElement( 'input', array( + 'name' => $name, + 'type' => 'hidden', + 'value' => $value ) + $attribs ); +} + +/** + * Returns an escaped string suitable for inclusion in a string literal + * for JavaScript source code. + * Illegal control characters are assumed not to be present. + * + * @param string $string + * @return string + */ +function wfEscapeJsString( $string ) { + // See ECMA 262 section 7.8.4 for string literal format + $pairs = array( + "\\" => "\\\\", + "\"" => "\\\"", + '\'' => '\\\'', + "\n" => "\\n", + "\r" => "\\r", + + # To avoid closing the element or CDATA section + "<" => "\\x3c", + ">" => "\\x3e", + ); + return strtr( $string, $pairs ); +} + +/** + * Check if a string is well-formed XML. + * Must include the surrounding tag. + * + * @param string $text + * @return bool + * + * @todo Error position reporting return + */ +function wfIsWellFormedXml( $text ) { + $parser = xml_parser_create( "UTF-8" ); + + # case folding violates XML standard, turn it off + xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false ); + + if( !xml_parse( $parser, $text, true ) ) { + $err = xml_error_string( xml_get_error_code( $parser ) ); + $position = xml_get_current_byte_index( $parser ); + //$fragment = $this->extractFragment( $html, $position ); + //$this->mXmlError = "$err at byte $position:\n$fragment"; + xml_parser_free( $parser ); + return false; + } + xml_parser_free( $parser ); + return true; +} + +/** + * Check if a string is a well-formed XML fragment. + * Wraps fragment in an bit and doctype, so it can be a fragment + * and can use HTML named entities. + * + * @param string $text + * @return bool + */ +function wfIsWellFormedXmlFragment( $text ) { + $html = + Sanitizer::hackDocType() . + '' . + $text . + ''; + return wfIsWellFormedXml( $html ); +} + + +?> \ No newline at end of file diff --git a/languages/Messages.php b/languages/Messages.php index aa2a428407..248d2e3d5e 100644 --- a/languages/Messages.php +++ b/languages/Messages.php @@ -565,6 +565,38 @@ Legend: (cur) = difference with current version, 'deletedrev' => '[deleted]', 'histfirst' => 'Earliest', 'histlast' => 'Latest', +'rev-deleted-comment' => '(comment removed)', +'rev-deleted-user' => '(username removed)', +'rev-deleted-text-permission' => '', +'rev-deleted-text-view' => '', +#'rev-delundel' => 'del/undel', +'rev-delundel' => 'show/hide', + +# Revision deletion +# +'revisiondelete' => 'Delete/undelete revisions', +'revdelete-selected' => 'Selected revision of [[:$1]]:', +'revdelete-text' => "Deleted revisions will still appear in the page history, +but their text contents will be inaccessible to the public. + +Other admins on this wiki will still be able to access the hidden content and can +undelete it again through this same interface, unless an additional restriction +is placed by the site operators.", +'revdelete-legend' => 'Set revision restrictions:', +'revdelete-hide-text' => 'Hide revision text', +'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-log' => 'Log comment:', +'revdelete-submit' => 'Apply to selected revision', +'revdelete-logentry' => 'changed revision visibility for [[$1]]', # Diffs # diff --git a/skins/monobook/main.css b/skins/monobook/main.css index 4c67d07e83..bea3f64d91 100644 --- a/skins/monobook/main.css +++ b/skins/monobook/main.css @@ -341,7 +341,8 @@ table.small { */ #toc, -.toc { +.toc, +.mw-warning { border: 1px solid #aaa; background-color: #f9f9f9; padding: 5px; @@ -378,6 +379,11 @@ table.small { font-size: 94%; } +.mw-warning { + margin-left: 50px; + margin-right: 50px; + text-align: center; +} /* images */ div.floatright, table.floatright {