r66856 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r66855‎ | r66856 | r66857 >
Date:23:24, 24 May 2010
Author:reedy
Status:reverted
Tags:
Comment:
bug 18608 done in a fashion much closer to how I should've done it originally.

Latching onto bug 22061 output

Following up r62195 and r61125Part of bug 23332

Move everything that isn't the SpecialRevisionDelete class out of specials/SpecialRevisiondelete.php into RevisionDelete.php
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/RevisionDelete.php (added) (history)
  • /trunk/phase3/includes/specials/SpecialRevisiondelete.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/AutoLoader.php
@@ -200,6 +200,7 @@
201201 'Replacer' => 'includes/StringUtils.php',
202202 'ReverseChronologicalPager' => 'includes/Pager.php',
203203 'Revision' => 'includes/Revision.php',
 204+ 'RevisionDelete' => 'includes/RevisionDelete.php',
204205 'RSSFeed' => 'includes/Feed.php',
205206 'Sanitizer' => 'includes/Sanitizer.php',
206207 'SiteConfiguration' => 'includes/SiteConfiguration.php',
Index: trunk/phase3/includes/specials/SpecialRevisiondelete.php
@@ -596,1411 +596,4 @@
597597 array( 'value' => $bitfield, 'comment' => $reason )
598598 );
599599 }
600 -}
601 -
602 -/**
603 - * Temporary b/c interface, collection of static functions.
604 - * @ingroup SpecialPage
605 - */
606 -class RevisionDeleter {
607 - /**
608 - * Checks for a change in the bitfield for a certain option and updates the
609 - * provided array accordingly.
610 - *
611 - * @param $desc String: description to add to the array if the option was
612 - * enabled / disabled.
613 - * @param $field Integer: the bitmask describing the single option.
614 - * @param $diff Integer: the xor of the old and new bitfields.
615 - * @param $new Integer: the new bitfield
616 - * @param $arr Array: the array to update.
617 - */
618 - protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
619 - if( $diff & $field ) {
620 - $arr[ ( $new & $field ) ? 0 : 1 ][] = $desc;
621 - }
622 - }
623 -
624 - /**
625 - * Gets an array of message keys describing the changes made to the visibility
626 - * of the revision. If the resulting array is $arr, then $arr[0] will contain an
627 - * array of strings describing the items that were hidden, $arr[2] will contain
628 - * an array of strings describing the items that were unhidden, and $arr[3] will
629 - * contain an array with a single string, which can be one of "applied
630 - * restrictions to sysops", "removed restrictions from sysops", or null.
631 - *
632 - * @param $n Integer: the new bitfield.
633 - * @param $o Integer: the old bitfield.
634 - * @return An array as described above.
635 - */
636 - protected static function getChanges( $n, $o ) {
637 - $diff = $n ^ $o;
638 - $ret = array( 0 => array(), 1 => array(), 2 => array() );
639 - // Build bitfield changes in language
640 - self::checkItem( 'revdelete-content',
641 - Revision::DELETED_TEXT, $diff, $n, $ret );
642 - self::checkItem( 'revdelete-summary',
643 - Revision::DELETED_COMMENT, $diff, $n, $ret );
644 - self::checkItem( 'revdelete-uname',
645 - Revision::DELETED_USER, $diff, $n, $ret );
646 - // Restriction application to sysops
647 - if( $diff & Revision::DELETED_RESTRICTED ) {
648 - if( $n & Revision::DELETED_RESTRICTED )
649 - $ret[2][] = 'revdelete-restricted';
650 - else
651 - $ret[2][] = 'revdelete-unrestricted';
652 - }
653 - return $ret;
654 - }
655 -
656 - /**
657 - * Gets a log message to describe the given revision visibility change. This
658 - * message will be of the form "[hid {content, edit summary, username}];
659 - * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
660 - *
661 - * @param $count Integer: The number of effected revisions.
662 - * @param $nbitfield Integer: The new bitfield for the revision.
663 - * @param $obitfield Integer: The old bitfield for the revision.
664 - * @param $isForLog Boolean
665 - * @param $forContent Boolean
666 - */
667 - public static function getLogMessage( $count, $nbitfield, $obitfield, $isForLog = false, $forContent = false ) {
668 - global $wgLang, $wgContLang;
669 -
670 - $lang = $forContent ? $wgContLang : $wgLang;
671 - $msgFunc = $forContent ? "wfMsgForContent" : "wfMsg";
672 -
673 - $s = '';
674 - $changes = self::getChanges( $nbitfield, $obitfield );
675 - array_walk($changes, 'RevisionDeleter::expandMessageArray', $forContent);
676 -
677 - $changesText = array();
678 -
679 - if( count( $changes[0] ) ) {
680 - $changesText[] = $msgFunc( 'revdelete-hid', $lang->commaList( $changes[0] ) );
681 - }
682 - if( count( $changes[1] ) ) {
683 - $changesText[] = $msgFunc( 'revdelete-unhid', $lang->commaList( $changes[1] ) );
684 - }
685 -
686 - $s = $lang->semicolonList( $changesText );
687 - if( count( $changes[2] ) ) {
688 - $s .= $s ? ' (' . $changes[2][0] . ')' : ' ' . $changes[2][0];
689 - }
690 -
691 - $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
692 - return wfMsgExt( $msg, $forContent ? array( 'parsemag', 'content' ) : array( 'parsemag' ), $s, $lang->formatNum($count) );
693 - }
694 -
695 - private static function expandMessageArray(& $msg, $key, $forContent) {
696 - if ( is_array ($msg) ) {
697 - array_walk($msg, 'RevisionDeleter::expandMessageArray', $forContent);
698 - } else {
699 - if ( $forContent ) {
700 - $msg = wfMsgForContent($msg);
701 - } else {
702 - $msg = wfMsg($msg);
703 - }
704 - }
705 - }
706 -
707 - // Get DB field name for URL param...
708 - // Future code for other things may also track
709 - // other types of revision-specific changes.
710 - // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
711 - public static function getRelationType( $typeName ) {
712 - if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
713 - $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
714 - }
715 - if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
716 - $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
717 - $list = new $class( null, null, null );
718 - return $list->getIdField();
719 - } else {
720 - return null;
721 - }
722 - }
723 -
724 - // Checks if a revision still exists in the revision table.
725 - // If it doesn't, returns the corresponding ar_timestamp field
726 - // so that this key can be used instead.
727 - public static function checkRevisionExistence( $title, $revid ) {
728 - $dbr = wfGetDB( DB_SLAVE );
729 - $exists = $dbr->selectField( 'revision', '1',
730 - array( 'rev_id' => $revid ), __METHOD__ );
731 -
732 - if ( $exists ) {
733 - return true;
734 - }
735 -
736 - $timestamp = $dbr->selectField( 'archive', 'ar_timestamp',
737 - array( 'ar_namespace' => $title->getNamespace(),
738 - 'ar_title' => $title->getDBkey(),
739 - 'ar_rev_id' => $revid ), __METHOD__ );
740 -
741 - return $timestamp;
742 - }
743 -
744 - // Creates utility links for log entries.
745 - public static function getLogLinks( $title, $paramArray, $skin, $messages ) {
746 - global $wgLang;
747 -
748 - if( count($paramArray) >= 2 ) {
749 - // Different revision types use different URL params...
750 - $originalKey = $key = $paramArray[0];
751 - // $paramArray[1] is a CSV of the IDs
752 - $Ids = explode( ',', $paramArray[1] );
753 - $query = $paramArray[1];
754 - $revert = array();
755 -
756 - // For if undeleted revisions are found amidst deleted ones.
757 - $undeletedRevisions = array();
758 -
759 - // This is not going to work if some revs are deleted and some
760 - // aren't.
761 - if ($key == 'revision') {
762 - foreach( $Ids as $k => $id ) {
763 - $existResult =
764 - self::checkRevisionExistence( $title, $id );
765 -
766 - if ($existResult !== true) {
767 - $key = 'archive';
768 - $Ids[$k] = $existResult;
769 - } elseif ($key != $originalKey) {
770 - // Undeleted revision amidst deleted ones
771 - unset($Ids[$k]);
772 - $undeletedRevisions[] = $id;
773 - }
774 - }
775 - }
776 -
777 - // Diff link for single rev deletions
778 - if( count($Ids) == 1 && !count($undeletedRevisions) ) {
779 - // Live revision diffs...
780 - if( in_array( $key, array( 'oldid', 'revision' ) ) ) {
781 - $revert[] = $skin->link(
782 - $title,
783 - $messages['diff'],
784 - array(),
785 - array(
786 - 'diff' => intval( $Ids[0] ),
787 - 'unhide' => 1
788 - ),
789 - array( 'known', 'noclasses' )
790 - );
791 - // Deleted revision diffs...
792 - } else if( in_array( $key, array( 'artimestamp','archive' ) ) ) {
793 - $revert[] = $skin->link(
794 - SpecialPage::getTitleFor( 'Undelete' ),
795 - $messages['diff'],
796 - array(),
797 - array(
798 - 'target' => $title->getPrefixedDBKey(),
799 - 'diff' => 'prev',
800 - 'timestamp' => $Ids[0]
801 - ),
802 - array( 'known', 'noclasses' )
803 - );
804 - }
805 - }
806 -
807 - // View/modify link...
808 - if ( count($undeletedRevisions) ) {
809 - // FIXME THIS IS A HORRIBLE HORRIBLE HACK AND SHOULD DIE
810 - // It's not possible to pass a list of both deleted and
811 - // undeleted revisions to SpecialRevisionDelete, so we're
812 - // stuck with two links. See bug 23363.
813 - $restoreLinks = array();
814 -
815 - $restoreLinks[] = $skin->link(
816 - SpecialPage::getTitleFor( 'Revisiondelete' ),
817 - $messages['revdel-restore-visible'],
818 - array(),
819 - array(
820 - 'target' => $title->getPrefixedText(),
821 - 'type' => $originalKey,
822 - 'ids' => implode(',', $undeletedRevisions),
823 - ),
824 - array( 'known', 'noclasses' )
825 - );
826 -
827 - $restoreLinks[] = $skin->link(
828 - SpecialPage::getTitleFor( 'Revisiondelete' ),
829 - $messages['revdel-restore-deleted'],
830 - array(),
831 - array(
832 - 'target' => $title->getPrefixedText(),
833 - 'type' => $key,
834 - 'ids' => implode(',', $Ids),
835 - ),
836 - array( 'known', 'noclasses' )
837 - );
838 -
839 - $revert[] = $messages['revdel-restore'] . ' [' .
840 - $wgLang->pipeList( $restoreLinks ) . ']';
841 - } else {
842 - $revert[] = $skin->link(
843 - SpecialPage::getTitleFor( 'Revisiondelete' ),
844 - $messages['revdel-restore'],
845 - array(),
846 - array(
847 - 'target' => $title->getPrefixedText(),
848 - 'type' => $key,
849 - 'ids' => implode(',', $Ids),
850 - ),
851 - array( 'known', 'noclasses' )
852 - );
853 - }
854 -
855 - // Pipe links
856 - $revert = wfMsg( 'parentheses', $wgLang->pipeList( $revert ) );
857 - }
858 - return $revert;
859 - }
860 -}
861 -
862 -/**
863 - * Abstract base class for a list of deletable items
864 - */
865 -abstract class RevDel_List {
866 - var $special, $title, $ids, $res, $current;
867 - var $type = null; // override this
868 - var $idField = null; // override this
869 - var $dateField = false; // override this
870 - var $authorIdField = false; // override this
871 - var $authorNameField = false; // override this
872 -
873 - /**
874 - * @param $special The parent SpecialPage
875 - * @param $title The target title
876 - * @param $ids Array of IDs
877 - */
878 - public function __construct( $special, $title, $ids ) {
879 - $this->special = $special;
880 - $this->title = $title;
881 - $this->ids = $ids;
882 - }
883 -
884 - /**
885 - * Get the internal type name of this list. Equal to the table name.
886 - */
887 - public function getType() {
888 - return $this->type;
889 - }
890 -
891 - /**
892 - * Get the DB field name associated with the ID list
893 - */
894 - public function getIdField() {
895 - return $this->idField;
896 - }
897 -
898 - /**
899 - * Get the DB field name storing timestamps
900 - */
901 - public function getTimestampField() {
902 - return $this->dateField;
903 - }
904 -
905 - /**
906 - * Get the DB field name storing user ids
907 - */
908 - public function getAuthorIdField() {
909 - return $this->authorIdField;
910 - }
911 -
912 - /**
913 - * Get the DB field name storing user names
914 - */
915 - public function getAuthorNameField() {
916 - return $this->authorNameField;
917 - }
918 - /**
919 - * Set the visibility for the revisions in this list. Logging and
920 - * transactions are done here.
921 - *
922 - * @param $params Associative array of parameters. Members are:
923 - * value: The integer value to set the visibility to
924 - * comment: The log comment.
925 - * @return Status
926 - */
927 - public function setVisibility( $params ) {
928 - $bitPars = $params['value'];
929 - $comment = $params['comment'];
930 -
931 - $this->res = false;
932 - $dbw = wfGetDB( DB_MASTER );
933 - $this->doQuery( $dbw );
934 - $dbw->begin();
935 - $status = Status::newGood();
936 - $missing = array_flip( $this->ids );
937 - $this->clearFileOps();
938 - $idsForLog = array();
939 - $authorIds = $authorIPs = array();
940 -
941 - for ( $this->reset(); $this->current(); $this->next() ) {
942 - $item = $this->current();
943 - unset( $missing[ $item->getId() ] );
944 -
945 - $oldBits = $item->getBits();
946 - // Build the actual new rev_deleted bitfield
947 - $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits );
948 -
949 - if ( $oldBits == $newBits ) {
950 - $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
951 - $status->failCount++;
952 - continue;
953 - } elseif ( $oldBits == 0 && $newBits != 0 ) {
954 - $opType = 'hide';
955 - } elseif ( $oldBits != 0 && $newBits == 0 ) {
956 - $opType = 'show';
957 - } else {
958 - $opType = 'modify';
959 - }
960 -
961 - if ( $item->isHideCurrentOp( $newBits ) ) {
962 - // Cannot hide current version text
963 - $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
964 - $status->failCount++;
965 - continue;
966 - }
967 - if ( !$item->canView() ) {
968 - // Cannot access this revision
969 - $msg = ($opType == 'show') ?
970 - 'revdelete-show-no-access' : 'revdelete-modify-no-access';
971 - $status->error( $msg, $item->formatDate(), $item->formatTime() );
972 - $status->failCount++;
973 - continue;
974 - }
975 - // Cannot just "hide from Sysops" without hiding any fields
976 - if( $newBits == Revision::DELETED_RESTRICTED ) {
977 - $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
978 - $status->failCount++;
979 - continue;
980 - }
981 -
982 - // Update the revision
983 - $ok = $item->setBits( $newBits );
984 -
985 - if ( $ok ) {
986 - $idsForLog[] = $item->getId();
987 - $status->successCount++;
988 - if( $item->getAuthorId() > 0 ) {
989 - $authorIds[] = $item->getAuthorId();
990 - } else if( IP::isIPAddress( $item->getAuthorName() ) ) {
991 - $authorIPs[] = $item->getAuthorName();
992 - }
993 - } else {
994 - $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
995 - $status->failCount++;
996 - }
997 - }
998 -
999 - // Handle missing revisions
1000 - foreach ( $missing as $id => $unused ) {
1001 - $status->error( 'revdelete-modify-missing', $id );
1002 - $status->failCount++;
1003 - }
1004 -
1005 - if ( $status->successCount == 0 ) {
1006 - $status->ok = false;
1007 - $dbw->rollback();
1008 - return $status;
1009 - }
1010 -
1011 - // Save success count
1012 - $successCount = $status->successCount;
1013 -
1014 - // Move files, if there are any
1015 - $status->merge( $this->doPreCommitUpdates() );
1016 - if ( !$status->isOK() ) {
1017 - // Fatal error, such as no configured archive directory
1018 - $dbw->rollback();
1019 - return $status;
1020 - }
1021 -
1022 - // Log it
1023 - $this->updateLog( array(
1024 - 'title' => $this->title,
1025 - 'count' => $successCount,
1026 - 'newBits' => $newBits,
1027 - 'oldBits' => $oldBits,
1028 - 'comment' => $comment,
1029 - 'ids' => $idsForLog,
1030 - 'authorIds' => $authorIds,
1031 - 'authorIPs' => $authorIPs
1032 - ) );
1033 - $dbw->commit();
1034 -
1035 - // Clear caches
1036 - $status->merge( $this->doPostCommitUpdates() );
1037 - return $status;
1038 - }
1039 -
1040 - /**
1041 - * Reload the list data from the master DB. This can be done after setVisibility()
1042 - * to allow $item->getHTML() to show the new data.
1043 - */
1044 - function reloadFromMaster() {
1045 - $dbw = wfGetDB( DB_MASTER );
1046 - $this->res = $this->doQuery( $dbw );
1047 - }
1048 -
1049 - /**
1050 - * Record a log entry on the action
1051 - * @param $params Associative array of parameters:
1052 - * newBits: The new value of the *_deleted bitfield
1053 - * oldBits: The old value of the *_deleted bitfield.
1054 - * title: The target title
1055 - * ids: The ID list
1056 - * comment: The log comment
1057 - * authorsIds: The array of the user IDs of the offenders
1058 - * authorsIPs: The array of the IP/anon user offenders
1059 - */
1060 - protected function updateLog( $params ) {
1061 - // Get the URL param's corresponding DB field
1062 - $field = RevisionDeleter::getRelationType( $this->getType() );
1063 - if( !$field ) {
1064 - throw new MWException( "Bad log URL param type!" );
1065 - }
1066 - // Put things hidden from sysops in the oversight log
1067 - if ( ( $params['newBits'] | $params['oldBits'] ) & $this->getSuppressBit() ) {
1068 - $logType = 'suppress';
1069 - } else {
1070 - $logType = 'delete';
1071 - }
1072 - // Add params for effected page and ids
1073 - $logParams = $this->getLogParams( $params );
1074 - // Actually add the deletion log entry
1075 - $log = new LogPage( $logType );
1076 - $logid = $log->addEntry( $this->getLogAction(), $params['title'],
1077 - $params['comment'], $logParams );
1078 - // Allow for easy searching of deletion log items for revision/log items
1079 - $log->addRelations( $field, $params['ids'], $logid );
1080 - $log->addRelations( 'target_author_id', $params['authorIds'], $logid );
1081 - $log->addRelations( 'target_author_ip', $params['authorIPs'], $logid );
1082 - }
1083 -
1084 - /**
1085 - * Get the log action for this list type
1086 - */
1087 - public function getLogAction() {
1088 - return 'revision';
1089 - }
1090 -
1091 - /**
1092 - * Get log parameter array.
1093 - * @param $params Associative array of log parameters, same as updateLog()
1094 - * @return array
1095 - */
1096 - public function getLogParams( $params ) {
1097 - return array(
1098 - $this->getType(),
1099 - implode( ',', $params['ids'] ),
1100 - "ofield={$params['oldBits']}",
1101 - "nfield={$params['newBits']}"
1102 - );
1103 - }
1104 -
1105 - /**
1106 - * Initialise the current iteration pointer
1107 - */
1108 - protected function initCurrent() {
1109 - $row = $this->res->current();
1110 - if ( $row ) {
1111 - $this->current = $this->newItem( $row );
1112 - } else {
1113 - $this->current = false;
1114 - }
1115 - }
1116 -
1117 - /**
1118 - * Start iteration. This must be called before current() or next().
1119 - * @return First list item
1120 - */
1121 - public function reset() {
1122 - if ( !$this->res ) {
1123 - $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) );
1124 - } else {
1125 - $this->res->rewind();
1126 - }
1127 - $this->initCurrent();
1128 - return $this->current;
1129 - }
1130 -
1131 - /**
1132 - * Get the current list item, or false if we are at the end
1133 - */
1134 - public function current() {
1135 - return $this->current;
1136 - }
1137 -
1138 - /**
1139 - * Move the iteration pointer to the next list item, and return it.
1140 - */
1141 - public function next() {
1142 - $this->res->next();
1143 - $this->initCurrent();
1144 - return $this->current;
1145 - }
1146 -
1147 - /**
1148 - * Get the number of items in the list.
1149 - */
1150 - public function length() {
1151 - if( !$this->res ) {
1152 - return 0;
1153 - } else {
1154 - return $this->res->numRows();
1155 - }
1156 - }
1157 -
1158 - /**
1159 - * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates()
1160 - * STUB
1161 - */
1162 - public function clearFileOps() {
1163 - }
1164 -
1165 - /**
1166 - * A hook for setVisibility(): do batch updates pre-commit.
1167 - * STUB
1168 - * @return Status
1169 - */
1170 - public function doPreCommitUpdates() {
1171 - return Status::newGood();
1172 - }
1173 -
1174 - /**
1175 - * A hook for setVisibility(): do any necessary updates post-commit.
1176 - * STUB
1177 - * @return Status
1178 - */
1179 - public function doPostCommitUpdates() {
1180 - return Status::newGood();
1181 - }
1182 -
1183 - /**
1184 - * Create an item object from a DB result row
1185 - * @param $row stdclass
1186 - */
1187 - abstract public function newItem( $row );
1188 -
1189 - /**
1190 - * Do the DB query to iterate through the objects.
1191 - * @param $db Database object to use for the query
1192 - */
1193 - abstract public function doQuery( $db );
1194 -
1195 - /**
1196 - * Get the integer value of the flag used for suppression
1197 - */
1198 - abstract public function getSuppressBit();
1199 -}
1200 -
1201 -/**
1202 - * Abstract base class for deletable items
1203 - */
1204 -abstract class RevDel_Item {
1205 - /** The parent SpecialPage */
1206 - var $special;
1207 -
1208 - /** The parent RevDel_List */
1209 - var $list;
1210 -
1211 - /** The DB result row */
1212 - var $row;
1213 -
1214 - /**
1215 - * @param $list RevDel_List
1216 - * @param $row DB result row
1217 - */
1218 - public function __construct( $list, $row ) {
1219 - $this->special = $list->special;
1220 - $this->list = $list;
1221 - $this->row = $row;
1222 - }
1223 -
1224 - /**
1225 - * Get the ID, as it would appear in the ids URL parameter
1226 - */
1227 - public function getId() {
1228 - $field = $this->list->getIdField();
1229 - return $this->row->$field;
1230 - }
1231 -
1232 - /**
1233 - * Get the date, formatted with $wgLang
1234 - */
1235 - public function formatDate() {
1236 - global $wgLang;
1237 - return $wgLang->date( $this->getTimestamp() );
1238 - }
1239 -
1240 - /**
1241 - * Get the time, formatted with $wgLang
1242 - */
1243 - public function formatTime() {
1244 - global $wgLang;
1245 - return $wgLang->time( $this->getTimestamp() );
1246 - }
1247 -
1248 - /**
1249 - * Get the timestamp in MW 14-char form
1250 - */
1251 - public function getTimestamp() {
1252 - $field = $this->list->getTimestampField();
1253 - return wfTimestamp( TS_MW, $this->row->$field );
1254 - }
1255 -
1256 - /**
1257 - * Get the author user ID
1258 - */
1259 - public function getAuthorId() {
1260 - $field = $this->list->getAuthorIdField();
1261 - return intval( $this->row->$field );
1262 - }
1263 -
1264 - /**
1265 - * Get the author user name
1266 - */
1267 - public function getAuthorName() {
1268 - $field = $this->list->getAuthorNameField();
1269 - return strval( $this->row->$field );
1270 - }
1271 -
1272 - /**
1273 - * Returns true if the item is "current", and the operation to set the given
1274 - * bits can't be executed for that reason
1275 - * STUB
1276 - */
1277 - public function isHideCurrentOp( $newBits ) {
1278 - return false;
1279 - }
1280 -
1281 - /**
1282 - * Returns true if the current user can view the item
1283 - */
1284 - abstract public function canView();
1285 -
1286 - /**
1287 - * Returns true if the current user can view the item text/file
1288 - */
1289 - abstract public function canViewContent();
1290 -
1291 - /**
1292 - * Get the current deletion bitfield value
1293 - */
1294 - abstract public function getBits();
1295 -
1296 - /**
1297 - * Get the HTML of the list item. Should be include <li></li> tags.
1298 - * This is used to show the list in HTML form, by the special page.
1299 - */
1300 - abstract public function getHTML();
1301 -
1302 - /**
1303 - * Set the visibility of the item. This should do any necessary DB queries.
1304 - *
1305 - * The DB update query should have a condition which forces it to only update
1306 - * if the value in the DB matches the value fetched earlier with the SELECT.
1307 - * If the update fails because it did not match, the function should return
1308 - * false. This prevents concurrency problems.
1309 - *
1310 - * @return boolean success
1311 - */
1312 - abstract public function setBits( $newBits );
1313 -}
1314 -
1315 -/**
1316 - * List for revision table items
1317 - */
1318 -class RevDel_RevisionList extends RevDel_List {
1319 - var $currentRevId;
1320 - var $type = 'revision';
1321 - var $idField = 'rev_id';
1322 - var $dateField = 'rev_timestamp';
1323 - var $authorIdField = 'rev_user';
1324 - var $authorNameField = 'rev_user_text';
1325 -
1326 - public function doQuery( $db ) {
1327 - $ids = array_map( 'intval', $this->ids );
1328 - return $db->select( array('revision','page'), '*',
1329 - array(
1330 - 'rev_page' => $this->title->getArticleID(),
1331 - 'rev_id' => $ids,
1332 - 'rev_page = page_id'
1333 - ),
1334 - __METHOD__,
1335 - array( 'ORDER BY' => 'rev_id DESC' )
1336 - );
1337 - }
1338 -
1339 - public function newItem( $row ) {
1340 - return new RevDel_RevisionItem( $this, $row );
1341 - }
1342 -
1343 - public function getCurrent() {
1344 - if ( is_null( $this->currentRevId ) ) {
1345 - $dbw = wfGetDB( DB_MASTER );
1346 - $this->currentRevId = $dbw->selectField(
1347 - 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
1348 - }
1349 - return $this->currentRevId;
1350 - }
1351 -
1352 - public function getSuppressBit() {
1353 - return Revision::DELETED_RESTRICTED;
1354 - }
1355 -
1356 - public function doPreCommitUpdates() {
1357 - $this->title->invalidateCache();
1358 - return Status::newGood();
1359 - }
1360 -
1361 - public function doPostCommitUpdates() {
1362 - $this->title->purgeSquid();
1363 - // Extensions that require referencing previous revisions may need this
1364 - wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$this->title ) );
1365 - return Status::newGood();
1366 - }
1367 -}
1368 -
1369 -/**
1370 - * Item class for a revision table row
1371 - */
1372 -class RevDel_RevisionItem extends RevDel_Item {
1373 - var $revision;
1374 -
1375 - public function __construct( $list, $row ) {
1376 - parent::__construct( $list, $row );
1377 - $this->revision = new Revision( $row );
1378 - }
1379 -
1380 - public function canView() {
1381 - return $this->revision->userCan( Revision::DELETED_RESTRICTED );
1382 - }
1383 -
1384 - public function canViewContent() {
1385 - return $this->revision->userCan( Revision::DELETED_TEXT );
1386 - }
1387 -
1388 - public function getBits() {
1389 - return $this->revision->mDeleted;
1390 - }
1391 -
1392 - public function setBits( $bits ) {
1393 - $dbw = wfGetDB( DB_MASTER );
1394 - // Update revision table
1395 - $dbw->update( 'revision',
1396 - array( 'rev_deleted' => $bits ),
1397 - array(
1398 - 'rev_id' => $this->revision->getId(),
1399 - 'rev_page' => $this->revision->getPage(),
1400 - 'rev_deleted' => $this->getBits()
1401 - ),
1402 - __METHOD__
1403 - );
1404 - if ( !$dbw->affectedRows() ) {
1405 - // Concurrent fail!
1406 - return false;
1407 - }
1408 - // Update recentchanges table
1409 - $dbw->update( 'recentchanges',
1410 - array(
1411 - 'rc_deleted' => $bits,
1412 - 'rc_patrolled' => 1
1413 - ),
1414 - array(
1415 - 'rc_this_oldid' => $this->revision->getId(), // condition
1416 - // non-unique timestamp index
1417 - 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
1418 - ),
1419 - __METHOD__
1420 - );
1421 - return true;
1422 - }
1423 -
1424 - public function isDeleted() {
1425 - return $this->revision->isDeleted( Revision::DELETED_TEXT );
1426 - }
1427 -
1428 - public function isHideCurrentOp( $newBits ) {
1429 - return ( $newBits & Revision::DELETED_TEXT )
1430 - && $this->list->getCurrent() == $this->getId();
1431 - }
1432 -
1433 - /**
1434 - * Get the HTML link to the revision text.
1435 - * Overridden by RevDel_ArchiveItem.
1436 - */
1437 - protected function getRevisionLink() {
1438 - global $wgLang;
1439 - $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
1440 - if ( $this->isDeleted() && !$this->canViewContent() ) {
1441 - return $date;
1442 - }
1443 - return $this->special->skin->link(
1444 - $this->list->title,
1445 - $date,
1446 - array(),
1447 - array(
1448 - 'oldid' => $this->revision->getId(),
1449 - 'unhide' => 1
1450 - )
1451 - );
1452 - }
1453 -
1454 - /**
1455 - * Get the HTML link to the diff.
1456 - * Overridden by RevDel_ArchiveItem
1457 - */
1458 - protected function getDiffLink() {
1459 - if ( $this->isDeleted() && !$this->canViewContent() ) {
1460 - return wfMsgHtml('diff');
1461 - } else {
1462 - return
1463 - $this->special->skin->link(
1464 - $this->list->title,
1465 - wfMsgHtml('diff'),
1466 - array(),
1467 - array(
1468 - 'diff' => $this->revision->getId(),
1469 - 'oldid' => 'prev',
1470 - 'unhide' => 1
1471 - ),
1472 - array(
1473 - 'known',
1474 - 'noclasses'
1475 - )
1476 - );
1477 - }
1478 - }
1479 -
1480 - public function getHTML() {
1481 - $difflink = $this->getDiffLink();
1482 - $revlink = $this->getRevisionLink();
1483 - $userlink = $this->special->skin->revUserLink( $this->revision );
1484 - $comment = $this->special->skin->revComment( $this->revision );
1485 - if ( $this->isDeleted() ) {
1486 - $revlink = "<span class=\"history-deleted\">$revlink</span>";
1487 - }
1488 - return "<li>($difflink) $revlink $userlink $comment</li>";
1489 - }
1490 -}
1491 -
1492 -/**
1493 - * List for archive table items, i.e. revisions deleted via action=delete
1494 - */
1495 -class RevDel_ArchiveList extends RevDel_RevisionList {
1496 - var $type = 'archive';
1497 - var $idField = 'ar_timestamp';
1498 - var $dateField = 'ar_timestamp';
1499 - var $authorIdField = 'ar_user';
1500 - var $authorNameField = 'ar_user_text';
1501 -
1502 - public function doQuery( $db ) {
1503 - $timestamps = array();
1504 - foreach ( $this->ids as $id ) {
1505 - $timestamps[] = $db->timestamp( $id );
1506 - }
1507 - return $db->select( 'archive', '*',
1508 - array(
1509 - 'ar_namespace' => $this->title->getNamespace(),
1510 - 'ar_title' => $this->title->getDBkey(),
1511 - 'ar_timestamp' => $timestamps
1512 - ),
1513 - __METHOD__,
1514 - array( 'ORDER BY' => 'ar_timestamp DESC' )
1515 - );
1516 - }
1517 -
1518 - public function newItem( $row ) {
1519 - return new RevDel_ArchiveItem( $this, $row );
1520 - }
1521 -
1522 - public function doPreCommitUpdates() {
1523 - return Status::newGood();
1524 - }
1525 -
1526 - public function doPostCommitUpdates() {
1527 - return Status::newGood();
1528 - }
1529 -}
1530 -
1531 -/**
1532 - * Item class for a archive table row
1533 - */
1534 -class RevDel_ArchiveItem extends RevDel_RevisionItem {
1535 - public function __construct( $list, $row ) {
1536 - RevDel_Item::__construct( $list, $row );
1537 - $this->revision = Revision::newFromArchiveRow( $row,
1538 - array( 'page' => $this->list->title->getArticleId() ) );
1539 - }
1540 -
1541 - public function getId() {
1542 - # Convert DB timestamp to MW timestamp
1543 - return $this->revision->getTimestamp();
1544 - }
1545 -
1546 - public function setBits( $bits ) {
1547 - $dbw = wfGetDB( DB_MASTER );
1548 - $dbw->update( 'archive',
1549 - array( 'ar_deleted' => $bits ),
1550 - array( 'ar_namespace' => $this->list->title->getNamespace(),
1551 - 'ar_title' => $this->list->title->getDBkey(),
1552 - // use timestamp for index
1553 - 'ar_timestamp' => $this->row->ar_timestamp,
1554 - 'ar_rev_id' => $this->row->ar_rev_id,
1555 - 'ar_deleted' => $this->getBits()
1556 - ),
1557 - __METHOD__ );
1558 - return (bool)$dbw->affectedRows();
1559 - }
1560 -
1561 - protected function getRevisionLink() {
1562 - global $wgLang;
1563 - $undelete = SpecialPage::getTitleFor( 'Undelete' );
1564 - $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
1565 - if ( $this->isDeleted() && !$this->canViewContent() ) {
1566 - return $date;
1567 - }
1568 - return $this->special->skin->link( $undelete, $date, array(),
1569 - array(
1570 - 'target' => $this->list->title->getPrefixedText(),
1571 - 'timestamp' => $this->revision->getTimestamp()
1572 - ) );
1573 - }
1574 -
1575 - protected function getDiffLink() {
1576 - if ( $this->isDeleted() && !$this->canViewContent() ) {
1577 - return wfMsgHtml( 'diff' );
1578 - }
1579 - $undelete = SpecialPage::getTitleFor( 'Undelete' );
1580 - return $this->special->skin->link( $undelete, wfMsgHtml('diff'), array(),
1581 - array(
1582 - 'target' => $this->list->title->getPrefixedText(),
1583 - 'diff' => 'prev',
1584 - 'timestamp' => $this->revision->getTimestamp()
1585 - ) );
1586 - }
1587 -}
1588 -
1589 -/**
1590 - * List for oldimage table items
1591 - */
1592 -class RevDel_FileList extends RevDel_List {
1593 - var $type = 'oldimage';
1594 - var $idField = 'oi_archive_name';
1595 - var $dateField = 'oi_timestamp';
1596 - var $authorIdField = 'oi_user';
1597 - var $authorNameField = 'oi_user_text';
1598 - var $storeBatch, $deleteBatch, $cleanupBatch;
1599 -
1600 - public function doQuery( $db ) {
1601 - $archiveName = array();
1602 - foreach( $this->ids as $timestamp ) {
1603 - $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
1604 - }
1605 - return $db->select( 'oldimage', '*',
1606 - array(
1607 - 'oi_name' => $this->title->getDBkey(),
1608 - 'oi_archive_name' => $archiveNames
1609 - ),
1610 - __METHOD__,
1611 - array( 'ORDER BY' => 'oi_timestamp DESC' )
1612 - );
1613 - }
1614 -
1615 - public function newItem( $row ) {
1616 - return new RevDel_FileItem( $this, $row );
1617 - }
1618 -
1619 - public function clearFileOps() {
1620 - $this->deleteBatch = array();
1621 - $this->storeBatch = array();
1622 - $this->cleanupBatch = array();
1623 - }
1624 -
1625 - public function doPreCommitUpdates() {
1626 - $status = Status::newGood();
1627 - $repo = RepoGroup::singleton()->getLocalRepo();
1628 - if ( $this->storeBatch ) {
1629 - $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
1630 - }
1631 - if ( !$status->isOK() ) {
1632 - return $status;
1633 - }
1634 - if ( $this->deleteBatch ) {
1635 - $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
1636 - }
1637 - if ( !$status->isOK() ) {
1638 - // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
1639 - // modified (but destined for rollback) causes data loss
1640 - return $status;
1641 - }
1642 - if ( $this->cleanupBatch ) {
1643 - $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
1644 - }
1645 - return $status;
1646 - }
1647 -
1648 - public function doPostCommitUpdates() {
1649 - $file = wfLocalFile( $this->title );
1650 - $file->purgeCache();
1651 - $file->purgeDescription();
1652 - return Status::newGood();
1653 - }
1654 -
1655 - public function getSuppressBit() {
1656 - return File::DELETED_RESTRICTED;
1657 - }
1658 -}
1659 -
1660 -/**
1661 - * Item class for an oldimage table row
1662 - */
1663 -class RevDel_FileItem extends RevDel_Item {
1664 - var $file;
1665 -
1666 - public function __construct( $list, $row ) {
1667 - parent::__construct( $list, $row );
1668 - $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
1669 - }
1670 -
1671 - public function getId() {
1672 - $parts = explode( '!', $this->row->oi_archive_name );
1673 - return $parts[0];
1674 - }
1675 -
1676 - public function canView() {
1677 - return $this->file->userCan( File::DELETED_RESTRICTED );
1678 - }
1679 -
1680 - public function canViewContent() {
1681 - return $this->file->userCan( File::DELETED_FILE );
1682 - }
1683 -
1684 - public function getBits() {
1685 - return $this->file->getVisibility();
1686 - }
1687 -
1688 - public function setBits( $bits ) {
1689 - # Queue the file op
1690 - # FIXME: move to LocalFile.php
1691 - if ( $this->isDeleted() ) {
1692 - if ( $bits & File::DELETED_FILE ) {
1693 - # Still deleted
1694 - } else {
1695 - # Newly undeleted
1696 - $key = $this->file->getStorageKey();
1697 - $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
1698 - $this->list->storeBatch[] = array(
1699 - $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
1700 - 'public',
1701 - $this->file->getRel()
1702 - );
1703 - $this->list->cleanupBatch[] = $key;
1704 - }
1705 - } elseif ( $bits & File::DELETED_FILE ) {
1706 - # Newly deleted
1707 - $key = $this->file->getStorageKey();
1708 - $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
1709 - $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
1710 - }
1711 -
1712 - # Do the database operations
1713 - $dbw = wfGetDB( DB_MASTER );
1714 - $dbw->update( 'oldimage',
1715 - array( 'oi_deleted' => $bits ),
1716 - array(
1717 - 'oi_name' => $this->row->oi_name,
1718 - 'oi_timestamp' => $this->row->oi_timestamp,
1719 - 'oi_deleted' => $this->getBits()
1720 - ),
1721 - __METHOD__
1722 - );
1723 - return (bool)$dbw->affectedRows();
1724 - }
1725 -
1726 - public function isDeleted() {
1727 - return $this->file->isDeleted( File::DELETED_FILE );
1728 - }
1729 -
1730 - /**
1731 - * Get the link to the file.
1732 - * Overridden by RevDel_ArchivedFileItem.
1733 - */
1734 - protected function getLink() {
1735 - global $wgLang, $wgUser;
1736 - $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
1737 - if ( $this->isDeleted() ) {
1738 - # Hidden files...
1739 - if ( !$this->canViewContent() ) {
1740 - $link = $date;
1741 - } else {
1742 - $link = $this->special->skin->link(
1743 - $this->special->getTitle(),
1744 - $date, array(),
1745 - array(
1746 - 'target' => $this->list->title->getPrefixedText(),
1747 - 'file' => $this->file->getArchiveName(),
1748 - 'token' => $wgUser->editToken( $this->file->getArchiveName() )
1749 - )
1750 - );
1751 - }
1752 - return '<span class="history-deleted">' . $link . '</span>';
1753 - } else {
1754 - # Regular files...
1755 - $url = $this->file->getUrl();
1756 - return Xml::element( 'a', array( 'href' => $this->file->getUrl() ), $date );
1757 - }
1758 - }
1759 - /**
1760 - * Generate a user tool link cluster if the current user is allowed to view it
1761 - * @return string HTML
1762 - */
1763 - protected function getUserTools() {
1764 - if( $this->file->userCan( Revision::DELETED_USER ) ) {
1765 - $link = $this->special->skin->userLink( $this->file->user, $this->file->user_text ) .
1766 - $this->special->skin->userToolLinks( $this->file->user, $this->file->user_text );
1767 - } else {
1768 - $link = wfMsgHtml( 'rev-deleted-user' );
1769 - }
1770 - if( $this->file->isDeleted( Revision::DELETED_USER ) ) {
1771 - return '<span class="history-deleted">' . $link . '</span>';
1772 - }
1773 - return $link;
1774 - }
1775 -
1776 - /**
1777 - * Wrap and format the file's comment block, if the current
1778 - * user is allowed to view it.
1779 - *
1780 - * @return string HTML
1781 - */
1782 - protected function getComment() {
1783 - if( $this->file->userCan( File::DELETED_COMMENT ) ) {
1784 - $block = $this->special->skin->commentBlock( $this->file->description );
1785 - } else {
1786 - $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
1787 - }
1788 - if( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
1789 - return "<span class=\"history-deleted\">$block</span>";
1790 - }
1791 - return $block;
1792 - }
1793 -
1794 - public function getHTML() {
1795 - global $wgLang;
1796 - $data =
1797 - wfMsg(
1798 - 'widthheight',
1799 - $wgLang->formatNum( $this->file->getWidth() ),
1800 - $wgLang->formatNum( $this->file->getHeight() )
1801 - ) .
1802 - ' (' .
1803 - wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file->getSize() ) ) .
1804 - ')';
1805 - $pageLink = $this->getLink();
1806 -
1807 - return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
1808 - $data . ' ' . $this->getComment(). '</li>';
1809 - }
1810 -}
1811 -
1812 -/**
1813 - * List for filearchive table items
1814 - */
1815 -class RevDel_ArchivedFileList extends RevDel_FileList {
1816 - var $type = 'filearchive';
1817 - var $idField = 'fa_id';
1818 - var $dateField = 'fa_timestamp';
1819 - var $authorIdField = 'fa_user';
1820 - var $authorNameField = 'fa_user_text';
1821 -
1822 - public function doQuery( $db ) {
1823 - $ids = array_map( 'intval', $this->ids );
1824 - return $db->select( 'filearchive', '*',
1825 - array(
1826 - 'fa_name' => $this->title->getDBkey(),
1827 - 'fa_id' => $ids
1828 - ),
1829 - __METHOD__,
1830 - array( 'ORDER BY' => 'fa_id DESC' )
1831 - );
1832 - }
1833 -
1834 - public function newItem( $row ) {
1835 - return new RevDel_ArchivedFileItem( $this, $row );
1836 - }
1837 -}
1838 -
1839 -/**
1840 - * Item class for a filearchive table row
1841 - */
1842 -class RevDel_ArchivedFileItem extends RevDel_FileItem {
1843 - public function __construct( $list, $row ) {
1844 - RevDel_Item::__construct( $list, $row );
1845 - $this->file = ArchivedFile::newFromRow( $row );
1846 - }
1847 -
1848 - public function getId() {
1849 - return $this->row->fa_id;
1850 - }
1851 -
1852 - public function setBits( $bits ) {
1853 - $dbw = wfGetDB( DB_MASTER );
1854 - $dbw->update( 'filearchive',
1855 - array( 'fa_deleted' => $bits ),
1856 - array(
1857 - 'fa_id' => $this->row->fa_id,
1858 - 'fa_deleted' => $this->getBits(),
1859 - ),
1860 - __METHOD__
1861 - );
1862 - return (bool)$dbw->affectedRows();
1863 - }
1864 -
1865 - protected function getLink() {
1866 - global $wgLang, $wgUser;
1867 - $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
1868 - $undelete = SpecialPage::getTitleFor( 'Undelete' );
1869 - $key = $this->file->getKey();
1870 - # Hidden files...
1871 - if( !$this->canViewContent() ) {
1872 - $link = $date;
1873 - } else {
1874 - $link = $this->special->skin->link( $undelete, $date, array(),
1875 - array(
1876 - 'target' => $this->list->title->getPrefixedText(),
1877 - 'file' => $key,
1878 - 'token' => $wgUser->editToken( $key )
1879 - )
1880 - );
1881 - }
1882 - if( $this->isDeleted() ) {
1883 - $link = '<span class="history-deleted">' . $link . '</span>';
1884 - }
1885 - return $link;
1886 - }
1887 -}
1888 -
1889 -/**
1890 - * List for logging table items
1891 - */
1892 -class RevDel_LogList extends RevDel_List {
1893 - var $type = 'logging';
1894 - var $idField = 'log_id';
1895 - var $dateField = 'log_timestamp';
1896 - var $authorIdField = 'log_user';
1897 - var $authorNameField = 'log_user_text';
1898 -
1899 - public function doQuery( $db ) {
1900 - global $wgMessageCache;
1901 - $wgMessageCache->loadAllMessages();
1902 - $ids = array_map( 'intval', $this->ids );
1903 - return $db->select( 'logging', '*',
1904 - array( 'log_id' => $ids ),
1905 - __METHOD__,
1906 - array( 'ORDER BY' => 'log_id DESC' )
1907 - );
1908 - }
1909 -
1910 - public function newItem( $row ) {
1911 - return new RevDel_LogItem( $this, $row );
1912 - }
1913 -
1914 - public function getSuppressBit() {
1915 - return Revision::DELETED_RESTRICTED;
1916 - }
1917 -
1918 - public function getLogAction() {
1919 - return 'event';
1920 - }
1921 -
1922 - public function getLogParams( $params ) {
1923 - return array(
1924 - implode( ',', $params['ids'] ),
1925 - "ofield={$params['oldBits']}",
1926 - "nfield={$params['newBits']}"
1927 - );
1928 - }
1929 -}
1930 -
1931 -/**
1932 - * Item class for a logging table row
1933 - */
1934 -class RevDel_LogItem extends RevDel_Item {
1935 - public function canView() {
1936 - return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
1937 - }
1938 -
1939 - public function canViewContent() {
1940 - return true; // none
1941 - }
1942 -
1943 - public function getBits() {
1944 - return $this->row->log_deleted;
1945 - }
1946 -
1947 - public function setBits( $bits ) {
1948 - $dbw = wfGetDB( DB_MASTER );
1949 - $dbw->update( 'recentchanges',
1950 - array(
1951 - 'rc_deleted' => $bits,
1952 - 'rc_patrolled' => 1
1953 - ),
1954 - array(
1955 - 'rc_logid' => $this->row->log_id,
1956 - 'rc_timestamp' => $this->row->log_timestamp // index
1957 - ),
1958 - __METHOD__
1959 - );
1960 - $dbw->update( 'logging',
1961 - array( 'log_deleted' => $bits ),
1962 - array(
1963 - 'log_id' => $this->row->log_id,
1964 - 'log_deleted' => $this->getBits()
1965 - ),
1966 - __METHOD__
1967 - );
1968 - return (bool)$dbw->affectedRows();
1969 - }
1970 -
1971 - public function getHTML() {
1972 - global $wgLang;
1973 -
1974 - $date = htmlspecialchars( $wgLang->timeanddate( $this->row->log_timestamp ) );
1975 - $paramArray = LogPage::extractParams( $this->row->log_params );
1976 - $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
1977 -
1978 - // Log link for this page
1979 - $loglink = $this->special->skin->link(
1980 - SpecialPage::getTitleFor( 'Log' ),
1981 - wfMsgHtml( 'log' ),
1982 - array(),
1983 - array( 'page' => $title->getPrefixedText() )
1984 - );
1985 - // Action text
1986 - if( !$this->canView() ) {
1987 - $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
1988 - } else {
1989 - $action = LogPage::actionText( $this->row->log_type, $this->row->log_action, $title,
1990 - $this->special->skin, $paramArray, true, true );
1991 - if( $this->row->log_deleted & LogPage::DELETED_ACTION )
1992 - $action = '<span class="history-deleted">' . $action . '</span>';
1993 - }
1994 - // User links
1995 - $userLink = $this->special->skin->userLink( $this->row->log_user,
1996 - User::WhoIs( $this->row->log_user ) );
1997 - if( LogEventsList::isDeleted($this->row,LogPage::DELETED_USER) ) {
1998 - $userLink = '<span class="history-deleted">' . $userLink . '</span>';
1999 - }
2000 - // Comment
2001 - $comment = $wgLang->getDirMark() . $this->special->skin->commentBlock( $this->row->log_comment );
2002 - if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
2003 - $comment = '<span class="history-deleted">' . $comment . '</span>';
2004 - }
2005 - return "<li>($loglink) $date $userLink $action $comment</li>";
2006 - }
2007 -}
 600+}
