sunnyman's (sunnyman) wrote in changelog,
sunnyman's
sunnyman
changelog

[ljcom] r11369: LJSUP-10937: Entries Selfpromo extended ...

Committer: vtroitsky
LJSUP-10937: Entries Selfpromo extended to Journals and Communities
U   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)
Tags: ljcom, local, pl, pm, sunnyman, tmpl, vtroitsky
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments