668 lines
24 KiB
Perl
668 lines
24 KiB
Perl
#!/usr/bin/perl
|
|
#
|
|
# DW::Logic::LogItems
|
|
#
|
|
# Contains logic used to calculate what items should be showed on the reading page
|
|
# and other related functions. Functions related to loading large numbers of entries
|
|
# in a complicated fashion should be in here. General purpose entry functionality
|
|
# should be in LJ::Entry, etc.
|
|
#
|
|
# Authors:
|
|
# Mark Smith <mark@dreamwidth.org>
|
|
#
|
|
# Copyright (c) 2009 by Dreamwidth Studios, LLC.
|
|
#
|
|
# This program is free software; you may redistribute it and/or modify it under
|
|
# the same terms as Perl itself. For a copy of the license, please reference
|
|
# 'perldoc perlartistic' or 'perldoc perlgpl'.
|
|
#
|
|
|
|
package DW::Logic::LogItems;
|
|
use strict;
|
|
|
|
use Carp qw/ confess /;
|
|
|
|
# name: $u->watch_items
|
|
# des: Return watch list items for a given user, filter, and period.
|
|
# args: hash of items, key/values:
|
|
# - remote
|
|
# - itemshow
|
|
# - skip
|
|
# - friends (opt) friends rows loaded via [func[LJ::get_friends]]
|
|
# - friends_u (opt) u objects of all friends loaded
|
|
# - idsbycluster (opt) hashref to set clusterid key to [ [ journalid, itemid ]+ ]
|
|
# - dateformat: either "S2" for S2 code, or anything else for S1
|
|
# - friendsoffriends: load friends of friends, not just friends
|
|
# - security: (public|access) or a group number
|
|
# - showtypes: /[PICNYF]/
|
|
# - events_date: date to load events for ($u must have friendspage_per_day)
|
|
# - content_filter: object of type DW::User::ContentFilters::Filter
|
|
# returns: Array of item hashrefs containing the same elements
|
|
sub watch_items {
|
|
my ( $u, %args ) = @_;
|
|
$u = LJ::want_user($u) or confess 'invalid user object';
|
|
|
|
# bail very, very early for accounts that are too big for a reading page
|
|
return () if $LJ::FORCE_EMPTY_SUBSCRIPTIONS{ $u->id };
|
|
|
|
# not sure where best to do this, so we're doing it here: don't allow
|
|
# content filters for community reading pages.
|
|
delete $args{content_filter} unless $u->is_individual;
|
|
|
|
# we only allow viewing protected content on your own reading page, if you
|
|
# are viewing someone else's reading page, we assume you're logged out
|
|
my $remote = LJ::want_user( delete $args{remote} ) || LJ::get_remote();
|
|
$remote = undef if $remote && $remote->id != $u->id;
|
|
|
|
# prepare some variables for later... many variables
|
|
my @items = ();
|
|
my $itemshow = $args{itemshow} + 0;
|
|
my $skip = $args{skip} + 0;
|
|
$skip = 0 if $skip < 0;
|
|
my $getitems = $itemshow + $skip;
|
|
|
|
# friendspage per day is allowed only for journals with the special cap 'friendspage_per_day'
|
|
my $events_date = $args{events_date};
|
|
$events_date = '' unless $remote && $u->can_use_daily_readpage;
|
|
|
|
my $filter = $args{content_filter};
|
|
my $max_age = $LJ::MAX_FRIENDS_VIEW_AGE || 3600 * 24 * 14; # 2 week default.
|
|
my $lastmax = $LJ::EndOfTime - ( $events_date || ( time() - $max_age ) );
|
|
my $lastmax_cutoff = 0
|
|
; # if nonzero, never search for entries with rlogtime higher than this (set when cache in use)
|
|
|
|
# given a hash of friends rows, strip out rows with invalid journaltype
|
|
my $filter_journaltypes = sub {
|
|
my ( $friends, $friends_u, $memcache_only, $valid_types ) = @_;
|
|
return unless $friends && $friends_u;
|
|
$valid_types ||= uc $args{showtypes} if defined $args{showtypes};
|
|
|
|
# make (F)eeds an alias for s(Y)ndicated
|
|
$valid_types =~ s/F/Y/g if defined $valid_types;
|
|
|
|
# load u objects for all the given
|
|
LJ::load_userids_multiple( [ map { $_, \$friends_u->{$_} } keys %$friends ],
|
|
[$remote], $memcache_only );
|
|
|
|
# delete u objects based on 'showtypes'
|
|
foreach my $fid ( keys %$friends_u ) {
|
|
my $fu = $friends_u->{$fid};
|
|
if ( !$fu
|
|
|| !$fu->is_visible
|
|
|| $valid_types && index( uc($valid_types), $fu->{journaltype} ) == -1 )
|
|
{
|
|
delete $friends_u->{$fid};
|
|
delete $friends->{$fid};
|
|
}
|
|
}
|
|
|
|
# all args passed by reference
|
|
return;
|
|
};
|
|
|
|
my @friends_buffer = ();
|
|
my $fr_loaded = 0; # flag: have we loaded friends?
|
|
|
|
# normal friends mode
|
|
my $get_next_friend = sub {
|
|
|
|
# return one if we already have some loaded.
|
|
return $friends_buffer[0] if @friends_buffer;
|
|
return undef if $fr_loaded;
|
|
|
|
# get all friends for this user and groupmask
|
|
my $friends = $u->watch_list( community_okay => 1 );
|
|
my %friends_u;
|
|
|
|
# strip out people who aren't in the filter, if we have one
|
|
if ($filter) {
|
|
foreach my $fid ( keys %$friends ) {
|
|
delete $friends->{$fid}
|
|
unless $filter->contains_userid($fid);
|
|
}
|
|
}
|
|
|
|
# strip out rows with invalid journal types
|
|
$filter_journaltypes->( $friends, \%friends_u );
|
|
|
|
# get update times for all the friendids
|
|
my $tu_opts = {};
|
|
my $fcount = scalar keys %$friends;
|
|
if ( $LJ::SLOPPY_FRIENDS_THRESHOLD && $fcount > $LJ::SLOPPY_FRIENDS_THRESHOLD ) {
|
|
$tu_opts->{memcache_only} = 1;
|
|
}
|
|
|
|
my $times =
|
|
$events_date
|
|
? LJ::get_times_multi( $tu_opts, keys %$friends )
|
|
: { updated => LJ::get_timeupdate_multi( $tu_opts, keys %$friends ) };
|
|
my $timeupdate = $times->{updated};
|
|
|
|
# now push a properly formatted @friends_buffer row
|
|
foreach my $fid ( keys %$timeupdate ) {
|
|
my $fu = $friends_u{$fid};
|
|
my $rupdate = $LJ::EndOfTime - ( $timeupdate->{$fid} || 0 );
|
|
my $clusterid = $fu->{'clusterid'};
|
|
push @friends_buffer, [ $fid, $rupdate, $clusterid, $friends->{$fid}, $fu ];
|
|
}
|
|
|
|
@friends_buffer =
|
|
sort { $a->[1] <=> $b->[1] }
|
|
grep {
|
|
( $timeupdate->{ $_->[0] } || 0 ) >= $lastmax and # reverse index
|
|
(
|
|
$events_date
|
|
? $times->{created}->{ $_->[0] } < $events_date
|
|
: 1
|
|
)
|
|
} @friends_buffer;
|
|
|
|
# note that we've already loaded the friends
|
|
$fr_loaded = 1;
|
|
|
|
# return one if we just found some, else we're all
|
|
# out and there's nobody else to load.
|
|
return @friends_buffer ? $friends_buffer[0] : undef;
|
|
};
|
|
|
|
# memcached friends of friends mode
|
|
$get_next_friend = sub {
|
|
|
|
# return one if we already have some loaded.
|
|
return $friends_buffer[0] if @friends_buffer;
|
|
return undef if $fr_loaded;
|
|
|
|
# get journal's friends
|
|
my $friends = $u->watch_list or return undef;
|
|
my %friends_u;
|
|
|
|
# fill %allfriends with all friendids and cut $friends
|
|
# down to only include those that match $filter
|
|
my %allfriends = ();
|
|
foreach my $fid ( keys %$friends ) {
|
|
$allfriends{$fid}++;
|
|
|
|
# if the person is in an active filter, allow them, else delete them
|
|
next unless $filter && !$filter->contains_userid($fid);
|
|
delete $friends->{$fid};
|
|
}
|
|
|
|
# strip out invalid friend journaltypes
|
|
$filter_journaltypes->( $friends, \%friends_u, "memcache_only", "P" );
|
|
|
|
# get update times for all the friendids
|
|
my $f_tu = LJ::get_timeupdate_multi( { 'memcache_only' => 1 }, keys %$friends );
|
|
|
|
# get friends of friends
|
|
my $ffct = 0;
|
|
my %ffriends = ();
|
|
foreach my $fid ( sort { $f_tu->{$b} <=> $f_tu->{$a} } keys %$friends ) {
|
|
last if $ffct > 50;
|
|
my $fu = $friends_u{$fid};
|
|
my $ff = $fu->watch_list( community_okay => 1, memcache_only => 1 );
|
|
my $ct = 0;
|
|
while ( my $ffid = each %$ff ) {
|
|
last if $ct > 100;
|
|
next if $allfriends{$ffid} || $ffid == $u->id;
|
|
$ffriends{$ffid} = $ff->{$ffid};
|
|
$ct++;
|
|
}
|
|
$ffct++;
|
|
}
|
|
|
|
# strip out invalid friendsfriends journaltypes
|
|
my %ffriends_u;
|
|
$filter_journaltypes->( \%ffriends, \%ffriends_u, "memcache_only" );
|
|
|
|
# get update times for all the friendids
|
|
my $ff_tu = LJ::get_timeupdate_multi( { 'memcache_only' => 1 }, keys %ffriends );
|
|
|
|
# build friends buffer
|
|
foreach my $ffid ( sort { $ff_tu->{$b} <=> $ff_tu->{$a} } keys %$ff_tu ) {
|
|
my $rupdate = $LJ::EndOfTime - $ff_tu->{$ffid};
|
|
my $clusterid = $ffriends_u{$ffid}->{'clusterid'};
|
|
|
|
# since this is ff mode, we'll force colors to ffffff on 000000
|
|
$ffriends{$ffid}->{'fgcolor'} = "#000000";
|
|
$ffriends{$ffid}->{'bgcolor'} = "#ffffff";
|
|
|
|
push @friends_buffer,
|
|
[ $ffid, $rupdate, $clusterid, $ffriends{$ffid}, $ffriends_u{$ffid} ];
|
|
}
|
|
|
|
@friends_buffer = sort { $a->[1] <=> $b->[1] } @friends_buffer;
|
|
|
|
# note that we've already loaded the friends
|
|
$fr_loaded = 1;
|
|
|
|
# return one if we just found some fine, else we're all
|
|
# out and there's nobody else to load.
|
|
return @friends_buffer ? $friends_buffer[0] : undef;
|
|
|
|
}
|
|
if $args{friendsoffriends} && @LJ::MEMCACHE_SERVERS;
|
|
|
|
# friends of friends disabled w/o memcache
|
|
confess 'friends of friends mode requires memcache'
|
|
if $args{friendsoffriends} && !@LJ::MEMCACHE_SERVERS;
|
|
|
|
my $loop = 1;
|
|
my $itemsleft = $getitems; # even though we got a bunch, potentially, they could be old
|
|
my $fr;
|
|
|
|
while ( $loop && ( $fr = $get_next_friend->() ) ) {
|
|
shift @friends_buffer;
|
|
|
|
# load the next recent updating friend's recent items
|
|
my $friendid = $fr->[0];
|
|
|
|
$args{friends}->{$friendid} = $fr->[3]; # friends row
|
|
$args{friends_u}->{$friendid} = $fr->[4]; # friend u object
|
|
|
|
my @newitems = LJ::get_log2_recent_user(
|
|
{
|
|
clusterid => $fr->[2],
|
|
userid => $friendid,
|
|
remote => $remote,
|
|
itemshow => $itemsleft,
|
|
filter => $filter,
|
|
notafter => $lastmax,
|
|
dateformat => $args{dateformat},
|
|
update => $LJ::EndOfTime - $fr->[1], # reverse back to normal
|
|
events_date => $events_date,
|
|
security => $args{security},
|
|
}
|
|
);
|
|
|
|
# stamp each with clusterid if from cluster, so ljviews and other
|
|
# callers will know which items are old (no/0 clusterid) and which
|
|
# are new
|
|
$_->{clusterid} = $fr->[2] foreach @newitems;
|
|
|
|
if (@newitems) {
|
|
push @items, @newitems;
|
|
|
|
$itemsleft--;
|
|
|
|
my $evtime = sub { LJ::mysqldate_to_time( $_[0]->{eventtime} ) };
|
|
|
|
# sort all the total items by rlogtime (recent at beginning).
|
|
# if there's an in-second tie, the "newer" post is determined by
|
|
# the later eventtime (if known/different from rlogtime), then by
|
|
# the higher jitemid, which means nothing if the posts aren't in
|
|
# the same journal, but means everything if they are (which happens
|
|
# almost never for a human, but all the time for RSS feeds)
|
|
@items = sort {
|
|
$a->{rlogtime} <=> $b->{rlogtime}
|
|
|| $evtime->($b) <=> $evtime->($a)
|
|
|| $b->{jitemid} <=> $a->{jitemid}
|
|
} @items;
|
|
|
|
# cut the list down to what we need.
|
|
@items = splice( @items, 0, $getitems ) if ( @items > $getitems );
|
|
}
|
|
|
|
if ( @items == $getitems ) {
|
|
$lastmax = $items[-1]->{'rlogtime'};
|
|
$lastmax = $lastmax_cutoff if $lastmax_cutoff && $lastmax > $lastmax_cutoff;
|
|
|
|
# stop looping if we know the next friend's newest entry
|
|
# is greater (older) than the oldest one we've already
|
|
# loaded.
|
|
my $nextfr = $get_next_friend->();
|
|
$loop = 0 if ( $nextfr && $nextfr->[1] > $lastmax );
|
|
}
|
|
}
|
|
|
|
# remove skipped ones
|
|
splice( @items, 0, $skip ) if $skip;
|
|
|
|
# get items
|
|
foreach (@items) {
|
|
$args{owners}->{ $_->{'ownerid'} } = 1;
|
|
}
|
|
|
|
# return the itemids grouped by clusters, if callers wants it.
|
|
if ( ref $args{idsbycluster} eq "HASH" ) {
|
|
foreach (@items) {
|
|
push @{ $args{idsbycluster}->{ $_->{'clusterid'} } },
|
|
[ $_->{'ownerid'}, $_->{'itemid'} ];
|
|
}
|
|
}
|
|
|
|
return @items;
|
|
}
|
|
*LJ::User::watch_items = \&watch_items;
|
|
*DW::User::watch_items = \&watch_items;
|
|
|
|
# name: $u->recent_items
|
|
# des: Returns journal entries for a given account.
|
|
# takes hash of options as arguments
|
|
# -- err: scalar ref to return error code/msg in
|
|
# -- remote: remote user's $u
|
|
# -- clusterid: clusterid of userid
|
|
# -- tagids: arrayref of tagids to return entries with
|
|
# -- security: (public|access|private) or a group number
|
|
# -- clustersource: if value 'slave', uses replicated databases
|
|
# -- order: if 'logtime', sorts by logtime, not eventtime
|
|
# -- friendsview: if true, sorts by logtime, not eventtime
|
|
# -- notafter: upper bound inclusive for rlogtime/revttime (depending on sort mode),
|
|
# defaults to no limit
|
|
# -- skip: items to skip
|
|
# -- itemshow: items to show
|
|
# -- viewall: if set, no security is used.
|
|
# -- dateformat: if "S2", uses S2's 'alldatepart' format.
|
|
# -- itemids: optional arrayref onto which itemids should be pushed
|
|
# -- posterid: optional, return (community) posts made by this poster only
|
|
# returns: array of hashrefs containing keys:
|
|
# -- itemid (the jitemid)
|
|
# -- posterid
|
|
# -- security
|
|
# -- alldatepart (in S1 or S2 fmt, depending on 'dateformat' req key)
|
|
# -- system_alldatepart (same as above, but for the system time)
|
|
# -- ownerid (if in 'friendsview' mode)
|
|
# -- rlogtime (if in 'friendsview' mode)
|
|
sub recent_items {
|
|
my ( $u, %args ) = @_;
|
|
$u = LJ::want_user($u) or confess 'invalid user object';
|
|
|
|
my $userid = $u->id;
|
|
|
|
my @items = (); # what we'll return
|
|
my $err = $args{err};
|
|
|
|
my $remote = LJ::want_user( delete $args{remote} );
|
|
my $remoteid = $remote ? $remote->id : 0;
|
|
|
|
my $max_hints = $LJ::MAX_SCROLLBACK_LASTN; # temporary
|
|
my $sort_key = "revttime";
|
|
|
|
my $clusterid = $args{'clusterid'} + 0;
|
|
my @sources = ("cluster$clusterid");
|
|
if ( my $ab = $LJ::CLUSTER_PAIR_ACTIVE{$clusterid} ) {
|
|
@sources = ("cluster${clusterid}${ab}");
|
|
}
|
|
unshift @sources, ( "cluster${clusterid}lite", "cluster${clusterid}slave" )
|
|
if $args{'clustersource'} eq "slave";
|
|
my $logdb = LJ::get_dbh(@sources);
|
|
|
|
# community/friend views need to post by log time, not event time
|
|
$sort_key = "rlogtime" if ( $args{'order'} eq "logtime"
|
|
|| $args{'friendsview'} );
|
|
|
|
# 'notafter':
|
|
# the friends view doesn't want to load things that it knows it
|
|
# won't be able to use. if this argument is zero or undefined,
|
|
# then we'll load everything less than or equal to 1 second from
|
|
# the end of time. we don't include the last end of time second
|
|
# because that's what backdated entries are set to. (so for one
|
|
# second at the end of time we'll have a flashback of all those
|
|
# backdated entries... but then the world explodes and everybody
|
|
# with 32 bit time_t structs dies)
|
|
#
|
|
# Unless we are not on a friends view, then want to use the actual end of time.
|
|
my $notafter = $args{notafter} ? $args{notafter} + 0 : 0;
|
|
$notafter ||= $args{friendsview} ? $LJ::EndOfTime - 1 : $LJ::EndOfTime;
|
|
|
|
my $skip = $args{skip} ? $args{skip} + 0 : 0;
|
|
my $itemshow = $args{itemshow} ? $args{itemshow} + 0 : 0;
|
|
$itemshow = $max_hints if $itemshow > $max_hints;
|
|
|
|
my $maxskip = $max_hints - $itemshow;
|
|
if ( $skip < 0 ) { $skip = 0; }
|
|
if ( $skip > $maxskip ) { $skip = $maxskip; }
|
|
my $itemload = $itemshow + $skip;
|
|
|
|
my $mask = 0;
|
|
if ( $remote && ( $remote->is_person || $remote->is_identity ) && $remoteid != $userid ) {
|
|
|
|
# if this is a community we're viewing, fake the mask to select on, as communities
|
|
# no longer have masks to users
|
|
if ( $u->is_community ) {
|
|
$mask = $remote->member_of($u) ? 1 : 0;
|
|
}
|
|
else {
|
|
$mask = $u->trustmask($remote);
|
|
}
|
|
}
|
|
|
|
# decide what level of security the remote user can see
|
|
my $secwhere = "";
|
|
if ( $userid == $remoteid
|
|
|| ( $remote && $remote->can_manage($u) )
|
|
|| $args{'viewall'} )
|
|
{
|
|
# no extra where restrictions... user can see all their own stuff
|
|
# community administrators can also see everything in their comms
|
|
# alternatively, if 'viewall' opt flag is set, security is off.
|
|
}
|
|
elsif ($mask) {
|
|
|
|
# can see public or things with them in the mask
|
|
$secwhere = "AND (security='public' OR (security='usemask' AND allowmask & $mask != 0))";
|
|
}
|
|
else {
|
|
# not a friend? only see public.
|
|
$secwhere = "AND security='public' ";
|
|
}
|
|
|
|
# because LJ::get_friend_items needs rlogtime for sorting.
|
|
my $extra_sql = '';
|
|
if ( $args{'friendsview'} ) {
|
|
$extra_sql .= "journalid AS 'ownerid', rlogtime, ";
|
|
}
|
|
|
|
# if we need to get by tag, get an itemid list now
|
|
my $jitemidwhere = '';
|
|
if ( ref $args{tagids} eq 'ARRAY' && @{ $args{tagids} } ) {
|
|
|
|
my $jitemids;
|
|
|
|
# $args{tagmode} = $args{getargs}->{mode} eq 'and' ? 'and' : 'or';
|
|
|
|
if ( $args{tagmode} eq 'and' ) {
|
|
|
|
my $limit = $LJ::TAG_INTERSECTION;
|
|
die "\$LJ::TAG_INTERSECTION not set!"
|
|
unless $limit && $limit > 0;
|
|
my $need = scalar @{ $args{tagids} };
|
|
$#{ $args{tagids} } = $limit - 1 if $need > $limit;
|
|
|
|
my $in = join( ',', map { $_ + 0 } @{ $args{tagids} } );
|
|
my $sth = $logdb->prepare(
|
|
"SELECT jitemid, kwid FROM logtagsrecent WHERE journalid = ? AND kwid IN ($in)");
|
|
$sth->execute($userid);
|
|
die $logdb->errstr if $logdb->err;
|
|
|
|
my %mix;
|
|
while ( my $row = $sth->fetchrow_arrayref ) {
|
|
my ($jitemid) = @$row;
|
|
$mix{$jitemid}++;
|
|
}
|
|
|
|
foreach my $jitemid ( keys %mix ) {
|
|
delete $mix{$jitemid} if $mix{$jitemid} < $need;
|
|
}
|
|
|
|
$jitemids = [ keys %mix ];
|
|
|
|
}
|
|
else { # mode: 'or'
|
|
# select jitemids uniquely
|
|
my $in = join( ',', map { $_ + 0 } @{ $args{tagids} } );
|
|
$jitemids = $logdb->selectcol_arrayref(
|
|
qq{
|
|
SELECT DISTINCT jitemid FROM logtagsrecent WHERE journalid = ? AND kwid IN ($in)
|
|
}, undef, $userid
|
|
);
|
|
die $logdb->errstr if $logdb->err;
|
|
}
|
|
|
|
# set $jitemidwhere iff we have jitemids
|
|
if (@$jitemids) {
|
|
$jitemidwhere = " AND jitemid IN (" . join( ',', map { $_ + 0 } @$jitemids ) . ")";
|
|
}
|
|
else {
|
|
# no items, so show no entries
|
|
return ();
|
|
}
|
|
}
|
|
|
|
# if we need to filter by security, build up the where clause for that too
|
|
my $securitywhere = '';
|
|
if ( $args{'security'} ) {
|
|
my $security = $args{'security'};
|
|
if ( ( $security eq "public" ) || ( $security eq "private" ) ) {
|
|
$securitywhere = " AND security = \"$security\"";
|
|
}
|
|
elsif ( $security eq "access" ) {
|
|
$securitywhere = " AND security = \"usemask\" AND allowmask = 1";
|
|
}
|
|
elsif ( $security =~ /^\d+$/ ) {
|
|
$securitywhere =
|
|
" AND security = \"usemask\" AND (allowmask & " . ( 1 << $security ) . ")";
|
|
}
|
|
}
|
|
|
|
my $sql;
|
|
|
|
my $dateformat = "%a %W %b %M %y %Y %c %m %e %d %D %p %i %l %h %k %H";
|
|
if ( $args{'dateformat'} eq "S2" ) {
|
|
$dateformat = "%Y %m %d %H %i %s %w"; # yyyy mm dd hh mm ss day_of_week
|
|
}
|
|
|
|
my ( $sql_limit, $sql_select ) = ( '', '' );
|
|
if ( $args{'ymd'} ) {
|
|
my ( $year, $month, $day );
|
|
if ( $args{'ymd'} =~ m!^(\d\d\d\d)/(\d\d)/(\d\d)\b! ) {
|
|
( $year, $month, $day ) = ( $1, $2, $3 );
|
|
|
|
# check
|
|
if ( $year !~ /^\d+$/ ) { $$err = "Corrupt or non-existant year."; return (); }
|
|
if ( $month !~ /^\d+$/ ) { $$err = "Corrupt or non-existant month."; return (); }
|
|
if ( $day !~ /^\d+$/ ) { $$err = "Corrupt or non-existant day."; return (); }
|
|
if ( $month < 1 || $month > 12 || int($month) != $month ) {
|
|
$$err = "Invalid month.";
|
|
return ();
|
|
}
|
|
if ( $year < 1970 || $year > 2038 || int($year) != $year ) {
|
|
$$err = "Invalid year: $year";
|
|
return ();
|
|
}
|
|
if ( $day < 1 || $day > 31 || int($day) != $day ) { $$err = "Invalid day."; return (); }
|
|
if ( $day > LJ::days_in_month( $month, $year ) ) {
|
|
$$err = "That month doesn't have that many days.";
|
|
return ();
|
|
}
|
|
}
|
|
else {
|
|
$$err = "wrong date: " . $args{'ymd'};
|
|
return ();
|
|
}
|
|
$sql_limit = "LIMIT 2000";
|
|
$sql_select = "AND year=$year AND month=$month AND day=$day";
|
|
$extra_sql .= "allowmask, ";
|
|
}
|
|
else {
|
|
$sql_limit = "LIMIT $skip,$itemshow";
|
|
$sql_select = "AND $sort_key <= $notafter";
|
|
}
|
|
|
|
my $posterwhere;
|
|
if ( $args{posterid} && $args{posterid} =~ /^(\d+)$/ ) {
|
|
$posterwhere = " AND posterid=$1";
|
|
}
|
|
else {
|
|
$posterwhere = "";
|
|
}
|
|
|
|
$sql = qq{
|
|
SELECT jitemid AS 'itemid', posterid, security, $extra_sql
|
|
DATE_FORMAT(eventtime, "$dateformat") AS 'alldatepart', anum,
|
|
DATE_FORMAT(logtime, "$dateformat") AS 'system_alldatepart',
|
|
allowmask, eventtime, logtime
|
|
FROM log2 USE INDEX ($sort_key)
|
|
WHERE journalid=$userid $sql_select $secwhere $jitemidwhere $securitywhere $posterwhere
|
|
ORDER BY journalid, $sort_key
|
|
$sql_limit
|
|
};
|
|
|
|
unless ($logdb) {
|
|
$$err = "nodb" if ref $err eq "SCALAR";
|
|
return ();
|
|
}
|
|
|
|
my $sth = $logdb->prepare($sql);
|
|
$sth->execute;
|
|
if ( $logdb->err ) { die $logdb->errstr; }
|
|
|
|
# keep track of the last alldatepart, and a per-minute buffer
|
|
my $last_time;
|
|
my @buf;
|
|
my $flush = sub {
|
|
return unless @buf;
|
|
push @items, sort { $b->{itemid} <=> $a->{itemid} } @buf;
|
|
@buf = ();
|
|
};
|
|
|
|
while ( my $li = $sth->fetchrow_hashref ) {
|
|
push @{ $args{'itemids'} }, $li->{'itemid'};
|
|
|
|
my $sortdate = {
|
|
rlogtime => 'system_alldatepart',
|
|
revttime => 'alldatepart'
|
|
}->{$sort_key};
|
|
|
|
$flush->()
|
|
unless defined $last_time
|
|
&& $li->{$sortdate} eq $last_time;
|
|
push @buf, $li;
|
|
$last_time = $li->{$sortdate};
|
|
|
|
# construct an LJ::Entry singleton
|
|
my $entry = LJ::Entry->new( $userid, jitemid => $li->{itemid} );
|
|
$entry->absorb_row(%$li);
|
|
}
|
|
$flush->();
|
|
|
|
return @items;
|
|
}
|
|
*LJ::User::recent_items = \&recent_items;
|
|
*DW::User::recent_items = \&recent_items;
|
|
|
|
# name: $u->active_entries
|
|
# des: Returns 10 last active entries for an account
|
|
# returns: array of itemids
|
|
sub active_entries {
|
|
my ($u) = @_;
|
|
my $uid = $u->userid;
|
|
|
|
# check memcache first
|
|
my $activeentries = LJ::MemCache::get( [ $uid, "activeentries:$uid" ] );
|
|
return @$activeentries if $activeentries;
|
|
|
|
# get latest 10 entries that were commented on - we will see whether $remote can view them later
|
|
# disregard screened and deleted comments when ordering
|
|
|
|
# NOTE: we have to force the index because MySQL's optimizer gets this wrong. we know that our
|
|
# data is going to be near the top.
|
|
my $entryids = $u->selectcol_arrayref(
|
|
q{SELECT DISTINCT nodeid FROM talk2 FORCE INDEX (PRIMARY)
|
|
WHERE journalid = ? AND state NOT IN ('D', 'S')
|
|
ORDER BY jtalkid DESC LIMIT 10},
|
|
undef, $u->id
|
|
);
|
|
die $u->errstr if $u->err;
|
|
return unless $entryids && @$entryids;
|
|
|
|
# memcache this data in the form: activeentries:journalid
|
|
LJ::MemCache::set( [ $uid, "activeentries:$uid" ], \@$entryids );
|
|
|
|
# return. we check whether the user viewing is allowed to view these entries later
|
|
return @$entryids;
|
|
}
|
|
|
|
*LJ::User::active_entries = \&active_entries;
|
|
*DW::User::active_entries = \&active_entries;
|
|
|
|
1;
|