\ No newline at end of file
Index: trunk/phase3/includes/RevisionDelete.php
@@ -0,0 +1,1407 @@
 2+<?php
 3+/**
 4+ * Temporary b/c interface, collection of static functions.
 5+ * @ingroup SpecialPage
 6+ */
 7+class RevisionDeleter {
 8+ /**
 9+ * Checks for a change in the bitfield for a certain option and updates the
 10+ * provided array accordingly.
 11+ *
 12+ * @param $desc String: description to add to the array if the option was
 13+ * enabled / disabled.
 14+ * @param $field Integer: the bitmask describing the single option.
 15+ * @param $diff Integer: the xor of the old and new bitfields.
 16+ * @param $new Integer: the new bitfield
 17+ * @param $arr Array: the array to update.
 18+ */
 19+ protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
 20+ if( $diff & $field ) {
 21+ $arr[ ( $new & $field ) ? 0 : 1 ][] = $desc;
 22+ }
 23+ }
 24+
 25+ /**
 26+ * Gets an array of message keys describing the changes made to the visibility
 27+ * of the revision. If the resulting array is $arr, then $arr[0] will contain an
 28+ * array of strings describing the items that were hidden, $arr[2] will contain
 29+ * an array of strings describing the items that were unhidden, and $arr[3] will
 30+ * contain an array with a single string, which can be one of "applied
 31+ * restrictions to sysops", "removed restrictions from sysops", or null.
 32+ *
 33+ * @param $n Integer: the new bitfield.
 34+ * @param $o Integer: the old bitfield.
 35+ * @return An array as described above.
 36+ */
 37+ protected static function getChanges( $n, $o ) {
 38+ $diff = $n ^ $o;
 39+ $ret = array( 0 => array(), 1 => array(), 2 => array() );
 40+ // Build bitfield changes in language
 41+ self::checkItem( 'revdelete-content',
 42+ Revision::DELETED_TEXT, $diff, $n, $ret );
 43+ self::checkItem( 'revdelete-summary',
 44+ Revision::DELETED_COMMENT, $diff, $n, $ret );
 45+ self::checkItem( 'revdelete-uname',
 46+ Revision::DELETED_USER, $diff, $n, $ret );
 47+ // Restriction application to sysops
 48+ if( $diff & Revision::DELETED_RESTRICTED ) {
 49+ if( $n & Revision::DELETED_RESTRICTED )
 50+ $ret[2][] = 'revdelete-restricted';
 51+ else
 52+ $ret[2][] = 'revdelete-unrestricted';
 53+ }
 54+ return $ret;
 55+ }
 56+
 57+ /**
 58+ * Gets a log message to describe the given revision visibility change. This
 59+ * message will be of the form "[hid {content, edit summary, username}];
 60+ * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
 61+ *
 62+ * @param $count Integer: The number of effected revisions.
 63+ * @param $nbitfield Integer: The new bitfield for the revision.
 64+ * @param $obitfield Integer: The old bitfield for the revision.
 65+ * @param $isForLog Boolean
 66+ * @param $forContent Boolean
 67+ */
 68+ public static function getLogMessage( $count, $nbitfield, $obitfield, $isForLog = false, $forContent = false ) {
 69+ global $wgLang, $wgContLang;
 70+
 71+ $lang = $forContent ? $wgContLang : $wgLang;
 72+ $msgFunc = $forContent ? "wfMsgForContent" : "wfMsg";
 73+
 74+ $s = '';
 75+ $changes = self::getChanges( $nbitfield, $obitfield );
 76+ array_walk($changes, 'RevisionDeleter::expandMessageArray', $forContent);
 77+
 78+ $changesText = array();
 79+
 80+ if( count( $changes[0] ) ) {
 81+ $changesText[] = $msgFunc( 'revdelete-hid', $lang->commaList( $changes[0] ) );
 82+ }
 83+ if( count( $changes[1] ) ) {
 84+ $changesText[] = $msgFunc( 'revdelete-unhid', $lang->commaList( $changes[1] ) );
 85+ }
 86+
 87+ $s = $lang->semicolonList( $changesText );
 88+ if( count( $changes[2] ) ) {
 89+ $s .= $s ? ' (' . $changes[2][0] . ')' : ' ' . $changes[2][0];
 90+ }
 91+
 92+ $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
 93+ return wfMsgExt( $msg, $forContent ? array( 'parsemag', 'content' ) : array( 'parsemag' ), $s, $lang->formatNum($count) );
 94+ }
 95+
 96+ private static function expandMessageArray(& $msg, $key, $forContent) {
 97+ if ( is_array ($msg) ) {
 98+ array_walk($msg, 'RevisionDeleter::expandMessageArray', $forContent);
 99+ } else {
 100+ if ( $forContent ) {
 101+ $msg = wfMsgForContent($msg);
 102+ } else {
 103+ $msg = wfMsg($msg);
 104+ }
 105+ }
 106+ }
 107+
 108+ // Get DB field name for URL param...
 109+ // Future code for other things may also track
 110+ // other types of revision-specific changes.
 111+ // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
 112+ public static function getRelationType( $typeName ) {
 113+ if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
 114+ $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
 115+ }
 116+ if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
 117+ $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
 118+ $list = new $class( null, null, null );
 119+ return $list->getIdField();
 120+ } else {
 121+ return null;
 122+ }
 123+ }
 124+
 125+ // Checks if a revision still exists in the revision table.
 126+ // If it doesn't, returns the corresponding ar_timestamp field
 127+ // so that this key can be used instead.
 128+ public static function checkRevisionExistence( $title, $revid ) {
 129+ $dbr = wfGetDB( DB_SLAVE );
 130+ $exists = $dbr->selectField( 'revision', '1',
 131+ array( 'rev_id' => $revid ), __METHOD__ );
 132+
 133+ if ( $exists ) {
 134+ return true;
 135+ }
 136+
 137+ $timestamp = $dbr->selectField( 'archive', 'ar_timestamp',
 138+ array( 'ar_namespace' => $title->getNamespace(),
 139+ 'ar_title' => $title->getDBkey(),
 140+ 'ar_rev_id' => $revid ), __METHOD__ );
 141+
 142+ return $timestamp;
 143+ }
 144+
 145+ // Creates utility links for log entries.
 146+ public static function getLogLinks( $title, $paramArray, $skin, $messages ) {
 147+ global $wgLang;
 148+
 149+ if( count($paramArray) >= 2 ) {
 150+ // Different revision types use different URL params...
 151+ $originalKey = $key = $paramArray[0];
 152+ // $paramArray[1] is a CSV of the IDs
 153+ $Ids = explode( ',', $paramArray[1] );
 154+ $query = $paramArray[1];
 155+ $revert = array();
 156+
 157+ // For if undeleted revisions are found amidst deleted ones.
 158+ $undeletedRevisions = array();
 159+
 160+ // This is not going to work if some revs are deleted and some
 161+ // aren't.
 162+ if ($key == 'revision') {
 163+ foreach( $Ids as $k => $id ) {
 164+ $existResult =
 165+ self::checkRevisionExistence( $title, $id );
 166+
 167+ if ($existResult !== true) {
 168+ $key = 'archive';
 169+ $Ids[$k] = $existResult;
 170+ } elseif ($key != $originalKey) {
 171+ // Undeleted revision amidst deleted ones
 172+ unset($Ids[$k]);
 173+ $undeletedRevisions[] = $id;
 174+ }
 175+ }
 176+ }
 177+
 178+ // Diff link for single rev deletions
 179+ if( count($Ids) == 1 && !count($undeletedRevisions) ) {
 180+ // Live revision diffs...
 181+ if( in_array( $key, array( 'oldid', 'revision' ) ) ) {
 182+ $revert[] = $skin->link(
 183+ $title,
 184+ $messages['diff'],
 185+ array(),
 186+ array(
 187+ 'diff' => intval( $Ids[0] ),
 188+ 'unhide' => 1
 189+ ),
 190+ array( 'known', 'noclasses' )
 191+ );
 192+ // Deleted revision diffs...
 193+ } else if( in_array( $key, array( 'artimestamp','archive' ) ) ) {
 194+ $revert[] = $skin->link(
 195+ SpecialPage::getTitleFor( 'Undelete' ),
 196+ $messages['diff'],
 197+ array(),
 198+ array(
 199+ 'target' => $title->getPrefixedDBKey(),
 200+ 'diff' => 'prev',
 201+ 'timestamp' => $Ids[0]
 202+ ),
 203+ array( 'known', 'noclasses' )
 204+ );
 205+ }
 206+ }
 207+
 208+ // View/modify link...
 209+ if ( count($undeletedRevisions) ) {
 210+ // FIXME THIS IS A HORRIBLE HORRIBLE HACK AND SHOULD DIE
 211+ // It's not possible to pass a list of both deleted and
 212+ // undeleted revisions to SpecialRevisionDelete, so we're
 213+ // stuck with two links. See bug 23363.
 214+ $restoreLinks = array();
 215+
 216+ $restoreLinks[] = $skin->link(
 217+ SpecialPage::getTitleFor( 'Revisiondelete' ),
 218+ $messages['revdel-restore-visible'],
 219+ array(),
 220+ array(
 221+ 'target' => $title->getPrefixedText(),
 222+ 'type' => $originalKey,
 223+ 'ids' => implode(',', $undeletedRevisions),
 224+ ),
 225+ array( 'known', 'noclasses' )
 226+ );
 227+
 228+ $restoreLinks[] = $skin->link(
 229+ SpecialPage::getTitleFor( 'Revisiondelete' ),
 230+ $messages['revdel-restore-deleted'],
 231+ array(),
 232+ array(
 233+ 'target' => $title->getPrefixedText(),
 234+ 'type' => $key,
 235+ 'ids' => implode(',', $Ids),
 236+ ),
 237+ array( 'known', 'noclasses' )
 238+ );
 239+
 240+ $revert[] = $messages['revdel-restore'] . ' [' .
 241+ $wgLang->pipeList( $restoreLinks ) . ']';
 242+ } else {
 243+ $revert[] = $skin->link(
 244+ SpecialPage::getTitleFor( 'Revisiondelete' ),
 245+ $messages['revdel-restore'],
 246+ array(),
 247+ array(
 248+ 'target' => $title->getPrefixedText(),
 249+ 'type' => $key,
 250+ 'ids' => implode(',', $Ids),
 251+ ),
 252+ array( 'known', 'noclasses' )
 253+ );
 254+ }
 255+
 256+ // Pipe links
 257+ $revert = wfMsg( 'parentheses', $wgLang->pipeList( $revert ) );
 258+ }
 259+ return $revert;
 260+ }
 261+}
 262+
 263+/**
 264+ * Abstract base class for a list of deletable items
 265+ */
 266+abstract class RevDel_List {
 267+ var $special, $title, $ids, $res, $current;
 268+ var $type = null; // override this
 269+ var $idField = null; // override this
 270+ var $dateField = false; // override this
 271+ var $authorIdField = false; // override this
 272+ var $authorNameField = false; // override this
 273+
 274+ /**
 275+ * @param $special The parent SpecialPage
 276+ * @param $title The target title
 277+ * @param $ids Array of IDs
 278+ */
 279+ public function __construct( $special, $title, $ids ) {
 280+ $this->special = $special;
 281+ $this->title = $title;
 282+ $this->ids = $ids;
 283+ }
 284+
 285+ /**
 286+ * Get the internal type name of this list. Equal to the table name.
 287+ */
 288+ public function getType() {
 289+ return $this->type;
 290+ }
 291+
 292+ /**
 293+ * Get the DB field name associated with the ID list
 294+ */
 295+ public function getIdField() {
 296+ return $this->idField;
 297+ }
 298+
 299+ /**
 300+ * Get the DB field name storing timestamps
 301+ */
 302+ public function getTimestampField() {
 303+ return $this->dateField;
 304+ }
 305+
 306+ /**
 307+ * Get the DB field name storing user ids
 308+ */
 309+ public function getAuthorIdField() {
 310+ return $this->authorIdField;
 311+ }
 312+
 313+ /**
 314+ * Get the DB field name storing user names
 315+ */
 316+ public function getAuthorNameField() {
 317+ return $this->authorNameField;
 318+ }
 319+ /**
 320+ * Set the visibility for the revisions in this list. Logging and
 321+ * transactions are done here.
 322+ *
 323+ * @param $params Associative array of parameters. Members are:
 324+ * value: The integer value to set the visibility to
 325+ * comment: The log comment.
 326+ * @return Status
 327+ */
 328+ public function setVisibility( $params ) {
 329+ $bitPars = $params['value'];
 330+ $comment = $params['comment'];
 331+
 332+ $this->res = false;
 333+ $dbw = wfGetDB( DB_MASTER );
 334+ $this->doQuery( $dbw );
 335+ $dbw->begin();
 336+ $status = Status::newGood();
 337+ $missing = array_flip( $this->ids );
 338+ $this->clearFileOps();
 339+ $idsForLog = array();
 340+ $authorIds = $authorIPs = array();
 341+
 342+ for ( $this->reset(); $this->current(); $this->next() ) {
 343+ $item = $this->current();
 344+ unset( $missing[ $item->getId() ] );
 345+
 346+ $oldBits = $item->getBits();
 347+ // Build the actual new rev_deleted bitfield
 348+ $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits );
 349+
 350+ if ( $oldBits == $newBits ) {
 351+ $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
 352+ $status->failCount++;
 353+ continue;
 354+ } elseif ( $oldBits == 0 && $newBits != 0 ) {
 355+ $opType = 'hide';
 356+ } elseif ( $oldBits != 0 && $newBits == 0 ) {
 357+ $opType = 'show';
 358+ } else {
 359+ $opType = 'modify';
 360+ }
 361+
 362+ if ( $item->isHideCurrentOp( $newBits ) ) {
 363+ // Cannot hide current version text
 364+ $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
 365+ $status->failCount++;
 366+ continue;
 367+ }
 368+ if ( !$item->canView() ) {
 369+ // Cannot access this revision
 370+ $msg = ($opType == 'show') ?
 371+ 'revdelete-show-no-access' : 'revdelete-modify-no-access';
 372+ $status->error( $msg, $item->formatDate(), $item->formatTime() );
 373+ $status->failCount++;
 374+ continue;
 375+ }
 376+ // Cannot just "hide from Sysops" without hiding any fields
 377+ if( $newBits == Revision::DELETED_RESTRICTED ) {
 378+ $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
 379+ $status->failCount++;
 380+ continue;
 381+ }
 382+
 383+ // Update the revision
 384+ $ok = $item->setBits( $newBits );
 385+
 386+ if ( $ok ) {
 387+ $idsForLog[] = $item->getId();
 388+ $status->successCount++;
 389+ if( $item->getAuthorId() > 0 ) {
 390+ $authorIds[] = $item->getAuthorId();
 391+ } else if( IP::isIPAddress( $item->getAuthorName() ) ) {
 392+ $authorIPs[] = $item->getAuthorName();
 393+ }
 394+ } else {
 395+ $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
 396+ $status->failCount++;
 397+ }
 398+ }
 399+
 400+ // Handle missing revisions
 401+ foreach ( $missing as $id => $unused ) {
 402+ $status->error( 'revdelete-modify-missing', $id );
 403+ $status->failCount++;
 404+ }
 405+
 406+ if ( $status->successCount == 0 ) {
 407+ $status->ok = false;
 408+ $dbw->rollback();
 409+ return $status;
 410+ }
 411+
 412+ // Save success count
 413+ $successCount = $status->successCount;
 414+
 415+ // Move files, if there are any
 416+ $status->merge( $this->doPreCommitUpdates() );
 417+ if ( !$status->isOK() ) {
 418+ // Fatal error, such as no configured archive directory
 419+ $dbw->rollback();
 420+ return $status;
 421+ }
 422+
 423+ // Log it
 424+ $this->updateLog( array(
 425+ 'title' => $this->title,
 426+ 'count' => $successCount,
 427+ 'newBits' => $newBits,
 428+ 'oldBits' => $oldBits,
 429+ 'comment' => $comment,
 430+ 'ids' => $idsForLog,
 431+ 'authorIds' => $authorIds,
 432+ 'authorIPs' => $authorIPs
 433+ ) );
 434+ $dbw->commit();
 435+
 436+ // Clear caches
 437+ $status->merge( $this->doPostCommitUpdates() );
 438+ return $status;
 439+ }
 440+
 441+ /**
 442+ * Reload the list data from the master DB. This can be done after setVisibility()
 443+ * to allow $item->getHTML() to show the new data.
 444+ */
 445+ function reloadFromMaster() {
 446+ $dbw = wfGetDB( DB_MASTER );
 447+ $this->res = $this->doQuery( $dbw );
 448+ }
 449+
 450+ /**
 451+ * Record a log entry on the action
 452+ * @param $params Associative array of parameters:
 453+ * newBits: The new value of the *_deleted bitfield
 454+ * oldBits: The old value of the *_deleted bitfield.
 455+ * title: The target title
 456+ * ids: The ID list
 457+ * comment: The log comment
 458+ * authorsIds: The array of the user IDs of the offenders
 459+ * authorsIPs: The array of the IP/anon user offenders
 460+ */
 461+ protected function updateLog( $params ) {
 462+ // Get the URL param's corresponding DB field
 463+ $field = RevisionDeleter::getRelationType( $this->getType() );
 464+ if( !$field ) {
 465+ throw new MWException( "Bad log URL param type!" );
 466+ }
 467+ // Put things hidden from sysops in the oversight log
 468+ if ( ( $params['newBits'] | $params['oldBits'] ) & $this->getSuppressBit() ) {
 469+ $logType = 'suppress';
 470+ } else {
 471+ $logType = 'delete';
 472+ }
 473+ // Add params for effected page and ids
 474+ $logParams = $this->getLogParams( $params );
 475+ // Actually add the deletion log entry
 476+ $log = new LogPage( $logType );
 477+ $logid = $log->addEntry( $this->getLogAction(), $params['title'],
 478+ $params['comment'], $logParams );
 479+ // Allow for easy searching of deletion log items for revision/log items
 480+ $log->addRelations( $field, $params['ids'], $logid );
 481+ $log->addRelations( 'target_author_id', $params['authorIds'], $logid );
 482+ $log->addRelations( 'target_author_ip', $params['authorIPs'], $logid );
 483+ }
 484+
 485+ /**
 486+ * Get the log action for this list type
 487+ */
 488+ public function getLogAction() {
 489+ return 'revision';
 490+ }
 491+
 492+ /**
 493+ * Get log parameter array.
 494+ * @param $params Associative array of log parameters, same as updateLog()
 495+ * @return array
 496+ */
 497+ public function getLogParams( $params ) {
 498+ return array(
 499+ $this->getType(),
 500+ implode( ',', $params['ids'] ),
 501+ "ofield={$params['oldBits']}",
 502+ "nfield={$params['newBits']}"
 503+ );
 504+ }
 505+
 506+ /**
 507+ * Initialise the current iteration pointer
 508+ */
 509+ protected function initCurrent() {
 510+ $row = $this->res->current();
 511+ if ( $row ) {
 512+ $this->current = $this->newItem( $row );
 513+ } else {
 514+ $this->current = false;
 515+ }
 516+ }
 517+
 518+ /**
 519+ * Start iteration. This must be called before current() or next().
 520+ * @return First list item
 521+ */
 522+ public function reset() {
 523+ if ( !$this->res ) {
 524+ $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) );
 525+ } else {
 526+ $this->res->rewind();
 527+ }
 528+ $this->initCurrent();
 529+ return $this->current;
 530+ }
 531+
 532+ /**
 533+ * Get the current list item, or false if we are at the end
 534+ */
 535+ public function current() {
 536+ return $this->current;
 537+ }
 538+
 539+ /**
 540+ * Move the iteration pointer to the next list item, and return it.
 541+ */
 542+ public function next() {
 543+ $this->res->next();
 544+ $this->initCurrent();
 545+ return $this->current;
 546+ }
 547+
 548+ /**
 549+ * Get the number of items in the list.
 550+ */
 551+ public function length() {
 552+ if( !$this->res ) {
 553+ return 0;
 554+ } else {
 555+ return $this->res->numRows();
 556+ }
 557+ }
 558+
 559+ /**
 560+ * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates()
 561+ * STUB
 562+ */
 563+ public function clearFileOps() {
 564+ }
 565+
 566+ /**
 567+ * A hook for setVisibility(): do batch updates pre-commit.
 568+ * STUB
 569+ * @return Status
 570+ */
 571+ public function doPreCommitUpdates() {
 572+ return Status::newGood();
 573+ }
 574+
 575+ /**
 576+ * A hook for setVisibility(): do any necessary updates post-commit.
 577+ * STUB
 578+ * @return Status
 579+ */
 580+ public function doPostCommitUpdates() {
 581+ return Status::newGood();
 582+ }
 583+
 584+ /**
 585+ * Create an item object from a DB result row
 586+ * @param $row stdclass
 587+ */
 588+ abstract public function newItem( $row );
 589+
 590+ /**
 591+ * Do the DB query to iterate through the objects.
 592+ * @param $db Database object to use for the query
 593+ */
 594+ abstract public function doQuery( $db );
 595+
 596+ /**
 597+ * Get the integer value of the flag used for suppression
 598+ */
 599+ abstract public function getSuppressBit();
 600+}
 601+
 602+/**
 603+ * Abstract base class for deletable items
 604+ */
 605+abstract class RevDel_Item {
 606+ /** The parent SpecialPage */
 607+ var $special;
 608+
 609+ /** The parent RevDel_List */
 610+ var $list;
 611+
 612+ /** The DB result row */
 613+ var $row;
 614+
 615+ /**
 616+ * @param $list RevDel_List
 617+ * @param $row DB result row
 618+ */
 619+ public function __construct( $list, $row ) {
 620+ $this->special = $list->special;
 621+ $this->list = $list;
 622+ $this->row = $row;
 623+ }
 624+
 625+ /**
 626+ * Get the ID, as it would appear in the ids URL parameter
 627+ */
 628+ public function getId() {
 629+ $field = $this->list->getIdField();
 630+ return $this->row->$field;
 631+ }
 632+
 633+ /**
 634+ * Get the date, formatted with $wgLang
 635+ */
 636+ public function formatDate() {
 637+ global $wgLang;
 638+ return $wgLang->date( $this->getTimestamp() );
 639+ }
 640+
 641+ /**
 642+ * Get the time, formatted with $wgLang
 643+ */
 644+ public function formatTime() {
 645+ global $wgLang;
 646+ return $wgLang->time( $this->getTimestamp() );
 647+ }
 648+
 649+ /**
 650+ * Get the timestamp in MW 14-char form
 651+ */
 652+ public function getTimestamp() {
 653+ $field = $this->list->getTimestampField();
 654+ return wfTimestamp( TS_MW, $this->row->$field );
 655+ }
 656+
 657+ /**
 658+ * Get the author user ID
 659+ */
 660+ public function getAuthorId() {
 661+ $field = $this->list->getAuthorIdField();
 662+ return intval( $this->row->$field );
 663+ }
 664+
 665+ /**
 666+ * Get the author user name
 667+ */
 668+ public function getAuthorName() {
 669+ $field = $this->list->getAuthorNameField();
 670+ return strval( $this->row->$field );
 671+ }
 672+
 673+ /**
 674+ * Returns true if the item is "current", and the operation to set the given
 675+ * bits can't be executed for that reason
 676+ * STUB
 677+ */
 678+ public function isHideCurrentOp( $newBits ) {
 679+ return false;
 680+ }
 681+
 682+ /**
 683+ * Returns true if the current user can view the item
 684+ */
 685+ abstract public function canView();
 686+
 687+ /**
 688+ * Returns true if the current user can view the item text/file
 689+ */
 690+ abstract public function canViewContent();
 691+
 692+ /**
 693+ * Get the current deletion bitfield value
 694+ */
 695+ abstract public function getBits();
 696+
 697+ /**
 698+ * Get the HTML of the list item. Should be include <li></li> tags.
 699+ * This is used to show the list in HTML form, by the special page.
 700+ */
 701+ abstract public function getHTML();
 702+
 703+ /**
 704+ * Set the visibility of the item. This should do any necessary DB queries.
 705+ *
 706+ * The DB update query should have a condition which forces it to only update
 707+ * if the value in the DB matches the value fetched earlier with the SELECT.
 708+ * If the update fails because it did not match, the function should return
 709+ * false. This prevents concurrency problems.
 710+ *
 711+ * @return boolean success
 712+ */
 713+ abstract public function setBits( $newBits );
 714+}
 715+
 716+/**
 717+ * List for revision table items
 718+ */
 719+class RevDel_RevisionList extends RevDel_List {
 720+ var $currentRevId;
 721+ var $type = 'revision';
 722+ var $idField = 'rev_id';
 723+ var $dateField = 'rev_timestamp';
 724+ var $authorIdField = 'rev_user';
 725+ var $authorNameField = 'rev_user_text';
 726+
 727+ public function doQuery( $db ) {
 728+ $ids = array_map( 'intval', $this->ids );
 729+ return $db->select( array('revision','page'), '*',
 730+ array(
 731+ 'rev_page' => $this->title->getArticleID(),
 732+ 'rev_id' => $ids,
 733+ 'rev_page = page_id'
 734+ ),
 735+ __METHOD__,
 736+ array( 'ORDER BY' => 'rev_id DESC' )
 737+ );
 738+ }
 739+
 740+ public function newItem( $row ) {
 741+ return new RevDel_RevisionItem( $this, $row );
 742+ }
 743+
 744+ public function getCurrent() {
 745+ if ( is_null( $this->currentRevId ) ) {
 746+ $dbw = wfGetDB( DB_MASTER );
 747+ $this->currentRevId = $dbw->selectField(
 748+ 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
 749+ }
 750+ return $this->currentRevId;
 751+ }
 752+
 753+ public function getSuppressBit() {
 754+ return Revision::DELETED_RESTRICTED;
 755+ }
 756+
 757+ public function doPreCommitUpdates() {
 758+ $this->title->invalidateCache();
 759+ return Status::newGood();
 760+ }
 761+
 762+ public function doPostCommitUpdates() {
 763+ $this->title->purgeSquid();
 764+ // Extensions that require referencing previous revisions may need this
 765+ wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$this->title ) );
 766+ return Status::newGood();
 767+ }
 768+}
 769+
 770+/**
 771+ * Item class for a revision table row
 772+ */
 773+class RevDel_RevisionItem extends RevDel_Item {
 774+ var $revision;
 775+
 776+ public function __construct( $list, $row ) {
 777+ parent::__construct( $list, $row );
 778+ $this->revision = new Revision( $row );
 779+ }
 780+
 781+ public function canView() {
 782+ return $this->revision->userCan( Revision::DELETED_RESTRICTED );
 783+ }
 784+
 785+ public function canViewContent() {
 786+ return $this->revision->userCan( Revision::DELETED_TEXT );
 787+ }
 788+
 789+ public function getBits() {
 790+ return $this->revision->mDeleted;
 791+ }
 792+
 793+ public function setBits( $bits ) {
 794+ $dbw = wfGetDB( DB_MASTER );
 795+ // Update revision table
 796+ $dbw->update( 'revision',
 797+ array( 'rev_deleted' => $bits ),
 798+ array(
 799+ 'rev_id' => $this->revision->getId(),
 800+ 'rev_page' => $this->revision->getPage(),
 801+ 'rev_deleted' => $this->getBits()
 802+ ),
 803+ __METHOD__
 804+ );
 805+ if ( !$dbw->affectedRows() ) {
 806+ // Concurrent fail!
 807+ return false;
 808+ }
 809+ // Update recentchanges table
 810+ $dbw->update( 'recentchanges',
 811+ array(
 812+ 'rc_deleted' => $bits,
 813+ 'rc_patrolled' => 1
 814+ ),
 815+ array(
 816+ 'rc_this_oldid' => $this->revision->getId(), // condition
 817+ // non-unique timestamp index
 818+ 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
 819+ ),
 820+ __METHOD__
 821+ );
 822+ return true;
 823+ }
 824+
 825+ public function isDeleted() {
 826+ return $this->revision->isDeleted( Revision::DELETED_TEXT );
 827+ }
 828+
 829+ public function isHideCurrentOp( $newBits ) {
 830+ return ( $newBits & Revision::DELETED_TEXT )
 831+ && $this->list->getCurrent() == $this->getId();
 832+ }
 833+
 834+ /**
 835+ * Get the HTML link to the revision text.
 836+ * Overridden by RevDel_ArchiveItem.
 837+ */
 838+ protected function getRevisionLink() {
 839+ global $wgLang;
 840+ $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
 841+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 842+ return $date;
 843+ }
 844+ return $this->special->skin->link(
 845+ $this->list->title,
 846+ $date,
 847+ array(),
 848+ array(
 849+ 'oldid' => $this->revision->getId(),
 850+ 'unhide' => 1
 851+ )
 852+ );
 853+ }
 854+
 855+ /**
 856+ * Get the HTML link to the diff.
 857+ * Overridden by RevDel_ArchiveItem
 858+ */
 859+ protected function getDiffLink() {
 860+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 861+ return wfMsgHtml('diff');
 862+ } else {
 863+ return
 864+ $this->special->skin->link(
 865+ $this->list->title,
 866+ wfMsgHtml('diff'),
 867+ array(),
 868+ array(
 869+ 'diff' => $this->revision->getId(),
 870+ 'oldid' => 'prev',
 871+ 'unhide' => 1
 872+ ),
 873+ array(
 874+ 'known',
 875+ 'noclasses'
 876+ )
 877+ );
 878+ }
 879+ }
 880+
 881+ public function getHTML() {
 882+ $difflink = $this->getDiffLink();
 883+ $revlink = $this->getRevisionLink();
 884+ $userlink = $this->special->skin->revUserLink( $this->revision );
 885+ $comment = $this->special->skin->revComment( $this->revision );
 886+ if ( $this->isDeleted() ) {
 887+ $revlink = "<span class=\"history-deleted\">$revlink</span>";
 888+ }
 889+ return "<li>($difflink) $revlink $userlink $comment</li>";
 890+ }
 891+}
 892+
 893+/**
 894+ * List for archive table items, i.e. revisions deleted via action=delete
 895+ */
 896+class RevDel_ArchiveList extends RevDel_RevisionList {
 897+ var $type = 'archive';
 898+ var $idField = 'ar_timestamp';
 899+ var $dateField = 'ar_timestamp';
 900+ var $authorIdField = 'ar_user';
 901+ var $authorNameField = 'ar_user_text';
 902+
 903+ public function doQuery( $db ) {
 904+ $timestamps = array();
 905+ foreach ( $this->ids as $id ) {
 906+ $timestamps[] = $db->timestamp( $id );
 907+ }
 908+ return $db->select( 'archive', '*',
 909+ array(
 910+ 'ar_namespace' => $this->title->getNamespace(),
 911+ 'ar_title' => $this->title->getDBkey(),
 912+ 'ar_timestamp' => $timestamps
 913+ ),
 914+ __METHOD__,
 915+ array( 'ORDER BY' => 'ar_timestamp DESC' )
 916+ );
 917+ }
 918+
 919+ public function newItem( $row ) {
 920+ return new RevDel_ArchiveItem( $this, $row );
 921+ }
 922+
 923+ public function doPreCommitUpdates() {
 924+ return Status::newGood();
 925+ }
 926+
 927+ public function doPostCommitUpdates() {
 928+ return Status::newGood();
 929+ }
 930+}
 931+
 932+/**
 933+ * Item class for a archive table row
 934+ */
 935+class RevDel_ArchiveItem extends RevDel_RevisionItem {
 936+ public function __construct( $list, $row ) {
 937+ RevDel_Item::__construct( $list, $row );
 938+ $this->revision = Revision::newFromArchiveRow( $row,
 939+ array( 'page' => $this->list->title->getArticleId() ) );
 940+ }
 941+
 942+ public function getId() {
 943+ # Convert DB timestamp to MW timestamp
 944+ return $this->revision->getTimestamp();
 945+ }
 946+
 947+ public function setBits( $bits ) {
 948+ $dbw = wfGetDB( DB_MASTER );
 949+ $dbw->update( 'archive',
 950+ array( 'ar_deleted' => $bits ),
 951+ array( 'ar_namespace' => $this->list->title->getNamespace(),
 952+ 'ar_title' => $this->list->title->getDBkey(),
 953+ // use timestamp for index
 954+ 'ar_timestamp' => $this->row->ar_timestamp,
 955+ 'ar_rev_id' => $this->row->ar_rev_id,
 956+ 'ar_deleted' => $this->getBits()
 957+ ),
 958+ __METHOD__ );
 959+ return (bool)$dbw->affectedRows();
 960+ }
 961+
 962+ protected function getRevisionLink() {
 963+ global $wgLang;
 964+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
 965+ $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
 966+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 967+ return $date;
 968+ }
 969+ return $this->special->skin->link( $undelete, $date, array(),
 970+ array(
 971+ 'target' => $this->list->title->getPrefixedText(),
 972+ 'timestamp' => $this->revision->getTimestamp()
 973+ ) );
 974+ }
 975+
 976+ protected function getDiffLink() {
 977+ if ( $this->isDeleted() && !$this->canViewContent() ) {
 978+ return wfMsgHtml( 'diff' );
 979+ }
 980+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
 981+ return $this->special->skin->link( $undelete, wfMsgHtml('diff'), array(),
 982+ array(
 983+ 'target' => $this->list->title->getPrefixedText(),
 984+ 'diff' => 'prev',
 985+ 'timestamp' => $this->revision->getTimestamp()
 986+ ) );
 987+ }
 988+}
 989+
 990+/**
 991+ * List for oldimage table items
 992+ */
 993+class RevDel_FileList extends RevDel_List {
 994+ var $type = 'oldimage';
 995+ var $idField = 'oi_archive_name';
 996+ var $dateField = 'oi_timestamp';
 997+ var $authorIdField = 'oi_user';
 998+ var $authorNameField = 'oi_user_text';
 999+ var $storeBatch, $deleteBatch, $cleanupBatch;
 1000+
 1001+ public function doQuery( $db ) {
 1002+ $archiveName = array();
 1003+ foreach( $this->ids as $timestamp ) {
 1004+ $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
 1005+ }
 1006+ return $db->select( 'oldimage', '*',
 1007+ array(
 1008+ 'oi_name' => $this->title->getDBkey(),
 1009+ 'oi_archive_name' => $archiveNames
 1010+ ),
 1011+ __METHOD__,
 1012+ array( 'ORDER BY' => 'oi_timestamp DESC' )
 1013+ );
 1014+ }
 1015+
 1016+ public function newItem( $row ) {
 1017+ return new RevDel_FileItem( $this, $row );
 1018+ }
 1019+
 1020+ public function clearFileOps() {
 1021+ $this->deleteBatch = array();
 1022+ $this->storeBatch = array();
 1023+ $this->cleanupBatch = array();
 1024+ }
 1025+
 1026+ public function doPreCommitUpdates() {
 1027+ $status = Status::newGood();
 1028+ $repo = RepoGroup::singleton()->getLocalRepo();
 1029+ if ( $this->storeBatch ) {
 1030+ $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
 1031+ }
 1032+ if ( !$status->isOK() ) {
 1033+ return $status;
 1034+ }
 1035+ if ( $this->deleteBatch ) {
 1036+ $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
 1037+ }
 1038+ if ( !$status->isOK() ) {
 1039+ // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
 1040+ // modified (but destined for rollback) causes data loss
 1041+ return $status;
 1042+ }
 1043+ if ( $this->cleanupBatch ) {
 1044+ $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
 1045+ }
 1046+ return $status;
 1047+ }
 1048+
 1049+ public function doPostCommitUpdates() {
 1050+ $file = wfLocalFile( $this->title );
 1051+ $file->purgeCache();
 1052+ $file->purgeDescription();
 1053+ return Status::newGood();
 1054+ }
 1055+
 1056+ public function getSuppressBit() {
 1057+ return File::DELETED_RESTRICTED;
 1058+ }
 1059+}
 1060+
 1061+/**
 1062+ * Item class for an oldimage table row
 1063+ */
 1064+class RevDel_FileItem extends RevDel_Item {
 1065+ var $file;
 1066+
 1067+ public function __construct( $list, $row ) {
 1068+ parent::__construct( $list, $row );
 1069+ $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
 1070+ }
 1071+
 1072+ public function getId() {
 1073+ $parts = explode( '!', $this->row->oi_archive_name );
 1074+ return $parts[0];
 1075+ }
 1076+
 1077+ public function canView() {
 1078+ return $this->file->userCan( File::DELETED_RESTRICTED );
 1079+ }
 1080+
 1081+ public function canViewContent() {
 1082+ return $this->file->userCan( File::DELETED_FILE );
 1083+ }
 1084+
 1085+ public function getBits() {
 1086+ return $this->file->getVisibility();
 1087+ }
 1088+
 1089+ public function setBits( $bits ) {
 1090+ # Queue the file op
 1091+ # FIXME: move to LocalFile.php
 1092+ if ( $this->isDeleted() ) {
 1093+ if ( $bits & File::DELETED_FILE ) {
 1094+ # Still deleted
 1095+ } else {
 1096+ # Newly undeleted
 1097+ $key = $this->file->getStorageKey();
 1098+ $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
 1099+ $this->list->storeBatch[] = array(
 1100+ $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
 1101+ 'public',
 1102+ $this->file->getRel()
 1103+ );
 1104+ $this->list->cleanupBatch[] = $key;
 1105+ }
 1106+ } elseif ( $bits & File::DELETED_FILE ) {
 1107+ # Newly deleted
 1108+ $key = $this->file->getStorageKey();
 1109+ $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
 1110+ $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
 1111+ }
 1112+
 1113+ # Do the database operations
 1114+ $dbw = wfGetDB( DB_MASTER );
 1115+ $dbw->update( 'oldimage',
 1116+ array( 'oi_deleted' => $bits ),
 1117+ array(
 1118+ 'oi_name' => $this->row->oi_name,
 1119+ 'oi_timestamp' => $this->row->oi_timestamp,
 1120+ 'oi_deleted' => $this->getBits()
 1121+ ),
 1122+ __METHOD__
 1123+ );
 1124+ return (bool)$dbw->affectedRows();
 1125+ }
 1126+
 1127+ public function isDeleted() {
 1128+ return $this->file->isDeleted( File::DELETED_FILE );
 1129+ }
 1130+
 1131+ /**
 1132+ * Get the link to the file.
 1133+ * Overridden by RevDel_ArchivedFileItem.
 1134+ */
 1135+ protected function getLink() {
 1136+ global $wgLang, $wgUser;
 1137+ $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
 1138+ if ( $this->isDeleted() ) {
 1139+ # Hidden files...
 1140+ if ( !$this->canViewContent() ) {
 1141+ $link = $date;
 1142+ } else {
 1143+ $link = $this->special->skin->link(
 1144+ $this->special->getTitle(),
 1145+ $date, array(),
 1146+ array(
 1147+ 'target' => $this->list->title->getPrefixedText(),
 1148+ 'file' => $this->file->getArchiveName(),
 1149+ 'token' => $wgUser->editToken( $this->file->getArchiveName() )
 1150+ )
 1151+ );
 1152+ }
 1153+ return '<span class="history-deleted">' . $link . '</span>';
 1154+ } else {
 1155+ # Regular files...
 1156+ $url = $this->file->getUrl();
 1157+ return Xml::element( 'a', array( 'href' => $this->file->getUrl() ), $date );
 1158+ }
 1159+ }
 1160+ /**
 1161+ * Generate a user tool link cluster if the current user is allowed to view it
 1162+ * @return string HTML
 1163+ */
 1164+ protected function getUserTools() {
 1165+ if( $this->file->userCan( Revision::DELETED_USER ) ) {
 1166+ $link = $this->special->skin->userLink( $this->file->user, $this->file->user_text ) .
 1167+ $this->special->skin->userToolLinks( $this->file->user, $this->file->user_text );
 1168+ } else {
 1169+ $link = wfMsgHtml( 'rev-deleted-user' );
 1170+ }
 1171+ if( $this->file->isDeleted( Revision::DELETED_USER ) ) {
 1172+ return '<span class="history-deleted">' . $link . '</span>';
 1173+ }
 1174+ return $link;
 1175+ }
 1176+
 1177+ /**
 1178+ * Wrap and format the file's comment block, if the current
 1179+ * user is allowed to view it.
 1180+ *
 1181+ * @return string HTML
 1182+ */
 1183+ protected function getComment() {
 1184+ if( $this->file->userCan( File::DELETED_COMMENT ) ) {
 1185+ $block = $this->special->skin->commentBlock( $this->file->description );
 1186+ } else {
 1187+ $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
 1188+ }
 1189+ if( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
 1190+ return "<span class=\"history-deleted\">$block</span>";
 1191+ }
 1192+ return $block;
 1193+ }
 1194+
 1195+ public function getHTML() {
 1196+ global $wgLang;
 1197+ $data =
 1198+ wfMsg(
 1199+ 'widthheight',
 1200+ $wgLang->formatNum( $this->file->getWidth() ),
 1201+ $wgLang->formatNum( $this->file->getHeight() )
 1202+ ) .
 1203+ ' (' .
 1204+ wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file->getSize() ) ) .
 1205+ ')';
 1206+ $pageLink = $this->getLink();
 1207+
 1208+ return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
 1209+ $data . ' ' . $this->getComment(). '</li>';
 1210+ }
 1211+}
 1212+
 1213+/**
 1214+ * List for filearchive table items
 1215+ */
 1216+class RevDel_ArchivedFileList extends RevDel_FileList {
 1217+ var $type = 'filearchive';
 1218+ var $idField = 'fa_id';
 1219+ var $dateField = 'fa_timestamp';
 1220+ var $authorIdField = 'fa_user';
 1221+ var $authorNameField = 'fa_user_text';
 1222+
 1223+ public function doQuery( $db ) {
 1224+ $ids = array_map( 'intval', $this->ids );
 1225+ return $db->select( 'filearchive', '*',
 1226+ array(
 1227+ 'fa_name' => $this->title->getDBkey(),
 1228+ 'fa_id' => $ids
 1229+ ),
 1230+ __METHOD__,
 1231+ array( 'ORDER BY' => 'fa_id DESC' )
 1232+ );
 1233+ }
 1234+
 1235+ public function newItem( $row ) {
 1236+ return new RevDel_ArchivedFileItem( $this, $row );
 1237+ }
 1238+}
 1239+
 1240+/**
 1241+ * Item class for a filearchive table row
 1242+ */
 1243+class RevDel_ArchivedFileItem extends RevDel_FileItem {
 1244+ public function __construct( $list, $row ) {
 1245+ RevDel_Item::__construct( $list, $row );
 1246+ $this->file = ArchivedFile::newFromRow( $row );
 1247+ }
 1248+
 1249+ public function getId() {
 1250+ return $this->row->fa_id;
 1251+ }
 1252+
 1253+ public function setBits( $bits ) {
 1254+ $dbw = wfGetDB( DB_MASTER );
 1255+ $dbw->update( 'filearchive',
 1256+ array( 'fa_deleted' => $bits ),
 1257+ array(
 1258+ 'fa_id' => $this->row->fa_id,
 1259+ 'fa_deleted' => $this->getBits(),
 1260+ ),
 1261+ __METHOD__
 1262+ );
 1263+ return (bool)$dbw->affectedRows();
 1264+ }
 1265+
 1266+ protected function getLink() {
 1267+ global $wgLang, $wgUser;
 1268+ $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
 1269+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
 1270+ $key = $this->file->getKey();
 1271+ # Hidden files...
 1272+ if( !$this->canViewContent() ) {
 1273+ $link = $date;
 1274+ } else {
 1275+ $link = $this->special->skin->link( $undelete, $date, array(),
 1276+ array(
 1277+ 'target' => $this->list->title->getPrefixedText(),
 1278+ 'file' => $key,
 1279+ 'token' => $wgUser->editToken( $key )
 1280+ )
 1281+ );
 1282+ }
 1283+ if( $this->isDeleted() ) {
 1284+ $link = '<span class="history-deleted">' . $link . '</span>';
 1285+ }
 1286+ return $link;
 1287+ }
 1288+}
 1289+
 1290+/**
 1291+ * List for logging table items
 1292+ */
 1293+class RevDel_LogList extends RevDel_List {
 1294+ var $type = 'logging';
 1295+ var $idField = 'log_id';
 1296+ var $dateField = 'log_timestamp';
 1297+ var $authorIdField = 'log_user';
 1298+ var $authorNameField = 'log_user_text';
 1299+
 1300+ public function doQuery( $db ) {
 1301+ global $wgMessageCache;
 1302+ $wgMessageCache->loadAllMessages();
 1303+ $ids = array_map( 'intval', $this->ids );
 1304+ return $db->select( 'logging', '*',
 1305+ array( 'log_id' => $ids ),
 1306+ __METHOD__,
 1307+ array( 'ORDER BY' => 'log_id DESC' )
 1308+ );
 1309+ }
 1310+
 1311+ public function newItem( $row ) {
 1312+ return new RevDel_LogItem( $this, $row );
 1313+ }
 1314+
 1315+ public function getSuppressBit() {
 1316+ return Revision::DELETED_RESTRICTED;
 1317+ }
 1318+
 1319+ public function getLogAction() {
 1320+ return 'event';
 1321+ }
 1322+
 1323+ public function getLogParams( $params ) {
 1324+ return array(
 1325+ implode( ',', $params['ids'] ),
 1326+ "ofield={$params['oldBits']}",
 1327+ "nfield={$params['newBits']}"
 1328+ );
 1329+ }
 1330+}
 1331+
 1332+/**
 1333+ * Item class for a logging table row
 1334+ */
 1335+class RevDel_LogItem extends RevDel_Item {
 1336+ public function canView() {
 1337+ return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
 1338+ }
 1339+
 1340+ public function canViewContent() {
 1341+ return true; // none
 1342+ }
 1343+
 1344+ public function getBits() {
 1345+ return $this->row->log_deleted;
 1346+ }
 1347+
 1348+ public function setBits( $bits ) {
 1349+ $dbw = wfGetDB( DB_MASTER );
 1350+ $dbw->update( 'recentchanges',
 1351+ array(
 1352+ 'rc_deleted' => $bits,
 1353+ 'rc_patrolled' => 1
 1354+ ),
 1355+ array(
 1356+ 'rc_logid' => $this->row->log_id,
 1357+ 'rc_timestamp' => $this->row->log_timestamp // index
 1358+ ),
 1359+ __METHOD__
 1360+ );
 1361+ $dbw->update( 'logging',
 1362+ array( 'log_deleted' => $bits ),
 1363+ array(
 1364+ 'log_id' => $this->row->log_id,
 1365+ 'log_deleted' => $this->getBits()
 1366+ ),
 1367+ __METHOD__
 1368+ );
 1369+ return (bool)$dbw->affectedRows();
 1370+ }
 1371+
 1372+ public function getHTML() {
 1373+ global $wgLang;
 1374+
 1375+ $date = htmlspecialchars( $wgLang->timeanddate( $this->row->log_timestamp ) );
 1376+ $paramArray = LogPage::extractParams( $this->row->log_params );
 1377+ $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
 1378+
 1379+ // Log link for this page
 1380+ $loglink = $this->special->skin->link(
 1381+ SpecialPage::getTitleFor( 'Log' ),
 1382+ wfMsgHtml( 'log' ),
 1383+ array(),
 1384+ array( 'page' => $title->getPrefixedText() )
 1385+ );
 1386+ // Action text
 1387+ if( !$this->canView() ) {
 1388+ $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
 1389+ } else {
 1390+ $action = LogPage::actionText( $this->row->log_type, $this->row->log_action, $title,
 1391+ $this->special->skin, $paramArray, true, true );
 1392+ if( $this->row->log_deleted & LogPage::DELETED_ACTION )
 1393+ $action = '<span class="history-deleted">' . $action . '</span>';
 1394+ }
 1395+ // User links
 1396+ $userLink = $this->special->skin->userLink( $this->row->log_user,
 1397+ User::WhoIs( $this->row->log_user ) );
 1398+ if( LogEventsList::isDeleted($this->row,LogPage::DELETED_USER) ) {
 1399+ $userLink = '<span class="history-deleted">' . $userLink . '</span>';
 1400+ }
 1401+ // Comment
 1402+ $comment = $wgLang->getDirMark() . $this->special->skin->commentBlock( $this->row->log_comment );
 1403+ if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
 1404+ $comment = '<span class="history-deleted">' . $comment . '</span>';
 1405+ }
 1406+ return "<li>($loglink) $date $userLink $action $comment</li>";
 1407+ }
 1408+}
Property changes on: trunk/phase3/includes/RevisionDelete.php
___________________________________________________________________
Name: svn:eol-style
11409 + native
Name: svn:keywords
21410 + Author Date Id Revision

Follow-up revisions

RevisionCommit summaryAuthorDate
r66857Revert r66856, due to the complete rubbish commit summary (dupe of another re...reedy23:28, 24 May 2010
r66858Try r66856 again - Less fail this time...reedy23:32, 24 May 2010
r77695Followup r77679, 1 more for bug 23332reedy00:13, 4 December 2010

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r61125* (bug 22061) API: add prop=headitems to action=parse...reedy13:07, 16 January 2010
r62195* (bug 18608) - API should provide list of CSS styles to apply to rendered o...reedy20:47, 9 February 2010
r66834bug 18608 done in a fashion much closer to how I should've done it originally....reedy16:35, 24 May 2010

Status & tagging log