r13224 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r13223‎ | r13224 | r13225 >
Date:19:04, 16 March 2006
Author:vibber
Status:old
Tags:
Comment:
* 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.
Modified paths:
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/includes/Article.php (modified) (history)
  • /trunk/phase3/includes/ChangesList.php (modified) (history)
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/DifferenceEngine.php (modified) (history)
  • /trunk/phase3/includes/Export.php (modified) (history)
  • /trunk/phase3/includes/GlobalFunctions.php (modified) (history)
  • /trunk/phase3/includes/Linker.php (modified) (history)
  • /trunk/phase3/includes/LogPage.php (modified) (history)
  • /trunk/phase3/includes/PageHistory.php (modified) (history)
  • /trunk/phase3/includes/RawPage.php (modified) (history)
  • /trunk/phase3/includes/Revision.php (modified) (history)
  • /trunk/phase3/includes/SpecialContributions.php (modified) (history)
  • /trunk/phase3/includes/SpecialPage.php (modified) (history)
  • /trunk/phase3/includes/SpecialRevisiondelete.php (added) (history)
  • /trunk/phase3/includes/WebRequest.php (modified) (history)
  • /trunk/phase3/includes/XmlFunctions.php (added) (history)
  • /trunk/phase3/languages/Messages.php (modified) (history)
  • /trunk/phase3/skins/monobook/main.css (modified) (history)

Diff [purge]

Index: trunk/phase3/skins/monobook/main.css
@@ -341,7 +341,8 @@
342342 */
343343
344344 #toc,
345 -.toc {
 345+.toc,
 346+.mw-warning {
346347 border: 1px solid #aaa;
347348 background-color: #f9f9f9;
348349 padding: 5px;
@@ -378,6 +379,11 @@
379380 font-size: 94%;
380381 }
381382
 383+.mw-warning {
 384+ margin-left: 50px;
 385+ margin-right: 50px;
 386+ text-align: center;
 387+}
382388
383389 /* images */
384390 div.floatright, table.floatright {
Index: trunk/phase3/includes/XmlFunctions.php
@@ -0,0 +1,273 @@
 2+<?php
 3+
 4+/**
 5+ * Format an XML element with given attributes and, optionally, text content.
 6+ * Element and attribute names are assumed to be ready for literal inclusion.
 7+ * Strings are assumed to not contain XML-illegal characters; special
 8+ * characters (<, >, &) are escaped but illegals are not touched.
 9+ *
 10+ * @param string $element
 11+ * @param array $attribs Name=>value pairs. Values will be escaped.
 12+ * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
 13+ * @return string
 14+ */
 15+function wfElement( $element, $attribs = null, $contents = '') {
 16+ $out = '<' . $element;
 17+ if( !is_null( $attribs ) ) {
 18+ foreach( $attribs as $name => $val ) {
 19+ $out .= ' ' . $name . '="' . htmlspecialchars( $val ) . '"';
 20+ }
 21+ }
 22+ if( is_null( $contents ) ) {
 23+ $out .= '>';
 24+ } else {
 25+ if( $contents == '' ) {
 26+ $out .= ' />';
 27+ } else {
 28+ $out .= '>' . htmlspecialchars( $contents ) . "</$element>";
 29+ }
 30+ }
 31+ return $out;
 32+}
 33+
 34+/**
 35+ * Format an XML element as with wfElement(), but run text through the
 36+ * UtfNormal::cleanUp() validator first to ensure that no invalid UTF-8
 37+ * is passed.
 38+ *
 39+ * @param string $element
 40+ * @param array $attribs Name=>value pairs. Values will be escaped.
 41+ * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
 42+ * @return string
 43+ */
 44+function wfElementClean( $element, $attribs = array(), $contents = '') {
 45+ if( $attribs ) {
 46+ $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
 47+ }
 48+ if( $contents ) {
 49+ $contents = UtfNormal::cleanUp( $contents );
 50+ }
 51+ return wfElement( $element, $attribs, $contents );
 52+}
 53+
 54+/ Shortcuts
 55+function wfOpenElement( $element, $attribs = null ) { return wfElement( $element, $attribs, null ); }
 56+function wfCloseElement( $element ) { return "</$element>"; }
 57+
 58+/**
 59+ * Create a namespace selector
 60+ *
 61+ * @param mixed $selected The namespace which should be selected, default ''
 62+ * @param string $allnamespaces Value of a special item denoting all namespaces. Null to not include (default)
 63+ * @param bool $includehidden Include hidden namespaces?
 64+ * @return Html string containing the namespace selector
 65+ */
 66+function &HTMLnamespaceselector($selected = '', $allnamespaces = null, $includehidden=false) {
 67+ global $wgContLang;
 68+ if( $selected !== '' ) {
 69+ if( is_null( $selected ) ) {
 70+ / No namespace selected; let exact match work without hitting Main
 71+ $selected = '';
 72+ } else {
 73+ / Let input be numeric strings without breaking the empty match.
 74+ $selected = intval( $selected );
 75+ }
 76+ }
 77+ $s = "<select name='namespace' class='namespaceselector'>\n\t";
 78+ $arr = $wgContLang->getFormattedNamespaces();
 79+ if( !is_null($allnamespaces) ) {
 80+ $arr = array($allnamespaces => wfMsgHtml('namespacesall')) + $arr;
 81+ }
 82+ foreach ($arr as $index => $name) {
 83+ if ($index < NS_MAIN) continue;
 84+
 85+ $name = $index !== 0 ? $name : wfMsgHtml('blanknamespace');
 86+
 87+ if ($index === $selected) {
 88+ $s .= wfElement("option",
 89+ array("value" => $index, "selected" => "selected"),
 90+ $name);
 91+ } else {
 92+ $s .= wfElement("option", array("value" => $index), $name);
 93+ }
 94+ }
 95+ $s .= "\n</select>\n";
 96+ return $s;
 97+}
 98+
 99+function wfSpan( $text, $class, $attribs=array() ) {
 100+ return wfElement( 'span', array( 'class' => $class ) + $attribs, $text );
 101+}
 102+
 103+/**
 104+ * Convenience function to build an HTML text input field
 105+ * @return string HTML
 106+ */
 107+function wfInput( $name, $size=false, $value=false, $attribs=array() ) {
 108+ return wfElement( 'input', array(
 109+ 'name' => $name,
 110+ 'size' => $size,
 111+ 'value' => $value ) + $attribs );
 112+}
 113+
 114+/**
 115+ * Internal function for use in checkboxes and radio buttons and such.
 116+ * @return array
 117+ */
 118+function wfAttrib( $name, $present = true ) {
 119+ return $present ? array( $name => $name ) : array();
 120+}
 121+
 122+/**
 123+ * Convenience function to build an HTML checkbox
 124+ * @return string HTML
 125+ */
 126+function wfCheck( $name, $checked=false, $attribs=array() ) {
 127+ return wfElement( 'input', array(
 128+ 'name' => $name,
 129+ 'type' => 'checkbox',
 130+ 'value' => 1 ) + wfAttrib( 'checked', $checked ) + $attribs );
 131+}
 132+
 133+/**
 134+ * Convenience function to build an HTML radio button
 135+ * @return string HTML
 136+ */
 137+function wfRadio( $name, $value, $checked=false, $attribs=array() ) {
 138+ return wfElement( 'input', array(
 139+ 'name' => $name,
 140+ 'type' => 'radio',
 141+ 'value' => $value ) + wfAttrib( 'checked', $checked ) + $attribs );
 142+}
 143+
 144+/**
 145+ * Convenience function to build an HTML form label
 146+ * @return string HTML
 147+ */
 148+function wfLabel( $label, $id ) {
 149+ return wfElement( 'label', array( 'for' => $id ), $label );
 150+}
 151+
 152+/**
 153+ * Convenience function to build an HTML text input field with a label
 154+ * @return string HTML
 155+ */
 156+function wfInputLabel( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
 157+ return wfLabel( $label, $id ) .
 158+ '&nbsp;' .
 159+ wfInput( $name, $size, $value, array( 'id' => $id ) + $attribs );
 160+}
 161+
 162+/**
 163+ * Convenience function to build an HTML checkbox with a label
 164+ * @return string HTML
 165+ */
 166+function wfCheckLabel( $label, $name, $id, $checked=false, $attribs=array() ) {
 167+ return wfCheck( $name, $checked, array( 'id' => $id ) + $attribs ) .
 168+ '&nbsp;' .
 169+ wfLabel( $label, $id );
 170+}
 171+
 172+/**
 173+ * Convenience function to build an HTML radio button with a label
 174+ * @return string HTML
 175+ */
 176+function wfRadioLabel( $label, $name, $value, $id, $checked=false, $attribs=array() ) {
 177+ return wfRadio( $name, $checked, $value, array( 'id' => $id ) + $attribs ) .
 178+ '&nbsp;' .
 179+ wfLabel( $label, $id );
 180+}
 181+
 182+/**
 183+ * Convenience function to build an HTML submit button
 184+ * @param string $value Label text for the button
 185+ * @param array $attribs optional custom attributes
 186+ * @return string HTML
 187+ */
 188+function wfSubmitButton( $value, $attribs=array() ) {
 189+ return wfElement( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
 190+}
 191+
 192+/**
 193+ * Convenience function to build an HTML hidden form field
 194+ * @param string $value Label text for the button
 195+ * @param array $attribs optional custom attributes
 196+ * @return string HTML
 197+ */
 198+function wfHidden( $name, $value, $attribs=array() ) {
 199+ return wfElement( 'input', array(
 200+ 'name' => $name,
 201+ 'type' => 'hidden',
 202+ 'value' => $value ) + $attribs );
 203+}
 204+
 205+/**
 206+ * Returns an escaped string suitable for inclusion in a string literal
 207+ * for JavaScript source code.
 208+ * Illegal control characters are assumed not to be present.
 209+ *
 210+ * @param string $string
 211+ * @return string
 212+ */
 213+function wfEscapeJsString( $string ) {
 214+ / See ECMA 262 section 7.8.4 for string literal format
 215+ $pairs = array(
 216+ "\\" => "\\\\",
 217+ "\"" => "\\\"",
 218+ '\'' => '\\\'',
 219+ "\n" => "\\n",
 220+ "\r" => "\\r",
 221+
 222+ # To avoid closing the element or CDATA section
 223+ "<" => "\\x3c",
 224+ ">" => "\\x3e",
 225+ );
 226+ return strtr( $string, $pairs );
 227+}
 228+
 229+/**
 230+ * Check if a string is well-formed XML.
 231+ * Must include the surrounding tag.
 232+ *
 233+ * @param string $text
 234+ * @return bool
 235+ *
 236+ * @todo Error position reporting return
 237+ */
 238+function wfIsWellFormedXml( $text ) {
 239+ $parser = xml_parser_create( "UTF-8" );
 240+
 241+ # case folding violates XML standard, turn it off
 242+ xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
 243+
 244+ if( !xml_parse( $parser, $text, true ) ) {
 245+ $err = xml_error_string( xml_get_error_code( $parser ) );
 246+ $position = xml_get_current_byte_index( $parser );
 247+ /$fragment = $this->extractFragment( $html, $position );
 248+ /$this->mXmlError = "$err at byte $position:\n$fragment";
 249+ xml_parser_free( $parser );
 250+ return false;
 251+ }
 252+ xml_parser_free( $parser );
 253+ return true;
 254+}
 255+
 256+/**
 257+ * Check if a string is a well-formed XML fragment.
 258+ * Wraps fragment in an <html> bit and doctype, so it can be a fragment
 259+ * and can use HTML named entities.
 260+ *
 261+ * @param string $text
 262+ * @return bool
 263+ */
 264+function wfIsWellFormedXmlFragment( $text ) {
 265+ $html =
 266+ Sanitizer::hackDocType() .
 267+ '<html>' .
 268+ $text .
 269+ '</html>';
 270+ return wfIsWellFormedXml( $html );
 271+}
 272+
 273+
 274+?>
\ No newline at end of file
Property changes on: trunk/phase3/includes/XmlFunctions.php
___________________________________________________________________
Added: svn:eol-style
1275 + native
Added: svn:keywords
2276 + Author Date Id Revision
Index: trunk/phase3/includes/Article.php
@@ -495,7 +495,10 @@
496496 }
497497 }
498498
499 - $this->mContent = $revision->getText();
 499+ / FIXME: Horrible, horrible! This content-loading interface just plain sucks.
 500+ / We should instead work with the Revision object when we need it...
 501+ $this->mContent = $revision->userCan( MW_REV_DELETED_TEXT ) ? $revision->getRawText() : "";
 502+ /$this->mContent = $revision->getText();
500503
501504 $this->mUser = $revision->getUser();
502505 $this->mUserText = $revision->getUserText();
@@ -767,7 +770,7 @@
768771 wfProfileOut( $fname );
769772 return;
770773 }
771 -
 774+
