mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)
Mark Smith ([staff profile] mark) wrote in [site community profile] changelog2009-03-07 06:08 am

[dw-free] Implement delete_trust_group method

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

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

Add delete_trust_group method. Deprecate editfriends and editfriendgroups
protocol modes.

Patch by [staff profile] mark.

Files modified:
  • bin/test/test-wtf
  • cgi-bin/DW/User/Edges/WatchTrust.pm
  • cgi-bin/ljprotocol.pl
  • cgi-bin/taglib.pl
--------------------------------------------------------------------------------
diff -r d655bcb8c70f -r dfd18646e8e7 bin/test/test-wtf
--- a/bin/test/test-wtf	Fri Mar 06 17:26:56 2009 -0800
+++ b/bin/test/test-wtf	Sat Mar 07 06:07:58 2009 +0000
@@ -419,6 +419,30 @@ push @tests, [ 'test contains', sub
     return 1;
 } ];
 
+
+################################################################################
+push @tests, [ 'delete trust group 3', sub
+{
+    # have to create a group with a known id for these tests
+    $u1->edit_trust_group( id => 3, groupname => 'bar group 3', _force_create => 1 );
+
+    $u1->trust_group_contains( $u2, 3 ) == 1 or return 0;
+    $u1->trust_group_contains( $u2, 4 ) == 1 or return 0;
+    $u1->trust_group_contains( $u2, 5 ) == 0 or return 0;
+
+    # now delete the group
+    $u1->delete_trust_group( name => 'bar group 3' ) or return 0;
+
+    $u1->trust_group_contains( $u2, 3 ) == 0 or return 0;
+    $u1->trust_group_contains( $u2, 4 ) == 1 or return 0;
+    $u1->trust_group_contains( $u2, 5 ) == 0 or return 0;
+
+    # validate group doesn't fetch
+    $u1->trust_groups( name => 'bar group 3' ) and return 0;
+
+    return 1;
+} ];
+
 ################################################################################
 push @tests, [ 'clear groups', sub
 {
diff -r d655bcb8c70f -r dfd18646e8e7 cgi-bin/DW/User/Edges/WatchTrust.pm
--- a/cgi-bin/DW/User/Edges/WatchTrust.pm	Fri Mar 06 17:26:56 2009 -0800
+++ b/cgi-bin/DW/User/Edges/WatchTrust.pm	Sat Mar 07 06:07:58 2009 +0000
@@ -772,6 +772,95 @@ sub edit_trust_group {
 *LJ::User::edit_trust_group = \&edit_trust_group;
 
 
+# deletes a trust_group, arguments is a hash of options
+#   id => NNN,       delete by id (1..60)
+#   name => "ZZZ",   or delete by name
+#
+# specify either the id or the name.  note that deletion is a rather permanent
+# option that will remove this group from all entries that are secured to it
+# as well as remove this bit from all trustmasks.
+#
+# returns 1/0.
+#
+sub delete_trust_group {
+    my ( $u, %opts ) = @_;
+    $u = LJ::want_user( $u ) or confess 'invalid user object';
+
+    # use existing accessor to figure out what group they mean
+    my $grp = $u->trust_groups( id => $opts{id}, name => $opts{name} );
+    return 0 unless $grp;
+
+    # set bit to remove
+    my $bit = $grp->{groupnum}+0;
+    return 0 unless $bit >= 1 && $bit <= 60;
+
+    # remove the bits for deleted groups from all friends groupmasks
+    my $dbh = LJ::get_db_writer()
+        or return 0;
+    $dbh->do(
+        q{UPDATE wt_edges SET groupmask = groupmask & ~(1 << ?) WHERE from_userid = ?},
+        undef, $bit, $u->id
+    );
+    return 0 if $dbh->err;
+
+    # remove all posts from allowing that group:
+    my @posts_to_clean = @{ $u->selectcol_arrayref(
+        q{SELECT jitemid FROM logsec2 WHERE journalid = ? AND allowmask & (1 << ?)},
+        undef, $u->id, $bit
+    ) || [] };
+
+    # now clean the posts while we can, this is a loop so we can do it in blocks of twenty
+    # as it's somewhat hard on the database to do this enmasse
+    my $userid = $u->id; # convenience
+    while ( @posts_to_clean ) {
+        my @batch = splice( @posts_to_clean, 0, 50 );
+
+        # actually updates the entries.  note that we do not return an error here because
+        # it's not the end of the world if one of these fails...
+        my $in = join ',', @batch;
+        $u->do("UPDATE log2 SET allowmask=allowmask & ~(1 << $bit) ".
+               "WHERE journalid=$userid AND jitemid IN ($in) AND security='usemask'");
+        $u->do("UPDATE logsec2 SET allowmask=allowmask & ~(1 << $bit) ".
+               "WHERE journalid=$userid AND jitemid IN ($in)");
+
+        foreach my $id (@batch) {
+            LJ::MemCache::delete([$userid, "log2:$userid:$id"]);
+        }
+        LJ::MemCache::delete([$userid, "log2lt:$userid"]);
+    }
+
+    # notify the tags system so it can do its thing
+    LJ::Tags::deleted_trust_group( $u, $bit );
+
+    # iterate over everybody in this group and remove the bit
+    foreach my $tid ( keys %{ $u->trust_group_list( id => $bit ) || {} } ) {
+        $dbh->do(
+            q{UPDATE wt_edges SET groupmask = groupmask & ~(1 << ?) WHERE from_userid = ? AND to_userid = ?},
+            undef, $bit, $u->id, $tid
+        );
+
+        # don't forget memcache
+        LJ::MemCache::delete( [$userid, "trustmask:$userid:$tid"] );
+    }
+
+    # finally remove the trust group, huzzah
+    $dbh->do(
+        q{DELETE FROM trust_groups WHERE userid = ? AND groupnum = ?},
+        undef, $u->id, $bit
+    );
+    return 0 if $dbh->err;
+
+    # invalidate memcache of friends/groups
+    LJ::memcache_kill( $u->id, "trust_group" );
+    LJ::memcache_kill( $u->id, "wt_list" );
+
+    # sister mary of the holy hand grenade says hi and apologies if any of the
+    # above failed.  we think it worked by this point, though.
+    return 1;
+}
+*LJ::User::delete_trust_group = \&delete_trust_group;
+
+
 # alters a trustmask to munge someone's group membership
 #
 #   $u->edit_trustmask( $otheru, ARGUMENTS )
diff -r d655bcb8c70f -r dfd18646e8e7 cgi-bin/ljprotocol.pl
--- a/cgi-bin/ljprotocol.pl	Fri Mar 06 17:26:56 2009 -0800
+++ b/cgi-bin/ljprotocol.pl	Sat Mar 07 06:07:58 2009 +0000
@@ -2247,352 +2247,14 @@ sub getevents
     return $res;
 }
 
-sub editfriends
-{
-    my ($req, $err, $flags) = @_;
-
-    # TODO(mark): most of this code can move to the new trust groups editing
-    #             sort of thing, but for now just say we're deprecated
-    return fail( $err, 504 );
-
-    return undef unless authenticate($req, $err, $flags);
-
-    my $u = $flags->{'u'};
-    my $userid = $u->{'userid'};
-    my $dbh = LJ::get_db_writer();
-    my $sth;
-
-    return fail($err,306) unless $dbh;
-
-    # do not let locked people do this
-    return fail($err, 308) if $u->{statusvis} eq 'L';
-
-    my $res = {};
-
-    ## first, figure out who the current friends are to save us work later
-    my %curfriend;
-    my $friend_count = 0;
-    # TAG:FR:protocol:editfriends1
-    $sth = $dbh->prepare("SELECT u.user FROM useridmap u, friends f ".
-                         "WHERE u.userid=f.friendid AND f.userid=$userid");
-    $sth->execute;
-    while (my ($friend) = $sth->fetchrow_array) {
-        $curfriend{$friend} = 1;
-        $friend_count++;
-    }
-    $sth->finish;
-
-    # perform the deletions
-  DELETEFRIEND:
-    foreach (@{$req->{'delete'}})
-    {
-        my $deluser = LJ::canonical_username($_);
-        next DELETEFRIEND unless $curfriend{$deluser};
-
-        $u->remove_edge( LJ::get_userid( $deluser ), watch => {}, trust => {} );
-        $friend_count--;
-    }
-
-    my $error_flag = 0;
-    my $friends_added = 0;
-    my $fail = sub {
-        LJ::memcache_kill($userid, "friends");
-        return fail($err, $_[0], $_[1]);
-    };
-
-    # only people, shared journals, and owned syn feeds can add friends
-    return $fail->(104, "Journal type cannot add friends")
-        unless ($u->{'journaltype'} eq 'P' ||
-                $u->{'journaltype'} eq 'S' ||
-                $u->{'journaltype'} eq 'I' ||
-                ($u->{'journaltype'} eq "Y" && $u->password));
-
-    # Don't let suspended users add friend
-    return $fail->(305, "Suspended journals cannot add friends.")
-        if ($u->is_suspended);
-
-     my $sclient = LJ::theschwartz();
-
-    # perform the adds
-  ADDFRIEND:
-    foreach my $fa (@{$req->{'add'}})
-    {
-        unless (ref $fa eq "HASH") {
-            $fa = { 'username' => $fa };
-        }
-
-        my $aname = LJ::canonical_username($fa->{'username'});
-        unless ($aname) {
-            $error_flag = 1;
-            next ADDFRIEND;
-        }
-
-        $friend_count++ unless $curfriend{$aname};
-
-        my $err;
-#TODO(mark): wtf
-        return $fail->(104, "$err")
-            unless 1;#$u->can_add_friends(\$err, { 'numfriends' => $friend_count, friend => $fa });
-
-        my $fg = $fa->{'fgcolor'} || "#000000";
-        my $bg = $fa->{'bgcolor'} || "#FFFFFF";
-        if ($fg !~ /^\#[0-9A-F]{6,6}$/i || $bg !~ /^\#[0-9A-F]{6,6}$/i) {
-            return $fail->(203, "Invalid color values");
-        }
-
-        my $row = LJ::load_user($aname);
-        my $currently_is_friend = LJ::is_friend($u, $row);
-        my $currently_is_banned = LJ::is_banned($u, $row);
-
-        # XXX - on some errors we fail out, on others we continue and try adding
-        # any other users in the request. also, error message for redirect should
-        # point the user to the redirected username.
-        if (! $row) {
-            $error_flag = 1;
-        } elsif ($row->{'journaltype'} eq "R") {
-            return $fail->(154);
-        } elsif ($row->{'statusvis'} ne "V") {
-            $error_flag = 1;
-        } else {
-            $friends_added++;
-            my $added = { 'username' => $aname,
-                          'fullname' => $row->{'name'},
-                      };
-            if ($req->{'ver'} >= 1) {
-                LJ::text_out(\$added->{'fullname'});
-            }
-            push @{$res->{'added'}}, $added;
-
-            my $qfg = LJ::color_todb($fg);
-            my $qbg = LJ::color_todb($bg);
-
-            my $friendid = $row->{'userid'};
-
-            my $gmask = $fa->{'groupmask'};
-            if (! $gmask && $curfriend{$aname}) {
-                # if no group mask sent, use the existing one if this is an existing friend
-                # TAG:FR:protocol:editfriends3_getmask
-                my $sth = $dbh->prepare("SELECT groupmask FROM friends ".
-                                        "WHERE userid=$userid AND friendid=$friendid");
-                $sth->execute;
-                $gmask = $sth->fetchrow_array;
-            }
-
-            # force bit 0 and 61 on, since people who use this old path are going to
-            # be forced to watch+trust.
-            $gmask |= ( 1 & ( 1 << 61 ) );
-
-            # TAG:FR:protocol:editfriends4_addeditfriend
-            $dbh->do("REPLACE INTO friends (userid, friendid, fgcolor, bgcolor, groupmask) ".
-                     "VALUES ($userid, $friendid, $qfg, $qbg, $gmask)");
-            return $fail->(501,$dbh->errstr) if $dbh->err;
-
-            my $memkey = [$userid,"frgmask:$userid:$friendid"];
-            LJ::MemCache::set($memkey, $gmask+0, time()+60*15);
-            LJ::memcache_kill($friendid, 'friendofs');
-            LJ::memcache_kill($friendid, 'friendofs2');
-
-            if ($sclient && !$currently_is_friend && !$currently_is_banned) {
-                my @jobs;
-                push @jobs, LJ::Event::Befriended->new(LJ::load_userid($friendid), LJ::load_userid($userid))->fire_job
-                    if !$LJ::DISABLED{esn};
-
-                push @jobs, TheSchwartz::Job->new(
-                                                  funcname => "LJ::Worker::FriendChange",
-                                                  arg      => [$userid, 'add', $friendid],
-                                                  ) unless $LJ::DISABLED{'friendchange-schwartz'};
-
-                $sclient->insert_jobs(@jobs) if @jobs;
-            }
-
-            LJ::run_hooks('befriended', LJ::load_userid($userid), LJ::load_userid($friendid));
-        }
-    }
-
-    return $fail->(104) if $error_flag;
-
-    # invalidate memcache of friends
-    LJ::memcache_kill($userid, "friends");
-    LJ::memcache_kill($userid, "friends2");
-
-    return $res;
+# deprecated
+sub editfriends {
+    return fail( $_[1], 504 );
 }
 
-sub editfriendgroups
-{
-    my ($req, $err, $flags) = @_;
-
-    # TODO(mark): most of this code can move to the new trust groups editing
-    #             sort of thing, but for now just say we're deprecated
-    return fail( $err, 504 );
-
-    return undef unless authenticate($req, $err, $flags);
-
-    my $u = $flags->{'u'};
-    my $userid = $u->{'userid'};
-    my ($db, $fgtable, $bmax, $cmax) = $u->{dversion} > 5 ?
-                         ($u->writer, 'friendgroup2', LJ::BMAX_GRPNAME2, LJ::CMAX_GRPNAME2) :
-                         (LJ::get_db_writer(), 'friendgroup', LJ::BMAX_GRPNAME, LJ::CMAX_GRPNAME);
-    my $sth;
-
-    return fail($err,306) unless $db;
-
-    # do not let locked people do this
-    return fail($err, 308) if $u->{statusvis} eq 'L';
-
-    my $res = {};
-
-    ## make sure tree is how we want it
-    $req->{'groupmasks'} = {} unless
-        (ref $req->{'groupmasks'} eq "HASH");
-    $req->{'set'} = {} unless
-        (ref $req->{'set'} eq "HASH");
-    $req->{'delete'} = [] unless
-        (ref $req->{'delete'} eq "ARRAY");
-
-    # Keep track of what bits are already set, so we can know later
-    # whether to INSERT or UPDATE.
-    my %bitset;
-    my $groups = LJ::get_friend_group($userid);
-    foreach my $bit (keys %{$groups || {}}) {
-        $bitset{$bit} = 1;
-    }
-
-    ## before we perform any DB operations, validate input text
-    # (groups' names) for correctness so we can fail gracefully
-    if ($LJ::UNICODE) {
-        foreach my $bit (keys %{$req->{'set'}})
-        {
-            my $name = $req->{'set'}->{$bit}->{'name'};
-            return fail($err,207,"non-ASCII names require a Unicode-capable client")
-                if $req->{'ver'} < 1 and not LJ::is_ascii($name);
-            return fail($err,208,"Invalid group names. Please see $LJ::SITEROOT/support/encodings.bml for more information.")
-                unless LJ::text_in($name);
-        }
-    }
-
-    ## figure out deletions we'll do later
-    foreach my $bit (@{$req->{'delete'}})
-    {
-        $bit += 0;
-        next unless ($bit >= 1 && $bit <= 60);
-        $bitset{$bit} = 0;  # so later we replace into, not update.
-    }
-
-    ## do additions/modifications ('set' hash)
-    my %added;
-    foreach my $bit (keys %{$req->{'set'}})
-    {
-        $bit += 0;
-        next unless ($bit >= 1 && $bit <= 60);
-        my $sa = $req->{'set'}->{$bit};
-        my $name = LJ::text_trim($sa->{'name'}, $bmax, $cmax);
-
-        # can't end with a slash
-        $name =~ s!/$!!;
-
-        # setting it to name is like deleting it.
-        unless ($name =~ /\S/) {
-            push @{$req->{'delete'}}, $bit;
-            next;
-        }
-
-        my $qname = $db->quote($name);
-        my $qsort = defined $sa->{'sort'} ? ($sa->{'sort'}+0) : 50;
-        my $qpublic = $db->quote(defined $sa->{'public'} ? ($sa->{'public'}+0) : 0);
-
-        if ($bitset{$bit}) {
-            # so update it
-            my $sets;
-            if (defined $sa->{'public'}) {
-                $sets .= ", is_public=$qpublic";
-            }
-            $db->do("UPDATE $fgtable SET groupname=$qname, sortorder=$qsort ".
-                    "$sets WHERE userid=$userid AND groupnum=$bit");
-        } else {
-            $db->do("REPLACE INTO $fgtable (userid, groupnum, ".
-                    "groupname, sortorder, is_public) VALUES ".
-                    "($userid, $bit, $qname, $qsort, $qpublic)");
-        }
-        $added{$bit} = 1;
-    }
-
-
-    ## do deletions ('delete' array)
-    my $dbcm = LJ::get_cluster_master($u);
-
-    # ignore bits that aren't integers or that are outside 1-60 range
-    my @delete_bits = grep {$_ >= 1 and $_ <= 60} map {$_+0} @{$req->{'delete'}};
-    my $delete_mask = 0;
-    foreach my $bit (@delete_bits) {
-        $delete_mask |= (1 << $bit)
-    }
-
-    # remove the bits for deleted groups from all friends groupmasks
-    my $dbh = LJ::get_db_writer();
-    if ($delete_mask) {
-        # TAG:FR:protocol:editfriendgroups_removemasks
-        $dbh->do("UPDATE friends".
-                 "   SET groupmask = groupmask & ~$delete_mask".
-                 " WHERE userid = $userid");
-    }
-
-    foreach my $bit (@delete_bits)
-    {
-        # remove all posts from allowing that group:
-        my @posts_to_clean = ();
-        $sth = $dbcm->prepare("SELECT jitemid FROM logsec2 WHERE journalid=$userid AND allowmask & (1 << $bit)");
-        $sth->execute;
-        while (my ($id) = $sth->fetchrow_array) { push @posts_to_clean, $id; }
-        while (@posts_to_clean) {
-            my @batch;
-            if (scalar(@posts_to_clean) < 20) {
-                @batch = @posts_to_clean;
-                @posts_to_clean = ();
-            } else {
-                @batch = splice(@posts_to_clean, 0, 20);
-            }
-
-            my $in = join(",", @batch);
-            $u->do("UPDATE log2 SET allowmask=allowmask & ~(1 << $bit) ".
-                   "WHERE journalid=$userid AND jitemid IN ($in) AND security='usemask'");
-            $u->do("UPDATE logsec2 SET allowmask=allowmask & ~(1 << $bit) ".
-                   "WHERE journalid=$userid AND jitemid IN ($in)");
-
-            foreach my $id (@batch) {
-                LJ::MemCache::delete([$userid, "log2:$userid:$id"]);
-            }
-            LJ::MemCache::delete([$userid, "log2lt:$userid"]);
-        }
-        LJ::Tags::deleted_friend_group($u, $bit);
-        LJ::run_hooks('delete_friend_group', $u, $bit);
-
-        # remove the friend group, unless we just added it this transaction
-        unless ($added{$bit}) {
-            $db->do("DELETE FROM $fgtable WHERE ".
-                    "userid=$userid AND groupnum=$bit");
-        }
-    }
-
-    ## change friends' masks
-    # TAG:FR:protocol:editfriendgroups_changemasks
-    foreach my $friend (keys %{$req->{'groupmasks'}})
-    {
-        my $mask = int($req->{'groupmasks'}->{$friend}) | 1;
-        my $friendid = LJ::get_userid($dbh, $friend);
-
-        $dbh->do("UPDATE friends SET groupmask=$mask ".
-                 "WHERE userid=$userid AND friendid=?",
-                 undef, $friendid);
-        LJ::MemCache::set([$userid, "frgmask:$userid:$friendid"], $mask);
-    }
-
-    # invalidate memcache of friends/groups
-    LJ::memcache_kill($userid, "friends");
-    LJ::memcache_kill($userid, "fgrp");
-
-    # return value for this is nothing.
-    return {};
+# deprecated
+sub editfriendgroups {
+    return fail( $_[1], 504 );
 }
 
 sub sessionexpire {
diff -r d655bcb8c70f -r dfd18646e8e7 cgi-bin/taglib.pl
--- a/cgi-bin/taglib.pl	Fri Mar 06 17:26:56 2009 -0800
+++ b/cgi-bin/taglib.pl	Sat Mar 07 06:07:58 2009 +0000
@@ -1292,7 +1292,7 @@ sub set_usertag_display {
 }
 
 # <LJFUNC>
-# name: LJ::Tags::deleted_friend_group
+# name: LJ::Tags::deleted_trust_group
 # class: tags
 # des: Called from ljprotocol when a friends group is deleted.
 # args: uobj, bit
@@ -1300,7 +1300,7 @@ sub set_usertag_display {
 # des-bit: The id (1..60) of the friends group being deleted.
 # returns: 1 of success undef on failure.
 # </LJFUNC>
-sub deleted_friend_group {
+sub deleted_trust_group {
     my $u = LJ::want_user(shift);
     my $bit = shift() + 0;
     return undef unless $u && $bit >= 1 && $bit <= 60;
--------------------------------------------------------------------------------