LJSUP-17669: Login.bml form refactoring
[livejournal.git] / cgi-bin / LJ / Event.pm
blobc5068823eba5dd2541d4afd1332edf30d1ea6e66
1 =comment
3 LJ::Event module: the "event" part of the Event-Subscription-Notification (ESN)
4 subsystem. See comments in LJ::ESN for information about how ESN works.
6 ESN Event is a "value object" in the sense that it never gets stored in the DB
7 (it may be passed to workers, however). Event object is essentially a structure
8 with the following fields:
10 * event type (etypeid), which can be translated to a subclass of LJ::Event;
11 LJ::Typemap is used to maintain this translation.
12 * journal in which the event happened (userid).
13 * two arguments, arg1 and arg2; meaning of these arguments is determined by
14 the logic of specific LJ::Event subclass.
16 Note that Event is not a Subscription, so it doesn't have information about
17 which user is supposed to receive a Notification about it. Functions that
18 are supposed to return information about various Event-User relationships
19 (e.g. "can this user subscribe to this event?") receive an LJ::User object
20 as one of their parameters.
22 LJ::Event is an abstract class, only implementing functionality common to all
23 event types. There is a number of LJ::Event::* subclasses which implement
24 event-specific functionality.
26 Guide to subclasses:
27 * JournalNewEntry -- a journal has a new entry in it
28 * JoutnalNewEntry -- a journal has a new repost in it
29 * JournalNewComment -- a journal has a new comment in it
30 * CommunityEntryReply -- a user's community entry has received a comment
31 * CommentReply -- someone replied to a user's comment
32 * Befriended -- one user added another as a friend
33 * Defriended -- one user removed another from their friends list
34 * CommunityInvite -- one user invited another to a community
35 * InvitedFriendJoins -- a person invited to LiveJournal by a user has created a journal
36 * NewUserpic -- a user uploaded a userpic
37 * UserExpunged -- a user has been purged from the servers
38 * Birthday -- a user has upcoming birthday
39 * PollVote -- a user voted in a poll
40 * UserMessageRecvd -- a user has received a message from another
41 * UserMessageSent -- a user has sent a message to another
43 In this module, "ExampleEvent" will be used for documentation purposes only.
44 (And we assume that $example_etypeid is its etypeid.) It is not an actually
45 existing event.
47 Please note that there are two possible uses for LJ::Event object:
49 * "Fire event" -- send Schwartz a notification that the event has occured.
50 * As a value object -- to store information about an event or a group of events.
51 These objects do not necessarily represent valid events; for example, they
52 may not have the "journal" field set.
54 =cut
56 package LJ::Event;
57 use strict;
58 no warnings 'uninitialized';
60 use Carp qw(confess);
61 use LJ::ESN;
62 use LJ::ModuleLoader;
63 use LJ::Subscription;
64 use LJ::Typemap;
65 use LJ::Text;
67 use constant {
68 BASE_PRIORITY => 0,
71 ### COMMON FUNCTIONS ###
73 # create a new event structure based on its type, journal and arguments
75 # my $event = LJ::Event::ExampleEvent->new($u, $arg1, $arg2);
76 sub new {
77 my ($class, $u, @args) = @_;
78 confess("too many args") if @args > 4;
80 my $userid = LJ::want_userid($u);
82 return bless {
83 userid => $userid,
84 args => \@args,
85 user => $u,
86 }, $class;
89 # create a new event structure based on its type, journal and arguments
90 # difference from "new" is that all arguments are integers: etypeid for
91 # determining type, and userid for journalid for determining journal.
93 # my $event =
94 # LJ::Event->new_from_raw_params($example_etypeid, $u->id, $arg1, $arg2, $arg3, $arg4);
95 sub new_from_raw_params {
96 my (undef, $etypeid, $journalid, @args ) = @_;
98 my $class = LJ::Event->class($etypeid) or confess "Classname cannot be undefined/false";
99 my $journal = LJ::load_userid($journalid);
100 my $evt = LJ::Event->new($journal, @args);
102 # Store etypeid in instance to check it faster
103 $evt->{'etypeid'} = $etypeid;
105 # bless into correct class
106 bless $evt, $class;
108 return $evt;
111 # return an array or an arrayref of event fields: etypeid, journalid, args
113 # my ($etypeid, $journalid, $arg1, $arg2) = $event->raw_params;
115 # my $params = $event->raw_params;
116 # my ($etypeid, $journalid, $arg1, $arg2) = @$params;
117 sub raw_params {
118 my $self = shift;
120 my $ju = $self->event_journal;
121 Carp::confess("Event $self has no journal")
122 unless $ju || $self->allow_emails_to_unauthorised;
124 my $uid = $ju ? $ju->id : 0 ;
126 my @params = map { $_ || 0 } (
127 $self->etypeid,
128 $uid,
129 @{$self->{args}},
131 return wantarray ? @params : \@params;
134 # "getter" methods; all of these are final.
136 # my $journal = $event->event_journal;
137 # my $journal = $event->u;
138 # my $arg1 = $event->arg1;
139 sub userid { $_[0]->{userid}; }
141 sub u { return $_[0]->{'u'} ||= LJ::load_userid($_[0]->{'userid'}); }
143 *event_journal = \&u;
145 sub arg1 { $_[0]->{args}[0] }
146 sub arg2 { $_[0]->{args}[1] }
148 # alias for LJ::ESN->process_fired_events
149 sub process_fired_events {
150 my $class = shift;
151 confess("Can't call in web context") if LJ::is_web_context();
152 LJ::ESN->process_fired_events;
155 # returns a scalar indicating what a journal=0 wildcard means in a subscription
156 # of this type.
158 # valid values are nothing ("" or undef), "all", or "friends"
160 # this is a virtual function; base class function returns "" for nothing.
162 # warn "not notifying"
163 # unless $sub->journal || $event->zero_journalid_subs_means;
164 sub zero_journalid_subs_means { "" }
166 # returns a boolean value indicating whether the inbox notification for this
167 # event should initially come already read.
169 # this is a virtual function; base class function returns 0 for "initially
170 # mark unread"
172 # $inbox->mark_read($notification) if $event->mark_read;
173 sub mark_read {
174 my $self = shift;
175 return 0;
178 # return the typemap for the Events classes
180 # my $tm = LJ::Event->typemap;
181 # my $class = $tm->typeid_to_class($etypeid);
182 sub typemap {
183 return LJ::Typemap->new(
184 table => 'eventtypelist',
185 classfield => 'class',
186 idfield => 'etypeid',
190 my (%classes, %etypeids);
192 # return the class name, given an etypeid
194 # my $class = LJ::Event->class($etypeid);
195 sub class {
196 my ($class, $typeid) = @_;
198 $typeid ||= $class->etypeid;
200 unless ($classes{$typeid}) {
201 my $tm = $class->typemap
202 or return undef;
204 $classes{$typeid} = $tm->typeid_to_class($typeid);
207 return $classes{$typeid};
210 # return etypeid for the class
212 # my $etypeid = LJ::Event::ExampleEvent->etypeid;
213 sub etypeid {
214 my $invocant = $_[0];
215 my $class;
217 if (ref $invocant) {
218 return $invocant->{'etypeid'}
219 if $invocant->{'etypeid'};
221 $class = ref $invocant;
222 } else {
223 $class = $invocant;
226 unless ($etypeids{$class}) {
227 my $tm = $class->typemap
228 or return undef;
230 $etypeids{$class} = $tm->class_to_typeid($class);
233 return $etypeids{$class};
236 # return etypeid for the given class
238 # my $etypeid = LJ::Event->event_to_etypeid('ExampleEvent');
239 # my $etypeid = LJ::Event->event_to_etypeid('LJ::Event::ExampleEvent');
240 sub event_to_etypeid {
241 my ($class, $evt_name) = @_;
243 $evt_name = "LJ::Event::$evt_name" unless $evt_name =~ /^LJ::Event::/;
245 return undef
246 unless $class->typemap->class_to_typeid($evt_name);
248 my $tm = $class->typemap
249 or return undef;
250 return $tm->class_to_typeid($evt_name);
253 # return an array listing all LJ::Event subclasses
255 # my @classes = LJ::Event->all_classes;
256 sub all_classes {
257 my $class = shift;
259 # return config'd classes if they exist, otherwise just return everything that has a mapping
260 return @LJ::EVENT_TYPES if @LJ::EVENT_TYPES;
262 confess "all_classes is a class method" unless $class;
264 my $tm = $class->typemap
265 or confess "Bad class $class";
267 return $tm->all_classes;
270 sub priority {
271 return BASE_PRIORITY;
274 # return string containing nicely-represented list of links to go with
275 # the notification (the "now that you're receiving this notification, you
276 # can" one)
278 # my $lang = 'en';
279 # my $mlvars = {
280 # 'aopts' => qq{ href="$LJ::SITEROOT"; },
281 # };
282 # my $urls = {
283 # # key is an ML key (and $mlvars are used to expand it), value is an
284 # # arrayref
286 # # the first element of that arrayref is a sort order number; passing 0
287 # # there prevents the element from showing.
289 # # the second element is where the link should point.
290 # 'esn.smile' => [ 1, $LJ::SITEROOT ],
291 # 'esn.facepalm' => [ 0, $LJ::SITEROOT ],
292 # };
293 # my $extra = "additional blinky text";
294 # my $str = LJ::Event->format_options($is_html, $lang, $mlvars, $urls, $extra);
295 sub format_options {
296 my ($self, $is_html, $lang, $vars, $urls, $extra) = @_;
298 my ($tag_p, $tag_np, $tag_li, $tag_nli, $tag_ul, $tag_nul, $tag_br) = ('','','','','','',"\n");
300 if ($is_html) {
301 $tag_p = '<p>'; $tag_np = '</p>';
302 $tag_li = '<li>'; $tag_nli = '</li>';
303 $tag_ul = '<ul>'; $tag_nul = '</ul>';
306 my $options = $tag_br . $tag_br . $tag_ul;
308 if ($is_html) {
309 $vars->{'closelink'} = '</a>';
310 $options .=
311 join('',
312 map {
313 my $key = $_;
314 $vars->{'openlink'} = '<a href="' . $urls->{$key}->[1] . '">';
315 $tag_li . LJ::Lang::get_text($lang, $key, undef, $vars) . $tag_nli;
317 sort { $urls->{$a}->[0] <=> $urls->{$b}->[0] }
318 grep { $urls->{$_}->[0] }
319 keys %$urls);
320 } else {
321 $vars->{'openlink'} = '';
322 $vars->{'closelink'} = '';
323 $options .=
324 join('',
325 map {
326 my $key = $_;
327 ' - ' . LJ::Lang::get_text($lang, $key, undef, $vars) . ":\n" .
328 ' ' . $urls->{$key}->[1] . "\n"
330 sort { $urls->{$a}->[0] <=> $urls->{$b}->[0] }
331 grep { $urls->{$_}->[0] }
332 keys %$urls);
333 chomp($options);
336 $options .= $extra if $extra;
338 $options .= $tag_nul . $tag_br;
340 return $options;
343 ### FIRED EVENT FUNCTIONS ###
345 # return a boolean value specifying whether we should notify the user
346 # regardless of their account state -- for example, if they are suspended.
348 # this is a virtual function; base class function returns "do not notify",
349 # meaning that only visible users with their emails validated are notified.
351 # return unless $u->is_visible || $event->is_significant;
352 sub is_significant { 0 }
354 # return HTML code representing the event.
356 # this is a purely virtual function; base class function returns an empty
357 # string.
359 # my $html = $event->content;
360 sub content { '' }
362 # return a hashref representing information returned by the getinbox protocol
363 # and XML-RPC method.
365 # this is a virtual function; base class function returns a hashref containing
366 # information about event class only.
368 # my $hashref = $event->raw_info;
369 # print $hashref->{'type'};
370 sub raw_info {
371 my $self = shift;
373 my $subclass = ref $self;
374 $subclass =~ s/LJ::Event:?:?//;
376 return { type => $subclass };
379 # return a string representing a notification sent to the passed user notifying
380 # them that this event has happened.
382 # this is a virtual function; base class function returns a dummy string
383 # listing information about the event class and its arguments. it does not
384 # do anything trying to parse arguments.
386 # print $event->as_string($u);
387 sub as_string {
388 my ($self, $u) = @_;
390 confess "No target passed to Event->as_string" unless LJ::isu($u);
392 my ($classname) = (ref $self) =~ /Event::(.+?)$/;
393 return "Event $classname fired for user=$u->{user}, args=[@{$self->{args}}]";
396 # return HTML code representing a notification sent to the passed user notifying
397 # them that this event has happened.
399 # this is a virtual function; base class function returns whatever "as_string"
400 # method returns.
402 # print $event->as_html($u);
403 sub as_html {
404 my ($self, $u) = @_;
406 confess "No target passed to Event->as_string" unless LJ::isu($u);
408 return $self->as_string;
411 sub tmpl_params {return {}}
413 # return a string representing an IM (Jabber) notification sent to the passed
414 # user notifying them that this event has happened.
416 # this is a virtual function; base class function returns whatever "as_string"
417 # method returns.
419 # $ljtalk->send($u, $event->as_im($u));
420 sub as_im {
421 my ($self, $u) = @_;
422 return $self->as_string($u);
425 # return a string representing an "Alerts" (Windows Live Messenger)
426 # notification sent to the passed user notifying them that this event has
427 # happened.
429 # this is a virtual function; base class function returns whatever "as_string"
430 # method returns.
432 # $wlm->send($u, $event->as_alert($u));
433 sub as_alert {
434 my ($self, $u) = @_;
435 return $self->as_string($u);
438 # return a string representing an "Web Notification" body (or another popup
439 # notification type)
440 # notification sent to the passed user notifying them that this event has
441 # happened.
443 # this is a virtual function; base class function returns whatever default
444 # structure method returns.
446 # $wlm->send($u, $event->as_web_body($u));
447 sub as_web_notification {
448 my ($self, $u) = @_;
449 return { title => $self->as_email_subject($u),
450 content => $self->as_string($u),
451 tag => '',
452 icon => '',
453 url => '' };
456 # return a string representing an email subject of an email notification sent
457 # to the passed user notifying them that this event has happened.
459 # this is a virtual function; base class function returns whatever "as_string"
460 # method returns.
462 # my $subject = $event->as_email_subject($u);
463 sub as_email_subject {
464 my ($self, $u) = @_;
465 return $self->as_string($u);
468 # return a string representing HTML content of an email notification sent
469 # to the passed user notifying them that this event has happened.
471 # this is a virtual function; base class function returns whatever
472 # "as_email_string" method returns.
474 # my $body_html = $event->as_email_html($u);
475 sub as_email_html {
476 my ($self, $u) = @_;
477 return $self->as_email_string($u);
480 # return a string representing plain text content of an email notification sent
481 # to the passed user notifying them that this event has happened.
483 # this is a virtual function; base class function returns whatever
484 # "as_string" method returns.
486 # my $body_text = $event->as_email_string($u);
487 sub as_email_string {
488 my ($self, $u) = @_;
489 return $self->as_string($u);
492 # return a string representing the "From:" header of an email notification sent
493 # to the passed user notifying them that this event has happened.
495 # this is a virtual function; base class function returns $LJ::SITENAMESHORT.
497 # my $from = $event->as_email_from_name($u);
498 sub as_email_from_name {
499 my ($self, $u) = @_;
500 return $LJ::SITENAMESHORT;
503 # return a hashref representing additional headers of an email notification sent
504 # to the passed user notifying them that this event has happened.
506 # this is a virtual function; base class function returns undef, which means
507 # "no additional headers".
509 # my $headers = $event->as_email_headers($u);
510 # print $headers->{'Message-ID'};
511 sub as_email_headers {
512 my ($self, $u) = @_;
513 return undef;
516 # return a boolean value indicating that email notifications of this event need
517 # a standard footer (ml(esn.email.html.footer)) appended to them.
519 # this is a virtual function; the base class function returns 1 for "yes".
521 # if ($evt->need_standard_footer) { $html .= BML::ml('esn.email.html.footer'); }
522 sub need_standard_footer { 1 }
524 # return a boolean value indicating that email notifications of this event need
525 # a promo appended to them.
527 # this is a virtual function; the base class function returns 1 for "yes".
528 sub show_promo { 1 }
530 # return a string representing an "SMS" (TxtLJ) notification sent to the passed
531 # user notifying them that this event has happened.
533 # this is a virtual function; base class function returns whatever "as_string"
534 # method returns, truncated to 160 chars with an EBCDIC ellipsis ('...') added.
536 # $txtlj->send($u, $event->as_sms($u));
537 sub as_sms {
538 my ($self, $u, $opt) = @_;
539 return LJ::Text->truncate_with_ellipsis(
540 'str' => $self->as_string($u, $opt),
541 'bytes' => 160,
542 'ellipsis' => '...',
546 sub as_push { warn "method 'as_push' has to be overriden in ".ref(shift)."!"; return '' }
548 sub as_push_payload { warn "method 'as_push_payload' has to be overriden in ".ref(shift)."!"; return ''}
552 # Returns a string representing a Schwartz role [queue] used to handle this event
553 # By default returns undef, which is ok for most cases.
554 sub schwartz_role {
555 my $self = shift;
556 my $class = ref $self || $self;
558 return $LJ::SCHWARTZ_ROLE_FOR_ESN_CLASS{$class};
561 # insert a job for TheSchwartz to process this event.
563 # $event->fire;
564 sub fire {
565 my $self = shift;
566 return 0 if $LJ::DISABLED{'esn'};
568 my $sclient = LJ::theschwartz( { role => $self->schwartz_role } );
569 return 0 unless $sclient;
571 my $job = $self->fire_job or
572 return 0;
574 $job->priority($self->priority);
576 my $h = $sclient->insert($job);
577 return $h ? 1 : 0;
580 # returns a Schwartz Job object to process this event
582 # my $job = $event->fire_job;
583 sub fire_job {
584 my $self = shift;
585 return if $LJ::DISABLED{'esn'};
587 if (my $val = $LJ::DEBUG{'firings'}) {
588 if (ref $val eq "CODE") {
589 $val->($self);
590 } else {
591 warn $self->as_string( $self->event_journal ) . "\n";
595 return TheSchwartz::Job->new_from_array("LJ::Worker::FiredEvent", [ $self->raw_params ]);
598 # returns an array of subscriptions that MAY match this event; it is not
599 # necessary for all of them to match it -- it is needed to further filter
600 # them to remove ones that do not match.
602 # this is a virtual function; base class function only filters by etypeid,
603 # journal, and flags (must be active). it doesn't filter by
604 # arg1/arg2, whatever these might mean in a subclss.
606 # it may be senseful to override this in a subclass to allow for additional
607 # subscriptions to be triggered.
609 # my @subs = $event->subscriptions(
610 # 'cluster' => 1, # optional, search the subs table on the given cluster
611 # 'limit' => 5000, # optional, return no more than $limit subscriptions
612 # );
613 sub subscriptions {
614 my ($self, %args) = @_;
615 my $cid = delete $args{'cluster'};
616 my $limit = delete $args{'limit'};
617 confess("Unknown options: " . join(', ', keys %args)) if %args;
618 confess("Can't call in web context") if LJ::is_web_context();
620 # allsubs
621 my @subs;
623 my $allmatch = 0;
624 my $zeromeans = $self->zero_journalid_subs_means;
626 my @wildcards_from;
628 if ($zeromeans eq 'friends') {
629 # find friendofs, add to @wildcards_from
630 @wildcards_from = $self->u->friendof_uids();
632 elsif ($zeromeans eq 'all') {
633 $allmatch = 1;
636 my $limit_remain = $limit;
638 # SQL to match only on active subs
639 my $and_enabled = "AND flags & " .
640 LJ::Subscription->INACTIVE . " = 0";
642 # TODO: gearman parallelize:
643 foreach my $cid ($cid ? ($cid) : @LJ::CLUSTERS) {
644 # we got enough subs
645 last if $limit && $limit_remain <= 0;
647 ## hack: use inactive server of user cluster to find subscriptions
648 ## LJ::DBUtil wouldn't load in web-context
649 ## inactive DB may be unavailable due to backup, or on dev servers
650 ## TODO: check that LJ::get_cluster_master($cid) in other parts of code
651 ## will return handle to 'active' db, not cached 'inactive' db handle
652 my $udbh = '';
654 if (not $LJ::DISABLED{'try_to_load_subscriptions_from_slave'}){
655 $udbh = eval {
656 require 'LJ/DBUtil.pm';
657 LJ::DBUtil->get_inactive_db($cid); # connect to slave
661 $udbh ||= LJ::get_cluster_master($cid); # default (master) connect
663 die "Can't connect to db" unless $udbh;
665 # first we find exact matches (or all matches)
666 my $journal_match = $allmatch ? "" : "AND journalid=?";
668 my $limit_sql = ($limit && $limit_remain) ? "LIMIT $limit_remain" : '';
669 my ($extra_condition, @extra_args) = $self->extra_params_for_finding_subscriptions();
670 my $sql = "SELECT userid, subid, is_dirty, journalid, etypeid, " .
671 "arg1, arg2, ntypeid, createtime, expiretime, flags " .
672 "FROM subs WHERE etypeid=? $journal_match $and_enabled $extra_condition " .
673 $limit_sql;
675 my $sth = $udbh->prepare($sql);
676 my @args = ($self->etypeid);
677 push @args, $self->u->id unless $allmatch;
678 push @args, @extra_args;
680 $sth->execute(@args);
682 if ($sth->err) {
683 warn "SQL: [$sql], args=[@args]\n";
684 die $sth->errstr;
687 while (my $row = $sth->fetchrow_hashref) {
688 my $sub = LJ::Subscription->new_from_row($row);
689 next unless $sub->owner->clusterid == $cid;
691 push @subs, $sub;
694 # then we find wildcard matches.
695 if (@wildcards_from) {
696 # FIXME: journals are only on one cluster! split jidlist based on cluster
697 my $jidlist = join(",", @wildcards_from);
699 ## Strictly specify USE INDEX(PRIMARY) hint.
700 ## Otherwise mysql query planner is acting out and query takes inexcusable amount of time.
701 my $sth = $udbh->prepare(qq{
702 SELECT
703 userid, subid, is_dirty, journalid, etypeid,
704 arg1, arg2, ntypeid, createtime, expiretime, flags
705 FROM subs
706 USE INDEX(PRIMARY)
707 WHERE etypeid=? AND journalid=0 $and_enabled
708 AND userid IN ($jidlist)
711 $sth->execute($self->etypeid);
712 die $sth->errstr if $sth->err;
714 while (my $row = $sth->fetchrow_hashref) {
715 my $sub = LJ::Subscription->new_from_row($row);
716 next unless $sub->owner->clusterid == $cid;
718 push @subs, $sub;
722 $limit_remain = $limit - @subs;
725 return @subs;
728 sub extra_params_for_finding_subscriptions {
729 return '';
732 # returns a boolean value indicating whether the given subscription matches
733 # the event.
735 # this is a virtual function; base class function returns 1 for "yes".
737 # my @subs = grep { $event->matches_filter($_) } @subs;
738 sub matches_filter {
739 my ($self, $subsc) = @_;
740 return 1;
743 # returns a scalar value representing the time event happened.
745 # this is a virtual function; base class function returns undef for "unknown"
747 # my $time = $event->eventtime_unix;
748 # print scalar(localtime($time)) if defined $time;
749 sub eventtime_unix {
750 return undef;
753 # Returns path to template file by event type for certain language, journal
754 # and e-mail section.
756 # @params: section = [subject | body_html | body_text]
757 # lang = [ en | ru | ... ]
759 # @returns: filename or undef if template could not be found.
761 sub template_file_for {
762 my $self = shift;
763 my %opts = @_;
765 return if LJ::conf_test($LJ::DISABLED{template_files});
767 my $section = $opts{section};
768 my $lang = $opts{lang} || 'default';
769 my ($event_type) = (ref $self) =~ /\:([^:]+)$/; #
770 my $journal_name = $self->event_journal->user;
772 # all ESN e-mail templates are located in:
773 # $LJHOME/templates/ESN/$event_type/$language/$journal_name
775 # go though file paths until found existing one
776 foreach my $file (
777 "$event_type/$lang/$journal_name/$section.tmpl",
778 "$event_type/$lang/default/$section.tmpl",
779 "$event_type/default/$journal_name/$section.tmpl",
780 "$event_type/default/default/$section.tmpl",
782 $file = "$ENV{LJHOME}/templates/ESN/$file"; # add common prefix
783 return $file if -e $file;
785 return undef;
788 ### VALUE OBJECT FUNCTIONS ###
790 # return a string with HTML code representing a subscription as shown to
791 # the user on a settings page.
793 # this is a virtual function; base class function returns a dummy string listing
794 # event class, journal, and arguments.
796 # my $html = LJ::Event::ExampleEvent->subscription_as_html(bless({
797 # 'journalid' => $journal->id,
798 # 'arg1' => $arg1,
799 # 'arg2' => $arg2,
800 # }, "LJ::Subscription"));
802 # my $html = LJ::Event::ExampleEvent->subscription_as_html(bless({
803 # 'journalid' => $journal->id,
804 # 'arg1' => $arg1,
805 # 'arg2' => $arg2,
806 # }, "LJ::Subscription::Group"));
807 sub subscription_as_html {
808 my ($class, $subscr) = @_;
810 confess "No subscription" unless $subscr;
812 my $arg1 = $subscr->arg1;
813 my $arg2 = $subscr->arg2;
814 my $journalid = $subscr->journalid;
816 my $user = $journalid ? LJ::ljuser(LJ::load_userid($journalid)) : "(wildcard)";
818 return $class . " arg1: $arg1 arg2: $arg2 user: $user";
821 # return a boolean indicating that event could be emailed to unauthorized users
822 # it is required for SupportRequest and SupportResponse events,
823 # since support requests could be created by non-users
824 sub allow_emails_to_unauthorised { 0 }
826 # return a boolean indicating relation of the event to Support
827 sub is_support_class { 0 }
829 # return a boolean value indicating whether a user is able to subscribe to
830 # this event and receive notifications.
832 # this is a virtual function; base class function returns true, which means
833 # "yes, they can".
835 # @subs = grep { $_->available_for_user($u) } @subs;
836 sub available_for_user {
837 my ($self, $u) = @_;
839 return 1;
842 # return a boolean indicating whether the subscription for this event is a
843 # "tracking" one, that is, it
845 # * counts towards user's quota of subscriptions
846 # * shows up on the bottom of the user's subscriptions list on the main
847 # settings page
849 # this is a virtual function; base class function returns 1 for "yes"
851 # next unless $event->is_tracking;
852 sub is_tracking { 1 }
854 # return a boolean indicating whether the subscription for this event may
855 # be shown to the user. it may still not be shown if the calling page doesn't
856 # want it to be shown; returning true from here prevents the subscription
857 # from showing for good -- when this is done, LJ::Widget::SubscribeInterface
858 # will never show it, regardless of what is passed to it.
860 # this is a virtual function; base class function returns 1 for "yes"
862 # next unless $event->is_subscription_visible_to($u);
863 sub is_subscription_visible_to { 1 }
865 # return a string containing HTML code with information for the user about
866 # what they can do to have this subscription available (e.g. upgrade account).
868 # this is a virtual function; base class function calls a "disabled_esn_sub"
869 # hook for the user and returns whatever it returned or an empty string.
871 # print $event->get_disabled_pic;
872 sub get_disabled_pic {
873 my ($self, $u) = @_;
875 return LJ::run_hook("disabled_esn_sub", $u) || '';
878 # return a boolean indicating whether the checkbox corresponding to the
879 # given notification type may be shown to the user.
881 # this is a virtual function; base class function returns 1 for "yes".
883 # $checkbox = '' unless $event->is_subscription_ntype_visible_to($ntypeid, $u);
884 sub is_subscription_ntype_visible_to { 1 }
886 # return a boolean indicating whether the checkbox corresponding to the
887 # given notification type must be disabled.
889 # this is a virtual function; base class function checks whether the user
890 # is able to subscribe to this event, and whether the notification method
891 # is configured for them -- if one of these conditions not met, it returns
892 # 0 for "no"; otherwise, it returns 1 for "yes".
894 # overriding functions in subclasses are still expected to call the parent
895 # class function in order to avoid duplicating these checks.
897 # $disabled = $event->is_subscription_ntype_disabled_for($ntypeid, $u);
898 sub is_subscription_ntype_disabled_for {
899 my ($self, $ntypeid, $u) = @_;
901 if ($self->class !~ /Support/) {
902 return 1 unless $self->available_for_user($u);
905 my $nclass = LJ::NotificationMethod->class($ntypeid);
906 return 1 unless $nclass->configured_for_user($u);
908 return 1 unless $nclass->is_subtype_enabled($self);
910 return 0;
913 # assuming that the checkbox corresponding to the given notification type
914 # is disabled for the user, return a boolean value whether it is checked or not.
916 # this is a virtual function; base class function returns 0 for "no".
918 # $value = $disabled ?
919 # $event->get_subscription_ntype_force($ntype, $u) :
920 # $sub->is_active;
921 sub get_subscription_ntype_force { 0 }
923 # returns a hashref with the information returned from these functions:
925 # * is_subscription_visible_to (the 'visible' key)
926 # * !available_for_user (the 'disabled' key)
927 # * get_disabled_pic (the 'disabled_pic' key)
929 # note that this function forces the event to be invisible in case user is
930 # unable to subscribe to this event and is unable to upgrade to it [that is, is
931 # an OpenID user].
933 # my $interface_info = $event->get_interface_status($u);
934 sub get_interface_status {
935 my ($self, $u) = @_;
937 my $available = $self->available_for_user($u);
939 my $visible = $self->is_subscription_visible_to($u);
940 $visible &&= ($available || !$u->is_identity);
942 return {
943 'visible' => $visible,
944 'disabled' => !$available,
945 'disabled_pic' => $self->get_disabled_pic($u),
949 # returns a hashref with the information returned from these functions:
951 # * is_subscription_ntype_visible_to (the 'visible' key)
952 # * is_subscription_ntype_disabled_for (the 'disabled' key)
953 # * get_subscription_ntype_force (the 'force' key)
955 # my $interface_info = $event->get_ntype_interface_status($u);
956 sub get_ntype_interface_status {
957 my ($self, $ntypeid, $u) = @_;
959 return {
960 'visible' => $self->is_subscription_ntype_visible_to($ntypeid, $u),
961 'disabled' => $self->is_subscription_ntype_disabled_for($ntypeid, $u),
962 'force' => $self->get_subscription_ntype_force($ntypeid, $u),
966 # initialization code. do not touch this.
967 my @EVENTS = LJ::ModuleLoader->module_subclasses("LJ::Event");
968 foreach my $event (@EVENTS) {
969 eval "use $event";
970 confess "Error loading event module '$event': $@" if $@;
973 sub as_email_to {
974 my ($self, $u) = @_;
975 return $u->email_raw;
978 sub as_email_from {
979 my ($self, $u) = @_;
980 return $LJ::BOGUS_EMAIL;
983 sub go_through_clusters {1}
985 sub has_frame { 0 }
987 sub update_events_counter { 0 }