772775 if ( empty( $oldid ) && $this->checkTouched() ) {
773776 $wgOut->setETag($parserCache->getETag($this, $wgUser));
774777
@@ -846,7 +849,18 @@
847850
848851 if ( !empty( $oldid ) ) {
849852 $this->setOldSubtitle( isset($this->mOldId) ? $this->mOldId : $oldid );
850 - $wgOut->setRobotpolicy( 'noindex,follow' );
 853+ $wgOut->setRobotpolicy( 'noindex,nofollow' );
 854+ if( $this->mRevision->isDeleted( MW_REV_DELETED_TEXT ) ) {
 855+ if( !$this->mRevision->userCan( MW_REV_DELETED_TEXT ) ) {
 856+ $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) );
 857+ $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
 858+ return;
 859+ } else {
 860+ $wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) );
 861+ / and we are allowed to see...
 862+ }
 863+ }
 864+
851865 }
852866 }
853867 if( !$outputDone ) {
Index: trunk/phase3/includes/GlobalFunctions.php
@@ -30,6 +30,7 @@
3131 require_once( 'UpdateClasses.php' );
3232 require_once( 'LogPage.php' );
3333 require_once( 'normal/UtfNormalUtil.php' );
 34+require_once( 'XmlFunctions.php' );
3435
3536 /**
3637 * Compatibility functions
@@ -839,30 +840,7 @@
840841 return $out;
841842 }
842843
843 -/**
844 - * Returns an escaped string suitable for inclusion in a string literal
845 - * for JavaScript source code.
846 - * Illegal control characters are assumed not to be present.
847 - *
848 - * @param string $string
849 - * @return string
850 - */
851 -function wfEscapeJsString( $string ) {
852 - / See ECMA 262 section 7.8.4 for string literal format
853 - $pairs = array(
854 - "\\" => "\\\\",
855 - "\"" => "\\\"",
856 - '\'' => '\\\'',
857 - "\n" => "\\n",
858 - "\r" => "\\r",
859844
860 - # To avoid closing the element or CDATA section
861 - "<" => "\\x3c",
862 - ">" => "\\x3e",
863 - );
864 - return strtr( $string, $pairs );
865 -}
866 -
867845 /**
868846 * @todo document
869847 * @return float
@@ -873,15 +851,6 @@
874852 }
875853
876854 /**
877 - * Changes the first character to an HTML entity
878 - */
879 -function wfHtmlEscapeFirst( $text ) {
880 - $ord = ord($text);
881 - $newText = substr($text, 1);
882 - return "&#$ord;$newText";
883 -}
884 -
885 -/**
886855 * Sets dest to source and returns the original value of dest
887856 * If source is NULL, it just returns the value, it doesn't set the variable
888857 */
@@ -1453,100 +1422,6 @@
14541423 return( $siteNotice );
14551424 }
14561425
1457 -/**
1458 - * Format an XML element with given attributes and, optionally, text content.
1459 - * Element and attribute names are assumed to be ready for literal inclusion.
1460 - * Strings are assumed to not contain XML-illegal characters; special
1461 - * characters (<, >, &) are escaped but illegals are not touched.
1462 - *
1463 - * @param string $element
1464 - * @param array $attribs Name=>value pairs. Values will be escaped.
1465 - * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
1466 - * @return string
1467 - */
1468 -function wfElement( $element, $attribs = null, $contents = '') {
1469 - $out = '<' . $element;
1470 - if( !is_null( $attribs ) ) {
1471 - foreach( $attribs as $name => $val ) {
1472 - $out .= ' ' . $name . '="' . htmlspecialchars( $val ) . '"';
1473 - }
1474 - }
1475 - if( is_null( $contents ) ) {
1476 - $out .= '>';
1477 - } else {
1478 - if( $contents == '' ) {
1479 - $out .= ' />';
1480 - } else {
1481 - $out .= '>' . htmlspecialchars( $contents ) . "</$element>";
1482 - }
1483 - }
1484 - return $out;
1485 -}
1486 -
1487 -/**
1488 - * Format an XML element as with wfElement(), but run text through the
1489 - * UtfNormal::cleanUp() validator first to ensure that no invalid UTF-8
1490 - * is passed.
1491 - *
1492 - * @param string $element
1493 - * @param array $attribs Name=>value pairs. Values will be escaped.
1494 - * @param string $contents NULL to make an open tag only; '' for a contentless closed tag (default)
1495 - * @return string
1496 - */
1497 -function wfElementClean( $element, $attribs = array(), $contents = '') {
1498 - if( $attribs ) {
1499 - $attribs = array_map( array( 'UtfNormal', 'cleanUp' ), $attribs );
1500 - }
1501 - if( $contents ) {
1502 - $contents = UtfNormal::cleanUp( $contents );
1503 - }
1504 - return wfElement( $element, $attribs, $contents );
1505 -}
1506 -
1507 -/ Shortcuts
1508 -function wfOpenElement( $element, $attribs = null ) { return wfElement( $element, $attribs, null ); }
1509 -function wfCloseElement( $element ) { return "</$element>"; }
1510 -
1511 -/**
1512 - * Create a namespace selector
1513 - *
1514 - * @param mixed $selected The namespace which should be selected, default ''
1515 - * @param string $allnamespaces Value of a special item denoting all namespaces. Null to not include (default)
1516 - * @return Html string containing the namespace selector
1517 - */
1518 -function &HTMLnamespaceselector($selected = '', $allnamespaces = null) {
1519 - global $wgContLang;
1520 - if( $selected !== '' ) {
1521 - if( is_null( $selected ) ) {
1522 - / No namespace selected; let exact match work without hitting Main
1523 - $selected = '';
1524 - } else {
1525 - / Let input be numeric strings without breaking the empty match.
1526 - $selected = intval( $selected );
1527 - }
1528 - }
1529 - $s = "<select id='namespace' name='namespace' class='namespaceselector'>\n\t";
1530 - $arr = $wgContLang->getFormattedNamespaces();
1531 - if( !is_null($allnamespaces) ) {
1532 - $arr = array($allnamespaces => wfMsgHtml('namespacesall')) + $arr;
1533 - }
1534 - foreach ($arr as $index => $name) {
1535 - if ($index < NS_MAIN) continue;
1536 -
1537 - $name = $index !== 0 ? $name : wfMsgHtml('blanknamespace');
1538 -
1539 - if ($index === $selected) {
1540 - $s .= wfElement("option",
1541 - array("value" => $index, "selected" => "selected"),
1542 - $name);
1543 - } else {
1544 - $s .= wfElement("option", array("value" => $index), $name);
1545 - }
1546 - }
1547 - $s .= "\n</select>\n";
1548 - return $s;
1549 -}
1550 -
15511426 /** Global singleton instance of MimeMagic. This is initialized on demand,
15521427 * please always use the wfGetMimeMagic() function to get the instance.
15531428 *
@@ -1727,50 +1602,6 @@
17281603 }
17291604
17301605 /**
1731 - * Check if a string is well-formed XML.
1732 - * Must include the surrounding tag.
1733 - *
1734 - * @param string $text
1735 - * @return bool
1736 - *
1737 - * @todo Error position reporting return
1738 - */
1739 -function wfIsWellFormedXml( $text ) {
1740 - $parser = xml_parser_create( "UTF-8" );
1741 -
1742 - # case folding violates XML standard, turn it off
1743 - xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
1744 -
1745 - if( !xml_parse( $parser, $text, true ) ) {
1746 - $err = xml_error_string( xml_get_error_code( $parser ) );
1747 - $position = xml_get_current_byte_index( $parser );
1748 - /$fragment = $this->extractFragment( $html, $position );
1749 - /$this->mXmlError = "$err at byte $position:\n$fragment";
1750 - xml_parser_free( $parser );
1751 - return false;
1752 - }
1753 - xml_parser_free( $parser );
1754 - return true;
1755 -}
1756 -
1757 -/**
1758 - * Check if a string is a well-formed XML fragment.
1759 - * Wraps fragment in an <html> bit and doctype, so it can be a fragment
1760 - * and can use HTML named entities.
1761 - *
1762 - * @param string $text
1763 - * @return bool
1764 - */
1765 -function wfIsWellFormedXmlFragment( $text ) {
1766 - $html =
1767 - Sanitizer::hackDocType() .
1768 - '<html>' .
1769 - $text .
1770 - '</html>';
1771 - return wfIsWellFormedXml( $html );
1772 -}
1773 -
1774 -/**
17751606 * shell_exec() with time and memory limits mirrored from the PHP configuration,
17761607 * if supported.
17771608 */
Index: trunk/phase3/includes/SpecialRevisiondelete.php
@@ -0,0 +1,258 @@
 2+<?php
 3+
 4+/**
 5+ * Not quite ready for production use yet; need to fix up the restricted mode,
 6+ * and provide for preservation across delete/undelete of the page.
 7+ *
 8+ * To try this out, set up extra permissions something like:
 9+ * $wgGroupPermissions['sysop']['deleterevision'] = true;
 10+ * $wgGroupPermissions['bureaucrat']['hiderevision'] = true;
 11+ */
 12+
 13+function wfSpecialRevisiondelete( $par = null ) {
 14+ global $wgOut, $wgRequest, $wgUser, $wgContLang;
 15+
 16+ $target = $wgRequest->getVal( 'target' );
 17+ $oldid = $wgRequest->getInt( 'oldid' );
 18+
 19+ $sk = $wgUser->getSkin();
 20+ $page = Title::newFromUrl( $target );
 21+
 22+ if( is_null( $page ) ) {
 23+ $wgOut->errorpage( 'notargettitle', 'notargettext' );
 24+ return;
 25+ }
 26+
 27+ $form = new RevisionDeleteForm( $wgRequest );
 28+ if( $wgRequest->wasPosted() ) {
 29+ $form->submit( $wgRequest );
 30+ } else {
 31+ $form->show( $wgRequest );
 32+ }
 33+}
 34+
 35+class RevisionDeleteForm {
 36+ /**
 37+ * @param Title $page
 38+ * @param int $oldid
 39+ */
 40+ function __construct( $request ) {
 41+ global $wgUser;
 42+
 43+ $target = $request->getVal( 'target' );
 44+ $this->page = Title::newFromUrl( $target );
 45+
 46+ $this->revisions = $request->getIntArray( 'oldid', array() );
 47+
 48+ $this->skin = $wgUser->getSkin();
 49+ $this->checks = array(
 50+ array( 'revdelete-hide-text', 'wpHideText', MW_REV_DELETED_TEXT ),
 51+ array( 'revdelete-hide-comment', 'wpHideComment', MW_REV_DELETED_COMMENT ),
 52+ array( 'revdelete-hide-user', 'wpHideUser', MW_REV_DELETED_USER ),
 53+ array( 'revdelete-hide-restricted', 'wpHideRestricted', MW_REV_DELETED_RESTRICTED ) );
 54+ }
 55+
 56+ /**
 57+ * @param WebRequest $request
 58+ */
 59+ function show( $request ) {
 60+ global $wgOut, $wgUser;
 61+
 62+ $first = $this->revisions[0];
 63+
 64+ $wgOut->addWikiText( wfMsg( 'revdelete-selected', $this->page->getPrefixedText() ) );
 65+
 66+ $wgOut->addHtml( "<ul>" );
 67+ foreach( $this->revisions as $revid ) {
 68+ $rev = Revision::newFromTitle( $this->page, $revid );
 69+ $wgOut->addHtml( $this->historyLine( $rev ) );
 70+ $bitfields[] = $rev->mDeleted; / FIXME
 71+ }
 72+ $wgOut->addHtml( "</ul>" );
 73+
 74+ $wgOut->addWikiText( wfMsg( 'revdelete-text' ) );
 75+
 76+ $items = array(
 77+ wfInputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
 78+ wfSubmitButton( wfMsg( 'revdelete-submit' ) ) );
 79+ $hidden = array(
 80+ wfHidden( 'wpEditToken', $wgUser->editToken() ),
 81+ wfHidden( 'target', $this->page->getPrefixedText() ) );
 82+ foreach( $this->revisions as $revid ) {
 83+ $hidden[] = wfHidden( 'oldid[]', $revid );
 84+ }
 85+
 86+ $special = Title::makeTitle( NS_SPECIAL, 'Revisiondelete' );
 87+ $wgOut->addHtml( wfElement( 'form', array(
 88+ 'method' => 'post',
 89+ 'action' => $special->getLocalUrl( 'action=submit' ) ) ) );
 90+
 91+ $wgOut->addHtml( '<fieldset><legend>' . wfMsgHtml( 'revdelete-legend' ) . '</legend>' );
 92+ foreach( $this->checks as $item ) {
 93+ list( $message, $name, $field ) = $item;
 94+ $wgOut->addHtml( '<div>' .
 95+ wfCheckLabel( wfMsg( $message), $name, $name, $rev->isDeleted( $field ) ) .
 96+ '</div>' );
 97+ }
 98+ $wgOut->addHtml( '</fieldset>' );
 99+ foreach( $items as $item ) {
 100+ $wgOut->addHtml( '<p>' . $item . '</p>' );
 101+ }
 102+ foreach( $hidden as $item ) {
 103+ $wgOut->addHtml( $item );
 104+ }
 105+
 106+ $wgOut->addHtml( '</form>' );
 107+ }
 108+
 109+ /**
 110+ * @param Revision $rev
 111+ * @returns string
 112+ */
 113+ private function historyLine( $rev ) {
 114+ global $wgContLang;
 115+ $date = $wgContLang->timeanddate( $rev->getTimestamp() );
 116+ return
 117+ "<li>" .
 118+ $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() ) .
 119+ " " .
 120+ $this->skin->revUserLink( $rev ) .
 121+ " " .
 122+ $this->skin->revComment( $rev ) .
 123+ "</li>";
 124+ }
 125+
 126+ /**
 127+ * @param WebRequest $request
 128+ */
 129+ function submit( $request ) {
 130+ $bitfield = $this->extractBitfield( $request );
 131+ $comment = $request->getText( 'wpReason' );
 132+ if( $this->save( $bitfield, $comment ) ) {
 133+ return $this->success( $request );
 134+ } else {
 135+ return $this->show( $request );
 136+ }
 137+ }
 138+
 139+ function success( $request ) {
 140+ global $wgOut;
 141+ $wgOut->addWikiText( 'woo' );
 142+ }
 143+
 144+ /**
 145+ * Put together a rev_deleted bitfield from the submitted checkboxes
 146+ * @param WebRequest $request
 147+ * @return int
 148+ */
 149+ function extractBitfield( $request ) {
 150+ $bitfield = 0;
 151+ foreach( $this->checks as $item ) {
 152+ list( $message, $name, $field ) = $item;
 153+ if( $request->getCheck( $name ) ) {
 154+ $bitfield |= $field;
 155+ }
 156+ }
 157+ return $bitfield;
 158+ }
 159+
 160+ function save( $bitfield, $reason ) {
 161+ $dbw = wfGetDB( DB_MASTER );
 162+ $deleter = new RevisionDeleter( $dbw );
 163+ $ok = $deleter->setVisibility( $this->revisions, $bitfield, $reason );
 164+ }
 165+}
 166+
 167+
 168+class RevisionDeleter {
 169+ function __construct( $db ) {
 170+ $this->db = $db;
 171+ }
 172+
 173+ /**
 174+ * @param array $items list of revision ID numbers
 175+ * @param int $bitfield new rev_deleted value
 176+ * @param string $comment Comment for log records
 177+ */
 178+ function setVisibility( $items, $bitfield, $comment ) {
 179+ $pages = array();
 180+
 181+ / To work!
 182+ foreach( $items as $revid ) {
 183+ $rev = Revision::newFromId( $revid );
 184+ $this->updateRevision( $rev, $bitfield );
 185+ $this->updateRecentChanges( $rev, $bitfield );
 186+
 187+ / For logging, maintain a count of revisions per page
 188+ $pageid = $rev->getPage();
 189+ if( isset( $pages[$pageid] ) ) {
 190+ $pages[$pageid]++;
 191+ } else {
 192+ $pages[$pageid] = 1;
 193+ }
 194+ }
 195+
 196+ / Clear caches...
 197+ foreach( $pages as $pageid => $count ) {
 198+ $title = Title::newFromId( $pageid );
 199+ $this->updatePage( $title );
 200+ $this->updateLog( $title, $count, $bitfield, $comment );
 201+ }
 202+
 203+ return true;
 204+ }
 205+
 206+ /**
 207+ * Update the revision's rev_deleted field
 208+ * @param Revision $rev
 209+ * @param int $bitfield new rev_deleted bitfield value
 210+ */
 211+ function updateRevision( $rev, $bitfield ) {
 212+ $this->db->update( 'revision',
 213+ array( 'rev_deleted' => $bitfield ),
 214+ array( 'rev_id' => $rev->getId() ),
 215+ 'RevisionDeleter::updateRevision' );
 216+ }
 217+
 218+ /**
 219+ * Update the revision's recentchanges record if fields have been hidden
 220+ * @param Revision $rev
 221+ * @param int $bitfield new rev_deleted bitfield value
 222+ */
 223+ function updateRecentChanges( $rev, $bitfield ) {
 224+ $this->db->update( 'recentchanges',
 225+ array(
 226+ 'rc_user' => ($bitfield & MW_REV_DELETED_USER) ? 0 : $rev->getUser(),
 227+ 'rc_user_text' => ($bitfield & MW_REV_DELETED_USER) ? wfMsg( 'rev-deleted-user' ) : $rev->getUserText(),
 228+ 'rc_comment' => ($bitfield & MW_REV_DELETED_COMMENT) ? wfMsg( 'rev-deleted-comment' ) : $rev->getComment() ),
 229+ array(
 230+ 'rc_this_oldid' => $rev->getId() ),
 231+ 'RevisionDeleter::updateRecentChanges' );
 232+ }
 233+
 234+ /**
 235+ * Touch the page's cache invalidation timestamp; this forces cached
 236+ * history views to refresh, so any newly hidden or shown fields will
 237+ * update properly.
 238+ * @param Title $title
 239+ */
 240+ function updatePage( $title ) {
 241+ $title->invalidateCache();
 242+ }
 243+
 244+ /**
 245+ * Record a log entry on the action
 246+ * @param Title $title
 247+ * @param int $count the number of revisions altered for this page
 248+ * @param int $bitfield the new rev_deleted value
 249+ * @param string $comment
 250+ */
 251+ function updateLog( $title, $count, $bitfield, $comment ) {
 252+ $log = new LogPage( 'delete' );
 253+ $reason = "changed $count revisions to $bitfield";
 254+ $reason .= ": $comment";
 255+ $log->addEntry( 'revision', $title, $reason );
 256+ }
 257+}
 258+
 259+?>
