Committer: vtroitsky
LJSUP-11372: [Refactoring] Develop regional self promo for Ukraine. Final results. Switching to a new Code.U trunk/bin/maint/selfpromo.pl U trunk/cgi-bin/LJ/Console/Command/SelfPromo.pm U trunk/cgi-bin/LJ/Pay/Payment/PayItem/SelfPromo.pm U trunk/cgi-bin/LJ/Pay/SelfPromo.pm U trunk/cgi-bin/LJ/Widget/Shop/View/SelfPromo.pm U trunk/templates/Shop/SelfPromo.tmpl
Modified: trunk/bin/maint/selfpromo.pl =================================================================== --- trunk/bin/maint/selfpromo.pl 2012-04-26 11:49:02 UTC (rev 11814) +++ trunk/bin/maint/selfpromo.pl 2012-04-26 11:51:17 UTC (rev 11815) @@ -5,11 +5,16 @@ use vars qw( %maint ); use LJ::Pay::SelfPromo; +use LJ::Pay::PromotionSlot; $maint{'selfpromo_check'} = sub { - LJ::Pay::SelfPromo::EntryPromo->check_current_promo; - LJ::Pay::SelfPromo::JournalPromo->check_current_promo; - LJ::Pay::SelfPromo::CommunityPromo->check_current_promo; + # Iterate through all slots available + for my $region qw(ua cyr) { + for my $type qw(entry journal community) { + my $slot = LJ::Pay::PromotionSlot->find_slot(class => PROMO_SELF, type => $type, region => $region); + LJ::Pay::SelfPromo->check_current_promotion($slot) if $slot; + } + } }; 1; Modified: trunk/cgi-bin/LJ/Console/Command/SelfPromo.pm =================================================================== --- trunk/cgi-bin/LJ/Console/Command/SelfPromo.pm 2012-04-26 11:49:02 UTC (rev 11814) +++ trunk/cgi-bin/LJ/Console/Command/SelfPromo.pm 2012-04-26 11:51:17 UTC (rev 11815) @@ -5,6 +5,7 @@ use base qw( LJ::Console::Command ); use LJ::Pay::SelfPromo; +use LJ::Pay::PromotionSlot qw(:classes); sub cmd {'selfpromo'} @@ -45,24 +46,24 @@ my $object = $args{'object'}; my $refund = $args{'refund'}; my $reason = $args{'reason'}; + my $slot = $args{'slot'}; - my $class = LJ::Pay::SelfPromo->get_class_by_type($type); - # refund defaults to 1 $refund = 1 unless defined $refund; my $admin = LJ::get_remote(); - my $message = 'admin cancel: ' . ($type eq 'entry' ? $object->url : $object->display_name); + my $message = 'admin cancel: ' . $object->name; $message .= ', without refund' unless $refund; $message .= '. Reason: '. $reason if $reason; - LJ::statushistory_add( $type eq 'entry' ? $object->poster : $object, $admin, 'selfpromo', $message ); + LJ::statushistory_add( $object->owner, $admin, 'selfpromo', $message ); - $class->admin_cancel_promo( - 'object' => $object, - 'admin' => $admin, - 'refund' => $refund, + $slot->admin_cancel_promo( + 'slot' => $slot, + 'object' => $object, + 'admin' => $admin, + 'refund' => $refund, ); } @@ -85,21 +86,18 @@ # either it's an entry URL, or a username, or something unknown my $type; my $object_orig = $object; - if ( $object =~ m{^http://} ) { - $object = LJ::Entry->new_from_url($object); - $type = 'entry'; - unless ( $object && $object->valid ) { + + $object = LJ::Pay::PromotedObject->new_from_url($object); + unless ($object) { + if ( $object_orig =~ m{^http://} ) { return $self->error("No entry found at $object_orig"); - } - } elsif ( $object =~ /^[a-z0-9\-_]+$/i ) { - $object = LJ::load_user($object); - $type = 'user'; - unless ($object) { + } elsif ( $object_orig =~ /^[a-z0-9\-_]+$/i ) { return $self->error("No such user $object_orig"); + } else { + return $self->error("Invalid object $object_orig passed"); } - } else { - return $self->error("Invalid object $object passed"); } + $type = $object->type; my ($norefund, $reason) = 0; if (@args_remainder) { @@ -117,51 +115,53 @@ return $self->error("Insufficient arguments"); } + my $country = 'cyr'; # TODO + + if ( $cmd eq 'cancel' ) { if ( $type eq 'user' ) { return $self->error('"cancel" requires personal journal or community') unless ($object->is_community || $object->is_personal); $type = $object->is_community ? 'community' : 'journal'; } - my $class = LJ::Pay::SelfPromo->get_class_by_type($type); - $class->check_current_promo; - my $current_promo = $class->current_promo_info; + my $slot = LJ::Pay::PromotionSlot->find_slot(class => PROMO_SELF(), type => $type, country => $country); + $slot->check_current_promotion; + my $current_promotion = $slot->current_promotion; # use Data::Dumper; # return $self->error("Type: $type". Dumper($current_promo)); - unless ($current_promo) { + unless ($current_promotion) { return $self->error("No $type currently promoted"); } - unless ($current_promo->is_promoting($object)) + unless ($current_promotion->is_promoting($object)) { return $self->error( $object_orig . " is not the $type currently promoted" ); } - $self->_cancel_object( 'type' => $type, 'object' => $object, 'refund' => !$norefund, 'reason' => $reason ); + $self->_cancel_object( 'slot' => $slot, 'type' => $type, 'object' => $object, 'refund' => !$norefund, 'reason' => $reason ); $self->info( $object_orig . ' cancelled successfully' ); return 1; } if ( $cmd eq 'ban' ) { - if ( $object->isa('LJ::User') ) { - my $u = $object; - $type = $object->is_community ? 'community' : 'journal'; - + if ( $type ne PROMO_OBJECT_TYPE_ENTRY() ) { + + my $u = $object->object; # LJ::User + # check all current promotions foreach my $ptype qw(entry journal community) { + my $slot = LJ::Pay::PromotionSlot->find_slot(class => PROMO_SELF(), type => $ptype, country => $country); + $slot->check_current_promotion; + my $current_promo = $slot->current_promotion(); - my $class = LJ::Pay::SelfPromo->get_class_by_type($ptype); - $class->check_current_promo; - my $current_promo = $class->current_promo_info(); - if ( $current_promo && ( $current_promo->owner->equals($u) || $current_promo->journal->equals($u) || $current_promo->promoter->equals($u)) ) { $self->info('The ban will affect the currently promoted '. $ptype . ', so cancelling that.'); my $current_object = $current_promo->object(); - $self->_cancel_object( 'type' => $ptype, 'object' => $current_object, 'reason' => $reason ); + $self->_cancel_object( 'slot' => $slot, 'type' => $ptype, 'object' => $current_object, 'reason' => $reason ); } } @@ -172,18 +172,18 @@ return 1; } - if ( $object->isa('LJ::Entry') ) { - my $entry = $object; + if ( $type eq PROMO_OBJECT_TYPE_ENTRY() ) { + my $entry = $object->object; + my $slot = LJ::Pay::PromotionSlot->find_slot(class => PROMO_SELF(), type => $type); - my $class = LJ::Pay::SelfPromo->get_class_by_type($type); - $class->check_current_promo; - my $current_promo = $class->current_promo_info(); + $slot->check_current_promotion; + my $current_promo = $slot->current_promotion(); - if ( $current_promo && $current_promo->is_promoting($entry) ) + if ( $current_promo && $current_promo->is_promoting($object) ) { $self->info('The ban will affect the currently promoted ' . 'entry, so cancelling that.'); - $self->_cancel_object( 'type' => $type, 'object' => $entry, 'reason' => $reason ); + $self->_cancel_object( 'slot' => $slot, 'type' => $type, 'object' => $object, 'reason' => $reason ); } $entry->set_prop( 'selfpromo_banned' => 1 ); @@ -197,15 +197,15 @@ } if ( $cmd eq 'unban' ) { - if ( $object->isa('LJ::User') ) { - $object->clear_prop('selfpromo_banned'); - LJ::statushistory_add( $object, $admin, 'selfpromo', 'admin unbanned user. Reason: '. $reason ); - $self->info( $object->display_name . ' unbanned successfully.'); + if ( $object->type ne PROMO_OBJECT_TYPE_ENTRY() ) { + my $user = $object->object; + $user->clear_prop('selfpromo_banned'); + LJ::statushistory_add( $user, $admin, 'selfpromo', 'admin unbanned user. Reason: '. $reason ); + $self->info( $user->display_name . ' unbanned successfully.'); return 1; - } - - if ( $object->isa('LJ::Entry') ) { - $object->set_prop( 'selfpromo_banned' => undef ); + } else { # entry + my $entry = $object->object; + $entry->set_prop( 'selfpromo_banned' => undef ); LJ::statushistory_add( $object->journal, $admin, 'selfpromo', 'admin unbanned entry: '. $object->url .'. Reason: '. $reason ); $self->info( $object->url . ' unbanned successfully.'); return 1; Modified: trunk/cgi-bin/LJ/Pay/Payment/PayItem/SelfPromo.pm =================================================================== --- trunk/cgi-bin/LJ/Pay/Payment/PayItem/SelfPromo.pm 2012-04-26 11:49:02 UTC (rev 11814) +++ trunk/cgi-bin/LJ/Pay/Payment/PayItem/SelfPromo.pm 2012-04-26 11:51:17 UTC (rev 11815) @@ -2,7 +2,7 @@ use strict; use warnings; -use LJ::Pay::SelfPromo; +use LJ::Pay::PromotedObject; use base qw( LJ::Pay::Payment::PayItem ); @@ -20,28 +20,13 @@ # return LJ::Entry->new( $journalid, 'ditemid' => $ditemid ); #} - -# TODO (Refactoring): Incapsulate all that logic in PromoObject object + add constructor to create Promo object from the cart item SelfPromo + use methods of Promo object - -sub view_ua_ratings { - my $remote = shift; - my $view_ua_ratings = 0; - if (LJ::is_enabled('personal_stats_ua')) { - my $country = LJ::GeoLocation->get_country_info_by_ip; - $country = $remote->prop('country') if $remote && $country ne 'UA'; - if ($country eq 'UA') { - my $view_ua_ratings_cookie = LJ::Request->cookie('view_ua_ratings')||'Y'; - my $view_ua_ratings_prop = $remote ? ($remote->prop('view_ua_ratings')||'Y') : 'N'; - $view_ua_ratings = 1 if $view_ua_ratings_cookie eq 'Y' || $view_ua_ratings_prop eq 'Y'; - } - } - return $view_ua_ratings; -} - sub get_object { - #return LJ::Pay::SelfPromo::Promo->create_from_item($self); my ($self) = @_; + $self->{promoted_object} ||= LJ::Pay::PromotedObject->new_from_cart_item($self); + return $self->{promoted_object}; + # TOREMOVE: obsolete + my $sp_type = $self->get_prop('selfpromo'); my $journalid = $self->get_prop('selfpromo_journalid'); @@ -59,9 +44,10 @@ sub get_object_url { my ($self) = @_; + $self->{promoted_object} ||= LJ::Pay::PromotedObject->new_from_cart_item($self); + return $self->{promoted_object}->object_url; - #my $promotion_object = LJ::Pay::SelfPromo::Promo->create_from_item($self); - # return $promo_object->url; + # TOREMOVE: obsolete my $object = $self->get_object; return unless $object; @@ -70,11 +56,11 @@ } sub get_object_owner { - # return $promo_object->owner; my ($self) = @_; + $self->{promoted_object} ||= LJ::Pay::PromotedObject->new_from_cart_item($self); #cache object creation + return $self->{promoted_object}->owner; - #my $promotion_object = LJ::Pay::Promotion->new_from_item($self); #cache object creation - # return $promo_object->owner; + # TOREMOVE: obsolete my $object = $self->get_object; return unless $object; Modified: trunk/cgi-bin/LJ/Pay/SelfPromo.pm =================================================================== --- trunk/cgi-bin/LJ/Pay/SelfPromo.pm 2012-04-26 11:49:02 UTC (rev 11814) +++ trunk/cgi-bin/LJ/Pay/SelfPromo.pm 2012-04-26 11:51:17 UTC (rev 11815) @@ -4,14 +4,21 @@ use LJ::Pay::Wallet; use LJ::Pay::Wallet::Error qw(:codes); +use LJ::Pay::PromotionSlot qw(:classes); +use LJ::Pay::PromotedObject qw(:types); +use LJ::Pay::Promotion; use LJ::Pay::SelfPromo::History; -use LJ::Pay::SelfPromo::EntryPromo; -use LJ::Pay::SelfPromo::JournalPromo; -use LJ::Pay::SelfPromo::CommunityPromo; -use LJ::Pay::SelfPromo::EntryPromoUA; -use LJ::Pay::SelfPromo::JournalPromoUA; -use LJ::Pay::SelfPromo::CommunityPromoUA; +#use LJ::Pay::SelfPromo::Lock; +#use LJ::Pay::SelfPromo::Error; + +#use LJ::Pay::SelfPromo::EntryPromo; +#use LJ::Pay::SelfPromo::JournalPromo; +#use LJ::Pay::SelfPromo::CommunityPromo; +#use LJ::Pay::SelfPromo::EntryPromoUA; +#use LJ::Pay::SelfPromo::JournalPromoUA; +#use LJ::Pay::SelfPromo::CommunityPromoUA; + use Sys::Hostname qw(); use Exporter; @@ -55,26 +62,34 @@ 'journal' => 'journal', ); -sub type { 'entry' } -sub class { 'entry' }; # TODO: remove that function from base SelfPromo class -sub country {''} +sub old_type { 'entry' } +sub old_class { 'entry' }; # TODO: remove that function from base SelfPromo class +sub old_country {''} + +sub class { PROMO_SELF() } -sub get_class_by_type { +sub old_get_class_by_type { my ($class, $type, $country) = @_; $country ||= 'cyr'; return $classes_map{$country}{$type}; } -sub get_object_by_type { +sub old_get_object_by_type { my ($class, $type) = @_; return $objects_map{$type}; } ### HIGH LEVEL INTERFACE ### # NOTE: ALL FUNCTIONS HERE HAVE TO PERFORM EXCLUSIVE MODE OPERATIONS !# - -# TODO: specify promo_type : pass slot object sub buyout_cost { + my ($class, $slot) = @_; + if ( my $promotion = $class->current_promotion( $slot, 'require_db' => 1 ) ) { + return $promotion->buyout_cost; + } + return $slot->config->{'min_cost'}; # return minimal price +} + +sub old_buyout_cost { my ($class) = @_; if ( my $promo = $class->current_promo_info( undef, 'require_db' => 1 ) ) { # changed to current_promo_info @@ -101,6 +116,26 @@ =cut sub is_object_eligible { + my ($class, $slot, $promoted_object, $promoter, $reason_ref) = @_; + my $type = $promoted_object->type; + + unless ($type) { + $$reason_ref = 'unknown_object'; + } + + unless ($slot->type eq $type) { + $$reason_ref = 'mismatch_type'; + return; + } + + return unless $promoted_object->is_eligible($promoter, $reason_ref); + + # TODO: Ensure that object is not promoting at the moment in all promos!!!! + + return 1; +} + +sub old_is_object_eligible { my ($class, $object, $promoter, $reason_ref) = @_; my $type = ref $object; @@ -137,7 +172,167 @@ 8. Update history =cut + sub buyout { + my ( $class, $slot, $promoted_object, $promoter, $price ) = @_; + + + die "NO SLOT" unless $slot; + + # Acquire the lock to process buyout exclusive! + # If lock is not acquired during TIMEOUT period, system wide default value 10s, then exception raised. + + my $lock = $class->lock($slot); # actually use the lock specific for that class, lock() + + $class->raise_error( ERROR_BIDS_HAVE_CHANGED ) unless $lock; + + ## check object that we are going to promote + do { + my $reason; + unless ( $class->is_object_eligible( $slot, $promoted_object, $promoter, \$reason ) ) { + $class->raise_error( ERROR_OBJECT_INELIGIBLE, $reason ); + # TODO: fix error messages, make it class specific + } + }; + + ## check price + do { + my $price_additional = $price - $slot->config->{'min_cost'}; + if ( $price_additional % $slot->config->{'step'} ) { + $class->raise_error(ERROR_INVALID_PRICE); + } + + my $min_price = $class->buyout_cost($slot, 'require_db' => 1 ); + unless ( $price >= $min_price ) { + LJ::Pay::SelfPromo->debug_msg( + "not enough money offered, need at least $min_price"); + + $class->raise_error( ERROR_PRICE_INSUFFICIENT, $min_price ); + } + + my $wallet_balance = int LJ::Pay::Wallet->get_user_balance($promoter); + unless ( $wallet_balance >= $price ) { + LJ::Pay::SelfPromo->debug_msg( + "promoter doesn't actually have that much " . + "(has $wallet_balance, tried to buy out for $price)" + ); + + $class->raise_error( ERROR_WALLET_INSUFFICIENT_FUNDS, + $wallet_balance ); + } + }; + + # create a cart and pay for it from the user's balance; + # this may throw a wallet exception + my $cart; + my $current_promotion; + do { + + LJ::Pay::SelfPromo->debug_msg("creating the cart"); + + # calculate how much money goes where for this item: + # a part of it goes to the system, a part of it goes to the + # user who purchased the selfpromo that was running before the + # buyout, and a part of it is a rounding error that + # goes to a special account made for these purposes + my ( $refund, $refund_promoid, $refund_userid ) = ( 0, 0, 0 ); + if ( $current_promotion = $class->current_promotion( $slot, 'require_db' => 1 ) ) { + $refund = $current_promotion->calculate_refund(); + $refund_promoid = $current_promotion->promoid; + $refund_userid = $current_promotion->promoterid; + } + + my $refund_remainder = + $refund % $slot->config->{'refund_step'}; + my $refund_rounded = $refund - $refund_remainder; + my $profit = $price - $refund; + + my $remainder_to = + LJ::load_user( $slot->config->{'remainder_receiver'} ); + my $remainder_userid = $remainder_to ? $remainder_to->userid : 0; + + $cart = $class->create_shop_cart( + { + 'slot' => $slot, + 'cart_owner' => $promoter, + 'object' => $promoted_object, + 'price' => $price, + 'profit' => $profit, + 'rcptid' => $promoter->userid, + 'refund' => $refund_rounded, + 'refund_userid' => $refund_userid, + 'refund_promoid' => $refund_promoid, + 'remainder' => $refund_remainder, + 'remainder_userid' => $remainder_userid, + 'type' => 'buyout', + } + ); + + LJ::Pay::SelfPromo->debug_msg("cart created, setting the method"); + + # Take the money from the promoter + $cart->set_method( LJ::Pay::Method::Wallet->code ); + + LJ::Pay::SelfPromo->debug_msg("paying for the cart"); + LJ::Pay::Wallet->pay_for_cart( $cart->get_payid ); + LJ::Pay::SelfPromo->debug_msg("paid successfully"); + # TODO: Catch exceptions from wallet operations!!! + + }; + + # deliver the cart synchronously, so that changes are applied + # immediately; we can do that because you can't schedule + # a selfpromo + # + # this part isn't supposed to throw any exceptions because + # all the checks have been done before + + # NOTES: + # 1. There might not be enought LJ tokens in the user's wallet, at the moment when transaction begins. + # 2. + + do { + $class->debug_msg("delivering the cart/items"); + $class->debug_msg( "payid=" . $cart->get_payid ); + + my ($item) = $cart->get_items; + + LJ::Pay::SelfPromo->debug_msg( "piid=" . $item->get_piid ); + + $cart->update( 'used' => 'Y' ); + $item->update( 'status' => 'pend' ); + + # Perform deliver: deactivate previous selfpromo object / return money to the previous promoter / create new promotion object + + $class->_deliver_item($slot, $item, $promoter, $current_promotion, $promoted_object); + + LJ::Pay::SelfPromo->debug_msg( + "cart and item updated to be ready for delivery"); + + local $SIG{'__WARN__'} = sub { + my @messages = @_; + $class->debug_msg( "delivery warning: ", @messages ); + warn @messages; + }; + + # Mark all delivered by calling deliver method + my $deliver_res = $item->deliver( $cart, time, sub { } ); + unless ($deliver_res) { + $class->debug_msg("delivery failed"); + die "delivering item failed, see error log for details"; + } + $cart->mark_complete; + }; + + LJ::Pay::SelfPromo->debug_msg("all done"); + + # Update history ??? + LJ::Pay::SelfPromo::History->update(); + + return $cart; +} + +sub old_buyout { my ( $class, $object, $promoter, $price ) = @_; # Acquire the lock to process buyout exclusive! @@ -292,9 +487,203 @@ =item _deliver_item - Deliver cart item + Actually perform all specific actions to cancel/refuns previous promo + Parameters: + $slot, $item, $rcpt_u, $current_promotion, $promoted_object =cut sub _deliver_item { + my ($class, $slot, $item, $rcpt_u, $current_promotion, $promoted_object) = @_; + + LJ::Pay::SelfPromo->debug_msg("delivery of a selfpromo item requested"); + LJ::Pay::SelfPromo->debug_msg( "piid=" . $item->get_piid ); + + # this should be temporary until we l10n the shop + my $lang = ($rcpt_u && $rcpt_u->prop('browselang')) ? $rcpt_u->prop('browselang') : $LJ::DEFAULT_LANG; + my $ml = sub { + my ( $code, $params ) = @_; + return LJ::Lang::get_text( $lang, $code, undef, $params ); + }; + + my $object_type = $slot->type; + + my $type = $item->get_prop('selfpromo_type'); # action type + + LJ::Pay::SelfPromo->debug_msg( "TEST" ); + + LJ::Pay::SelfPromo->debug_msg("type = $type, object_type = $object_type, class = $class"); + # if there's an entry being promoted, deactivate it, and + # then handle any refund if need be + # + # note that it also stays valid for admin or poster cancellations because they + # also only cancel the current promotion + my $object_url; + if ( $current_promotion ) { + LJ::Pay::SelfPromo->debug_msg("current PROMO received"); + $object_url = $current_promotion->object_url; + my $object = $current_promotion->promoted_object; + $current_promotion->deactivate( + 'reason' => 'buyout', + 'details' => $item->get_piid, + ); + # Refund some tokens to previous promoter + send notifications + if ( my $refund_userid = $item->get_prop('selfpromo_refund_userid') ) { + my $refund_to = LJ::load_userid($refund_userid); + my $refund = $item->get_prop('selfpromo_refund'); + if ($refund) { + LJ::Pay::SelfPromo->debug_msg("crediting them"); + + # credit them: + LJ::Pay::Wallet->try_add( $refund_to, $refund ); + LJ::Pay::Wallet::Log->log( + 'userid' => $refund_userid, + 'action' => LJ::Pay::Wallet::Log::ACTION_ADD, + 'qty' => $refund, + 'payid' => $item->get_payid, + 'piid' => $item->get_piid, + 'status' => LJ::Pay::Wallet::Log::STATUS_FINISHED, + 'time_end' => time, + ); + + LJ::Pay::SelfPromo->debug_msg("successfully credited"); + } + + my $reason_map = { + 'buyout' => 'buyout', + 'admin_cancel' => 'admin', + 'owner_cancel' => 'owner', + }; + + # email them: + $class->send_notification( + $slot, $refund_to, 'deactivate', + 'reason' => $reason_map->{$type}, + 'object_url' => $object_url, + 'object' => $object, + 'owner' => $current_promotion->owner, + 'refund_amount' => $refund, + 'duration' => time - $current_promotion->start_time, + ); + + unless ( LJ::u_equals($current_promotion->owner, $refund_to) ) { + $class->send_notification( + $slot, $current_promotion->owner, 'deactivate', + 'reason' => $reason_map->{$type}, + 'object_url' => $object_url, + 'object' => $object, + 'refund_amount' => $refund, + 'duration' => time - $current_promotion->start_time, + 'notify_owner' => 1, + ); + } + LJ::Pay::SelfPromo->debug_msg("refund successful"); + } + } + + # if there's any 'remainder' tokens we need to send, go send them: + if ( my $remainder = $item->get_prop('selfpromo_remainder') ) { + my $remainder_to_userid = + $item->get_prop('selfpromo_remainder_userid'); + my $remainder_to = LJ::load_userid($remainder_to_userid); + + if ($remainder_to) { + LJ::Pay::SelfPromo->debug_msg( + "sending the remainder of tokens to " . + $remainder_to->username ); + + LJ::Pay::Wallet->try_add( $remainder_to, $remainder ); + LJ::Pay::Wallet::Log->log( + 'userid' => $remainder_to_userid, + 'action' => LJ::Pay::Wallet::Log::ACTION_ADD, + 'qty' => $remainder, + 'payid' => $item->get_payid, + 'piid' => $item->get_piid, + 'status' => LJ::Pay::Wallet::Log::STATUS_FINISHED, + 'time_end' => time, + ); + } + + } + + # now that we've handled all the deactivations, refunds, and remainders, + # we will need to determine if we need to activate anything, + # and what to email, which really depends on the item type + my ( $email_subject, $email_body ); + + if ( $type eq 'buyout' ) { + + my $object = $promoted_object; + my $owner = $promoted_object->owner; + my $object_url = $promoted_object->url; # New promo object URL + + my $cost = $item->get_amt * LJ::Pay::Wallet::EXCHANGE_RATE(); + + my $new_promotion = LJ::Pay::Promotion::Auction->create_active($slot, $promoted_object, $cost, $rcpt_u); + + $item->set_subitem(join('-', ($slot->slot_id, $new_promotion->promoid))); + $item->save(); + + $email_subject = $ml->("selfpromo.$object_type.notification.activate.subject"); + + LJ::Pay::SelfPromo->debug_msg("generating the emails"); + # Need to send second email if object owner and recipient are different + unless ( LJ::u_equals($owner, $rcpt_u) ) { + $email_body = $ml->( + "selfpromo.$object_type.notification.activate.body", + { 'user' => $owner->display_name, + 'object_url' => $object_url, + }, + ); + + LJ::send_mail( + { 'to' => $owner->email_raw, + 'from' => $LJ::DONOTREPLY_EMAIL, + 'fromname' => $LJ::SITENAME, + 'subject' => $email_subject, + 'body' => $email_body, + } + ); + } + + $email_body = $ml->( + "selfpromo.$object_type.notification.activate.body", + { 'user' => $rcpt_u->display_name, + 'object_url' => $object_url, + }, + ); + } + elsif ( $type eq 'admin_cancel' ) { + + # we don't need to activate anything, but the admin + # gets notified that everything was cancelled + # successfully + + $email_subject = $ml->("selfpromo.$object_type.notification.admin_cancel.subject"); + + $email_body = $ml->( + "selfpromo.$object_type.notification.admin_cancel.body", + { 'user' => $rcpt_u->display_name, + 'object_url' => $current_promotion ? $object_url : '', + }, + ); + } + + if ($email_subject && $email_body) { + LJ::send_mail( + { 'to' => $rcpt_u->email_raw, + 'from' => $LJ::ACCOUNTS_EMAIL, + 'fromname' => $LJ::SITENAMESHORT, + 'subject' => $email_subject, + 'body' => $email_body, + 'wrap' => 1, + 'charset' => 'utf-8', + } + ); + } + + LJ::Pay::SelfPromo->debug_msg("delivery successful"); +} + +sub _old_deliver_item { my ($class, $item, $rcpt_u) = @_; LJ::Pay::SelfPromo->debug_msg("delivery of a selfpromo item requested"); @@ -480,7 +869,6 @@ ); } - if ($email_subject && $email_body) { LJ::send_mail( { 'to' => $rcpt_u->email_raw, @@ -496,13 +884,104 @@ LJ::Pay::SelfPromo->debug_msg("delivery successful"); } - + =item admin_cancel_promo Admin function to cancel current promo =cut sub admin_cancel_promo { my ( $class, %args ) = @_; + my $promoted_object = $args{'object'}; + my $admin = $args{'admin'}; + my $slot = $args{'slot'}; + + # TODO: Acquire lock to perform cancel action! + my $lock = $class->lock($slot); + # TODO: Perform lock timeout processing + + my $promotion = $class->current_promotion($slot, 'require_db' => 1 ); + unless ( $promotion && $promotion->is_promoting($promoted_object) ) + { + # your princess is in another castle, sorry + # TODO: return an error + return; + } + + # create a cart and set it as free, and then mark it as pending + # delivery + my $cart; + do { + + # calculate how much money goes where for this item: + # none it goes to the system, a part of it goes to the + # user who purchased the selfpromo being cancelled, + # and a part of it is a rounding error that goes to a + # special account made for these purposes + my $refund = 0; + if ( $args{'refund'} ) { + $refund = $promotion->calculate_refund(); + } + + my $refund_promoid = $promotion->promoid; + my $refund_userid = $promotion->promoterid; + + my $refund_remainder = + $refund % $slot->config->{'refund_step'}; + my $refund_rounded = $refund - $refund_remainder; + + my $remainder_to = + LJ::load_user( $slot->config->{'remainder_receiver'} ); + my $remainder_userid = $remainder_to ? $remainder_to->userid : 0; + + $cart = $class->create_shop_cart( + { + 'slot' => $slot, + 'cart_owner' => $admin, + 'object' => undef, + 'price' => 0, + 'profit' => 0, + 'rcptid' => $admin->userid, + 'refund' => $refund_rounded, + 'refund_userid' => $refund_userid, + 'refund_promoid' => $refund_promoid, + 'remainder' => $refund_remainder, + 'remainder_userid' => $remainder_userid, + 'type' => 'admin_cancel', + } + ); + + # mark this cart as paid for + $cart->set_method( LJ::Pay::Method::Free->code ); + $cart->update( 'used' => 'N', 'mailed' => 'N' ); + $cart->set_daterecv_epoch(time); + }; + + # deliver the cart synchronously, so that changes are applied + # immediately; we can do that because you can't schedule + # a selfpromo + # + # this part isn't supposed to throw any exceptions because + # all the checks have been done before + + do { + my ($item) = $cart->get_items; + + $cart->update( 'used' => 'Y' ); + $item->update( 'status' => 'pend' ); + + $class->_deliver_item($slot, $item, $admin, $promotion, undef); + + $item->deliver( $item->get_cart, time, sub { } ); + + $cart->mark_complete(); + }; + + return 1; +} + +sub old_admin_cancel_promo { + my ( $class, %args ) = @_; + my $object = $args{'object'}; my $admin = $args{'admin'}; @@ -592,8 +1071,80 @@ } # owner of the promoted object cancel promotion with refunding to the promotera -# TODO: Move to LOW LEVEL interface +# obsolete sub owner_cancel_promo { # the name of the function has been changed from poster_cancel_promo + my ( $class, $slot, $promoted_object ) = @_; + + return unless $promoted_object; + + # TODO: Acquire lock to perform cancelling! + my $lock = $class->lock($slot); + # TODO: Perform error processing when lock couldn't be acquired during default timeout, 10s + + my $promotion = $class->current_promotion( $slot, 'require_db' => 1 ); + use Data::Dumper; + + # warn "CP:".Dumper($promotion); + + unless ( $promotion && $promotion->is_promoting($promoted_object) ) + { + # your princess is in another castle, sorry + # TODO: return error + die "ERRR"; + return; + } + + my $owner = $promotion->owner; + + my $cart; + my $refund = $promotion->calculate_refund(); + my $promoter = $promotion->promoter; + + my $refund_remainder = + $refund % $slot->config->{'refund_step'}; + + my $refund_rounded = $refund - $refund_remainder; + + my $remainder_to = + LJ::load_user( $slot->config->{'remainder_receiver'} ); + my $remainder_userid = $remainder_to ? $remainder_to->userid : 0; + + $cart = $class->create_shop_cart( + { + 'slot' => $slot, + 'cart_owner' => $owner, + 'object' => undef, + 'price' => 0, + 'profit' => 0, + 'rcptid' => $owner->userid, + 'refund' => $refund_rounded, + 'refund_userid' => $promoter->userid, + 'refund_promoid' => $promotion->promoid, + 'remainder' => $refund_remainder, + 'remainder_userid' => $remainder_userid, + 'type' => 'owner_cancel', + } + ); + + $cart->set_method( LJ::Pay::Method::Free->code ); + $cart->update( 'used' => 'N', 'mailed' => 'N' ); + $cart->set_daterecv_epoch(time); + + my ($item) = $cart->get_items; + + $cart->update( 'used' => 'Y' ); + $item->update( 'status' => 'pend' ); + + $class->_deliver_item($slot, $item, $owner, $promotion, undef); + + $item->deliver( $item->get_cart, time, sub { } ); + + $cart->mark_complete; + + return 1; +} + +sub old_owner_cancel_promo { # the name of the function has been changed from poster_cancel_promo my ( $class, $object ) = @_; return unless $object; @@ -666,8 +1217,86 @@ # REFACTORING: # 1. Pass slot # 2. +sub withdraw_object { + my ( $class, $slot, $promoted_object ) = @_; + + # TODO? : Acquire lock to perform cancelling operation on selfpromo + # TODO: Add errors processing in case of not acquiring lock during default timeout. + my $lock = $class->lock($slot); + + # So we are actually here in case we've got lock successfully!!! + + my $promotion = $class->current_promotion( $slot, 'require_db' => 1 ); + return unless $promotion; # do nothing & return undef in case of -sub withdraw_object { + my $owner = $promotion->owner; # get the owner if the cucrrently promoted object + my $promoter = $promotion->promoter; # get the promoter object + my $object_url = $promotion->object_url; + + # TODO??? : return undef is better? + # TODO: Here better raise common error + die 'promoted object mismatch' + unless $promotion && $promotion->is_promoting($promoted_object); + + unless ( LJ::u_equals($owner, $promoter) ) { + + my $remote = LJ::get_remote(); # TODO: To remove dependency from remote user !!! + + # Owner cancels promo, need to pay refund to promoter + unless ( LJ::u_equals($remote, $promoter) ) { + # Req: $owner == $remote + # Notifications will be sent via shop + # TODO: ensure $owner == $remote , it's not obvious from conditions, it has to be concluded! +# return $class->owner_cancel_promo($object); + + # send notifications to the promoter (owner cancelled) + $class->send_notification( + $slot, $promoter, 'deactivate', + 'reason' => 'owner', + 'object_url' => $object_url, + 'object' => $promoted_object, + 'owner' => $owner, + 'duration' => time - $promotion->start_time, + ); + + # send notification to the owner itself (owner cancelled) + $class->send_notification( + $slot, $promotion->owner, 'deactivate', + 'reason' => 'owner', + 'object_url' => $object_url, + 'object' => $promoted_object, + 'duration' => time - $promotion->start_time, + 'notify_owner' => 1, + ); + + $promotion->deactivate( 'reason' => 'withdraw' ); + return 1; + + } else { + $class->send_notification( # send notification to the owner of the promoted object + $slot, $owner, 'deactivate', + 'reason' => 'withdraw', + 'object_url' => $object_url, + 'duration' => $promotion->stop_time - $promotion->start_time, + 'notify_owner' => 1, + ); + } + } + + # Send notification to promoter + $class->send_notification( + $slot, $promoter, 'deactivate', + 'reason' => 'withdraw', + 'object_url' => $object_url, + 'duration' => $promotion->stop_time - $promotion->start_time, + ); + + $promotion->deactivate( 'reason' => 'withdraw' ); + + return 1; +} + +sub old_withdraw_object { my ( $class, $object ) = @_; # TODO? : Acquire lock to perform cancelling operation on selfpromo @@ -767,6 +1396,13 @@ # return current promoted object: # So it might be: entry / journal / community sub current_promoted_object { + my ($class, $slot, %opts) = @_; + my $promotion = $class->current_promotion($slot, %opts); + return unless $promotion; + return $promotion->promoted_object; +} + +sub old_current_promoted_object { my ($class, %opts) = @_; my $promo = $class->current_promo_info(undef, %opts); return unless $promo; @@ -777,7 +1413,98 @@ # deactivate it: depending on cases #sub check_current_entry { # Called from worker -sub check_current_promo { + +sub check_current_promotion { + my ($class, $slot) = @_; + + # Acquire lock to exclusive change promotion status + # TODO: Perform error processing in case we can't acquire the lock during default timeout. + my $lock = $class->lock($slot); + # return unless $lock; + + my $promotion = $class->current_promotion( $slot, 'require_db' => 1 ); + return unless $promotion; # everything's fine if nothing's promoted + + my $promoted_object = $promotion->promoted_object; # promoted object: entry / user(community/journal) + my $owner = $promotion->owner; # owner of the promoted object: user + my $promoter = $promotion->promoter; # promoter: user + my $object_url = $promotion->object_url; + + if ($promotion->is_object_deleted) { + + # we no longer have an entry because it's been deleted, + # so let's reconstruct the URL => get it from promo object + + $promotion->deactivate( 'reason' => 'deleted' ); + $class->send_notification( + $slot, $promoter, 'deactivate', + 'reason' => 'deleted', + 'object_url' => $object_url, + 'duration' => time - $promotion->start_time, + ); + + unless ( LJ::u_equals($owner, $promoter) ) { + $class->send_notification( + $slot, $owner, 'deactivate', + 'reason' => 'deleted', + 'object_url' => $object_url, + 'duration' => time - $promotion->start_time, + 'notify_owner' => 1, + ); + } + } + + my $reason; + + unless ( $promoted_object->is_eligible( $promoter, \$reason ) ) { + $promotion->deactivate( + 'reason' => 'ineligible', + 'details' => $reason, + ); + + $class->send_notification( + $slot, $promoter, 'deactivate', + 'reason' => 'ineligible', + 'details' => $reason, + 'object_url' => $object_url, + 'duration' => time - $promotion->start_time, + ); + + unless ( LJ::u_equals($owner, $promoter) ) { + $class->send_notification( + $slot, $owner, 'deactivate', + 'reason' => 'ineligible', + 'details' => $reason, + 'object_url' => $object_url, + 'duration' => time - $promotion->start_time, + 'notify_owner' => 1, + ); + } + } + + unless ( $promotion->stop_time > time ) { + $promotion->deactivate( 'reason' => 'expired' ); + + $class->send_notification( + $slot, $promoter, 'deactivate', + 'reason' => 'expired', + 'object_url' => $object_url, + 'duration' => time - $promotion->start_time, + ); + + unless ( LJ::u_equals($owner, $promoter) ) { + $class->send_notification( + $slot, $owner, 'deactivate', + 'reason' => 'expired', + 'object_url' => $object_url, + 'duration' => time - $promotion->start_time, + 'notify_owner' => 1, + ); + } + } +} + +sub old_check_current_promo { my ($class) = @_; # Acquire lock to exclusive change promotion status @@ -892,95 +1619,63 @@ } } -sub find_promos { - my ( $class, $params ) = @_; +### LOW LEVEL INTERFACE ### +# BEWARE NOTE: All functions here don't perform exclusive operations, no locks and mutexes! + +=item current_promotion + return current promotion objct +=cut +sub current_promotion { + my ($class, $slot, %opts) = @_; - my @where = (1); - my @binds; - my ( $sort_cond, $limit_cond, $offset_cond ) = ( '', '', '' ); + # warn "current_promotion"; + die "NO SLOT" unless($slot); - # Find only one class of promos - push @where, 'type = ?'; - push @binds, $class->type; + my $promoid = $opts{'promoid'}; - push @where, 'country = ?'; - push @binds, $class->country; + unless ($promoid) { + my $from_memcache; - if ( my $started_min = delete $params->{'started_min'} ) { - push @where, 'started > ?'; - push @binds, $started_min; - } + unless ( $opts{'require_db'} ) { + $from_memcache = LJ::MemCache::get( $slot->memcache_active_key ); + } - if ( my $finished_min = delete $params->{'finished_min'} ) { - push @where, '(finished > ?'. (delete $params->{'unfinished'}? ' OR active = 1' : ''). ')'; - push @binds, $finished_min; - } + # from_memcache = undef means there's nothing in memcache + # (or it's disabled in the options), so we need to hit DB; + # from_memcache = 0 means 'no current entry'; + # from_memcache = HASH(0xDEADBEEF) means bless it and return + if ( defined $from_memcache ) { + return unless $from_memcache; + return LJ::Pay::Promotion::Auction->new_from_row($from_memcache); + } + my $rows = LJ::Pay::Promotion->find_promotions( # perform select for class specific rows + { 'slot' => $slot, 'require_active' => 1, 'return_rows' => 1 } ); - if ( my $started_max = delete $params->{'started_max'} ) { - push @where, 'started < ?'; - push @binds, $started_max; - } + unless ($rows && @$rows) { + LJ::MemCache::set( $slot->memcache_active_key, 0, 1800 ); + return; + } - if ( my $promoid = delete $params->{'promoid'} ) { - push @where, 'promoid = ?'; - push @binds, $promoid; - } + unless ( @$rows == 1 ) { + warn 'more than 1 entry in selfpromo, data corrupt?'; + } - if ( my $promoid_max = delete $params->{'promoid_max'} ) { - push @where, 'promoid < ?'; - push @binds, $promoid_max; + my $row = $rows->[0]; + LJ::MemCache::set( $slot->memcache_active_key, $row, 1800 ); + return LJ::Pay::Promotion::Auction->new_from_row($row); } - if ( my $posterid = delete $params->{'posterid'} ) { - push @where, 'posterid = ?'; - push @binds, $posterid; - } + return do { + my $promos = LJ::Pay::Promotion->find_promotions( { 'slot' => $slot, 'promoid' => $promoid } ); - if ( delete $params->{'require_active'} ) { - push @where, 'active = 1'; - } + return unless @$promos; + return $promos->[0]; + }; - if ( my $sort = delete $params->{'sort'} ) { - $sort_cond = "ORDER BY $sort"; - } - - if ( my $limit = delete $params->{'limit'} ) { - $limit_cond = "LIMIT $limit"; - } - - if ( my $offset = delete $params->{'offset'} ) { - $offset_cond = "OFFSET $offset"; - } - - if ( my $ditemid = delete $params->{'ditemid'} ) { - push @where, 'ditemid = ?'; - push @binds, $ditemid; - } - my $where_sql = join( ' AND ', @where ); - - my $dbh = LJ::get_db_writer(); - my $rows = $dbh->selectall_arrayref( - "SELECT * FROM selfpromo WHERE $where_sql $sort_cond $limit_cond $offset_cond", - { 'Slice' => {} }, @binds, - ); - - return $rows if $params->{'return_rows'}; - - my $promos = [ map { LJ::Pay::SelfPromo::Promo->new($_) } @$rows ]; - return $promos; } -### LOW LEVEL INTERFACE ### -# BEWARE NOTE: All functions here don't perform exclusive operations, no locks and mutexes! - -#sub check_object { -# my ($class, $object) = @_; -# return undef unless UNIVERSAL::isa($entry, 'LJ::Entry'); -# return 1; -#} - # return object LJ::Pay::SelfPromo::Promo, make subclasses? -sub current_promo_info { +sub old_current_promo_info { my ($class, $promoid, %opts) = @_; unless ($promoid) { @@ -1032,9 +1727,9 @@ # * it doesn't send any notifications # # called from LJ::Pay::Payment::PayItem::SelfPromo -# Actually creates new selfpromo object / active row in DB - -sub activate_object { + +# Actually creates new selfpromo object / active row in DBa +sub old_activate_object { my ( $class, $object, $cost, $promoter ) = @_; # ????: Before activate the new object, check current promo???? gain lock ??? @@ -1098,12 +1793,10 @@ ); } - # supported opts: reason, details, object, refund # # called from LJ::Pay::Payment::PayItem::SelfPromo - -sub deactivate_object { +sub old_deactivate_object { my ( $class, %opts ) = @_; my $promo = $class->current_promo_info(undef, 'require_db' => 1); @@ -1133,9 +1826,10 @@ ); } + # supported opts: reason, details, object, refund # TODO: we have no specify promoid -sub deactivate_promo { +sub old_deactivate_promo { my ( $class, $promoid, $promoter, %opts ) = @_; $class->db_update_promo( $promoid, 'active' => 0, 'finished' => time ); @@ -1164,7 +1858,7 @@ # * refund_promoid # * remainder # * remainder_userid -# * entry +# * object sub create_shop_cart { my ( $class, $opts ) = @_; @@ -1173,6 +1867,60 @@ my $cart = LJ::Pay::Payment::new_cart( $opts->{'cart_owner'} ); + $cart->payvar_append('do_not_notify_recepient', 1); + + my $it = LJ::Pay::Payment::PayItem->new_memonly( + 'item' => 'selfpromo', + 'subitem' => '', + 'amt' => $opts->{'price'} / LJ::Pay::Wallet::EXCHANGE_RATE, + 'qty' => 1, + 'anon' => 0, + 'rcptid' => $opts->{'rcptid'}, + ); + + $cart->add_item(%$it); + + ($it) = $cart->get_items; + + # set payitem props as appropriate + $it->set_prop( 'selfpromo' => $opts->{'slot'}->type ); + $it->set_prop( 'selfpromo_type' => $opts->{'type'} ); + $it->set_prop( 'selfpromo_profit' => $opts->{'profit'} ); + $it->set_prop( 'selfpromo_refund' => $opts->{'refund'} ); + $it->set_prop( 'selfpromo_refund_userid' => $opts->{'refund_userid'} ); + $it->set_prop( 'selfpromo_refund_promoid' => $opts->{'refund_promoid'} ); + $it->set_prop( 'selfpromo_remainder' => $opts->{'remainder'} ); + $it->set_prop( + 'selfpromo_remainder_userid' => $opts->{'remainder_userid'} ); + + # Add object specific properties, if object exists (in case of non refunding cart) + if ( my $object = $opts->{'object'} ) { + + my $ownerid = $object->ownerid; + $it->set_prop( 'selfpromo_journalid' => $ownerid ); + + if ( $object->type eq PROMO_OBJECT_TYPE_ENTRY() ) { + $it->set_prop( 'selfpromo_ditemid' => $object->ditemid ); + } + } + + LJ::Pay::SelfPromo->debug_msg( + "create_shop_cart successful, " . + "payid=" . $cart->get_payid . ", " . + "piid=" . $it->get_piid + ); + + return $cart; +} + +sub old_create_shop_cart { + my ( $class, $opts ) = @_; + + LJ::Pay::SelfPromo->debug_msg( + "create_shop_cart called: " . LJ::compact_dumper($opts) ); + + my $cart = LJ::Pay::Payment::new_cart( $opts->{'cart_owner'} ); + $cart->payvar_append('do_not_notify_recepient',1); my $it = LJ::Pay::Payment::PayItem->new_memonly( @@ -1224,6 +1972,91 @@ # TODO (Refactoring): Pass PromoObject instead of %opts and use methods sub send_notification { + my ( $class, $slot, $user, $action, %opts ) = @_; + + my ( $ml_var, %ml_params ); + my $sp_class = $slot->type(); # type of objects promoted in that slot + + if ( $action eq 'deactivate' ) { + my $reason = $opts{'reason'}; + my $object = $opts{'object'}; # note that it may be undefined, especially for entries!!!! + + %ml_params = ( + 'refund_amount' => $opts{'refund_amount'}, + 'object_url' => $opts{'object_url'}, + 'duration_hours' => int( $opts{'duration'} / 3600 ), + 'duration_min' => int( ( $opts{'duration'} % 3600 ) / 60 ), + 'user' => $user->display_name, + ); + + if ( $reason eq 'admin' ) { + if ( $opts{'notify_owner'} ) { + $ml_var = "selfpromo.$sp_class.notification.deactivate.admin3"; + } else { + $ml_var = "selfpromo.$sp_class.notification.deactivate.admin2"; + unless ( $opts{'refund_amount'} > 0 ) { + $ml_var .= '.norefund'; + } + $ml_params{'owner'} = $opts{'owner'} ? $opts{'owner'}->display_name : ''; + } + } + + if ( $reason eq 'buyout' ) { + if ( $opts{'notify_owner'} ) { + $ml_var = "selfpromo.$sp_class.notification.deactivate.buyout3"; + } else { + $ml_var = "selfpromo.$sp_class.notification.deactivate.buyout2"; + $ml_params{'owner'} = $opts{'owner'} ? $opts{'owner'}->display_name : ''; + } + } + + if ( $reason eq 'deleted' ) { + $ml_var = "selfpromo.$sp_class.notification.deactivate.deleted"; + } + + if ( $reason eq 'expired' ) { + $ml_var = "selfpromo.$sp_class.notification.deactivate.expired"; + } + + if ( $reason eq 'ineligible' ) { + my $details = $opts{'details'}; + $ml_var = "selfpromo.$sp_class.notification.deactivate.ineligible.$details"; + } + + if ( $reason eq 'withdraw' ) { + $ml_var = "selfpromo.$sp_class.notification.deactivate.withdraw"; + } + + if ( $reason eq 'owner' ) { + if ( $opts{'notify_owner'} ) { + $ml_var = "selfpromo.$sp_class.notification.deactivate.withdraw"; + } else { + $ml_var = "selfpromo.$sp_class.notification.deactivate.owner"; + $ml_params{'promoter'} = $ml_params{'user'}; + $ml_params{'owner'} = $opts{'owner'} ? $opts{'owner'}->display_name : ''; + } + } + } + + my $lang = $user->prop('browselang') || $LJ::DEFAULT_LANG; + my $subject = + LJ::Lang::get_text( $lang, "$ml_var.subject", undef, + \%ml_params, ); + + my $body = LJ::Lang::get_text( $lang, "$ml_var.body", undef, + \%ml_params, ); + + LJ::send_mail( + { 'to' => $user->email_raw, + 'from' => $LJ::DONOTREPLY_EMAIL, + 'fromname' => $LJ::SITENAME, + 'subject' => $subject, + 'body' => $body, + } + ); +} + +sub old_send_notification { my ( $class, $user, $action, %opts ) = @_; my ( $ml_var, %ml_params ); @@ -1314,7 +2147,20 @@ ); } -sub calculate_refund { +# TODO: Candidate to move it to PromotionSlot +sub was_promoted { + my ($class, $slot, $object) = @_; + return undef unless ($object->type eq PROMO_OBJECT_TYPE_ENTRY); + return LJ::MemCache::get_or_set( + $slot->was_promoted_key($object->ownerid, $object->ditemid), sub { + my $res = LJ::Pay::Promotion->find_promotions({ slot => $slot, ditemid => $object->ditemid, posterid => $object->ownerid }); + return @$res? 1 : 0; + }, 7200 + ); +} + +# Moved to LJ::Pay::Promotion::Auction +sub old_calculate_refund { my ( $class, $promo ) = @_; my $unit = $LJ::SELF_PROMO_CONF->{$class->class}{'refund_time_unit'}; @@ -1332,7 +2178,8 @@ return $refund_amount; } -sub db_insert_promo { +# Moved to LJ::Pay::Promotion::Auction +sub old_db_insert_promo { my ( $class, %args ) = @_; my $dbh = LJ::get_db_writer(); @@ -1349,8 +2196,8 @@ $dbh->do( "INSERT INTO selfpromo SET $sets", undef, @binds ); } - -sub db_update_promo { +# Moved to LJ::Pay::Promotion::Auction +sub old_db_update_promo { my ( $class, $promoid, %args ) = @_; my $dbh = LJ::get_db_writer(); @@ -1366,38 +2213,53 @@ undef, @binds, $promoid, $class->type, $class->country); } -sub clear_memcache { +sub old_clear_memcache { my ($class, $ownerid, $objectid) = @_; LJ::MemCache::delete( $class->memcache_key ); LJ::MemCache::delete( $class->was_promoted_key($ownerid, $objectid) ) if $ownerid and $objectid; } -sub memcache_key { +sub old_memcache_key { my $self = shift; return 'self_promo_'.$self->class().$self->country().'_active'; } -sub was_promoted_key { 'self_promo_'.$_[0]->class().$_[0]->country().'_ids_promoted:'. join ':', $_[1], $_[2] } +sub old_was_promoted_key { 'self_promo_'.$_[0]->class().$_[0]->country().'_ids_promoted:'. join ':', $_[1], $_[2] } + sub raise_error { my ( $class, @args ) = @_; LJ::Pay::SelfPromo::Error->raise(@args); } sub lock { + my ($class, $slot) = @_; + return LJ::Pay::SelfPromo::Lock->new($slot ? $slot->slot_id : $class->class()); +} + +sub old_lock { return LJ::Pay::SelfPromo::Lock->new(shift->class()); } sub lock_taken { + my ($class, $slot) = @_; + return LJ::Pay::SelfPromo::Lock->taken($slot ? $slot->slot_id : $class->class()); +} + +sub old_lock_taken { return LJ::Pay::SelfPromo::Lock->taken(shift->class()); } sub lock_taken_elsewhere { + my ($class, $slot) = @_; + return LJ::Pay::SelfPromo::Lock->taken_elsewhere($slot ? $slot->slot_id : $class->class()); +} + +sub old_lock_taken_elsewhere { return LJ::Pay::SelfPromo::Lock->taken_elsewhere(shift->class()); } -# supported opts: object_url, event, reason, details -# TODO: Add specific common messages for all types of selfpromo -sub log { +# supported opts: object_url, event, reason, detailsh# TODO: Add specific common messages for all types of selfpromo +sub old_log { my ( $class, $promoter, %opts ) = @_; my $message = ''; @@ -1431,95 +2293,6 @@ warn "$msg in sub ${package}::${sub} at $filename line $line\n"; } -# TODO(2nd refactoring stage): Roadway: Incapsulate here more logic from all other parts (look through all TODO remarks in the code) -# 1. Incapsulate all construction methods here. -# 2. Incapsulate promo history storage logic here. -# 3. Add object state -package LJ::Pay::SelfPromo::Promo; - -use base qw( Class::Accessor ); - -__PACKAGE__->mk_accessors( - qw( - promoid journalid posterid jitemid ditemid - started exptime cost active finished promoterid type - ) -); - -sub new { - my ( $class, $data ) = @_; - return bless $data, $class; -} - -sub journal { - my ($self) = @_; - return LJ::load_userid( $self->journalid ); -} - -# TODO: Entry specific -sub poster { - my ($self) = @_; - return LJ::load_userid( $self->posterid ); -} - -sub ownerid { - my ($self) = @_; - if ($self->type eq 'community') { - my $super_maintainers = LJ::load_rel_user_cache($self->journalid, 'S') || LJ::load_rel_user_cache($self->journalid, 'A') || []; - return $self->journalid unless scalar(@$super_maintainers); - return shift @$super_maintainers; # appoximation in case of absence the supermaintainer in community - } else { - return ($self->type eq 'entry' ? $self->posterid : $self->journalid); - } -} - -# owner of the promoted object: -# if entry that poster -# if community/journal that use journal/community user -sub owner { - my ($self) = @_; - return LJ::load_userid( $self->ownerid ); -} - -sub promoter { - my ($self) = @_; - # TODO: remove posterid !!! - return LJ::load_userid( $self->promoterid || $self->posterid ); -} - -sub object { - my ($self) = @_; - return ( $self->type eq 'entry' ? LJ::Entry->new( $self->journalid, 'jitemid' => $self->jitemid ) : LJ::load_userid( $self->journalid )); -} - -sub is_object_deleted { - my ($self) = @_; - return ( $self->type eq 'entry' ? ! $self->object : ! ($self->object && $self->object->is_visible) ); -} - -# TODO: Entry specific!!!! Incapsulate -sub entry { - my ($self) = @_; - return LJ::Entry->new( $self->journalid, 'jitemid' => $self->jitemid ); -} - -# NOTE: Always return object URL, -# ??? But if journal/community deleted? -sub object_url { - my ($self) = @_; - return ( $self->type eq 'entry' ? $self->journal->journal_base . '/' . $self->ditemid . '.html' : $self->journal->journal_base ); -} - -=item is_promoting - Ensure that promotion contains the specified object - In fact that is comparison OP -=cut -sub is_promoting { - my ($self, $object) = @_; - return $self->type eq 'entry' ? $object && $object->isa('LJ::Entry') && $self->journalid == $object->... (truncated)