fu: Close-up of Fu, bringing a scoop of water to her mouth (Default)
fu ([personal profile] fu) wrote in [site community profile] changelog2011-09-22 04:53 am

[dw-free] move cgi-bin/lj*.pl files into proper modules (in cgi-bin/LJ)

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

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

This file already works like a module, so make the filename follow module
naming conventions, and call it appropriately.

Patch by [personal profile] kareila.

Files modified:
  • cgi-bin/LJ/Memories.pm
  • cgi-bin/ljmemories.pl
  • cgi-bin/modperl_subs.pl
--------------------------------------------------------------------------------
diff -r 670a563d0295 -r b74e50684390 cgi-bin/LJ/Memories.pm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cgi-bin/LJ/Memories.pm	Thu Sep 22 12:52:49 2011 +0800
@@ -0,0 +1,656 @@
+#!/usr/bin/perl
+# This code was forked from the LiveJournal project owned and operated
+# by Live Journal, Inc. The code has been modified and expanded by
+# Dreamwidth Studios, LLC. These files were originally licensed under
+# the terms of the license supplied by Live Journal, Inc, which can
+# currently be found at:
+#
+# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
+#
+# In accordance with the original license, this code and all its
+# modifications are provided under the GNU General Public License.
+# A copy of that license can be found in the LICENSE file included as
+# part of this distribution.
+
+package LJ::Memories;
+use strict;
+
+# <LJFUNC>
+# name: LJ::Memories::count
+# class: web
+# des: Returns the number of memories that a user has.
+# args: uuobj
+# des-uuobj: Userid or user object to count memories of.
+# returns: Some number; undef on error.
+# </LJFUNC>
+sub count {
+    my $u = shift;
+    $u = LJ::want_user($u);
+    return undef unless $u;
+
+    # check memcache first
+    my $count = LJ::MemCache::get([$u->{userid}, "memct:$u->{userid}"]);
+    return $count if $count;
+
+    # now count
+    my $dbcr = LJ::get_cluster_def_reader( $u );
+    $count = $dbcr->selectrow_array( 'SELECT COUNT(*) FROM memorable2 WHERE userid = ?',
+                                        undef, $u->{userid} );
+    return undef if $dbcr->err;
+
+    $count += 0;
+
+    # now put in memcache and return it
+    my $expiration = $LJ::MEMCACHE_EXPIRATION{'memct'} || 43200; # 12 hours
+    LJ::MemCache::set([$u->{userid}, "memct:$u->{userid}"], $count, $expiration);
+    return $count;
+}
+
+# <LJFUNC>
+# name: LJ::Memories::create
+# class: web
+# des: Create a new memory for a user.
+# args: uuobj, opts, kwids?
+# des-uuobj: User id or user object to insert memory for.
+# des-opts: Hashref of options that define the memory; keys = journalid, ditemid, des, security.
+# des-kwids: Optional; arrayref of keyword ids to categorize this memory under.
+# returns: 1 on success, undef on error
+# </LJFUNC>
+sub create {
+    my ($u, $opts, $kwids) = @_;
+    $u = LJ::want_user($u);
+    return undef unless $u && %{$opts || {}};
+
+    # make sure we got enough options
+    my ( $userid, $journalid, $ditemid, $des, $security ) =
+        ( $u->userid, map { $opts->{$_} } qw(journalid ditemid des security) );
+    $userid += 0;
+    $journalid += 0;
+    $ditemid += 0;
+    $security ||= 'public';
+    $kwids ||= [ $u->get_keyword_id( '*' ) ]; # * means no category
+    $des = LJ::trim($des);
+    return undef unless $userid && $journalid && $ditemid && $des && $security && @$kwids;
+    return undef unless $security =~ /^(?:public|friends|private)$/;
+
+    # we have valid data, now let's insert it
+    return undef unless $u->writer;
+
+    # allocate memory id to use
+    my $memid = LJ::alloc_user_counter( $u, 'R' );
+    return undef unless $memid;
+
+    # insert main memory
+    $u->do( "INSERT INTO memorable2 (userid, memid, journalid, ditemid, des, security) " .
+            "VALUES (?, ?, ?, ?, ?, ?)", undef, $userid, $memid, $journalid, $ditemid, $des, $security );
+        return undef if $u->err;
+
+    # insert keywords
+    my $val = join ',', map { "($userid, $memid, $_)" } @$kwids;
+    $u->do( "REPLACE INTO memkeyword2 (userid, memid, kwid) VALUES $val" );
+
+
+    # Delete the appropriate memcache entries
+    LJ::MemCache::delete( [$userid, "memct:$userid"] );
+    my $filter = $journalid == $userid ? 'own' : 'other';
+    my $filter_char   = _map_filter_to_char($filter);
+    my $security_char = _map_security_to_char($security);
+    my $memcache_key = "memkwcnt:$userid:$filter_char:$security_char";
+    LJ::MemCache::delete( [$userid, $memcache_key] );
+
+    return 1;
+}
+
+# <LJFUNC>
+# name: LJ::Memories::delete_by_id
+# class: web
+# des: Deletes a bunch of memories by memid.
+# args: uuobj, memids
+# des-uuobj: User id or user object to delete memories of.
+# des-memids: Arrayref of memids.
+# returns: 1 on success; undef on error.
+# </LJFUNC>
+sub delete_by_id {
+    my ( $u, $memids ) = @_;
+    $u = LJ::want_user( $u );
+    $memids = [ $memids ] if $memids && !ref $memids; # so they can just pass a single thing...
+    return undef unless $u && @{$memids || []};
+
+    # delete actual memory
+    my $in = join ',', map { $_ + 0 } @$memids;
+    $u->do("DELETE FROM memorable2 WHERE userid = ? AND memid IN ($in)", undef, $u->{userid});
+    return undef if $u->err;
+
+    # delete keyword associations
+    my $euser = "userid = $u->{userid} AND";
+    $u->do("DELETE FROM memkeyword2 WHERE $euser memid IN ($in)");
+
+    # delete cache of count and keyword counts
+    clear_memcache($u);
+
+    # success at this point, since the first delete succeeded
+    return 1;
+}
+
+# <LJFUNC>
+# name: LJ::Memories::get_keyword_counts
+# class: web
+# des: Get a list of keywords and the counts for memories, showing how many memories are under
+#      each keyword.
+# args: uuobj, opts?
+# des-uuobj: User id or object of user.
+# des-opts: Optional; hashref passed to _memory_getter, suggested keys are security and filter
+#           if you want to get only certain memories in the keyword list.
+# returns: Hashref { kwid => count }; undef on error
+# </LJFUNC>
+sub get_keyword_counts {
+    my ($u, $opts) = @_;
+    $u = LJ::want_user($u);
+    return undef unless $u;
+    my $userid = $u->{userid};
+
+    my $filter_parm   = $opts->{filter};
+    my @security_parm = $opts->{security} ? @{$opts->{security}} : ();
+
+    my ($cache_counts, $missing_keys) = _get_memcache_keyword_counts($userid, $filter_parm, @security_parm);
+    return $cache_counts unless @$missing_keys;
+
+    # Get the user's memories based on filter and security
+    $opts->{filter_security_pairs} = $missing_keys;
+    $opts->{notext} = 1;
+    my $memories = LJ::Memories::_memory_getter($u, $opts);
+    return undef unless defined $memories; # error case
+
+    # Generate mapping of memid to filter (e.g. own) and security (e.g. private)
+    my (%mem_filter, @all_memids);
+    foreach my $memid (keys %$memories) {
+        push @all_memids, $memid;
+        my $memory_filter   = $memories->{$memid}->{journalid} == $userid ? 'own' : 'other';
+        my $memory_security = $memories->{$memid}->{security};
+        $mem_filter{$memid} = [$memory_filter, $memory_security];
+    }
+
+    # now let's get the keywords these memories use
+    my $mem_kw_rows;
+
+    if (@all_memids) {
+        my $in = join ',', @all_memids;
+        my $dbcr = LJ::get_cluster_reader( $u );
+        my $sql = "SELECT kwid, memid FROM memkeyword2 WHERE userid = $userid AND memid IN ($in)";
+        $mem_kw_rows = $dbcr->selectall_arrayref( $sql );
+        return undef if $dbcr->err;
+    }
+
+    # Filter and Sum
+    my %counts;
+    foreach my $row (@{$mem_kw_rows||[]}) {
+        my ($kwid, $memid) = @$row;
+        my ($filter, $security) = @{$mem_filter{$memid}};
+        $counts{$filter}{$security}{$kwid}++;
+    }
+
+    # Add these new counts to our memcache counts to get totals
+    my $output_counts = $cache_counts;
+    foreach my $filter (keys %counts) {
+        foreach my $security (keys %{$counts{$filter}}) {
+            if ($counts{$filter}{$security}) {
+                add_hash($output_counts, $counts{$filter}{$security});
+            }
+        }
+    }
+
+    # Create empty anonymous hashes for missing key combos
+    foreach my $missing_key (@$missing_keys) {
+        my ($missing_filter, $missing_security) = split /-/, $missing_key;
+        next if exists $counts{$missing_filter}{$missing_security};
+        $counts{$missing_filter}{$missing_security} = {};
+    }
+
+    # Store memcache entries with counts
+    foreach my $filter (qw/own other/) {
+        foreach my $security (qw/friends private public/) {
+            next unless exists $counts{$filter}{$security};
+            my $filter_char   = _map_filter_to_char($filter);
+            my $security_char = _map_security_to_char($security);
+            my $memcache_key = "memkwcnt:$userid:$filter_char:$security_char";
+            my $expiration = $LJ::MEMCACHE_EXPIRATION{'memkwcnt'} || 86400;
+            LJ::MemCache::set([$userid, $memcache_key], $counts{$filter}{$security}, $expiration);
+        }
+    }
+
+    return $output_counts;
+}
+
+#
+# Name: _map_security_to_char
+# API: Private to this module
+# Description: Map a verbose security name to a single character
+# Parameter: Verbose security name
+# Return: Single character representation of security
+#
+sub _map_security_to_char {
+    my $verbose_security = shift;
+    my %security_map = (friends => 'f', private => 'v', public => 'u');
+    return $security_map{$verbose_security} || die "Can't map security '" . LJ::ehtml($verbose_security) . "' to character";
+}
+
+#
+# Name: _map_filter_to_char
+# API: Private to this module
+# Description: Map a verbose filter name to a single character
+# Parameter: Verbose filter name
+# Return: Single character representation of filter
+#
+sub _map_filter_to_char {
+    my $verbose_filter = shift;
+    my %filter_map = (own => 'w', other => 't');
+    return $filter_map{$verbose_filter} || die "Can't map filter '" . LJ::ehtml($verbose_filter) . "' to character";
+}
+
+#
+# Name: _get_memcache_keyword_counts
+#
+# API: Private to this module
+#
+# Description:
+# - Get keyword counts from memcache based on user, filter, and security
+# - Return hash of counts and array of missing keys
+#
+# Parameters:
+# - $userid = ID of the User
+# - $filter_parm = {own|other}
+# - @security_parm = array of values {friends|private|public} - () = all
+#
+# Return Values:
+# - HashRef of counts by Keyword ID
+# - ArrayRef of missing keys (e.g. 'owner-private')
+#
+sub _get_memcache_keyword_counts {
+    my ($userid, $filter_parm, @security_parm) = @_;
+
+    # Build up the memcache keys that we're looking for
+    my @memcache_keys;
+    my %filter_security_map;
+    foreach my $filter (qw/own other/) {
+        foreach my $security (qw/friends private public/) {
+            my $filter_matches = ($filter_parm eq $filter) || ($filter_parm eq 'all');
+            my $security_matches = @security_parm == 0 || grep(/$security/, @security_parm);
+            next unless $filter_matches && $security_matches;
+            my $filter_char   = _map_filter_to_char($filter);
+            my $security_char = _map_security_to_char($security);
+            my $memcache_key = "memkwcnt:$userid:$filter_char:$security_char";
+            push @memcache_keys, $memcache_key;
+            $filter_security_map{"$filter_char:$security_char"} = [$filter, $security];
+        }
+    }
+
+    # Loop over our memcache results, get counts and total them as we go
+    my (%output_counts, @missing_keys);
+    my $memcache_counts = LJ::is_enabled('memkwcnt_memcaching') ? LJ::MemCache::get_multi(map { [$userid, $_] } @memcache_keys) : {};
+    foreach my $memcache_key (@memcache_keys) {
+        my $counts = $memcache_counts->{$memcache_key};
+        if ($counts) { # Add these memcache counts to totals
+            add_hash(\%output_counts, $counts);
+        } else {
+            my ($filter_security_chars) = $memcache_key =~ /$userid:(.:.)$/;
+            my ($filter, $security) = @{$filter_security_map{$filter_security_chars}};
+            push @missing_keys, $filter . '-' . $security;
+        }
+    }
+
+    return \%output_counts, \@missing_keys;
+}
+
+# <LJFUNC>
+# name: LJ::Memories::add_hash
+# class: web
+# des: Add values of one hash, to the corresponding entries in another.
+# args: HashRef1, HashRef2
+# returns: Values are added to the first parameter hash.
+# </LJFUNC>
+sub add_hash {
+    my ($hash1, $hash2) = @_;
+
+    while (my ($key,$value) = each %$hash2) {
+        $hash1->{$key} += $value;
+    }
+}
+
+# <LJFUNC>
+# name: LJ::Memories::get_keywordids
+# class: web
+# des: Get all keyword ids a user has used for a certain memory.
+# args: uuobj, memid
+# des-uuobj: User id or user object to check memory of.
+# des-memid: Memory id to get keyword ids for.
+# returns: Arrayref of keywordids; undef on error.
+# </LJFUNC>
+sub get_keywordids {
+    my ( $u, $memid ) = @_;
+    $u = LJ::want_user( $u );
+    $memid += 0;
+    return undef unless $u && $memid;
+
+    # definitive reader/master because this function is usually called when
+    # someone is on an edit page.
+    my $dbcr = LJ::get_cluster_def_reader( $u );
+    my $kwids = $dbcr->selectcol_arrayref( 'SELECT kwid FROM memkeyword2 WHERE userid = ? AND memid = ?',
+                                           undef, $u->userid, $memid );
+    return undef if $dbcr->err;
+
+
+    # all good, return
+    return $kwids;
+}
+
+# <LJFUNC>
+# name: LJ::Memories::update_memory
+# class: web
+# des: Updates the description and security of a memory.
+# args: uuobj, memid, updopts
+# des-uuobj: User id or user object to update memory of.
+# des-memid: Memory id to update.
+# des-updopts: Update options; hashref with keys 'des' and 'security', values being what
+#              you want to update the memory to have.
+# returns: 1 on success, undef on error
+# </LJFUNC>
+# sub update_memory {
+#     my ($u, $memid, $upd) = @_;
+#     $u = LJ::want_user($u);
+#     $memid += 0;
+#     return unless $u && $memid && %{$upd || {}};
+#
+#     # get database handle
+#     my ($db, $table) = ($u, '2');
+#     return undef unless $db;
+#
+#     # construct update lines... only valid things we can update are des and security
+#     my @updates;
+#     my $security_updated;
+#     foreach my $what (keys %$upd) {
+#         next unless $what =~ m/^(?:des|security)$/;
+#         $security_updated = 1 if $what eq 'security';
+#         push @updates, "$what=" . $db->quote($upd->{$what});
+#     }
+#     my $updstr = join ',', @updates;
+#
+#     # now perform update
+#     $db->do("UPDATE memorable$table SET $updstr WHERE userid = ? AND memid = ?",
+#             undef, $u->{userid}, $memid);
+#     return undef if $db->err;
+#
+#     # Delete memcache entries if the security of the memory was updated
+#     clear_memcache($u) if $security_updated;
+#
+#     return 1;
+# }
+
+# this messy function gets memories based on an options hashref.  this is an
+# API API and isn't recommended for use by BML etc... add to the API and have
+# API functions call this if needed.
+#
+# options in $opts hashref:
+#   security => [ 'public', 'private', ... ], or some subset thereof
+#   filter => 'all' | 'own' | 'other', filter -- defaults to all
+#   filter_security_pairs => [ 'own-private', ... ], Pairs of filter/security
+#   notext => 1/0, if on, do not load/return description field
+#   byid => [ 1, 2, 3, ... ], load memories by *memid*
+#   byditemid => [ 1, 2, 3 ... ], load by ditemid (MUST specify journalid too)
+#   journalid => 1, find memories by ditemid (see above) for this journalid
+#
+# note that all memories are loaded from a single user, specified as the first
+# parameter.  does not let you load memories from more than one user.
+sub _memory_getter {
+    my ($u, $opts) = @_;
+    $u = LJ::want_user($u);
+    $opts ||= {};
+    return undef unless $u;
+
+    # Specify filter/security by pair, or individually
+    my $secwhere = '';
+    my $extrawhere;
+    if ($opts->{filter_security_pairs}) {
+        my @pairs;
+        foreach my $filter_security_pair (@{$opts->{filter_security_pairs}}) {
+            my ($filter, $security) = $filter_security_pair =~ /^(\w+)-(\w+)$/;
+            my $filter_predicate = ($filter eq 'all') ? '' : 'journalid' . ($filter eq 'own' ? '=' : '<>') . $u->{userid};
+            push @pairs, "($filter_predicate AND security='$security')";
+        }
+        $secwhere = 'AND (' . join(' OR ', @pairs) . ')';
+    } else {
+        if (@{$opts->{security} || []}) {
+            my @secs;
+            foreach my $sec (@{$opts->{security}}) {
+                push @secs, $sec
+                    if $sec =~ /^(?:public|friends|private)$/;
+            }
+            $secwhere = "AND security IN (" . join(',', map { "'$_'" } @secs) . ")";
+        }
+        if ($opts->{filter} eq 'all') { $extrawhere = ''; }
+        elsif ($opts->{filter} eq 'own') { $extrawhere = "AND journalid = $u->{userid}"; }
+        elsif ($opts->{filter} eq 'other') { $extrawhere = "AND journalid <> $u->{userid}"; }
+    }
+
+    my $des = $opts->{notext} ? '' : 'des, ';
+    my $selwhere;
+    if (@{$opts->{byid} || []}) {
+        # they want to get some explicit memories by memid
+        my $in = join ',', map { $_+0 } @{$opts->{byid}};
+        $selwhere = "AND memid IN ($in)";
+    } elsif ($opts->{byditemid} && $opts->{journalid}) {
+        # or, they want to see if a memory exists for a particular item
+        my $selitemid = "ditemid";
+        $opts->{byditemid} += 0;
+        $opts->{journalid} += 0;
+        $selwhere = "AND journalid = $opts->{journalid} AND $selitemid = $opts->{byditemid}";
+    } elsif ($opts->{byditemid}) {
+        # get memory, OLD STYLE so journalid is 0
+        my $selitemid = "ditemid";
+        $opts->{byditemid} += 0;
+        $selwhere = "AND journalid = 0 AND $selitemid = $opts->{byditemid}";
+    }
+
+    # load up memories into hashref
+    my ( %memories, $sth );
+    my $dbcr = LJ::get_cluster_reader( $u );
+    my $sql = "SELECT memid, userid, journalid, ditemid, $des security "
+            . "FROM memorable2 WHERE userid = ? $selwhere $secwhere $extrawhere";
+    $sth = $dbcr->prepare( $sql );
+
+    # general execution and fetching for return
+    $sth->execute($u->{userid});
+    return undef if $sth->err;
+    while ($_ = $sth->fetchrow_hashref()) {
+        # we have to do this ditemid->jitemid to make old code work,
+        # but this can probably go away at some point...
+        if (defined $_->{ditemid}) {
+            $_->{jitemid} = $_->{ditemid};
+        } else {
+            $_->{ditemid} = $_->{jitemid};
+        }
+        $memories{$_->{memid}} = $_;
+    }
+
+    my @jids = map { $_->{journalid} } values %memories;
+    my $us = LJ::load_userids(@jids);
+    foreach my $mem (values %memories) {
+        next unless $mem->{journalid};
+        $mem->{user} = $us->{$mem->{journalid}}->user;
+    }
+
+    return \%memories;
+}
+
+# <LJFUNC>
+# name: LJ::Memories::get_by_id
+# class: web
+# des: Get memories given some memory ids.
+# args: uuobj, memids
+# des-uuobj: User id or user object to get memories for.
+# des-memids: The rest of the memory ids.  Array.  (Pass them in as individual parameters...)
+# returns: Hashref of memories with keys being memid; undef on error.
+# </LJFUNC>
+# sub get_by_id {
+#     my $u = shift;
+#     return {} unless @_; # make sure they gave us some ids
+#
+#     # pass to getter to get by id
+#     return LJ::Memories::_memory_getter($u, { byid => [ map { $_+0 } @_ ] });
+# }
+
+# <LJFUNC>
+# name: LJ::Memories::get_by_ditemid
+# class: web
+# des: Get memory for a given journal entry.
+# args: uuobj, journalid, ditemid
+# des-uuobj: User id or user object to get memories for.
+# des-journalid: Userid for journal entry is in.
+# des-ditemid: Display itemid of entry.
+# returns: Hashref of individual memory.
+# </LJFUNC>
+sub get_by_ditemid {
+    my ($u, $jid, $ditemid) = @_;
+    $jid += 0;
+    $ditemid += 0;
+    return undef unless $ditemid; # _memory_getter checks $u and $jid isn't necessary
+                                  # because this might be an old-style memory
+
+    # pass to getter with appropriate options
+    my $memhash = LJ::Memories::_memory_getter($u, { byditemid => $ditemid, journalid => $jid });
+    return undef unless %{$memhash || {}};
+    return [ values %$memhash ]->[0]; # ugly
+}
+
+# <LJFUNC>
+# name: LJ::Memories::get_by_user
+# class: web
+# des: Get memories given a user.
+# args: uuobj
+# des-uuobj: User id or user object to get memories for.
+# returns: Hashref of memories with keys being memid; undef on error.
+# </LJFUNC>
+# sub get_by_user {
+#     # simply passes through to _memory_getter
+#     return LJ::Memories::_memory_getter(@_);
+# }
+
+# <LJFUNC>
+# name: LJ::Memories::get_by_keyword
+# class: web
+# des: Get memories given a user and a keyword/keyword id.
+# args: uuobj, kwoid, opts
+# des-uuobj: User id or user object to get memories for.
+# des-kwoid: Keyword (string) or keyword id (number) to get memories for.
+# des-opts: Hashref of extra options to pass through to memory getter.  Suggested options
+#           are filter and security for limiting the memories returned.
+# returns: Hashref of memories with keys being memid; undef on error.
+# </LJFUNC>
+sub get_by_keyword {
+    my ($u, $kwoid, $opts) = @_;
+    $u = LJ::want_user($u);
+    my $kwid = $kwoid+0;
+    my $kw = defined $kwoid && !$kwid ? $kwoid : undef;
+    return undef unless $u && ($kwid || defined $kw);
+
+    my $memids;
+    my $dbcr = LJ::get_cluster_reader( $u );
+    return undef unless $dbcr;
+
+    # get keyword id if we don't have it
+    if ( defined $kw ) {
+        $kwid = $dbcr->selectrow_array( 'SELECT kwid FROM userkeywords WHERE userid = ? AND keyword = ?',
+                                        undef, $u->userid, $kw ) + 0;
+    }
+    return undef unless $kwid;
+
+    # now get the actual memory ids
+    $memids = $dbcr->selectcol_arrayref( 'SELECT memid FROM memkeyword2 WHERE userid = ? AND kwid = ?',
+                                         undef, $u->{userid}, $kwid );
+    return undef if $dbcr->err;
+
+    # return
+    $memids = [] unless defined($memids);
+    my $memories = @$memids > 0 ?  LJ::Memories::_memory_getter($u, {%{$opts || {}}, byid => $memids }) : {};
+    return $memories;
+}
+
+# <LJFUNC>
+# name: LJ::Memories::get_keywords
+# class:
+# des: Retrieves keyword/keyids without big joins, returns a hashref.
+# args: uobj
+# des-uobj: User object to get keyword pairs for.
+# returns: Hashref; { keywordid => keyword }
+# </LJFUNC>
+sub get_keywords {
+    my $u = shift;
+    $u = LJ::want_user($u);
+    return undef unless $u;
+
+    my $use_reader = 0;
+    my $memkey = [$u->{userid},"memkwid:$u->{userid}"];
+    my $ret = LJ::MemCache::get($memkey);
+    return $ret if defined $ret;
+    $ret = {};
+
+    my $dbcm = LJ::get_cluster_def_reader( $u );
+    unless ( $dbcm ) {
+        $use_reader = 1;
+        $dbcm = LJ::get_cluster_reader( $u );
+    }
+    my $ids = $dbcm->selectcol_arrayref( 'SELECT DISTINCT kwid FROM memkeyword2 WHERE userid = ?',
+                                         undef, $u->userid );
+    if ( @{$ids || []} ) {
+        my $in = join ",", @$ids;
+        my $rows = $dbcm->selectall_arrayref( 'SELECT kwid, keyword FROM userkeywords ' .
+                                              "WHERE userid = ? AND kwid IN ($in)",
+                                              undef, $u->userid );
+        $ret->{$_->[0]} = $_->[1] foreach @{$rows || []};
+    }
+
+    my $expiration = $LJ::MEMCACHE_EXPIRATION{'memkwid'} || 86400;
+    LJ::MemCache::set($memkey, $ret, $expiration) unless $use_reader;
+    return $ret;
+}
+
+# <LJFUNC>
+# name: LJ::Memories::updated_keywords
+# class: web
+# des: Deletes memcached keyword data.
+# args: uobj
+# des-uobj: User object to clear memcached keywords for.
+# returns: undef.
+# </LJFUNC>
+sub updated_keywords {
+    return clear_memcache(shift);
+}
+
+# <LJFUNC>
+# name: LJ::Memories::clear_memcache
+# class: web
+# des: Deletes memcached keyword data.
+# args: uobj
+# des-uobj: User object to clear memcached keywords for.
+# returns: undef.
+# </LJFUNC>
+sub clear_memcache {
+    my $u = shift;
+    return unless ref $u;
+    my $userid = $u->{userid};
+
+    LJ::MemCache::delete([$userid, "memct:$userid"]);
+
+    LJ::MemCache::delete([$userid, "memkwid:$userid"]);
+
+    # Delete all memkwcnt entries
+    LJ::MemCache::delete([$userid, "memkwcnt:$userid:w:f"]);
+    LJ::MemCache::delete([$userid, "memkwcnt:$userid:w:v"]);
+    LJ::MemCache::delete([$userid, "memkwcnt:$userid:w:u"]);
+    LJ::MemCache::delete([$userid, "memkwcnt:$userid:t:f"]);
+    LJ::MemCache::delete([$userid, "memkwcnt:$userid:t:v"]);
+    LJ::MemCache::delete([$userid, "memkwcnt:$userid:t:u"]);
+
+    return undef;
+}
+
+1;
diff -r 670a563d0295 -r b74e50684390 cgi-bin/ljmemories.pl
--- a/cgi-bin/ljmemories.pl	Thu Sep 22 12:46:51 2011 +0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,656 +0,0 @@
-#!/usr/bin/perl
-# This code was forked from the LiveJournal project owned and operated
-# by Live Journal, Inc. The code has been modified and expanded by
-# Dreamwidth Studios, LLC. These files were originally licensed under
-# the terms of the license supplied by Live Journal, Inc, which can
-# currently be found at:
-#
-# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
-#
-# In accordance with the original license, this code and all its
-# modifications are provided under the GNU General Public License.
-# A copy of that license can be found in the LICENSE file included as
-# part of this distribution.
-
-package LJ::Memories;
-use strict;
-
-# <LJFUNC>
-# name: LJ::Memories::count
-# class: web
-# des: Returns the number of memories that a user has.
-# args: uuobj
-# des-uuobj: Userid or user object to count memories of.
-# returns: Some number; undef on error.
-# </LJFUNC>
-sub count {
-    my $u = shift;
-    $u = LJ::want_user($u);
-    return undef unless $u;
-
-    # check memcache first
-    my $count = LJ::MemCache::get([$u->{userid}, "memct:$u->{userid}"]);
-    return $count if $count;
-
-    # now count
-    my $dbcr = LJ::get_cluster_def_reader( $u );
-    $count = $dbcr->selectrow_array( 'SELECT COUNT(*) FROM memorable2 WHERE userid = ?',
-                                        undef, $u->{userid} );
-    return undef if $dbcr->err;
-
-    $count += 0;
-
-    # now put in memcache and return it
-    my $expiration = $LJ::MEMCACHE_EXPIRATION{'memct'} || 43200; # 12 hours
-    LJ::MemCache::set([$u->{userid}, "memct:$u->{userid}"], $count, $expiration);
-    return $count;
-}
-
-# <LJFUNC>
-# name: LJ::Memories::create
-# class: web
-# des: Create a new memory for a user.
-# args: uuobj, opts, kwids?
-# des-uuobj: User id or user object to insert memory for.
-# des-opts: Hashref of options that define the memory; keys = journalid, ditemid, des, security.
-# des-kwids: Optional; arrayref of keyword ids to categorize this memory under.
-# returns: 1 on success, undef on error
-# </LJFUNC>
-sub create {
-    my ($u, $opts, $kwids) = @_;
-    $u = LJ::want_user($u);
-    return undef unless $u && %{$opts || {}};
-
-    # make sure we got enough options
-    my ( $userid, $journalid, $ditemid, $des, $security ) =
-        ( $u->userid, map { $opts->{$_} } qw(journalid ditemid des security) );
-    $userid += 0;
-    $journalid += 0;
-    $ditemid += 0;
-    $security ||= 'public';
-    $kwids ||= [ $u->get_keyword_id( '*' ) ]; # * means no category
-    $des = LJ::trim($des);
-    return undef unless $userid && $journalid && $ditemid && $des && $security && @$kwids;
-    return undef unless $security =~ /^(?:public|friends|private)$/;
-
-    # we have valid data, now let's insert it
-    return undef unless $u->writer;
-
-    # allocate memory id to use
-    my $memid = LJ::alloc_user_counter( $u, 'R' );
-    return undef unless $memid;
-
-    # insert main memory
-    $u->do( "INSERT INTO memorable2 (userid, memid, journalid, ditemid, des, security) " .
-            "VALUES (?, ?, ?, ?, ?, ?)", undef, $userid, $memid, $journalid, $ditemid, $des, $security );
-        return undef if $u->err;
-
-    # insert keywords
-    my $val = join ',', map { "($userid, $memid, $_)" } @$kwids;
-    $u->do( "REPLACE INTO memkeyword2 (userid, memid, kwid) VALUES $val" );
-
-
-    # Delete the appropriate memcache entries
-    LJ::MemCache::delete( [$userid, "memct:$userid"] );
-    my $filter = $journalid == $userid ? 'own' : 'other';
-    my $filter_char   = _map_filter_to_char($filter);
-    my $security_char = _map_security_to_char($security);
-    my $memcache_key = "memkwcnt:$userid:$filter_char:$security_char";
-    LJ::MemCache::delete( [$userid, $memcache_key] );
-
-    return 1;
-}
-
-# <LJFUNC>
-# name: LJ::Memories::delete_by_id
-# class: web
-# des: Deletes a bunch of memories by memid.
-# args: uuobj, memids
-# des-uuobj: User id or user object to delete memories of.
-# des-memids: Arrayref of memids.
-# returns: 1 on success; undef on error.
-# </LJFUNC>
-sub delete_by_id {
-    my ( $u, $memids ) = @_;
-    $u = LJ::want_user( $u );
-    $memids = [ $memids ] if $memids && !ref $memids; # so they can just pass a single thing...
-    return undef unless $u && @{$memids || []};
-
-    # delete actual memory
-    my $in = join ',', map { $_ + 0 } @$memids;
-    $u->do("DELETE FROM memorable2 WHERE userid = ? AND memid IN ($in)", undef, $u->{userid});
-    return undef if $u->err;
-
-    # delete keyword associations
-    my $euser = "userid = $u->{userid} AND";
-    $u->do("DELETE FROM memkeyword2 WHERE $euser memid IN ($in)");
-
-    # delete cache of count and keyword counts
-    clear_memcache($u);
-
-    # success at this point, since the first delete succeeded
-    return 1;
-}
-
-# <LJFUNC>
-# name: LJ::Memories::get_keyword_counts
-# class: web
-# des: Get a list of keywords and the counts for memories, showing how many memories are under
-#      each keyword.
-# args: uuobj, opts?
-# des-uuobj: User id or object of user.
-# des-opts: Optional; hashref passed to _memory_getter, suggested keys are security and filter
-#           if you want to get only certain memories in the keyword list.
-# returns: Hashref { kwid => count }; undef on error
-# </LJFUNC>
-sub get_keyword_counts {
-    my ($u, $opts) = @_;
-    $u = LJ::want_user($u);
-    return undef unless $u;
-    my $userid = $u->{userid};
-
-    my $filter_parm   = $opts->{filter};
-    my @security_parm = $opts->{security} ? @{$opts->{security}} : ();
-
-    my ($cache_counts, $missing_keys) = _get_memcache_keyword_counts($userid, $filter_parm, @security_parm);
-    return $cache_counts unless @$missing_keys;
-
-    # Get the user's memories based on filter and security
-    $opts->{filter_security_pairs} = $missing_keys;
-    $opts->{notext} = 1;
-    my $memories = LJ::Memories::_memory_getter($u, $opts);
-    return undef unless defined $memories; # error case
-
-    # Generate mapping of memid to filter (e.g. own) and security (e.g. private)
-    my (%mem_filter, @all_memids);
-    foreach my $memid (keys %$memories) {
-        push @all_memids, $memid;
-        my $memory_filter   = $memories->{$memid}->{journalid} == $userid ? 'own' : 'other';
-        my $memory_security = $memories->{$memid}->{security};
-        $mem_filter{$memid} = [$memory_filter, $memory_security];
-    }
-
-    # now let's get the keywords these memories use
-    my $mem_kw_rows;
-
-    if (@all_memids) {
-        my $in = join ',', @all_memids;
-        my $dbcr = LJ::get_cluster_reader( $u );
-        my $sql = "SELECT kwid, memid FROM memkeyword2 WHERE userid = $userid AND memid IN ($in)";
-        $mem_kw_rows = $dbcr->selectall_arrayref( $sql );
-        return undef if $dbcr->err;
-    }
-
-    # Filter and Sum
-    my %counts;
-    foreach my $row (@{$mem_kw_rows||[]}) {
-        my ($kwid, $memid) = @$row;
-        my ($filter, $security) = @{$mem_filter{$memid}};
-        $counts{$filter}{$security}{$kwid}++;
-    }
-
-    # Add these new counts to our memcache counts to get totals
-    my $output_counts = $cache_counts;
-    foreach my $filter (keys %counts) {
-        foreach my $security (keys %{$counts{$filter}}) {
-            if ($counts{$filter}{$security}) {
-                add_hash($output_counts, $counts{$filter}{$security});
-            }
-        }
-    }
-
-    # Create empty anonymous hashes for missing key combos
-    foreach my $missing_key (@$missing_keys) {
-        my ($missing_filter, $missing_security) = split /-/, $missing_key;
-        next if exists $counts{$missing_filter}{$missing_security};
-        $counts{$missing_filter}{$missing_security} = {};
-    }
-
-    # Store memcache entries with counts
-    foreach my $filter (qw/own other/) {
-        foreach my $security (qw/friends private public/) {
-            next unless exists $counts{$filter}{$security};
-            my $filter_char   = _map_filter_to_char($filter);
-            my $security_char = _map_security_to_char($security);
-            my $memcache_key = "memkwcnt:$userid:$filter_char:$security_char";
-            my $expiration = $LJ::MEMCACHE_EXPIRATION{'memkwcnt'} || 86400;
-            LJ::MemCache::set([$userid, $memcache_key], $counts{$filter}{$security}, $expiration);
-        }
-    }
-
-    return $output_counts;
-}
-
-#
-# Name: _map_security_to_char
-# API: Private to this module
-# Description: Map a verbose security name to a single character
-# Parameter: Verbose security name
-# Return: Single character representation of security
-#
-sub _map_security_to_char {
-    my $verbose_security = shift;
-    my %security_map = (friends => 'f', private => 'v', public => 'u');
-    return $security_map{$verbose_security} || die "Can't map security '" . LJ::ehtml($verbose_security) . "' to character";
-}
-
-#
-# Name: _map_filter_to_char
-# API: Private to this module
-# Description: Map a verbose filter name to a single character
-# Parameter: Verbose filter name
-# Return: Single character representation of filter
-#
-sub _map_filter_to_char {
-    my $verbose_filter = shift;
-    my %filter_map = (own => 'w', other => 't');
-    return $filter_map{$verbose_filter} || die "Can't map filter '" . LJ::ehtml($verbose_filter) . "' to character";
-}
-
-#
-# Name: _get_memcache_keyword_counts
-#
-# API: Private to this module
-#
-# Description:
-# - Get keyword counts from memcache based on user, filter, and security
-# - Return hash of counts and array of missing keys
-#
-# Parameters:
-# - $userid = ID of the User
-# - $filter_parm = {own|other}
-# - @security_parm = array of values {friends|private|public} - () = all
-#
-# Return Values:
-# - HashRef of counts by Keyword ID
-# - ArrayRef of missing keys (e.g. 'owner-private')
-#
-sub _get_memcache_keyword_counts {
-    my ($userid, $filter_parm, @security_parm) = @_;
-
-    # Build up the memcache keys that we're looking for
-    my @memcache_keys;
-    my %filter_security_map;
-    foreach my $filter (qw/own other/) {
-        foreach my $security (qw/friends private public/) {
-            my $filter_matches = ($filter_parm eq $filter) || ($filter_parm eq 'all');
-            my $security_matches = @security_parm == 0 || grep(/$security/, @security_parm);
-            next unless $filter_matches && $security_matches;
-            my $filter_char   = _map_filter_to_char($filter);
-            my $security_char = _map_security_to_char($security);
-            my $memcache_key = "memkwcnt:$userid:$filter_char:$security_char";
-            push @memcache_keys, $memcache_key;
-            $filter_security_map{"$filter_char:$security_char"} = [$filter, $security];
-        }
-    }
-
-    # Loop over our memcache results, get counts and total them as we go
-    my (%output_counts, @missing_keys);
-    my $memcache_counts = LJ::is_enabled('memkwcnt_memcaching') ? LJ::MemCache::get_multi(map { [$userid, $_] } @memcache_keys) : {};
-    foreach my $memcache_key (@memcache_keys) {
-        my $counts = $memcache_counts->{$memcache_key};
-        if ($counts) { # Add these memcache counts to totals
-            add_hash(\%output_counts, $counts);
-        } else {
-            my ($filter_security_chars) = $memcache_key =~ /$userid:(.:.)$/;
-            my ($filter, $security) = @{$filter_security_map{$filter_security_chars}};
-            push @missing_keys, $filter . '-' . $security;
-        }
-    }
-
-    return \%output_counts, \@missing_keys;
-}
-
-# <LJFUNC>
-# name: LJ::Memories::add_hash
-# class: web
-# des: Add values of one hash, to the corresponding entries in another.
-# args: HashRef1, HashRef2
-# returns: Values are added to the first parameter hash.
-# </LJFUNC>
-sub add_hash {
-    my ($hash1, $hash2) = @_;
-
-    while (my ($key,$value) = each %$hash2) {
-        $hash1->{$key} += $value;
-    }
-}
-
-# <LJFUNC>
-# name: LJ::Memories::get_keywordids
-# class: web
-# des: Get all keyword ids a user has used for a certain memory.
-# args: uuobj, memid
-# des-uuobj: User id or user object to check memory of.
-# des-memid: Memory id to get keyword ids for.
-# returns: Arrayref of keywordids; undef on error.
-# </LJFUNC>
-sub get_keywordids {
-    my ( $u, $memid ) = @_;
-    $u = LJ::want_user( $u );
-    $memid += 0;
-    return undef unless $u && $memid;
-
-    # definitive reader/master because this function is usually called when
-    # someone is on an edit page.
-    my $dbcr = LJ::get_cluster_def_reader( $u );
-    my $kwids = $dbcr->selectcol_arrayref( 'SELECT kwid FROM memkeyword2 WHERE userid = ? AND memid = ?',
-                                           undef, $u->userid, $memid );
-    return undef if $dbcr->err;
-
-
-    # all good, return
-    return $kwids;
-}
-
-# <LJFUNC>
-# name: LJ::Memories::update_memory
-# class: web
-# des: Updates the description and security of a memory.
-# args: uuobj, memid, updopts
-# des-uuobj: User id or user object to update memory of.
-# des-memid: Memory id to update.
-# des-updopts: Update options; hashref with keys 'des' and 'security', values being what
-#              you want to update the memory to have.
-# returns: 1 on success, undef on error
-# </LJFUNC>
-# sub update_memory {
-#     my ($u, $memid, $upd) = @_;
-#     $u = LJ::want_user($u);
-#     $memid += 0;
-#     return unless $u && $memid && %{$upd || {}};
-#
-#     # get database handle
-#     my ($db, $table) = ($u, '2');
-#     return undef unless $db;
-#
-#     # construct update lines... only valid things we can update are des and security
-#     my @updates;
-#     my $security_updated;
-#     foreach my $what (keys %$upd) {
-#         next unless $what =~ m/^(?:des|security)$/;
-#         $security_updated = 1 if $what eq 'security';
-#         push @updates, "$what=" . $db->quote($upd->{$what});
-#     }
-#     my $updstr = join ',', @updates;
-#
-#     # now perform update
-#     $db->do("UPDATE memorable$table SET $updstr WHERE userid = ? AND memid = ?",
-#             undef, $u->{userid}, $memid);
-#     return undef if $db->err;
-#
-#     # Delete memcache entries if the security of the memory was updated
-#     clear_memcache($u) if $security_updated;
-#
-#     return 1;
-# }
-
-# this messy function gets memories based on an options hashref.  this is an
-# API API and isn't recommended for use by BML etc... add to the API and have
-# API functions call this if needed.
-#
-# options in $opts hashref:
-#   security => [ 'public', 'private', ... ], or some subset thereof
-#   filter => 'all' | 'own' | 'other', filter -- defaults to all
-#   filter_security_pairs => [ 'own-private', ... ], Pairs of filter/security
-#   notext => 1/0, if on, do not load/return description field
-#   byid => [ 1, 2, 3, ... ], load memories by *memid*
-#   byditemid => [ 1, 2, 3 ... ], load by ditemid (MUST specify journalid too)
-#   journalid => 1, find memories by ditemid (see above) for this journalid
-#
-# note that all memories are loaded from a single user, specified as the first
-# parameter.  does not let you load memories from more than one user.
-sub _memory_getter {
-    my ($u, $opts) = @_;
-    $u = LJ::want_user($u);
-    $opts ||= {};
-    return undef unless $u;
-
-    # Specify filter/security by pair, or individually
-    my $secwhere = '';
-    my $extrawhere;
-    if ($opts->{filter_security_pairs}) {
-        my @pairs;
-        foreach my $filter_security_pair (@{$opts->{filter_security_pairs}}) {
-            my ($filter, $security) = $filter_security_pair =~ /^(\w+)-(\w+)$/;
-            my $filter_predicate = ($filter eq 'all') ? '' : 'journalid' . ($filter eq 'own' ? '=' : '<>') . $u->{userid};
-            push @pairs, "($filter_predicate AND security='$security')";
-        }
-        $secwhere = 'AND (' . join(' OR ', @pairs) . ')';
-    } else {
-        if (@{$opts->{security} || []}) {
-            my @secs;
-            foreach my $sec (@{$opts->{security}}) {
-                push @secs, $sec
-                    if $sec =~ /^(?:public|friends|private)$/;
-            }
-            $secwhere = "AND security IN (" . join(',', map { "'$_'" } @secs) . ")";
-        }
-        if ($opts->{filter} eq 'all') { $extrawhere = ''; }
-        elsif ($opts->{filter} eq 'own') { $extrawhere = "AND journalid = $u->{userid}"; }
-        elsif ($opts->{filter} eq 'other') { $extrawhere = "AND journalid <> $u->{userid}"; }
-    }
-
-    my $des = $opts->{notext} ? '' : 'des, ';
-    my $selwhere;
-    if (@{$opts->{byid} || []}) {
-        # they want to get some explicit memories by memid
-        my $in = join ',', map { $_+0 } @{$opts->{byid}};
-        $selwhere = "AND memid IN ($in)";
-    } elsif ($opts->{byditemid} && $opts->{journalid}) {
-        # or, they want to see if a memory exists for a particular item
-        my $selitemid = "ditemid";
-        $opts->{byditemid} += 0;
-        $opts->{journalid} += 0;
-        $selwhere = "AND journalid = $opts->{journalid} AND $selitemid = $opts->{byditemid}";
-    } elsif ($opts->{byditemid}) {
-        # get memory, OLD STYLE so journalid is 0
-        my $selitemid = "ditemid";
-        $opts->{byditemid} += 0;
-        $selwhere = "AND journalid = 0 AND $selitemid = $opts->{byditemid}";
-    }
-
-    # load up memories into hashref
-    my ( %memories, $sth );
-    my $dbcr = LJ::get_cluster_reader( $u );
-    my $sql = "SELECT memid, userid, journalid, ditemid, $des security "
-            . "FROM memorable2 WHERE userid = ? $selwhere $secwhere $extrawhere";
-    $sth = $dbcr->prepare( $sql );
-
-    # general execution and fetching for return
-    $sth->execute($u->{userid});
-    return undef if $sth->err;
-    while ($_ = $sth->fetchrow_hashref()) {
-        # we have to do this ditemid->jitemid to make old code work,
-        # but this can probably go away at some point...
-        if (defined $_->{ditemid}) {
-            $_->{jitemid} = $_->{ditemid};
-        } else {
-            $_->{ditemid} = $_->{jitemid};
-        }
-        $memories{$_->{memid}} = $_;
-    }
-
-    my @jids = map { $_->{journalid} } values %memories;
-    my $us = LJ::load_userids(@jids);
-    foreach my $mem (values %memories) {
-        next unless $mem->{journalid};
-        $mem->{user} = $us->{$mem->{journalid}}->user;
-    }
-
-    return \%memories;
-}
-
-# <LJFUNC>
-# name: LJ::Memories::get_by_id
-# class: web
-# des: Get memories given some memory ids.
-# args: uuobj, memids
-# des-uuobj: User id or user object to get memories for.
-# des-memids: The rest of the memory ids.  Array.  (Pass them in as individual parameters...)
-# returns: Hashref of memories with keys being memid; undef on error.
-# </LJFUNC>
-# sub get_by_id {
-#     my $u = shift;
-#     return {} unless @_; # make sure they gave us some ids
-#
-#     # pass to getter to get by id
-#     return LJ::Memories::_memory_getter($u, { byid => [ map { $_+0 } @_ ] });
-# }
-
-# <LJFUNC>
-# name: LJ::Memories::get_by_ditemid
-# class: web
-# des: Get memory for a given journal entry.
-# args: uuobj, journalid, ditemid
-# des-uuobj: User id or user object to get memories for.
-# des-journalid: Userid for journal entry is in.
-# des-ditemid: Display itemid of entry.
-# returns: Hashref of individual memory.
-# </LJFUNC>
-sub get_by_ditemid {
-    my ($u, $jid, $ditemid) = @_;
-    $jid += 0;
-    $ditemid += 0;
-    return undef unless $ditemid; # _memory_getter checks $u and $jid isn't necessary
-                                  # because this might be an old-style memory
-
-    # pass to getter with appropriate options
-    my $memhash = LJ::Memories::_memory_getter($u, { byditemid => $ditemid, journalid => $jid });
-    return undef unless %{$memhash || {}};
-    return [ values %$memhash ]->[0]; # ugly
-}
-
-# <LJFUNC>
-# name: LJ::Memories::get_by_user
-# class: web
-# des: Get memories given a user.
-# args: uuobj
-# des-uuobj: User id or user object to get memories for.
-# returns: Hashref of memories with keys being memid; undef on error.
-# </LJFUNC>
-# sub get_by_user {
-#     # simply passes through to _memory_getter
-#     return LJ::Memories::_memory_getter(@_);
-# }
-
-# <LJFUNC>
-# name: LJ::Memories::get_by_keyword
-# class: web
-# des: Get memories given a user and a keyword/keyword id.
-# args: uuobj, kwoid, opts
-# des-uuobj: User id or user object to get memories for.
-# des-kwoid: Keyword (string) or keyword id (number) to get memories for.
-# des-opts: Hashref of extra options to pass through to memory getter.  Suggested options
-#           are filter and security for limiting the memories returned.
-# returns: Hashref of memories with keys being memid; undef on error.
-# </LJFUNC>
-sub get_by_keyword {
-    my ($u, $kwoid, $opts) = @_;
-    $u = LJ::want_user($u);
-    my $kwid = $kwoid+0;
-    my $kw = defined $kwoid && !$kwid ? $kwoid : undef;
-    return undef unless $u && ($kwid || defined $kw);
-
-    my $memids;
-    my $dbcr = LJ::get_cluster_reader( $u );
-    return undef unless $dbcr;
-
-    # get keyword id if we don't have it
-    if ( defined $kw ) {
-        $kwid = $dbcr->selectrow_array( 'SELECT kwid FROM userkeywords WHERE userid = ? AND keyword = ?',
-                                        undef, $u->userid, $kw ) + 0;
-    }
-    return undef unless $kwid;
-
-    # now get the actual memory ids
-    $memids = $dbcr->selectcol_arrayref( 'SELECT memid FROM memkeyword2 WHERE userid = ? AND kwid = ?',
-                                         undef, $u->{userid}, $kwid );
-    return undef if $dbcr->err;
-
-    # return
-    $memids = [] unless defined($memids);
-    my $memories = @$memids > 0 ?  LJ::Memories::_memory_getter($u, {%{$opts || {}}, byid => $memids }) : {};
-    return $memories;
-}
-
-# <LJFUNC>
-# name: LJ::Memories::get_keywords
-# class:
-# des: Retrieves keyword/keyids without big joins, returns a hashref.
-# args: uobj
-# des-uobj: User object to get keyword pairs for.
-# returns: Hashref; { keywordid => keyword }
-# </LJFUNC>
-sub get_keywords {
-    my $u = shift;
-    $u = LJ::want_user($u);
-    return undef unless $u;
-
-    my $use_reader = 0;
-    my $memkey = [$u->{userid},"memkwid:$u->{userid}"];
-    my $ret = LJ::MemCache::get($memkey);
-    return $ret if defined $ret;
-    $ret = {};
-
-    my $dbcm = LJ::get_cluster_def_reader( $u );
-    unless ( $dbcm ) {
-        $use_reader = 1;
-        $dbcm = LJ::get_cluster_reader( $u );
-    }
-    my $ids = $dbcm->selectcol_arrayref( 'SELECT DISTINCT kwid FROM memkeyword2 WHERE userid = ?',
-                                         undef, $u->userid );
-    if ( @{$ids || []} ) {
-        my $in = join ",", @$ids;
-        my $rows = $dbcm->selectall_arrayref( 'SELECT kwid, keyword FROM userkeywords ' .
-                                              "WHERE userid = ? AND kwid IN ($in)",
-                                              undef, $u->userid );
-        $ret->{$_->[0]} = $_->[1] foreach @{$rows || []};
-    }
-
-    my $expiration = $LJ::MEMCACHE_EXPIRATION{'memkwid'} || 86400;
-    LJ::MemCache::set($memkey, $ret, $expiration) unless $use_reader;
-    return $ret;
-}
-
-# <LJFUNC>
-# name: LJ::Memories::updated_keywords
-# class: web
-# des: Deletes memcached keyword data.
-# args: uobj
-# des-uobj: User object to clear memcached keywords for.
-# returns: undef.
-# </LJFUNC>
-sub updated_keywords {
-    return clear_memcache(shift);
-}
-
-# <LJFUNC>
-# name: LJ::Memories::clear_memcache
-# class: web
-# des: Deletes memcached keyword data.
-# args: uobj
-# des-uobj: User object to clear memcached keywords for.
-# returns: undef.
-# </LJFUNC>
-sub clear_memcache {
-    my $u = shift;
-    return unless ref $u;
-    my $userid = $u->{userid};
-
-    LJ::MemCache::delete([$userid, "memct:$userid"]);
-
-    LJ::MemCache::delete([$userid, "memkwid:$userid"]);
-
-    # Delete all memkwcnt entries
-    LJ::MemCache::delete([$userid, "memkwcnt:$userid:w:f"]);
-    LJ::MemCache::delete([$userid, "memkwcnt:$userid:w:v"]);
-    LJ::MemCache::delete([$userid, "memkwcnt:$userid:w:u"]);
-    LJ::MemCache::delete([$userid, "memkwcnt:$userid:t:f"]);
-    LJ::MemCache::delete([$userid, "memkwcnt:$userid:t:v"]);
-    LJ::MemCache::delete([$userid, "memkwcnt:$userid:t:u"]);
-
-    return undef;
-}
-
-1;
diff -r 670a563d0295 -r b74e50684390 cgi-bin/modperl_subs.pl
--- a/cgi-bin/modperl_subs.pl	Thu Sep 22 12:46:51 2011 +0800
+++ b/cgi-bin/modperl_subs.pl	Thu Sep 22 12:52:49 2011 +0800
@@ -87,7 +87,7 @@
 use LJ::CleanHTML;
 use LJ::Talk;
 require "ljfeed.pl";
-require "ljmemories.pl";
+use LJ::Memories;
 require "ljmail.pl";
 use LJ::Sysban;
 use LJ::Community;
--------------------------------------------------------------------------------