Property changes on: trunk/phase3/includes/SpecialRevisiondelete.php
___________________________________________________________________
Added: svn:eol-style
1260 + native
Added: svn:keywords
2261 + Author Date Id Revision
Index: trunk/phase3/includes/Linker.php
@@ -737,6 +737,115 @@
738738 }
739739
740740 /**
 741+ * Make user link (or user contributions for unregistered users)
 742+ * @param int $userId
 743+ * @param string $userText
 744+ * @return string HTML fragment
 745+ * @access private
 746+ */
 747+ function userLink( $userId, $userText ) {
 748+ $encName = htmlspecialchars( $userText );
 749+ if( $userId == 0 ) {
 750+ $contribsPage = Title::makeTitle( NS_SPECIAL, 'Contributions' );
 751+ return $this->makeKnownLinkObj( $contribsPage,
 752+ $encName, 'target=' . urlencode( $userText ) );
 753+ } else {
 754+ $userPage = Title::makeTitle( NS_USER, $userText );
 755+ return $this->makeLinkObj( $userPage, $encName );
 756+ }
 757+ }
 758+
 759+ /**
 760+ * @param int $userId
 761+ * @param string $userText
 762+ * @return string HTML fragment with talk and/or block links
 763+ * @access private
 764+ */
 765+ function userToolLinks( $userId, $userText ) {
 766+ global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans;
 767+ $talkable = !( $wgDisableAnonTalk && 0 == $userId );
 768+ $blockable = ( $wgSysopUserBans || 0 == $userId );
 769+
 770+ $items = array();
 771+ if( $talkable ) {
 772+ $items[] = $this->userTalkLink( $userId, $userText );
 773+ }
 774+ if( $blockable && $wgUser->isAllowed( 'block' ) ) {
 775+ $items[] = $this->blockLink( $userId, $userText );
 776+ }
 777+
 778+ if( $items ) {
 779+ return ' (' . implode( ' | ', $items ) . ')';
 780+ } else {
 781+ return '';
 782+ }
 783+ }
 784+
 785+ /**
 786+ * @param int $userId
 787+ * @param string $userText
 788+ * @return string HTML fragment with user talk link
 789+ * @access private
 790+ */
 791+ function userTalkLink( $userId, $userText ) {
 792+ global $wgContLang;
 793+ $talkname = $wgContLang->getNsText( NS_TALK ); # use the shorter name
 794+
 795+ $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
 796+ $userTalkLink = $this->makeLinkObj( $userTalkPage, $talkname );
 797+ return $userTalkLink;
 798+ }
 799+
 800+ /**
 801+ * @param int $userId
 802+ * @param string $userText
 803+ * @return string HTML fragment with block link
 804+ * @access private
 805+ */
 806+ function blockLink( $userId, $userText ) {
 807+ $blockPage = Title::makeTitle( NS_SPECIAL, 'Blockip' );
 808+ $blockLink = $this->makeKnownLinkObj( $blockPage,
 809+ wfMsgHtml( 'blocklink' ), 'ip=' . urlencode( $userText ) );
 810+ return $blockLink;
 811+ }
 812+
 813+ /**
 814+ * Generate a user link if the current user is allowed to view it
 815+ * @param Revision $rev
 816+ * @return string HTML
 817+ */
 818+ function revUserLink( $rev ) {
 819+ if( $rev->userCan( MW_REV_DELETED_USER ) ) {
 820+ $link = $this->userLink( $rev->getRawUser(), $rev->getRawUserText() );
 821+ } else {
 822+ $link = wfMsgHtml( 'rev-deleted-user' );
 823+ }
 824+ if( $rev->isDeleted( MW_REV_DELETED_USER ) ) {
 825+ return '<span class="history-deleted">' . $link . '</span>';
 826+ }
 827+ return $link;
 828+ }
 829+
 830+ /**
 831+ * Generate a user tool link cluster if the current user is allowed to view it
 832+ * @param Revision $rev
 833+ * @return string HTML
 834+ */
 835+ function revUserTools( $rev ) {
 836+ if( $rev->userCan( MW_REV_DELETED_USER ) ) {
 837+ $link = $this->userLink( $rev->getRawUser(), $rev->getRawUserText() ) .
 838+ ' ' .
 839+ $this->userToolLinks( $rev->getRawUser(), $rev->getRawUserText() );
 840+ } else {
 841+ $link = wfMsgHtml( 'rev-deleted-user' );
 842+ }
 843+ if( $rev->isDeleted( MW_REV_DELETED_USER ) ) {
 844+ return '<span class="history-deleted">' . $link . '</span>';
 845+ }
 846+ return $link;
 847+ }
 848+
 849+ /**
741850 * This function is called by all recent changes variants, by the page history,
742851 * and by the user contributions list. It is responsible for formatting edit
743852 * comments. It escapes any HTML in the comment, but adds some CSS to format
@@ -823,25 +932,39 @@
824933 *
825934 * @param string $comment
826935 * @param Title $title
827 - * @param bool $deleted
828936 *
829937 * @return string
830938 */
831 - function commentBlock( $comment, $title = NULL, $deleted = false ) {
 939+ function commentBlock( $comment, $title = NULL ) {
832940 / '*' used to be the comment inserted by the software way back
833941 / in antiquity in case none was provided, here for backwards
834942 / compatability, acc. to brion -ævar
835943 if( $comment == '' || $comment == '*' ) {
836944 return '';
837945 } else {
838 - if ( $deleted )
839 - return " <span class='comment'>(...)</span>";
840 - else {
841 - $formatted = $this->formatComment( $comment, $title );
842 - return " <span class='comment'>($formatted)</span>";
843 - }
 946+ $formatted = $this->formatComment( $comment, $title );
 947+ return " <span class='comment'>($formatted)</span>";
844948 }
845949 }
 950+
 951+ /**
 952+ * Wrap and format the given revision's comment block, if the current
 953+ * user is allowed to view it.
 954+ * @param Revision $rev
 955+ * @return string HTML
 956+ */
 957+ function revComment( $rev ) {
 958+ if( $rev->userCan( MW_REV_DELETED_COMMENT ) ) {
 959+ $block = $this->commentBlock( $rev->getRawComment(), $rev->getTitle() );
 960+ } else {
 961+ $block = " <span class='comment'>" .
 962+ wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
 963+ }
 964+ if( $rev->isDeleted( MW_REV_DELETED_COMMENT ) ) {
 965+ return " <span class='history-deleted'>$block</span>";
 966+ }
 967+ return $block;
 968+ }
846969
847970 /** @todo document */
848971 function tocIndent() {
Index: trunk/phase3/includes/SpecialContributions.php
@@ -128,7 +128,7 @@
129129 $use_index = $this->dbr->useIndexClause($index);
130130 $sql = "SELECT
131131 page_namespace,page_title,page_is_new,page_latest,
132 - rev_id,rev_timestamp,rev_comment,rev_minor_edit,rev_user_text,
 132+ rev_id,rev_page,rev_text_id,rev_timestamp,rev_comment,rev_minor_edit,rev_user,rev_user_text,
133133 rev_deleted
134134 FROM $page,$revision $use_index
135135 WHERE page_id=rev_page AND $userCond $nscond $offsetQuery
@@ -358,8 +358,10 @@
359359 }
360360 }
361361
362 - $page =& Title::makeTitle( $row->page_namespace, $row->page_title );
363 - $link = $sk->makeKnownLinkObj( $page, '' );
 362+ $rev = new Revision( $row );
 363+
 364+ $page = Title::makeTitle( $row->page_namespace, $row->page_title );
 365+ $link = $sk->makeKnownLinkObj( $page );
