Committer: vtroitsky
LJSUP-10937: Entries Selfpromo extended to Journals and CommunitiesU trunk/bin/maint/selfpromo.pl U trunk/bin/upgrading/update-db-local.pl 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/htdocs/shop/selfpromo.bml.text.local U trunk/templates/Shop/SelfPromo.tmpl U trunk/templates/Shop/SelfPromoPreview.tmpl
Modified: trunk/bin/maint/selfpromo.pl =================================================================== --- trunk/bin/maint/selfpromo.pl 2012-01-31 05:43:25 UTC (rev 11368) +++ trunk/bin/maint/selfpromo.pl 2012-01-31 06:15:04 UTC (rev 11369) @@ -7,7 +7,7 @@ use LJ::Pay::SelfPromo; $maint{'selfpromo_check'} = sub { - LJ::Pay::SelfPromo->check_current_entry; + LJ::Pay::SelfPromo::EntryPromo->check_current_promo; }; 1; Modified: trunk/bin/upgrading/update-db-local.pl =================================================================== --- trunk/bin/upgrading/update-db-local.pl 2012-01-31 05:43:25 UTC (rev 11368) +++ trunk/bin/upgrading/update-db-local.pl 2012-01-31 06:15:04 UTC (rev 11369) @@ -1723,6 +1723,7 @@ `cost` int(11) NOT NULL default '0', `active` int(11) NOT NULL default '1', `finished` int(11) NOT NULL default '0', + `type` enum('E','J','C') NOT NULL default 'E', PRIMARY KEY (`promoid`) ) ENGINE=InnoDB }); @@ -2268,6 +2269,13 @@ ); } + unless (column_type('selfpromo', 'type')) { + do_alter( + 'selfpromo', + "ALTER TABLE selfpromo ADD COLUMN type enum('E','J','C') NOT NULL DEFAULT 'E'", + ); + } + unless (column_type("userapps", "userhead")) { do_alter("userapps", qq{ ALTER TABLE userapps Modified: trunk/cgi-bin/LJ/Pay/Payment/PayItem/SelfPromo.pm =================================================================== --- trunk/cgi-bin/LJ/Pay/Payment/PayItem/SelfPromo.pm 2012-01-31 05:43:25 UTC (rev 11368) +++ trunk/cgi-bin/LJ/Pay/Payment/PayItem/SelfPromo.pm 2012-01-31 06:15:04 UTC (rev 11369) @@ -8,35 +8,83 @@ sub item {'selfpromo'} -sub get_entry { +#sub get_entry { +# my ($self) = @_; +# +# my $sp_class = $self->get_prop('selfpromo'); +# my $journalid = $self->get_prop('selfpromo_journalid'); +# my $ditemid = $self->get_prop('selfpromo_ditemid'); +# +# return unless $journalid && $ditemid; +# +# 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 get_object { + #return LJ::Pay::SelfPromo::Promo->create_from_item($self); my ($self) = @_; + my $sp_class = $self->get_prop('selfpromo'); my $journalid = $self->get_prop('selfpromo_journalid'); - my $ditemid = $self->get_prop('selfpromo_ditemid'); - return unless $journalid && $ditemid; - - return LJ::Entry->new( $journalid, 'ditemid' => $ditemid ); + # print "journalid: $journalid\n"; + if ($sp_class eq 'entry') { + my $ditemid = $self->get_prop('selfpromo_ditemid'); + # print "ditemid: $ditemid\n"; + return unless $journalid && $ditemid; + return LJ::Entry->new( $journalid, 'ditemid' => $ditemid ); + } else { + return LJ::load_userid($journalid); + } + return; } -sub get_entry_url { +sub get_object_url { my ($self) = @_; - my $journalid = $self->get_prop('selfpromo_journalid'); - my $ditemid = $self->get_prop('selfpromo_ditemid'); + #my $promo_object = LJ::Pay::SelfPromo::Promo->create_from_item($self); + # return $promo_object->url; - return unless $journalid && $ditemid; + my $object = $self->get_object; + return unless $object; + my $sp_class = $self->get_prop('selfpromo'); + return ($sp_class eq 'entry' ? $object->url : $object->journal_base); +} - my $journal = LJ::load_userid($journalid); +sub get_object_owner { + # return $promo_object->owner; + my ($self) = @_; - return $journal->journal_base . "/$ditemid.html"; + #my $promo_object = LJ::Pay::SelfPromo::Promo->create_from_item($self); + # return $promo_object->url; + + my $object = $self->get_object; + return unless $object; + my $sp_class = $self->get_prop('selfpromo'); + return ($sp_class eq 'entry' ? $object->poster : $sp_class eq 'community' ? $object : $object); } +#sub get_entry_url { +# my ($self) = @_; +# +# my $journalid = $self->get_prop('selfpromo_journalid'); +# my $ditemid = $self->get_prop('selfpromo_ditemid'); +# +# return unless $journalid && $ditemid; +# +# my $journal = LJ::load_userid($journalid); +# +# return $journal->journal_base . "/$ditemid.html"; +#} + sub get_product_name { my ($self) = @_; return LJ::Lang::ml( 'selfpromo.shop_item.name', - { 'entry_url' => $self->get_entry_url } ); + { 'object_url' => $self->get_object_url } ); } sub render_cart_item { @@ -73,6 +121,8 @@ LJ::Pay::SelfPromo->debug_msg("delivery of a selfpromo item requested"); LJ::Pay::SelfPromo->debug_msg( "piid=" . $self->get_piid ); +# TODO: Refactoring: Move most of that code have to be refactored more and to be moved into the LJ/Pay/SelfPromo* modules!, just put here necesasary functions calls + # if we cannot get a lock straight away, let this be delivered later; # resolves race condition with ljmaint.pl and webs trying to deliver # the same item simultaneously @@ -89,47 +139,46 @@ return LJ::Lang::get_text( $lang, $code, undef, $params ); }; + + my $class_type = $self->get_prop('selfpromo'); + my $class = LJ::Pay::SelfPromo->get_class_by_type($class_type); + LJ::Pay::SelfPromo->debug_msg("acquiring the lock"); - my $lock = LJ::Pay::SelfPromo->lock; + my $lock = $class->lock; LJ::Pay::SelfPromo->debug_msg("got the lock"); LJ::Pay::SelfPromo->debug_msg("checking the current entry"); - LJ::Pay::SelfPromo->check_current_entry; + $class->check_current_promo; LJ::Pay::SelfPromo->debug_msg("current entry checked"); my $type = $self->get_prop('selfpromo_type'); - LJ::Pay::SelfPromo->debug_msg("item type: $type"); # 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 $entry_promoted = LJ::Pay::SelfPromo->current_entry( - 'require_db' => 1 ); + my $promo; + my $object_url; + if ($promo = $class->current_promo_info(undef, 'require_db' => 1)) { + $object_url = $promo->object_url; + my $object = $promo->object; - if ($entry_promoted) { - LJ::Pay::SelfPromo->debug_msg( - "cancelling the current entry" . $entry_promoted->url ); - - LJ::Pay::SelfPromo->deactivate_entry( - 'entry' => $entry_promoted, + $class->deactivate_object( + 'object' => $object, + 'object_url' => $object_url, 'reason' => 'buyout', 'details' => $self->get_piid, ); + # Refund some tokens to previous promoter + send notifications if ( my $refund_userid = $self->get_prop('selfpromo_refund_userid') ) { - LJ::Pay::SelfPromo->debug_msg( - "issuing a refund to userid=$refund_userid"); - my $promoid = $self->get_prop('selfpromo_refund_promoid'); - LJ::Pay::SelfPromo->debug_msg("refund promoid=$promoid"); + # my $promoid = $self->get_prop('selfpromo_refund_promoid'); my $refund_to = LJ::load_userid($refund_userid); - LJ::Pay::SelfPromo->debug_msg( - "refund goes to user=" . $refund_to->username ); - my $promo = LJ::Pay::SelfPromo->current_entry_info($promoid); + # my $promo = LJ::Pay::SelfPromo->current_entry_info($promoid); my $refund = $self->get_prop('selfpromo_refund'); if ($refund) { @@ -150,36 +199,34 @@ LJ::Pay::SelfPromo->debug_msg("successfully credited"); } - my $entry_url = $promo->entry_url; + # $object_url = $promo->entry_url; my $reason_map = { 'buyout' => 'buyout', 'admin_cancel' => 'admin', - 'poster_cancel' => 'poster', + 'owner_cancel' => 'owner', }; - LJ::Pay::SelfPromo->debug_msg( - "emailing them that someone bought the place out"); - # email them: - LJ::Pay::SelfPromo->send_notification( + $class->send_notification( $refund_to, 'deactivate', 'reason' => $reason_map->{$type}, - 'entry_url' => $entry_url, - 'entry' => $promo->entry, + 'object_url' => $object_url, + 'object' => $object, + 'owner' => $promo->owner, 'refund_amount' => $refund, 'duration' => time - $promo->started, ); - unless ( LJ::u_equals($promo->poster, $refund_to) ) { - LJ::Pay::SelfPromo->send_notification( - $promo->poster, 'deactivate', + unless ( LJ::u_equals($promo->owner, $refund_to) ) { + $class->send_notification( + $promo->owner, 'deactivate', 'reason' => $reason_map->{$type}, - 'entry_url' => $entry_url, - 'entry' => $promo->entry, + 'object_url' => $object_url, + 'object' => $object, 'refund_amount' => $refund, 'duration' => time - $promo->started, - 'notify_poster' => 1, + 'notify_owner' => 1, ); } @@ -218,31 +265,28 @@ my ( $email_subject, $email_body ); if ( $type eq 'buyout' ) { - my $entry = $self->get_entry; - my $poster = $entry->poster; - - LJ::Pay::SelfPromo->debug_msg( - "setting the new active entry: " . $entry->url ); - + # my $entry = $self->get_entry; + my $object = $self->get_object; + my $owner = $self->get_object_owner; + my $object_url = $self->get_object_url; # New promo object URL + my $cost = $self->get_amt * LJ::Pay::Wallet::EXCHANGE_RATE(); - LJ::Pay::SelfPromo->activate_entry( $entry, $cost, $rcpt_u ); - - LJ::Pay::SelfPromo->debug_msg("entry activated successfully"); - - $email_subject = $ml->('selfpromo.notification.activate.subject'); - + $class->activate_object( $object, $cost, $rcpt_u ); + + $email_subject = $ml->("selfpromo.$class_type.notification.activate.subject"); + LJ::Pay::SelfPromo->debug_msg("generating the emails"); # Need to send second email if entry poster and recipient are different - unless ( LJ::u_equals($poster, $rcpt_u) ) { + unless ( LJ::u_equals($owner, $rcpt_u) ) { $email_body = $ml->( - 'selfpromo.notification.activate.body', - { 'poster' => $poster->display_name, - 'entry_url' => $entry->url, + "selfpromo.$class_type.notification.activate.body", + { 'owner' => $owner->display_name, + 'object_url' => $object_url, }, ); LJ::send_mail( - { 'to' => $poster->email_raw, + { 'to' => $owner->email_raw, 'from' => $LJ::DONOTREPLY_EMAIL, 'fromname' => $LJ::SITENAME, 'subject' => $email_subject, @@ -252,9 +296,9 @@ } $email_body = $ml->( - 'selfpromo.notification.activate.body', - { 'poster' => $rcpt_u->display_name, - 'entry_url' => $entry->url, + "selfpromo.$class_type.notification.activate.body", + { 'owner' => $rcpt_u->display_name, + 'object_url' => $object_url, }, ); } @@ -264,12 +308,12 @@ # gets notified that everything was cancelled # successfully - $email_subject = $ml->('selfpromo.notification.admin_cancel.subject'); + $email_subject = $ml->("selfpromo.$class_type.notification.admin_cancel.subject"); $email_body = $ml->( - 'selfpromo.notification.admin_cancel.body', + "selfpromo.$class_type.notification.admin_cancel.body", { 'admin' => $rcpt_u->display_name, - 'entry_url' => $entry_promoted ? $entry_promoted->url : '', + 'object_url' => $promo ? $object_url : '', }, ); } Modified: trunk/cgi-bin/LJ/Pay/SelfPromo.pm =================================================================== --- trunk/cgi-bin/LJ/Pay/SelfPromo.pm 2012-01-31 05:43:25 UTC (rev 11368) +++ trunk/cgi-bin/LJ/Pay/SelfPromo.pm 2012-01-31 06:15:04 UTC (rev 11369) @@ -5,6 +5,9 @@ use LJ::Pay::Wallet; use LJ::Pay::Wallet::Error qw(:codes); use LJ::Pay::SelfPromo::History; +use LJ::Pay::SelfPromo::EntryPromo; +use LJ::Pay::SelfPromo::JournalPromo; +use LJ::Pay::SelfPromo::CommunityPromo; use Sys::Hostname qw(); @@ -15,10 +18,12 @@ BEGIN { $errors = { - ERROR_ENTRY_INELIGIBLE => 1, + ERROR_OBJECT_INELIGIBLE => 1, ERROR_PRICE_INSUFFICIENT => 2, ERROR_INVALID_PRICE => 3, ERROR_WALLET_INSUFFICIENT_FUNDS => 4, + ERROR_BIDS_HAVE_CHANGED => 5, + ERROR_PROMO_HAS_CHANGED => 6, }; our @EXPORT_OK = ( keys %$errors ); @@ -28,131 +33,108 @@ use constant $errors; +my %classes_map = ( + 'entry' => 'LJ::Pay::SelfPromo::EntryPromo', + 'community' => 'LJ::Pay::SelfPromo::CommunityPromo', + 'journal' => 'LJ::Pay::SelfPromo::JournalPromo', +); + +my %objects_map = ( + 'entry' => 'E', + 'community' => 'C', + 'journal' => 'J', +); + +sub type { 'E' } +sub class { 'entry' }; # TODO: remove that function from base SelfPromo class + + +sub get_class_by_type { + my ($class, $type) = @_; + return $classes_map{$type}; +} + +sub 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 sub buyout_cost { - my ( $class, %opts ) = @_; - - if ( my $promo = $class->current_entry_info( undef, %opts ) ) { - + my ($class) = @_; + + if ( my $promo = $class->current_promo_info( undef, 'require_db' => 1 ) ) { # changed to current_promo_info + # there actually is a promoted entry right now, so let's # return however much its author paid for it plus the bidding step - - return $promo->cost + $LJ::SELF_PROMO_CONF->{'step'}; + + return $promo->cost + $LJ::SELF_PROMO_CONF->{$class->class}{'step'}; } - - return $LJ::SELF_PROMO_CONF->{'min_cost'}; + + return $LJ::SELF_PROMO_CONF->{$class->class}{'min_cost'}; } - -sub is_entry_eligible { - my ( $class, $entry, $promoter, $reason_ref ) = @_; - - my $journal = $entry->journal; - my $poster = $entry->poster; - - my $err = sub { - my ($reason) = @_; - - $$reason_ref = $reason; - return; - }; - - ## journal checks - unless ( $journal and $poster ) { return $err->('not_found') } - my $journal_adult = $journal->adult_content_calculated; - unless ( $journal->is_visible ) { return $err->('journal_invisible'); } - unless ( $journal_adult eq 'none' ) { - return $err->('journal_adult_content'); + + +=item is_object_eligible + Check that specified object is eligible to promote: + As object may be considered: entries and users (entries and communities) + Have to be overriden in childs +=cut +sub is_object_eligible { + my ($class, $object, $promoter, $reason_ref) = @_; + my $type = ref $object; + + unless ($type) { + $$reason_ref = 'unknown_object'; } - - ## community checks - my $need_promoter_check = 1; - unless ( $LJ::DISABLED{'community_selfpromo'} ) { - my $promo = $class->current_entry_info( undef, 'require_db' => 1 ); - my $entry_active = $promo && - LJ::u_equals($promo->promoter, $promoter) && - $promo->journalid == $entry->journalid && - $promo->jitemid == $entry->jitemid; - - if ( $journal->is_community and $promoter->can_manage($journal) || $entry_active ) { - $need_promoter_check = 0; + + if ($type eq 'LJ::Entry') { + # LJ::Entry promo + return $class->is_entry_eligible($object, $promoter, $reason_ref); + } elsif ($type eq 'LJ::User') { + # LJ::User promo + if ($object->is_community) { + return $class->is_community_eligible($object, $promoter, $reason_ref); + } else { + return $class->is_journal_eligible($object, $promoter, $reason_ref); } + } else { + $$reason_ref = 'unknown_object'; } - - ## poster checks - unless ( $poster->is_visible ) { return $err->('poster_invisible'); } - if ( $need_promoter_check ) { - unless ( LJ::u_equals( $poster, $promoter ) ) { - unless ( $LJ::DISABLED{'community_selfpromo'} ) { - return $err->('cannot_manage') if $journal->is_community; - } - - return $err->('promoter_poster_mismatch'); - } - } - - ## entry checks - my $entry_adult = $entry->adult_content_calculated; - unless ( $entry->is_visible ) { return $err->('entry_invisible'); } - unless ( $entry->is_public ) { return $err->('entry_not_public'); } - if ( $entry_adult && $entry_adult ne 'none' ) { - return $err->('entry_adult_content'); - } - - # bans; this is separate because poster needs to be checked before - # journal - if ( $poster->prop('selfpromo_banned') ) { - return $err->('poster_banned'); - } - if ( $journal->prop('selfpromo_banned') ) { - return $err->('journal_banned'); - } - if ( $entry->prop('selfpromo_banned') ) { - return $err->('entry_banned'); - } - - # TODO: check if this is the very entry being promoted right now? - - return 1; } - + +=item buyout + Function to make a bid: core logic is implemented here + TODO: remove specific logic to subfunctions +=cut sub buyout { - my ( $class, $entry, $promoter, $price ) = @_; + my ( $class, $object, $promoter, $price ) = @_; + + # Acquire the lock to process buyout exclusive! + # If lock is not acquired during TIMEOUT period, system wide default value 10s, then exception raised. + + # TODO: Translate exception to SelfPromo Error! + my $lock = $class->lock; # actually use the lock specific for that class, lock() - LJ::Pay::SelfPromo->debug_msg( - "buyout requested, " . - "entry => " . $entry->url . ", " . - "promoter => " . $promoter->username . ", " . - "price => $price" - ); + #$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( $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 - $LJ::SELF_PROMO_CONF->{$class->class()}{'min_cost'}; + if ( $price_additional % $LJ::SELF_PROMO_CONF->{$class->class()}{'step'} ) { + $class->raise_error(ERROR_INVALID_PRICE); + } - my $lock = $class->lock; - - LJ::Pay::SelfPromo->debug_msg("lock acquired successfully"); - - ## check entry - do { - my $reason; - LJ::Pay::SelfPromo->debug_msg("checking if the entry is eligible..."); - - unless ( $class->is_entry_eligible( $entry, $promoter, \$reason ) ) { - LJ::Pay::SelfPromo->debug_msg("no it's not: $reason"); - $class->raise_error( ERROR_ENTRY_INELIGIBLE, $reason ); - } - - LJ::Pay::SelfPromo->debug_msg("yes it is"); - }; - - ## check price - do { - LJ::Pay::SelfPromo->debug_msg("checking the price"); - - my $price_additional = $price - $LJ::SELF_PROMO_CONF->{'min_cost'}; - if ( $price_additional % $LJ::SELF_PROMO_CONF->{'step'} ) { - LJ::Pay::SelfPromo->debug_msg("price is invalid: $price"); - $class->raise_error(ERROR_INVALID_PRICE); - } - my $min_price = $class->buyout_cost( 'require_db' => 1 ); unless ( $price >= $min_price ) { LJ::Pay::SelfPromo->debug_msg( @@ -186,25 +168,25 @@ # 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 ); - my $promo = $class->current_entry_info( undef, 'require_db' => 1 ); - if ($promo) { + if ( my $promo = $class->current_promo_info( undef, 'require_db' => 1 ) ) { $refund = $class->calculate_refund($promo); $refund_promoid = $promo->promoid; - $refund_userid = $promo->promoterid || $promo->posterid; + $refund_userid = $promo->promoterid || $promo->posterid; # TODO: Have to remove posterid, implement it inside $promo object } my $refund_remainder = - $refund % $LJ::SELF_PROMO_CONF->{'refund_step'}; + $refund % $LJ::SELF_PROMO_CONF->{$class->class()}{'refund_step'}; my $refund_rounded = $refund - $refund_remainder; my $profit = $price - $refund; my $remainder_to = - LJ::load_user( $LJ::SELF_PROMO_CONF->{'remainder_receiver'} ); + LJ::load_user( $LJ::SELF_PROMO_CONF->{$class->class()}{'remainder_receiver'} ); my $remainder_userid = $remainder_to ? $remainder_to->userid : 0; $cart = $class->create_shop_cart( - { 'cart_owner' => $promoter, - 'entry' => $entry, + { + 'cart_owner' => $promoter, + 'object' => $object, 'price' => $price, 'profit' => $profit, 'rcptid' => $promoter->userid, @@ -219,11 +201,14 @@ 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 @@ -232,6 +217,11 @@ # # 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 { LJ::Pay::SelfPromo->debug_msg("delivering the cart/items"); LJ::Pay::SelfPromo->debug_msg( "payid=" . $cart->get_payid ); @@ -243,6 +233,8 @@ $cart->update( 'used' => 'Y' ); $item->update( 'status' => 'pend' ); + # Perform deliver: deactivate previous selfpromo object / return money to the previous promoter / create new promotion object + LJ::Pay::SelfPromo->debug_msg( "cart and item updated to be ready for delivery"); @@ -261,24 +253,33 @@ LJ::Pay::SelfPromo->debug_msg("all done"); + # Update history ??? LJ::Pay::SelfPromo::History->update(); return $cart; } - + +=item admin_cancel_promo + Admin function to cancel current promo +=cut sub admin_cancel_promo { my ( $class, %args ) = @_; - my $entry = $args{'entry'}; + my $object = $args{'object'}; my $admin = $args{'admin'}; - my $promo = $class->current_entry_info( undef, 'require_db' => 1 ); - unless ( $promo - && $promo->journalid == $entry->journalid - && $promo->jitemid == $entry->jitemid ) + # TODO: Acquire lock to perform cancel action! + my $lock = $class->lock; + # TODO: Perform lock timeout processing + + my $promo = $class->current_promo_info( undef, 'require_db' => 1 ); + unless ( $promo && $promo->is_promoting($object)) # TODO: Implement function to compare specified object with current promoted one +# && $promo->journalid == $entry->journalid +# && $promo->jitemid == $entry->jitemid ) { # your princess is in another castle, sorry + # TODO: return an error return; } @@ -298,20 +299,20 @@ } my $refund_promoid = $promo->promoid; - my $refund_userid = $promo->promoterid || $promo->posterid; + my $refund_userid = $promo->promoterid || $promo->posterid; # TODO: remote posterid inside $promo object my $refund_remainder = - $refund % $LJ::SELF_PROMO_CONF->{'refund_step'}; + $refund % $LJ::SELF_PROMO_CONF->{$class->class()}{'refund_step'}; my $refund_rounded = $refund - $refund_remainder; my $remainder_to = - LJ::load_user( $LJ::SELF_PROMO_CONF->{'remainder_receiver'} ); + LJ::load_user( $LJ::SELF_PROMO_CONF->{$class->class()}{'remainder_receiver'} ); my $remainder_userid = $remainder_to ? $remainder_to->userid : 0; $cart = $class->create_shop_cart( { 'cart_owner' => $admin, - 'entry' => undef, + 'object' => undef, 'price' => 0, 'profit' => 0, 'rcptid' => $admin->userid, @@ -336,6 +337,7 @@ # # this part isn't supposed to throw any exceptions because # all the checks have been done before + do { my ($item) = $cart->get_items; @@ -347,50 +349,57 @@ return 1; } -sub poster_cancel_promo { - my ( $class, $entry ) = @_; +# owner of the promoted object cancel promotion with refunding to the promotera +# TODO: Move to LOW LEVEL interface +sub owner_cancel_promo { # the name of the function has been changed from poster_cancel_promo + my ( $class, $object ) = @_; - return unless $entry; + return unless $object; - my $promo = $class->current_entry_info( undef, 'require_db' => 1 ); - my $journal = $entry->journal; - my $poster = $entry->poster; + # TODO: Acquire lock to perform cancelling! + # my $lock = $class->lock; + # TODO: Perform error processing when lock couldn't be acquired during default timeout, 10s - unless ( $promo - && $promo->journalid == $entry->journalid - && $promo->jitemid == $entry->jitemid ) + my $promo = $class->current_promo_info( undef, 'require_db' => 1 ); + + unless ( $promo && $promo->is_promoting($object) ) +# && $promo->journalid == $entry->journalid +# && $promo->jitemid == $entry->jitemid ) { # your princess is in another castle, sorry + # TODO: return error return; } + my $owner = $promo->owner; my $cart; my $refund = $class->calculate_refund($promo); my $promoter = $promo->promoter; my $refund_remainder = - $refund % $LJ::SELF_PROMO_CONF->{'refund_step'}; + $refund % $LJ::SELF_PROMO_CONF->{$class->class()}{'refund_step'}; + my $refund_rounded = $refund - $refund_remainder; my $remainder_to = - LJ::load_user( $LJ::SELF_PROMO_CONF->{'remainder_receiver'} ); + LJ::load_user( $LJ::SELF_PROMO_CONF->{$class->class()}{'remainder_receiver'} ); my $remainder_userid = $remainder_to ? $remainder_to->userid : 0; $cart = $class->create_shop_cart( { - 'cart_owner' => $poster, - 'entry' => undef, + 'cart_owner' => $owner, + 'object' => undef, 'price' => 0, 'profit' => 0, - 'rcptid' => $poster->userid, + 'rcptid' => $owner->userid, 'refund' => $refund_rounded, 'refund_userid' => $promoter->userid, 'refund_promoid' => $promo->promoid, 'remainder' => $refund_remainder, 'remainder_userid' => $remainder_userid, - 'type' => 'poster_cancel', + 'type' => 'owner_cancel', } ); @@ -406,109 +415,146 @@ return 1; } +# Remote / promoter itself cancel promotion without refunding +#sub withdraw_entry { +sub withdraw_object { + my ( $class, $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; -sub withdraw_entry { - my ( $class, $entry ) = @_; + # So we are actually here in case we've got lock successfully!!! - my $promo = $class->current_entry_info( undef, 'require_db' => 1 ); - my $journal = $entry->journal; - my $poster = $entry->poster; - my $promoter = $promo->promoter; + my $promo = $class->current_promo_info( undef, 'require_db' => 1 ); + return unless $promo; # do nothing & return undef in case of + + my $owner = $promo->owner; # get the owner if the cucrrently promoted object + my $promoter = $promo->promoter; # get the promoter object + my $object_url = $promo->object_url; + + # TODO??? : return undef is better? + # TODO: Here better raise common error + die 'promoted object mismatch' + unless $promo && $promo->is_promoting($object); +# $promo->journalid == $entry->journalid +# && $promo->jitemid == $entry->jitemid; + + unless ( LJ::u_equals($owner, $promoter) ) { - die 'entry mismatch' - unless $promo->journalid == $entry->journalid - && $promo->jitemid == $entry->jitemid; - - unless ( LJ::u_equals($poster, $promoter) ) { - my $remote = LJ::get_remote(); - - # Poster cancels promo, need to pay refund to promoter - unless ( LJ::u_equals($remote, $promoter) ) { + my $remote = LJ::get_remote(); # TODO: To remove dependency from remote user !!! + + # Poster cancels promo, need to pay refund to promoter + unless ( LJ::u_equals($remote, $promoter) ) { + # Req: $owner == $remote # Notifications will be sent via shop - return $class->poster_cancel_promo($entry) - } else { - $class->send_notification( - $poster, 'deactivate', - 'reason' => 'withdraw', - 'entry_url' => $entry->url, - 'duration' => $promo->exptime - $promo->started, - 'notify_poster' => 1, - ); - } - } - + # TODO: ensure $owner == $remote , it's not obvious from conditions, it has to be concluded! + return $class->owner_cancel_promo($object); + } else { + $class->send_notification( # send notification to the owner of the promoted object + $owner, 'deactivate', + 'reason' => 'withdraw', +# 'entry_url' => $entry->url, # It's object name actually + 'object_url' => $object_url, + 'duration' => $promo->exptime - $promo->started, +# 'notify_poster' => 1, + 'notify_owner' => 1, + ); + } + } + + # Send notification to promoter $class->send_notification( - $promoter, 'deactivate', - 'reason' => 'withdraw', - 'entry_url' => $entry->url, - 'duration' => $promo->exptime - $promo->started, + $promoter, 'deactivate', + 'reason' => 'withdraw', + 'object_url' => $object_url, +# 'entry_url' => $entry->url, + 'duration' => $promo->exptime - $promo->started, ); + + $class->deactivate_promo( + $promo->promoid, $promoter, + 'reason' => 'withdraw', +# 'entry' => $entry, +# 'entry_url' => $entry->url, + 'object' => $object, + 'object_url' => $object_url, + ); + + return 1; + } - $class->deactivate_promo( - $promo->promoid, $promoter, - 'reason' => 'withdraw', - 'entry' => $entry, - 'entry_url' => $entry->url, - ); - - return 1; -} - -sub current_entry { - my ( $class, %opts ) = @_; - - my $promo = $class->current_entry_info( undef, %opts ); +# return current promoted object: +# So it might be: entry / journal / community +sub current_promoted_object { + my ($class, %opts) = @_; + my $promo = $class->current_promo_info(undef, %opts); return unless $promo; - return $promo->entry; + return $promo->object; } - -sub check_current_entry { + +# Check that current promo can still be active +# deactivate it: depending on cases +#sub check_current_entry { +# Called from worker +sub check_current_promo { my ($class) = @_; - + + # 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; - - my $promo = $class->current_entry_info( undef, 'require_db' => 1 ); - my $entry = $class->current_entry( 'require_db' => 1 ); - + #return unless $lock; + + my $promo = $class->current_promo_info( undef, 'require_db' => 1 ); return unless $promo; # everything's fine if nothing's promoted - my $poster = $promo->poster; - my $promoter = $promo->promoter; + + my $object = $promo->object; # promoted object: entry / user(community/journal) + my $owner = $promo->owner; # owner of the promoted object: user + my $promoter = $promo->promoter; # promoter: user + my $object_url = $promo->object_url; - unless ($entry) { - + if ($promo->is_object_deleted) { + # we no longer have an entry because it's been deleted, - # so let's reconstruct the URL - my $entry_url = $promo->entry_url; - + # so let's reconstruct the URL => get it from promo object + $class->deactivate_promo( $promo->promoid, $promoter, 'reason' => 'deleted', - 'entry_url' => $entry_url, + # 'entry_url' => $entry_url, + 'object_url' => $object_url, + 'promo' => $promo, 'promoinfo' => $promo, ); - + $class->send_notification( $promoter, 'deactivate', 'reason' => 'deleted', - 'entry_url' => $entry->url, + # 'entry_url' => $entry->url, + 'object_url' => $object_url, 'duration' => time - $promo->started, ); - - unless ( LJ::u_equals($poster, $promoter) ) { + + unless ( LJ::u_equals($owner, $promoter) ) { $class->send_notification( - $poster, 'deactivate', + $owner, 'deactivate', 'reason' => 'deleted', - 'entry_url' => $entry->url, - 'duration' => time - $promo->started, - 'notify_poster' => 1, + # 'entry_url' => $entry->url, + 'object_url' => $object_url, + 'duration' => time - $promo->started, + # 'notify_poster' => 1, + 'notify_owner' => 1, ); } } + + my $reason; - my $reason; - unless ( $class->is_entry_eligible( $entry, $promoter, \$reason ) ) { - $class->deactivate_entry( - 'entry' => $entry, + unless ( $class->is_object_eligible( $object, $promoter, \$reason ) ) { + $class->deactivate_object( + # 'entry' => $entry, + 'object' => $object, + 'object_url' => $object_url, 'reason' => 'ineligible', 'details' => $reason, ); @@ -517,43 +563,50 @@ $promoter, 'deactivate', 'reason' => 'ineligible', 'details' => $reason, - 'entry' => $entry, - 'entry_url' => $entry->url, + # 'entry' => $entry, + # 'entry_url' => $entry->url, + 'object_url' => $object_url, 'duration' => time - $promo->started, ); - unless ( LJ::u_equals($poster, $promoter) ) { + unless ( LJ::u_equals($owner, $promoter) ) { $class->send_notification( - $poster, 'deactivate', + $owner, 'deactivate', 'reason' => 'ineligible', 'details' => $reason, - 'entry' => $entry, - 'entry_url' => $entry->url, + # 'entry' => $entry, + # 'entry_url' => $entry->url, + 'object_url' => $object_url, 'duration' => time - $promo->started, - 'notify_poster' => 1, + 'notify_owner' => 1, ); } } unless ( $promo->exptime > time ) { - $class->deactivate_entry( 'entry' => $entry, 'reason' => 'expired' ); + $class->deactivate_object( #'entry' => $entry, + 'object' => $object, + 'object_url' => $object_url, + 'reason' => 'expired' ); $class->send_notification( $promoter, 'deactivate', 'reason' => 'expired', - 'entry' => $entry, - 'entry_url' => $entry->url, + # 'entry' => $entry, + # 'entry_url' => $entry->url, + 'object_url' => $object_url, 'duration' => time - $promo->started, ); - unless ( LJ::u_equals($poster, $promoter) ) { + unless ( LJ::u_equals($owner, $promoter) ) { $class->send_notification( - $poster, 'deactivate', + $owner, 'deactivate', 'reason' => 'expired', - 'entry' => $entry, - 'entry_url' => $entry->url, + # 'entry' => $entry, + # 'entry_url' => $entry->url, + 'object_url' => $object_url, 'duration' => time - $promo->started, - 'notify_poster' => 1, + 'notify_owner' => 1, ); } } @@ -566,6 +619,10 @@ my @binds; my ( $sort_cond, $limit_cond, $offset_cond ) = ( '', '', '' ); + # Find only one class of promos + push @where, 'type = ?'; + push @binds, $class->type; + if ( my $started_min = delete $params->{'started_min'} ) { push @where, 'started > ?'; push @binds, $started_min; @@ -630,25 +687,18 @@ return $promos; } -sub was_promoted { - my ($class, $entry) = @_; - return undef unless UNIVERSAL::isa($entry, 'LJ::Entry'); - - return LJ::MemCache::get_or_set( - $class->was_promoted_key($entry->posterid, $entry->ditemid), sub { - my $res = $class->find_promos({ ditemid => $entry->ditemid, posterid => $entry->posterid }); - return @$res? 1 : 0; - }, 7200 - ); -} - ### 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; +#} -# !$promoid => currently promoted entry -# $promoid => $promotion with promoid=$promoid -# called from LJ::Pay::Payment::PayItem::SelfPromo -sub current_entry_info { - my ( $class, $promoid, %opts ) = @_; +# return object LJ::Pay::SelfPromo::Promo, make subclasses? +sub current_promo_info { + my ($class, $promoid, %opts) = @_; unless ($promoid) { my $from_memcache; @@ -665,7 +715,7 @@ return LJ::Pay::SelfPromo::Promo->new($from_memcache); } - my $rows = $class->find_promos( + my $rows = $class->find_promos( # perform select for class specific rows { 'require_active' => 1, 'return_rows' => 1 } ); unless (@$rows) { @@ -688,6 +738,7 @@ return unless @$promos; return $promos->[0]; }; + } # this one pretty much tries to satisfy the PayItem's needs: @@ -696,55 +747,91 @@ # * it doesn't send any notifications # # called from LJ::Pay::Payment::PayItem::SelfPromo -sub activate_entry { - my ( $class, $entry, $cost, $promoter ) = @_; +# Actually creates new selfpromo object / active row in DB + +sub activate_object { + my ( $class, $object, $cost, $promoter ) = @_; + + # ????: Before activate the new object, check current promo???? gain lock ??? + $class->check_current_promo; + + if ( my $object_promoted = $class->current_promoted_object( 'require_db' => 1 )) { + # this is an abnormal case, that's why the exception is stringly-typed + + # my $entry_url = $entry->url; + # my $promoted_url = $entry_promoted->url; + # die + # "cannot promote $entry_url because $promoted_url is already there"; + } - $class->check_current_entry; +# Possible remove all operations with DB into SelfPromo::Promo object ? +# my $promo = LJ::Pay::SelfPromo::Promo->create( +# 'object' => $object, +# 'started' => time, +# 'exptime' => time + $LJ::SELF_PROMO_CONF->{$class->class()}{'duration'}, +# 'cost' => $cost, +# 'promoterid' => $promoter->userid, # promoter have to be defined! +# 'type' => $class->type, +# ); - if ( my $entry_promoted = $class->current_entry( 'require_db' => 1 ) ) { +# or (better) +# $promo_object->activate(); - # this is an abnormal case, that's why the exception is stringly-typed +# TODO: Move that to SelfPromo::Promo class, make it constructor + + $class->db_insert_promo( - my $entry_url = $entry->url; - my $promoted_url = $entry_promoted->url; - die - "cannot promote $entry_url because $promoted_url is already there"; - } + # TODO: Entry specific code (move into Promo object creation) + $class->class eq 'entry' ? ( + 'journalid' => $object->journalid, # common + 'posterid' => $object->posterid, # entry specific - $class->db_insert_promo( - 'journalid' => $entry->journalid, - 'posterid' => $entry->posterid, - 'jitemid' => $entry->jitemid, - 'ditemid' => $entry->ditemid, + 'jitemid' => $object->jitemid, # entry specific + 'ditemid' => $object->ditemid, # entry specific + ) : ( + 'journalid' => $object->{userid}, + 'posterid' => $object->{userid}, + 'jitemid' => 0, + 'ditemid' => 0, + ), 'started' => time, - 'exptime' => time + $LJ::SELF_PROMO_CONF->{'duration'}, + 'exptime' => time + $LJ::SELF_PROMO_CONF->{$class->class()}{'duration'}, 'cost' => $cost, - 'promoterid' => $promoter? $promoter->userid : $entry->posterid, + 'promoterid' => $promoter->userid, # promoter have to be defined! ); + +# $class->clear_memcache($object->posterid, $object->ditemid); - $class->clear_memcache($entry->posterid, $entry->ditemid); - + $class->clear_memcache($object); # First read op go to DB + $class->log( $promoter, - 'entry_url' => $entry->url, + # 'entry_url' => $entry->url, + 'object_url' => ($class->class eq 'entry' ? $object->url : $object->journal_base), #$promo->object_url, TODO: Entry specific!!!!! + 'object' => $object, 'event' => 'activate', ); } -# supported opts: reason, details, entry, refund + +# supported opts: reason, details, object, refund # # called from LJ::Pay::Payment::PayItem::SelfPromo -sub deactivate_entry { + +sub deactivate_object { my ( $class, %opts ) = @_; - - my $promo = $class->current_entry_info( undef, 'require_db' => 1 ); - my $entry = $opts{'entry'}; - - unless ( $promo - && $promo->journalid == $entry->journalid - && $promo->jitemid == $entry->jitemid ) + + my $promo = $class->current_promo_info(undef, 'require_db' => 1); + my $object = $opts{'object'}; + + # Ensure we deactivate active object only! + # Just take the active object and do that!!!! + +# ???? + unless ( $promo && $promo->is_promoting($object) ) +# && $promo->journalid == $entry->journalid +# && $promo->jitemid == $entry->jitemid ) { - # your princess is in another castle, sorry return; } @@ -753,13 +840,16 @@ $promo->promoid, $promo->promoter, 'reason' => $opts{'reason'}, 'details' => $opts{'details'}, - 'entry' => $opts{'entry'}, - 'entry_url' => $entry->url, + 'object' => $opts{'object'}, + # 'entry' => $opts{'entry'}, + # 'entry_url' => $entry->url, + 'object_url' => $opts{'object_url'}, 'refund' => $opts{'refund'}, ); } -# supported opts: reason, details, entry, entry_url, refund +# supported opts: reason, details, object, refund +# TODO: we have no specify promoid sub deactivate_promo { my ( $class, $promoid, $promoter, %opts ) = @_; @@ -768,7 +858,9 @@ $class->log( $promoter, - 'entry_url' => $opts{'entry_url'}, + # 'entry_url' => $opts{'entry_url'}, + 'object_url' => $opts{'object_url'}, + 'object' => $opts{'object'}, 'event' => 'deactivate', 'reason' => $opts{'reason'}, 'details' => $opts{'details'}, @@ -810,18 +902,25 @@ ($it) = $cart->get_items; # set payitem props as appropriate + $it->set_prop( 'selfpromo' => $class->class ); $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'} ); + $it->set_prop( + 'selfpromo_remainder_userid' => $opts->{'remainder_userid'} ); - if ( my $entry = $opts->{'entry'} ) { - $it->set_prop( 'selfpromo_journalid' => $entry->journal->userid ); - $it->set_prop( 'selfpromo_ditemid' => $entry->ditemid ); + # Add object specific properties, if object exists (in case of non refunding cart) + if ( my $object = $opts->{'object'} ) { + + my $ownerid = ($class->type eq 'E' ? $object->journal->userid : $object->userid); # TODO: Refactor that + $it->set_prop( 'selfpromo_journalid' => $ownerid ); + + if ( $class->type eq 'E' ) { + $it->set_prop( 'selfpromo_ditemid' => $object->ditemid ); + } } LJ::Pay::SelfPromo->debug_msg( @@ -833,29 +932,37 @@ return $cart; } -# supported opts: reason, details, entry, refund_amount, exptime +# supported opts: reason, details, object, refund_amount, exptimea +# Send notification to the specified user about action performed with promo related to him + +# TODO (Refactoring): Pass PromoObject instead of %opts and use methods sub send_notification { - my ( $class, $poster, $action, %opts ) = @_; - + my ( $class, $user, $action, %opts ) = @_; + my ( $ml_var, %ml_params ); - + + my $sp_class = $class->class(); + if ( $action eq 'deactivate' ) { my $reason = $opts{'reason'}; - my $entry = $opts{'entry'}; # note that it may be undefined - + # my $entry = $opts{'entry'}; # note that it may be undefined + my $object = $opts{'object'}; # note that it may be undefined, especially for entries!!!! + %ml_params = ( 'refund_amount' => $opts{'refund_amount'}, - 'entry_url' => $opts{'entry_url'}, +# 'entry_url' => $opts{'entry_url'}, + 'object_url' => $opts{'object_url'}, 'duration_hours' => int( $opts{'duration'} / 3600 ), 'duration_min' => int( ( $opts{'duration'} % 3600 ) / 60 ), - 'poster' => $poster->display_name, + # 'poster' => $user->display_name, # TODO: Update ml names for semantic consistency! + 'user' => $user->display_name, ); - + if ( $reason eq 'admin' ) { - if ( $opts{'notify_poster'} ) { - $ml_var = 'selfpromo.notification.deactivate.admin3'; + if ( $opts{'notify_owner'} ) { + $ml_var = "selfpromo.$sp_class.notification.deactivate.admin3"; } else { - $ml_var = 'selfpromo.notification.deactivate.admin2'; + $ml_var = "selfpromo.$sp_class.notification.deactivate.admin2"; unless ( $opts{'refund_amount'} > 0 ) { $ml_var .= '.norefund'; } @@ -863,43 +970,44 @@ } if ( $reason eq 'buyout' ) { - if ( $opts{'notify_poster'} ) { - $ml_var = 'selfpromo.notification.deactivate.buyout3'; + if ( $opts{'notify_owner'} ) { + $ml_var = "selfpromo.$sp_class.notification.deactivate.buyout3"; } else { - $ml_var = 'selfpromo.notification.deactivate.buyout2'; + $ml_var = "selfpromo.$sp_class.notification.deactivate.buyout2"; } } if ( $reason eq 'deleted' ) { - $ml_var = 'selfpromo.notification.deactivate.deleted'; + $ml_var = "selfpromo.$sp_class.notification.deactivate.deleted"; } if ( $reason eq 'expired' ) { - $ml_var = 'selfpromo.notification.deactivate.expired'; + $ml_var = "selfpromo.$sp_class.notification.deactivate.expired"; } if ( $reason eq 'ineligible' ) { my $details = $opts{'details'}; - $ml_var = "selfpromo.notification.deactivate.ineligible.$details"; - $ml_params{'entry_subject'} = $entry->subject_text; + $ml_var = "selfpromo.$sp_class.notification.deactivate.ineligible.$details"; + # $ml_params{'object_subject'} = $object->subject_text; # TODO: Make it more coommon for journals/communitites: make it PromoObject method } if ( $reason eq 'withdraw' ) { - $ml_var = 'selfpromo.notification.deactivate.withdraw'; + $ml_var = "selfpromo.$sp_class.notification.deactivate.withdraw"; } - if ( $reason eq 'poster' ) { - if ( $opts{'notify_poster'} ) { - $ml_var = 'selfpromo.notification.deactivate.withdraw'; + if ( $reason eq 'owner' ) { + if ( $opts{'notify_owner'} ) { + $ml_var = "selfpromo.$sp_class.notification.deactivate.withdraw"; } else { - $ml_var = 'selfpromo.notification.deactivate.poster'; - $ml_params{'promoter'} = $ml_params{'poster'}; - $ml_params{'poster'} = $entry ? $entry->poster->display_name : ''; + $ml_var = "selfpromo.$sp_class.notification.deactivate.owner"; + $ml_params{'promoter'} = $ml_params{'user'}; + $ml_params{'owner'} = $opts{'owner'} ? $opts{'owner'}->display_name : ''; + # $ml_params{'poster'} = $object ? $object->poster->display_name : ''; # TODO: Use jfd } } } - my $lang = $poster->prop('browselang') || $LJ::DEFAULT_LANG; + my $lang = $user->prop('browselang') || $LJ::DEFAULT_LANG; my $subject = LJ::Lang::get_text( $lang, "$ml_var.subject", undef, \%ml_params, ); @@ -908,7 +1016,7 @@ \%ml_params, ); LJ::send_mail( - { 'to' => $poster->email_raw, + { 'to' => $user->email_raw, 'from' => $LJ::DONOTREPLY_EMAIL, 'fromname' => $LJ::SITENAME, 'subject' => $subject, @@ -920,9 +1028,9 @@ sub calculate_refund { my ( $class, $promo ) = @_; - my $unit = $LJ::SELF_PROMO_CONF->{'refund_time_unit'}; + my $unit = $LJ::SELF_PROMO_CONF->{$class->class}{'refund_time_unit'}; my $duration = $promo->exptime - $promo->started; - my $refund_step = $LJ::SELF_PROMO_CONF->{'refund_step'}; + my $refund_step = $LJ::SELF_PROMO_CONF->{$class->class}{'refund_step'}; my $remaining_time = $promo->exptime - time; my $remaining_time_units = int( $remaining_time / $unit ); @@ -938,6 +1046,8 @@ my $dbh = LJ::get_db_writer(); + $args{'type'} = $class->type; + my ( @sets, @binds ); while ( my ( $k, $v ) = each %args ) { push @sets, "$k=?"; @@ -960,18 +1070,18 @@ } my $sets = join( ',', @sets ); - $dbh->do( "UPDATE selfpromo SET $sets WHERE promoid=?", - undef, @binds, $promoid ); + $dbh->do( "UPDATE selfpromo SET $sets WHERE promoid=? AND type=?", + undef, @binds, $promoid, $class->type ); } sub clear_memcache { - my ($class, $posterid, $ditemid) = @_; + my ($class, $ownerid, $objectid) = @_; LJ::MemCache::delete( $class->memcache_key ); - LJ::MemCache::delete( $class->was_promoted_key($posterid, $ditemid) ) if $posterid and $ditemid; + LJ::MemCache::delete( $class->was_promoted_key($ownerid, $objectid) ) if $ownerid and $objectid; } -sub memcache_key { 'self_promo_active' } -sub was_promoted_key { 'self_promo_ditemid_promoted:'. join ':', $_[1], $_[2] } +sub memcache_key { 'self_promo_'.shift->class().'_active' } +sub was_promoted_key { 'self_promo_'.$_[0]->class().'_ids_promoted:'. join ':', $_[1], $_[2] } sub raise_error { my ( $class, @args ) = @_; @@ -979,24 +1089,25 @@ } sub lock { - return LJ::Pay::SelfPromo::Lock->new; + return LJ::Pay::SelfPromo::Lock->new(shift->class()); } sub lock_taken { - return LJ::Pay::SelfPromo::Lock->taken; + return LJ::Pay::SelfPromo::Lock->taken(shift->class()); } sub lock_taken_elsewhere { - return LJ::Pay::SelfPromo::Lock->taken_elsewhere; + return LJ::Pay::SelfPromo::Lock->taken_elsewhere(shift->class()); } -# supported opts: entry_url, event, reason, details +# supported opts: object_url, event, reason, details +# TODO: Add specific common messages for all types of selfpromo sub log { my ( $class, $promoter, %opts ) = @_; my $message = ''; - $message .= 'entry: ' . $opts{'entry_url'} . '; event: ' . $opts{'event'}; + $message .= 'type: '.$class->class.'; object: ' . $opts{'object_url'} . '; event: ' . $opts{'event'}; if ( my $reason = $opts{'reason'} ) { if ( my $details = $opts{'details'} ) { @@ -1025,6 +1136,10 @@ 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 ); @@ -1032,7 +1147,7 @@ __PACKAGE__->mk_accessors( qw( promoid journalid posterid jitemid ditemid - started exptime cost active finished promoterid + started exptime cost active finished promoterid type ) ); @@ -1046,26 +1161,64 @@ return LJ::load_userid( $self->journalid ); } +# TODO: Entry specific sub poster { my ($self) = @_; return LJ::load_userid( $self->posterid ); } +sub ownerid { + my ($self) = @_; + return ($self->type eq 'E' ? $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 'E' ? LJ::Entry->new( $self->journalid, 'jitemid' => $self->jitemid ) : LJ::load_userid( $self->journalid )); +} + +sub is_object_deleted { + my ($self) = @_; + return ( $self->type eq 'E' ? ! $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 ); } -sub entry_url { +# NOTE: Always return object URL, +# ??? But if journal/community deleted? +sub object_url { my ($self) = @_; - return $self->journal->journal_base . '/' . $self->ditemid . '.html'; + return ( $self->type eq 'E' ? $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 'E' ? $self->journalid == $object->journalid && $self->jitemid == $object->jitemid : LJ::u_equals($object, $self->object)); +} + + package LJ::Pay::SelfPromo::Error; sub raise { @@ -1078,18 +1231,22 @@ package LJ::Pay::SelfPromo::Lock; +my $locked; + sub new { - my ($class) = @_; + my ($class, @names) = @_; LJ::Pay::SelfPr... (truncated)