r108076 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r108075‎ | r108076 | r108077 >
Date:19:51, 4 January 2012
Author:ashley
Status:resolved (Comments)
Tags:
Comment:
VoteNY: actually make this work under 1.18+, not just in ResourceLoader's debug mode.
Changelist:
*removed backwards-compatibility code
*used HTML5 data attributes to store various information that we access in the JS file
*incremented the extension's version number
*removed ancient vote key stuff (the "vote key" was being passed to the AJAX function but it wasn't used in any of the functions)
*updated Linker class, that class is now static
*killd $wgTitle

Some things are now a bit slower, i.e. if you click on the green voting box, then click again when the unvote link appears, it doesn't seem to change back to "vote" again...but at least it should work now.
Modified paths:
  • /trunk/extensions/VoteNY/SpecialTopRatings.php (modified) (history)
  • /trunk/extensions/VoteNY/Vote.js (modified) (history)
  • /trunk/extensions/VoteNY/Vote.php (modified) (history)
  • /trunk/extensions/VoteNY/VoteClass.php (modified) (history)
  • /trunk/extensions/VoteNY/VoteHooks.php (modified) (history)
  • /trunk/extensions/VoteNY/Vote_AjaxFunctions.php (modified) (history)

Diff [purge]

Index: trunk/extensions/VoteNY/Vote_AjaxFunctions.php
@@ -4,7 +4,7 @@
55 */
66 $wgAjaxExportList[] = 'wfVoteClick';
77
8 -function wfVoteClick( $voteValue, $pageId, $mk ) {
 8+function wfVoteClick( $voteValue, $pageId ) {
99 global $wgUser;
1010
1111 if ( !$wgUser->isAllowed( 'vote' ) ) {
@@ -22,7 +22,7 @@
2323 }
2424
2525 $wgAjaxExportList[] = 'wfVoteDelete';
26 -function wfVoteDelete( $pageId, $mk ) {
 26+function wfVoteDelete( $pageId ) {
2727 global $wgUser;
2828
2929 if ( !$wgUser->isAllowed( 'vote' ) ) {
@@ -40,7 +40,7 @@
4141 }
4242
4343 $wgAjaxExportList[] = 'wfVoteStars';
44 -function wfVoteStars( $voteValue, $pageId, $mk ) {
 44+function wfVoteStars( $voteValue, $pageId ) {
4545 global $wgUser;
4646
4747 if ( !$wgUser->isAllowed( 'vote' ) ) {
@@ -57,7 +57,7 @@
5858 }
5959
6060 $wgAjaxExportList[] = 'wfVoteStarsMulti';
61 -function wfVoteStarsMulti( $voteValue, $pageId, $mk ) {
 61+function wfVoteStarsMulti( $voteValue, $pageId ) {
6262 global $wgUser;
6363
6464 if ( !$wgUser->isAllowed( 'vote' ) ) {
Index: trunk/extensions/VoteNY/SpecialTopRatings.php
@@ -9,7 +9,7 @@
1010 *
1111 * @file
1212 * @ingroup Extensions
13 - * @date 21 August 2011
 13+ * @date 11 December 2011
1414 * @license To the extent that it is possible, this code is in the public domain
1515 */
1616 class SpecialTopRatings extends IncludableSpecialPage {
@@ -27,7 +27,7 @@
2828 * @param $par Mixed: parameter passed to the special page or null
2929 */
3030 public function execute( $par ) {
31 - global $wgOut, $wgScriptPath, $wgUser;
 31+ global $wgOut, $wgScriptPath;
3232
3333 // Set the page title, robot policies, etc.
3434 $this->setHeaders();
@@ -49,13 +49,12 @@
5050 $limit = 50;
5151 }
5252
53 - // Add JS -- needed so that users can vote on this page and so that
54 - // their browsers' consoles won't be filled with JS errors ;-)
55 - $wgOut->addScriptFile( $wgScriptPath . '/extensions/VoteNY/Vote.js' );
 53+ // Add JS (and CSS) -- needed so that users can vote on this page and
 54+ // so that their browsers' consoles won't be filled with JS errors ;-)
 55+ $wgOut->addModules( 'ext.voteNY' );
5656
5757 $ratings = array();
5858 $output = '';
59 - $sk = $wgUser->getSkin();
6059
6160 $dbr = wfGetDB( DB_SLAVE );
6261 $tables = $where = $joinConds = array();
@@ -124,7 +123,7 @@
125124
126125 $vote = new VoteStars( $pageId );
127126 $output .= '<div class="user-list-rating">' .
128 - $sk->link(
 127+ Linker::link(
129128 $titleObj,
130129 $titleObj->getPrefixedText() // prefixed, so that the namespace shows!
131130 ) . wfMsg( 'word-separator' ) . // i18n overkill? ya betcha...
Index: trunk/extensions/VoteNY/Vote.php
@@ -4,7 +4,7 @@
55 *
66 * @file
77 * @ingroup Extensions
8 - * @version 2.3.3
 8+ * @version 2.4
99 * @author Aaron Wright <aaron.wright@gmail.com>
1010 * @author David Pean <david.pean@gmail.com>
1111 * @author Jack Phoenix <jack@countervandalism.net>
@@ -23,7 +23,7 @@
2424 // Extension credits that show up on Special:Version
2525 $wgExtensionCredits['parserhook'][] = array(
2626 'name' => 'Vote',
27 - 'version' => '2.3.3',
 27+ 'version' => '2.4',
2828 'author' => array( 'Aaron Wright', 'David Pean', 'Jack Phoenix' ),
2929 'description' => 'JavaScript-based voting with the <tt>&lt;vote&gt;</tt> tag',
3030 'url' => 'https://www.mediawiki.org/wiki/Extension:VoteNY'
@@ -55,7 +55,6 @@
5656 $wgAutoloadClasses['VoteHooks'] = $dir . 'VoteHooks.php';
5757
5858 $wgHooks['ParserFirstCallInit'][] = 'VoteHooks::registerParserHook';
59 -$wgHooks['MakeGlobalVariablesScript'][] = 'VoteHooks::addJSGlobalVariables';
6059 $wgHooks['RenameUserSQL'][] = 'VoteHooks::onUserRename';
6160 // Translations for {{NUMBEROFVOTES}}
6261 //$wgExtensionMessagesFiles['NumberOfVotes'] = $dir . 'Vote.i18n.magic.php';
Index: trunk/extensions/VoteNY/VoteHooks.php
@@ -27,7 +27,7 @@
2828 * @return String: HTML
2929 */
3030 public static function renderVote( $input, $args, $parser ) {
31 - global $wgOut, $wgTitle, $wgScriptPath;
 31+ global $wgOut;
3232
3333 wfProfileIn( __METHOD__ );
3434
@@ -39,12 +39,7 @@
4040 // Add CSS & JS
4141 // In order for us to do this *here* instead of having to do this in
4242 // registerParserHook(), we must've disabled parser cache
43 - if ( defined( 'MW_SUPPORTS_RESOURCE_MODULES' ) ) {
44 - $wgOut->addModules( 'ext.voteNY' );
45 - } else {
46 - $wgOut->addScriptFile( $wgScriptPath . '/extensions/VoteNY/Vote.js' );
47 - $wgOut->addExtensionStyle( $wgScriptPath . '/extensions/VoteNY/Vote.css' );
48 - }
 43+ $wgOut->addModules( 'ext.voteNY' );
4944
5045 // Define variable - 0 means that we'll get that green voting box by default
5146 $type = 0;
@@ -56,7 +51,7 @@
5752 $type = intval( $args['type'] );
5853 }
5954
60 - $articleID = $wgTitle->getArticleID();
 55+ $articleID = $wgOut->getTitle()->getArticleID();
6156 switch( $type ) {
6257 case 0:
6358 $vote = new Vote( $articleID );
@@ -76,18 +71,6 @@
7772 }
7873
7974 /**
80 - * Adds required JS variables to the HTML output.
81 - *
82 - * @param $vars Array: array of pre-existing JS globals
83 - * @return Boolean: true
84 - */
85 - public static function addJSGlobalVariables( $vars ) {
86 - $vars['_VOTE_LINK'] = wfMsg( 'vote-link' );
87 - $vars['_UNVOTE_LINK'] = wfMsg( 'vote-unvote-link' );
88 - return true;
89 - }
90 -
91 - /**
9275 * For the Renameuser extension.
9376 *
9477 * @param $renameUserSQL
@@ -172,18 +155,13 @@
173156 * Creates the necessary database table when the user runs
174157 * maintenance/update.php.
175158 *
176 - * @param $updater Object: instance of DatabaseUpdater
 159+ * @param $updater DatabaseUpdater
177160 * @return Boolean: true
178161 */
179 - public static function addTable( $updater = null ) {
 162+ public static function addTable( $updater ) {
180163 $dir = dirname( __FILE__ );
181164 $file = "$dir/vote.sql";
182 - if ( $updater === null ) {
183 - global $wgExtNewTables;
184 - $wgExtNewTables[] = array( 'Vote', $file );
185 - } else {
186 - $updater->addExtensionUpdate( array( 'addTable', 'Vote', $file, true ) );
187 - }
 165+ $updater->addExtensionUpdate( array( 'addTable', 'Vote', $file, true ) );
188166 return true;
189167 }
190168 }
\ No newline at end of file
Index: trunk/extensions/VoteNY/Vote.js
@@ -4,11 +4,11 @@
55 * @file
66 * @ingroup Extensions
77 * @author Jack Phoenix <jack@countervandalism.net>
8 - * @date 19 June 2011
 8+ * @date 4 January 2012
99 */
1010 var VoteNY = {
1111 MaxRating: 5,
12 - clearRatingTimer: '',
 12+ clearRatingTimer: null,
1313 voted_new: [],
1414 id: 0,
1515 last_id: 0,
@@ -16,57 +16,46 @@
1717
1818 /**
1919 * Called when voting through the green square voting box
 20+ *
2021 * @param TheVote
2122 * @param PageID Integer: internal ID number of the current article
22 - * @param mk Mixed: random token
2323 */
24 - clickVote: function( TheVote, PageID, mk ) {
 24+ clickVote: function( TheVote, PageID ) {
2525 sajax_request_type = 'POST';
26 - sajax_do_call( 'wfVoteClick', [ TheVote, PageID, mk ], function( request ) {
 26+ sajax_do_call( 'wfVoteClick', [ TheVote, PageID ], function( request ) {
2727 document.getElementById( 'votebox' ).style.cursor = 'default';
2828 document.getElementById( 'PollVotes' ).innerHTML = request.responseText;
29 - var unvoteMessage;
30 - if ( typeof( mediaWiki ) == 'undefined' ) {
31 - unvoteMessage = _UNVOTE_LINK;
32 - } else {
33 - unvoteMessage = mediaWiki.msg( 'vote-unvote-link' );
34 - }
3529 document.getElementById( 'Answer' ).innerHTML =
36 - "<a href=javascript:VoteNY.unVote(" + PageID + ",'" + mk +
37 - "')>" + unvoteMessage + '</a>';
 30+ '<a href="javascript:void(0);" class="vote-unvote-link">' +
 31+ mediaWiki.msg( 'vote-unvote-link' ) + '</a>';
3832 } );
3933 },
4034
4135 /**
4236 * Called when removing your vote through the green square voting box
 37+ *
4338 * @param PageID Integer: internal ID number of the current article
4439 * @param mk Mixed: random token
4540 */
46 - unVote: function( PageID, mk ) {
 41+ unVote: function( PageID ) {
4742 sajax_request_type = 'POST';
48 - sajax_do_call( 'wfVoteDelete', [ PageID, mk ], function( request ) {
 43+ sajax_do_call( 'wfVoteDelete', [ PageID ], function( request ) {
4944 document.getElementById( 'votebox' ).style.cursor = 'pointer';
5045 document.getElementById( 'PollVotes' ).innerHTML = request.responseText;
51 - var voteMessage;
52 - if ( typeof( mediaWiki ) == 'undefined' ) {
53 - voteMessage = _VOTE_LINK;
54 - } else {
55 - voteMessage = mediaWiki.msg( 'vote-link' );
56 - }
5746 document.getElementById( 'Answer' ).innerHTML =
58 - '<a href=javascript:VoteNY.clickVote(1,' + PageID + ',"' + mk +
59 - '")>' + voteMessage + '</a>';
 47+ '<a href="javascript:void(0);" class="vote-vote-link">' +
 48+ mediaWiki.msg( 'vote-link' ) + '</a>';
6049 } );
6150 },
6251
6352 /**
6453 * Called when adding a vote after a user has clicked the yellow voting stars
 54+ *
6555 * @param PageID Integer: internal ID number of the current article
66 - * @param mk Mixed: random token
6756 * @param id Integer: ID of the current rating star
6857 * @param action Integer: controls which AJAX function will be called
6958 */
70 - clickVoteStars: function( TheVote, PageID, mk, id, action ) {
 59+ clickVoteStars: function( TheVote, PageID, id, action ) {
7160 VoteNY.voted_new[id] = TheVote;
7261 var rsfun;
7362 if( action == 3 ) {
@@ -78,26 +67,25 @@
7968
8069 var resultElement = document.getElementById( 'rating_' + id );
8170 sajax_request_type = 'POST';
82 - sajax_do_call( rsfun, [ TheVote, PageID, mk ], resultElement );
 71+ sajax_do_call( rsfun, [ TheVote, PageID ], resultElement );
8372 },
8473
8574 /**
8675 * Called when removing your vote through the yellow voting stars
 76+ *
8777 * @param PageID Integer: internal ID number of the current article
88 - * @param mk Mixed: random token
8978 * @param id Integer: ID of the current rating star
9079 */
91 - unVoteStars: function( PageID, mk, id ) {
 80+ unVoteStars: function( PageID, id ) {
9281 var resultElement = document.getElementById( 'rating_' + id );
9382 sajax_request_type = 'POST';
94 - sajax_do_call( 'wfVoteStarsDelete', [ PageID, mk ], resultElement );
 83+ sajax_do_call( 'wfVoteStarsDelete', [ PageID ], resultElement );
9584 },
9685
9786 startClearRating: function( id, rating, voted ) {
98 - VoteNY.clearRatingTimer = setTimeout(
99 - "VoteNY.clearRating('" + id + "',0," + rating + ',' + voted + ')',
100 - 200
101 - );
 87+ VoteNY.clearRatingTimer = setTimeout( function() {
 88+ VoteNY.clearRating( id, 0, rating, voted );
 89+ }, 200 );
10290 },
10391
10492 clearRating: function( id, num, prev_rating, voted ) {
@@ -133,4 +121,48 @@
134122 }
135123 VoteNY.last_id = id;
136124 }
137 -};
\ No newline at end of file
 125+};
 126+
 127+jQuery( document ).ready( function() {
 128+ // Green voting box
 129+ jQuery( '#votebox, a.vote-vote-link' ).click( function() {
 130+ VoteNY.clickVote( 1, mw.config.get( 'wgArticleId' ) );
 131+ } );
 132+
 133+ jQuery( 'a.vote-unvote-link' ).click( function() {
 134+ VoteNY.unVote( mw.config.get( 'wgArticleId' ) );
 135+ } );
 136+
 137+ // Rating stars
 138+ jQuery( 'img.vote-rating-star' ).click( function() {
 139+ var that = jQuery( this );
 140+ VoteNY.clickVoteStars(
 141+ that.data( 'vote-the-vote' ),
 142+ mw.config.get( 'wgArticleId' ),
 143+ that.data( 'vote-id' ),
 144+ that.data( 'vote-action' )
 145+ );
 146+ } ).mouseover( function() {
 147+ var that = jQuery( this );
 148+ VoteNY.updateRating(
 149+ that.data( 'vote-id' ),
 150+ that.data( 'vote-the-vote' ),
 151+ that.data( 'vote-rating' )
 152+ );
 153+ } ).mouseout( function() {
 154+ var that = jQuery( this );
 155+ VoteNY.startClearRating(
 156+ that.data( 'vote-id' ),
 157+ that.data( 'vote-rating' ),
 158+ that.data( 'vote-voted' )
 159+ );
 160+ } );
 161+
 162+ // Remove vote (rating stars)
 163+ jQuery( 'a.vote-remove-stars-link' ).click( function() {
 164+ VoteNY.unVoteStars(
 165+ mw.config.get( 'wgArticleId' ),
 166+ jQuery( this ).data( 'vote-id' )
 167+ );
 168+ } );
 169+} );
\ No newline at end of file
Index: trunk/extensions/VoteNY/VoteClass.php
@@ -13,6 +13,7 @@
1414
1515 /**
1616 * Constructor
 17+ *
1718 * @param $pageID Integer: article ID number
1819 */
1920 public function __construct( $pageID ) {
@@ -26,10 +27,12 @@
2728 /**
2829 * Counts all votes, fetching the data from memcached if available
2930 * or from the database if memcached isn't available
 31+ *
3032 * @return Integer: amount of votes
3133 */
3234 function count() {
3335 global $wgMemc;
 36+
3437 $key = wfMemcKey( 'vote', 'count', $this->PageID );
3538 $data = $wgMemc->get( $key );
3639
@@ -52,6 +55,7 @@
5356 }
5457 $wgMemc->set( $key, $vote_count );
5558 }
 59+
5660 return $vote_count;
5761 }
5862
@@ -82,6 +86,7 @@
8387 }
8488 $wgMemc->set( $key, $voteAvg );
8589 }
 90+
8691 return number_format( $voteAvg, 2 );
8792 }
8893
@@ -96,21 +101,22 @@
97102 $wgMemc->delete( wfMemcKey( 'vote', 'avg', $this->PageID ) );
98103
99104 // Purge squid
100 - $page_title = Title::newFromID( $this->PageID );
101 - if( is_object( $page_title ) ) {
102 - $page_title->invalidateCache();
103 - $page_title->purgeSquid();
 105+ $pageTitle = Title::newFromID( $this->PageID );
 106+ if( is_object( $pageTitle ) ) {
 107+ $pageTitle->invalidateCache();
 108+ $pageTitle->purgeSquid();
104109
105110 // Kill parser cache
106 - $article = new Article( $page_title );
 111+ $article = new Article( $pageTitle );
107112 $parserCache = ParserCache::singleton();
108 - $parser_key = $parserCache->getKey( $article, $wgUser );
109 - $wgMemc->delete( $parser_key );
 113+ $parserKey = $parserCache->getKey( $article, $wgUser );
 114+ $wgMemc->delete( $parserKey );
110115 }
111116 }
112117
113118 /**
114 - * Delete the user's vote from the DB if s/he wants to remove his/her vote
 119+ * Delete the user's vote from the database, purges normal caches and
 120+ * updates SocialProfile's statistics, if SocialProfile is active.
115121 */
116122 function delete() {
117123 $dbw = wfGetDB( DB_MASTER );
@@ -169,8 +175,9 @@
170176
171177 /**
172178 * Checks if a user has already voted
173 - * @return Boolean: false if s/he hasn't, otherwise returns the value of
174 - * 'vote_value' column from Vote DB table
 179+ *
 180+ * @return Boolean|Integer: false if s/he hasn't, otherwise returns the
 181+ * value of 'vote_value' column from Vote DB table
175182 */
176183 function UserAlreadyVoted() {
177184 $dbr = wfGetDB( DB_SLAVE );
@@ -197,7 +204,6 @@
198205 function display() {
199206 global $wgUser;
200207
201 - $this->votekey = md5( $this->PageID . 'pants' . $this->Username );
202208 $voted = $this->UserAlreadyVoted();
203209
204210 $make_vote_box_clickable = '';
@@ -205,7 +211,7 @@
206212 $make_vote_box_clickable = ' vote-clickable';
207213 }
208214
209 - $output = "<div class=\"vote-box{$make_vote_box_clickable}\" id=\"votebox\" onclick=\"VoteNY.clickVote(1,{$this->PageID},'{$this->votekey}')\">";
 215+ $output = "<div class=\"vote-box{$make_vote_box_clickable}\" id=\"votebox\">";
210216 $output .= '<span id="PollVotes" class="vote-number">' . $this->count() . '</span>';
211217 $output .= '</div>';
212218 $output .= '<div id="Answer" class="vote-action">';
@@ -221,10 +227,10 @@
222228 } else {
223229 if( !wfReadOnly() ) {
224230 if( $voted == false ) {
225 - $output .= "<a href=\"javascript:VoteNY.clickVote(1,{$this->PageID},'{$this->votekey}')\">" .
 231+ $output .= '<a href="javascript:void(0);" class="vote-vote-link">' .
226232 wfMsg( 'vote-link' ) . '</a>';
227233 } else {
228 - $output .= "<a href=\"javascript:VoteNY.unVote('{$this->PageID}', '{$this->votekey}')\">" .
 234+ $output .= '<a href="javascript:void(0);" class="vote-unvote-link">' .
229235 wfMsg( 'vote-unvote-link' ) . '</a>';
230236 }
231237 }
@@ -244,7 +250,8 @@
245251
246252 /**
247253 * Displays voting stars
248 - * @param $voted Boolean: false by default
 254+ *
 255+ * @param $voted Boolean: has the user already voted? False by default
249256 * @return Mixed: HTML output
250257 */
251258 function display( $voted = false ) {
@@ -258,7 +265,6 @@
259266 $display_stars_rating = $this->getAverageVote();
260267 }
261268
262 - $this->votekey = md5( $this->PageID . 'pants' . $this->Username );
263269 $id = '';
264270
265271 // Should probably be $this->PageID or something?
@@ -279,7 +285,7 @@
280286 $output .= '<div class="rating-voted">' .
281287 wfMsgExt( 'vote-gave-this', 'parsemag', $already_voted ) .
282288 " </div>
283 - <a href=\"javascript:VoteNY.unVoteStars({$this->PageID},'{$this->votekey}','{$id}')\">("
 289+ <a href=\"javascript:void(0);\" class=\"vote-remove-stars-link\" data-vote-id=\"{$id}\">("
284290 . wfMsg( 'vote-remove' ) .
285291 ')</a>';
286292 }
@@ -304,7 +310,6 @@
305311 if( !$rating ) {
306312 $rating = 0;
307313 }
308 - $this->votekey = md5( $this->PageID . 'pants' . $this->Username );
309314 if( !$voted ) {
310315 $voted = 0;
311316 }
@@ -316,10 +321,10 @@
317322 } else {
318323 $action = 5;
319324 }
320 - $onclick = "VoteNY.clickVoteStars({$x},{$this->PageID},'{$this->votekey}','{$id}',$action);";
321 - $onmouseover = "VoteNY.updateRating('{$id}',{$x},{$rating});";
322 - $onmouseout = "VoteNY.startClearRating('{$id}','{$rating}',{$voted});";
323 - $output .= "<img onclick=\"javascript:{$onclick}\" onmouseover=\"javascript:{$onmouseover}\" onmouseout=\"javascript:{$onmouseout}\" id=\"rating_{$id}_{$x}\" src=\"{$wgScriptPath}/extensions/VoteNY/images/star_";
 325+ $output .= "<img class=\"vote-rating-star\" data-vote-the-vote=\"{$x}\"" .
 326+ " data-vote-id=\"{$id}\" data-vote-action=\"{$action}\" data-vote-rating=\"{$rating}\"" .
 327+ " data-vote-voted=\"{$voted}\" id=\"rating_{$id}_{$x}\"" .
 328+ " src=\"{$wgScriptPath}/extensions/VoteNY/images/star_";
324329 switch( true ) {
325330 case $rating >= $x:
326331 if( $voted ) {

Follow-up revisions

RevisionCommit summaryAuthorDate
r108319VoteNY: follow-up to r108076: specify oldid parameter for Article constructor...ashley15:09, 7 January 2012

Comments

#Comment by Johnduhart (talk | contribs)   00:53, 5 January 2012
- * @date 21 August 2011
+ * @date 11 December 2011

Is that right?

#Comment by Jack Phoenix (talk | contribs)   19:26, 5 January 2012

It depends; while it certainly looks funky in this context, it's true that I made fixes to SpecialTopRatings.php back in December 2011, as those fixes were very easy. Most of the code committed in this revision was written yesterday, though (because only yesterday I remembered about HTML5 and its useful data attributes).

#Comment by Nikerabbit (talk | contribs)   06:49, 5 January 2012

Looks prone to bugs:

$article = new Article( $pageTitle );
#Comment by Jack Phoenix (talk | contribs)   19:28, 5 January 2012

What kind of bugs? I'm aware that in trunk $wgArticle is deprecated and the Article class was split into WikiPage & friends, but besides that? (I could add something like class_exists( 'WikiPage' ) there, but I think I'll just update the extension for 1.19 when it has been released and drop pre-1.19 compatibility then.)

#Comment by Aaron Schulz (talk | contribs)   19:32, 5 January 2012

He may be referring to the lack of an explicit oldid parameter.

#Comment by Nikerabbit (talk | contribs)   20:05, 5 January 2012

Correct. The mystery man strikes again.

Status & tagging log