364366 $difftext = $topmarktext = '';
365367 if( $row->rev_id == $row->page_latest ) {
366368 $topmarktext .= '<strong>' . $messages['uctop'] . '</strong>';
@@ -379,15 +381,19 @@
380382 }
381383
382384 }
383 - if( $row->rev_deleted && !$wgUser->isAllowed( 'delete' ) ) {
 385+ if( $rev->userCan( MW_REV_DELETED_TEXT ) ) {
 386+ $difftext = '(' . $sk->makeKnownLinkObj( $page, $messages['diff'], 'diff=prev&oldid='.$row->rev_id ) . ')';
 387+ } else {
384388 $difftext = '(' . $messages['diff'] . ')';
385 - } else {
386 - $difftext = '(' . $sk->makeKnownLinkObj( $page, $messages['diff'], 'diff=prev&oldid='.$row->rev_id ) . ')';
387389 }
388390 $histlink='('.$sk->makeKnownLinkObj( $page, $messages['hist'], 'action=history' ) . ')';
389391
390 - $comment = $sk->commentBlock( $row->rev_comment, $page, (bool)$row->rev_deleted );
 392+ $comment = $sk->revComment( $rev );
391393 $d = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->rev_timestamp), true );
 394+
 395+ if( $rev->isDeleted( MW_REV_DELETED_TEXT ) ) {
 396+ $d = '<span class="history-deleted">' . $d . '</span>';
 397+ }
