Index: trunk/phase3/maintenance/language/messages.inc |
— | — | @@ -717,6 +717,7 @@ |
718 | 718 | 'revdelete-otherreason', |
719 | 719 | 'revdelete-reasonotherlist', |
720 | 720 | 'revdelete-edit-reasonlist', |
| 721 | + 'revdelete-offender', |
721 | 722 | ), |
722 | 723 | 'suppression' => array( |
723 | 724 | 'suppressionlog', |
Index: trunk/phase3/maintenance/populateLogSearch.php |
— | — | @@ -27,6 +27,8 @@ |
28 | 28 | |
29 | 29 | const LOG_SEARCH_BATCH_SIZE = 100; |
30 | 30 | |
| 31 | + static $tableMap = array('rev' => 'revision', 'fa' => 'filearchive', 'oi' => 'oldimage', 'ar' => 'archive'); |
| 32 | + |
31 | 33 | public function __construct() { |
32 | 34 | parent::__construct(); |
33 | 35 | $this->mDescription = "Migrate log params to new table and index for searching"; |
— | — | @@ -48,6 +50,8 @@ |
49 | 51 | $end += self::LOG_SEARCH_BATCH_SIZE - 1; |
50 | 52 | $blockStart = $start; |
51 | 53 | $blockEnd = $start + self::LOG_SEARCH_BATCH_SIZE - 1; |
| 54 | + |
| 55 | + $delTypes = array('delete','suppress'); // revisiondelete types |
52 | 56 | while( $blockEnd <= $end ) { |
53 | 57 | $this->output( "...doing log_id from $blockStart to $blockEnd\n" ); |
54 | 58 | $cond = "log_id BETWEEN $blockStart AND $blockEnd"; |
— | — | @@ -55,33 +59,74 @@ |
56 | 60 | $batch = array(); |
57 | 61 | foreach( $res as $row ) { |
58 | 62 | // RevisionDelete logs - revisions |
59 | | - if( LogEventsList::typeAction( $row, array('delete','suppress'), 'revision' ) ) { |
| 63 | + if( LogEventsList::typeAction( $row, $delTypes, 'revision' ) ) { |
60 | 64 | $params = LogPage::extractParams( $row->log_params ); |
61 | 65 | // Param format: <urlparam> <item CSV> [<ofield> <nfield>] |
62 | | - if( count($params) >= 2 ) { |
| 66 | + if( count($params) < 2 ) continue; // bad row? |
| 67 | + $field = RevisionDeleter::getRelationType($params[0]); |
| 68 | + // B/C, the params may start with a title key (<title> <urlparam> <CSV>) |
| 69 | + if( $field == null ) { |
| 70 | + array_shift($params); // remove title param |
63 | 71 | $field = RevisionDeleter::getRelationType($params[0]); |
64 | | - // B/C, the params may start with a title key |
65 | 72 | if( $field == null ) { |
66 | | - array_shift($params); |
67 | | - $field = RevisionDeleter::getRelationType($params[0]); |
68 | | - } |
69 | | - if( $field == null ) { |
70 | | - $this->output( "Invalid param type for $row->log_id\n" ); |
| 73 | + $this->output( "Invalid param type for {$row->log_id}\n" ); |
71 | 74 | continue; // skip this row |
| 75 | + } else { |
| 76 | + // Clean up the row... |
| 77 | + $db->update( 'logging', |
| 78 | + array('log_params' => implode(',',$params) ), |
| 79 | + array('log_id' => $row->log_id ) ); |
72 | 80 | } |
73 | | - $items = explode(',',$params[1]); |
74 | | - $log = new LogPage( $row->log_type ); |
75 | | - $log->addRelations( $field, $items, $row->log_id ); |
76 | 81 | } |
| 82 | + $items = explode(',',$params[1]); |
| 83 | + $log = new LogPage( $row->log_type ); |
| 84 | + // Add item relations... |
| 85 | + $log->addRelations( $field, $items, $row->log_id ); |
| 86 | + // Determine what table to query... |
| 87 | + $prefix = substr( $field, 0, strpos($field,'_') ); // db prefix |
| 88 | + if( !isset(self::$tableMap[$prefix]) ) |
| 89 | + continue; // bad row? |
| 90 | + $table = self::$tableMap[$prefix]; |
| 91 | + $userField = $prefix.'_user'; |
| 92 | + $userTextField = $prefix.'_user_text'; |
| 93 | + // Add item author relations... |
| 94 | + $userIds = $userIPs = array(); |
| 95 | + $sres = $db->select( $table, |
| 96 | + array($userField,$userTextField), |
| 97 | + array($field => $items) |
| 98 | + ); |
| 99 | + foreach( $sres as $srow ) { |
| 100 | + if( $srow->$userField > 0 ) |
| 101 | + $userIds[] = intval($srow->$userField); |
| 102 | + else if( $srow->$userTextField != '' ) |
| 103 | + $userIPs[] = $srow->$userTextField; |
| 104 | + } |
| 105 | + // Add item author relations... |
| 106 | + $log->addRelations( 'target_author_id', $userIds, $row->log_id ); |
| 107 | + $log->addRelations( 'target_author_ip', $userIPs, $row->log_id ); |
77 | 108 | // RevisionDelete logs - log events |
78 | | - } else if( LogEventsList::typeAction( $row, array('delete','suppress'), 'event' ) ) { |
| 109 | + } else if( LogEventsList::typeAction( $row, $delTypes, 'event' ) ) { |
79 | 110 | $params = LogPage::extractParams( $row->log_params ); |
80 | 111 | // Param format: <item CSV> [<ofield> <nfield>] |
81 | | - if( count($params) >= 1 ) { |
82 | | - $items = explode(',',$params[0]); |
83 | | - $log = new LogPage( $row->log_type ); |
84 | | - $log->addRelations( 'log_id', $items, $row->log_id ); |
| 112 | + if( count($params) < 1 ) continue; // bad row |
| 113 | + $items = explode( ',', $params[0] ); |
| 114 | + $log = new LogPage( $row->log_type ); |
| 115 | + // Add item relations... |
| 116 | + $log->addRelations( 'log_id', $items, $row->log_id ); |
| 117 | + // Add item author relations... |
| 118 | + $userIds = $userIPs = array(); |
| 119 | + $sres = $db->select( 'logging', |
| 120 | + array('log_user','log_user_text'), |
| 121 | + array('log_id' => $items) |
| 122 | + ); |
| 123 | + foreach( $sres as $srow ) { |
| 124 | + if( $srow->log_user > 0 ) |
| 125 | + $userIds[] = intval($srow->log_user); |
| 126 | + else if( IP::isIPAddress($srow->log_user_text) ) |
| 127 | + $userIPs[] = $srow->log_user_text; |
85 | 128 | } |
| 129 | + $log->addRelations( 'target_author_id', $userIds, $row->log_id ); |
| 130 | + $log->addRelations( 'target_author_ip', $userIPs, $row->log_id ); |
86 | 131 | } |
87 | 132 | } |
88 | 133 | $blockStart += self::LOG_SEARCH_BATCH_SIZE; |
Index: trunk/phase3/includes/LogEventsList.php |
— | — | @@ -94,6 +94,7 @@ |
95 | 95 | $html .= $this->getTypeMenu( $types ) . "\n"; |
96 | 96 | $html .= $this->getUserInput( $user ) . "\n"; |
97 | 97 | $html .= $this->getTitleInput( $page ) . "\n"; |
| 98 | + $html .= $this->getExtraInputs( $types ) . "\n"; |
98 | 99 | |
99 | 100 | // Title pattern, if allowed |
100 | 101 | if (!$wgMiserMode) { |
— | — | @@ -238,6 +239,15 @@ |
239 | 240 | Xml::checkLabel( wfMsg( 'log-title-wildcard' ), 'pattern', 'pattern', $pattern ) . |
240 | 241 | '</span>'; |
241 | 242 | } |
| 243 | + |
| 244 | + private function getExtraInputs( $types ) { |
| 245 | + global $wgRequest; |
| 246 | + if( count($types) == 1 && $types[0] == 'suppress' ) { |
| 247 | + return Xml::inputLabel( wfMsg('revdelete-offender'), 'offender', |
| 248 | + 'mw-log-offender', 20, $wgRequest->getVal('offender') ); |
| 249 | + } |
| 250 | + return ''; |
| 251 | + } |
242 | 252 | |
243 | 253 | public function beginLogEventsList() { |
244 | 254 | return "<ul>\n"; |
Index: trunk/phase3/includes/specials/SpecialLog.php |
— | — | @@ -52,9 +52,19 @@ |
53 | 53 | $y = ''; |
54 | 54 | $m = ''; |
55 | 55 | } |
| 56 | + # Handle type-specific inputs |
| 57 | + $qc = array(); |
| 58 | + if( $type == 'suppress' ) { |
| 59 | + $offender = User::newFromName( $wgRequest->getVal('offender'), false ); |
| 60 | + if( $offender && $offender->getId() > 0 ) { |
| 61 | + $qc = array( 'ls_field' => 'target_author_id', 'ls_value' => $offender->getId() ); |
| 62 | + } else if( $offender && IP::isIPAddress( $offender->getName() ) ) { |
| 63 | + $qc = array( 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() ); |
| 64 | + } |
| 65 | + } |
56 | 66 | # Create a LogPager item to get the results and a LogEventsList item to format them... |
57 | 67 | $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 ); |
58 | | - $pager = new LogPager( $loglist, $type, $user, $title, $pattern, array(), $y, $m, $tagFilter ); |
| 68 | + $pager = new LogPager( $loglist, $type, $user, $title, $pattern, $qc, $y, $m, $tagFilter ); |
59 | 69 | # Set title and add header |
60 | 70 | $loglist->showHeader( $pager->getType() ); |
61 | 71 | # Show form options |
Index: trunk/phase3/includes/specials/SpecialRevisiondelete.php |
— | — | @@ -201,9 +201,8 @@ |
202 | 202 | # Give a link to the logs/hist for this page |
203 | 203 | if( $this->targetObj ) { |
204 | 204 | $links = array(); |
205 | | - $logtitle = SpecialPage::getTitleFor( 'Log' ); |
206 | 205 | $links[] = $this->skin->linkKnown( |
207 | | - $logtitle, |
| 206 | + SpecialPage::getTitleFor( 'Log' ), |
208 | 207 | wfMsgHtml( 'viewpagelogs' ), |
209 | 208 | array(), |
210 | 209 | array( 'page' => $this->targetObj->getPrefixedText() ) |
— | — | @@ -616,6 +615,7 @@ |
617 | 616 | // Get DB field name for URL param... |
618 | 617 | // Future code for other things may also track |
619 | 618 | // other types of revision-specific changes. |
| 619 | + // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name |
620 | 620 | public static function getRelationType( $typeName ) { |
621 | 621 | if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) { |
622 | 622 | $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName]; |
— | — | @@ -638,6 +638,8 @@ |
639 | 639 | var $type = null; // override this |
640 | 640 | var $idField = null; // override this |
641 | 641 | var $dateField = false; // override this |
| 642 | + var $authorIdField = false; // override this |
| 643 | + var $authorNameField = false; // override this |
642 | 644 | |
643 | 645 | /** |
644 | 646 | * @param $special The parent SpecialPage |
— | — | @@ -658,7 +660,7 @@ |
659 | 661 | } |
660 | 662 | |
661 | 663 | /** |
662 | | - * Get the DB field name associated with the ID list/ |
| 664 | + * Get the DB field name associated with the ID list |
663 | 665 | */ |
664 | 666 | public function getIdField() { |
665 | 667 | return $this->idField; |
— | — | @@ -672,6 +674,19 @@ |
673 | 675 | } |
674 | 676 | |
675 | 677 | /** |
| 678 | + * Get the DB field name storing user ids |
| 679 | + */ |
| 680 | + public function getAuthorIdField() { |
| 681 | + return $this->authorIdField; |
| 682 | + } |
| 683 | + |
| 684 | + /** |
| 685 | + * Get the DB field name storing user names |
| 686 | + */ |
| 687 | + public function getAuthorNameField() { |
| 688 | + return $this->authorNameField; |
| 689 | + } |
| 690 | + /** |
676 | 691 | * Set the visibility for the revisions in this list. Logging and |
677 | 692 | * transactions are done here. |
678 | 693 | * |
— | — | @@ -692,6 +707,7 @@ |
693 | 708 | $missing = array_flip( $this->ids ); |
694 | 709 | $this->clearFileOps(); |
695 | 710 | $idsForLog = array(); |
| 711 | + $authorIds = $authorIPs = array(); |
696 | 712 | |
697 | 713 | for ( $this->reset(); $this->current(); $this->next() ) { |
698 | 714 | $item = $this->current(); |
— | — | @@ -731,6 +747,11 @@ |
732 | 748 | if ( $ok ) { |
733 | 749 | $idsForLog[] = $item->getId(); |
734 | 750 | $status->successCount++; |
| 751 | + if( $item->getAuthorId() > 0 ) { |
| 752 | + $authorIds[] = $item->getAuthorId(); |
| 753 | + } else if( IP::isIPAddress( $item->getAuthorName() ) ) { |
| 754 | + $authorIPs[] = $item->getAuthorName(); |
| 755 | + } |
735 | 756 | } else { |
736 | 757 | $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() ); |
737 | 758 | $status->failCount++; |
— | — | @@ -768,6 +789,8 @@ |
769 | 790 | 'oldBits' => $oldBits, |
770 | 791 | 'comment' => $comment, |
771 | 792 | 'ids' => $idsForLog, |
| 793 | + 'authorIds' => $authorIds, |
| 794 | + 'authorIPs' => $authorIPs |
772 | 795 | ) ); |
773 | 796 | $dbw->commit(); |
774 | 797 | |
— | — | @@ -793,6 +816,8 @@ |
794 | 817 | * title: The target title |
795 | 818 | * ids: The ID list |
796 | 819 | * comment: The log comment |
| 820 | + * authorsIds: The array of the user IDs of the offenders |
| 821 | + * authorsIPs: The array of the IP/anon user offenders |
797 | 822 | */ |
798 | 823 | protected function updateLog( $params ) { |
799 | 824 | // Get the URL param's corresponding DB field |
— | — | @@ -814,6 +839,8 @@ |
815 | 840 | $params['comment'], $logParams ); |
816 | 841 | // Allow for easy searching of deletion log items for revision/log items |
817 | 842 | $log->addRelations( $field, $params['ids'], $logid ); |
| 843 | + $log->addRelations( 'target_author_id', $params['authorIds'], $logid ); |
| 844 | + $log->addRelations( 'target_author_ip', $params['authorIPs'], $logid ); |
818 | 845 | } |
819 | 846 | |
820 | 847 | /** |
— | — | @@ -976,7 +1003,23 @@ |
977 | 1004 | $field = $this->list->getTimestampField(); |
978 | 1005 | return wfTimestamp( TS_MW, $this->row->$field ); |
979 | 1006 | } |
| 1007 | + |
| 1008 | + /** |
| 1009 | + * Get the author user ID |
| 1010 | + */ |
| 1011 | + public function getAuthorId() { |
| 1012 | + $field = $this->list->getAuthorIdField(); |
| 1013 | + return intval( $this->row->$field ); |
| 1014 | + } |
980 | 1015 | |
| 1016 | + /** |
| 1017 | + * Get the author user name |
| 1018 | + */ |
| 1019 | + public function getAuthorName() { |
| 1020 | + $field = $this->list->getAuthorNameField(); |
| 1021 | + return strval( $this->row->$field ); |
| 1022 | + } |
| 1023 | + |
981 | 1024 | /** |
982 | 1025 | * Returns true if the item is "current", and the operation to set the given |
983 | 1026 | * bits can't be executed for that reason |
— | — | @@ -1023,6 +1066,8 @@ |
1024 | 1067 | var $type = 'revision'; |
1025 | 1068 | var $idField = 'rev_id'; |
1026 | 1069 | var $dateField = 'rev_timestamp'; |
| 1070 | + var $authorIdField = 'rev_user'; |
| 1071 | + var $authorNameField = 'rev_user_text'; |
1027 | 1072 | |
1028 | 1073 | public function doQuery( $db ) { |
1029 | 1074 | $ids = array_map( 'intval', $this->ids ); |
— | — | @@ -1192,6 +1237,8 @@ |
1193 | 1238 | var $type = 'archive'; |
1194 | 1239 | var $idField = 'ar_timestamp'; |
1195 | 1240 | var $dateField = 'ar_timestamp'; |
| 1241 | + var $authorIdField = 'ar_user'; |
| 1242 | + var $authorNameField = 'ar_user_text'; |
1196 | 1243 | |
1197 | 1244 | public function doQuery( $db ) { |
1198 | 1245 | $timestamps = array(); |
— | — | @@ -1286,6 +1333,8 @@ |
1287 | 1334 | var $type = 'oldimage'; |
1288 | 1335 | var $idField = 'oi_archive_name'; |
1289 | 1336 | var $dateField = 'oi_timestamp'; |
| 1337 | + var $authorIdField = 'oi_user'; |
| 1338 | + var $authorNameField = 'oi_user_text'; |
1290 | 1339 | var $storeBatch, $deleteBatch, $cleanupBatch; |
1291 | 1340 | |
1292 | 1341 | public function doQuery( $db ) { |
— | — | @@ -1501,6 +1550,8 @@ |
1502 | 1551 | var $type = 'filearchive'; |
1503 | 1552 | var $idField = 'fa_id'; |
1504 | 1553 | var $dateField = 'fa_timestamp'; |
| 1554 | + var $authorIdField = 'fa_user'; |
| 1555 | + var $authorNameField = 'fa_user_text'; |
1505 | 1556 | |
1506 | 1557 | public function doQuery( $db ) { |
1507 | 1558 | $ids = array_map( 'intval', $this->ids ); |
— | — | @@ -1566,6 +1617,8 @@ |
1567 | 1618 | var $type = 'logging'; |
1568 | 1619 | var $idField = 'log_id'; |
1569 | 1620 | var $dateField = 'log_timestamp'; |
| 1621 | + var $authorIdField = 'log_user'; |
| 1622 | + var $authorNameField = 'log_user_text'; |
1570 | 1623 | |
1571 | 1624 | public function doQuery( $db ) { |
1572 | 1625 | global $wgMessageCache; |
— | — | @@ -1619,7 +1672,7 @@ |
1620 | 1673 | ), |
1621 | 1674 | array( |
1622 | 1675 | 'rc_logid' => $this->row->log_id, |
1623 | | - 'rc_timestamp' => $this->row->log_timestamp |
| 1676 | + 'rc_timestamp' => $this->row->log_timestamp // index |
1624 | 1677 | ), |
1625 | 1678 | __METHOD__ |
1626 | 1679 | ); |
— | — | @@ -1641,9 +1694,9 @@ |
1642 | 1695 | $paramArray = LogPage::extractParams( $this->row->log_params ); |
1643 | 1696 | $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title ); |
1644 | 1697 | |
1645 | | - $logtitle = SpecialPage::getTitleFor( 'Log' ); |
| 1698 | + // Log link for this page |
1646 | 1699 | $loglink = $this->special->skin->link( |
1647 | | - $logtitle, |
| 1700 | + SpecialPage::getTitleFor( 'Log' ), |
1648 | 1701 | wfMsgHtml( 'log' ), |
1649 | 1702 | array(), |
1650 | 1703 | array( 'page' => $title->getPrefixedText() ) |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -1509,6 +1509,7 @@ |
1510 | 1510 | 'revdelete-otherreason' => 'Other/additional reason:', |
1511 | 1511 | 'revdelete-reasonotherlist' => 'Other reason', |
1512 | 1512 | 'revdelete-edit-reasonlist' => 'Edit delete reasons', |
| 1513 | +'revdelete-offender' => 'Offender:', |
1513 | 1514 | |
1514 | 1515 | # Suppression log |
1515 | 1516 | 'suppressionlog' => 'Suppression log', |