Index: trunk/phase3/maintenance/archives/patch-change_tag.sql |
— | — | @@ -0,0 +1,31 @@ |
| 2 | +-- A table to track tags for revisions, logs and recent changes. |
| 3 | +-- Andrew Garrett, 2009-01 |
| 4 | +CREATE TABLE /*_*/change_tag ( |
| 5 | + ct_rc_id int NULL, |
| 6 | + ct_log_id int NULL, |
| 7 | + ct_rev_id int NULL, |
| 8 | + ct_tag varchar(255) NOT NULL, |
| 9 | + ct_params BLOB NULL, |
| 10 | + |
| 11 | + UNIQUE KEY (ct_rc_id,ct_tag), |
| 12 | + UNIQUE KEY (ct_log_id,ct_tag), |
| 13 | + UNIQUE KEY (ct_rev_id,ct_tag), |
| 14 | + KEY (ct_tag,ct_rc_id,ct_rev_id,ct_log_id) -- Covering index, so we can pull all the info only out of the index. |
| 15 | +) /*$wgDBTableOptions*/; |
| 16 | + |
| 17 | +-- Rollup table to pull a LIST of tags simply without ugly GROUP_CONCAT that only works on MySQL 4.1+ |
| 18 | +CREATE TABLE /*_*/tag_summary ( |
| 19 | + ts_rc_id int NULL, |
| 20 | + ts_log_id int NULL, |
| 21 | + ts_rev_id int NULL, |
| 22 | + ts_tags BLOB NOT NULL, |
| 23 | + |
| 24 | + UNIQUE KEY (ts_rc_id), |
| 25 | + UNIQUE KEY (ts_log_id), |
| 26 | + UNIQUE KEY (ts_rev_id) |
| 27 | +) /*$wgDBTableOptions*/; |
| 28 | + |
| 29 | +CREATE TABLE /*_*/valid_tag ( |
| 30 | + vt_tag varchar(255) NOT NULL, |
| 31 | + PRIMARY KEY (vt_tag) |
| 32 | +) /*$wgDBTableOptions*/; |
\ No newline at end of file |
Index: trunk/phase3/maintenance/updaters.inc |
— | — | @@ -158,6 +158,9 @@ |
159 | 159 | array( 'do_active_users_init' ), |
160 | 160 | array( 'add_field', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ), |
161 | 161 | array( 'sqlite_initial_indexes' ), |
| 162 | + array( 'add_table', 'change_tag', 'patch-change_tag.sql' ), |
| 163 | + array( 'add_table', 'tag_summary', 'patch-change_tag.sql' ), |
| 164 | + array( 'add_table', 'valid_tag', 'patch-change_tag.sql' ), |
162 | 165 | ), |
163 | 166 | ); |
164 | 167 | |
Index: trunk/phase3/maintenance/tables.sql |
— | — | @@ -1234,4 +1234,36 @@ |
1235 | 1235 | ul_key varchar(255) NOT NULL PRIMARY KEY |
1236 | 1236 | ) /*$wgDBTableOptions*/; |
1237 | 1237 | |
| 1238 | +--- A table to track tags for revisions, logs and recent changes. |
| 1239 | +REATE TABLE /*_*/change_tag ( |
| 1240 | + ct_rc_id int NULL, |
| 1241 | + ct_log_id int NULL, |
| 1242 | + ct_rev_id int NULL, |
| 1243 | + ct_tag varchar(255) NOT NULL, |
| 1244 | + ct_params BLOB NULL, |
| 1245 | + |
| 1246 | + UNIQUE KEY (ct_rc_id,ct_tag), |
| 1247 | + UNIQUE KEY (ct_log_id,ct_tag), |
| 1248 | + UNIQUE KEY (ct_rev_id,ct_tag), |
| 1249 | + KEY (ct_tag,ct_rc_id,ct_rev_id,ct_log_id) -- Covering index, so we can pull all the info only out of the index. |
| 1250 | +) /*$wgDBTableOptions*/; |
| 1251 | + |
| 1252 | +-- Rollup table to pull a LIST of tags simply without ugly GROUP_CONCAT that only works on MySQL 4.1+ |
| 1253 | +CREATE TABLE /*_*/tag_summary ( |
| 1254 | + ts_rc_id int NULL, |
| 1255 | + ts_log_id int NULL, |
| 1256 | + ts_rev_id int NULL, |
| 1257 | + ts_tags BLOB NOT NULL, |
| 1258 | + |
| 1259 | + UNIQUE KEY (ts_rc_id), |
| 1260 | + UNIQUE KEY (ts_log_id), |
| 1261 | + UNIQUE KEY (ts_rev_id), |
| 1262 | +) /*$wgDBTableOptions*/; |
| 1263 | + |
| 1264 | +CREATE TABLE /*_*/valid_tag ( |
| 1265 | + vt_tag varchar(255) NOT NULL, |
| 1266 | + PRIMARY KEY (vt_tag) |
| 1267 | +) /*$wgDBTableOptions*/; |
| 1268 | + |
| 1269 | + |
1238 | 1270 | -- vim: sw=2 sts=2 et |
Index: trunk/phase3/skins/common/history.js |
— | — | @@ -27,7 +27,13 @@ |
28 | 28 | } |
29 | 29 | if (oli) { // it's the second checked radio |
30 | 30 | if (inputs[1].checked) { |
31 | | - oli.className = "selected"; |
| 31 | + if ( (typeof oli.className) != 'undefined') { |
| 32 | + oli.classNameOriginal = oli.className.replace( 'selected', '' ); |
| 33 | + } else { |
| 34 | + oli.classNameOriginal = ''; |
| 35 | + } |
| 36 | + |
| 37 | + oli.className = "selected "+oli.classNameOriginal; |
32 | 38 | return false; |
33 | 39 | } |
34 | 40 | } else if (inputs[0].checked) { |
— | — | @@ -42,7 +48,13 @@ |
43 | 49 | if (dli) { |
44 | 50 | inputs[1].style.visibility = 'hidden'; |
45 | 51 | } |
46 | | - lis[i].className = "selected"; |
| 52 | + if ( (typeof lis[i].className) != 'undefined') { |
| 53 | + lis[i].classNameOriginal = lis[i].className.replace( 'selected', '' ); |
| 54 | + } else { |
| 55 | + lis[i].classNameOriginal = ''; |
| 56 | + } |
| 57 | + |
| 58 | + lis[i].className = "selected "+lis[i].classNameOriginal; |
47 | 59 | oli = lis[i]; |
48 | 60 | } else { // no radio is checked in this row |
49 | 61 | if (!oli) { |
— | — | @@ -55,7 +67,7 @@ |
56 | 68 | } else { |
57 | 69 | inputs[1].style.visibility = 'visible'; |
58 | 70 | } |
59 | | - lis[i].className = ""; |
| 71 | + lis[i].className = lis[i].classNameOriginal; |
60 | 72 | } |
61 | 73 | } |
62 | 74 | } |
Index: trunk/phase3/docs/hooks.txt |
— | — | @@ -846,6 +846,9 @@ |
847 | 847 | 'LinksUpdateConstructed': At the end of LinksUpdate() is contruction. |
848 | 848 | &$linksUpdate: the LinkUpdate object |
849 | 849 | |
| 850 | +'ListDefinedTags': When trying to find all defined tags. |
| 851 | +&$tags: The list of tags. |
| 852 | + |
850 | 853 | 'LoadAllMessages': called by MessageCache::loadAllMessages() to load extensions messages |
851 | 854 | |
852 | 855 | 'LoadExtensionSchemaUpdates': called by maintenance/updaters.inc when upgrading database schema |
Index: trunk/phase3/includes/ChangeTags.php |
— | — | @@ -0,0 +1,163 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +if (!defined( 'MEDIAWIKI' )) |
| 5 | + die; |
| 6 | + |
| 7 | +class ChangeTags { |
| 8 | + static function formatSummaryRow( $tags, $page ) { |
| 9 | + if (!$tags) |
| 10 | + return array('',array()); |
| 11 | + |
| 12 | + $classes = array(); |
| 13 | + |
| 14 | + $tags = explode( ',', $tags ); |
| 15 | + $displayTags = array(); |
| 16 | + foreach( $tags as $tag ) { |
| 17 | + $displayTags[] = self::tagDescription( $tag ); |
| 18 | + $classes[] = "mw-tag-$tag"; |
| 19 | + } |
| 20 | + |
| 21 | + return array( '(' . implode( ', ', $displayTags ) . ')', $classes ); |
| 22 | + } |
| 23 | + |
| 24 | + static function tagDescription( $tag ) { |
| 25 | + $msg = wfMsgExt( "tag-$tag", 'parseinline' ); |
| 26 | + if ( wfEmptyMsg( "tag-$tag", $msg ) ) { |
| 27 | + return htmlspecialchars($tag); |
| 28 | + } |
| 29 | + return $msg; |
| 30 | + } |
| 31 | + |
| 32 | + ## Basic utility method to add tags to a particular change, given its rc_id, rev_id and/or log_id. |
| 33 | + static function addTags( $tags, $rc_id=null, $rev_id=null, $log_id=null, $params = null ) { |
| 34 | + if ( !is_array($tags) ) { |
| 35 | + $tags = array( $tags ); |
| 36 | + } |
| 37 | + |
| 38 | + $tags = array_filter( $tags ); // Make sure we're submitting all tags... |
| 39 | + |
| 40 | + if (!$rc_id && !$rev_id && !$log_id) { |
| 41 | + throw new MWException( "At least one of: RCID, revision ID, and log ID MUST be specified when adding a tag to a change!" ); |
| 42 | + } |
| 43 | + |
| 44 | + $dbr = wfGetDB( DB_SLAVE ); |
| 45 | + |
| 46 | + // Might as well look for rcids and so on. |
| 47 | + if (!$rc_id) { |
| 48 | + $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave. |
| 49 | + if ($log_id) { |
| 50 | + $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_logid' => $log_id ), __METHOD__ ); |
| 51 | + } elseif ($rev_id) { |
| 52 | + $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_this_oldid' => $rev_id ), __METHOD__ ); |
| 53 | + } |
| 54 | + } elseif (!$log_id && !$rev_id) { |
| 55 | + $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave. |
| 56 | + $log_id = $dbr->selectField( 'recentchanges', 'rc_logid', array( 'rc_id' => $rc_id ), __METHOD__ ); |
| 57 | + $rev_id = $dbr->selectField( 'recentchanges', 'rc_this_oldid', array( 'rc_id' => $rc_id ), __METHOD__ ); |
| 58 | + } |
| 59 | + |
| 60 | + $tsConds = array_filter( array( 'ts_rc_id' => $rc_id, 'ts_rev_id' => $rev_id, 'ts_log_id' => $log_id ) ); |
| 61 | + |
| 62 | + ## Update the summary row. |
| 63 | + $prevTags = $dbr->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ ); |
| 64 | + $prevTags = $prevTags ? $prevTags : ''; |
| 65 | + $prevTags = array_filter( explode( ',', $prevTags ) ); |
| 66 | + $newTags = array_unique( array_merge( $prevTags, $tags ) ); |
| 67 | + sort($prevTags); |
| 68 | + sort($newTags); |
| 69 | + |
| 70 | + if ( $prevTags == $newTags ) { |
| 71 | + // No change. |
| 72 | + return false; |
| 73 | + } |
| 74 | + |
| 75 | + $dbw = wfGetDB( DB_MASTER ); |
| 76 | + $dbw->replace( 'tag_summary', array( 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ), array_filter( array_merge( $tsConds, array( 'ts_tags' => implode( ',', $newTags ) ) ) ), __METHOD__ ); |
| 77 | + |
| 78 | + // Insert the tags rows. |
| 79 | + $tagsRows = array(); |
| 80 | + foreach( $tags as $tag ) { // Filter so we don't insert NULLs as zero accidentally. |
| 81 | + $tagsRows[] = array_filter( array( 'ct_tag' => $tag, 'ct_rc_id' => $rc_id, 'ct_log_id' => $log_id, 'ct_rev_id' => $rev_id, 'ct_params' => $params ) ); |
| 82 | + } |
| 83 | + |
| 84 | + $dbw->insert( 'change_tag', $tagsRows, __METHOD__, array('IGNORE') ); |
| 85 | + |
| 86 | + return true; |
| 87 | + } |
| 88 | + |
| 89 | + /** |
| 90 | + * Applies all tags-related changes to a query. |
| 91 | + * Handles selecting tags, and filtering. |
| 92 | + * Needs $tables to be set up properly, so we can figure out which join conditions to use. |
| 93 | + */ |
| 94 | + static function modifyDisplayQuery( &$tables, &$fields, &$conds, &$join_conds, $filter_tag = false ) { |
| 95 | + global $wgRequest; |
| 96 | + |
| 97 | + if ($filter_tag === false) { |
| 98 | + $filter_tag = $wgRequest->getVal( 'tagfilter' ); |
| 99 | + } |
| 100 | + |
| 101 | + // Figure out which conditions can be done. |
| 102 | + $join_field = ''; |
| 103 | + if ( in_array('recentchanges', $tables) ) { |
| 104 | + $join_cond = 'rc_id'; |
| 105 | + } elseif( in_array('logging', $tables) ) { |
| 106 | + $join_cond = 'log_id'; |
| 107 | + } elseif ( in_array('revision', $tables) ) { |
| 108 | + $join_cond = 'rev_id'; |
| 109 | + } else { |
| 110 | + throw new MWException( "Unable to determine appropriate JOIN condition for tagging." ); |
| 111 | + } |
| 112 | + |
| 113 | + // JOIN on tag_summary |
| 114 | + $tables[] = 'tag_summary'; |
| 115 | + $join_conds['tag_summary'] = array( 'LEFT JOIN', "ts_$join_cond=$join_cond" ); |
| 116 | + $fields[] = 'ts_tags'; |
| 117 | + |
| 118 | + if ($filter_tag) { |
| 119 | + // Somebody wants to filter on a tag. |
| 120 | + // Add an INNER JOIN on change_tag |
| 121 | + |
| 122 | + $tables[] = 'change_tag'; |
| 123 | + $join_conds['change_tag'] = array( 'INNER JOIN', "ct_$join_cond=$join_cond" ); |
| 124 | + $conds['ct_tag'] = $filter_tag; |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + /** |
| 129 | + * If $fullForm is set to false, then it returns an array of (label, form). |
| 130 | + * If $fullForm is true, it returns an entire form. |
| 131 | + */ |
| 132 | + static function buildTagFilterSelector( $selected='', $fullForm = false /* used to put a full form around the selector */ ) { |
| 133 | + global $wgTitle; |
| 134 | + |
| 135 | + $data = array( wfMsgExt( 'tag-filter', 'parseinline' ), Xml::input( 'tagfilter', 20, $selected ) ); |
| 136 | + |
| 137 | + if (!$fullForm) { |
| 138 | + return $data; |
| 139 | + } |
| 140 | + |
| 141 | + $html = implode( ' ', $data ); |
| 142 | + $html .= "\n" . Xml::element( 'input', array( 'type' => 'submit', 'value' => wfMsg( 'tag-filter-submit' ) ) ); |
| 143 | + $html .= "\n" . Xml::hidden( 'title', $wgTitle-> getPrefixedText() ); |
| 144 | + $html = Xml::tags( 'form', array( 'action' => $wgTitle->getLocalURL(), 'method' => 'get' ), $html ); |
| 145 | + |
| 146 | + return $html; |
| 147 | + } |
| 148 | + |
| 149 | + /** Basically lists defined tags which count even if they aren't applied to anything */ |
| 150 | + static function listDefinedTags() { |
| 151 | + $emptyTags = array(); |
| 152 | + |
| 153 | + // Some DB stuff |
| 154 | + $dbr = wfGetDB( DB_SLAVE ); |
| 155 | + $res = $dbr->select( 'valid_tag', 'vt_tag', array(), __METHOD__ ); |
| 156 | + while( $row = $res->fetchObject() ) { |
| 157 | + $emptyTags[] = $row->vt_tag; |
| 158 | + } |
| 159 | + |
| 160 | + wfRunHooks( 'ListDefinedTags', array(&$emptyTags) ); |
| 161 | + |
| 162 | + return array_filter( array_unique( $emptyTags ) ); |
| 163 | + } |
| 164 | +} |
\ No newline at end of file |
Index: trunk/phase3/includes/LogEventsList.php |
— | — | @@ -68,7 +68,7 @@ |
69 | 69 | * @param $filter Boolean |
70 | 70 | */ |
71 | 71 | public function showOptions( $type = '', $user = '', $page = '', $pattern = '', $year = '', |
72 | | - $month = '', $filter = null ) |
| 72 | + $month = '', $filter = null, $tagFilter='' ) |
73 | 73 | { |
74 | 74 | global $wgScript, $wgMiserMode; |
75 | 75 | $action = htmlspecialchars( $wgScript ); |
— | — | @@ -83,7 +83,8 @@ |
84 | 84 | $this->getTitleInput( $page ) . "\n" . |
85 | 85 | ( !$wgMiserMode ? ($this->getTitlePattern( $pattern )."\n") : "" ) . |
86 | 86 | "<p>" . $this->getDateMenu( $year, $month ) . "\n" . |
87 | | - ( $filter ? "</p><p>".$this->getFilterLinks( $type, $filter )."\n" : "" ) . |
| 87 | + Xml::tags( 'p', null, implode( ' ', ChangeTags::buildTagFilterSelector( $tagFilter ) ) ) . "\n" . |
| 88 | + ( $filter ? "</p><p>".$this->getFilterLinks( $type, $filter )."\n" : "" ) . "\n" . |
88 | 89 | Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "</p>\n" . |
89 | 90 | "</fieldset></form>" |
90 | 91 | ); |
— | — | @@ -230,6 +231,7 @@ |
231 | 232 | global $wgLang, $wgUser, $wgContLang; |
232 | 233 | |
233 | 234 | $title = Title::makeTitle( $row->log_namespace, $row->log_title ); |
| 235 | + $classes = array( "mw-logline-{$row->log_type}" ); |
234 | 236 | $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->log_timestamp), true ); |
235 | 237 | // User links |
236 | 238 | if( self::isDeleted($row,LogPage::DELETED_USER) ) { |
— | — | @@ -357,12 +359,16 @@ |
358 | 360 | $this->skin, $paramArray, true ); |
359 | 361 | } |
360 | 362 | |
| 363 | + // Any tags... |
| 364 | + list($tagDisplay, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'logevent' ); |
| 365 | + $classes = array_merge( $classes, $newClasses ); |
| 366 | + |
361 | 367 | if( $revert != '' ) { |
362 | 368 | $revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>'; |
363 | 369 | } |
364 | 370 | |
365 | | - return Xml::tags( 'li', array( "class" => "mw-logline-$row->log_type" ), |
366 | | - $del . $time . ' ' . $userLink . ' ' . $action . ' ' . $comment . ' ' . $revert ) . "\n"; |
| 371 | + return Xml::tags( 'li', array( "class" => implode( ' ', $classes ) ), |
| 372 | + $del . $time . ' ' . $userLink . ' ' . $action . ' ' . $comment . ' ' . $revert . " $tagDisplay" ) . "\n"; |
367 | 373 | } |
368 | 374 | |
369 | 375 | /** |
— | — | @@ -508,7 +514,7 @@ |
509 | 515 | * @param $month Integer |
510 | 516 | */ |
511 | 517 | public function __construct( $list, $type = '', $user = '', $title = '', $pattern = '', |
512 | | - $conds = array(), $year = false, $month = false ) |
| 518 | + $conds = array(), $year = false, $month = false, $tagFilter = '' ) |
513 | 519 | { |
514 | 520 | parent::__construct(); |
515 | 521 | $this->mConds = $conds; |
— | — | @@ -519,6 +525,7 @@ |
520 | 526 | $this->limitUser( $user ); |
521 | 527 | $this->limitTitle( $title, $pattern ); |
522 | 528 | $this->getDateCond( $year, $month ); |
| 529 | + $this->mTagFilter = $tagFilter; |
523 | 530 | } |
524 | 531 | |
525 | 532 | public function getDefaultQuery() { |
— | — | @@ -643,13 +650,18 @@ |
644 | 651 | } else { |
645 | 652 | $index = array( 'USE INDEX' => array( 'logging' => 'times' ) ); |
646 | 653 | } |
647 | | - return array( |
| 654 | + $info = array( |
648 | 655 | 'tables' => array( 'logging', 'user' ), |
649 | 656 | 'fields' => array( 'log_type', 'log_action', 'log_user', 'log_namespace', 'log_title', 'log_params', |
650 | 657 | 'log_comment', 'log_id', 'log_deleted', 'log_timestamp', 'user_name', 'user_editcount' ), |
651 | 658 | 'conds' => $this->mConds, |
652 | | - 'options' => $index |
| 659 | + 'options' => $index, |
| 660 | + 'join_conds' => array( 'user' => array( 'INNER JOIN', 'user_id=log_user' ) ), |
653 | 661 | ); |
| 662 | + |
| 663 | + ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'], $info['join_conds'], $this->mTagFilter ); |
| 664 | + |
| 665 | + return $info; |
654 | 666 | } |
655 | 667 | |
656 | 668 | function getIndexField() { |
— | — | @@ -700,6 +712,10 @@ |
701 | 713 | public function getMonth() { |
702 | 714 | return $this->mMonth; |
703 | 715 | } |
| 716 | + |
| 717 | + public function getTagFilter() { |
| 718 | + return $this->mTagFilter; |
| 719 | + } |
704 | 720 | } |
705 | 721 | |
706 | 722 | /** |
— | — | @@ -721,6 +737,7 @@ |
722 | 738 | $pattern = $request->getBool( 'pattern' ); |
723 | 739 | $year = $request->getIntOrNull( 'year' ); |
724 | 740 | $month = $request->getIntOrNull( 'month' ); |
| 741 | + $tagFilter = $request->getVal( 'tagfilter' ); |
725 | 742 | # Don't let the user get stuck with a certain date |
726 | 743 | $skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev'; |
727 | 744 | if( $skip ) { |
— | — | @@ -729,7 +746,7 @@ |
730 | 747 | } |
731 | 748 | # Use new list class to output results |
732 | 749 | $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 ); |
733 | | - $this->pager = new LogPager( $loglist, $type, $user, $title, $pattern, $year, $month ); |
| 750 | + $this->pager = new LogPager( $loglist, $type, $user, $title, $pattern, $year, $month, $tagFilter ); |
734 | 751 | } |
735 | 752 | |
736 | 753 | /** |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -28,6 +28,7 @@ |
29 | 29 | 'CategoryViewer' => 'includes/CategoryPage.php', |
30 | 30 | 'ChangesList' => 'includes/ChangesList.php', |
31 | 31 | 'ChangesFeed' => 'includes/ChangesFeed.php', |
| 32 | + 'ChangeTags' => 'includes/ChangeTags.php', |
32 | 33 | 'ChannelFeed' => 'includes/Feed.php', |
33 | 34 | 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php', |
34 | 35 | 'ConstantDependency' => 'includes/CacheDependency.php', |
— | — | @@ -495,6 +496,7 @@ |
496 | 497 | 'SpecialSearch' => 'includes/specials/SpecialSearch.php', |
497 | 498 | 'SpecialSearchOld' => 'includes/specials/SpecialSearch.php', |
498 | 499 | 'SpecialStatistics' => 'includes/specials/SpecialStatistics.php', |
| 500 | + 'SpecialTags' => 'includes/specials/SpecialTags.php', |
499 | 501 | 'SpecialVersion' => 'includes/specials/SpecialVersion.php', |
500 | 502 | 'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php', |
501 | 503 | 'UncategorizedPagesPage' => 'includes/specials/SpecialUncategorizedpages.php', |
Index: trunk/phase3/includes/ChangesList.php |
— | — | @@ -338,6 +338,12 @@ |
339 | 339 | } |
340 | 340 | } |
341 | 341 | } |
| 342 | + |
| 343 | + protected function insertTags( &$s, &$rc, &$classes ) { |
| 344 | + list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $rc->mAttribs['ts_tags'], 'changeslist' ); |
| 345 | + $classes = array_merge( $classes, $newClasses ); |
| 346 | + $s .= ' ' . $tagSummary; |
| 347 | + } |
342 | 348 | } |
343 | 349 | |
344 | 350 | |
— | — | @@ -358,6 +364,7 @@ |
359 | 365 | $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] ); |
360 | 366 | |
361 | 367 | $s = ''; |
| 368 | + $classes = array(); |
362 | 369 | // Moved pages |
363 | 370 | if( $rc->mAttribs['rc_type'] == RC_MOVE || $rc->mAttribs['rc_type'] == RC_MOVE_OVER_REDIRECT ) { |
364 | 371 | $this->insertMove( $s, $rc ); |
— | — | @@ -394,6 +401,8 @@ |
395 | 402 | $this->insertAction( $s, $rc ); |
396 | 403 | # Edit or log comment |
397 | 404 | $this->insertComment( $s, $rc ); |
| 405 | + # Tags |
| 406 | + $this->insertTags( $s, $rc, $classes ); |
398 | 407 | # Rollback |
399 | 408 | $this->insertRollback( $s, $rc ); |
400 | 409 | # Mark revision as deleted if so |
— | — | @@ -409,7 +418,7 @@ |
410 | 419 | wfRunHooks( 'OldChangesListRecentChangesLine', array(&$this, &$s, $rc) ); |
411 | 420 | |
412 | 421 | wfProfileOut( __METHOD__ ); |
413 | | - return "$dateheader<li>$s</li>\n"; |
| 422 | + return "$dateheader<li class=\"".implode( ' ', $classes )."\">$s</li>\n"; |
414 | 423 | } |
415 | 424 | } |
416 | 425 | |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -1455,7 +1455,7 @@ |
1456 | 1456 | * to ensure that client-side caches don't keep obsolete copies of global |
1457 | 1457 | * styles. |
1458 | 1458 | */ |
1459 | | -$wgStyleVersion = '201'; |
| 1459 | +$wgStyleVersion = '202'; |
1460 | 1460 | |
1461 | 1461 | |
1462 | 1462 | # Server-side caching: |
Index: trunk/phase3/includes/specials/SpecialRecentchangeslinked.php |
— | — | @@ -15,6 +15,7 @@ |
16 | 16 | $opts = parent::getDefaultOptions(); |
17 | 17 | $opts->add( 'target', '' ); |
18 | 18 | $opts->add( 'showlinkedto', false ); |
| 19 | + $opts->add( 'tagfilter', '' ); |
19 | 20 | return $opts; |
20 | 21 | } |
21 | 22 | |
— | — | @@ -83,6 +84,8 @@ |
84 | 85 | $join_conds['watchlist'] = array( 'LEFT JOIN', "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace" ); |
85 | 86 | } |
86 | 87 | |
| 88 | + ChangeTags::modifyDisplayQuery( $tables, $select, $conds, $join_conds, $opts['tagfilter'] ); |
| 89 | + |
87 | 90 | // XXX: parent class does this, should we too? |
88 | 91 | // wfRunHooks('SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts ) ); |
89 | 92 | |
— | — | @@ -169,6 +172,7 @@ |
170 | 173 | Xml::input( 'target', 40, str_replace('_',' ',$opts['target']) ) . |
171 | 174 | Xml::check( 'showlinkedto', $opts['showlinkedto'], array('id' => 'showlinkedto') ) . ' ' . |
172 | 175 | Xml::label( wfMsg("recentchangeslinked-to"), 'showlinkedto' ) ); |
| 176 | + $extraOpts['tagfilter'] = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] ); |
173 | 177 | return $extraOpts; |
174 | 178 | } |
175 | 179 | |
Index: trunk/phase3/includes/specials/SpecialNewpages.php |
— | — | @@ -32,6 +32,7 @@ |
33 | 33 | $opts->add( 'namespace', '0' ); |
34 | 34 | $opts->add( 'username', '' ); |
35 | 35 | $opts->add( 'feed', '' ); |
| 36 | + $opts->add( 'tagfilter', '' ); |
36 | 37 | |
37 | 38 | // Set values |
38 | 39 | $opts->fetchValuesFromRequest( $wgRequest ); |
— | — | @@ -176,6 +177,8 @@ |
177 | 178 | } |
178 | 179 | $hidden = implode( "\n", $hidden ); |
179 | 180 | |
| 181 | + list( $tagFilterLabel, $tagFilterSelector ) = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] ); |
| 182 | + |
180 | 183 | $form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) . |
181 | 184 | Xml::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . |
182 | 185 | Xml::fieldset( wfMsg( 'newpages' ) ) . |
— | — | @@ -188,6 +191,14 @@ |
189 | 192 | Xml::namespaceSelector( $namespace, 'all' ) . |
190 | 193 | "</td> |
191 | 194 | </tr>" . |
| 195 | + "<tr> |
| 196 | + <td class='mw-label'>" . |
| 197 | + $tagFilterLabel . |
| 198 | + "</td> |
| 199 | + <td class='mw-input'>" . |
| 200 | + $tagFilterSelector . |
| 201 | + "</td> |
| 202 | + </tr>" . |
192 | 203 | ($wgEnableNewpagesUserFilter ? |
193 | 204 | "<tr> |
194 | 205 | <td class='mw-label'>" . |
— | — | @@ -235,6 +246,9 @@ |
236 | 247 | */ |
237 | 248 | public function formatRow( $result ) { |
238 | 249 | global $wgLang, $wgContLang, $wgUser; |
| 250 | + |
| 251 | + $classes = array(); |
| 252 | + |
239 | 253 | $dm = $wgContLang->getDirMark(); |
240 | 254 | |
241 | 255 | $title = Title::makeTitleSafe( $result->rc_namespace, $result->rc_title ); |
— | — | @@ -247,9 +261,17 @@ |
248 | 262 | $ulink = $this->skin->userLink( $result->rc_user, $result->rc_user_text ) . ' ' . |
249 | 263 | $this->skin->userToolLinks( $result->rc_user, $result->rc_user_text ); |
250 | 264 | $comment = $this->skin->commentBlock( $result->rc_comment ); |
251 | | - $css = $this->patrollable( $result ) ? " class='not-patrolled'" : ''; |
| 265 | + |
| 266 | + if ( $this->patrollable( $result ) ) |
| 267 | + $classes[] = 'not-patrolled'; |
252 | 268 | |
253 | | - return "<li{$css}>{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment}</li>\n"; |
| 269 | + # Tags, if any. |
| 270 | + list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' ); |
| 271 | + $classes = array_merge( $classes, $newClasses ); |
| 272 | + |
| 273 | + $css = count($classes) ? ' class="'.implode( " ", $classes).'"' : ''; |
| 274 | + |
| 275 | + return "<li{$css}>{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment} {$tagDisplay}</li>\n"; |
254 | 276 | } |
255 | 277 | |
256 | 278 | /** |
— | — | @@ -378,7 +400,6 @@ |
379 | 401 | } else { |
380 | 402 | $rcIndexes = array( 'rc_timestamp' ); |
381 | 403 | } |
382 | | - $conds[] = 'page_id = rc_cur_id'; |
383 | 404 | |
384 | 405 | # $wgEnableNewpagesUserFilter - temp WMF hack |
385 | 406 | if( $wgEnableNewpagesUserFilter && $user ) { |
— | — | @@ -400,13 +421,24 @@ |
401 | 422 | $conds['page_is_redirect'] = 0; |
402 | 423 | } |
403 | 424 | |
404 | | - return array( |
| 425 | + $info = array( |
405 | 426 | 'tables' => array( 'recentchanges', 'page' ), |
406 | 427 | 'fields' => 'rc_namespace,rc_title, rc_cur_id, rc_user,rc_user_text,rc_comment, |
407 | | - rc_timestamp,rc_patrolled,rc_id,page_len as length, page_latest as rev_id', |
| 428 | + rc_timestamp,rc_patrolled,rc_id,page_len as length, page_latest as rev_id, ts_tags', |
408 | 429 | 'conds' => $conds, |
409 | | - 'options' => array( 'USE INDEX' => array('recentchanges' => $rcIndexes) ) |
| 430 | + 'options' => array( 'USE INDEX' => array('recentchanges' => $rcIndexes) ), |
| 431 | + 'join_conds' => array( |
| 432 | + 'page' => array('INNER JOIN', 'page_id=rc_cur_id'), |
| 433 | + ), |
410 | 434 | ); |
| 435 | + |
| 436 | + ## Empty array for fields, it'll be set by us anyway. |
| 437 | + $fields = array(); |
| 438 | + |
| 439 | + ## Modify query for tags |
| 440 | + ChangeTags::modifyDisplayQuery( $info['tables'], $fields, $info['conds'], $info['join_conds'], $this->opts['tagfilter'] ); |
| 441 | + |
| 442 | + return $info; |
411 | 443 | } |
412 | 444 | |
413 | 445 | function getIndexField() { |
Index: trunk/phase3/includes/specials/SpecialLog.php |
— | — | @@ -45,6 +45,7 @@ |
46 | 46 | $pattern = $wgRequest->getBool( 'pattern' ); |
47 | 47 | $y = $wgRequest->getIntOrNull( 'year' ); |
48 | 48 | $m = $wgRequest->getIntOrNull( 'month' ); |
| 49 | + $tagFilter = $wgRequest->getVal( 'tagfilter' ); |
49 | 50 | # Don't let the user get stuck with a certain date |
50 | 51 | $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev'; |
51 | 52 | if( $skip ) { |
— | — | @@ -53,12 +54,12 @@ |
54 | 55 | } |
55 | 56 | # Create a LogPager item to get the results and a LogEventsList item to format them... |
56 | 57 | $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 ); |
57 | | - $pager = new LogPager( $loglist, $type, $user, $title, $pattern, array(), $y, $m ); |
| 58 | + $pager = new LogPager( $loglist, $type, $user, $title, $pattern, array(), $y, $m, $tagFilter ); |
58 | 59 | # Set title and add header |
59 | 60 | $loglist->showHeader( $pager->getType() ); |
60 | 61 | # Show form options |
61 | 62 | $loglist->showOptions( $pager->getType(), $pager->getUser(), $pager->getPage(), $pager->getPattern(), |
62 | | - $pager->getYear(), $pager->getMonth(), $pager->getFilterParams() ); |
| 63 | + $pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $tagFilter ); |
63 | 64 | # Insert list |
64 | 65 | $logBody = $pager->getBody(); |
65 | 66 | if( $logBody ) { |
Index: trunk/phase3/includes/specials/SpecialRecentchanges.php |
— | — | @@ -35,6 +35,7 @@ |
36 | 36 | |
37 | 37 | $opts->add( 'categories', '' ); |
38 | 38 | $opts->add( 'categories_any', false ); |
| 39 | + $opts->add( 'tagfilter', '' ); |
39 | 40 | return $opts; |
40 | 41 | } |
41 | 42 | |
— | — | @@ -275,13 +276,19 @@ |
276 | 277 | $namespace = $opts['namespace']; |
277 | 278 | $invert = $opts['invert']; |
278 | 279 | |
| 280 | + $join_conds = array(); |
| 281 | + |
279 | 282 | // JOIN on watchlist for users |
280 | 283 | if( $uid ) { |
281 | 284 | $tables[] = 'watchlist'; |
282 | | - $join_conds = array( 'watchlist' => array('LEFT JOIN', |
283 | | - "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace") ); |
| 285 | + $join_conds['watchlist'] = array('LEFT JOIN', |
| 286 | + "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace"); |
284 | 287 | } |
285 | 288 | |
| 289 | + // Tag stuff. |
| 290 | + $fields = array(); // Fields are * in this case, so let the function modify an empty array to keep it happy. |
| 291 | + ChangeTags::modifyDisplayQuery( &$tables, $fields, &$conds, &$join_conds, $opts['tagfilter'] ); |
| 292 | + |
286 | 293 | wfRunHooks('SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts ) ); |
287 | 294 | |
288 | 295 | // Is there either one namespace selected or excluded? |
— | — | @@ -454,6 +461,8 @@ |
455 | 462 | $extraOpts['category'] = $this->categoryFilterForm( $opts ); |
456 | 463 | } |
457 | 464 | |
| 465 | + $extraOpts['tagfilter'] = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] ); |
| 466 | + |
458 | 467 | wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); |
459 | 468 | return $extraOpts; |
460 | 469 | } |
Index: trunk/phase3/includes/specials/SpecialContributions.php |
— | — | @@ -63,6 +63,8 @@ |
64 | 64 | } else { |
65 | 65 | $this->opts['namespace'] = ''; |
66 | 66 | } |
| 67 | + |
| 68 | + $this->opts['tagfilter'] = $wgRequest->getVal( 'tagfilter' ); |
67 | 69 | |
68 | 70 | // Allows reverts to have the bot flag in recent changes. It is just here to |
69 | 71 | // be passed in the form at the top of the page |
— | — | @@ -256,6 +258,7 @@ |
257 | 259 | Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . |
258 | 260 | Xml::namespaceSelector( $this->opts['namespace'], '' ) . |
259 | 261 | '</span>' . |
| 262 | + Xml::tags( 'p', null, implode( ' ', ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] ) ) ) . |
260 | 263 | Xml::openElement( 'p' ) . |
261 | 264 | '<span style="white-space: nowrap">' . |
262 | 265 | Xml::label( wfMsg( 'year' ), 'year' ) . ' '. |
— | — | @@ -307,7 +310,7 @@ |
308 | 311 | $target = $this->opts['target'] == 'newbies' ? 'newbies' : $nt->getText(); |
309 | 312 | |
310 | 313 | $pager = new ContribsPager( $target, $this->opts['namespace'], |
311 | | - $this->opts['year'], $this->opts['month'] ); |
| 314 | + $this->opts['year'], $this->opts['month'], $this->opts['tagfilter'] ); |
312 | 315 | |
313 | 316 | $pager->mLimit = min( $this->opts['limit'], $wgFeedLimit ); |
314 | 317 | |
— | — | @@ -371,13 +374,14 @@ |
372 | 375 | var $messages, $target; |
373 | 376 | var $namespace = '', $mDb; |
374 | 377 | |
375 | | - function __construct( $target, $namespace = false, $year = false, $month = false ) { |
| 378 | + function __construct( $target, $namespace = false, $year = false, $month = false, $tagFilter = false ) { |
376 | 379 | parent::__construct(); |
377 | 380 | foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist newpageletter minoreditletter' ) as $msg ) { |
378 | 381 | $this->messages[$msg] = wfMsgExt( $msg, array( 'escape') ); |
379 | 382 | } |
380 | 383 | $this->target = $target; |
381 | 384 | $this->namespace = $namespace; |
| 385 | + $this->tagFilter = $tagFilter; |
382 | 386 | |
383 | 387 | $this->getDateCond( $year, $month ); |
384 | 388 | |
— | — | @@ -392,7 +396,10 @@ |
393 | 397 | |
394 | 398 | function getQueryInfo() { |
395 | 399 | list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond(); |
396 | | - $conds = array_merge( array('page_id=rev_page'), $userCond, $this->getNamespaceCond() ); |
| 400 | + |
| 401 | + $conds = array_merge( $userCond, $this->getNamespaceCond() ); |
| 402 | + $join_cond['page'] = array( 'INNER JOIN', 'page_id=rev_page' ); |
| 403 | + |
397 | 404 | $queryInfo = array( |
398 | 405 | 'tables' => $tables, |
399 | 406 | 'fields' => array( |
— | — | @@ -404,6 +411,9 @@ |
405 | 412 | 'options' => array( 'USE INDEX' => array('revision' => $index) ), |
406 | 413 | 'join_conds' => $join_cond |
407 | 414 | ); |
| 415 | + |
| 416 | + ChangeTags::modifyDisplayQuery( $queryInfo['tables'], $queryInfo['fields'], $queryInfo['conds'], $queryInfo['join_conds'], $this->tagFilter ); |
| 417 | + |
408 | 418 | wfRunHooks( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) ); |
409 | 419 | return $queryInfo; |
410 | 420 | } |
— | — | @@ -463,6 +473,7 @@ |
464 | 474 | |
465 | 475 | $sk = $this->getSkin(); |
466 | 476 | $rev = new Revision( $row ); |
| 477 | + $classes = array(); |
467 | 478 | |
468 | 479 | $page = Title::newFromRow( $row ); |
469 | 480 | $page->resetArticleId( $row->rev_page ); // use process cache |
— | — | @@ -521,10 +532,17 @@ |
522 | 533 | if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { |
523 | 534 | $ret .= ' ' . wfMsgHtml( 'deletedrev' ); |
524 | 535 | } |
| 536 | + |
| 537 | + # Tags, if any. |
| 538 | + list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'contributions' ); |
| 539 | + $classes = array_merge( $classes, $newClasses ); |
| 540 | + $ret .= " $tagSummary"; |
| 541 | + |
525 | 542 | // Let extensions add data |
526 | 543 | wfRunHooks( 'ContributionsLineEnding', array( &$this, &$ret, $row ) ); |
527 | | - |
528 | | - $ret = "<li>$ret</li>\n"; |
| 544 | + |
| 545 | + $classes = implode( ' ', $classes ); |
| 546 | + $ret = "<li class=\"$classes\">$ret</li>\n"; |
529 | 547 | wfProfileOut( __METHOD__ ); |
530 | 548 | return $ret; |
531 | 549 | } |
Index: trunk/phase3/includes/specials/SpecialTags.php |
— | — | @@ -0,0 +1,75 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +if (!defined('MEDIAWIKI')) |
| 5 | + die; |
| 6 | + |
| 7 | +class SpecialTags extends SpecialPage { |
| 8 | + |
| 9 | + function __construct() { |
| 10 | + parent::__construct( 'Tags' ); |
| 11 | + } |
| 12 | + |
| 13 | + function execute() { |
| 14 | + global $wgOut, $wgUser, $wgMessageCache; |
| 15 | + |
| 16 | + $wgMessageCache->loadAllMessages(); |
| 17 | + |
| 18 | + $sk = $wgUser->getSkin(); |
| 19 | + $wgOut->setPageTitle( wfMsg( 'tags-title' ) ); |
| 20 | + $wgOut->addWikiMsg( 'tags-intro' ); |
| 21 | + |
| 22 | + // Write the headers |
| 23 | + $html = ''; |
| 24 | + $html = Xml::tags( 'tr', null, Xml::tags( 'th', null, wfMsgExt( 'tags-tag', 'parseinline' ) ) . |
| 25 | + Xml::tags( 'th', null, wfMsgExt( 'tags-display-header', 'parseinline' ) ) . |
| 26 | + Xml::tags( 'th', null, wfMsgExt( 'tags-description-header', 'parseinline' ) ) . |
| 27 | + Xml::tags( 'th', null, wfMsgExt( 'tags-hitcount-header', 'parseinline' ) ) |
| 28 | + ); |
| 29 | + $dbr = wfGetDB( DB_SLAVE ); |
| 30 | + $res = $dbr->select( 'change_tag', array( 'ct_tag', 'count(*) as hitcount' ), array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) ); |
| 31 | + |
| 32 | + while ( $row = $res->fetchObject() ) { |
| 33 | + $html .= $this->doTagRow( $row->ct_tag, $row->hitcount ); |
| 34 | + } |
| 35 | + |
| 36 | + foreach( ChangeTags::listDefinedTags() as $tag ) { |
| 37 | + $html .= $this->doTagRow( $tag, 0 ); |
| 38 | + } |
| 39 | + |
| 40 | + $html = "<table style='width: 80%'><tbody>$html</tbody></table>"; |
| 41 | + |
| 42 | + $wgOut->addHTML( $html ); |
| 43 | + } |
| 44 | + |
| 45 | + function doTagRow( $tag, $hitcount ) { |
| 46 | + static $sk=null, $doneTags=array(); |
| 47 | + if (!$sk) { |
| 48 | + global $wgUser; |
| 49 | + $sk = $wgUser->getSkin(); |
| 50 | + } |
| 51 | + |
| 52 | + if ( in_array( $tag, $doneTags ) ) { |
| 53 | + return ''; |
| 54 | + } |
| 55 | + |
| 56 | + $newRow = ''; |
| 57 | + $newRow .= Xml::tags( 'td', null, Xml::element( 'tt', null, $tag ) ); |
| 58 | + |
| 59 | + $disp = ChangeTags::tagDescription( $tag ); |
| 60 | + $disp .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ), wfMsg( 'tag-edit' ) ) . ')'; |
| 61 | + $newRow .= Xml::tags( 'td', null, $disp ); |
| 62 | + |
| 63 | + $desc = wfMsgExt( "tag-$tag-description", 'parseinline' ); |
| 64 | + $desc = wfEmptyMsg( "tag-$tag-description", $desc ) ? '' : $desc; |
| 65 | + $desc .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), wfMsg( 'tag-edit' ) ) . ')'; |
| 66 | + $newRow .= Xml::tags( 'td', null, $desc ); |
| 67 | + |
| 68 | + $hitcount = wfMsg( 'tags-hitcount', $hitcount ); |
| 69 | + $hitcount = $sk->link( SpecialPage::getTitleFor( 'RecentChanges' ), $hitcount, array(), array( 'tagfilter' => $tag ) ); |
| 70 | + $newRow .= Xml::tags( 'td', null, $hitcount ); |
| 71 | + |
| 72 | + $doneTags[] = $tag; |
| 73 | + |
| 74 | + return Xml::tags( 'tr', null, $newRow ) . "\n"; |
| 75 | + } |
| 76 | +} |
\ No newline at end of file |
Index: trunk/phase3/includes/specials/SpecialWatchlist.php |
— | — | @@ -218,7 +218,8 @@ |
219 | 219 | $tables[] = 'page'; |
220 | 220 | $join_conds['page'] = array('LEFT JOIN','rc_cur_id=page_id'); |
221 | 221 | } |
222 | | - |
| 222 | + |
| 223 | + ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, '' ); |
223 | 224 | wfRunHooks('SpecialWatchlistQuery', array(&$conds,&$tables,&$join_conds,&$fields) ); |
224 | 225 | |
225 | 226 | $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds ); |
Index: trunk/phase3/includes/PageHistory.php |
— | — | @@ -112,6 +112,7 @@ |
113 | 113 | */ |
114 | 114 | $year = $wgRequest->getInt( 'year' ); |
115 | 115 | $month = $wgRequest->getInt( 'month' ); |
| 116 | + $tagFilter = $wgRequest->getVal( 'tagfilter' ); |
116 | 117 | |
117 | 118 | $action = htmlspecialchars( $wgScript ); |
118 | 119 | $wgOut->addHTML( |
— | — | @@ -120,6 +121,7 @@ |
121 | 122 | Xml::hidden( 'title', $this->mTitle->getPrefixedDBKey() ) . "\n" . |
122 | 123 | Xml::hidden( 'action', 'history' ) . "\n" . |
123 | 124 | $this->getDateMenu( $year, $month ) . ' ' . |
| 125 | + implode( ' ', ChangeTags::buildTagFilterSelector( $tagFilter ) ) . ' ' . |
124 | 126 | Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . |
125 | 127 | '</fieldset></form>' |
126 | 128 | ); |
— | — | @@ -129,7 +131,7 @@ |
130 | 132 | /** |
131 | 133 | * Do the list |
132 | 134 | */ |
133 | | - $pager = new PageHistoryPager( $this, $year, $month ); |
| 135 | + $pager = new PageHistoryPager( $this, $year, $month, $tagFilter ); |
134 | 136 | $this->linesonpage = $pager->getNumRows(); |
135 | 137 | $wgOut->addHTML( |
136 | 138 | $pager->getNavigationBar() . |
— | — | @@ -287,6 +289,7 @@ |
288 | 290 | $lastlink = $this->lastLink( $rev, $next, $counter ); |
289 | 291 | $arbitrary = $this->diffButtons( $rev, $firstInList, $counter ); |
290 | 292 | $link = $this->revLink( $rev ); |
| 293 | + $classes = array(); |
291 | 294 | |
292 | 295 | $s = "($curlink) ($lastlink) $arbitrary"; |
293 | 296 | |
— | — | @@ -355,9 +358,16 @@ |
356 | 359 | $s .= ' (' . implode( ' | ', $tools ) . ')'; |
357 | 360 | } |
358 | 361 | |
| 362 | + # Tags |
| 363 | + list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'history' ); |
| 364 | + $classes = array_merge( $classes, $newClasses ); |
| 365 | + $s .= " $tagSummary"; |
| 366 | + |
359 | 367 | wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row , &$s ) ); |
360 | 368 | |
361 | | - return "<li>$s</li>\n"; |
| 369 | + $classes = implode( ' ', $classes ); |
| 370 | + |
| 371 | + return "<li class=\"$classes\">$s</li>\n"; |
362 | 372 | } |
363 | 373 | |
364 | 374 | /** |
— | — | @@ -589,20 +599,23 @@ |
590 | 600 | class PageHistoryPager extends ReverseChronologicalPager { |
591 | 601 | public $mLastRow = false, $mPageHistory, $mTitle; |
592 | 602 | |
593 | | - function __construct( $pageHistory, $year='', $month='' ) { |
| 603 | + function __construct( $pageHistory, $year='', $month='', $tagFilter = '' ) { |
594 | 604 | parent::__construct(); |
595 | 605 | $this->mPageHistory = $pageHistory; |
596 | 606 | $this->mTitle =& $this->mPageHistory->mTitle; |
| 607 | + $this->tagFilter = $tagFilter; |
597 | 608 | $this->getDateCond( $year, $month ); |
598 | 609 | } |
599 | 610 | |
600 | 611 | function getQueryInfo() { |
601 | 612 | $queryInfo = array( |
602 | 613 | 'tables' => array('revision'), |
603 | | - 'fields' => Revision::selectFields(), |
| 614 | + 'fields' => array_merge( Revision::selectFields(), array('ts_tags') ), |
604 | 615 | 'conds' => array('rev_page' => $this->mPageHistory->mTitle->getArticleID() ), |
605 | | - 'options' => array( 'USE INDEX' => array('revision' => 'page_timestamp') ) |
| 616 | + 'options' => array( 'USE INDEX' => array('revision' => 'page_timestamp') ), |
| 617 | + 'join_conds' => array( 'tag_summary' => array( 'LEFT JOIN', 'ts_rev_id=rev_id' ) ), |
606 | 618 | ); |
| 619 | + ChangeTags::modifyDisplayQuery( $queryInfo['tables'], $queryInfo['fields'], $queryInfo['conds'], $queryInfo['join_conds'], $this->tagFilter ); |
607 | 620 | wfRunHooks( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) ); |
608 | 621 | return $queryInfo; |
609 | 622 | } |
Index: trunk/phase3/includes/SpecialPage.php |
— | — | @@ -159,6 +159,7 @@ |
160 | 160 | 'Randomredirect' => 'SpecialRandomredirect', |
161 | 161 | 'Withoutinterwiki' => array( 'SpecialPage', 'Withoutinterwiki' ), |
162 | 162 | 'Filepath' => array( 'SpecialPage', 'Filepath' ), |
| 163 | + 'Tags' => 'SpecialTags', |
163 | 164 | |
164 | 165 | 'Mypage' => array( 'SpecialMypage' ), |
165 | 166 | 'Mytalk' => array( 'SpecialMytalk' ), |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -3801,4 +3801,15 @@ |
3802 | 3802 | |
3803 | 3803 | #Put all regex fragments above this line. Leave this line exactly as it is</pre>', |
3804 | 3804 | |
| 3805 | +## Taggng-related stuff |
| 3806 | +'tag-filter' => '[[Special:Tags|Tag]] filter:', |
| 3807 | +'tag-filter-submit' => 'Filter', |
| 3808 | +'tags-title' => 'Tags', |
| 3809 | +'tags-intro' => 'This page lists the tags that the software may mark an edit with, and their meaning.', |
| 3810 | +'tags-tag' => 'Internal tag name', |
| 3811 | +'tags-display-header' => 'Appearance on change lists', |
| 3812 | +'tags-description-header' => 'Full description of meaning', |
| 3813 | +'tags-hitcount-header' => 'Tagged edits', |
| 3814 | +'tags-edit' => 'edit', |
| 3815 | +'tags-hitcount' => '$1 changes', |
3805 | 3816 | ); |
Index: trunk/extensions/TorBlock/TorBlock.i18n.php |
— | — | @@ -15,6 +15,8 @@ |
16 | 16 | 'torblock-blocked' => 'Your IP address, <tt>$1</tt>, has been automatically identified as a tor exit node. |
17 | 17 | Editing through tor is blocked to prevent abuse.', |
18 | 18 | 'right-torunblocked' => 'Bypass automatic blocks of tor exit nodes', |
| 19 | + 'tag-tor-description' => 'If this tag is set, an edit was made from a Tor exit node.', |
| 20 | + 'tag-tor' => 'Made through tor', |
19 | 21 | ); |
20 | 22 | |
21 | 23 | /** Message documentation (Message documentation) |
Index: trunk/extensions/TorBlock/TorBlock.php |
— | — | @@ -33,6 +33,8 @@ |
34 | 34 | $wgHooks['GetAutoPromoteGroups'][] = 'TorBlock::onGetAutoPromoteGroups'; |
35 | 35 | $wgHooks['GetBlockedStatus'][] = 'TorBlock::onGetBlockedStatus'; |
36 | 36 | $wgHooks['AutopromoteCondition'][] = 'TorBlock::onAutopromoteCondition'; |
| 37 | +$wgHooks['RecentChange_save'][] = 'TorBlock::onRecentChangeSave'; |
| 38 | +$wgHooks['ListDefinedTags'][] = 'TorBlock::onListDefinedTags'; |
37 | 39 | |
38 | 40 | // Define new autopromote condition |
39 | 41 | define('APCOND_TOR', 'tor'); // Numbers won't work, we'll get collisions |
Index: trunk/extensions/TorBlock/TorBlock.class.php |
— | — | @@ -168,4 +168,16 @@ |
169 | 169 | |
170 | 170 | return true; |
171 | 171 | } |
| 172 | + |
| 173 | + public static function onRecentChangeSave( $recentChange ) { |
| 174 | + if ( class_exists('ChangeTags') && self::isExitNode() ) { |
| 175 | + ChangeTags::addTags( 'tor', $recentChange->mAttribs['rc_id'], $recentChange->mAttribs['rc_this_oldid'], $recentChange->mAttribs['rc_logid'] ); |
| 176 | + } |
| 177 | + return true; |
| 178 | + } |
| 179 | + |
| 180 | + public static function onListDefinedTags( &$emptyTags ) { |
| 181 | + $emptyTags[] = 'tor'; |
| 182 | + return true; |
| 183 | + } |
172 | 184 | } |
Index: trunk/extensions/AbuseFilter/AbuseFilter.php |
— | — | @@ -52,8 +52,8 @@ |
53 | 53 | $wgHooks['ArticleDelete'][] = 'AbuseFilterHooks::onArticleDelete'; |
54 | 54 | $wgHooks['LoadExtensionSchemaUpdates'][] = 'AbuseFilterHooks::onSchemaUpdate'; |
55 | 55 | $wgHooks['AbortDeleteQueueNominate'][] = 'AbuseFilterHooks::onAbortDeleteQueueNominate'; |
56 | | -// $wgHooks['RecentChange_save'][] = 'AbuseFilterHooks::onRecentChangeSave'; |
57 | | -// $wgHooks['ListDefinedTags'][] = 'AbuseFilterHooks::onListDefinedTags'; |
| 56 | +$wgHooks['RecentChange_save'][] = 'AbuseFilterHooks::onRecentChangeSave'; |
| 57 | +$wgHooks['ListDefinedTags'][] = 'AbuseFilterHooks::onListDefinedTags'; |
58 | 58 | |
59 | 59 | $wgAvailableRights[] = 'abusefilter-modify'; |
60 | 60 | $wgAvailableRights[] = 'abusefilter-log-detail'; |
— | — | @@ -63,7 +63,7 @@ |
64 | 64 | $wgAvailableRights[] = 'abusefilter-modify-restricted'; |
65 | 65 | $wgAvailableRights[] = 'abusefilter-revert'; |
66 | 66 | |
67 | | -$wgAbuseFilterAvailableActions = array( 'flag', 'throttle', 'warn', 'disallow', 'blockautopromote', 'block', 'degroup', /* Disabled because it's ridiculously excessive 'rangeblock'*/ /*, 'tag' Disabled for now to avoid trunk changes. */ ); |
| 67 | +$wgAbuseFilterAvailableActions = array( 'flag', 'throttle', 'warn', 'disallow', 'blockautopromote', 'block', 'degroup', 'tag' ); |
68 | 68 | |
69 | 69 | $wgAbuseFilterConditionLimit = 1000; |
70 | 70 | |
Index: trunk/extensions/AbuseFilter/Views/AbuseFilterViewEdit.php |
— | — | @@ -328,21 +328,20 @@ |
329 | 329 | $warnFields['abusefilter-edit-warn-message'] = Xml::input( 'wpFilterWarnMessage', 45, $warnMsg ); |
330 | 330 | $output .= Xml::tags( 'p', null, Xml::buildForm( $warnFields ) ); |
331 | 331 | return $output; |
332 | | - // Commented out to avoid trunk changes for now. |
333 | | -// case 'tag': |
334 | | -// if ($set) { |
335 | | -// $tags = $parameters; |
336 | | -// } else { |
337 | | -// $tags = array(); |
338 | | -// } |
339 | | -// $output = ''; |
340 | | -// |
341 | | -// $checkbox = Xml::checkLabel( wfMsg('abusefilter-edit-action-tag'), 'wpFilterActionTag', 'wpFilterActionTag', $set ); |
342 | | -// $output .= Xml::tags( 'p', null, $checkbox ); |
343 | | -// |
344 | | -// $tagFields['abusefilter-edit-tag-tag'] = Xml::textarea( 'wpFilterTags', implode( "\n", $tags ) ); |
345 | | -// $output .= Xml::tags( 'p', null, Xml::buildForm( $tagFields ) ); |
346 | | -// return $output; |
| 332 | + case 'tag': |
| 333 | + if ($set) { |
| 334 | + $tags = $parameters; |
| 335 | + } else { |
| 336 | + $tags = array(); |
| 337 | + } |
| 338 | + $output = ''; |
| 339 | + |
| 340 | + $checkbox = Xml::checkLabel( wfMsg('abusefilter-edit-action-tag'), 'wpFilterActionTag', 'wpFilterActionTag', $set ); |
| 341 | + $output .= Xml::tags( 'p', null, $checkbox ); |
| 342 | + |
| 343 | + $tagFields['abusefilter-edit-tag-tag'] = Xml::textarea( 'wpFilterTags', implode( "\n", $tags ) ); |
| 344 | + $output .= Xml::tags( 'p', null, Xml::buildForm( $tagFields ) ); |
| 345 | + return $output; |
347 | 346 | default: |
348 | 347 | $message = 'abusefilter-edit-action-'.$action; |
349 | 348 | $form_field = 'wpFilterAction' . ucfirst($action); |
Index: trunk/extensions/AbuseFilter/AbuseFilter.class.php |
— | — | @@ -439,16 +439,18 @@ |
440 | 440 | // Do nothing. Here for completeness. |
441 | 441 | break; |
442 | 442 | |
443 | | -// case 'tag': |
444 | | -// // Mark with a tag on recentchanges. |
445 | | -// global $wgUser; |
446 | | -// |
447 | | -// $actionID = implode( '-', array( |
448 | | -// $title->getPrefixedText(), $wgUser->getName(), $vars['ACTION'] |
449 | | -// ) ); |
450 | | -// |
451 | | -// AbuseFilter::$tagsToSet[$actionID] = $parameters; |
452 | | -// break; |
| 443 | + case 'tag': |
| 444 | + // Mark with a tag on recentchanges. |
| 445 | + global $wgUser; |
| 446 | + |
| 447 | + $actionID = implode( '-', array( |
| 448 | + $title->getPrefixedText(), $wgUser->getName(), $vars['ACTION'] |
| 449 | + ) ); |
| 450 | + |
| 451 | + AbuseFilter::$tagsToSet[$actionID] = $parameters; |
| 452 | + break; |
| 453 | + default: |
| 454 | + throw new MWException( "Unrecognised action $action" ); |
453 | 455 | } |
454 | 456 | |
455 | 457 | return $display; |
Index: trunk/extensions/AbuseFilter/AbuseFilter.hooks.php |
— | — | @@ -172,31 +172,30 @@ |
173 | 173 | return $filter_result == '' || $filter_result === true; |
174 | 174 | } |
175 | 175 | |
176 | | -// Commented out to avoid trunk changes for now. |
177 | | -// public static function onRecentChangeSave( $recentChange ) { |
178 | | -// $title = Title::makeTitle( $recentChange->mAttribs['rc_namespace'], $recentChange->mAttribs['rc_title'] ); |
179 | | -// $action = $recentChange->mAttribs['rc_log_type'] ? $recentChange->mAttribs['rc_log_type'] : 'edit'; |
180 | | -// $actionID = implode( '-', array( |
181 | | -// $title->getPrefixedText(), $recentChange->mAttribs['rc_user_text'], $action |
182 | | -// ) ); |
183 | | -// |
184 | | -// if ( !empty( AbuseFilter::$tagsToSet[$actionID] ) && count( $tags = AbuseFilter::$tagsToSet[$actionID]) ) { |
185 | | -// ChangeTags::addTags( $tags, $recentChange->mAttribs['rc_id'], $recentChange->mAttribs['rc_this_oldid'], $recentChange->mAttribs['rc_logid'] ); |
186 | | -// } |
187 | | -// |
188 | | -// return true; |
189 | | -// } |
| 176 | + public static function onRecentChangeSave( $recentChange ) { |
| 177 | + $title = Title::makeTitle( $recentChange->mAttribs['rc_namespace'], $recentChange->mAttribs['rc_title'] ); |
| 178 | + $action = $recentChange->mAttribs['rc_log_type'] ? $recentChange->mAttribs['rc_log_type'] : 'edit'; |
| 179 | + $actionID = implode( '-', array( |
| 180 | + $title->getPrefixedText(), $recentChange->mAttribs['rc_user_text'], $action |
| 181 | + ) ); |
190 | 182 | |
191 | | -// public static function onListDefinedTags( &$emptyTags ) { |
192 | | -// ## This is a pretty awful hack. |
193 | | -// $dbr = wfGetDB( DB_SLAVE ); |
194 | | -// |
195 | | -// $res = $dbr->select( 'abuse_filter_action', 'afa_parameters', array( 'afa_consequence' => 'tag' ), __METHOD__ ); |
196 | | -// |
197 | | -// while( $row = $res->fetchObject() ) { |
198 | | -// $emptyTags = array_filter( array_merge( explode( "\n", $row->afa_parameters ), $emptyTags ) ); |
199 | | -// } |
200 | | -// |
201 | | -// return true; |
202 | | -// } |
| 183 | + if ( !empty( AbuseFilter::$tagsToSet[$actionID] ) && count( $tags = AbuseFilter::$tagsToSet[$actionID]) ) { |
| 184 | + ChangeTags::addTags( $tags, $recentChange->mAttribs['rc_id'], $recentChange->mAttribs['rc_this_oldid'], $recentChange->mAttribs['rc_logid'] ); |
| 185 | + } |
| 186 | + |
| 187 | + return true; |
| 188 | + } |
| 189 | + |
| 190 | + public static function onListDefinedTags( &$emptyTags ) { |
| 191 | + ## This is a pretty awful hack. |
| 192 | + $dbr = wfGetDB( DB_SLAVE ); |
| 193 | + |
| 194 | + $res = $dbr->select( 'abuse_filter_action', 'afa_parameters', array( 'afa_consequence' => 'tag' ), __METHOD__ ); |
| 195 | + |
| 196 | + while( $row = $res->fetchObject() ) { |
| 197 | + $emptyTags = array_filter( array_merge( explode( "\n", $row->afa_parameters ), $emptyTags ) ); |
| 198 | + } |
| 199 | + |
| 200 | + return true; |
| 201 | + } |
203 | 202 | } |