392398
393399 if( $row->rev_minor_edit ) {
394400 $mflag = '<span class="minor">' . $messages['minoreditletter'] . '</span> ';
@@ -396,8 +402,8 @@
397403 }
398404
399405 $ret = "{$d} {$histlink} {$difftext} {$mflag} {$link} {$comment} {$topmarktext}";
400 - if( $row->rev_deleted ) {
401 - $ret = "<span class='deleted'>$ret</span>";
 406+ if( $rev->isDeleted( MW_REV_DELETED_TEXT ) ) {
 407+ $ret .= ' ' . wfMsgHtml( 'deletedrev' );
402408 }
403409 $ret = "<li>$ret</li>\n";
404410 wfProfileOut( $fname );
Index: trunk/phase3/includes/Export.php
@@ -1,5 +1,5 @@
22 <?php
3 -# Copyright (C) 2003, 2005 Brion Vibber <[email protected]>
 3+# Copyright (C) 2003, 2005, 2006 Brion Vibber <[email protected]>
44 # http://www.mediawiki.org/
55 #
66 # This program is free software; you can redistribute it and/or modify
@@ -232,7 +232,7 @@
233233 * @return string
234234 */
235235 function schemaVersion() {
236 - return "0.3";
 236+ return "0.3"; / FIXME: upgrade to 0.4 when updated XSD is ready, for the revision deletion bits
237237 }
238238
239239 /**
@@ -360,23 +360,31 @@
361361 $ts = wfTimestamp( TS_ISO_8601, $row->rev_timestamp );
362362 $out .= " " . wfElement( 'timestamp', null, $ts ) . "\n";
363363
364 - $out .= " <contributor>\n";
365 - if( $row->rev_user ) {
366 - $out .= " " . wfElementClean( 'username', null, strval( $row->rev_user_text ) ) . "\n";
367 - $out .= " " . wfElement( 'id', null, strval( $row->rev_user ) ) . "\n";
 364+ if( $row->rev_deleted & MW_REV_DELETED_USER ) {
 365+ $out .= " " . wfElement( 'contributor', array( 'deleted' => 'deleted' ) ) . "\n";
368366 } else {
369 - $out .= " " . wfElementClean( 'ip', null, strval( $row->rev_user_text ) ) . "\n";
 367+ $out .= " <contributor>\n";
 368+ if( $row->rev_user ) {
 369+ $out .= " " . wfElementClean( 'username', null, strval( $row->rev_user_text ) ) . "\n";
 370+ $out .= " " . wfElement( 'id', null, strval( $row->rev_user ) ) . "\n";
 371+ } else {
 372+ $out .= " " . wfElementClean( 'ip', null, strval( $row->rev_user_text ) ) . "\n";
 373+ }
 374+ $out .= " </contributor>\n";
370375 }
371 - $out .= " </contributor>\n";
372376
373377 if( $row->rev_minor_edit ) {
374378 $out .= " <minor/>\n";
375379 }
376 - if( $row->rev_comment != '' ) {
 380+ if( $row->rev_deleted & MW_REV_DELETED_COMMENT ) {
 381+ $out .= " " . wfElement( 'comment', array( 'deleted' => 'deleted' ) ) . "\n";
 382+ } elseif( $row->rev_comment != '' ) {
377383 $out .= " " . wfElementClean( 'comment', null, strval( $row->rev_comment ) ) . "\n";
378384 }
379385
380 - if( isset( $row->old_text ) ) {
 386+ if( $row->rev_deleted & MW_REV_DELETED_TEXT ) {
 387+ $out .= " " . wfElement( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
 388+ } elseif( isset( $row->old_text ) ) {
381389 / Raw text from the database may have invalid chars
382390 $text = strval( Revision::getRevisionText( $row ) );
383391 $out .= " " . wfElementClean( 'text',
Index: trunk/phase3/includes/WebRequest.php
@@ -162,6 +162,24 @@
163163 return (array)$val;
164164 }
165165 }
 166+
 167+ /**
 168+ * Fetch an array of integers, or return $default if it's not set.
 169+ * If source was scalar, will return an array with a single element.
 170+ * If no source and no default, returns NULL.
 171+ * If an array is returned, contents are guaranteed to be integers.
 172+ *
 173+ * @param string $name
 174+ * @param array $default option default (or NULL)
 175+ * @return array of ints
 176+ */
 177+ function getIntArray( $name, $default = NULL ) {
 178+ $val = $this->getArray( $name, $default );
 179+ if( is_array( $val ) ) {
 180+ $val = array_map( 'intval', $val );
 181+ }
 182+ return $val;
 183+ }
166184
167185 /**
168186 * Fetch an integer value from the input or return $default if not set.
Index: trunk/phase3/includes/Revision.php
@@ -8,6 +8,13 @@
99 require_once( 'Database.php' );
1010 require_once( 'Article.php' );
1111
 12+/** @+ */
 13+define( 'MW_REV_DELETED_TEXT', 1 );
 14+define( 'MW_REV_DELETED_COMMENT', 2 );
 15+define( 'MW_REV_DELETED_USER', 4 );
 16+define( 'MW_REV_DELETED_RESTRICTED', 8 );
 17+/** @- */
 18+
1219 /**
1320 * @package MediaWiki
1421 * @todo document
@@ -246,9 +253,14 @@
247254 $this->mTimestamp = $row->rev_timestamp;
248255 $this->mDeleted = intval( $row->rev_deleted );
249256
250 - $this->mCurrent = ( $row->rev_id == $row->page_latest );
251 - $this->mTitle = Title::makeTitle( $row->page_namespace,
252 - $row->page_title );
 257+ if( isset( $row->page_latest ) ) {
 258+ $this->mCurrent = ( $row->rev_id == $row->page_latest );
 259+ $this->mTitle = Title::makeTitle( $row->page_namespace,
 260+ $row->page_title );
 261+ } else {
 262+ $this->mCurrent = false;
 263+ $this->mTitle = null;
 264+ }
253265
254266 if( isset( $row->old_text ) ) {
255267 $this->mText = $this->getRevisionText( $row );
@@ -327,23 +339,62 @@
328340 }
329341
330342 /**
 343+ * Fetch revision's user id if it's available to all users
331344 * @return int
332345 */
333346 function getUser() {
 347+ if( $this->isDeleted( MW_REV_DELETED_USER ) ) {
 348+ return 0;
 349+ } else {
 350+ return $this->mUser;
 351+ }
 352+ }
 353+
 354+ /**
 355+ * Fetch revision's user id without regard for the current user's permissions
 356+ * @return string
 357+ */
 358+ function getRawUser() {
334359 return $this->mUser;
335360 }
336361
337362 /**
 363+ * Fetch revision's username if it's available to all users
338364 * @return string
339365 */
340366 function getUserText() {
 367+ if( $this->isDeleted( MW_REV_DELETED_USER ) ) {
 368+ return "";
 369+ } else {
 370+ return $this->mUserText;
 371+ }
 372+ }
 373+
 374+ /**
 375+ * Fetch revision's username without regard for view restrictions
 376+ * @return string
 377+ */
 378+ function getRawUserText() {
341379 return $this->mUserText;
342380 }
 381+
 382+ /**
 383+ * Fetch revision comment if it's available to all users
 384+ * @return string
 385+ */
 386+ function getComment() {
 387+ if( $this->isDeleted( MW_REV_DELETED_COMMENT ) ) {
 388+ return "";
 389+ } else {
 390+ return $this->mComment;
 391+ }
 392+ }
343393
344394 /**
 395+ * Fetch revision comment without regard for the current user's permissions
345396 * @return string
346397 */
347 - function getComment() {
 398+ function getRawComment() {
348399 return $this->mComment;
349400 }
350401
@@ -355,16 +406,30 @@
356407 }
357408
358409 /**
 410+ * int $field one of MW_REV_DELETED_* bitfield constants
359411 * @return bool
360412 */
361 - function isDeleted() {
362 - return (bool)$this->mDeleted;
 413+ function isDeleted( $field ) {
 414+ return ($this->mDeleted & $field) == $field;
363415 }
364416
365417 /**
 418+ * Fetch revision text if it's available to all users
366419 * @return string
367420 */
368421 function getText() {
 422+ if( $this->isDeleted( MW_REV_DELETED_TEXT ) ) {
 423+ return "";
 424+ } else {
 425+ return $this->getRawText();
 426+ }
 427+ }
 428+
 429+ /**
 430+ * Fetch revision text without regard for view restrictions
 431+ * @return string
 432+ */
 433+ function getRawText() {
369434 if( is_null( $this->mText ) ) {
370435 / Revision text is immutable. Load on demand:
371436 $this->mText = $this->loadText();
@@ -650,6 +715,28 @@
651716 wfProfileOut( $fname );
652717 return $revision;
653718 }
 719+
 720+ /**
 721+ * Determine if the current user is allowed to view a particular
 722+ * field of this revision, if it's marked as deleted.
 723+ * @param int $field one of MW_REV_DELETED_TEXT,
 724+ * MW_REV_DELETED_COMMENT,
 725+ * MW_REV_DELETED_USER
 726+ * @return bool
 727+ */
 728+ function userCan( $field ) {
 729+ if( ( $this->mDeleted & $field ) == $field ) {
 730+ global $wgUser;
 731+ $permission = ( $this->mDeleted & MW_REV_DELETED_RESTRICTED ) == MW_REV_DELETED_RESTRICTED
 732+ ? 'hiderevision'
 733+ : 'deleterevision';
 734+ wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
 735+ return $wgUser->isAllowed( $permission );
 736+ } else {
 737+ return true;
 738+ }
 739+ }
654740
655741 }
 742+
656743 ?>
Index: trunk/phase3/includes/ChangesList.php
@@ -181,8 +181,8 @@
182182
183183 /** Insert links to user page, user talk page and eventually a blocking link */
184184 function insertUserRelatedLinks(&$s, &$rc) {
185 - $s .= $this->userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
186 - $s .= $this->userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
 185+ $s .= $this->skin->userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
 186+ $s .= $this->skin->userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
187187 }
188188
189189 /** insert a formatted comment */
@@ -203,79 +203,7 @@
204204 ( !$wgOnlySysopsCanPatrol || $wgUser->isAllowed( 'patrol' ) );
205205 }
206206
207 - /**
208 - * Make user link (or user contributions for unregistered users)
209 - * @param int $userId
210 - * @param string $userText
211 - * @return string HTML fragment
212 - * @access private
213 - */
214 - function userLink( $userId, $userText ) {
215 - $encName = htmlspecialchars( $userText );
216 - if( $userId == 0 ) {
217 - $contribsPage = Title::makeTitle( NS_SPECIAL, 'Contributions' );
218 - return $this->skin->makeKnownLinkObj( $contribsPage,
219 - $encName, 'target=' . urlencode( $userText ) );
220 - } else {
221 - $userPage = Title::makeTitle( NS_USER, $userText );
222 - return $this->skin->makeLinkObj( $userPage, $encName );
223 - }
224 - }
225207
226 - /**
227 - * @param int $userId
228 - * @param string $userText
229 - * @return string HTML fragment with talk and/or block links
230 - * @access private
231 - */
232 - function userToolLinks( $userId, $userText ) {
233 - global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans;
234 - $talkable = !( $wgDisableAnonTalk && 0 == $userId );
235 - $blockable = ( $wgSysopUserBans || 0 == $userId );
236 -
237 - $items = array();
238 - if( $talkable ) {
239 - $items[] = $this->userTalkLink( $userId, $userText );
240 - }
241 - if( $blockable && $wgUser->isAllowed( 'block' ) ) {
242 - $items[] = $this->blockLink( $userId, $userText );
243 - }
244 -
245 - if( $items ) {
246 - return ' (' . implode( ' | ', $items ) . ')';
247 - } else {
248 - return '';
249 - }
250 - }
251 -
252 - /**
253 - * @param int $userId
254 - * @param string $userText
255 - * @return string HTML fragment with user talk link
256 - * @access private
257 - */
258 - function userTalkLink( $userId, $userText ) {
259 - global $wgContLang;
260 - $talkname = $wgContLang->getNsText( NS_TALK ); # use the shorter name
261 -
262 - $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
263 - $userTalkLink = $this->skin->makeLinkObj( $userTalkPage, $talkname );
264 - return $userTalkLink;
265 - }
266 -
267 - /**
268 - * @param int $userId
269 - * @param string $userText
270 - * @return string HTML fragment with block link
271 - * @access private
272 - */
273 - function blockLink( $userId, $userText ) {
274 - $blockPage = Title::makeTitle( NS_SPECIAL, 'Blockip' );
275 - $blockLink = $this->skin->makeKnownLinkObj( $blockPage,
276 - $this->message['blocklink'], 'ip=' . urlencode( $userText ) );
277 - return $blockLink;
278 - }
279 -
280208 }
281209
282210
@@ -429,13 +357,13 @@
430358 $curIdEq.'&diff='.$rc_this_oldid.'&oldid='.$rc_last_oldid . $rcIdQuery );
431359 }
432360
433 - $rc->userlink = $this->userLink( $rc_user, $rc_user_text );
 361+ $rc->userlink = $this->skin->userLink( $rc_user, $rc_user_text );
