r100896 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r100895‎ | r100896 | r100897 >
Date:23:06, 26 October 2011
Author:khorn
Status:ok
Tags:fundraising 
Comment:
Mostly concerned with splitting currentTransaction into getCurrentTransaction and setCurrentTransaction, in the main gateway adapter parent class.
Lots of function comments.
Additional: Many functions that are never ever ever ever ever supposed to be accessed from the outside of an adapter, are now protected. The ones that are absolutely supposed to be used form the outside are now explicitly public. Those that are just functions have not yet been evaluated during the cleanup process. This should help immensely with what is turning out to be a rather large and unwieldy class.
If you absolutely must access protected functions directly, I don't believe you.
(...unless you're doing unit tests, in which case you should have a dummy adapter child class anyway, and that can give you direct pass-through access to whatever you feel you need.)
r95923
Modified paths:
  • /trunk/extensions/DonationInterface/gateway_common/gateway.adapter.php (modified) (history)
  • /trunk/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php (modified) (history)
  • /trunk/extensions/DonationInterface/tests/DonationInterfaceTestCase.php (modified) (history)

Diff [purge]

Index: trunk/extensions/DonationInterface/tests/DonationInterfaceTestCase.php
@@ -62,7 +62,7 @@
6363
6464 $this->gatewayAdapter = new GlobalCollectAdapter( $options );
6565
66 - $this->gatewayAdapter->currentTransaction('INSERT_ORDERWITHPAYMENT');
 66+ $this->gatewayAdapter->setCurrentTransaction('INSERT_ORDERWITHPAYMENT');
6767
6868 $request = trim( $this->gatewayAdapter->buildRequestXML() );
6969
Index: trunk/extensions/DonationInterface/globalcollect_gateway/globalcollect.adapter.php
@@ -555,7 +555,7 @@
556556 public function getResponseData( $response ) {
557557 $data = array( );
558558
559 - $transaction = $this->currentTransaction();
 559+ $transaction = $this->getCurrentTransaction();
560560
561561 switch ( $transaction ) {
562562 case 'INSERT_ORDERWITHPAYMENT':
Index: trunk/extensions/DonationInterface/gateway_common/gateway.adapter.php
@@ -67,7 +67,7 @@
6868 * First array key: Some way for us to id the transaction. Doesn't actually have to be the gateway's name for it, but I'm going with that until I have a reason not to.
6969 * Second array key:
7070 * 'request' contains the structure of that request. Leaves in the array tree will eventually be mapped to actual values of ours,
71 - * according to the precidence established in the getValue function.
 71+ * according to the precidence established in the getTransactionSpecificValue function.
7272 * 'values' contains default values for the transaction. Things that are typically not overridden should go here.
7373 */
7474 function defineTransactions();
@@ -135,6 +135,7 @@
136136 protected $transaction_results;
137137 protected $form_class;
138138 protected $validation_errors;
 139+ protected $current_transaction;
139140 public $action; //Currently, hooks need to be able to set this directly.
140141 public $debugarray; //TODO: Take me out.
141142
@@ -230,7 +231,14 @@
231232 return $returnto;
232233 }
233234
234 - function checkTokens() {
 235+ /**
 236+ * Checks the edit tokens in the user's session against the one gathered
 237+ * from populated form data.
 238+ * Adds a string to the debugarray, to make it a little easier to tell what
 239+ * happened if we turn the debug results on.
 240+ * @return boolean true if match, else false.
 241+ */
 242+ public function checkTokens() {
235243 $checkResult = $this->dataObj->checkTokens();
236244
237245 if ( $checkResult ) {
@@ -320,21 +328,41 @@
321329 }
322330
323331 /**
324 - * getValue
325 - *
326 - * @todo
327 - * - This is specific to transactions.
328 - * - This method probably needs to be renamed.
 332+ * This function is used exclusively by the two functions that build
 333+ * requests to be sent directly to external payment gateway servers. Those
 334+ * two functions are buildRequestNameValueString, and (perhaps less
 335+ * obviously) buildRequestXML. As such, unless a valid current transaction
 336+ * has already been set, this will error out rather hard.
 337+ * In other words: In all likelihood, this is not the function you're
 338+ * looking for.
 339+ * @param string $gateway_field_name The GATEWAY's field name that we are
 340+ * hoping to populate. Probably not even remotely the way we name the same
 341+ * data internally.
 342+ * @param boolean $token This is a throwback to a road we nearly went down,
 343+ * with ajax and client-side token replacement. The idea was, if this was
 344+ * set to true, we would simply pass the fully-formed transaction structure
 345+ * with our tokenized var names in the spots where form values would usually
 346+ * go, so we could fetch the structure and have some client-side voodoo
 347+ * populate the transaction so we wouldn't have to touch the data at all.
 348+ * At this point, very likely cruft that can be removed, but as I'm not 100%
 349+ * on that point, I'm keeping it for now. If we do kill off this param, we
 350+ * should also get rid of the function buildTransactionFormat and anything
 351+ * that calls it.
 352+ * @return mixed The value we want to send directly to the gateway, for the
 353+ * specified gateway field name.
329354 */
330 - public function getValue( $gateway_field_name, $token = false ) {
 355+ protected function getTransactionSpecificValue( $gateway_field_name, $token = false ) {
331356 if ( empty( $this->transactions ) ) {
332 - //TODO: These dies should all throw exceptions or something less completely fatal.
333357 $msg = self::getGatewayName() . ': Transactions structure is empty! No transaction can be constructed.';
334358 self::log( $msg, LOG_CRIT );
335359 throw new MWException( $msg );
336360 }
337 - //How do we determine the value of a field asked for in a particular transaction?
338 - $transaction = $this->currentTransaction();
 361+ //Ensures we are using the correct transaction structure for our various lookups.
 362+ $transaction = $this->getCurrentTransaction();
 363+
 364+ if ( !$transaction ){
 365+ return null;
 366+ }
339367
340368 //If there's a hard-coded value in the transaction definition, use that.
341369 if ( !empty( $transaction ) ) {
@@ -382,14 +410,45 @@
383411 self::log( $msg, LOG_CRIT );
384412 throw new MWException( $msg );
385413 }
 414+
 415+
 416+ /**
 417+ * Returns the current transaction request structure if it exists, otherwise
 418+ * returns false.
 419+ * Fails nicely if the current transaction is simply not set yet.
 420+ * Throws an exception if the transaction is set, but no structure is defined.
 421+ * @return mixed current transaction's structure as an array, or false
 422+ */
 423+ protected function getTransactionRequestStructure(){
 424+ $transaction = $this->getCurrentTransaction();
 425+ if ( !$transaction ){
 426+ return false;
 427+ }
 428+
 429+ if ( empty( $this->transactions ) ||
 430+ !array_key_exists( $transaction, $this->transactions ) ||
 431+ ! array_key_exists( 'request', $this->transactions[$transaction] ) ) {
 432+
 433+ $msg = self::getGatewayName() . ": $transaction request structure is empty! No transaction can be constructed.";
 434+ self::log( $msg, LOG_CRIT );
 435+ throw new MWException( $msg );
 436+ }
 437+
 438+ return $this->transactions[$transaction]['request'];
 439+ }
386440
387441 /**
388 - * Build a string of name/value pairs out of our donation data for submission to the payment
389 - * processor.
 442+ * Builds a set of transaction data in name/value format
 443+ * *)The current transaction must be set before you call this function.
 444+ * *)Uses getTransactionSpecificValue to assign staged values to the
 445+ * fields required by the gateway. Look there for more insight into the
 446+ * heirarchy of all possible data sources.
 447+ * @return string The raw transaction in name/value format, ready to be
 448+ * curl'd off to the remote server.
390449 */
391 - function buildRequestNameValueString() {
 450+ protected function buildRequestNameValueString() {
392451 // Look up the request structure for our current transaction type in the transactions array
393 - $structure = $this->transactions[$this->currentTransaction()]['request'];
 452+ $structure = $this->getTransactionRequestStructure();
394453 if ( !is_array( $structure ) ) {
395454 return '';
396455 }
@@ -398,7 +457,7 @@
399458
400459 //we are going to assume a flat array, because... namevalue.
401460 foreach ( $structure as $fieldname ) {
402 - $fieldvalue = $this->getValue( $fieldname );
 461+ $fieldvalue = $this->getTransactionSpecificValue( $fieldname );
403462 if ( $fieldvalue !== '' && $fieldvalue !== false ) {
404463 $queryvals[] = $fieldname . '[' . strlen( $fieldvalue ) . ']=' . $fieldvalue;
405464 }
@@ -409,22 +468,39 @@
410469 }
411470
412471 /**
413 - * Build an XML document out of our donation data for submission to the payment processor.
 472+ * Builds a set of transaction data in XML format
 473+ * *)The current transaction must be set before you call this function.
 474+ * *)(eventually) uses getTransactionSpecificValue to assign staged
 475+ * values to the fields required by the gateway. Look there for more insight
 476+ * into the heirarchy of all possible data sources.
 477+ * @return string The raw transaction in xml format, ready to be
 478+ * curl'd off to the remote server.
414479 */
415 - function buildRequestXML() {
 480+ protected function buildRequestXML() {
416481 $this->xmlDoc = new DomDocument( '1.0' );
417482 $node = $this->xmlDoc->createElement( 'XML' );
418483
419484 // Look up the request structure for our current transaction type in the transactions array
420 - $structure = $this->transactions[$this->currentTransaction()]['request'];
 485+ $structure = $this->getTransactionRequestStructure();
 486+ if ( !is_array( $structure ) ) {
 487+ return '';
 488+ }
421489
422490 $this->buildTransactionNodes( $structure, $node );
423491 $this->xmlDoc->appendChild( $node );
424492 return $this->xmlDoc->saveXML();
425493 }
426494
427 - function buildTransactionNodes( $structure, &$node, $js = false ) {
428 - $transaction = $this->currentTransaction();
 495+ /**
 496+ * buildRequestXML helper function.
 497+ * Builds the XML transaction by recursively crawling the transaction
 498+ * structure and adding populated nodes by reference.
 499+ * @param array $structure Current transaction's more leafward structure,
 500+ * from the point of view of the current XML node.
 501+ * @param xmlNode $node The current XML node.
 502+ * @param bool $js More likely cruft relating back to buildTransactionFormat
 503+ */
 504+ protected function buildTransactionNodes( $structure, &$node, $js = false ) {
429505
430506 if ( !is_array( $structure ) ) { //this is a weird case that shouldn't ever happen. I'm just being... thorough. But, yeah: It's like... the base-1 case.
431507 $this->appendNodeIfValue( $structure, $node, $js );
@@ -443,21 +519,48 @@
444520 //not actually returning anything. It's all side-effects. Because I suck like that.
445521 }
446522
447 - function appendNodeIfValue( $value, &$node, $js = false ) {
448 - $nodevalue = $this->getValue( $value, $js );
 523+ /**
 524+ * appendNodeIfValue is a helper function for buildTransactionNodes, which
 525+ * is used by buildRequestXML to construct an XML transaction.
 526+ * This function will append an XML node to the transaction being built via
 527+ * the passed-in parent node, only if the current node would have a
 528+ * non-empty value.
 529+ * @param string $value The GATEWAY's field name for the current node.
 530+ * @param string $node The parent node this node will be contained in, if it
 531+ * is determined to have a non-empty value.
 532+ * @param bool $js Probably cruft at this point. This is connected to the
 533+ * function buildTransactionFormat.
 534+ */
 535+ protected function appendNodeIfValue( $value, &$node, $js = false ) {
 536+ $nodevalue = $this->getTransactionSpecificValue( $value, $js );
449537 if ( $nodevalue !== '' && $nodevalue !== false ) {
450538 $temp = $this->xmlDoc->createElement( $value, $nodevalue );
451539 $node->appendChild( $temp );
452540 }
453541 }
454542
455 - //TODO: You can actually take this out if we never ever want to use ajax for a gateway.
456 - function buildTransactionFormat( $transaction ) {
457 - $this->currentTransaction( $transaction );
 543+ /**
 544+ * This is a throwback to a road we nearly went down,
 545+ * with ajax and client-side token replacement. The idea was, if this was
 546+ * set to true, we would simply pass the fully-formed transaction structure
 547+ * with our tokenized var names in the spots where form values would usually
 548+ * go, so we could fetch the structure and have some client-side voodoo
 549+ * populate the transaction so we wouldn't have to touch the data at all.
 550+ * At this point, very likely cruft that can be removed, but as I'm not 100%
 551+ * on that point, I'm keeping it for now.
 552+ * @param string $transaction The current transaction.
 553+ * @return string XML transaction with the form values tokenized instead of
 554+ * populated.
 555+ */
 556+ public function buildTransactionFormat( $transaction ) {
 557+ $this->setCurrentTransaction( $transaction );
458558 $this->xmlDoc = new DomDocument( '1.0' );
459559 $node = $this->xmlDoc->createElement( 'XML' );
460560
461 - $structure = $this->transactions[$this->currentTransaction()]['request'];
 561+ $structure = $this->getTransactionRequestStructure();
 562+ if ( !is_array( $structure ) ) {
 563+ return '';
 564+ }
462565
463566 $this->buildTransactionNodes( $structure, $node, true );
464567 $this->xmlDoc->appendChild( $node );
@@ -471,14 +574,37 @@
472575 }
473576
474577 /**
475 - * Perform a transaction through the gateway
 578+ * Perform a transaction through the gateway.
 579+ * This is the entire end-to-end function, meant to be used from the
 580+ * outside, to communicate with a properly constructed gateway and handle
 581+ * all the return data in an appropriate manner, according to the requested
 582+ * transaction's structure and definition.
476583 *
477 - * @param $transaction string This is a specific transaction type like 'INSERT_ORDERWITHPAYMENT'
 584+ * @param string $transaction This is a specific transaction type like 'INSERT_ORDERWITHPAYMENT'
478585 * that maps to a first-level key in the $transactions array.
 586+ * @return array The results of the transaction attempt. Minimum keys include:
 587+ * 'status' = The result of the pure communication attempt. If there was a
 588+ * server there, and it responded in a way that was parsable, this will be
 589+ * set to true, even if it gave us bad news. In all other cases, this will be false.
 590+ * 'message' = An appropriate thing to say to... whatever called us, about
 591+ * the overall result that happened here.
 592+ * TODO: Some kind of i18n here. Either pass message labels, or...
 593+ * ...wait, I like that one. Let's pass message labels.
 594+ * 'errors' = sort of a misnomer, that should probably be renamed to
 595+ * result_codes or similar. This is always going to be an array of
 596+ * numeric codes (even if we have to make them up ourselves) and
 597+ * human-readable assessments of what happened, probably straight from
 598+ * the gateway.
 599+ * 'action' = (sometimes there) What the pre-commit hooks said we should go
 600+ * do with ourselves. Mostly in there for debugging purposes at this
 601+ * point, as nothing on the outside should care at all, how we do things
 602+ * internally.
 603+ * 'data' = The data passed back to us from the transaction, in a nice
 604+ * key-value array.
479605 */
480 - function do_transaction( $transaction ) {
 606+ public function do_transaction( $transaction ) {
481607 try {
482 - $this->currentTransaction( $transaction );
 608+ $this->setCurrentTransaction( $transaction );
483609 //update the contribution tracking data
484610 $this->incrementNumAttempt();
485611
@@ -488,10 +614,7 @@
489615 }
490616
491617 $this->runPreProcess(); //many hooks get fired here...
492 - //TODO: Uhmmm... what if none of the validate hooks are enabled?
493 - //Currently, I think that means the transaction stops here, and that's not quite right.
494 - //...is it?
495 - // if the transaction was NOT flagged for processing by something in runPreProcess()...
 618+
496619 if ( $this->action != 'process' ) {
497620 self::log( "Transaction failed pre-process checks." . print_r( $this->getData(), true ) );
498621 return array(
@@ -681,28 +804,40 @@
682805 }
683806
684807 /**
685 - * Get or set the current transaction
686 - *
687 - * @param $transaction string This is a specific transaction type like 'INSERT_ORDERWITHPAYMENT'
688 - * that maps to a first-level key in the $transactions array.
 808+ * Sets the transaction you are about to send to the payment gateway. This
 809+ * will throw an exception if you try to set it to something that has no
 810+ * transaction definition.
 811+ * @param type $transaction_name This is a specific transaction type like
 812+ * 'INSERT_ORDERWITHPAYMENT' (if you're GlobalCollect) that maps to a
 813+ * first-level key in the $transactions array.
689814 */
690 - public function currentTransaction( $transaction = '' ) { //get&set in one!
691 - static $current_transaction;
692 - if ( $transaction != '' ) {
693 - $current_transaction = $transaction;
694 - }
695 - if ( !isset( $current_transaction ) ) {
696 - return false;
697 - }
698 - if ( empty( $this->transactions ) || !is_array( $this->transactions ) || !array_key_exists( $current_transaction, $this->transactions ) ) {
699 - $msg = self::getGatewayName() . ': Transactions structure is malformed! ' . $current_transaction . ' transaction cannot be constructed.';
 815+ public function setCurrentTransaction( $transaction_name ){
 816+ if ( empty( $this->transactions ) || !is_array( $this->transactions ) || !array_key_exists( $transaction_name, $this->transactions ) ) {
 817+ $msg = self::getGatewayName() . ': Transaction Name "' . $transaction_name . '" undefined for this gateway.';
700818 self::log( $msg, LOG_CRIT );
701819 throw new MWException( $msg );
 820+ } else {
 821+ $this->current_transaction = $transaction_name;
702822 }
703 - return $current_transaction;
704823 }
705824
706825 /**
 826+ * Gets the currently set transaction name. This value should only ever be
 827+ * set with setCurrentTransaction: A function that ensures the current
 828+ * transaction maps to a first-level key that is known to exist in the
 829+ * $transactions array, defined in the child gateway.
 830+ * @return mixed The name of the properly set transaction, or false if none
 831+ * has been set.
 832+ */
 833+ public function getCurrentTransaction(){
 834+ if ( is_null( $this->current_transaction ) ) {
 835+ return false;
 836+ } else {
 837+ return $this->current_transaction;
 838+ }
 839+ }
 840+
 841+ /**
707842 * Define payment methods
708843 *
709844 * Payment methods include:
@@ -756,9 +891,14 @@
757892 }
758893
759894 /**
760 - * Sends a name-value pair string to Payflow gateway
761 - *
762 - * @param $data String: The exact thing we want to send.
 895+ * Sends a curl request to the gateway server, and gets a response.
 896+ * Saves that response to the gateway object with setTransactionResult();
 897+ * @param string $data the raw data we want to curl up to a server somewhere.
 898+ * Should have been constructed with either buildRequestNameValueString, or
 899+ * buildRequestXML.
 900+ * @return boolean true if the communication was successful and there is a
 901+ * parseable response, false if there was a fundamental communication
 902+ * problem. (timeout, bad URL, etc.)
763903 */
764904 protected function curl_transaction( $data ) {
765905 // assign header data necessary for the curl_setopt() function
@@ -803,14 +943,13 @@
804944 }
805945 }
806946
807 - $this->saveCommunicationStats( __FUNCTION__, $this->currentTransaction(), "Request:" . print_r( $data, true ) . "\nResponse" . print_r( $results, true ) );
 947+ $this->saveCommunicationStats( __FUNCTION__, $this->getCurrentTransaction(), "Request:" . print_r( $data, true ) . "\nResponse" . print_r( $results, true ) );
808948
809949 if ( $results['headers']['http_code'] != 200 ) {
810950 $results['result'] = false;
811951 //TODO: i18n here!
812952 //TODO: But also, fire off some kind of "No response from the gateway" thing to somebody so we know right away.
813953 $results['message'] = 'No response from ' . self::getGatewayName() . '. Please try again later!';
814 - $when = time();
815954 self::log( $this->postdatadefaults['order_id'] . ' No response from ' . self::getGatewayName() . ': ' . curl_error( $ch ) );
816955 curl_close( $ch );
817956 return false;
@@ -985,15 +1124,24 @@
9861125 }
9871126
9881127 /**
 1128+ * addCodeRange is used to define ranges of response codes for major WMF
 1129+ * donation-making gateway transactions, that let us know what status bucket
 1130+ * to sort them into.
9891131 * DO NOT DEFINE OVERLAPPING RANGES!
990 - * TODO: Make sure it won't let you add overlapping ranges. That would probably necessitate the sort moving to here, too.
991 - * @param type $transaction
992 - * @param type $key
993 - * @param type $action
994 - * @param type $lower
995 - * @param type $upper
 1132+ * TODO: Make sure it won't let you add overlapping ranges. That would
 1133+ * probably necessitate the sort moving to here, too.
 1134+ * @param string $transaction The transaction these codes map to.
 1135+ * @param string $key The (incoming) field name containing the numeric codes
 1136+ * we're defining here.
 1137+ * @param string $action Should be limited to the values 'complete',
 1138+ * 'pending', 'pending-poke', 'failed' and 'revised'... but for now you're
 1139+ * on the honor system, kids. TODO: Limit these values in this function,
 1140+ * once we are slightly more certain we don't need more values.
 1141+ * @param int $lower The integer value of the lower-bound in this code range.
 1142+ * @param int $upper Optional: The integer value of the upper-bound in the
 1143+ * code range. If omitted, it will make a range of one value: The lower bound.
9961144 */
997 - function addCodeRange( $transaction, $key, $action, $lower, $upper = null ) {
 1145+ protected function addCodeRange( $transaction, $key, $action, $lower, $upper = null ) {
9981146 if ( $upper === null ) {
9991147 $this->return_value_map[$transaction][$key][$lower] = $action;
10001148 } else {
@@ -1039,7 +1187,15 @@
10401188 return null;
10411189 }
10421190
1043 - function addDonorDataToSession() {
 1191+ /**
 1192+ * Adds donor data to the user's session, presumably for retrieval before we
 1193+ * unset all the session data.
 1194+ * An example of a time you would want to use this, is when fetching
 1195+ * GlobalCollect's credit card iFrame. They need the donor data, but don't
 1196+ * pass it back to us, so we have to hold on to it somehow prior to the
 1197+ * point we save it to the database (on successful conversion)
 1198+ */
 1199+ public function addDonorDataToSession() {
10441200 $this->dataObj->addDonorDataToSession();
10451201 }
10461202
@@ -1053,7 +1209,20 @@
10541210 $this->debugarray[] = 'Killed all the session everything.';
10551211 }
10561212
1057 - function doStompTransaction() {
 1213+ /**
 1214+ * Called in do_transaction, in the case that we have successfully completed
 1215+ * a transaction that has 'do_processhooks' enabled.
 1216+ * Saves a stomp frame to the configured server and queue, based on the
 1217+ * outcome of our current transaction.
 1218+ * The big tricky thing here, is that we DO NOT SET a TransactionWMFStatus,
 1219+ * unless we have just learned what happened to a donation in progress,
 1220+ * through performing the current transaction.
 1221+ * To put it another way, getTransactionWMFStatus should always return
 1222+ * false, unless it's new data about a new transaction. In that case, the
 1223+ * outcome will be assigned and the proper stomp hook selected.
 1224+ * @return null
 1225+ */
 1226+ protected function doStompTransaction() {
10581227 $this->debugarray[] = "Attempting Stomp Transaction!";
10591228 $hook = '';
10601229
@@ -1386,13 +1555,17 @@
13871556
13881557 function transaction_option( $option_value ) {
13891558 //ooo, ugly.
1390 - if ( array_key_exists( $option_value, $this->transactions[$this->currentTransaction()] ) ) {
1391 - if ( $this->transactions[$this->currentTransaction()][$option_value] === true ) {
 1559+ $transaction = $this->getCurrentTransaction();
 1560+ if ( !$transaction ){
 1561+ return false;
 1562+ }
 1563+ if ( array_key_exists( $option_value, $this->transactions[$transaction] ) ) {
 1564+ if ( $this->transactions[$transaction][$option_value] === true ) {
13921565 return true;
13931566 }
1394 - if ( is_array( $this->transactions[$this->currentTransaction()][$option_value] ) &&
1395 - !empty( $this->transactions[$this->currentTransaction()][$option_value] ) ) {
1396 - return $this->transactions[$this->currentTransaction()][$option_value];
 1567+ if ( is_array( $this->transactions[$transaction][$option_value] ) &&
 1568+ !empty( $this->transactions[$transaction][$option_value] ) ) {
 1569+ return $this->transactions[$transaction][$option_value];
13971570 }
13981571 }
13991572 return false;

Follow-up revisions

RevisionCommit summaryAuthorDate
r100936follow-up to r100896, just removing an unnecessary !empty and fixing a !kaldari02:31, 27 October 2011

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r95923More refactoring. The new globalcollect gateway is now inheriting from the co...khorn00:43, 1 September 2011

Status & tagging log