LJSUP-17669: Login.bml form refactoring
[livejournal.git] / cgi-bin / ljmemories.pl
blob97b32eb08403c00d15ac41740df03922870d0f37
1 #!/usr/bin/perl
3 package LJ::Memories;
4 use strict;
6 # <LJFUNC>
7 # name: LJ::Memories::count
8 # class: web
9 # des: Returns the number of memories that a user has.
10 # args: uuobj
11 # des-uuobj: Userid or user object to count memories of.
12 # returns: Some number; undef on error.
13 # </LJFUNC>
14 sub count {
15 my $u = shift;
16 $u = LJ::want_user($u);
17 return undef unless $u;
19 # check memcache first
20 my $count = LJ::MemCache::get([$u->{userid}, "memct:$u->{userid}"]);
21 return $count if $count;
23 # now count
24 if ($u->{dversion} > 5) {
25 my $dbcr = LJ::get_cluster_def_reader($u);
26 $count = $dbcr->selectrow_array('SELECT COUNT(*) FROM memorable2 WHERE userid = ?',
27 undef, $u->{userid});
28 return undef if $dbcr->err;
29 } else {
30 my $dbh = LJ::get_db_writer();
31 $count = $dbh->selectrow_array('SELECT COUNT(*) FROM memorable WHERE userid = ?',
32 undef, $u->{userid});
33 return undef if $dbh->err;
35 $count += 0;
37 # now put in memcache and return it
38 my $expiration = $LJ::MEMCACHE_EXPIRATION{'memct'} || 43200; # 12 hours
39 LJ::MemCache::set([$u->{userid}, "memct:$u->{userid}"], $count, $expiration);
40 return $count;
43 # <LJFUNC>
44 # name: LJ::Memories::create
45 # class: web
46 # des: Create a new memory for a user.
47 # args: uuobj, opts, kwids?
48 # des-uuobj: User id or user object to insert memory for.
49 # des-opts: Hashref of options that define the memory; keys = journalid, ditemid, des, security.
50 # des-kwids: Optional; arrayref of keyword ids to categorize this memory under.
51 # returns: 1 on success, undef on error
52 # </LJFUNC>
53 sub create {
54 my ($u, $opts, $kwids) = @_;
55 $u = LJ::want_user($u);
56 return undef unless $u && %{$opts || {}};
58 # make sure we got enough options
59 my ($userid, $journalid, $ditemid, $des, $security) =
60 ($u->{userid}, map { $opts->{$_} } qw(journalid ditemid des security));
61 $userid += 0;
62 $journalid += 0;
63 $ditemid += 0;
64 $security ||= 'public';
65 $kwids ||= [ LJ::get_keyword_id($u, '*') ]; # * means no category
66 $des = LJ::trim($des);
67 return undef unless $userid && $journalid && $ditemid && $des && $security && @$kwids;
68 return undef unless $security =~ /^(?:public|friends|private)$/;
70 # we have valid data, now let's insert it
71 if ($u->{dversion} > 5) {
72 return undef unless $u->writer;
74 # allocate memory id to use
75 my $memid = LJ::alloc_user_counter($u, 'R');
76 return undef unless $memid;
78 # insert main memory
79 $u->do("INSERT INTO memorable2 (userid, memid, journalid, ditemid, des, security) " .
80 "VALUES (?, ?, ?, ?, ?, ?)", undef, $userid, $memid, $journalid, $ditemid, $des, $security);
81 return undef if $u->err;
83 # insert keywords
84 my $val = join ',', map { "($u->{userid}, $memid, $_)" } @$kwids;
85 $u->do("REPLACE INTO memkeyword2 (userid, memid, kwid) VALUES $val");
87 } else {
88 my $dbh = LJ::get_db_writer();
89 return undef unless $dbh;
91 # insert main memory
92 $dbh->do("INSERT INTO memorable (userid, journalid, jitemid, des, security) " .
93 "VALUES (?, ?, ?, ?, ?)", undef, $userid, $journalid, $ditemid, $des, $security);
94 return undef if $dbh->err;
96 # insert keywords
97 my $memid = $dbh->{mysql_insertid}+0;
98 my $val = join ',', map { "($memid, $_)" } @$kwids;
99 $dbh->do("REPLACE INTO memkeyword (memid, kwid) VALUES $val");
102 # Delete the appropriate memcache entries
103 LJ::MemCache::delete([$u->{userid}, "memct:$u->{userid}"]);
104 my $filter = $journalid == $u->{userid} ? 'own' : 'other';
105 my $filter_char = _map_filter_to_char($filter);
106 my $security_char = _map_security_to_char($security);
107 my $memcache_key = "memkwcnt:$u->{userid}:$filter_char:$security_char";
108 LJ::MemCache::delete([$u->{userid}, $memcache_key]);
110 return 1;
113 # <LJFUNC>
114 # name: LJ::Memories::delete_by_id
115 # class: web
116 # des: Deletes a bunch of memories by memid.
117 # args: uuobj, memids
118 # des-uuobj: User id or user object to delete memories of.
119 # des-memids: Arrayref of memids.
120 # returns: 1 on success; undef on error.
121 # </LJFUNC>
122 sub delete_by_id {
123 my ($u, $memids) = @_;
124 $u = LJ::want_user($u);
125 $memids = [ $memids ] if $memids && !ref $memids; # so they can just pass a single thing...
126 return undef unless $u && @{$memids || []};
128 # setup
129 my ($db, $table) = $u->{dversion} > 5 ?
130 ($u, '2') :
131 (LJ::get_db_writer(), '');
133 # if dversion 5, verify the ids
134 my $in = join ',', map { $_+0 } @$memids;
135 if ($u->{dversion} == 5) {
136 $memids = $db->selectcol_arrayref("SELECT memid FROM memorable WHERE userid = ? AND memid IN ($in)",
137 undef, $u->{userid});
138 return undef if $db->err;
139 return 1 unless @{$memids || []}; # if we got nothing, pretend success
140 $in = join ',', map { $_+0 } @$memids;
143 # delete actual memory
144 $db->do("DELETE FROM memorable$table WHERE userid = ? AND memid IN ($in)", undef, $u->{userid});
145 return undef if $db->err;
147 # delete keyword associations
148 my $euser = $u->{dversion} > 5 ? "userid = $u->{userid} AND" : '';
149 $db->do("DELETE FROM memkeyword$table WHERE $euser memid IN ($in)");
151 # delete cache of count and keyword counts
152 clear_memcache($u);
154 # success at this point, since the first delete succeeded
155 return 1;
158 # <LJFUNC>
159 # name: LJ::Memories::get_keyword_counts
160 # class: web
161 # des: Get a list of keywords and the counts for memories, showing how many memories are under
162 # each keyword.
163 # args: uuobj, opts?
164 # des-uuobj: User id or object of user.
165 # des-opts: Optional; hashref passed to _memory_getter, suggested keys are security and filter
166 # if you want to get only certain memories in the keyword list.
167 # returns: Hashref { kwid => count }; undef on error
168 # </LJFUNC>
169 sub get_keyword_counts {
170 my ($u, $opts) = @_;
171 $u = LJ::want_user($u);
172 return undef unless $u;
173 my $userid = $u->{userid};
175 my $filter_parm = $opts->{filter};
176 my @security_parm = $opts->{security} ? @{$opts->{security}} : ();
178 my ($cache_counts, $missing_keys) = _get_memcache_keyword_counts($userid, $filter_parm, @security_parm);
179 return $cache_counts unless @$missing_keys;
181 # Get the user's memories based on filter and security
182 $opts->{filter_security_pairs} = $missing_keys;
183 $opts->{notext} = 1;
184 my $memories = LJ::Memories::_memory_getter($u, $opts);
185 return undef unless defined $memories; # error case
187 # Generate mapping of memid to filter (e.g. own) and security (e.g. private)
188 my (%mem_filter, @all_memids);
189 foreach my $memid (keys %$memories) {
190 push @all_memids, $memid;
191 my $memory_filter = $memories->{$memid}->{journalid} == $userid ? 'own' : 'other';
192 my $memory_security = $memories->{$memid}->{security};
193 $mem_filter{$memid} = [$memory_filter, $memory_security];
196 # now let's get the keywords these memories use
197 my $mem_kw_rows;
199 if (@all_memids) {
200 my $in = join ',', @all_memids;
201 if ($u->{dversion} > 5) {
202 my $dbcr = LJ::get_cluster_reader($u);
203 my $sql = "SELECT kwid, memid FROM memkeyword2 WHERE userid = $userid AND memid IN ($in)";
204 $mem_kw_rows = $dbcr->selectall_arrayref($sql);
205 return undef if $dbcr->err;
206 } else {
207 my $dbr = LJ::get_db_reader();
208 my $sql = "SELECT kwid, memid FROM memkeyword WHERE kwid IN ($in)";
209 $mem_kw_rows = $dbr->selectall_arrayref($sql);
210 return undef if $dbr->err;
214 # Filter and Sum
215 my %counts;
216 foreach my $row (@{$mem_kw_rows||[]}) {
217 my ($kwid, $memid) = @$row;
218 my ($filter, $security) = @{$mem_filter{$memid}};
219 $counts{$filter}{$security}{$kwid}++;
222 # Add these new counts to our memcache counts to get totals
223 my $output_counts = $cache_counts;
224 foreach my $filter (keys %counts) {
225 foreach my $security (keys %{$counts{$filter}}) {
226 if ($counts{$filter}{$security}) {
227 add_hash($output_counts, $counts{$filter}{$security});
232 # Create empty anonymous hashes for missing key combos
233 foreach my $missing_key (@$missing_keys) {
234 my ($missing_filter, $missing_security) = split /-/, $missing_key;
235 next if exists $counts{$missing_filter}{$missing_security};
236 $counts{$missing_filter}{$missing_security} = {};
239 # Store memcache entries with counts
240 foreach my $filter (qw/own other/) {
241 foreach my $security (qw/friends private public/) {
242 next unless exists $counts{$filter}{$security};
243 my $filter_char = _map_filter_to_char($filter);
244 my $security_char = _map_security_to_char($security);
245 my $memcache_key = "memkwcnt:$userid:$filter_char:$security_char";
246 my $expiration = $LJ::MEMCACHE_EXPIRATION{'memkwcnt'} || 86400;
247 LJ::MemCache::set([$userid, $memcache_key], $counts{$filter}{$security}, $expiration);
251 return $output_counts;
255 # Name: _map_security_to_char
256 # API: Private to this module
257 # Description: Map a verbose security name to a single character
258 # Parameter: Verbose security name
259 # Return: Single character representation of security
261 sub _map_security_to_char {
262 my $verbose_security = shift;
263 my %security_map = (friends => 'f', private => 'v', public => 'u');
264 return $security_map{$verbose_security} || die "Can't map security '" . LJ::ehtml($verbose_security) . "' to character";
268 # Name: _map_filter_to_char
269 # API: Private to this module
270 # Description: Map a verbose filter name to a single character
271 # Parameter: Verbose filter name
272 # Return: Single character representation of filter
274 sub _map_filter_to_char {
275 my $verbose_filter = shift;
276 my %filter_map = (own => 'w', other => 't');
277 return $filter_map{$verbose_filter} || die "Can't map filter '" . LJ::ehtml($verbose_filter) . "' to character";
281 # Name: _get_memcache_keyword_counts
283 # API: Private to this module
285 # Description:
286 # - Get keyword counts from memcache based on user, filter, and security
287 # - Return hash of counts and array of missing keys
289 # Parameters:
290 # - $userid = ID of the User
291 # - $filter_parm = {own|other}
292 # - @security_parm = array of values {friends|private|public} - () = all
294 # Return Values:
295 # - HashRef of counts by Keyword ID
296 # - ArrayRef of missing keys (e.g. 'owner-private')
298 sub _get_memcache_keyword_counts {
299 my ($userid, $filter_parm, @security_parm) = @_;
301 # Build up the memcache keys that we're looking for
302 my @memcache_keys;
303 my %filter_security_map;
304 foreach my $filter (qw/own other/) {
305 foreach my $security (qw/friends private public/) {
306 my $filter_matches = ($filter_parm eq $filter) || ($filter_parm eq 'all');
307 my $security_matches = @security_parm == 0 || grep(/$security/, @security_parm);
308 next unless $filter_matches && $security_matches;
309 my $filter_char = _map_filter_to_char($filter);
310 my $security_char = _map_security_to_char($security);
311 my $memcache_key = "memkwcnt:$userid:$filter_char:$security_char";
312 push @memcache_keys, $memcache_key;
313 $filter_security_map{"$filter_char:$security_char"} = [$filter, $security];
317 # Loop over our memcache results, get counts and total them as we go
318 my (%output_counts, @missing_keys);
319 my $memcache_counts = $LJ::DISABLED{'memkwcnt_memcaching'} ? {} : LJ::MemCache::get_multi(map { [$userid, $_] } @memcache_keys);
320 foreach my $memcache_key (@memcache_keys) {
321 my $counts = $memcache_counts->{$memcache_key};
322 if ($counts) { # Add these memcache counts to totals
323 add_hash(\%output_counts, $counts);
324 } else {
325 my ($filter_security_chars) = $memcache_key =~ /$userid:(.:.)$/;
326 my ($filter, $security) = @{$filter_security_map{$filter_security_chars}};
327 push @missing_keys, $filter . '-' . $security;
331 return \%output_counts, \@missing_keys;
334 # <LJFUNC>
335 # name: LJ::Memories::add_hash
336 # class: web
337 # des: Add values of one hash, to the corresponding entries in another.
338 # args: HashRef1, HashRef2
339 # returns: Values are added to the first parameter hash.
340 # </LJFUNC>
341 sub add_hash {
342 my ($hash1, $hash2) = @_;
344 while (my ($key,$value) = each %$hash2) {
345 $hash1->{$key} += $value;
349 # <LJFUNC>
350 # name: LJ::Memories::get_keywordids
351 # class: web
352 # des: Get all keyword ids a user has used for a certain memory.
353 # args: uuobj, memid
354 # des-uuobj: User id or user object to check memory of.
355 # des-memid: Memory id to get keyword ids for.
356 # returns: Arrayref of keywordids; undef on error.
357 # </LJFUNC>
358 sub get_keywordids {
359 my ($u, $memid) = @_;
360 $u = LJ::want_user($u);
361 $memid += 0;
362 return undef unless $u && $memid;
364 # definitive reader/master because this function is usually called when
365 # someone is on an edit page.
366 my $kwids;
367 if ($u->{dversion} > 5) {
368 my $dbcr = LJ::get_cluster_def_reader($u);
369 $kwids = $dbcr->selectcol_arrayref('SELECT kwid FROM memkeyword2 WHERE userid = ? AND memid = ?',
370 undef, $u->{userid}, $memid);
371 return undef if $dbcr->err;
373 } else {
374 my $dbh = LJ::get_db_writer();
375 $kwids = $dbh->selectcol_arrayref('SELECT kwid FROM memkeyword WHERE memid = ?', undef, $memid);
376 return undef if $dbh->err;
379 # all good, return
380 return $kwids;
383 # <LJFUNC>
384 # name: LJ::Memories::update_memory
385 # class: web
386 # des: Updates the description and security of a memory.
387 # args: uuobj, memid, updopts
388 # des-uuobj: User id or user object to update memory of.
389 # des-memid: Memory id to update.
390 # des-updopts: Update options; hashref with keys 'des' and 'security', values being what
391 # you want to update the memory to have.
392 # returns: 1 on success, undef on error
393 # </LJFUNC>
394 sub update_memory {
395 my ($u, $memid, $upd) = @_;
396 $u = LJ::want_user($u);
397 $memid += 0;
398 return unless $u && $memid && %{$upd || {}};
400 # get database handle
401 my ($db, $table) = $u->{dversion} > 5 ?
402 ($u, '2') :
403 (LJ::get_db_writer(), '');
404 return undef unless $db;
406 # construct update lines... only valid things we can update are des and security
407 my @updates;
408 my $security_updated;
409 foreach my $what (keys %$upd) {
410 next unless $what =~ m/^(?:des|security)$/;
411 $security_updated = 1 if $what eq 'security';
412 push @updates, "$what=" . $db->quote($upd->{$what});
414 my $updstr = join ',', @updates;
416 # now perform update
417 $db->do("UPDATE memorable$table SET $updstr WHERE userid = ? AND memid = ?",
418 undef, $u->{userid}, $memid);
419 return undef if $db->err;
421 # Delete memcache entries if the security of the memory was updated
422 clear_memcache($u) if $security_updated;
424 return 1;
427 # this messy function gets memories based on an options hashref. this is an
428 # API API and isn't recommended for use by BML etc... add to the API and have
429 # API functions call this if needed.
431 # options in $opts hashref:
432 # security => [ 'public', 'private', ... ], or some subset thereof
433 # filter => 'all' | 'own' | 'other', filter -- defaults to all
434 # filter_security_pairs => [ 'own-private', ... ], Pairs of filter/security
435 # notext => 1/0, if on, do not load/return description field
436 # byid => [ 1, 2, 3, ... ], load memories by *memid*
437 # byditemid => [ 1, 2, 3 ... ], load by ditemid (MUST specify journalid too)
438 # journalid => 1, find memories by ditemid (see above) for this journalid
440 # note that all memories are loaded from a single user, specified as the first
441 # parameter. does not let you load memories from more than one user.
442 sub _memory_getter {
443 my ($u, $opts) = @_;
444 $u = LJ::want_user($u);
445 $opts ||= {};
446 return undef unless $u;
448 # Specify filter/security by pair, or individually
449 my $secwhere = '';
450 my $extrawhere;
451 if ($opts->{filter_security_pairs}) {
452 my @pairs;
453 foreach my $filter_security_pair (@{$opts->{filter_security_pairs}}) {
454 my ($filter, $security) = $filter_security_pair =~ /^(\w+)-(\w+)$/;
455 my $filter_predicate = ($filter eq 'all') ? '' : 'journalid' . ($filter eq 'own' ? '=' : '<>') . $u->{userid};
456 push @pairs, "($filter_predicate AND security='$security')";
458 $secwhere = 'AND (' . join(' OR ', @pairs) . ')';
459 } else {
460 if (@{$opts->{security} || []}) {
461 my @secs;
462 foreach my $sec (@{$opts->{security}}) {
463 push @secs, $sec
464 if $sec =~ /^(?:public|friends|private)$/;
466 $secwhere = "AND security IN (" . join(',', map { "'$_'" } @secs) . ")";
468 if ($opts->{filter} eq 'all') { $extrawhere = ''; }
469 elsif ($opts->{filter} eq 'own') { $extrawhere = "AND journalid = $u->{userid}"; }
470 elsif ($opts->{filter} eq 'other') { $extrawhere = "AND journalid <> $u->{userid}"; }
473 my $des = $opts->{notext} ? '' : 'des, ';
474 my $selwhere;
475 if (@{$opts->{byid} || []}) {
476 # they want to get some explicit memories by memid
477 my $in = join ',', map { $_+0 } @{$opts->{byid}};
478 $selwhere = "AND memid IN ($in)";
479 } elsif ($opts->{byditemid} && $opts->{journalid}) {
480 # or, they want to see if a memory exists for a particular item
481 my $selitemid = $u->{dversion} > 5 ? "ditemid" : "jitemid";
482 $opts->{byditemid} += 0;
483 $opts->{journalid} += 0;
484 $selwhere = "AND journalid = $opts->{journalid} AND $selitemid = $opts->{byditemid}";
485 } elsif ($opts->{byditemid}) {
486 # get memory, OLD STYLE so journalid is 0
487 my $selitemid = $u->{dversion} > 5 ? "ditemid" : "jitemid";
488 $opts->{byditemid} += 0;
489 $selwhere = "AND journalid = 0 AND $selitemid = $opts->{byditemid}";
492 # load up memories into hashref
493 my (%memories, $sth);
494 if ($u->{dversion} > 5) {
495 # new clustered memories
496 my $dbcr = LJ::get_cluster_reader($u);
497 my $sql = "SELECT memid, userid, journalid, ditemid, $des security "
498 . "FROM memorable2 WHERE userid = ? $selwhere $secwhere $extrawhere";
499 $sth = $dbcr->prepare($sql);
500 } else {
501 # old global memories
502 my $dbr = LJ::get_db_reader();
503 $sth = $dbr->prepare("SELECT memid, userid, journalid, jitemid, $des security " .
504 "FROM memorable WHERE userid = ? $selwhere $secwhere $extrawhere");
507 # general execution and fetching for return
508 $sth->execute($u->{userid});
509 return undef if $sth->err;
510 while ($_ = $sth->fetchrow_hashref()) {
511 # we have to do this ditemid->jitemid to make old code work,
512 # but this can probably go away at some point...
513 if (defined $_->{ditemid}) {
514 $_->{jitemid} = $_->{ditemid};
515 } else {
516 $_->{ditemid} = $_->{jitemid};
518 $memories{$_->{memid}} = $_;
521 my @jids = map { $_->{journalid} } values %memories;
522 my $us = LJ::load_userids(@jids);
523 foreach my $mem (values %memories) {
524 next unless $mem->{journalid};
525 $mem->{user} = $us->{$mem->{journalid}}->user;
528 return \%memories;
531 # <LJFUNC>
532 # name: LJ::Memories::get_by_id
533 # class: web
534 # des: Get memories given some memory ids.
535 # args: uuobj, memids
536 # des-uuobj: User id or user object to get memories for.
537 # des-memids: The rest of the memory ids. Array. (Pass them in as individual parameters...)
538 # returns: Hashref of memories with keys being memid; undef on error.
539 # </LJFUNC>
540 sub get_by_id {
541 my $u = shift;
542 return {} unless @_; # make sure they gave us some ids
544 # pass to getter to get by id
545 return LJ::Memories::_memory_getter($u, { byid => [ map { $_+0 } @_ ] });
548 # <LJFUNC>
549 # name: LJ::Memories::get_by_ditemid
550 # class: web
551 # des: Get memory for a given journal entry.
552 # args: uuobj, journalid, ditemid
553 # des-uuobj: User id or user object to get memories for.
554 # des-journalid: Userid for journal entry is in.
555 # des-ditemid: Display itemid of entry.
556 # returns: Hashref of individual memory.
557 # </LJFUNC>
558 sub get_by_ditemid {
559 my ($u, $jid, $ditemid) = @_;
560 $jid += 0;
561 $ditemid += 0;
562 return undef unless $ditemid; # _memory_getter checks $u and $jid isn't necessary
563 # because this might be an old-style memory
565 # pass to getter with appropriate options
566 my $memhash = LJ::Memories::_memory_getter($u, { byditemid => $ditemid, journalid => $jid });
567 return undef unless %{$memhash || {}};
568 return [ values %$memhash ]->[0]; # ugly
571 # <LJFUNC>
572 # name: LJ::Memories::get_by_user
573 # class: web
574 # des: Get memories given a user.
575 # args: uuobj
576 # des-uuobj: User id or user object to get memories for.
577 # returns: Hashref of memories with keys being memid; undef on error.
578 # </LJFUNC>
579 sub get_by_user {
580 # simply passes through to _memory_getter
581 return LJ::Memories::_memory_getter(@_);
584 # <LJFUNC>
585 # name: LJ::Memories::get_by_keyword
586 # class: web
587 # des: Get memories given a user and a keyword/keyword id.
588 # args: uuobj, kwoid, opts
589 # des-uuobj: User id or user object to get memories for.
590 # des-kwoid: Keyword (string) or keyword id (number) to get memories for.
591 # des-opts: Hashref of extra options to pass through to memory getter. Suggested options
592 # are filter and security for limiting the memories returned.
593 # returns: Hashref of memories with keys being memid; undef on error.
594 # </LJFUNC>
595 sub get_by_keyword {
596 my ($u, $kwoid, $opts) = @_;
597 $u = LJ::want_user($u);
598 my $kwid = $kwoid+0;
599 my $kw = defined $kwoid && !$kwid ? $kwoid : undef;
600 return undef unless $u && ($kwid || defined $kw);
602 # two entirely separate codepaths, depending on the user's dversion.
603 my $memids;
604 if ($u->{dversion} > 5) {
605 # the smart way
606 my $dbcr = LJ::get_cluster_reader($u);
607 return undef unless $dbcr;
609 # get keyword id if we don't have it
610 if (defined $kw) {
611 $kwid = $dbcr->selectrow_array('SELECT kwid FROM userkeywords WHERE userid = ? AND keyword = ?',
612 undef, $u->{userid}, $kw)+0;
614 return undef unless $kwid;
616 # now get the actual memory ids
617 $memids = $dbcr->selectcol_arrayref('SELECT memid FROM memkeyword2 WHERE userid = ? AND kwid = ?',
618 undef, $u->{userid}, $kwid);
619 return undef if $dbcr->err;
620 } else {
621 # the dumb way
622 my $dbr = LJ::get_db_reader();
623 return undef unless $dbr;
625 # get keyword id if we don't have it
626 if (defined $kw) {
627 $kwid = $dbr->selectrow_array('SELECT kwid FROM keywords WHERE keyword = ?', undef, $kw)+0;
629 return undef unless $kwid;
631 # now get memory ids. this has to join. :(
632 $memids = $dbr->selectcol_arrayref('SELECT m.memid FROM memorable m, memkeyword mk ' .
633 'WHERE m.userid = ? AND mk.memid = m.memid AND mk.kwid = ?',
634 undef, $u->{userid}, $kwid);
635 return undef if $dbr->err;
638 # return
639 $memids = [] unless defined($memids);
640 my $memories = @$memids > 0 ? LJ::Memories::_memory_getter($u, {%{$opts || {}}, byid => $memids }) : {};
641 return $memories;
644 # <LJFUNC>
645 # name: LJ::Memories::get_keywords
646 # class:
647 # des: Retrieves keyword/keyids without big joins, returns a hashref.
648 # args: uobj
649 # des-uobj: User object to get keyword pairs for.
650 # returns: Hashref; { keywordid => keyword }
651 # </LJFUNC>
652 sub get_keywords {
653 my $u = shift;
654 $u = LJ::want_user($u);
655 return undef unless $u;
657 my $use_reader = 0;
658 my $memkey = [$u->{userid},"memkwid:$u->{userid}"];
659 my $ret = LJ::MemCache::get($memkey);
660 return $ret if defined $ret;
661 $ret = {};
663 if ($u->{dversion} > 5) {
664 # new style clustered code
665 my $dbcm = LJ::get_cluster_def_reader($u);
666 unless ($dbcm) {
667 $use_reader = 1;
668 $dbcm = LJ::get_cluster_reader($u);
670 my $ids = $dbcm->selectcol_arrayref('SELECT DISTINCT kwid FROM memkeyword2 WHERE userid = ?',
671 undef, $u->{userid});
672 if (@{$ids || []}) {
673 my $in = join ",", @$ids;
674 my $rows = $dbcm->selectall_arrayref('SELECT kwid, keyword FROM userkeywords ' .
675 "WHERE userid = ? AND kwid IN ($in)", undef, $u->{userid});
676 $ret->{$_->[0]} = $_->[1] foreach @{$rows || []};
679 } else {
680 # old style code using global
681 my $dbh = LJ::get_db_writer();
682 unless ($dbh) {
683 $use_reader = 1;
684 $dbh = LJ::get_db_reader();
686 my $sth = $dbh->prepare("SELECT DISTINCT mk.kwid ".
687 "FROM ".
688 " memorable m FORCE INDEX (uniq),".
689 " memkeyword mk ".
690 "WHERE mk.memid=m.memid AND m.userid=?");
691 $sth->execute($u->{userid});
692 my @ids;
693 push @ids, $_ while $_ = $sth->fetchrow_array;
695 if (@ids) {
696 my $in = join(",", @ids);
697 $sth = $dbh->prepare("SELECT kwid, keyword FROM keywords WHERE kwid IN ($in)");
698 $sth->execute;
700 while (my ($id,$kw) = $sth->fetchrow_array) {
701 $ret->{$id} = $kw;
706 my $expiration = $LJ::MEMCACHE_EXPIRATION{'memkwid'} || 86400;
707 LJ::MemCache::set($memkey, $ret, $expiration) unless $use_reader;
708 return $ret;
711 # <LJFUNC>
712 # name: LJ::Memories::updated_keywords
713 # class: web
714 # des: Deletes memcached keyword data.
715 # args: uobj
716 # des-uobj: User object to clear memcached keywords for.
717 # returns: undef.
718 # </LJFUNC>
719 sub updated_keywords {
720 return clear_memcache(shift);
723 # <LJFUNC>
724 # name: LJ::Memories::clear_memcache
725 # class: web
726 # des: Deletes memcached keyword data.
727 # args: uobj
728 # des-uobj: User object to clear memcached keywords for.
729 # returns: undef.
730 # </LJFUNC>
731 sub clear_memcache {
732 my $u = shift;
733 return unless ref $u;
734 my $userid = $u->{userid};
736 LJ::MemCache::delete([$userid, "memct:$userid"]);
738 LJ::MemCache::delete([$userid, "memkwid:$userid"]);
740 # Delete all memkwcnt entries
741 LJ::MemCache::delete([$userid, "memkwcnt:$userid:w:f"]);
742 LJ::MemCache::delete([$userid, "memkwcnt:$userid:w:v"]);
743 LJ::MemCache::delete([$userid, "memkwcnt:$userid:w:u"]);
744 LJ::MemCache::delete([$userid, "memkwcnt:$userid:t:f"]);
745 LJ::MemCache::delete([$userid, "memkwcnt:$userid:t:v"]);
746 LJ::MemCache::delete([$userid, "memkwcnt:$userid:t:u"]);
748 return undef;