434362
435363 $rc->lastlink = $lastLink;
436364 $rc->curlink = $curLink;
437365 $rc->difflink = $diffLink;
438366
439 - $rc->usertalklink = $this->userToolLinks( $rc_user, $rc_user_text );
 367+ $rc->usertalklink = $this->skin->userToolLinks( $rc_user, $rc_user_text );
440368
441369 # Put accumulated information into the cache, for later display
442370 # Page moves go on their own line
Index: trunk/phase3/includes/DifferenceEngine.php
@@ -24,8 +24,6 @@
2525 var $mOldid, $mNewid, $mTitle;
2626 var $mOldtitle, $mNewtitle, $mPagetitle;
2727 var $mOldtext, $mNewtext;
28 - var $mOldUser, $mNewUser;
29 - var $mOldComment, $mNewComment;
3028 var $mOldPage, $mNewPage;
3129 var $mRcidMarkPatrolled;
3230 var $mOldRev, $mNewRev;
@@ -153,21 +151,11 @@
154152 $talk = $wgContLang->getNsText( NS_TALK );
155153 $contribs = wfMsg( 'contribslink' );
156154
157 - $this->mOldComment = $sk->formatComment($this->mOldComment);
158 - $this->mNewComment = $sk->formatComment($this->mNewComment);
159 -
160 - $oldUserLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER, $this->mOldUser ), $this->mOldUser );
161 - $newUserLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER, $this->mNewUser ), $this->mNewUser );
162 - $oldUTLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER_TALK, $this->mOldUser ), $talk );
163 - $newUTLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER_TALK, $this->mNewUser ), $talk );
164 - $oldContribs = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), $contribs,
165 - 'target=' . urlencode($this->mOldUser) );
166 - $newContribs = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), $contribs,
167 - 'target=' . urlencode($this->mNewUser) );
168155 if ( $this->mNewRev->isCurrent() && $wgUser->isAllowed('rollback') ) {
 156+ $username = $this->mNewRev->getUserText();
169157 $rollback = '&nbsp;&nbsp;&nbsp;<strong>[' . $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'rollbacklink' ),
170 - 'action=rollback&from=' . urlencode($this->mNewUser) .
171 - '&token=' . urlencode( $wgUser->editToken( array( $this->mTitle->getPrefixedText(), $this->mNewUser ) ) ) ) .
 158+ 'action=rollback&from=' . urlencode( $username ) .
 159+ '&token=' . urlencode( $wgUser->editToken( array( $this->mTitle->getPrefixedText(), $username ) ) ) ) .
