657 lines
19 KiB
Perl
657 lines
19 KiB
Perl
|
|
# This code was forked from the LiveJournal project owned and operated
|
||
|
|
# by Live Journal, Inc. The code has been modified and expanded by
|
||
|
|
# Dreamwidth Studios, LLC. These files were originally licensed under
|
||
|
|
# the terms of the license supplied by Live Journal, Inc, which can
|
||
|
|
# currently be found at:
|
||
|
|
#
|
||
|
|
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
|
||
|
|
#
|
||
|
|
# In accordance with the original license, this code and all its
|
||
|
|
# modifications are provided under the GNU General Public License.
|
||
|
|
# A copy of that license can be found in the LICENSE file included as
|
||
|
|
# part of this distribution.
|
||
|
|
|
||
|
|
package LJ::Event;
|
||
|
|
|
||
|
|
use strict;
|
||
|
|
use v5.10;
|
||
|
|
no warnings 'uninitialized';
|
||
|
|
use Log::Log4perl;
|
||
|
|
my $log = Log::Log4perl->get_logger(__PACKAGE__);
|
||
|
|
|
||
|
|
use Carp qw(croak);
|
||
|
|
|
||
|
|
use DW::Task::ESN::FiredEvent;
|
||
|
|
use DW::TaskQueue;
|
||
|
|
use LJ::ModuleLoader;
|
||
|
|
use LJ::ESN;
|
||
|
|
use LJ::Subscription;
|
||
|
|
use LJ::Typemap;
|
||
|
|
|
||
|
|
my @EVENTS = LJ::ModuleLoader->module_subclasses("LJ::Event");
|
||
|
|
foreach my $event (@EVENTS) {
|
||
|
|
eval "use $event";
|
||
|
|
die "Error loading event module '$event': $@" if $@;
|
||
|
|
}
|
||
|
|
|
||
|
|
# Guide to subclasses:
|
||
|
|
# LJ::Event::JournalNewEntry -- a journal (user/community) has a new entry in it
|
||
|
|
# ($ju,$ditemid,undef)
|
||
|
|
# LJ::Event::JournalNewComment -- a journal has a new comment in it
|
||
|
|
# ($ju,$jtalkid) # TODO: should probably be ($ju,$jitemid,$jtalkid)
|
||
|
|
# LJ::Event::JournalNewComment::TopLevel -- a journal has a new top-level comment in it
|
||
|
|
# ($ju,$jitemid)
|
||
|
|
# LJ::Event::JournalNewComment::Reply -- reply to your own comment/entry or reply by you
|
||
|
|
# ($ju,$jtalkid)
|
||
|
|
# LJ::Event::AddedToCircle -- user $fromuserid added $u to their circle; $actionid is 1 (trust) or 2 (watch)
|
||
|
|
# ($u,$fromuserid,$actionid)
|
||
|
|
# LJ::Event::RemovedFromCircle -- user $fromuserid removed $u to their circle; $actionid is 1 (trust) or 2 (watch)
|
||
|
|
# ($u,$fromuserid,$actionid)
|
||
|
|
# LJ::Event::CommunityInvite -- user $fromuserid invited $u to join $commid community)
|
||
|
|
# ($u,$fromuserid, $commid)
|
||
|
|
# LJ::Event::InvitedFriendJoins -- user $u1 was invited to join by $u2 and created a journal
|
||
|
|
# ($u1, $u2)
|
||
|
|
# LJ::Event::NewUserpic -- user $u uploaded userpic $up
|
||
|
|
# ($u,$up)
|
||
|
|
# LJ::Event::UserExpunged -- user $u is expunged
|
||
|
|
# ($u)
|
||
|
|
# LJ::Event::Birthday -- user $u's birthday
|
||
|
|
# ($u)
|
||
|
|
# LJ::Event::PollVote -- $u1 voted in poll $p posted by $u
|
||
|
|
# ($u, $u1, $up)
|
||
|
|
# LJ::Event::UserMessageRecvd -- user $u received message with ID $msgid from user $otherid
|
||
|
|
# ($u, $msgid, $otherid)
|
||
|
|
# LJ::Event::UserMessageSent -- user $u sent message with ID $msgid to user $otherid
|
||
|
|
# ($u, $msgid, $otherid)
|
||
|
|
# LJ::Event::ImportStatus -- user $u has received an import status notification
|
||
|
|
# ($u, $item, $hashref)
|
||
|
|
sub new {
|
||
|
|
my ( $class, $u, @args ) = @_;
|
||
|
|
croak("too many args") if @args > 2;
|
||
|
|
croak("args must be numeric") if grep { /\D/ } @args;
|
||
|
|
croak("u isn't a user") unless LJ::isu($u);
|
||
|
|
|
||
|
|
return bless {
|
||
|
|
userid => $u->id,
|
||
|
|
args => \@args,
|
||
|
|
}, $class;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub arg_list {
|
||
|
|
return ( "Arg 1", "Arg 2" );
|
||
|
|
}
|
||
|
|
|
||
|
|
# Class method
|
||
|
|
sub new_from_raw_params {
|
||
|
|
my ( undef, $etypeid, $journalid, $arg1, $arg2 ) = @_;
|
||
|
|
|
||
|
|
my $class = LJ::Event->class($etypeid) or die "Classname cannot be undefined/false";
|
||
|
|
my $journal = LJ::load_userid($journalid) or die "Invalid journalid $journalid";
|
||
|
|
my $evt = LJ::Event->new( $journal, $arg1, $arg2 );
|
||
|
|
|
||
|
|
# bless into correct class
|
||
|
|
bless $evt, $class;
|
||
|
|
|
||
|
|
return $evt;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub raw_params {
|
||
|
|
my $self = shift;
|
||
|
|
use Data::Dumper;
|
||
|
|
my $ju = $self->event_journal
|
||
|
|
or Carp::confess( "Event $self has no journal: " . Dumper($self) );
|
||
|
|
my @params =
|
||
|
|
map { $_ + 0 } ( $self->etypeid, $ju->{userid}, $self->{args}[0], $self->{args}[1] );
|
||
|
|
return wantarray ? @params : \@params;
|
||
|
|
}
|
||
|
|
|
||
|
|
# You can override this if you need, but shouldn't have to.
|
||
|
|
sub configure_logger {
|
||
|
|
my $self = $_[0];
|
||
|
|
|
||
|
|
Log::Log4perl::MDC->put( "evt_class", $self->class );
|
||
|
|
Log::Log4perl::MDC->put( "evt_user", $self->u->user );
|
||
|
|
Log::Log4perl::MDC->put( "evt_userid", $self->u->id );
|
||
|
|
Log::Log4perl::MDC->put( "evt_arg1", $self->arg1 );
|
||
|
|
Log::Log4perl::MDC->put( "evt_arg2", $self->arg2 );
|
||
|
|
}
|
||
|
|
|
||
|
|
# Override this. by default, events are rare, so subscriptions to
|
||
|
|
# them are tracked in target's "has_subscription" table.
|
||
|
|
# for common events, change this to '1' in subclasses and events
|
||
|
|
# will always fire without consulting the "has_subscription" table
|
||
|
|
sub is_common {
|
||
|
|
0;
|
||
|
|
}
|
||
|
|
|
||
|
|
# Override this with a false value if subscriptions to this event should
|
||
|
|
# not show up in normal UI
|
||
|
|
sub is_visible { 1 }
|
||
|
|
|
||
|
|
# Override this with a true if notification to this event should be sent
|
||
|
|
# even if user account is not in active state.
|
||
|
|
sub is_significant { 0 }
|
||
|
|
|
||
|
|
# Whether Inbox is always subscribed to
|
||
|
|
sub always_checked { 0 }
|
||
|
|
|
||
|
|
# Override this with HTML containing the actual event
|
||
|
|
sub content { '' }
|
||
|
|
|
||
|
|
# Override this with HTML containing a summary of the event text (may be left blank)
|
||
|
|
sub content_summary { '' }
|
||
|
|
|
||
|
|
# Override this to provide details, method for XMLRPC::getinbox
|
||
|
|
sub raw_info {
|
||
|
|
my $self = shift;
|
||
|
|
|
||
|
|
my $subclass = ref $self;
|
||
|
|
$subclass =~ s/LJ::Event:?:?//;
|
||
|
|
|
||
|
|
return { type => $subclass };
|
||
|
|
}
|
||
|
|
|
||
|
|
sub as_string {
|
||
|
|
my ( $self, $u ) = @_;
|
||
|
|
|
||
|
|
croak "No target passed to Event->as_string" unless LJ::isu($u);
|
||
|
|
|
||
|
|
my ($classname) = ( ref $self ) =~ /Event::(.+?)$/;
|
||
|
|
return "Event $classname fired for user=$u->{user}, args=[@{$self->{args}}]";
|
||
|
|
}
|
||
|
|
|
||
|
|
# default is just return the string, override if subclass
|
||
|
|
# actually can generate pretty content
|
||
|
|
sub as_html {
|
||
|
|
my ( $self, $u ) = @_;
|
||
|
|
|
||
|
|
croak "No target passed to Event->as_string" unless LJ::isu($u);
|
||
|
|
|
||
|
|
return $self->as_string;
|
||
|
|
}
|
||
|
|
|
||
|
|
# plaintext email subject
|
||
|
|
sub as_email_subject {
|
||
|
|
my ( $self, $u ) = @_;
|
||
|
|
return $self->as_string($u);
|
||
|
|
}
|
||
|
|
|
||
|
|
# contents for HTML email
|
||
|
|
sub as_email_html {
|
||
|
|
my ( $self, $u ) = @_;
|
||
|
|
return $self->as_email_string($u);
|
||
|
|
}
|
||
|
|
|
||
|
|
# contents for plaintext email
|
||
|
|
sub as_email_string {
|
||
|
|
my ( $self, $u ) = @_;
|
||
|
|
return $self->as_string($u);
|
||
|
|
}
|
||
|
|
|
||
|
|
# the "From" line for email
|
||
|
|
sub as_email_from_name {
|
||
|
|
my ( $self, $u ) = @_;
|
||
|
|
return $LJ::SITENAMESHORT;
|
||
|
|
}
|
||
|
|
|
||
|
|
# Optional headers (for comment notifications)
|
||
|
|
sub as_email_headers {
|
||
|
|
my ( $self, $u ) = @_;
|
||
|
|
return undef;
|
||
|
|
}
|
||
|
|
|
||
|
|
# class method, takes a subscription
|
||
|
|
sub subscription_as_html {
|
||
|
|
my ( $class, $subscr ) = @_;
|
||
|
|
|
||
|
|
croak "No subscription" unless $subscr;
|
||
|
|
|
||
|
|
my $arg1 = $subscr->arg1;
|
||
|
|
my $arg2 = $subscr->arg2;
|
||
|
|
my $journalid = $subscr->journalid;
|
||
|
|
|
||
|
|
my $user = $journalid ? LJ::ljuser( LJ::load_userid($journalid) ) : "(wildcard)";
|
||
|
|
|
||
|
|
return $class . " arg1: $arg1 arg2: $arg2 user: $user";
|
||
|
|
}
|
||
|
|
|
||
|
|
# override in subclasses
|
||
|
|
sub subscription_applicable {
|
||
|
|
my ( $class, $subscr ) = @_;
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# can $u subscribe to this event?
|
||
|
|
sub available_for_user {
|
||
|
|
my ( $class, $u, $subscr ) = @_;
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# Quick way to bypass during subscription lookup
|
||
|
|
sub early_filter_event {
|
||
|
|
|
||
|
|
# arguments: ($class,$evt) = @_;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# additional SQL for subscriptions
|
||
|
|
# does not need to be prefixed with 'AND'
|
||
|
|
sub additional_subscriptions_sql {
|
||
|
|
|
||
|
|
# arguments: ($class,$evt) = @_;
|
||
|
|
return ('');
|
||
|
|
}
|
||
|
|
|
||
|
|
# valid values are nothing ("" or undef), "all", or "friends"
|
||
|
|
sub zero_journalid_subs_means {
|
||
|
|
|
||
|
|
# arguments: ($class,$evt) = @_;
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
############################################################################
|
||
|
|
# Don't override
|
||
|
|
############################################################################
|
||
|
|
|
||
|
|
sub event_journal { &u; }
|
||
|
|
sub u { LJ::load_userid( $_[0]->{userid} ) }
|
||
|
|
sub arg1 { $_[0]->{args}[0] }
|
||
|
|
sub arg2 { $_[0]->{args}[1] }
|
||
|
|
|
||
|
|
# class method
|
||
|
|
# Drains all ESN tasks from the TaskQueue synchronously. Each phase of the ESN
|
||
|
|
# pipeline can enqueue tasks for the next phase, so we loop until all four
|
||
|
|
# queues are empty.
|
||
|
|
sub process_fired_events {
|
||
|
|
my $class = shift;
|
||
|
|
croak("Can't call in web context") if LJ::is_web_context();
|
||
|
|
|
||
|
|
my $tsq = DW::TaskQueue->get;
|
||
|
|
|
||
|
|
my @esn_classes = qw(
|
||
|
|
DW::Task::ESN::FiredEvent
|
||
|
|
DW::Task::ESN::FindSubsByCluster
|
||
|
|
DW::Task::ESN::FilterSubs
|
||
|
|
DW::Task::ESN::ProcessSub
|
||
|
|
);
|
||
|
|
|
||
|
|
my $found = 1;
|
||
|
|
while ($found) {
|
||
|
|
$found = 0;
|
||
|
|
foreach my $task_class (@esn_classes) {
|
||
|
|
while (1) {
|
||
|
|
my $messages = $tsq->receive( $task_class, 100, 0 );
|
||
|
|
last unless @{ $messages || [] };
|
||
|
|
$found = 1;
|
||
|
|
foreach my $pair (@$messages) {
|
||
|
|
my ( $handle, $task ) = @$pair;
|
||
|
|
$task->work($handle);
|
||
|
|
$tsq->completed( $task_class, $handle );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# instance method.
|
||
|
|
# fire either logs the event to the delayed work system to be
|
||
|
|
# processed later, or does nothing, if it's a rare event and there
|
||
|
|
# are no subscriptions for the event.
|
||
|
|
sub fire {
|
||
|
|
|
||
|
|
# The TaskQueue knows how to convert us to an appropriate job or task and
|
||
|
|
# schedule is in the correct place.
|
||
|
|
return DW::TaskQueue->dispatch( $_[0] ) ? 1 : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub fire_task {
|
||
|
|
my $self = $_[0];
|
||
|
|
return unless $self->should_enqueue;
|
||
|
|
return DW::Task::ESN::FiredEvent->new( $self->raw_params );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub subscriptions {
|
||
|
|
my ( $self, %args ) = @_;
|
||
|
|
my $cid_in = delete $args{'cluster'}; # optional
|
||
|
|
my $limit = delete $args{'limit'}; # optional
|
||
|
|
my $scratch = {};
|
||
|
|
croak( "Unknown options: " . join( ', ', keys %args ) ) if %args;
|
||
|
|
croak("Can't call in web context") if LJ::is_web_context();
|
||
|
|
|
||
|
|
$scratch->{limit_remain} = $limit;
|
||
|
|
|
||
|
|
my @subs;
|
||
|
|
|
||
|
|
my @event_classes = grep { $_->early_filter_event($self) } $self->related_event_classes;
|
||
|
|
|
||
|
|
foreach my $cid ( $cid_in ? ($cid_in) : @LJ::CLUSTERS ) {
|
||
|
|
last if $limit && $scratch->{limit_remain} <= 0;
|
||
|
|
foreach my $class (@event_classes) {
|
||
|
|
last if $limit && $scratch->{limit_remain} <= 0;
|
||
|
|
my $etypeid = $class->etypeid;
|
||
|
|
$scratch->{"evt:$etypeid"} //= {};
|
||
|
|
push @subs, $class->raw_subscriptions( $self, scratch => $scratch, cluster => $cid );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return @subs;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub raw_subscriptions {
|
||
|
|
my ( $class, $self, %args ) = @_;
|
||
|
|
my $cid = delete $args{'cluster'};
|
||
|
|
croak("Cluser id (cluster) must be provided") unless defined $cid;
|
||
|
|
|
||
|
|
my $scratch = delete $args{'scratch'} || {}; # optional
|
||
|
|
|
||
|
|
croak( "Unknown options: " . join( ', ', keys %args ) ) if %args;
|
||
|
|
croak("Can't call in web context") if LJ::is_web_context();
|
||
|
|
|
||
|
|
# allsubs
|
||
|
|
my @subs;
|
||
|
|
|
||
|
|
my $etypeid = $class->etypeid;
|
||
|
|
my $evt_scratch = $scratch->{"evt:$etypeid"} // {};
|
||
|
|
|
||
|
|
my $limit_remain = $scratch->{limit_remain};
|
||
|
|
my $and_enabled =
|
||
|
|
"AND flags & " . ( LJ::Subscription->INACTIVE | LJ::Subscription->DISABLED ) . " = 0";
|
||
|
|
|
||
|
|
return if defined $limit_remain && $limit_remain <= 0;
|
||
|
|
|
||
|
|
my $allmatch = 0;
|
||
|
|
my $zeromeans;
|
||
|
|
my ( $addl_sql, @addl_args );
|
||
|
|
my @wildcards_from;
|
||
|
|
|
||
|
|
if ( defined $evt_scratch->{allmatch} ) {
|
||
|
|
$zeromeans = $evt_scratch->{zeromeans};
|
||
|
|
$allmatch = $evt_scratch->{allmatch};
|
||
|
|
@wildcards_from = @{ $evt_scratch->{wildcards_from} };
|
||
|
|
$addl_sql = $evt_scratch->{addl_sql};
|
||
|
|
@addl_args = @{ $evt_scratch->{addl_args} };
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$zeromeans = $class->zero_journalid_subs_means($self);
|
||
|
|
|
||
|
|
( $addl_sql, @addl_args ) = $class->additional_subscriptions_sql($self);
|
||
|
|
$addl_sql = " AND ( $addl_sql )" if $addl_sql;
|
||
|
|
|
||
|
|
if ( $zeromeans eq 'trusted' ) {
|
||
|
|
@wildcards_from = $self->u->trusted_by_userids;
|
||
|
|
}
|
||
|
|
elsif ( $zeromeans eq 'watched' ) {
|
||
|
|
@wildcards_from = $self->u->watched_by_userids;
|
||
|
|
}
|
||
|
|
elsif ( $zeromeans eq 'trusted_or_watched' ) {
|
||
|
|
my %unique_ids =
|
||
|
|
map { $_ => 1 } ( $self->u->trusted_by_userids, $self->u->watched_by_userids );
|
||
|
|
@wildcards_from = keys %unique_ids;
|
||
|
|
}
|
||
|
|
elsif ( $zeromeans eq 'all' ) {
|
||
|
|
$allmatch = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
$evt_scratch->{zeromeans} = $zeromeans;
|
||
|
|
$evt_scratch->{allmatch} = $allmatch;
|
||
|
|
$evt_scratch->{wildcards_from} = \@wildcards_from;
|
||
|
|
$evt_scratch->{addl_sql} = $addl_sql;
|
||
|
|
$evt_scratch->{addl_args} = \@addl_args;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $dbcm = LJ::get_cluster_master($cid)
|
||
|
|
or die;
|
||
|
|
|
||
|
|
# first we find exact matches (or all matches)
|
||
|
|
my $journal_match = $allmatch ? "" : "AND journalid=?";
|
||
|
|
my $limit_sql = $limit_remain ? "LIMIT $limit_remain" : '';
|
||
|
|
my $sql =
|
||
|
|
"SELECT userid, subid, is_dirty, journalid, etypeid, "
|
||
|
|
. "arg1, arg2, ntypeid, createtime, expiretime, flags "
|
||
|
|
. "FROM subs WHERE etypeid = ? $journal_match $and_enabled $addl_sql $limit_sql";
|
||
|
|
|
||
|
|
my $sth = $dbcm->prepare($sql);
|
||
|
|
my @args = ($etypeid);
|
||
|
|
push @args, $self->u->id unless $allmatch;
|
||
|
|
$sth->execute( @args, @addl_args );
|
||
|
|
if ( $sth->err ) {
|
||
|
|
warn "SQL: [$sql], args=[@args], addl_args=[@addl_args]\n";
|
||
|
|
die $sth->errstr;
|
||
|
|
}
|
||
|
|
|
||
|
|
while ( my $row = $sth->fetchrow_hashref ) {
|
||
|
|
push @subs, LJ::Subscription->new_from_row($row);
|
||
|
|
}
|
||
|
|
|
||
|
|
# then we find wildcard matches.
|
||
|
|
if (@wildcards_from) {
|
||
|
|
|
||
|
|
# FIXME: journals are only on one cluster! split jidlist based on cluster
|
||
|
|
my $jidlist = join( ",", @wildcards_from );
|
||
|
|
|
||
|
|
my $sth =
|
||
|
|
$dbcm->prepare( "SELECT userid, subid, is_dirty, journalid, etypeid, "
|
||
|
|
. "arg1, arg2, ntypeid, createtime, expiretime, flags "
|
||
|
|
. "FROM subs USE INDEX(PRIMARY) WHERE etypeid = ? AND journalid=0 $and_enabled AND userid IN ($jidlist) $addl_sql"
|
||
|
|
);
|
||
|
|
|
||
|
|
$sth->execute( $etypeid, @addl_args );
|
||
|
|
die $sth->errstr if $sth->err;
|
||
|
|
|
||
|
|
while ( my $row = $sth->fetchrow_hashref ) {
|
||
|
|
push @subs, LJ::Subscription->new_from_row($row);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$limit_remain -= @subs;
|
||
|
|
|
||
|
|
$scratch->{limit_remain} = $limit_remain
|
||
|
|
if defined $scratch->{limit_remain};
|
||
|
|
|
||
|
|
return @subs;
|
||
|
|
}
|
||
|
|
|
||
|
|
# helper method to be called when overriding parent's raw_subscriptions
|
||
|
|
# method to always return a subscription object for the user
|
||
|
|
sub _raw_always_subscribed {
|
||
|
|
my ( $class, $self, %args ) = @_;
|
||
|
|
my $cid = delete $args{'cluster'};
|
||
|
|
croak("Cluser id (cluster) must be provided") unless defined $cid;
|
||
|
|
|
||
|
|
my $scratch = delete $args{'scratch'}; # optional
|
||
|
|
|
||
|
|
# hash keys specific to this helper method
|
||
|
|
my $skip_parent = delete $args{'skip_parent'}; # optional
|
||
|
|
my $ntypeid = delete $args{'ntypeid'};
|
||
|
|
croak("Failed to provide ntypeid") unless defined $ntypeid;
|
||
|
|
|
||
|
|
croak( "Unknown options: " . join( ', ', keys %args ) ) if %args;
|
||
|
|
croak("Can't call in web context") if LJ::is_web_context();
|
||
|
|
|
||
|
|
my @subs;
|
||
|
|
my $u = $self->u;
|
||
|
|
return unless $cid == $u->clusterid;
|
||
|
|
|
||
|
|
my $row = {
|
||
|
|
userid => $self->u->id,
|
||
|
|
ntypeid => $ntypeid,
|
||
|
|
etypeid => $class->etypeid,
|
||
|
|
};
|
||
|
|
|
||
|
|
push @subs, LJ::Subscription->new_from_row($row);
|
||
|
|
|
||
|
|
push @subs,
|
||
|
|
eval { LJ::Event::raw_subscriptions( $class, $self, cluster => $cid, scratch => $scratch ) }
|
||
|
|
unless $skip_parent;
|
||
|
|
|
||
|
|
return @subs;
|
||
|
|
}
|
||
|
|
|
||
|
|
# INSTANCE METHOD: SHOULD OVERRIDE if the subscriptions support filtering
|
||
|
|
sub matches_filter {
|
||
|
|
my ( $self, $subsc ) = @_;
|
||
|
|
|
||
|
|
return 0 unless $subsc->available_for_user;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# instance method. Override if possible.
|
||
|
|
# returns when the event happened, or undef if unknown
|
||
|
|
sub eventtime_unix {
|
||
|
|
return undef;
|
||
|
|
}
|
||
|
|
|
||
|
|
# instance method
|
||
|
|
sub should_enqueue {
|
||
|
|
my $self = shift;
|
||
|
|
return 1; # for now.
|
||
|
|
return $self->is_common || $self->has_subscriptions;
|
||
|
|
}
|
||
|
|
|
||
|
|
# instance method
|
||
|
|
# Override this to have notifications for an event show up as read
|
||
|
|
sub mark_read {
|
||
|
|
my $self = shift;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
# instance method
|
||
|
|
sub has_subscriptions {
|
||
|
|
my $self = shift;
|
||
|
|
return 1; # FIXME: consult "has_subs" table
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_subscriptions {
|
||
|
|
my ( $self, $u, $subid ) = @_;
|
||
|
|
|
||
|
|
return LJ::Subscription->new_by_id( $u, $subid );
|
||
|
|
}
|
||
|
|
|
||
|
|
# get the typemap for the subscriptions classes (class/instance method)
|
||
|
|
sub typemap {
|
||
|
|
return LJ::Typemap->new(
|
||
|
|
table => 'eventtypelist',
|
||
|
|
classfield => 'class',
|
||
|
|
idfield => 'etypeid',
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
# returns the class name, given an etypid
|
||
|
|
sub class {
|
||
|
|
my ( $class, $typeid ) = @_;
|
||
|
|
my $tm = $class->typemap
|
||
|
|
or return undef;
|
||
|
|
|
||
|
|
$typeid ||= $class->etypeid;
|
||
|
|
|
||
|
|
return $tm->typeid_to_class($typeid);
|
||
|
|
}
|
||
|
|
|
||
|
|
# returns the eventtypeid for this site.
|
||
|
|
# don't override this in subclasses.
|
||
|
|
sub etypeid {
|
||
|
|
my ($class_self) = @_;
|
||
|
|
my $class = ref $class_self ? ref $class_self : $class_self;
|
||
|
|
|
||
|
|
my $tm = $class->typemap
|
||
|
|
or return undef;
|
||
|
|
|
||
|
|
return $tm->class_to_typeid($class);
|
||
|
|
}
|
||
|
|
|
||
|
|
# return a list of related events, for considering as one group
|
||
|
|
# to avoid dupes when processing subs
|
||
|
|
# list includes your own etypeid
|
||
|
|
sub related_event_classes {
|
||
|
|
return $_[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
sub related_events {
|
||
|
|
return return map { $_->etypeid } $_[0]->related_event_classes;
|
||
|
|
}
|
||
|
|
|
||
|
|
# Class method
|
||
|
|
sub event_to_etypeid {
|
||
|
|
my ( $class, $evt_name ) = @_;
|
||
|
|
$evt_name = "LJ::Event::$evt_name" unless $evt_name =~ /^LJ::Event::/;
|
||
|
|
my $tm = $class->typemap
|
||
|
|
or return undef;
|
||
|
|
return $tm->class_to_typeid($evt_name);
|
||
|
|
}
|
||
|
|
|
||
|
|
# this returns a list of all possible event classes
|
||
|
|
# class method
|
||
|
|
sub all_classes {
|
||
|
|
my $class = shift;
|
||
|
|
|
||
|
|
# return config'd classes if they exist, otherwise just return everything that has a mapping
|
||
|
|
return @LJ::EVENT_TYPES if @LJ::EVENT_TYPES;
|
||
|
|
|
||
|
|
croak "all_classes is a class method" unless $class;
|
||
|
|
|
||
|
|
my $tm = $class->typemap
|
||
|
|
or croak "Bad class $class";
|
||
|
|
|
||
|
|
return $tm->all_classes;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub format_options {
|
||
|
|
my ( $self, $is_html, $lang, $vars, $urls, $extra ) = @_;
|
||
|
|
|
||
|
|
my ( $tag_p, $tag_np, $tag_li, $tag_nli, $tag_ul, $tag_nul, $tag_br ) =
|
||
|
|
( '', '', '', '', '', '', "\n" );
|
||
|
|
|
||
|
|
if ($is_html) {
|
||
|
|
$tag_p = '<p>';
|
||
|
|
$tag_np = '</p>';
|
||
|
|
$tag_li = '<li>';
|
||
|
|
$tag_nli = '</li>';
|
||
|
|
$tag_ul = '<ul>';
|
||
|
|
$tag_nul = '</ul>';
|
||
|
|
}
|
||
|
|
|
||
|
|
my $options = $tag_br . $tag_br . $tag_ul;
|
||
|
|
|
||
|
|
if ($is_html) {
|
||
|
|
$vars->{'closelink'} = '</a>';
|
||
|
|
$options .= join(
|
||
|
|
'',
|
||
|
|
map {
|
||
|
|
my $key = $_;
|
||
|
|
$vars->{'openlink'} = '<a href="' . $urls->{$key}->[1] . '">';
|
||
|
|
$tag_li . LJ::Lang::get_text( $lang, $key, undef, $vars ) . $tag_nli;
|
||
|
|
}
|
||
|
|
sort { $urls->{$a}->[0] <=> $urls->{$b}->[0] }
|
||
|
|
grep { $urls->{$_}->[0] }
|
||
|
|
keys %$urls
|
||
|
|
);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$vars->{'openlink'} = '';
|
||
|
|
$vars->{'closelink'} = '';
|
||
|
|
$options .= join(
|
||
|
|
'',
|
||
|
|
map {
|
||
|
|
my $key = $_;
|
||
|
|
' - '
|
||
|
|
. LJ::Lang::get_text( $lang, $key, undef, $vars ) . ":\n" . ' '
|
||
|
|
. $urls->{$key}->[1] . "\n"
|
||
|
|
}
|
||
|
|
sort { $urls->{$a}->[0] <=> $urls->{$b}->[0] }
|
||
|
|
grep { $urls->{$_}->[0] }
|
||
|
|
keys %$urls
|
||
|
|
);
|
||
|
|
chomp($options);
|
||
|
|
}
|
||
|
|
|
||
|
|
$options .= $extra if $extra;
|
||
|
|
|
||
|
|
$options .= $tag_nul . $tag_br;
|
||
|
|
|
||
|
|
return $options;
|
||
|
|
}
|
||
|
|
|
||
|
|
1;
|