554 lines
17 KiB
Perl
554 lines
17 KiB
Perl
#!/usr/bin/perl
|
|
#
|
|
# DW::Shop::Item::Account
|
|
#
|
|
# Represents a paid account that someone is purchasing.
|
|
#
|
|
# Authors:
|
|
# Mark Smith <mark@dreamwidth.org>
|
|
# Janine Smith <janine@netrophic.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::Account;
|
|
|
|
use base 'DW::Shop::Item';
|
|
|
|
use strict;
|
|
use DateTime;
|
|
use DW::InviteCodes;
|
|
use DW::Pay;
|
|
|
|
=head1 NAME
|
|
|
|
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 ) = @_;
|
|
|
|
if ( $args{anonymous_target} ) {
|
|
return undef unless $args{anonymous_target} == 1;
|
|
}
|
|
|
|
if ( $args{random} ) {
|
|
return undef unless $args{random} == 1;
|
|
}
|
|
|
|
my $self = $class->SUPER::new(%args);
|
|
|
|
if ($self) {
|
|
$self->{months} = $LJ::SHOP{ $self->{type} }->[1];
|
|
$self->{class} = $LJ::SHOP{ $self->{type} }->[2];
|
|
}
|
|
|
|
return $self;
|
|
}
|
|
|
|
# override
|
|
sub _apply {
|
|
my $self = $_[0];
|
|
|
|
return $self->_apply_email if $self->t_email;
|
|
return $self->_apply_userid if $self->t_userid;
|
|
|
|
# something weird, just kill this item!
|
|
$self->{applied} = 1;
|
|
return 1;
|
|
}
|
|
|
|
# internal application sub, do not call
|
|
sub _apply_userid {
|
|
my $self = $_[0];
|
|
return 1 if $self->applied;
|
|
|
|
# will need this later
|
|
my $fu = LJ::load_userid( $self->from_userid );
|
|
unless ( $self->anonymous || $self->from_name || $fu ) {
|
|
warn "Failed to apply: NOT anonymous, no from_name, no from_user!\n";
|
|
return 0;
|
|
}
|
|
|
|
# need this user
|
|
my $u = LJ::load_userid( $self->t_userid )
|
|
or return 0;
|
|
|
|
# try to add the paid time to the user
|
|
LJ::statushistory_add(
|
|
$u->id,
|
|
$self->from_userid,
|
|
'paidstatus',
|
|
sprintf(
|
|
'Order #%d: applied %d months of %s.',
|
|
$self->cartid, $self->months, $self->class_name
|
|
)
|
|
);
|
|
DW::Pay::add_paid_time( $u, $self->class, $self->months )
|
|
or return 0;
|
|
|
|
{
|
|
# By definition, things from anonymous purchasers are gifts.
|
|
my @tags = (
|
|
'gift:' . ( $fu && $fu->equals($u) ? 'no' : 'yes' ),
|
|
'anonymous:' . ( $self->anonymous ? 'yes' : 'no' ),
|
|
'type:' . $self->class,
|
|
'target:account'
|
|
);
|
|
DW::Stats::increment( 'dw.shop.paid_account.applied',
|
|
1, [ @tags, 'months:' . $self->months ] );
|
|
DW::Stats::increment( 'dw.shop.paid_account.applied_months', $self->months, [@tags] );
|
|
}
|
|
|
|
# we're applied now, regardless of what happens with the email
|
|
$self->{applied} = 1;
|
|
|
|
# look up the account's new expiration date
|
|
my $expdate;
|
|
my $paid_status = DW::Pay::get_paid_status($u);
|
|
|
|
if ( $paid_status && !$self->permanent ) {
|
|
my $exptime = DateTime->from_epoch( epoch => $paid_status->{expiretime} );
|
|
$expdate = $exptime->ymd;
|
|
}
|
|
|
|
# now we have to mail this code
|
|
my ( $body, $subj );
|
|
my $accounttype_string =
|
|
$self->permanent
|
|
? LJ::Lang::ml( 'shop.email.accounttype.permanent', { type => $self->class_name } )
|
|
: LJ::Lang::ml( 'shop.email.accounttype',
|
|
{ type => $self->class_name, nummonths => $self->months } );
|
|
$subj = LJ::Lang::ml( "shop.email.acct.subject", { sitename => $LJ::SITENAME } );
|
|
|
|
if ( $u->is_community ) {
|
|
my $maintus = LJ::load_userids( $u->maintainer_userids );
|
|
foreach my $maintu ( values %$maintus ) {
|
|
my $emailtype = $fu && $maintu->equals($fu) ? 'self' : 'other';
|
|
$emailtype = 'anon' if $self->anonymous;
|
|
$emailtype = 'explicit' if $self->from_name;
|
|
|
|
$body =
|
|
LJ::Lang::ml( "shop.email.acct.body.start", { touser => $maintu->display_name } );
|
|
$body .= "\n";
|
|
$body .= LJ::Lang::ml(
|
|
"shop.email.comm.$emailtype",
|
|
{
|
|
fromuser => $fu ? $fu->display_name : '',
|
|
commname => $u->display_name,
|
|
sitename => $LJ::SITENAME,
|
|
fromname => $self->from_name,
|
|
}
|
|
);
|
|
|
|
$body .=
|
|
LJ::Lang::ml( "shop.email.acct.body.type", { accounttype => $accounttype_string } );
|
|
$body .= "\n";
|
|
$body .= LJ::Lang::ml( "shop.email.acct.body.expires", { date => $expdate } )
|
|
if $expdate;
|
|
$body .= "\n";
|
|
$body .= LJ::Lang::ml( "shop.email.acct.body.note", { reason => $self->reason } )
|
|
if $self->reason;
|
|
|
|
$body .= LJ::Lang::ml("shop.email.comm.close");
|
|
$body .= LJ::Lang::ml( "shop.email.acct.body.end", { sitename => $LJ::SITENAME } );
|
|
|
|
# send the email to the maintainer
|
|
LJ::send_mail(
|
|
{
|
|
to => $maintu->email_raw,
|
|
from => $LJ::ACCOUNTS_EMAIL,
|
|
fromname => $LJ::SITENAME,
|
|
subject => $subj,
|
|
body => $body
|
|
}
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
my $emailtype;
|
|
if ( $self->random ) {
|
|
$emailtype = $self->anonymous ? 'random_anon' : 'random';
|
|
}
|
|
else {
|
|
$emailtype = $fu && $u->equals($fu) ? 'self' : 'other';
|
|
$emailtype = 'anon' if $self->anonymous;
|
|
$emailtype = 'explicit' if $self->from_name;
|
|
}
|
|
|
|
$body = LJ::Lang::ml( "shop.email.acct.body.start", { touser => $u->display_name } );
|
|
$body .= "\n";
|
|
$body .= LJ::Lang::ml(
|
|
"shop.email.user.$emailtype",
|
|
{
|
|
fromuser => $fu ? $fu->display_name : '',
|
|
sitename => $LJ::SITENAME,
|
|
fromname => $self->from_name,
|
|
}
|
|
);
|
|
|
|
$body .=
|
|
LJ::Lang::ml( "shop.email.acct.body.type", { accounttype => $accounttype_string } );
|
|
$body .= "\n";
|
|
$body .= LJ::Lang::ml( "shop.email.acct.body.expires", { date => $expdate } )
|
|
if $expdate;
|
|
$body .= "\n";
|
|
$body .= LJ::Lang::ml( "shop.email.acct.body.note", { reason => $self->reason } )
|
|
if $self->reason;
|
|
|
|
$body .= LJ::Lang::ml("shop.email.user.close");
|
|
$body .= LJ::Lang::ml( "shop.email.acct.body.end", { sitename => $LJ::SITENAME } );
|
|
|
|
# send the email to the user
|
|
LJ::send_mail(
|
|
{
|
|
to => $u->email_raw,
|
|
from => $LJ::ACCOUNTS_EMAIL,
|
|
fromname => $LJ::SITENAME,
|
|
subject => $subj,
|
|
body => $body
|
|
}
|
|
);
|
|
}
|
|
|
|
# tell the caller we're happy
|
|
return 1;
|
|
}
|
|
|
|
# internal application sub, do not call
|
|
sub _apply_email {
|
|
my $self = $_[0];
|
|
return 1 if $self->applied;
|
|
|
|
# will need this later
|
|
my $fu = LJ::load_userid( $self->from_userid );
|
|
unless ( $self->anonymous || $fu ) {
|
|
warn "Failed to apply: NOT anonymous and no from_user!\n";
|
|
return 0;
|
|
}
|
|
|
|
{
|
|
# By definition, things sent to email are gifts.
|
|
my @tags = (
|
|
'gift:yes', 'target:email',
|
|
'anonymous:' . ( $self->anonymous ? 'yes' : 'no' ), 'type:' . $self->class
|
|
);
|
|
DW::Stats::increment( 'dw.shop.paid_account.applied',
|
|
1, [ @tags, 'months:' . $self->months ] );
|
|
DW::Stats::increment( 'dw.shop.paid_account.applied_months', $self->months, [@tags] );
|
|
}
|
|
|
|
my $reason = join ':', 'payment', $self->class, $self->months;
|
|
my ($code) = DW::InviteCodes->generate( reason => $reason );
|
|
my ($acid) = DW::InviteCodes->decode($code);
|
|
|
|
# store in the db
|
|
my $dbh = LJ::get_db_writer()
|
|
or return 0;
|
|
$dbh->do( 'INSERT INTO shop_codes (acid, cartid, itemid) VALUES (?, ?, ?)',
|
|
undef, $acid, $self->cartid, $self->id );
|
|
return 0
|
|
if $dbh->err;
|
|
|
|
# now we have to mail this code
|
|
my ( $body, $subj );
|
|
my $accounttype_string =
|
|
$self->permanent
|
|
? LJ::Lang::ml( 'shop.email.accounttype.permanent', { type => $self->class_name } )
|
|
: LJ::Lang::ml( 'shop.email.accounttype',
|
|
{ type => $self->class_name, nummonths => $self->months } );
|
|
|
|
my $emailtype = $self->anonymous ? 'anon' : 'other';
|
|
|
|
$subj = LJ::Lang::ml( "shop.email.acct.subject", { sitename => $LJ::SITENAME } );
|
|
|
|
$body = LJ::Lang::ml( "shop.email.acct.body.start", { touser => $self->t_email } );
|
|
$body .= "\n";
|
|
$body .= LJ::Lang::ml(
|
|
"shop.email.email.$emailtype",
|
|
{
|
|
fromuser => $fu ? $fu->display_name : '',
|
|
sitename => $LJ::SITENAME,
|
|
}
|
|
);
|
|
|
|
$body .= LJ::Lang::ml( "shop.email.acct.body.type", { accounttype => $accounttype_string } );
|
|
$body .= LJ::Lang::ml( "shop.email.acct.body.create",
|
|
{ createurl => "$LJ::SITEROOT/create?code=$code" } );
|
|
$body .= LJ::Lang::ml( "shop.email.acct.body.note", { reason => $self->reason } )
|
|
if $self->reason;
|
|
|
|
$body .= LJ::Lang::ml("shop.email.email.close");
|
|
$body .= LJ::Lang::ml( "shop.email.acct.body.end", { sitename => $LJ::SITENAME } );
|
|
|
|
# send the email to the user
|
|
my $rv = LJ::send_mail(
|
|
{
|
|
to => $self->t_email,
|
|
from => $LJ::ACCOUNTS_EMAIL,
|
|
fromname => $LJ::SITENAME,
|
|
subject => $subj,
|
|
body => $body
|
|
}
|
|
);
|
|
|
|
# if this worked, then we're applied! yay!
|
|
if ($rv) {
|
|
$self->{applied} = 1;
|
|
return 1;
|
|
}
|
|
|
|
# else ... something naughty happened :(
|
|
warn "Failed to send email!\n";
|
|
return 0;
|
|
}
|
|
|
|
# override
|
|
sub unapply {
|
|
my $self = $_[0];
|
|
return unless $self->applied;
|
|
|
|
# do the application process now, and if it succeeds...
|
|
$self->{applied} = 0;
|
|
warn "$self->{class} unapplied $self->{months} months\n";
|
|
|
|
return 1;
|
|
}
|
|
|
|
# override
|
|
sub can_be_added {
|
|
my ( $self, %opts ) = @_;
|
|
|
|
my $errref = $opts{errref};
|
|
my $target_u = LJ::load_userid( $self->t_userid );
|
|
|
|
# the receiving user must be a personal or community account
|
|
if ( LJ::isu($target_u) && !$target_u->is_personal && !$target_u->is_community ) {
|
|
$$errref = LJ::Lang::ml('shop.item.account.canbeadded.invalidjournaltype');
|
|
return 0;
|
|
}
|
|
|
|
# check to see if we're over the permanent account limit
|
|
if ( $self->permanent && DW::Pay::num_permanent_accounts_available() < 1 ) {
|
|
$$errref = LJ::Lang::ml('shop.item.account.canbeadded.noperms');
|
|
return 0;
|
|
}
|
|
|
|
# check to make sure that the target user is valid: not deleted / suspended, etc
|
|
if ( !$opts{user_confirmed} && LJ::isu($target_u) && $target_u->is_inactive ) {
|
|
$$errref = LJ::Lang::ml( 'shop.item.account.canbeadded.notactive',
|
|
{ user => $target_u->ljuser_display } );
|
|
return 0;
|
|
}
|
|
|
|
# check to make sure the target user's current account type doesn't conflict with the item
|
|
if ( LJ::isu($target_u) ) {
|
|
my $account_type = DW::Pay::get_account_type($target_u);
|
|
if ( $account_type eq 'seed' ) {
|
|
|
|
# no paid time can be purchased for seed accounts
|
|
$$errref = LJ::Lang::ml( 'shop.item.account.canbeadded.alreadyperm',
|
|
{ user => $target_u->ljuser_display } );
|
|
return 0;
|
|
}
|
|
elsif ( !DW::Shop::Item::Account->allow_account_conversion( $target_u, $self->class ) ) {
|
|
|
|
# premium accounts can't get normal paid time
|
|
$$errref = LJ::Lang::ml(
|
|
'shop.item.account.canbeadded.nopaidforpremium',
|
|
{ user => $target_u->ljuser_display }
|
|
) if $self->class eq 'paid';
|
|
|
|
# paid accounts can't get premium time as a gift
|
|
$$errref = LJ::Lang::ml(
|
|
'shop.item.account.canbeadded.nopremiumforpaid',
|
|
{ user => $target_u->ljuser_display }
|
|
) if $self->class eq 'premium';
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
# this checks whether we can upgrade/downgrade between premium & paid
|
|
sub allow_account_conversion {
|
|
my ( $class, $u, $to ) = @_;
|
|
|
|
# no existing user; assume no previous conflicting account
|
|
return 1 unless LJ::isu($u);
|
|
|
|
# no previous paid status; assume no conflicts
|
|
my $paid_status = DW::Pay::get_paid_status($u);
|
|
return 1 unless $paid_status;
|
|
|
|
my $from = DW::Pay::type_shortname( $paid_status->{typeid} );
|
|
|
|
# ok unless we're going from premium to paid or paid to premium
|
|
my $changing = 0;
|
|
|
|
$changing = 1 if $from eq 'premium' && $to eq 'paid';
|
|
$changing = 1 if $from eq 'paid' && $to eq 'premium';
|
|
|
|
# doesn't match either scenario, so allow it
|
|
return 1 unless $changing;
|
|
|
|
# allow if we're within two weeks of expiration
|
|
return 1 if $paid_status->{expiresin} <= 3600 * 24 * 14;
|
|
|
|
# allow upgrading to premium if the remote user owns this account
|
|
my $remote = LJ::get_remote();
|
|
return 1 if $to eq 'premium' && $remote && $remote->can_purchase_for($u);
|
|
|
|
return 0;
|
|
}
|
|
|
|
# override
|
|
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;
|
|
|
|
# we can only conflict with other items of our own type
|
|
return
|
|
if ref $self ne ref $item;
|
|
|
|
# first see if we're talking about the same target
|
|
# note that we're not checking email here because they may want to buy
|
|
# multiple paid accounts and send them to all to the same email address
|
|
# (so they can can create multiple new paid accounts)
|
|
return
|
|
if ( $self->t_userid && ( $self->t_userid != $item->t_userid ) )
|
|
|| ( $self->t_email );
|
|
|
|
# target same, if both are permanent, then fail because
|
|
# THERE CAN BE ONLY ONE
|
|
return LJ::Lang::ml('shop.item.account.conflicts.multipleperms')
|
|
if $self->permanent && $item->permanent;
|
|
|
|
# otherwise ensure that the classes are the same
|
|
return LJ::Lang::ml('shop.item.account.conflicts.differentpaid')
|
|
if $self->class ne $item->class;
|
|
|
|
# guess we allow it
|
|
return undef;
|
|
}
|
|
|
|
# override
|
|
sub t_html {
|
|
my ( $self, $opts ) = @_;
|
|
|
|
if ( $self->anonymous_target ) {
|
|
my $random_user_string = LJ::Lang::ml('shop.item.account.randomuser');
|
|
if ( $opts->{admin} ) {
|
|
my $u = LJ::load_userid( $self->t_userid );
|
|
return "<strong>invalid userid " . $self->t_userid . "</strong>"
|
|
unless $u;
|
|
return "$random_user_string (" . $u->ljuser_display . ")";
|
|
}
|
|
else {
|
|
return "<strong>$random_user_string</strong>";
|
|
}
|
|
}
|
|
|
|
# otherwise, fall back upon default display
|
|
return $self->SUPER::t_html($opts);
|
|
}
|
|
|
|
# override
|
|
sub name_text {
|
|
my $self = $_[0];
|
|
|
|
my $name = $self->class_name;
|
|
|
|
if ( $self->cost_points > 0 ) {
|
|
return LJ::Lang::ml( 'shop.item.account.name.perm',
|
|
{ name => $name, points => $self->cost_points } )
|
|
if $self->permanent;
|
|
return LJ::Lang::ml( 'shop.item.account.name',
|
|
{ name => $name, num => $self->months, points => $self->cost_points } );
|
|
}
|
|
else {
|
|
return $name if $self->permanent;
|
|
return LJ::Lang::ml( 'shop.item.account.name.nopoints',
|
|
{ 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];
|
|
|
|
foreach my $cap ( keys %LJ::CAP ) {
|
|
|
|
# skip "unused" elements in CAP
|
|
next unless $LJ::CAP{$cap}->{_account_type};
|
|
|
|
return $LJ::CAP{$cap}->{_visible_name}
|
|
if $LJ::CAP{$cap}->{_account_type} eq $self->class;
|
|
}
|
|
|
|
return 'Invalid Account Class';
|
|
}
|
|
|
|
# simple accessors
|
|
|
|
=head2 C<< $self->months >>
|
|
|
|
Number of months of paid time to be applied.
|
|
|
|
=head2 C<< $self->class >>
|
|
|
|
Account class identifier; not for display.
|
|
|
|
=head2 C<< $self->permanent >>
|
|
|
|
Returns whether this item is for a permanent account, or just a normal paid.
|
|
|
|
=head2 C<< $self->random >>
|
|
|
|
Returns whether this item is for a random user.
|
|
|
|
=head2 C<< $self->anonymous_target >>
|
|
|
|
Returns whether this item for a random user should go to an anonymous user (true)
|
|
or to an identified user (false)
|
|
|
|
=cut
|
|
|
|
sub months { return $_[0]->{months}; }
|
|
sub class { return $_[0]->{class}; }
|
|
sub permanent { return $_[0]->months == 99; }
|
|
sub random { return $_[0]->{random}; }
|
|
sub anonymous_target { return $_[0]->{anonymous_target}; }
|
|
|
|
1;
|