172160 ']</strong>';
173161 } else {
174162 $rollback = '';
@@ -190,12 +178,14 @@
191179 'diff=next&oldid='.$this->mNewid, '', '', 'id="differences-nextlink"' );
192180 }
193181
194 - $oldHeader = "<strong>{$this->mOldtitle}</strong><br />$oldUserLink " .
195 - "($oldUTLink | $oldContribs)<br />" . $this->mOldComment .
196 - '<br />' . $prevlink;
197 - $newHeader = "<strong>{$this->mNewtitle}</strong><br />$newUserLink " .
198 - "($newUTLink | $newContribs) $rollback<br />" . $this->mNewComment .
199 - '<br />' . $nextlink . $patrol;
 182+ $oldHeader = "<strong>{$this->mOldtitle}</strong><br />" .
 183+ $sk->revUserTools( $this->mOldRev ) . "<br />" .
 184+ $sk->revComment( $this->mOldRev ) . "<br />" .
 185+ $prevlink;
 186+ $newHeader = "<strong>{$this->mNewtitle}</strong><br />" .
 187+ $sk->revUserTools( $this->mNewRev ) . " $rollback<br />" .
 188+ $sk->revComment( $this->mNewRev ) . "<br />" .
 189+ $nextlink . $patrol;
200190
201191 $this->showDiff( $oldHeader, $newHeader );
202192 $wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" );
@@ -255,19 +245,16 @@
256246 #
257247 $sk = $wgUser->getSkin();
258248
259 - $uTLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER_TALK, $this->mOldUser ), $wgLang->getNsText( NS_TALK ) );
260 - $userLink = $sk->makeLinkObj( Title::makeTitleSafe( NS_USER, $this->mOldUser ), $this->mOldUser );
261 - $contribs = $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), wfMsg( 'contribslink' ),
262 - 'target=' . urlencode($this->mOldUser) );
263249 $nextlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'nextdiff' ), 'diff=next&oldid='.$this->mNewid, '', '', 'id="differences-nextlink"' );
264 - $header = "<div class=\"firstrevisionheader\" style=\"text-align: center\"><strong>{$this->mOldtitle}</strong><br />$userLink " .
265 - "($uTLink | $contribs)<br />" . $this->mOldComment .
266 - '<br />' . $nextlink. "</div>\n";
 250+ $header = "<div class=\"firstrevisionheader\" style=\"text-align: center\"><strong>{$this->mOldtitle}</strong><br />" .
 251+ $sk->revUserTools( $this->mNewRev ) . "<br />" .
 252+ $sk->revComment( $this->mNewRev ) . "<br />" .
 253+ $nextlink . "</div>\n";
267254
268255 $wgOut->addHTML( $header );
269256
270257 $wgOut->setSubtitle( wfMsg( 'difference' ) );
271 - $wgOut->setRobotpolicy( 'noindex,follow' );
 258+ $wgOut->setRobotpolicy( 'noindex,nofollow' );
