mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)
Mark Smith ([staff profile] mark) wrote in [site community profile] changelog2010-02-22 07:48 am

[dw-free] Split out relevant portions of DW::Shop::Item::Account into a base DW::Shop::Item

[commit: http://hg.dwscoalition.org/dw-free/rev/c8f2f6f4000c]

http://bugs.dwscoalition.org/show_bug.cgi?id=2269

Make DW::Shop::Item actually a base class, with DW::Shop::Item::Account a
class that inherits from it.

Patch by [personal profile] afuna.

Files modified:
  • cgi-bin/DW/Shop/Item.pm
  • cgi-bin/DW/Shop/Item/Account.pm
--------------------------------------------------------------------------------
diff -r f1f20ae2fb51 -r c8f2f6f4000c cgi-bin/DW/Shop/Item.pm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cgi-bin/DW/Shop/Item.pm	Mon Feb 22 07:48:49 2010 +0000
@@ -0,0 +1,401 @@
+#!/usr/bin/perl
+#
+# DW::Shop::Item
+#
+# Base class containing basic behavior for items to be sold in the shop
+#
+# Authors:
+#      Mark Smith <mark@dreamwidth.org>
+#      Janine Smith <janine@netrophic.com>
+#      Afuna <coder.dw@afunamatata.com>
+#
+# Copyright (c) 2009 by Dreamwidth Studios, LLC.
+#
+# This program is free software; you may redistribute it and/or modify it under
+# the same terms as Perl itself.  For a copy of the license, please reference
+# 'perldoc perlartistic' or 'perldoc perlgpl'.
+#
+
+package DW::Shop::Item;
+
+use strict;
+use Carp;
+use DW::InviteCodes;
+use DW::Pay;
+
+=head1 NAME
+
+DW::Shop::Item - base class containing basic behavior for items to be sold in the shop
+
+=head1 SYNOPSIS
+
+=head1 API
+
+=head2 C<< $class->new(  [ $opts ] ) >>
+
+Instantiates an item to be purchased in the shop. The item must be defined in the 
+%LJ::SHOP hash in your config file.
+
+Arguments:
+=item type => item type, passed in by a subclass. Must be configured in %LJ::SHOP
+=item target_userid => userid,
+=item target_email => email,
+=item from_userid => userid,
+=item deliverydate => "yyyy-mm-dd",
+=item anonymous => 1,
+=item anonymous_target => 1,
+=item cannot_conflict => 1,
+=item noremove => 1,
+=item from_name => sender name,
+
+The type is required. Also, one target_* argument is required; it may be either
+a target_userid or a target_email. All other arguments are optional. 
+
+Subclasses must override this function to set the type. Subclasses may also do any 
+other modifications necessary when instantiating itself. See DW::Shop::Item::Account 
+for an example.
+
+=cut
+
+sub new {
+    my ( $class, %args ) = @_;
+
+    my $type = delete $args{type};
+    return undef unless exists $LJ::SHOP{$type};
+
+    # from_userid will be 0 if the sender isn't logged in
+    return undef unless $args{from_userid} == 0 || LJ::load_userid( $args{from_userid} );
+
+    # now do validation.  since new is only called when the item is being added
+    # to the shopping cart, then we are comfortable doing all of these checks
+    # on things at the time this item is put together
+    if ( my $uid = $args{target_userid} ) {
+        # userid needs to exist
+        return undef unless LJ::load_userid( $uid );
+    } elsif ( my $email = $args{target_email} ) {
+        # email address must be valid
+        my @email_errors;
+        LJ::check_email( $email, \@email_errors );
+        return undef if @email_errors;
+    } else {
+        return undef;
+    }
+
+    if ( $args{deliverydate} ) {
+        return undef unless $args{deliverydate} =~ /^\d\d\d\d-\d\d-\d\d$/;
+    }
+
+    if ( $args{anonymous} ) {
+        return undef unless $args{anonymous} == 1;
+    }
+
+    if ( $args{cannot_conflict} ) {
+        return undef unless $args{cannot_conflict} == 1;
+    }
+
+    if ( $args{noremove} ) {
+        return undef unless $args{noremove} == 1;
+    }
+
+    # looks good
+    return bless {
+        # user supplied arguments (close enough)
+        cost    => $LJ::SHOP{$type}->[0] + 0.00,
+        %args,
+
+        # internal things we use to track the state of this item,
+        applied => 0,
+        cartid  => 0,
+    }, $class;
+}
+
+
+=head2 C<< $self->apply >>
+
+Called when we are told we need to apply this item, i.e., turn it on. Note that we 
+update ourselves, but it's up to the cart to make sure that it saves.
+
+Subclasses may override this method, but a better approach would be to override the 
+internal $self->_apply method.
+
+=cut
+
+sub apply {
+    my $self = shift;
+    return 1 if $self->applied;
+
+    # 1) deliverydate must be present/past
+    if ( my $ddate = $self->deliverydate ) {
+        my $cur = LJ::mysql_time();
+        $cur =~ s/^(\d\d\d\d-\d\d-\d\d).+$/$1/;
+
+        return 0
+            unless $ddate le $cur;
+    }
+
+    return $self->_apply( @_ );
+}
+
+
+=head2 C<< $self->_apply >>
+
+Internal application sub.
+
+Subclasses must override this for item-specific behavior.
+
+=cut
+
+sub _apply {
+    croak "Cannot apply shop item; this method must be override by a subclass.";
+}
+
+
+=head2 C<< $self->unapply >>
+
+Called when we need to turn this item off.
+
+Subclasses may override this to add additional behavior or warnings.
+
+=cut
+
+sub unapply {
+    my $self = $_[0];
+    return unless $self->applied;
+
+    # do the application process now, and if it succeeds...
+    $self->{applied} = 0;
+
+    return 1;
+}
+
+=head2 C<< $self->cart_state_changed( $cart, $newstate ) >>
+
+Hook in the cart for custom behavior once the cart has been changed.
+
+Subclasses may override this for custom behavior: for example, creating a token or certificate once the cart has been paid for.
+
+=cut
+
+sub cart_state_changed {
+    my ( $cart, $newstate ) = @_;
+}
+
+=head2 C<< $self->can_be_added( [ %opts ] ) >>
+
+Returns 1 if this item is allowed to be added to the shopping cart.
+
+Subclasses must override this.
+
+=cut
+
+sub can_be_added {
+    my ( $self, %opts ) = @_;
+
+    return 1;
+}
+
+
+=head2 C<< $self->conflicts( $item ) >>
+
+Given another item, see if that item conflicts with this item (i.e.,
+if you can't have both in your shopping cart at the same time).
+
+Returns undef on "no conflict" else an error message.
+
+Subclasses may override.
+
+=cut
+
+sub conflicts {
+    my ( $self, $item ) = @_;
+
+    # if either item are set as "does not conflict" then never say yes
+    return if
+        $self->cannot_conflict || $item->cannot_conflict;
+
+    # subclasses can add additional logic here
+
+    # guess we allow it
+    return undef;
+}
+
+
+=head2 C<< $self->t_html( [ %opts ] ) >>
+
+Render our target as a string.
+
+Subclasses may override.
+
+=cut
+
+sub t_html {
+    my ( $self, %opts ) = @_;
+
+    if ( my $uid = $self->t_userid ) {
+        my $u = LJ::load_userid( $uid );
+        return $u->ljuser_display
+            if $u;
+        return "<strong>invalid userid $uid</strong>";
+
+    } elsif ( my $email = $self->t_email ) {
+        return "<strong>$email</strong>";
+
+    }
+
+    return "<strong>invalid/unknown target</strong>";
+}
+
+
+=head2 C<< $self->name_html >>
+
+Render the item name as a string, for display.
+
+Subclasses must override to provide a more specific and user-friendly display name.
+
+=cut
+
+sub name_html {
+    return ref $_[0];
+}
+
+
+=head2 C<< $self->short_desc >>
+
+Returns a short string talking about what this is.
+
+Subclasses may override to provide further description.
+
+=cut
+
+sub short_desc {
+    my $self = $_[0];
+
+    # does not contain HTML, I hope
+    my $desc = $self->name_html;
+
+    my $for = $self->t_email;
+    unless ( $for ) {
+        my $u = LJ::load_userid( $self->t_userid );
+        $for = $u->user
+            if $u;
+    }
+
+    # FIXME: english strip
+    return "$desc for $for";
+}
+
+
+=head2 C<< $self->id( $id ) >>
+
+This is a getter/setter so it is pulled out.
+
+=cut
+
+sub id {
+    return $_[0]->{id} unless defined $_[1];
+    return $_[0]->{id} = $_[1];
+}
+
+
+=head2 C<< $self->cartid( $cartid ) >>
+
+Gets/sets.
+
+=cut
+
+sub cartid {
+    return $_[0]->{cartid} unless defined $_[1];
+    return $_[0]->{cartid} = $_[1];
+}
+
+
+=head2 C<< $self->t_userid( $target_userid ) >>
+
+Gets/sets.
+
+=cut
+
+sub t_userid {
+    return $_[0]->{target_userid} unless defined $_[1];
+    return $_[0]->{target_userid} = $_[1];
+}
+
+
+=head2 C<< $self->from_html >>
+
+Display who this is from.
+
+=cut
+
+sub from_html {
+    my $self = $_[0];
+
+    return $self->{from_name} if $self->{from_name};
+
+    my $from_u = LJ::load_userid( $self->from_userid );
+    return LJ::Lang::ml( 'widget.shopcart.anonymous' )
+        if $self->anonymous || ! LJ::isu( $from_u );
+
+    return $from_u->ljuser_display;
+}
+
+# simple accessors
+
+=head2 C<< $self->applied >>
+
+Returns whether the item which was bought has been already applied
+
+=head2 C<< $self->cost >>
+
+Returns the cost of the item, as configured for this site.
+
+=head2 C<< $self->t_email >>
+
+Returns the target email this item was sent to.
+
+=head2 C<< $self->from_userid >>
+
+Returns the userid of the person who bought this item. May be 0.
+
+=head2 C<< $self->deliverydate >>
+
+Returns the date this item should be delivered, in "yyyy-mm-dd" format
+
+=head2 C<< $self->anonymous >>
+
+Returns whether this item should be gifted anonymously, or credited to the sender
+
+=head2 C<< $self->noremove >>
+
+Returns whether this item may or may not be removed from the cart. May be used by 
+promotions which automatically add a promo item to a user's cart, to prevent the 
+promo item from being removed
+
+=head2 C<< $self->from_name >>
+
+Name of the sender in special cases. For example, can be the site name for
+promotions. Not exposed/settable via the shop.
+
+=cut
+sub applied      { return $_[0]->{applied};         }
+sub cost         { return $_[0]->{cost};            }
+sub t_email      { return $_[0]->{target_email};    }
+sub from_userid  { return $_[0]->{from_userid};     }
+sub deliverydate { return $_[0]->{deliverydate};    }
+sub anonymous    { return $_[0]->{anonymous};       }
+sub noremove     { return $_[0]->{noremove};        }
+sub from_name    { return $_[0]->{from_name};       }
+
+
+=head2 C<< $self->cannot_conflict >>
+
+Returns whether this item may never conflict with any other item. If true, skip
+checks for conflict.
+
+Subclasses may override.
+
+=cut
+
+sub cannot_conflict  { return $_[0]->{cannot_conflict};  }
+
+1;
diff -r f1f20ae2fb51 -r c8f2f6f4000c cgi-bin/DW/Shop/Item/Account.pm
--- a/cgi-bin/DW/Shop/Item/Account.pm	Mon Feb 22 07:44:11 2010 +0000
+++ b/cgi-bin/DW/Shop/Item/Account.pm	Mon Feb 22 07:48:49 2010 +0000
@@ -17,92 +17,60 @@
 
 package DW::Shop::Item::Account;
 
+use base 'DW::Shop::Item';
+
 use strict;
 use DW::InviteCodes;
 use DW::Pay;
 
+=head1 NAME
 
-# instantiates an account to be purchased of some sort
+DW::Shop::Item::Account - Represents a paid account that someone is purchasing. See 
+the documentation for DW::Shop::Item for usage examples and description of methods
+inherited from that base class.
+
+=head1 API
+
+=head2 C<< $class->new( [ %args ] ) >>
+
+Instantiates an account of some sort to be purchased.
+
+Arguments:
+=item ( see DW::Shop::Item ), 
+=item months => number of months of paid time,
+=item class => type of paid account,
+=item random => 1 (if gifting paid time to a random user),
+=item anonymous_target => 1 (if random user should be anonymous, not identified) 
+
+=cut
+
+# override
 sub new {
     my ( $class, %args ) = @_;
 
-    my $type = delete $args{type};
-    return undef unless exists $LJ::SHOP{$type};
-
-    # from_userid will be 0 if the sender isn't logged in
-    return undef unless $args{from_userid} == 0 || LJ::load_userid( $args{from_userid} );
-
-    # now do validation.  since new is only called when the item is being added
-    # to the shopping cart, then we are comfortable doing all of these checks
-    # on things at the time this item is put together
-    if ( my $uid = $args{target_userid} ) {
-        # userid needs to exist
-        return undef unless LJ::load_userid( $uid );
-    } elsif ( my $email = $args{target_email} ) {
-        # email address must be valid
-        my @email_errors;
-        LJ::check_email( $email, \@email_errors );
-        return undef if @email_errors;
-    } else {
-        return undef;
-    }
-
-    if ( $args{deliverydate} ) {
-        return undef unless $args{deliverydate} =~ /^\d\d\d\d-\d\d-\d\d$/;
-    }
-
-    if ( $args{anonymous} ) {
-        return undef unless $args{anonymous} == 1;
+    if ( $args{anonymous_target} ) {
+        return undef unless $args{anonymous_target} == 1;
     }
 
     if ( $args{random} ) {
         return undef unless $args{random} == 1;
     }
 
-    if ( $args{anonymous_target} ) {
-        return undef unless $args{anonymous_target} == 1;
+    my $self = $class->SUPER::new( %args );
+
+    if ( $self ) {
+        $self->{months} = $LJ::SHOP{$self->{type}}->[1];
+        $self->{class} = $LJ::SHOP{$self->{type}}->[2];
     }
 
-    if ( $args{cannot_conflict} ) {
-        return undef unless $args{cannot_conflict} == 1;
-    }
-
-    if ( $args{noremove} ) {
-        return undef unless $args{noremove} == 1;
-    }
-
-    # looks good
-    return bless {
-        # user supplied arguments (close enough)
-        cost    => $LJ::SHOP{$type}->[0] + 0.00,
-        months  => $LJ::SHOP{$type}->[1],
-        class   => $LJ::SHOP{$type}->[2],
-        %args,
-
-        # internal things we use to track the state of this item
-        type    => 'account',
-        applied => 0,
-        cartid  => 0,
-    }, $class;
+    return $self;
 }
 
 
-# called when we are told we need to apply this item, i.e., turn it on.  note that we
-# update ourselves, but it's up to the cart to make sure that it saves.
-sub apply {
+# override
+sub _apply {
     my $self = $_[0];
-    return 1 if $self->applied;
 
-    # 1) deliverydate must be present/past
-    if ( my $ddate = $self->deliverydate ) {
-        my $cur = LJ::mysql_time();
-        $cur =~ s/^(\d\d\d\d-\d\d-\d\d).+$/$1/;
-
-        return 0
-            unless $ddate le $cur;
-    }
-
-    # application variability
     return $self->_apply_email  if $self->t_email;
     return $self->_apply_userid if $self->t_userid;
 
@@ -269,7 +237,7 @@ sub _apply_email {
 }
 
 
-# called when we need to turn this item off
+# override
 sub unapply {
     my $self = $_[0];
     return unless $self->applied;
@@ -282,7 +250,7 @@ sub unapply {
 }
 
 
-# returns 1 if this item is allowed to be added to the shopping cart
+# override
 sub can_be_added {
     my ( $self, %opts ) = @_;
 
@@ -319,10 +287,7 @@ sub can_be_added {
 }
 
 
-# given another item, see if that item conflicts with this item (i.e.,
-# if you can't have both in your shopping cart at the same time).
-#
-# returns undef on "no conflict" else an error message.
+# override
 sub conflicts {
     my ( $self, $item ) = @_;
 
@@ -352,7 +317,7 @@ sub conflicts {
 }
 
 
-# render our target as a string
+# override
 sub t_html {
     my ( $self, %opts ) = @_;
 
@@ -366,22 +331,14 @@ sub t_html {
         } else {
             return "<strong>$random_user_string</strong>";
         }
-    } elsif ( my $uid = $self->t_userid ) {
-        my $u = LJ::load_userid( $uid );
-        return $u->ljuser_display
-            if $u;
-        return "<strong>invalid userid $uid</strong>";
+    } 
 
-    } elsif ( my $email = $self->t_email ) {
-        return "<strong>$email</strong>";
-
-    }
-
-    return "<strong>invalid/unknown target</strong>";
+    # otherwise, fall back upon default display
+    return $self->SUPER::t_html( %opts );
 }
 
 
-# render the item name as a string
+# override
 sub name_html {
     my $self = $_[0];
 
@@ -390,6 +347,12 @@ sub name_html {
     return LJ::Lang::ml( 'shop.item.account.name', { name => $name, num => $self->months } );
 }
 
+
+=head2 C<< $self->class_name >>
+
+Return the display name of this account class.
+
+=cut
 
 sub class_name {
     my $self = $_[0];
@@ -403,75 +366,33 @@ sub class_name {
 }
 
 
-# returns a short string talking about what this is
-sub short_desc {
-    my $self = $_[0];
+# simple accessors
+=head2 C<< $self->months >>
 
-    # does not contain HTML, I hope
-    my $desc = $self->name_html;
+Number of months of paid time to be applied.
 
-    my $for = $self->t_email;
-    unless ( $for ) {
-        my $u = LJ::load_userid( $self->t_userid );
-        $for = $u->user
-            if $u;
-    }
+=head2 C<< $self->class >>
 
-    # FIXME: english strip
-    return "$desc for $for";
-}
+Account class identifier; not for display.
 
+=head2 C<< $self->permanent >>
 
-# this is a getter/setter so it is pulled out
-sub id {
-    return $_[0]->{id} unless defined $_[1];
-    return $_[0]->{id} = $_[1];
-}
+Returns whether this item is for a permanent account, or just a normal paid.
 
+=head2 C<< $self->random >>
 
-# gets/sets
-sub cartid {
-    return $_[0]->{cartid} unless defined $_[1];
-    return $_[0]->{cartid} = $_[1];
-}
+Returns whether this item is for a random user.
 
+=head2 C<< $self->anonymous_target >>
 
-# gets/sets
-sub t_userid {
-    return $_[0]->{target_userid} unless defined $_[1];
-    return $_[0]->{target_userid} = $_[1];
-}
+Returns whether this item for a random user should go to an anonymous user (true)
+or to an identified user (false)
 
-
-# display who this is from
-sub from_html {
-    my $self = $_[0];
-
-    return $self->{from_name} if $self->{from_name};
-
-    my $from_u = LJ::load_userid( $self->from_userid );
-    return LJ::Lang::ml( 'widget.shopcart.anonymous' )
-        if $self->anonymous || ! LJ::isu( $from_u );
-
-    return $from_u->ljuser_display;
-}
-
-
-# simple accessors
-sub applied      { return $_[0]->{applied};         }
-sub cost         { return $_[0]->{cost};            }
+=cut 
 sub months       { return $_[0]->{months};          }
 sub class        { return $_[0]->{class};           }
-sub t_email      { return $_[0]->{target_email};    }
 sub permanent    { return $_[0]->months == 99;      }
-sub from_userid  { return $_[0]->{from_userid};     }
-sub deliverydate { return $_[0]->{deliverydate};    }
-sub anonymous    { return $_[0]->{anonymous};       }
 sub random       { return $_[0]->{random};          }
-sub noremove     { return $_[0]->{noremove};        }
-sub from_name    { return $_[0]->{from_name};       }
 sub anonymous_target { return $_[0]->{anonymous_target}; }
-sub cannot_conflict  { return $_[0]->{cannot_conflict};  }
-
 
 1;
--------------------------------------------------------------------------------