mourningdove/cgi-bin/DW/Controller/Circle.pm

398 lines
14 KiB
Perl
Raw Permalink Normal View History

2026-05-24 01:03:05 +00:00
#!/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;