398 lines
14 KiB
Perl
398 lines
14 KiB
Perl
|
|
#!/usr/bin/perl
|
||
|
|
#
|
||
|
|
# Authors:
|
||
|
|
# Afuna <coder.dw@afunamatata.com>
|
||
|
|
#
|
||
|
|
# Copyright (c) 2014 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::Controller::Circle;
|
||
|
|
|
||
|
|
use strict;
|
||
|
|
use DW::Controller;
|
||
|
|
use DW::Routing;
|
||
|
|
use DW::Template;
|
||
|
|
|
||
|
|
=head1 NAME
|
||
|
|
|
||
|
|
DW::Controller::Circle - Circle management
|
||
|
|
|
||
|
|
=cut
|
||
|
|
|
||
|
|
DW::Routing->register_regex( '^/circle/(.+)/edit$', \&individual_edit_handler, app => 1 );
|
||
|
|
|
||
|
|
DW::Routing->register_redirect(
|
||
|
|
'/manage/circle/add',
|
||
|
|
sub { "/circle/$_[0]->{user}/edit" },
|
||
|
|
keep_args => ["action"]
|
||
|
|
);
|
||
|
|
DW::Routing->register_redirect( '/community/join', sub { "/circle/$_[0]->{comm}/edit" } );
|
||
|
|
DW::Routing->register_redirect( '/community/leave', sub { "/circle/$_[0]->{comm}/edit" } );
|
||
|
|
|
||
|
|
sub individual_edit_handler {
|
||
|
|
my ( $opts, $username ) = @_;
|
||
|
|
|
||
|
|
my ( $ok, $rv ) = controller( form_auth => 1 );
|
||
|
|
return $rv unless $ok;
|
||
|
|
|
||
|
|
my $r = $rv->{r};
|
||
|
|
my $remote = $rv->{remote};
|
||
|
|
my $get = $r->get_args;
|
||
|
|
my $ml_scope = "/circle/individual-edit.tt";
|
||
|
|
|
||
|
|
my $target_u = LJ::load_user($username);
|
||
|
|
return error_ml( "$ml_scope.error.invalidaccount", { user => LJ::ehtml($username) } )
|
||
|
|
unless $target_u;
|
||
|
|
|
||
|
|
if ( $target_u->is_redirect && $target_u->prop('renamedto') ) {
|
||
|
|
return $r->redirect(
|
||
|
|
LJ::create_url( "/circle/" . $target_u->prop('renamedto') . "/edit" ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
my %edges;
|
||
|
|
my $calculate_member_edge = sub {
|
||
|
|
my $edge = {
|
||
|
|
show => 1,
|
||
|
|
type => "membership",
|
||
|
|
on => $remote->member_of($target_u),
|
||
|
|
};
|
||
|
|
|
||
|
|
my $did_check_membership_type;
|
||
|
|
$edge->{can_change} =
|
||
|
|
$edge->{on}
|
||
|
|
? $remote->can_leave( $target_u, errref => \$edge->{error} )
|
||
|
|
: $remote->can_join(
|
||
|
|
$target_u,
|
||
|
|
errref => \$edge->{error},
|
||
|
|
membership_ref => \$did_check_membership_type
|
||
|
|
);
|
||
|
|
|
||
|
|
# add to the error message if we have closed membership
|
||
|
|
my $want_join = !$edge->{on};
|
||
|
|
my $has_membership_limitations = !$target_u->is_open_membership;
|
||
|
|
my $has_postlevel_limitations =
|
||
|
|
$target_u->post_level eq 'select' && !$target_u->prop('comm_postlevel_new');
|
||
|
|
|
||
|
|
if ( !$want_join && $has_membership_limitations ) {
|
||
|
|
$edge->{needs_leave_warning} = $target_u->is_closed_membership ? "closed" : "moderated";
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $want_join && ( $has_membership_limitations || $has_postlevel_limitations ) ) {
|
||
|
|
my $us = LJ::load_userids( $target_u->maintainer_userids );
|
||
|
|
my @admins;
|
||
|
|
foreach ( sort { $a->{user} cmp $b->{user} } values %$us ) {
|
||
|
|
next unless $_ && $_->is_visible;
|
||
|
|
push @admins, $_->ljuser_display;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $target_u->is_closed_membership ) {
|
||
|
|
$edge->{error} .= " "
|
||
|
|
. LJ::Lang::ml( "/circle/individual-edit.tt.error.membership_closed",
|
||
|
|
{ admins => join ", ", @admins } );
|
||
|
|
}
|
||
|
|
elsif ( $target_u->is_moderated_membership && $did_check_membership_type ) {
|
||
|
|
|
||
|
|
# a bit weird here because we want to cancel out the error message if we have moderated membership
|
||
|
|
# (but only if there's not some other reason preventing us from joining the community)
|
||
|
|
# that is, it would be bad if we allowed users to request membership to moderated communities they're banned from!
|
||
|
|
$edge->{error} = undef;
|
||
|
|
$edge->{moderated_membership} = 1;
|
||
|
|
$edge->{can_change} = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# moderated posting, and new members are *not* given posting access upon joining
|
||
|
|
if ($has_postlevel_limitations) {
|
||
|
|
$edge->{moderated_posting} = 1;
|
||
|
|
$edge->{admin_list} = \@admins;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$edge->{lastadmin_deletedcomm} = 1
|
||
|
|
if $target_u->is_deleted
|
||
|
|
&& $remote->can_manage($target_u)
|
||
|
|
&& length( $target_u->maintainer_userids ) == 1;
|
||
|
|
return $edge;
|
||
|
|
};
|
||
|
|
|
||
|
|
my $calculate_access_edge = sub {
|
||
|
|
my $edge = {
|
||
|
|
show => !$remote->equals($target_u) && $target_u->is_individual,
|
||
|
|
type => "access",
|
||
|
|
on => $remote->trusts($target_u),
|
||
|
|
};
|
||
|
|
|
||
|
|
# always allow to remove access, but only conditionally allow to grant access
|
||
|
|
my $error;
|
||
|
|
my $can_trust = $remote->can_trust( $target_u, errref => \$error );
|
||
|
|
$edge->{can_change} = $edge->{on} ? 1 : $can_trust;
|
||
|
|
$edge->{error} = $error unless $edge->{can_change};
|
||
|
|
|
||
|
|
# populate access filters (only allow to modify access filters if we can still grant access -- if we can't, no filters for them;
|
||
|
|
# only thing they can do is remove existing access)
|
||
|
|
my @access_filters;
|
||
|
|
foreach my $filter ( $can_trust ? $remote->trust_groups : () ) {
|
||
|
|
my $g = $filter->{groupnum};
|
||
|
|
my $ck = ( $remote->trustmask($target_u) & ( 1 << $g ) );
|
||
|
|
push @access_filters,
|
||
|
|
{
|
||
|
|
label => $filter->{groupname},
|
||
|
|
name => "bit_$g",
|
||
|
|
selected => $ck
|
||
|
|
};
|
||
|
|
}
|
||
|
|
$edge->{filters} = \@access_filters;
|
||
|
|
my $action = $get->{action} // '';
|
||
|
|
$edge->{expand_filters} = $edge->{on} || ( $action eq "access" );
|
||
|
|
return $edge;
|
||
|
|
};
|
||
|
|
|
||
|
|
my $calculate_subscribe_edge = sub {
|
||
|
|
my $edge = {
|
||
|
|
show => 1,
|
||
|
|
type => "subscribe",
|
||
|
|
on => $remote->watches($target_u),
|
||
|
|
};
|
||
|
|
|
||
|
|
# always allow to remove subscription, but only conditionally allow to subscribe
|
||
|
|
my $error;
|
||
|
|
my $can_watch = $remote->can_watch( $target_u, errref => \$error );
|
||
|
|
$edge->{can_change} = $edge->{on} ? 1 : $can_watch;
|
||
|
|
$edge->{error} = $error unless $edge->{can_change};
|
||
|
|
|
||
|
|
# populate content filters
|
||
|
|
my @content_filters;
|
||
|
|
foreach my $filter (
|
||
|
|
$can_watch
|
||
|
|
? sort { $a->{sortorder} <=> $b->{sortorder} } $remote->content_filters
|
||
|
|
: ()
|
||
|
|
)
|
||
|
|
{
|
||
|
|
my $ck = $filter->contains_userid( $target_u->userid )
|
||
|
|
|| ( $filter->is_default && !$edge->{on} );
|
||
|
|
my $fid = $filter->id;
|
||
|
|
push @content_filters,
|
||
|
|
{
|
||
|
|
label => $filter->{name},
|
||
|
|
name => "content_$fid",
|
||
|
|
selected => $ck,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
$edge->{filters} = \@content_filters;
|
||
|
|
my $action = $get->{action} // '';
|
||
|
|
$edge->{expand_filters} = $edge->{on} || ( $action eq "subscribe" );
|
||
|
|
return $edge;
|
||
|
|
};
|
||
|
|
|
||
|
|
my $calculate_edges = sub {
|
||
|
|
my $which = $_[0] || "all";
|
||
|
|
|
||
|
|
# each edge hash looks like this:
|
||
|
|
# show => 1 / 0
|
||
|
|
# type => "membership",
|
||
|
|
# on => $remote->member_of( $target_u ),
|
||
|
|
# can_change => ...
|
||
|
|
# error => ...
|
||
|
|
# filters => ...
|
||
|
|
# (edge-specific items)
|
||
|
|
|
||
|
|
if ( $target_u->is_community ) {
|
||
|
|
$edges{member} = $calculate_member_edge->() if $which eq "member" || $which eq "all";
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$edges{access} = $calculate_access_edge->() if $which eq "access" || $which eq "all";
|
||
|
|
}
|
||
|
|
$edges{subscribe} = $calculate_subscribe_edge->()
|
||
|
|
if $which eq "subscribe" || $which eq "all";
|
||
|
|
};
|
||
|
|
|
||
|
|
$calculate_edges->();
|
||
|
|
if ( $r->did_post ) {
|
||
|
|
my $post = $r->post_args;
|
||
|
|
|
||
|
|
my $member_status_new = $post->{"action:membership"};
|
||
|
|
if ( $member_status_new && !$edges{member}->{error} ) {
|
||
|
|
if ( $post->{new_state} eq "on" ) {
|
||
|
|
|
||
|
|
# join
|
||
|
|
|
||
|
|
# can members join this community openly?
|
||
|
|
if ( $target_u->is_moderated_membership ) {
|
||
|
|
|
||
|
|
# hit up the maintainers to let them know a join was requested
|
||
|
|
$target_u->comm_join_request($remote);
|
||
|
|
$edges{member}->{status_ok} = LJ::Lang::ml("$ml_scope.success.join_request");
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# join unconditionally
|
||
|
|
my $joined = $remote->join_community($target_u);
|
||
|
|
if ($joined) {
|
||
|
|
|
||
|
|
# update the display status
|
||
|
|
$calculate_edges->("member");
|
||
|
|
|
||
|
|
# show success messages and links
|
||
|
|
my $message = LJ::Lang::ml( "$ml_scope.success.join",
|
||
|
|
{ user => $target_u->ljuser_display } );
|
||
|
|
|
||
|
|
my $show_join_post_link = $target_u->hide_join_post_link ? 0 : 1;
|
||
|
|
my $post_url;
|
||
|
|
$post_url =
|
||
|
|
LJ::create_url( "/update", args => { usejournal => $target_u->user } )
|
||
|
|
if $show_join_post_link && $remote->can_post_to($target_u);
|
||
|
|
my $posting_guidelines_entry_url;
|
||
|
|
if ( $target_u->posting_guidelines_location eq "E" ) {
|
||
|
|
$posting_guidelines_entry_url = $target_u->posting_guidelines_url;
|
||
|
|
}
|
||
|
|
elsif ( $target_u->posting_guidelines_location eq "P" ) {
|
||
|
|
$posting_guidelines_entry_url = $target_u->profile_url;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $post_url || $posting_guidelines_entry_url ) {
|
||
|
|
$message .= "<ul>";
|
||
|
|
$message .=
|
||
|
|
"<li><a href='$post_url'>"
|
||
|
|
. LJ::Lang::ml("$ml_scope.success.join.actions.post")
|
||
|
|
. "</a></li>"
|
||
|
|
if $post_url;
|
||
|
|
$message .=
|
||
|
|
"<li><a href='$posting_guidelines_entry_url'>"
|
||
|
|
. LJ::Lang::ml("$ml_scope.success.join.actions.guidelines")
|
||
|
|
. "</a></li>"
|
||
|
|
if $posting_guidelines_entry_url;
|
||
|
|
$message .= "</ul>";
|
||
|
|
}
|
||
|
|
|
||
|
|
$edges{member}->{status_ok} = $message;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# show the error (from LJ::last_error)
|
||
|
|
$edges{member}->{status_error} = LJ::last_error();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# leave
|
||
|
|
$remote->leave_community($target_u);
|
||
|
|
$calculate_edges->("member");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
unless ( $edges{access}->{error} ) {
|
||
|
|
my $already_trusted = $remote->trusts($target_u) ? 1 : 0;
|
||
|
|
|
||
|
|
my $did_change_access = $post->{"action:access"};
|
||
|
|
my $did_change_access_filters = $post->{"action:accessfilters"};
|
||
|
|
|
||
|
|
my $do_grant_access = ( $did_change_access && $post->{new_state} eq "on" )
|
||
|
|
|| ( $did_change_access_filters && !$already_trusted ) ? 1 : 0;
|
||
|
|
my $do_revoke_access = $did_change_access && $post->{new_state} eq "off";
|
||
|
|
my $do_update_trustmask = $did_change_access_filters || $do_grant_access;
|
||
|
|
|
||
|
|
if ($do_grant_access) {
|
||
|
|
|
||
|
|
# grant access
|
||
|
|
$remote->add_edge(
|
||
|
|
$target_u,
|
||
|
|
trust => {
|
||
|
|
nonotify => $already_trusted ? 1 : 0,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($do_revoke_access) {
|
||
|
|
$remote->remove_edge(
|
||
|
|
$target_u,
|
||
|
|
trust => {
|
||
|
|
nonotify => $already_trusted ? 0 : 1,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($do_update_trustmask) {
|
||
|
|
|
||
|
|
# calculate trustmask
|
||
|
|
my $gmask = 1;
|
||
|
|
foreach my $bit ( 1 .. 60 ) {
|
||
|
|
next unless $post->{"bit_$bit"};
|
||
|
|
$gmask |= ( 1 << $bit );
|
||
|
|
}
|
||
|
|
$remote->trustmask( $target_u, $gmask );
|
||
|
|
}
|
||
|
|
|
||
|
|
$calculate_edges->("access");
|
||
|
|
}
|
||
|
|
|
||
|
|
unless ( $edges{subscribe}->{error} ) {
|
||
|
|
my $already_watched = $remote->watches($target_u) ? 1 : 0;
|
||
|
|
|
||
|
|
my $did_change_subscribe = $post->{"action:subscribe"};
|
||
|
|
my $did_change_subscribe_filters = $post->{"action:subscribefilters"};
|
||
|
|
|
||
|
|
my $do_subscribe = ( $did_change_subscribe && $post->{new_state} eq "on" )
|
||
|
|
|| ( $did_change_subscribe_filters && !$already_watched );
|
||
|
|
my $do_unsubscribe = $did_change_subscribe && $post->{new_state} eq "off";
|
||
|
|
my $do_update_filters = $did_change_subscribe_filters || $do_subscribe;
|
||
|
|
|
||
|
|
if ($do_subscribe) {
|
||
|
|
|
||
|
|
#my $fg = LJ::color_to_db( "#000000" );
|
||
|
|
#my $bg = LJ::color_to_db( "#ffffff" );
|
||
|
|
|
||
|
|
# subscribe
|
||
|
|
$remote->add_edge(
|
||
|
|
$target_u,
|
||
|
|
watch => {
|
||
|
|
|
||
|
|
# fgcolor => $fg,
|
||
|
|
# bgcolor => $bg,
|
||
|
|
nonotify => $already_watched ? 1 : 0,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($do_unsubscribe) {
|
||
|
|
$remote->remove_edge(
|
||
|
|
$target_u,
|
||
|
|
watch => {
|
||
|
|
nonotify => $already_watched ? 0 : 1,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($do_update_filters) {
|
||
|
|
my @content_filters = $remote->content_filters;
|
||
|
|
my $filter_id;
|
||
|
|
foreach my $filter (@content_filters) {
|
||
|
|
$filter_id = $filter->id;
|
||
|
|
|
||
|
|
# add to filter if box was checked and user is not already in filter
|
||
|
|
$filter->add_row( userid => $target_u->userid )
|
||
|
|
if $post->{"content_$filter_id"}
|
||
|
|
&& !$filter->contains_userid( $target_u->userid );
|
||
|
|
|
||
|
|
# remove from filter if box was not checked and user is in filter
|
||
|
|
$filter->delete_row( $target_u->userid )
|
||
|
|
if !$post->{"content_$filter_id"}
|
||
|
|
&& $filter->contains_userid( $target_u->userid );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$calculate_edges->("subscribe");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $vars = {
|
||
|
|
u => $target_u,
|
||
|
|
edges => \%edges,
|
||
|
|
|
||
|
|
form_url => LJ::create_url(),
|
||
|
|
};
|
||
|
|
|
||
|
|
return DW::Template->render_template( 'circle/individual-edit.tt', $vars );
|
||
|
|
}
|
||
|
|
|
||
|
|
1;
|