9 LJ::Event::CommunityInvite
10 LJ::Event::CommunityJoinReject
11 LJ::Event::CommunityJoinRequest
12 LJ::Event::CommunityJoinApprove
19 use LJ
::MemCacheProxy
;
20 use LJ
::RelationService
;
22 use LJ
::User
::FriendInvites
;
24 Readonly
my $COMMUNITY_ROW_CACHE_KEY => 'community:';
26 # Possible membership:
35 ## Create supermaintainer poll
37 ## comm_id = community id
38 ## alive_maintainers = array ref of alive maintainers (visible, active, is maintainers, etc)
39 ## no_job = nothing to do. only logging.
40 ## textref = where save log info
41 ## to_journal = which journal save polls to?
43 ## pollid = id of new created poll
44 sub create_supermaintainer_election_poll
{
46 my $comm_id = $args{'comm_id'};
47 my $alive_maintainers = $args{'maint_list'};
48 my $textref = $args{'log'};
49 my $no_job = $args{'no_job'} || 0;
50 my $check_active = $args{'check_active'} || 0;
51 my $to_journal = $args{'to_journal'} || LJ
::load_user
('lj_elections');
53 my $comm = LJ
::load_userid
($comm_id);
54 my $comm_username = $comm->{user
};
55 if ($comm_username eq 'cheaptrip' ||
56 $comm_username eq 'cheaptrip_spb' ||
57 $comm_username eq 'cheaptrip_ua')
59 die "Can't create supermaintainer election poll for $comm_username: the community doesn't participate in voting";
64 $entry = _create_post
(to
=> $to_journal, comm
=> $comm);
65 die "Entry for Poll does not created\n" unless $entry;
66 $$textref .= "Entry url: " . $entry->url . "\n";
70 foreach my $u (@
$alive_maintainers) {
71 $$textref .= "\tAdd ".$u->user." as item to poll\n";
73 item
=> "<lj user='".$u->user."'>",
80 qtext
=> LJ
::Lang
::ml
('poll.election.subject'),
88 $poll = LJ
::Poll
->create (entry
=> $entry, whovote
=> 'all', whoview
=> 'all', questions
=> \
@q)
89 or die "Poll was not created";
92 $poll->set_prop ('createdate' => $entry->eventtime_mysql)
93 or die "Can't set prop 'createdate'";
95 $poll->set_prop ('supermaintainer' => $comm->userid)
96 or die "Can't set prop 'supermaintainer'";
98 _edit_post
(to
=> $to_journal, comm
=> $comm, entry
=> $entry, poll
=> $poll)
99 or die "Can't edit post";
102 ## ugly, but reliable
107 warn Dumper
($to_journal);
112 ## We need to remove all previous owners from community because election poll is started.
113 my $s_maints = LJ
::load_rel_user
($comm_id, 'S');
114 foreach my $user_id (@
$s_maints) {
115 LJ
::clear_rel
($comm_id, $user_id, 'S');
118 ## All are ok. Emailing to all maintainers about election.
119 my $subject = LJ
::Lang
::ml
('poll.election.email.subject');
120 $$textref .= "Sending emails to all maintainers for community " . $comm->user . "\n";
121 foreach my $u (@
$alive_maintainers) {
122 next unless $u && $u->is_visible && $u->can_manage($comm);
123 next if !$check_active && $u->check_activity(90);
124 $$textref .= "\tSend email to maintainer ".$u->user."\n";
125 LJ
::send_mail
({ 'to' => $u->email_raw,
126 'from' => $LJ::ACCOUNTS_EMAIL
,
127 'fromname' => $LJ::SITENAMESHORT
,
129 'charset' => $u->mailencoding || 'utf-8',
130 'subject' => $subject,
131 'html' => (LJ
::Lang
::ml
('poll.election.start.email', {
132 username
=> LJ
::ljuser
($u),
133 communityname
=> LJ
::ljuser
($comm),
135 shortsite
=> $LJ::SITENAMESHORT
,
136 authas
=> $comm->{user
},
137 siteroot
=> $LJ::SITEROOT
,
140 'body' => (LJ
::Lang
::ml
('poll.election.start.email.plain', {
141 username
=> LJ
::ljuser
($u),
142 communityname
=> LJ
::ljuser
($comm),
144 shortsite
=> $LJ::SITENAMESHORT
,
145 authas
=> $comm->{user
},
146 siteroot
=> $LJ::SITEROOT
,
152 return $no_job ?
undef : $poll->pollid;
159 my $comm = $opts{comm
};
160 my $entry = $opts{entry
};
161 my $poll = $opts{poll
};
163 my $security = delete $opts{security
} || 'private';
164 my $proto_sec = $security;
165 if ($security eq "friends") {
166 $proto_sec = "usemask";
169 my $subject = delete $opts{subject
} || LJ
::Lang
::ml
('poll.election.post_subject');
170 my $body = delete $opts{body
} || LJ
::Lang
::ml
('poll.election.post_body', { comm
=> $comm->user });
174 ver
=> $LJ::PROTOCOL_VER
,
177 event
=> $body . "<br/>" . "<lj-poll-".$poll->pollid.">",
180 security
=> $proto_sec,
181 itemid
=> $entry->jitemid,
184 $req{allowmask
} = 1 if $security eq 'friends';
187 my $flags = { noauth
=> 1, nomod
=> 1 };
189 LJ
::do_request
(\
%req, \
%res, $flags);
191 die "Error posting: $res{errmsg}" unless $res{'success'} eq "OK";
192 my $jitemid = $res{itemid
} or die "No itemid";
194 return LJ
::Entry
->new($u, jitemid
=> $jitemid);
201 my $comm = $opts{comm
};
203 my $security = delete $opts{security
} || 'private';
204 my $proto_sec = $security;
205 if ($security eq "friends") {
206 $proto_sec = "usemask";
209 my $subject = delete $opts{subject
} || LJ
::Lang
::ml
('poll.election.post_subject');
210 my $body = delete $opts{body
} || LJ
::Lang
::ml
('poll.election.post_body', { comm
=> $comm->user });
214 ver
=> $LJ::PROTOCOL_VER
,
220 security
=> $proto_sec,
223 $req{allowmask
} = 1 if $security eq 'friends';
226 my $flags = { noauth
=> 1, nomod
=> 1 };
228 LJ
::do_request
(\
%req, \
%res, $flags);
230 die "Error posting: $res{errmsg}" unless $res{'success'} eq "OK";
231 my $jitemid = $res{itemid
} or die "No itemid";
233 return LJ
::Entry
->new($u, jitemid
=> $jitemid);
237 # name: LJ::get_sent_invites
238 # des: Get a list of sent invitations from the past 30 days.
240 # des-cuserid: a userid or u object of the community to get sent invitations for
241 # returns: hashref of arrayrefs with keys userid, maintid, recvtime, status, args (itself
242 # a hashref of what abilities the user would be given)
244 sub get_sent_invites
{
246 $cu = LJ
::want_user
($cu);
247 return undef unless $cu;
249 if (LJ
::is_enabled
('new_friends_and_subscriptions')) {
250 my @invites = LJ
::User
::FriendInvites
->list_sent_invites($cu);
254 LJ
::decode_url_string
($_->{args
}, $temp);
256 userid
=> $_->{userid
},
257 maintid
=> $_->{maintid
},
258 recvtime
=> $_->{recvtime
},
259 status
=> $_->{status
},
265 # now hit the database for their recent invites
266 my $dbcr = LJ
::get_cluster_def_reader
($cu);
267 return LJ
::error
('db') unless $dbcr;
268 my $data = $dbcr->selectall_arrayref('SELECT userid, maintid, recvtime, status, args FROM invitesent ' .
269 'WHERE commid = ? AND recvtime > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
270 undef, $cu->{userid
});
272 # now break data down into usable format for caller
274 foreach my $row (@
{$data || []}) {
276 LJ
::decode_url_string
($row->[4], $temp);
278 userid
=> $row->[0]+0,
279 maintid
=> $row->[1]+0,
280 recvtime
=> $row->[2],
291 # name: LJ::send_comm_invite
292 # des: Sends an invitation to a user to join a community with the passed abilities.
293 # args: uuserid, cuserid, muserid, attrs
294 # des-uuserid: a userid or u object of the user to invite.
295 # des-cuserid: a userid or u object of the community to invite the user to.
296 # des-muserid: a userid or u object of the maintainer doing the inviting.
297 # des-attrs: a hashref of abilities this user should have (e.g. member, post, unmoderated, ...)
298 # returns: 1 for success, undef if failure
300 sub send_comm_invite
{
301 my ($u, $cu, $mu, $attrs) = @_;
303 if (LJ
::is_enabled
('new_friends_and_subscriptions')) {
304 return LJ
::User
::FriendInvites
->send($u, $cu, $mu, $attrs);
307 $u = LJ
::want_user
($u);
308 $cu = LJ
::want_user
($cu);
309 $mu = LJ
::want_user
($mu);
310 return undef unless $u && $cu && $mu;
312 # step 1: if the user has banned the community, don't accept the invite
313 return LJ
::error
('comm_user_has_banned') if LJ
::is_banned
($cu, $u);
315 # step 2: lazily clean out old community invites.
316 return LJ
::error
('db') unless $u->writer;
317 $u->do('DELETE FROM inviterecv WHERE userid = ? AND ' .
318 'recvtime < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
319 undef, $u->{userid
});
321 return LJ
::error
('db') unless $cu->writer;
322 $cu->do('DELETE FROM invitesent WHERE commid = ? AND ' .
323 'recvtime < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
324 undef, $cu->{userid
});
326 my $dbcr = LJ
::get_cluster_def_reader
($u);
327 return LJ
::error
('db') unless $dbcr;
328 my $argstr = $dbcr->selectrow_array('SELECT args FROM inviterecv WHERE userid = ? AND commid = ?',
329 undef, $u->{userid
}, $cu->{userid
});
331 # step 4: exceeded outstanding invitation limit?
332 # should be checked when
333 # - there is no outstanding invite for this user AND
334 # - maintainer has no unlimited invites ability
335 if (!$argstr && !$LJ::UNLIMITED_INVITES_TO_COMMUNITIES
{ $mu->user }) {
336 my $cdbcr = LJ
::get_cluster_def_reader
($cu);
337 return LJ
::error
('db') unless $cdbcr;
338 my $count = $cdbcr->selectrow_array("SELECT COUNT(*) FROM invitesent WHERE commid = ? " .
339 "AND userid <> ? AND status = 'outstanding'",
340 undef, $cu->{userid
}, $u->{userid
});
341 my $fr = LJ
::get_friends
($cu) || {};
342 my $max = int(scalar(keys %$fr) / 10); # can invite up to 1/10th of the community
343 $max = 50 if $max < 50; # or 50, whichever is greater
344 return LJ
::error
('comm_invite_limit') if $count > $max;
347 # step 5: setup arg string as url-encoded string
348 my $newargstr = join('=1&', map { LJ
::eurl
($_) } @
$attrs) . '=1';
350 # step 6: branch here to update or insert
352 # merely an update, so just do it quietly
353 $u->do("UPDATE inviterecv SET args = ? WHERE userid = ? AND commid = ?",
354 undef, $newargstr, $u->{userid
}, $cu->{userid
});
356 $cu->do("UPDATE invitesent SET args = ?, status = 'outstanding' WHERE userid = ? AND commid = ?",
357 undef, $newargstr, $cu->{userid
}, $u->{userid
});
359 # insert new data, as this is a new invite
360 $u->do("INSERT INTO inviterecv VALUES (?, ?, ?, UNIX_TIMESTAMP(), ?)",
361 undef, $u->{userid
}, $cu->{userid
}, $mu->{userid
}, $newargstr);
363 $cu->do("REPLACE INTO invitesent VALUES (?, ?, ?, UNIX_TIMESTAMP(), 'outstanding', ?)",
364 undef, $cu->{userid
}, $u->{userid
}, $mu->{userid
}, $newargstr);
367 # Fire community invite event
368 LJ
::Event
::CommunityInvite
->new($u, $mu, $cu)->fire unless $LJ::DISABLED
{esn
};
370 # step 7: error check database work
371 return LJ
::error
('db') if $u->err || $cu->err;
373 _clear_invite_cache
($cu, $u);
380 # name: LJ::accept_comm_invite
381 # des: Accepts an invitation a user has received. This does all the work to make the
382 # user join the community as well as sets up privileges.
383 # args: uuserid, cuserid
384 # des-uuserid: a userid or u object of the user to get pending invites for
385 # des-cuserid: a userid or u object of the community to reject the invitation from
386 # returns: 1 for success, undef if failure
388 sub accept_comm_invite
{
391 if (LJ
::is_enabled
('new_friends_and_subscriptions')) {
392 return LJ
::User
::FriendInvites
->accept($u, $cu);
395 $u = LJ
::want_user
($u);
396 $cu = LJ
::want_user
($cu);
397 return undef unless $u && $cu;
399 # get their invite to make sure they have one
400 my $dbcr = LJ
::get_cluster_def_reader
($u);
401 return LJ
::error
('db') unless $dbcr;
402 my ($argstr, $maintid) = $dbcr->selectrow_array('SELECT args, maintid FROM inviterecv WHERE userid = ? AND commid = ? ' .
403 'AND recvtime > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
404 undef, $u->{userid
}, $cu->{userid
});
405 return undef unless $argstr;
407 # decode to find out what they get
409 LJ
::decode_url_string
($argstr, $args);
411 # valid invite. let's accept it as far as the community listing us goes.
412 # 1, 0 means add comm to user's friends list, but don't auto-add P edge.
413 if ($args->{'member'}) {
414 my ($code, $error) = LJ
::do_join_community
($u, $cu, 1, 0);
418 "Can't call LJ::join_community($u->{user}, $cu->{user}): $error"
423 # now grant necessary abilities
430 my ($is_super, $poll) = (undef, undef);
431 my $poll_id = $cu->prop('election_poll_id');
433 $poll = LJ
::Poll
->new ($poll_id);
434 $is_super = $poll->prop('supermaintainer');
436 my $flag_set_owner_error = 0;
437 foreach (keys %edgelist) {
438 if ($poll && $is_super && !$poll->is_closed && $_ eq 'admin' && $args->{$_}) {
439 $flag_set_owner_error = 1;
441 LJ
::set_rel
($cu->{userid
}, $u->{userid
}, $edgelist{$_}) if $args->{$_};
443 $cu->clear_cache_friends($u);
445 if ( $_ eq 'admin' && $args->{$_} ) {
446 LJ
::User
::UserlogRecord
::MaintainerAdd
->create( $cu,
447 'maintid' => $u->userid,
448 'remote' => LJ
::load_userid
($maintid) || $u,
454 # now we can delete the invite and update the status on the other side
455 return LJ
::error
('db') unless $u->writer;
456 $u->do("DELETE FROM inviterecv WHERE userid = ? AND commid = ?",
457 undef, $u->{userid
}, $cu->{userid
});
459 return LJ
::error
('db') unless $cu->writer;
460 $cu->do("UPDATE invitesent SET status = 'accepted' WHERE commid = ? AND userid = ?",
461 undef, $cu->{userid
}, $u->{userid
});
463 if ($flag_set_owner_error) {
464 ## Save for later acceptance after the elections will be closed
465 $u->do("INSERT INTO inviterecv VALUES (?, ?, ?, UNIX_TIMESTAMP(), ?)",
466 undef, $u->{userid
}, $cu->{userid
}, $maintid, 'A');
467 $cu->do("REPLACE INTO invitesent VALUES (?, ?, ?, UNIX_TIMESTAMP(), 'outstanding', ?)",
468 undef, $cu->{userid
}, $u->{userid
}, $maintid, 'A');
469 return LJ
::error
("Can't set user $u->{user} as maintainer for $cu->{user}")
472 $cu->clear_cache_friends($u);
474 _clear_invite_cache
($cu, $u);
481 # name: LJ::reject_comm_invite
482 # des: Rejects an invitation a user has received.
483 # args: uuserid, cuserid
484 # des-uuserid: a userid or u object of the user to get pending invites for.
485 # des-cuserid: a userid or u object of the community to reject the invitation from
486 # returns: 1 for success, undef if failure
488 sub reject_comm_invite
{
491 if (LJ
::is_enabled
('new_friends_and_subscriptions')) {
492 return LJ
::User
::FriendInvites
->reject($u, $cu);
495 $u = LJ
::want_user
($u);
496 $cu = LJ
::want_user
($cu);
497 return undef unless $u && $cu;
499 # get their invite to make sure they have one
500 my $dbcr = LJ
::get_cluster_def_reader
($u);
501 return LJ
::error
('db') unless $dbcr;
502 my $test = $dbcr->selectrow_array('SELECT userid FROM inviterecv WHERE userid = ? AND commid = ? ' .
503 'AND recvtime > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
504 undef, $u->{userid
}, $cu->{userid
});
505 return undef unless $test;
508 return LJ
::error
('db') unless $u->writer;
509 $u->do("DELETE FROM inviterecv WHERE userid = ? AND commid = ?",
510 undef, $u->{userid
}, $cu->{userid
});
512 return LJ
::error
('db') unless $cu->writer;
513 $cu->do("UPDATE invitesent SET status = 'rejected' WHERE commid = ? AND userid = ?",
514 undef, $cu->{userid
}, $u->{userid
});
516 $cu->clear_cache_friends($u);
518 _clear_invite_cache
($cu, $u);
525 # name: LJ::get_pending_invites
526 # des: Gets a list of pending invitations for a user to join a community.
528 # des-uuserid: a userid or u object of the user to get pending invites for.
529 # returns: [ [ commid, maintainerid, time, args(url encoded) ], [ ... ], ... ] or
532 sub get_pending_invites
{
534 $u = LJ
::want_user
($u);
535 return undef unless $u;
537 if (LJ
::is_enabled
('new_friends_and_subscriptions')) {
538 my @invites = LJ
::User
::FriendInvites
->list_recv_invites($u);
539 return [ map { [$_->{commid
}, $_->{maintid
}, $_->{recvtime
}, $_->{args
}] } @invites ];
542 # hit up database for invites and return them
543 my $dbcr = LJ
::get_cluster_def_reader
($u);
544 return LJ
::error
('db') unless $dbcr;
545 my $pending = $dbcr->selectall_arrayref('SELECT commid, maintid, recvtime, args FROM inviterecv WHERE userid = ? ' .
546 'AND recvtime > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
547 undef, $u->{userid
});
548 return undef if $dbcr->err;
553 # name: LJ::revoke_invites
554 # des: Revokes a list of outstanding invitations to a community.
555 # args: cuserid, userids
556 # des-cuserid: a userid or u object of the community.
557 # des-ruserids: userids to revoke invitations from.
558 # returns: 1 if success, undef if error
563 $cu = LJ
::want_user
($cu);
564 return undef unless ($cu && @uids);
566 foreach my $uid (@uids) {
567 return undef unless int($uid) > 0;
569 my $in = join(',', @uids);
571 return LJ
::error
('db') unless $cu->writer;
572 $cu->do("DELETE FROM invitesent WHERE commid = ? AND " .
573 "userid IN ($in)", undef, $cu->{userid
});
574 return LJ
::error
('db') if $cu->err;
577 journalid
=> $cu->userid,
578 journalcaps
=> $cu->caps,
582 # remove from inviterecv also,
583 # otherwise invite cannot be resent for over 30 days
584 foreach my $uid (@uids) {
585 my $u = LJ
::want_user
($uid);
586 my $res = $u->do("DELETE FROM inviterecv WHERE userid = ? AND " .
587 "commid = ?", undef, $uid, $cu->{userid
});
589 push @
{$stats->{users
}}, { id
=> $u->userid, caps
=> $u->caps } if $res;
591 if (LJ
::is_enabled
('new_friends_and_subscriptions')) {
592 my $invite = LJ
::User
::FriendInvites
->new({ commid
=> $cu->{userid
}, userid
=> $uid });
593 $invite->clear_cache;
595 _clear_invite_cache
($cu, $u);
599 LJ
::run_hooks
('revoke_invite', $stats);
605 # temporary method to handle beta/prod with enabled/disabled new scheme
606 sub _clear_invite_cache
{
608 my $keys = LJ
::User
::FriendInvites
->_memkey(undef, {fromjournal
=> $cu, recipient
=> $u});
609 my @keys = values %$keys;
610 map { $_ && LJ
::MemCache
::delete($_) } @keys;
615 # name: LJ::leave_community
616 # des: Makes a user leave a community. Takes care of all [special[reluserdefs]] and friend stuff.
617 # args: uuserid, ucommid, defriend
618 # des-uuserid: a userid or u object of the user doing the leaving.
619 # des-ucommid: a userid or u object of the community being left.
620 # des-defriend: remove comm from user's friends list.
621 # returns: 1 if success, 0 if error, and error/message if need
623 sub leave_community
{
624 my ($uid, $cid, $defriend) = @_;
625 my $u = LJ
::want_user
($uid);
626 my $c = LJ
::want_user
($cid);
628 die 'Expected parameter $u in LJ::leave_community not found' unless $u;
629 die 'Expected parameter $c in LJ::leave_community not found' unless $c;
631 unless ($c->is_community) {
632 return (0, LJ
::Lang
::ml
('error.code.comm_not_comm'));
635 if (LJ
::is_maintainer
($u, $c)) {
636 if (LJ
::count_maintainers
($c) <= 1) {
637 return (0, LJ
::Lang
::ml
('/community/leave.bml.label.lastmaintainer'));
641 # defriend comm -> user
642 unless ($c->remove_friend($u)) {
646 # clear edges that effect this relationship
647 foreach my $edge (qw(P N A M)) {
648 LJ
::clear_rel
($c, $u, $edge);
651 # defriend user -> comm?
653 $u->remove_friend($c);
654 $c->clear_cache_friends($u);
657 if (LJ
::is_maintainer
($u, $c)) {
658 LJ
::User
::UserlogRecord
::MaintainerRemove
->create(
659 $c, maintid
=> $u->id,
663 # don't care if we failed the removal of comm from user's friends list...
667 sub leave_all_communities
{
669 my $friendsof = $u->friendsof();
671 foreach my $c (values %$friendsof) {
673 next unless $c->is_community;
675 if (LJ
::is_maintainer
($u, $c)) {
676 if (LJ
::count_maintainers
($c) <= 1) {
681 # defriend comm -> user
682 unless ($c->remove_friend($u, {nonotify
=> 1})) {
686 # clear edges that effect this relationship
687 foreach my $edge (qw(P N A M)) {
688 LJ
::clear_rel
($c, $u, $edge);
691 if (LJ
::is_maintainer
($u, $c)) {
692 LJ
::User
::UserlogRecord
::MaintainerRemove
->create(
693 $c, maintid
=> $u->id,
697 $c->clear_cache_friends($u);
704 # name: LJ::join_community
705 # des: Makes a user join a community. Takes care of all [special[reluserdefs]] and friend stuff.
706 # args: uuserid, ucommid, friend?, noauto?
707 # des-uuserid: a userid or u object of the user doing the joining
708 # des-ucommid: a userid or u object of the community being joined
709 # des-friend: 1 to add this comm to user's friends list, else not
710 # des-noauto: if defined, 1 adds P edge, 0 does not; else, base on community postlevel
711 # returns: 1 if success, undef if error of some sort (ucommid not a comm, uuserid already in
712 # comm, db error, etc)
715 my ($uid, $cid, $friend, $canpost) = @_;
716 my $u = LJ
::want_user
($uid);
717 my $c = LJ
::want_user
($cid);
719 die 'Expected parameter $u in LJ::leave_community not found' unless $u;
720 die 'Expected parameter $c in LJ::leave_community not found' unless $c;
722 if ($c->is_banned($u)) {
723 return (0, LJ
::Lang
::ml
('/community/join.bml.label.banned'));
726 unless ($c->is_community) {
727 return (0, LJ
::Lang
::ml
('error.code.comm_not_comm'));
730 unless ($u->is_validated) {
731 return (0, qq|Sorry
, you aren
't allowed to join communities until your email address "
732 . "has been validated. If you've lost the confirmation email to
do this
, "
733 . "you can
<a href
="$LJ::SITEROOT/register.bml">have it re
-sent
.</a
>|);
736 unless ($u->can_join_adult_comm(comm
=> $c)) {
737 return (0, LJ
::Lang
::ml
(
738 '/community/join.bml.error.isminor', {
739 comm
=> $c->ljuser_display
744 my $row = LJ
::get_community_row
($c);
747 warn "Cant load community row [" . $c->user . "]";
753 last unless $row->{membership
};
754 last unless $row->{membership
} ne 'open';
757 my $maintainers = LJ
::load_userids
(@
{
758 LJ
::load_rel_user
($c->id, 'A') || []
761 my $maints = join(', ', map {
763 } values %$maintainers
766 if ($row->{membership
} eq 'closed') {
767 return (0, LJ
::Lang
::ml
(
768 '/community/join.bml.error.closed', {
774 if ($row->{membership
} eq 'moderated') {
776 if (LJ
::comm_join_request
($u, $c)) {
777 return (1, LJ
::Lang
::ml
('/community/join.bml.reqsubmitted.body') . $maints);
786 return LJ
::do_join_community
($u, $c, $friend, $canpost);
789 sub do_join_community
{
790 my ($u, $c, $friend, $canpost) = @_;
791 die 'Expected parameter $u in LJ::leave_community not found' unless $u;
792 die 'Expected parameter $c in LJ::leave_community not found' unless $c;
794 my $row = LJ
::get_community_row
($c);
797 warn "Cant load community row [" . $c->user . "]";
802 unless ($c->can_join_community(\
$err, { friend
=> $u })) {
806 # friend comm -> user
807 unless ($c->add_friend($u)) {
811 # add edges that effect this relationship... if the user sent a fourth
812 # argument, use that as a bool. else, load commrow and use the postlevel.
815 if (defined $canpost) {
816 $addpostacc = $canpost ?
1 : 0;
818 if ($row->{postlevel
}) {
819 if ($row->{postlevel
} eq 'members') {
826 LJ
::set_rel
($c->id, $u->id, 'P');
829 # friend user -> comm
831 if (LJ
::is_enabled
('new_friends_and_subscriptions')) {
832 $u->subscribe_to_user($c, nonotify
=> 1)
834 # don't do the work if they already friended the comm
835 unless ($u->has_friend($c)) {
838 unless ($u->can_add_friends(\
$err, { friend
=> $c })) {
839 return (1, "You have joined the community, but it has not been added to "
840 . "your Friends list. $err");
853 # name: LJ::get_community_row
854 # des: Gets data relevant to a community such as their membership level and posting access.
856 # des-ucommid: a userid or u object of the community
857 # returns: a hashref with user, userid, name, membership, and postlevel data from the
858 # user and community tables; undef if error.
860 sub get_community_row
{
862 my $c = LJ
::want_user
($uid);
870 my $dbh = LJ
::get_db_reader
();
876 my $row = LJ
::MemCacheProxy
::get
(
877 [$uid, $COMMUNITY_ROW_CACHE_KEY . $uid]
881 $row = $dbh->selectrow_hashref(qq[
883 membership
, postlevel
901 LJ
::MemCacheProxy
::set
(
902 [$uid, $COMMUNITY_ROW_CACHE_KEY . $uid], $row, 86400
906 # return result hashref
911 userid
=> $c->userid,
916 # name: LJ::get_community_moderation_queue
917 # des: Gets a list of hashrefs for posts that people have requested to be posted to a community
918 # but have not yet actually been approved or rejected.
920 # des-comm: a userid or u object of the community to get pending members of
921 # returns: an array of requests as it is in modlog db table
923 sub get_community_moderation_queue
{
925 my $c = LJ
::want_user
($comm);
927 my $dbcr = LJ
::get_cluster_reader
($c);
928 my @e; # fetched entries
929 my @entries; # entries to show
930 my @entries2del; # entries to delete
931 my $sth = $dbcr->prepare("SELECT * FROM modlog WHERE journalid=$c->{'userid'}");
933 while ($_ = $sth->fetchrow_hashref) {
938 my $suspend_time = $LJ::SUSPENDED_REQUESTS_TIMEOUT
|| 60; # days.
940 LJ
::load_userids_multiple
([ map { $_->{'posterid'}, \
$users{$_->{'posterid'}} } @e ]);
942 next unless keys %$e;
943 my $e_poster = $users{$e->{'posterid'}};
944 if (LJ
::isu
($e_poster)) {
945 if ($e_poster->is_suspended()) {
946 if (time - $e_poster->statusvisdate_unix() > $suspend_time * 24 * 3600) {
947 push @entries2del, $e;
953 push @entries2del, $e;
959 # Users has been suspended more then 60 days ago.
960 # Delete entries of this user(s) from modlog and modblob.
961 my $count = scalar @entries2del;
962 my $max_count = $count > 50 ?
50 : $count;
964 my $lst = join(',', map {$_->{modid
}} splice(@entries2del, 0, $max_count));
965 $c->do("DELETE FROM modlog WHERE modid in ($lst)");
966 $c->do("DELETE FROM modblob WHERE modid in ($lst)");
967 $count -= $max_count;
977 # name: LJ::comm_join_request
978 # des: Registers an authaction to add a user to a
979 # community and sends an approval email to the maintainers
980 # returns: Hashref; output of LJ::register_authaction()
981 # includes datecreate of old row if no new row was created
983 # des-comm: Community user object
984 # des-u: User object to add to community
986 sub comm_join_request
{
988 die 'Expected parameter $u in LJ::leave_community not found' unless $u;
989 die 'Expected parameter $c in LJ::leave_community not found' unless $c;
997 my $arg = "targetid=$uid";
998 my $dbh = LJ
::get_db_writer
();
1002 # check for duplicates within the same hour (to prevent spamming)
1003 my $oldaa = $dbh->selectrow_hashref(qq[
1005 aaid
, authcode
, datecreate
1013 action
= 'comm_join_request'
1017 NOW
() < datecreate
+ INTERVAL
1 HOUR
1032 return $oldaa if $oldaa;
1034 # insert authactions row
1035 my $aa = LJ
::register_authaction
(
1036 $cid, 'comm_join_request', $arg
1041 LJ
::User
::FriendInvites
->send($c, $u, $u);
1043 # if there are older duplicates, invalidate any existing unused authactions of this type
1056 action
= 'comm_invite'
1070 # get maintainers of community
1071 my $admins = $c->maintainers();
1073 # now prepare the emails
1074 foreach my $au (values %$admins) {
1075 next unless $au && !$au->is_expunged;
1077 # unless it's a hyphen, we need to migrate
1078 my $prop = $au->prop("opt_communityjoinemail");
1080 if ($prop && $prop ne "-") {
1083 event
=> 'CommunityJoinRequest',
1087 unless ($au->has_subscription(%params)) {
1088 foreach (qw(Inbox Email)) {
1089 $au->subscribe(%params, method
=> $_);
1094 $au->set_prop("opt_communityjoinemail", "-");
1097 LJ
::Event
::CommunityJoinRequest
->new($au, $u, $c)->fire;
1100 LJ
::MemCacheProxy
::delete([$cid, "community:request:$cid:$uid"]);
1106 # name: LJ::get_pending_members
1107 # des: Gets a list of userids for people that have requested to be added to a community
1108 # but have not yet actually been approved or rejected.
1110 # des-comm: a userid or u object of the community to get pending members of
1111 # returns: an arrayref of userids of people with pending membership requests
1113 sub get_pending_members
{
1115 my $cu = LJ
::want_user
($comm);
1118 my $dbr = LJ
::get_db_reader
();
1120 my $sth = $dbr->prepare('SELECT aaid, arg1 FROM authactions' .
1121 ' WHERE userid = ' . $cu->{userid
} .
1122 " AND action = 'comm_join_request' AND used = 'N'");
1123 # parse out the args
1127 my $suspend_time = $LJ::SUSPENDED_REQUESTS_TIMEOUT
|| 60; # days.
1128 while (my $row = $sth->fetchrow_hashref) {
1129 if ($row->{arg1
} =~ /^targetid=(\d+)$/) {
1130 my ($uid, $u) = ($1, LJ
::want_user
($1));
1132 if ($u->is_suspended()) {
1133 if (time - $u->statusvisdate_unix() > $suspend_time * 24 * 3600) {
1134 push @delete, $row->{aaid
};
1140 push @delete, $row->{aaid
};
1146 my $count = scalar @delete;
1147 my $max_count = $count > 50 ?
50 : $count;
1149 my $lst = join(',', splice(@delete, 0, $max_count));
1150 my $dbh = LJ
::get_db_writer
();
1151 $dbh->do("DELETE FROM authactions WHERE aaid in ($lst)");
1152 $count -= $max_count;
1160 # name: LJ::approve_pending_member
1161 # des: Approves someone's request to join a community. This updates the [dbtable[authactions]] table
1162 # as appropriate as well as does the regular join logic. This also generates an e-mail to
1163 # be sent to the user notifying them of the acceptance.
1164 # args: commid, userid
1165 # des-commid: userid of the community
1166 # des-userid: userid of the user doing the join
1167 # returns: 1 on success, 0/undef on error
1169 sub approve_pending_member
{
1170 my ($cid, $uid) = @_;
1171 my $c = LJ
::want_user
($cid);
1172 my $u = LJ
::want_user
($uid);
1177 my $arg = "targetid=$uid";
1178 my $dbh = LJ
::get_db_writer
();
1182 my $cnt = $dbh->do(qq[
1203 LJ
::User
::FriendInvites
->accept($c, $u);
1205 LJ
::run_hooks
('approve_member',{
1211 journalcaps
=> $c->caps,
1214 LJ
::MemCacheProxy
::delete([$cid, "community:request:$cid:$uid"]);
1216 my ($code, $error) = LJ
::do_join_community
($u, $c, 1);
1218 return unless $code;
1220 my %params = (event
=> 'CommunityJoinApprove', journal
=> $u);
1222 unless ($u->has_subscription(%params)) {
1223 $u->subscribe(%params, method
=> 'Email');
1226 unless ($LJ::DISABLED
{esn
}) {
1227 LJ
::Event
::CommunityJoinApprove
->new($u, $c)->fire;
1234 # name: LJ::reject_pending_member
1235 # des: Rejects someone's request to join a community.
1236 # Updates [dbtable[authactions]] and generates an e-mail to the user.
1237 # args: commid, userid
1238 # des-commid: userid of the community
1239 # des-userid: userid of the user doing the join
1240 # returns: 1 on success, 0/undef on error
1242 ## LJ::reject_pending_member($cid, $id, $remote->{userid}, $POST{'reason'});
1243 sub reject_pending_member
{
1244 my ($cid, $uid, $mid, $reason) = @_;
1245 my $c = LJ
::want_user
($cid);
1246 my $u = LJ
::want_user
($uid);
1247 my $m = LJ
::want_user
($mid);
1253 if ($reason eq '0') {
1254 $reason = LJ
::Lang
::ml
('/community/pending.bml.reason.default.text');
1257 # step 1, update authactions table
1258 my $arg = "targetid=$uid";
1259 my $dbh = LJ
::get_db_writer
();
1263 my $cnt = $dbh->do(qq[
1283 LJ
::User
::FriendInvites
->reject($c, $u);
1285 LJ
::run_hooks
('reject_member', {
1291 journalcaps
=> $c->caps,
1294 LJ
::MemCacheProxy
::delete([$cid, "community:request:$cid:$uid"]);
1296 # step 2, email the user
1297 my %params = (event
=> 'CommunityJoinReject', journal
=> $c);
1299 unless ($u->has_subscription(%params)) {
1300 $u->subscribe(%params, method
=> 'Email');
1303 # Email to user about rejecting
1304 unless ($LJ::DISABLED
{esn
}) {
1305 LJ
::Event
::CommunityJoinReject
->new($c, $u, undef, undef, $reason)->fire;
1308 # Email to maints about user rejecting
1309 my $maintainers = $c->maintainers();
1311 foreach my $mu (values %$maintainers) {
1312 next if $mu->id == $mid;
1314 my %params = (event
=> 'CommunityJoinReject', journal
=> $c);
1316 if ($mu && $mu->has_subscription(%params)) {
1317 unless ($LJ::DISABLED
{esn
}) {
1318 LJ
::Event
::CommunityJoinReject
->new($c, $mu, $m, $u, $reason)->fire;
1326 sub is_request_sent
{
1338 my $key = [$cid, "community:request:$cid:$uid"];
1339 my $val = LJ
::MemCacheProxy
::get
([$cid, "community:request:$cid:$uid"]);
1345 my $dbh = LJ
::get_db_writer
();
1346 my $arg = "targetid=$uid";
1350 my $row = $dbh->selectrow_hashref(qq[
1360 action
= 'comm_join_request'
1373 LJ
::MemCacheProxy
::set
([$cid, "community:request:$cid:$uid"], $row, 86400);
1378 sub maintainer_linkbar
{
1386 my $username = $comm->user;
1389 my %manage_link_info = LJ
::run_hook
('community_manage_link_info', $username);
1390 if (keys %manage_link_info) {
1391 push @links, $page eq "account" ?
1392 "<strong>$manage_link_info{text}</strong>" :
1393 "<a href='$manage_link_info{url}'>$manage_link_info{text}</a>";
1397 $page eq "profile" ?
1398 "<strong>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.actinfo2') . "</strong>" :
1399 "<a href='$LJ::SITEROOT/manage/profile/?authas=$username'>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.actinfo2') . "</a>",
1400 $page eq "customize" ?
1401 "<strong>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.customize2') . "</strong>" :
1402 "<a href='$LJ::SITEROOT/customize/?authas=$username'>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.customize2') . "</a>",
1403 $page eq "settings" ?
1404 "<strong>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.actsettings2') . "</strong>" :
1405 "<a href='$LJ::SITEROOT/community/settings.bml?authas=$username'>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.actsettings2') . "</a>",
1406 $page eq "invites" ?
1407 "<strong>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.actinvites') . "</strong>" :
1408 "<a href='$LJ::SITEROOT/community/sentinvites.bml?authas=$username'>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.actinvites') . "</a>",
1409 $page eq "members" ?
1410 "<strong>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.actmembers2') . "</strong>" :
1411 "<a href='$LJ::SITEROOT/community/members.bml?authas=$username'>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.actmembers2') . "</a>",
1414 if (LJ
::SUP
->is_sup_enabled($comm) && LJ
::is_enabled
('wishlist_v2')) {
1415 push @links, $page eq "wishlist" ?
1416 "<strong>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.wishlist') . "</strong>" :
1417 "<a href='".$comm->wishlist_url."'>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.wishlist') . "</a>";
1420 push @links, $page eq 'massmailing'
1421 ?
'<strong>' . LJ
::Lang
::ml
('/community/manage.bml.commlist.massmailing') . '</strong>'
1422 : "<a href='$LJ::SITEROOT/community/mailing.bml?authas=$username'>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.massmailing') . "</a>";
1424 if (LJ
::is_enabled
('lj_art') && ($comm->prop('ljart_event') || $comm->prop('ljart_institut'))) {
1425 push @links, $page eq "ljart" ?
1426 "<strong>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.ljart') . "</strong>" :
1427 "<a href='$LJ::SITEROOT/community/ljart.bml?authas=$username'>" . LJ
::Lang
::ml
('/community/manage.bml.commlist.ljart') . "</a>",
1430 my $ret .= "<strong>" . LJ
::Lang
::ml
('/community/manage.bml.managelinks', { user
=> $comm->ljuser_display }) . "</strong> ";
1431 $ret .= join(" | ", @links);
1433 return "<p style='margin-bottom: 20px;'>$ret</p>";
1436 # Get membership and posting level settings for a community
1437 sub get_comm_settings
{
1440 my $cid = $c->{userid
};
1441 my ($membership, $postlevel);
1442 my $memkey = [ $cid, "commsettings:$cid" ];
1444 my $memval = LJ
::MemCache
::get
($memkey);
1445 ($membership, $postlevel) = @
$memval if ($memval);
1446 return ($membership, $postlevel)
1447 if ( $membership && $postlevel );
1449 my $dbr = LJ
::get_db_reader
();
1450 ($membership, $postlevel) =
1451 $dbr->selectrow_array("SELECT membership, postlevel FROM community WHERE userid=?", undef, $cid);
1453 LJ
::MemCache
::set
($memkey, [$membership,$postlevel] ) if ( $membership && $postlevel );
1455 return ($membership, $postlevel);
1458 # Set membership and posting level settings for a community
1459 sub set_comm_settings
{
1460 my ($c, $u, $opts) = @_;
1462 die "User cannot modify this community"
1463 unless (LJ
::can_manage_other
($u, $c));
1465 die "Membership and posting levels are not available"
1466 unless ($opts->{membership
} && $opts->{postlevel
});
1468 my $cid = $c->{userid
};
1470 my $dbh = LJ
::get_db_writer
();
1471 $dbh->do("REPLACE INTO community (userid, membership, postlevel) VALUES (?,?,?)" , undef, $cid, $opts->{membership
}, $opts->{postlevel
});
1473 my $memkey = [ $cid, "commsettings:$cid" ];
1474 LJ
::MemCache
::delete($memkey);
1481 die 'Expected parameter $u in LJ::is_maintainer' unless $u;
1482 die 'Expected parameter $c in LJ::is_maintainer' unless $c;
1484 return LJ
::RelationService
->is_relation_to($c->id, $u->id, 'A');
1487 sub count_maintainers
{
1489 my $ids = LJ
::load_rel_user
($c->id, 'A');
1493 return scalar @
$ids;
1497 sub set_community_user_edge
{
1498 my ($c, $u, $edge, $remote) = @_;
1500 unless ($edge =~ /^[A-Z]$/) {
1510 my ( $is_super, $poll ) = ( undef, undef );
1511 my $poll_id = $c->prop('election_poll_id');
1513 $poll = LJ
::Poll
->new($poll_id);
1514 $is_super = $poll->prop('supermaintainer');
1517 if ( $poll && $is_super && !$poll->is_closed ) {
1518 return LJ
::error
("Can't set user $u->{user} as maintainer for $c->{user}");
1521 LJ
::User
::UserlogRecord
::MaintainerAdd
->create(
1523 'maintid' => $u->userid,
1524 'remote' => $remote || $u,
1528 LJ
::set_rel
( $c->userid, $u->userid, $edge );