mourningdove/cgi-bin/DW/Controller/Rename.pm
2026-05-24 01:03:05 +00:00

510 lines
17 KiB
Perl

#!/usr/bin/perl
#
# DW::Controller::Rename
#
# This controller is for renames
#
# Authors:
# Afuna <coder.dw@afunamatata.com>
#
# Copyright (c) 2010-2018 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::Rename;
use strict;
use warnings;
use DW::Controller;
use DW::Routing;
use DW::Template;
use DW::Controller::Admin;
use DW::FormErrors;
use DW::RenameToken;
use DW::Shop;
DW::Routing->register_string( "/rename/swap", \&swap_handler, app => 1 );
# token is now passed as a get argument
DW::Routing->register_string( "/rename/index", \&rename_handler, app => 1 );
DW::Routing->register_string( "/admin/rename/index", \&rename_admin_handler, app => 1 );
DW::Routing->register_string( "/admin/rename/edit", \&rename_admin_edit_handler, app => 1 );
DW::Routing->register_string( "/admin/rename/new", \&siteadmin_rename_handler, app => 1 );
DW::Controller::Admin->register_admin_page(
'/',
path => '/admin/rename/',
ml_scope => '/admin/rename.tt',
privs => [ 'siteadmin:rename', 'payments' ]
);
sub rename_handler {
my ($opts) = @_;
my $r = DW::Request->get;
my ( $ok, $rv ) = controller();
return $rv unless $ok;
my $remote = LJ::get_remote();
return error_ml('rename.error.invalidaccounttype') unless $remote->is_personal;
my $vars = {};
my $post_args = DW::Request->get->post_args;
my $get_args = DW::Request->get->get_args;
my $given_token = $get_args->{giventoken};
my $token = DW::RenameToken->new( token => $given_token );
$get_args->{type} ||= "P";
$get_args->{type} = "P" unless $get_args->{type} =~ m/^(P|C)$/;
if ( $r->method eq "POST" ) {
# this is kind of ugly. $post_ok is a hashref of template args
# if it's a success, and false if it failed
my $errors = DW::FormErrors->new;
my $post_ok = handle_post( $token, $post_args, errors => $errors );
return success_ml( "/rename.tt.success", $post_ok ) if $post_ok;
$vars->{errors} = $errors;
}
$vars->{invalidtoken} = $given_token
if $given_token && !$token;
my $rename_to_errors = DW::FormErrors->new;
if ( $get_args->{checkuser} ) {
$vars->{checkusername} = {
user => $get_args->{checkuser},
status => $remote->can_rename_to( $get_args->{checkuser}, errors => $rename_to_errors )
? "available"
: "unavailable",
errors => $rename_to_errors
};
}
if ($token) {
if ( $token->applied || $token->revoked ) {
$vars->{usedtoken} = $token->token;
}
else {
$vars->{token} = $token;
# not using the regular authas logic because we want to exclude the current username
my $authas = LJ::make_authas_select(
$remote,
{
selectonly => 1,
type => $get_args->{type},
authas => $post_args->{authas} || $get_args->{authas},
}
);
$authas .= $remote->user if $get_args->{type} eq "P";
my @rel_types =
$get_args->{type} eq "P"
? qw( trusted_by watched_by trusted watched communities )
: ();
$vars->{rel_types} = \@rel_types;
# initialize the form based on previous posts (in case of error) or with some default values
$vars->{formdata} = {
authas => $authas,
journaltype => $get_args->{type},
journalname => $get_args->{type} eq "P" ? $remote->user : "communityname",
journalurl => $get_args->{type} eq "P" ? $remote->journal_base
: LJ::journal_base( "communityname", vhost => "community" ),
pageurl => "/rename?giventoken=" . $token->token,
token => $token->token,
touser => $post_args->{touser} || $get_args->{to} || "",
redirect => $post_args->{redirect} || "disconnect",
rel_options => %$post_args ? { map { $_ => 1 } $post_args->get_all("rel_options") }
: { map { $_ => 1 } @rel_types },
others => %$post_args ? { map { $_ => 1 } $post_args->get_all("others") }
: { email => 0 },
};
}
}
if ( !$token || ( $token && $token->applied ) || ( $token && $token->revoked ) ) {
# grab a list of tokens they can use in case they didn't provide a usable token
# assume we always have a remote because our controller is registered as requiring a remote (default behavior)
$vars->{unused_tokens} = DW::RenameToken->by_owner_unused( userid => $remote->userid );
}
return DW::Template->render_template( 'rename.tt', $vars );
}
sub handle_post {
my ( $token, $post_args, %opts ) = @_;
my $errors = $opts{errors} || DW::FormErrors->new;
unless ( LJ::check_form_auth( $post_args->{lj_form_auth} ) ) {
$errors->add( '', '/rename.tt.error.invalidform' );
return 0;
}
# the journal we are going to rename: yourself or a community you maintain
my $journal = LJ::get_authas_user( $post_args->{authas} );
$errors->add( 'authas', '/rename.tt.error.nojournal' ) unless $journal;
my $fromusername = $journal ? $journal->user : "";
my $tousername = $post_args->{touser};
my $redirect_journal = $post_args->{redirect} && $post_args->{redirect} eq "disconnect" ? 0 : 1;
$errors->add( 'redirect', '/rename.tt.error.noredirectopt' ) unless $post_args->{redirect};
# since you can't recover deleted relationships, but you can delete the relationships later if something was missed
# negate the form submission so we're explicitly stating which rels we want to delete, rather than deleting everything not listed
my %keep_rel = map { $_ => 1 } $post_args->get_all("rel_options");
my %del_rel =
map { +"del_$_" => !$keep_rel{$_} } qw( trusted_by watched_by trusted watched communities );
my %other_opts = map { $_ => 1 } $post_args->get_all("others");
if ( $other_opts{email} ) {
if ( $post_args->{redirect} ne "forward" ) {
$errors->add(
'redirect',
'/rename.tt.error.emailnotforward',
{ emaildomain => "\@$LJ::USER_DOMAIN" }
);
$other_opts{email} = 0;
}
unless ( $journal->can_have_email_alias ) {
$errors->add( 'others_email', '/rename.tt.error.emailnoalias' );
$other_opts{email} = 0;
}
}
# try the rename and see if there are any errors
$journal->rename(
$tousername,
user => LJ::get_remote(),
token => $token,
redirect => $redirect_journal,
redirect_email => $other_opts{email},
%del_rel, errors => $errors
);
return { from => $fromusername, to => $journal->user } unless $errors->exist;
# the list of errors should be present in the caller
return 0;
}
sub swap_handler {
my $r = DW::Request->get;
my ( $ok, $rv ) = controller( authas => 1 );
return $rv unless $ok;
my $remote = $rv->{remote};
return error_ml('rename.error.invalidaccounttype') unless $remote->is_personal;
my $vars = {};
my $post_args = DW::Request->get->post_args;
if ( $r->method eq "POST" ) {
# this is kind of ugly. $post_ok is a hashref of template args
# if it's a success, and false if it failed
my $errors = DW::FormErrors->new;
my $post_ok = handle_swap_post( $post_args, user => $remote, errors => $errors );
return success_ml( "/rename/swap.tt.success", $post_ok ) if $post_ok;
$vars->{errors} = $errors;
}
my $authas = LJ::make_authas_select(
$remote,
{
selectonly => 1,
authas => $post_args->{authas},
}
);
$vars->{authas} = $authas;
$vars->{formdata} = $post_args;
return DW::Template->render_template( 'rename/swap.tt', $vars );
}
sub handle_swap_post {
my ( $post_args, %opts ) = @_;
my $errors = $opts{errors} || DW::FormErrors->new;
$errors->add( '', '/rename.tt.error.invalidform' )
unless LJ::check_form_auth( $post_args->{lj_form_auth} );
my $journal = LJ::get_authas_user( $post_args->{authas} );
$errors->add( 'authas', '/rename/swap.tt.error.nojournal' ) unless $journal;
my $swapjournal = LJ::load_user( $post_args->{swapjournal} );
$errors->add( 'swapjournal', '/rename/swap.tt.error.invalidswapjournal' ) unless $swapjournal;
my $remote = $opts{user};
my $get_unused_tokens =
sub { @{ DW::RenameToken->by_owner_unused( userid => $_[0] ) || [] } };
my @check_users = ( $journal, $swapjournal );
unshift @check_users, $remote if $remote;
my %check_uids = map { $_->id => 1 } @check_users; # remove duplicates
my @unused_tokens = grep { defined } map { $get_unused_tokens->($_) } keys %check_uids;
$errors->add(
'',
'/rename/swap.tt.numtokens.toofew',
{ aopts => "href='$LJ::SHOPROOT/renames?for=self'" }
) unless @unused_tokens > 1;
return 0 if $errors->exist;
( $journal, $swapjournal ) = ( $swapjournal, $journal )
if $journal->is_community && $swapjournal->is_personal;
# let's do this
my $swap_errors = $opts{errors} || DW::FormErrors->new;
$journal->swap_usernames(
$swapjournal,
user => $opts{user},
tokens => \@unused_tokens,
errors => $swap_errors
);
return {
journal => $journal->ljuser_display,
swapjournal => $swapjournal->ljuser_display
}
unless $swap_errors->exist;
# the list of errors should be present in the caller
return 0;
}
sub rename_admin_handler {
my ( $ok, $rv ) = controller( privcheck => [ 'siteadmin:rename', 'payments' ] );
return $rv unless $ok;
my $r = DW::Request->get;
my $post_args = $r->post_args;
my $get_args = $r->get_args;
# just get the username, and not a user object, because username may no longer be valid for a user
my $user = $post_args->{user} || $get_args->{user};
my @renames;
if ($user) {
my @rename_tokens = sort { $a->rendate <=> $b->rendate }
@{ DW::RenameToken->by_username( user => $user ) || [] };
foreach my $token (@rename_tokens) {
push @renames, {
token => $token->token,
from => $token->fromuser,
to => $token->touser,
# FIXME: these should probably just be in DW::RenameToken instead
owner => LJ::load_userid( $token->ownerid ),
target => LJ::load_userid( $token->renuserid ),
date => LJ::mysql_time( $token->rendate ),
};
}
}
my $vars = {
%$rv,
# lookup a list of renames involving a username
user => $user,
renames => $user ? \@renames : undef,
};
return DW::Template->render_template( "admin/rename.tt", $vars );
}
sub rename_admin_edit_handler {
my ( $ok, $rv ) = controller( privcheck => [ 'siteadmin:rename', 'payments' ] );
return $rv unless $ok;
my $r = DW::Request->get;
my $post_args = $r->post_args;
my $get_args = $r->get_args;
my $token = DW::RenameToken->new( token => $get_args->{token} );
return error_ml('rename.error.notoken') unless defined $token;
my $u = LJ::load_userid( $token->renuserid );
my @rel_types = qw( trusted_by watched_by trusted watched communities );
my $form = {
from => $token->fromuser,
to => $token->touser,
byuser => LJ::load_userid( $token->ownerid ),
user => $u,
journaltype => $u ? $u->journaltype : "P",
};
# load up the old values
my $token_details = $token->details;
if ($token_details) {
$form->{redirect} = $token_details->{redirect}->{username} ? "forward" : "disconnect";
$form->{rel_options} = { map { $_ => !$token_details->{del}->{$_} } @rel_types };
$form->{others}->{email} = $token_details->{redirect}->{email};
}
my $vars = {
%$rv,
formdata => $form,
rel_types => \@rel_types,
token => $token,
nodetails => $token_details ? 0 : 1,
};
if ( $r->did_post ) {
my $errors = DW::FormErrors->new;
my $post_ok = handle_admin_post(
$token, $post_args,
journal => $u,
from_user => $token->fromuser,
to_user => $token->touser,
errors => $errors,
);
return DW::Template->render_template( 'success.tt',
{ message => "Successfully changed settings." } )
if $post_ok;
$vars->{errors} = $errors;
}
return DW::Template->render_template( "admin/rename_edit.tt", $vars );
}
sub handle_admin_post {
my ( $token, $post_args, %opts ) = @_;
my $errors = $opts{errors} || DW::FormErrors->new;
unless ( LJ::check_form_auth( $post_args->{lj_form_auth} ) ) {
$errors->add( '', '/rename.tt.error.invalidform' );
return 0;
}
my %rename_opts = ( user => LJ::get_remote(), from => $opts{from_user}, to => $opts{to_user} );
if ( $post_args->{override_redirect} ) {
if ( LJ::isu( $opts{journal} ) && $opts{from_user} && $opts{to_user} ) {
my $redirect_journal =
$post_args->{redirect} && $post_args->{redirect} eq "disconnect" ? 0 : 1;
$rename_opts{break_redirect}->{username} = !$redirect_journal;
}
else {
$errors->add_string( '',
"Cannot do redirect; invalid journal, or no username provided to rename from/to." );
}
}
if ( $post_args->{override_relationships} ) {
# since you can't recover deleted relationships, but you can delete the relationships later if something was missed
# negate the form submission so we're explicitly stating which rels we want to delete, rather than deleting everything not listed
my %keep_rel = map { $_ => 1 } $post_args->get_all("rel_options");
my %del_rel = map { +"del_$_" => !$keep_rel{$_} }
qw( trusted_by watched_by trusted watched communities );
$rename_opts{del} = \%del_rel;
}
if ( $post_args->{override_others} ) {
my %other_opts = map { $_ => 1 } $post_args->get_all("others");
# force email to false if we can't support forwarding for this user
if ( $other_opts{email} ) {
if ( $post_args->{redirect} ne "forward" ) {
$errors->add(
'redirect',
'/rename.tt.error.emailnotforward',
{ emaildomain => "\@$LJ::USER_DOMAIN" }
);
$other_opts{email} = 0;
}
unless ( $opts{journal}->can_have_email_alias ) {
$errors->add( 'others_email', '/rename.tt.error.emailnoalias' );
$other_opts{email} = 0;
}
}
$rename_opts{break_redirect}->{email} = !$other_opts{email};
}
$opts{journal}->apply_rename_opts(%rename_opts);
return $errors->exist ? 0 : 1;
}
sub siteadmin_rename_handler {
my ( $ok, $rv ) = controller( privcheck => [ 'siteadmin:rename', 'payments' ] );
return $rv unless $ok;
my $r = DW::Request->get;
my $post_args = DW::Request->get->post_args;
my $vars = {};
if ( $r->method eq "POST" ) {
my $errors = DW::FormErrors->new;
my $post_ok = handle_siteadmin_rename_post( $post_args, errors => $errors );
return DW::Template->render_template( 'success.tt',
{ message => "Successfully changed settings." } )
if $post_ok;
$vars->{errors} = $errors;
# also prefill form with previously submitted data
$vars->{prev_user} = $post_args->{user};
$vars->{prev_touser} = $post_args->{touser};
}
return DW::Template->render_template( "admin/rename_new.tt", $vars );
}
sub handle_siteadmin_rename_post {
my ( $post_args, %opts ) = @_;
my $errors = $opts{errors} || DW::FormErrors->new;
unless ( LJ::check_form_auth( $post_args->{lj_form_auth} ) ) {
$errors->add( '', '/rename.tt.error.invalidform' );
return 0;
}
my $from_user = LJ::load_user( $post_args->{user} );
my $to_user = $post_args->{touser};
$errors->add( 'user', '/rename.tt.error.nojournal' ) unless $from_user;
$from_user->rename(
$to_user,
token => DW::RenameToken->create_token( systemtoken => 1 ),
user => LJ::get_remote(),
force => 1,
errors => $errors,
form_from => 'user',
) if defined $from_user;
return $errors->exist ? 0 : 1;
}
1;