#!/usr/bin/perl # # Authors: # Afuna # # 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 .= ""; } $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;