272259
273260
274261 # Show current revision
@@ -289,7 +276,7 @@
290277 global $wgOut;
291278 $diff = $this->getDiff( $otitle, $ntitle );
292279 if ( $diff === false ) {
293 - $wgOut->addWikitext( wfMsg( 'missingarticle', "<nowiki>$t</nowiki>" ) );
 280+ $wgOut->addWikitext( wfMsg( 'missingarticle', "<nowiki>(fixme, bug)</nowiki>" ) );
294281 return false;
295282 } else {
296283 $wgOut->addHTML( $diff );
@@ -510,9 +497,6 @@
511498 $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>";
512499 }
513500
514 - $this->mNewUser = $this->mNewRev->getUserText();
515 - $this->mNewComment = $this->mNewRev->getComment();
516 -
517501 / Load the old revision object
518502 $this->mOldRev = false;
519503 if( $this->mOldid ) {
@@ -539,10 +523,6 @@
540524 $t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true );
541525 $oldLink = $this->mOldPage->escapeLocalUrl( 'oldid=' . $this->mOldid );
542526 $this->mOldtitle = "<a href='$oldLink'>" . htmlspecialchars( wfMsg( 'revisionasof', $t ) ) . '</a>';
543 -
544 -
545 - $this->mOldUser = $this->mOldRev->getUserText();
546 - $this->mOldComment = $this->mOldRev->getComment();
547527 }
548528
549529 return true;
@@ -563,6 +543,7 @@
564544 return false;
565545 }
566546 if ( $this->mOldRev ) {
 547+ / FIXME: permission tests
567548 $this->mOldtext = $this->mOldRev->getText();
568549 if ( $this->mOldtext === false ) {
569550 return false;
Index: trunk/phase3/includes/RawPage.php
@@ -163,7 +163,7 @@
164164 if ( $rev ) {
165165 $lastmod = wfTimestamp( TS_RFC2822, $rev->getTimestamp() );
166166 header( "Last-modified: $lastmod" );
167 - $text = $rev->isDeleted() ? '' : $rev->getText();
 167+ $text = $rev->getText();
168168 } else
169169 $text = '';
170170 }
Index: trunk/phase3/includes/DefaultSettings.php
@@ -849,6 +849,10 @@
850850 / Permission to change users' group assignments
851851 $wgGroupPermissions['bureaucrat']['userrights'] = true;
852852
 853+/ Experimental permissions, not ready for production use
 854+/$wgGroupPermissions['sysop']['deleterevision'] = true;
 855+/$wgGroupPermissions['bureaucrat']['hiderevision'] = true;
 856+
853857 /**
854858 * The developer group is deprecated, but can be activated if need be
855859 * to use the 'lockdb' and 'unlockdb' special pages. Those require
Index: trunk/phase3/includes/PageHistory.php
@@ -222,93 +222,101 @@
223223
224224 /** @todo document */
225225 function historyLine( $row, $next, $counter = '', $notificationtimestamp = false, $latest = false, $firstInList = false ) {
 226+ global $wgUser;
 227+ $rev = new Revision( $row );
226228
227 - if ( 0 == $row->rev_user ) {
228 - $contribsPage =& Title::makeTitle( NS_SPECIAL, 'Contributions' );
229 - $ul = $this->mSkin->makeKnownLinkObj( $contribsPage,
230 - htmlspecialchars( $row->rev_user_text ),
231 - 'target=' . urlencode( $row->rev_user_text ) );
232 - } else {
233 - $userPage =& Title::makeTitle( NS_USER, $row->rev_user_text );
234 - $ul = $this->mSkin->makeLinkObj( $userPage , htmlspecialchars( $row->rev_user_text ) );
235 - }
 229+ $s = '<li>';
 230+ $curlink = $this->curLink( $rev, $latest );
 231+ $lastlink = $this->lastLink( $rev, $next, $counter );
 232+ $arbitrary = $this->diffButtons( $rev, $firstInList, $counter );
 233+ $link = $this->revLink( $rev );
 234+ $user = $this->mSkin->revUserLink( $rev );
236235
237 - $s = '<li>';
238 - /* This feature is not yet used according to schema */
239 - if( $row->rev_deleted ) {
240 - $s .= '<span class="history-deleted">';
 236+ $s .= "($curlink) ($lastlink) $arbitrary";
 237+
 238+ if( $wgUser->isAllowed( 'deleterevision' ) ) {
 239+ $revdel = Title::makeTitle( NS_SPECIAL, 'Revisiondelete' );
 240+ if( $firstInList ) {
 241+ / We don't currently handle well changing the top revision's settings
 242+ $del = wfMsgHtml( 'rev-delundel' );
 243+ } else {
 244+ $del = $this->mSkin->makeKnownLinkObj( $revdel,
 245+ wfMsg( 'rev-delundel' ),
 246+ 'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) .
 247+ '&oldid=' . urlencode( $rev->getId() ) );
 248+ }
 249+ $s .= "(<small>$del</small>) ";
241250 }
242 - $curlink = $this->curLink( $row, $latest );
243 - $lastlink = $this->lastLink( $row, $next, $counter );
244 - $arbitrary = $this->diffButtons( $row, $firstInList, $counter );
245 - $link = $this->revLink( $row );
 251+
 252+ $s .= " $link <span class='history-user'>$user</span>";
246253
247 - $s .= "($curlink) ($lastlink) $arbitrary $link <span class='history-user'>$ul</span>";
248 -
249254 if( $row->rev_minor_edit ) {
250255 $s .= ' ' . wfElement( 'span', array( 'class' => 'minor' ), wfMsgHtml( 'minoreditletter') );
251256 }
252257
253 - $s .= $this->mSkin->commentBlock( $row->rev_comment, $this->mTitle );
 258+ $s .= $this->mSkin->revComment( $rev );
254259 if ($notificationtimestamp && ($row->rev_timestamp >= $notificationtimestamp)) {
255260 $s .= ' <span class="updatedmarker">' . wfMsgHtml( 'updatedmarker' ) . '</span>';
256261 }
257 - if( $row->rev_deleted ) {
258 - $s .= '</span> ' . wfMsgHtml( 'deletedrev' );
 262+ if( $row->rev_deleted & MW_REV_DELETED_TEXT ) {
 263+ $s .= ' ' . wfMsgHtml( 'deletedrev' );
259264 }
260265 $s .= "</li>\n";
261266
262267 return $s;
263268 }
264 -
 269+
265270 /** @todo document */
266 - function revLink( $row ) {
 271+ function revLink( $rev ) {
267272 global $wgUser, $wgLang;
268 - $date = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->rev_timestamp), true );
269 - if( $row->rev_deleted && !$wgUser->isAllowed( 'undelete' ) ) {
270 - return $date;
 273+ $date = $wgLang->timeanddate( wfTimestamp(TS_MW, $rev->getTimestamp()), true );
 274+ if( $rev->userCan( MW_REV_DELETED_TEXT ) ) {
 275+ $link = $this->mSkin->makeKnownLinkObj(
 276+ $this->mTitle, $date, "oldid=" . $rev->getId() );
271277 } else {
272 - return $this->mSkin->makeKnownLinkObj(
273 - $this->mTitle, $date, "oldid={$row->rev_id}" );
 278+ $link = $date;
274279 }
 280+ if( $rev->isDeleted( MW_REV_DELETED_TEXT ) ) {
 281+ return '<span class="history-deleted">' . $link . '</span>';
 282+ }
 283+ return $link;
275284 }
276285
277286 /** @todo document */
278 - function curLink( $row, $latest ) {
 287+ function curLink( $rev, $latest ) {
279288 global $wgUser;
280289 $cur = wfMsgHtml( 'cur' );
281 - if( $latest
282 - || ( $row->rev_deleted && !$wgUser->isAllowed( 'undelete' ) ) ) {
 290+ if( $latest || !$rev->userCan( MW_REV_DELETED_TEXT ) ) {
283291 return $cur;
284292 } else {
285293 return $this->mSkin->makeKnownLinkObj(
286294 $this->mTitle, $cur,
287295 'diff=' . $this->getLatestID() .
288 - "&oldid={$row->rev_id}" );
 296+ "&oldid=" . $rev->getId() );
289297 }
290298 }
291299
292300 /** @todo document */
293 - function lastLink( $row, $next, $counter ) {
 301+ function lastLink( $rev, $next, $counter ) {
294302 global $wgUser;
295303 $last = htmlspecialchars( wfMsg( 'last' ) );
296304 if( is_null( $next ) ) {
297 - if( $row->rev_timestamp == $this->getEarliestOffset() ) {
 305+ if( $rev->getTimestamp() == $this->getEarliestOffset() ) {
298306 return $last;
299307 } else {
300308 / Cut off by paging; there are more behind us...
301309 return $this->mSkin->makeKnownLinkObj(
302310 $this->mTitle,
303311 $last,
304 - "diff={$row->rev_id}&oldid=prev" );
 312+ "diff=" . $rev->getId() . "&oldid=prev" );
305313 }
306 - } elseif( $row->rev_deleted && !$wgUser->isAllowed( 'undelete' ) ) {
 314+ } elseif( !$rev->userCan( MW_REV_DELETED_TEXT ) ) {
307315 return $last;
308316 } else {
309317 return $this->mSkin->makeKnownLinkObj(
310318 $this->mTitle,
311319 $last,
312 - "diff={$row->rev_id}&oldid={$next->rev_id}"
 320+ "diff=" . $rev->getId() . "&oldid={$next->rev_id}"
313321 /*,
314322 '',
315323 '',
@@ -317,17 +325,17 @@
318326 }
319327
320328 /** @todo document */
321 - function diffButtons( $row, $firstInList, $counter ) {
 329+ function diffButtons( $rev, $firstInList, $counter ) {
322330 global $wgUser;
323331 if( $this->linesonpage > 1) {
324332 $radio = array(
325333 'type' => 'radio',
326 - 'value' => $row->rev_id,
 334+ 'value' => $rev->getId(),
327335 # do we really need to flood this on every item?
328336 # 'title' => wfMsgHtml( 'selectolderversionfordiff' )
329337 );
330338
331 - if( $row->rev_deleted && !$wgUser->isAllowed( 'undelete' ) ) {
 339+ if( !$rev->userCan( MW_REV_DELETED_TEXT ) ) {
332340 $radio['disabled'] = 'disabled';
333341 }
334342
@@ -447,7 +455,7 @@
448456
449457 $res = $dbr->select(
450458 'revision',
451 - array('rev_id', 'rev_user', 'rev_comment', 'rev_user_text',
 459+ array('rev_id', 'rev_page', 'rev_text_id', 'rev_user', 'rev_comment', 'rev_user_text',
452460 'rev_timestamp', 'rev_minor_edit', 'rev_deleted'),
453461 array_merge(array("rev_page=$page_id"), $offsets),
454462 $fname,
Index: trunk/phase3/includes/SpecialPage.php
@@ -77,7 +77,8 @@
7878 'Userrights' => new SpecialPage( 'Userrights', 'userrights' ),
7979 'MIMEsearch' => new SpecialPage( 'MIMEsearch' ),
8080 'Unwatchedpages' => new SpecialPage( 'Unwatchedpages', 'unwatchedpages' ),
81 - 'Listredirects' => new SpecialPage( 'Listredirects' )
 81+ 'Listredirects' => new SpecialPage( 'Listredirects' ),
 82+ 'Revisiondelete' => new SpecialPage( 'Revisiondelete', 'deleterevision' ),
8283 );
8384
8485 if( !$wgDisableCounters ) {
Index: trunk/phase3/includes/LogPage.php
@@ -166,6 +166,7 @@
167167
168168 'delete/delete' => 'deletedarticle',
169169 'delete/restore' => 'undeletedarticle',
 170+ 'delete/revision' => 'revdelete-logentry',
170171 'upload/upload' => 'uploadedimage',
171172 'upload/revert' => 'uploadedimage',
172173 'move/move' => '1movedto2',
Index: trunk/phase3/RELEASE-NOTES
@@ -685,7 +685,12 @@
686686 unnecessary hidden UI work when watch/unwatch is performed on edit
687687 * Fixed bogus master fallback in external storage
688688 * (bug 5246) Add speak:none to "hiddenStructure" class in main.css
 689+* Further work on rev_deleted; changed to a bitfield with several data-hiding
 690+ options. Not yet ready for production use; Special:Revisiondelete is
 691+ incomplete, and the flags are not preserved across page deletion/undeletion.
 692+ To try it; add the 'deleterevision' permission to a privileged group.
689693
 694+
690695 === Caveats ===
691696
692697 Some output, particularly involving user-supplied inline HTML, may not
Index: trunk/phase3/languages/Messages.php
@@ -565,7 +565,39 @@
566566 'deletedrev' => '[deleted]',
567567 'histfirst' => 'Earliest',
568568 'histlast' => 'Latest',
 569+'rev-deleted-comment' => '(comment removed)',
 570+'rev-deleted-user' => '(username removed)',
 571+'rev-deleted-text-permission' => '<div class="mw-warning plainlinks">
 572+This page revision has been removed from the public archives.
 573+There may be details in the [{{fullurl:Special:Log/delete|page={{PAGENAMEE}}}} deletion log].
 574+</div>',
 575+'rev-deleted-text-view' => '<div class="mw-warning plainlinks">
 576+This page revision has been removed from the public archives.
 577+As an administrator on this site you can view it;
 578+there may be details in the [{{fullurl:Special:Log/delete|page={{PAGENAMEE}}}} deletion log].
 579+</div>',
 580+#'rev-delundel' => 'del/undel',
 581+'rev-delundel' => 'show/hide',
569582
 583+# Revision deletion
 584+#
 585+'revisiondelete' => 'Delete/undelete revisions',
 586+'revdelete-selected' => 'Selected revision of [[:$1]]:',
 587+'revdelete-text' => "Deleted revisions will still appear in the page history,
 588+but their text contents will be inaccessible to the public.
 589+
 590+Other admins on this wiki will still be able to access the hidden content and can
 591+undelete it again through this same interface, unless an additional restriction
 592+is placed by the site operators.",
 593+'revdelete-legend' => 'Set revision restrictions:',
 594+'revdelete-hide-text' => 'Hide revision text',
 595+'revdelete-hide-comment' => 'Hide edit comment',
 596+'revdelete-hide-user' => 'Hide editor\'s username/IP',
 597+'revdelete-hide-restricted' => 'Apply these restrictions to sysops as well as others',
 598+'revdelete-log' => 'Log comment:',
 599+'revdelete-submit' => 'Apply to selected revision',
 600+'revdelete-logentry' => 'changed revision visibility for [[$1]]',
 601+
570602 # Diffs
571603 #
572604 'difference' => '(Difference between revisions)',

Status & tagging log

Follow Lee on X/Twitter - Father, Husband, Serial builder creating AI, crypto, games & web tools. We are friends :) AI Will Come To Life!

Check out: eBank.nz (Art Generator) | Netwrck.com (AI Tools) | Text-Generator.io (AI API) | BitBank.nz (Crypto AI) | ReadingTime (Kids Reading) | RewordGame | BigMultiplayerChess | WebFiddle | How.nz | Helix AI Assistant