fu: Close-up of Fu, bringing a scoop of water to her mouth (Default)
fu ([personal profile] fu) wrote in [site community profile] changelog2010-08-20 10:22 am

[dw-free] Implement renaming for communities

[commit: http://hg.dwscoalition.org/dw-free/rev/3467e0caff10]

http://bugs.dwscoalition.org/show_bug.cgi?id=2281

Community renames: communities may rename to personal journals under the
maintainer's control, or to unregistered usernames.

Patch by [personal profile] fu.

Files modified:
  • bin/upgrading/en.dat
  • cgi-bin/DW/Controller/Rename.pm
  • cgi-bin/DW/User/Rename.pm
  • cgi-bin/weblib.pl
  • htdocs/shop/renames.bml.text
  • t/rename.t
  • views/rename.tt
  • views/rename.tt.text
--------------------------------------------------------------------------------
diff -r 4954905b7319 -r 3467e0caff10 bin/upgrading/en.dat
--- a/bin/upgrading/en.dat	Fri Aug 20 14:25:23 2010 +0800
+++ b/bin/upgrading/en.dat	Fri Aug 20 18:22:13 2010 +0800
@@ -2433,6 +2433,8 @@ protocol.readonly=Your account is tempor
 
 rename.error.invalidaccounttype=Only personal journals can own rename tokens.
 
+rename.error.invalidjournaltypeto=This username is in use and is not a personal journal; only personal journals may be renamed to.
+
 rename.error.invalidfrom=Tried to rename an invalid journal.
 
 rename.error.invalidstatusfrom=Cannot rename [[from]]: must be an active journal, not deleted or suspended.
diff -r 4954905b7319 -r 3467e0caff10 cgi-bin/DW/Controller/Rename.pm
--- a/cgi-bin/DW/Controller/Rename.pm	Fri Aug 20 14:25:23 2010 +0800
+++ b/cgi-bin/DW/Controller/Rename.pm	Fri Aug 20 18:22:13 2010 +0800
@@ -47,6 +47,9 @@ sub rename_handler {
     my $post_args = DW::Request->get->post_args || {};
     my $get_args = DW::Request->get->get_args || {};
 
+    $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. Basically, it's a rendered template if it's a success, and a list of errors if it failed
@@ -74,15 +77,31 @@ sub rename_handler {
         } 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 )
+                : ();
+
             # initialize the form based on previous posts (in case of error) or with some default values
             $vars->{form} = {
-                from        => $remote->user,
-                journalurl  => $remote->journal_base,
+                authas      => $authas,
+                journaltype => $get_args->{type},
+                journalurl  => $get_args->{type} eq "P" ? $remote->journal_base : LJ::journal_base( "communityname", "community" ),
+                pageurl     => "/rename/" . $token->token,
                 token       => $token->token,
                 to          => $post_args->{touser} || $get_args->{to} || "",
                 redirect    => $post_args->{redirect} || "disconnect",
+                rel_types   => \@rel_types,
                 rel_options => %$post_args ? { map { $_ => 1 } $post_args->get( "rel_options" ) }
-                                            : { map { $_ => 1 } qw( trusted_by watched_by trusted watched communities ) },
+                                            : { map { $_ => 1 } @rel_types },
                 others      => %$post_args ? { map { $_ => 1 } $post_args->get( "others" ) }
                                             : { email => 0 },
             };
@@ -102,13 +121,12 @@ sub handle_post {
 sub handle_post {
     my ( $token, $post_args ) = @_;
 
-    # FIXME: replace with official tt-implementation
     return ( 0, [ LJ::Lang::ml( '/rename.tt.error.invalidform' ) ] ) unless LJ::check_form_auth( $post_args->{lj_form_auth} );
 
     my $errref = [];
 
     # the journal we are going to rename; yourself or (eventually) a community you maintain
-    my $journal = LJ::get_remote();
+    my $journal = LJ::get_authas_user( $post_args->{authas} );
     push @$errref, LJ::Lang::ml( '/rename.tt.error.nojournal' ) unless $journal;
 
     my $fromusername = $journal ? $journal->user : "";
@@ -136,7 +154,7 @@ sub handle_post {
     }
 
     # try the rename and see if there are any errors
-    $journal->rename( $tousername, token => $token, redirect => $redirect_journal, redirect_email => $other_opts{email}, %del_rel, errref => $errref );
+    $journal->rename( $tousername, user => LJ::get_remote(), token => $token, redirect => $redirect_journal, redirect_email => $other_opts{email}, %del_rel, errref => $errref );
 
     return ( 1, success_ml( "/rename.tt.success", { from => $fromusername, to => $journal->user } ) ) unless @$errref;
 
diff -r 4954905b7319 -r 3467e0caff10 cgi-bin/DW/User/Rename.pm
--- a/cgi-bin/DW/User/Rename.pm	Fri Aug 20 14:25:23 2010 +0800
+++ b/cgi-bin/DW/User/Rename.pm	Fri Aug 20 18:22:13 2010 +0800
@@ -99,22 +99,37 @@ sub can_rename_to {
         return 0;
     }
 
-    # only personal accounts can be renamed
+    my $check_basics = sub {
+        my ( $fromu, $tou ) = @_;
+
+        # able to rename to unregistered accounts
+        return { ret => 1 } unless $tou;
+
+        # some journals can not be renamed to
+        if ( $tou->is_suspended || $tou->is_readonly || $tou->is_locked || $tou->is_memorial || $tou->is_renamed ) {
+            push @$errref, LJ::Lang::ml( 'rename.error.invalidstatusto', { to => $tou->ljuser_display } );
+            return { ret => 0 };
+        }
+
+        # expunged users can always be renamed to
+        return { ret => 1 } if $tou->is_expunged;
+
+        # communities cannot be renamed to
+        if ( ! $tou->is_personal ) {
+            push @$errref, LJ::Lang::ml( 'rename.error.invalidjournaltypeto' );
+            return { ret => 0 };
+        }
+    };
+
+    # only personal and community accounts can be renamed
     if ( $self->is_personal ) {
 
         # able to rename to unregistered accounts
         my $tou = LJ::load_user( $tousername );
-        return 1 unless $tou;
 
-        # some journals can not be renamed to
-        if ( $tou->is_suspended || $tou->is_readonly || $tou->is_locked || $tou->is_memorial || $tou->is_renamed ) {
-            push @$errref, LJ::Lang::ml( 'rename.error.invalidstatusto', { to => $self->ljuser_display } );
-            return 0;
-        }
-
-        # expunged users can always be renamed to
-        return 1 if $tou->is_expunged;
-
+        # check basic stuff that is common for all types of renames
+        my $rv = $check_basics->( $self, $tou );
+        return $rv->{ret} if $rv;
 
         # deleted and visible journals have extra safeguards:
         # person-to-person
@@ -122,6 +137,21 @@ sub can_rename_to {
 
         push @$errref, LJ::Lang::ml( 'rename.error.unauthorized', { to => $tou->ljuser_display } );
         return 0;
+    } elsif ( $self->is_community && LJ::isu( $opts{user} ) ) {
+        my $admin = $opts{user};
+
+        # user must be able to control (be an admin of) community
+        return 0 unless $admin->can_manage_other( $self );
+
+        my $tou = LJ::load_user( $tousername );
+
+        # check basic stuff that is common for all renames       
+        my $rv = $check_basics->( $self, $tou );
+        return $rv->{ret} if $rv;
+
+        # community-to-person
+        # able to rename to another personal journal under admin's control
+        return 1 if DW::User::Rename::_are_same_person( $admin, $tou );
     }
 
     # be strict in what we accept
@@ -147,8 +177,9 @@ sub rename {
 
     my $errref = $opts{errref} || [];
 
+    my $remote = LJ::isu( $opts{user} ) ? $opts{user} : $self;
     push @$errref, LJ::Lang::ml( 'rename.error.tokeninvalid' ) unless $opts{token} && $opts{token}->isa( "DW::RenameToken" )
-            && $opts{token}->ownerid == $self->userid;
+            && $opts{token}->ownerid == $remote->userid;
     push @$errref, LJ::Lang::ml( 'rename.error.tokenapplied' ) if $opts{token} && $opts{token}->applied;
 
     my $can_rename_to = $self->can_rename_to( $tousername, %opts );
@@ -254,7 +285,16 @@ sub _rename {
 
     $self->break_redirects;
     DW::User::Rename->create_redirect_journal( $fromusername, $tousername ) if $opts{redirect};
-    my $del = $self->delete_relationships( del_trusted_by => $opts{del_trusted_by}, del_watched_by => $opts{del_watched_by}, del_trusted => $opts{del_trusted}, del_watched => $opts{del_watched}, del_communities => $opts{del_communities} );
+
+    my $del = "";
+    if ( $self->is_personal ) {
+        $del = $self->delete_relationships(
+            del_trusted_by => $opts{del_trusted_by},
+            del_watched_by => $opts{del_watched_by},
+            del_trusted => $opts{del_trusted},
+            del_watched => $opts{del_watched},
+            del_communities => $opts{del_communities} );
+    }
 
     # this deletes the email under the old username
     DW::User::Rename->break_email_redirection( $fromusername, $tousername ) unless $opts{redirect} && $opts{redirect_email};
@@ -350,6 +390,8 @@ sub delete_relationships {
 sub delete_relationships {
     my ( $self, %opts ) = @_;
 
+    return unless $self->is_personal;
+
     if ( $opts{del_trusted_by} ) {
         foreach ( $self->trusted_by_users ) {
             $_->remove_edge( $self, trust => {} );
diff -r 4954905b7319 -r 3467e0caff10 cgi-bin/weblib.pl
--- a/cgi-bin/weblib.pl	Fri Aug 20 14:25:23 2010 +0800
+++ b/cgi-bin/weblib.pl	Fri Aug 20 18:22:13 2010 +0800
@@ -191,15 +191,20 @@ sub make_authas_select {
     # only do most of form if there are options to select from
     if (@list > 1 || $list[0] ne $u->{'user'}) {
         my $ret;
-        my $label = $BML::ML{'web.authas.label'};
-        $label = $BML::ML{'web.authas.label.comm'} if ($opts->{'type'} eq "C");
-        $ret = ($opts->{'label'} || $label) . " ";
+        unless ( $opts->{selectonly} ) {
+            my $label = $BML::ML{'web.authas.label'};
+            $label = $BML::ML{'web.authas.label.comm'} if ($opts->{'type'} eq "C");
+            $ret = ($opts->{'label'} || $label) . " ";
+        }
+
         $ret .= LJ::html_select({ 'name' => 'authas',
                                  'selected' => $opts->{'authas'} || $u->{'user'},
                                  'class' => 'hideable',
                                  },
                                  map { $_, $_ } @list) . " ";
-        $ret .= LJ::html_submit(undef, $opts->{'button'} || $BML::ML{'web.authas.btn'});
+        $ret .= LJ::html_submit(undef, $opts->{'button'} || $BML::ML{'web.authas.btn'})
+            unless $opts->{selectonly};
+
         return $ret;
     }
 
diff -r 4954905b7319 -r 3467e0caff10 htdocs/shop/renames.bml.text
--- a/htdocs/shop/renames.bml.text	Fri Aug 20 14:25:23 2010 +0800
+++ b/htdocs/shop/renames.bml.text	Fri Aug 20 18:22:13 2010 +0800
@@ -12,9 +12,9 @@
 
 .giftfor.username=Username to receive this rename token:
 
-.intro.gift=Buy a rename token for someone else to allow them to change their journal's username.
+.intro.gift=Buy a rename token for someone else to allow them to change their journal's username, or the username of a community they manage.
 
-.intro.self=Buy a rename token for your journal to change your username.
+.intro.self=Buy a rename token for your journal to change your username, or the username of a community you manage.
 
 .action=Once your payment has gone through, the token will be listed on <a [[aopts]]>the rename page</a>.
 
diff -r 4954905b7319 -r 3467e0caff10 t/rename.t
--- a/t/rename.t	Fri Aug 20 14:25:23 2010 +0800
+++ b/t/rename.t	Fri Aug 20 18:22:13 2010 +0800
@@ -299,8 +299,7 @@ note( "-- username issues" );
 }
 
 note( "-- community-to-unregistered" );
-TODO: {
-    local $TODO = "community to unregistered";
+{
     my $admin = temp_user();
     my $fromu = temp_comm();
     my $tousername = $fromu->username . "_renameto";
@@ -315,8 +314,9 @@ TODO: {
 
     ok( ! LJ::load_user( $tousername ), "Username '$tousername' is unregistered" );
     ok( $fromu->can_rename_to( $tousername, user => $admin ), "Can rename to $tousername" );
-    ok( $fromu->rename( $tousername, token => new_token( $fromu ), user => $admin ), "Renamed community to $tousername" );
+    ok( $fromu->rename( $tousername, token => new_token( $admin ), user => $admin ), "Renamed community to $tousername" );
 
+    LJ::update_user( $admin, { status => 'A' } );
     ok( $admin->is_validated, "Admin was validated so could rename.");
     LJ::update_user( $admin, { status => 'N' } );
     ok( ! $admin->is_validated && ! $fromu->can_rename_to( $tousername . "_rename", user => $admin ), "Admin no longer validated; can no longer rename" );
@@ -351,13 +351,12 @@ TODO: {
     ok( $admin->can_manage( $tou ), "User can manage community tou" );
 
     ok( $fromu->can_rename_to( $tousername, user => $admin ), $admin->user . " can rename community fromu to existing community $tousername (is admin of both)" );
-    ok( $fromu->rename( $tousername, token => new_token( $fromu ), user => $admin ), $admin->user . " renamed community fromu to existing community $tousername" );
+    ok( $fromu->rename( $tousername, token => new_token( $admin ), user => $admin ), $admin->user . " renamed community fromu to existing community $tousername" );
 }
 
 note( "-- community-to-personal" );
-TODO: {
-    local $TODO = "community to personal";
-    my ( $admin, $tou ) = $create_users->(); 
+{
+    my ( $admin, $tou ) = $create_users->( validated => 1 ); 
     my $fromu = temp_comm();
     my $tousername = $tou->user;
 
@@ -367,14 +366,14 @@ TODO: {
 
     ok( ! $fromu->can_rename_to( $tousername, user => $admin ), "Cannot rename fromu to existing user $tousername (tou is a personal journal not under admin's control)" );
 
-    ( $admin, $tou ) = $create_users->( match => 1 ); 
+    ( $admin, $tou ) = $create_users->( match => 1, validated => 1 ); 
     $tousername = $tou->user;
 
     # make admin of fromu
     LJ::set_rel( $fromu, $admin, "A" );
     delete $LJ::REQ_CACHE_REL{$fromu->userid."-".$admin->userid."-A"};
     ok( $fromu->can_rename_to( $tousername, user => $admin ), $admin->user . " can rename community fromu to existing user $tousername (tou is a personal journal under admin's control)" );
-    ok( $fromu->rename( $tousername, token => new_token( $fromu ), user => $admin ), $admin->user . " renamed community fromu to existing community $tousername" );
+    ok( $fromu->rename( $tousername, token => new_token( $admin ), user => $admin ), $admin->user . " renamed community fromu to existing community $tousername" );
 }
 
 note( "-- personal-to-community" );
diff -r 4954905b7319 -r 3467e0caff10 views/rename.tt
--- a/views/rename.tt	Fri Aug 20 14:25:23 2010 +0800
+++ b/views/rename.tt	Fri Aug 20 18:22:13 2010 +0800
@@ -34,31 +34,38 @@ the same terms as Perl itself.  For a co
                 <label for="token">[%- '.form.rename.token.label' | ml -%]</label> [%- form.token | html -%]
             </div>
             <div class="formfield">
-                <label>[%- '.form.rename.fromuser.label' | ml -%]</label> [% form.from | html %]
+                <label>[%- '.form.rename.fromuser.label' | ml -%]</label> [% form.authas %]
+                [% IF form.journaltype == "P" %]
+                    <a href="[%- form.pageurl | url -%]?type=C">[%- '.form.switchtype.comm' | ml -%]</a>
+                [% ELSE %]
+                    <a href="[%- form.pageurl | url -%]">[%- '.form.switchtype.journal' | ml -%]</a>
+                [% END %]
             </div>
             <div class="formfield">
                 <label for="touser">[%- '.form.rename.touser.label' | ml -%]</label><input type="text" id="touser" name="touser" value="[%- form.to | html -%]" />
             </div>
             <div class="formfield">
                 <fieldset>
-                <legend>What do you want to do with your old username?</legend>
-                <input type="radio" name="redirect" value="forward" id="redirect_forward" [% 'checked="checked"' IF form.redirect == "forward" %]/><label for="redirect_forward">[%- '.form.rename.forward.label' | ml %]</label>
-                <p class='detail'>[%- '.form.rename.forward.note' | ml( journalurl = form.journalurl ) %]</p>
-                <input type="radio" name="redirect" value="disconnect" id="redirect_disconnect" [% 'checked="checked"' IF form.redirect == 'disconnect' %]/><label for="redirect_disconnect">[%- '.form.rename.disconnect.label' | ml %]</label>
-                <p class='detail'>[%- '.form.rename.disconnect.note' | ml( journalurl = form.journalurl ) %]</p>
+                <legend>[%- '.form.rename.oldusername.header.' _ form.journaltype | ml -%]</legend>
+                <input type="radio" name="redirect" value="forward" id="redirect_forward" [% 'checked="checked"' IF form.redirect == "forward" %]/><label for="redirect_forward">[%- '.form.rename.forward.label.' _ form.journaltype | ml %]</label>
+                <p class='detail'>[%- '.form.rename.forward.note.' _ form.journaltype | ml( journalurl = form.journalurl ) %]</p>
+                <input type="radio" name="redirect" value="disconnect" id="redirect_disconnect" [% 'checked="checked"' IF form.redirect == 'disconnect' %]/><label for="redirect_disconnect">[%- '.form.rename.disconnect.label.' _ form.journaltype | ml %]</label>
+                <p class='detail'>[%- '.form.rename.disconnect.note.' _ form.journaltype | ml( journalurl = form.journalurl ) %]</p>
                 </fieldset>
             </div>
         </fieldset>
 
 
+        [% IF form.rel_types.size > 0 %]
         <fieldset class="relationships">
             <legend>[% '.form.relationships.header' | ml %]</legend>
-            [% FOREACH rel IN [ "watched_by", "trusted_by", "watched", "trusted", "communities" ] %]
+            [% FOREACH rel IN form.rel_types %]
                 <div class="formfield">
                     <input type="checkbox" name="rel_options" value="[% rel %]" id="rel_[% rel %]" [%- 'checked="checked"' IF form.rel_options.$rel -%] /><label for="rel_[% rel %]">[% ".form.relationships.$rel" | ml %]</label>
                 </div>
             [% END %]
         </fieldset>
+        [% END %]
 
         <fieldset class="others">
             <legend>[% '.form.others.header' | ml %]</legend>
@@ -105,14 +112,23 @@ the same terms as Perl itself.  For a co
         <p>[% '.token.list.header' | ml %]</p>
         <ul>
         [% FOREACH token = unused_tokens %]
-            <li>[% token.token | html %] - <a href="/rename/[%- token.token | url %]
-            [%- IF checkusername.user AND checkusername.status == 'available' ~%]
-                ?to=[% checkusername.user | url ~%]
-                ">[% '.token.list.item.withname' | ml( username = checkusername.user ) %]
-            [%- ELSE ~%]
-                ">[% '.token.list.item' | ml %]
-            [% END ~%]
-            </a></li>
+            <li>[% token.token | html %] - <a href="[%- dw.site | url %]/rename/[% token.token | url %]
+            [%~ IF checkusername.user AND checkusername.status == 'available' ~%]
+                ?to=[%~ checkusername.user | url ~%]
+                ">[%~ '.token.list.item.withname' | ml( username = checkusername.user ) ~%]
+            [%~ ELSE ~%]
+                ">[%~ '.token.list.item' | ml ~%]
+            [%~ END ~%]
+            </a>
+            <a href="[%- dw.site | url %]/rename/[% token.token | url %]?type=C
+            [%~ IF checkusername.user AND checkusername.status == 'available' ~%]
+                &to=[%~ checkusername.user | url ~%]
+                ">[%~ '.token.list.item.comm.withname' | ml( username = checkusername.user ) ~%]
+            [%~ ELSE ~%]
+                ">[%~ '.token.list.item.comm' | ml ~%]
+            [%~ END ~%]
+            </a>
+            </li>
         [% END %]
         </ul>
     [% ELSE %]
diff -r 4954905b7319 -r 3467e0caff10 views/rename.tt.text
--- a/views/rename.tt.text	Fri Aug 20 14:25:23 2010 +0800
+++ b/views/rename.tt.text	Fri Aug 20 18:22:13 2010 +0800
@@ -44,19 +44,35 @@
 
 .form.rename.header=Rename
 
-.form.rename.disconnect.label=Disconnect your old username from your new username, leaving the old username free to use
+.form.rename.disconnect.label.C=Disconnect the old username from the new username, leaving the old username free to use
 
-.form.rename.disconnect.note=[[journalurl]] will not point to your new username; comments and community entries that you posted will still show your new username.
+.form.rename.disconnect.label.P=Disconnect your old username from your new username, leaving the old username free to use
 
-.form.rename.forward.label=Forward your old username to your new username
+.form.rename.disconnect.note.C=[[journalurl]] will not point to the new username.
 
-.form.rename.forward.note=[[journalurl]] will automatically redirect to your new username.
+.form.rename.disconnect.note.P=[[journalurl]] will not point to your new username; comments and community entries that you posted will still show your new username.
+
+.form.rename.forward.label.C=Forward your community's old username to the new username
+
+.form.rename.forward.label.P=Forward your old username to your new username
+
+.form.rename.forward.note.C=[[journalurl]] will automatically redirect to your community's new username.
+
+.form.rename.forward.note.P=[[journalurl]] will automatically redirect to your new username.
 
 .form.rename.fromuser.label=Rename from
+
+.form.rename.oldusername.header.C=What do you want to do with your community's old username?
+
+.form.rename.oldusername.header.P=What do you want to do with your old username?
 
 .form.rename.token.label=Rename token
 
 .form.rename.touser.label=Rename to
+
+.form.switchtype.comm=rename a community you own
+
+.form.switchtype.journal=rename your journal
 
 
 .success=Successfully renamed journal from [[from]] to [[to]].
@@ -71,6 +87,10 @@
 
 .token.list.item.withname=Use token to rename journal to [[username]]
 
+.token.list.item.comm= Use token to rename a community
+
+.token.list.item.comm.withname=Use token to rename a community to [[username]]
+
 .token.notoken=<a [[aopts]]>Purchase a rename token</a> to perform a rename.
 
 .token.used=Token has been used: [[token]]
--------------------------------------------------------------------------------