ljr/local/cgi-bin/ljviews.pl

2390 lines
80 KiB
Perl
Executable File

#!/usr/bin/perl
#
# <LJDEP>
# lib: cgi-bin/ljlib.pl, cgi-bin/ljconfig.pl, cgi-bin/ljlang.pl, cgi-bin/cleanhtml.pl
# </LJDEP>
use strict;
package LJ::S1;
use vars qw(@themecoltypes);
eval "use LJR::Distributed;";
my $ljr = $@ ? 0 : 1;
if ($ljr) {
use LJR::Distributed;
}
# this used to be in a table, but that was kinda useless
@themecoltypes = (
[ 'page_back', 'Page background' ],
[ 'page_text', 'Page text' ],
[ 'page_link', 'Page link' ],
[ 'page_vlink', 'Page visited link' ],
[ 'page_alink', 'Page active link' ],
[ 'page_text_em', 'Page emphasized text' ],
[ 'page_text_title', 'Page title' ],
[ 'weak_back', 'Weak accent' ],
[ 'weak_text', 'Text on weak accent' ],
[ 'strong_back', 'Strong accent' ],
[ 'strong_text', 'Text on strong accent' ],
[ 'stronger_back', 'Stronger accent' ],
[ 'stronger_text', 'Text on stronger accent' ],
);
# updated everytime new S1 style cleaning rules are added,
# so cached cleaned versions are invalidated.
$LJ::S1::CLEANER_VERSION = 5;
# PROPERTY Flags:
# /a/:
# safe in styles as sole attributes, without any cleaning. for
# example: <a href="%%urlread%%"> is okay, # if we're in
# LASTN_TALK_READLINK, because the system generates # %%urlread%%.
# by default, if we don't declare things trusted here, # we'll
# double-check all attributes at the end for potential XSS #
# problems.
#
# /u/:
# is a URL. implies /a/.
#
#
# /d/:
# is a number. implies /a/.
#
# /t/:
# tainted! User controls via other some other variable.
#
# /s/:
# some system string... probably safe. but maybe possible to coerce it
# alongside something else.
my $commonprop = {
'dateformat' => {
'yy' => 'd', 'yyyy' => 'd',
'm' => 'd', 'mm' => 'd',
'd' => 'd', 'dd' => 'd',
'min' => 'd',
'12h' => 'd', '12hh' => 'd',
'24h' => 'd', '24hh' => 'd',
},
'talklinks' => {
'messagecount' => 'd',
'urlread' => 'u',
'urlpost' => 'u',
'itemid' => 'd',
},
'talkreadlink' => {
'messagecount' => 'd',
'urlread' => 'u',
},
'event' => {
'itemid' => 'd',
},
'pic' => {
'src' => 'u',
'width' => 'd',
'height' => 'd',
},
'newday' => {
yy => 'd', yyyy => 'd', m => 'd', mm => 'd',
d => 'd', dd => 'd',
},
'skip' => {
'numitems' => 'd',
'url' => 'u',
},
};
$LJ::S1::PROPS = {
'CALENDAR_DAY' => {
'd' => 'd',
'eventcount' => 'd',
'dayevent' => 't',
'daynoevent' => 't',
},
'CALENDAR_DAY_EVENT' => {
'eventcount' => 'd',
'dayurl' => 'u',
},
'CALENDAR_DAY_NOEVENT' => {
},
'CALENDAR_EMPTY_DAYS' => {
'numempty' => 'd',
},
'CALENDAR_MONTH' => {
'monlong' => 's',
'monshort' => 's',
'yy' => 'd',
'yyyy' => 'd',
'weeks' => 't',
'urlmonthview' => 'u',
},
'CALENDAR_NEW_YEAR' => {
'yy' => 'd',
'yyyy' => 'd',
},
'CALENDAR_PAGE' => {
'name' => 't',
"name-'s" => 's',
'yearlinks' => 't',
'months' => 't',
'username' => 's',
'website' => 't',
'head' => 't',
'urlfriends' => 'u',
'urllastn' => 'u',
},
'CALENDAR_WEBSITE' => {
'url' => 't',
'name' => 't',
},
'CALENDAR_WEEK' => {
'days' => 't',
'emptydays_beg' => 't',
'emptydays_end' => 't',
},
'CALENDAR_YEAR_DISPLAYED' => {
'yyyy' => 'd',
'yy' => 'd',
},
'CALENDAR_YEAR_LINK' => {
'yyyy' => 'd',
'yy' => 'd',
'url' => 'u',
},
'CALENDAR_YEAR_LINKS' => {
'years' => 't',
},
# day
'DAY_DATE_FORMAT' => $commonprop->{'dateformat'},
'DAY_EVENT' => $commonprop->{'event'},
'DAY_EVENT_PRIVATE' => $commonprop->{'event'},
'DAY_EVENT_PROTECTED' => $commonprop->{'event'},
'DAY_PAGE' => {
'prevday_url' => 'u',
'nextday_url' => 'u',
'yy' => 'd', 'yyyy' => 'd',
'm' => 'd', 'mm' => 'd',
'd' => 'd', 'dd' => 'd',
'urllastn' => 'u',
'urlcalendar' => 'u',
'urlfriends' => 'u',
},
'DAY_TALK_LINKS' => $commonprop->{'talklinks'},
'DAY_TALK_READLINK' => $commonprop->{'talkreadlink'},
# friends
'FRIENDS_DATE_FORMAT' => $commonprop->{'dateformat'},
'FRIENDS_EVENT' => $commonprop->{'event'},
'FRIENDS_EVENT_PRIVATE' => $commonprop->{'event'},
'FRIENDS_EVENT_PROTECTED' => $commonprop->{'event'},
'FRIENDS_FRIENDPIC' => $commonprop->{'pic'},
'FRIENDS_NEW_DAY' => $commonprop->{'newday'},
'FRIENDS_RANGE_HISTORY' => {
'numitems' => 'd',
'skip' => 'd',
},
'FRIENDS_RANGE_MOSTRECENT' => {
'numitems' => 'd',
},
'FRIENDS_SKIP_BACKWARD' => $commonprop->{'skip'},
'FRIENDS_SKIP_FORWARD' => $commonprop->{'skip'},
'FRIENDS_TALK_LINKS' => $commonprop->{'talklinks'},
'FRIENDS_TALK_READLINK' => $commonprop->{'talkreadlink'},
# lastn
'LASTN_ALTPOSTER' => {
'poster' => 's',
'owner' => 's',
'pic' => 't',
},
'LASTN_ALTPOSTER_PIC' => $commonprop->{'pic'},
'LASTN_CURRENT' => {
'what' => 's',
'value' => 't',
},
'LASTN_CURRENTS' => {
'currents' => 't',
},
'LASTN_DATEFORMAT' => $commonprop->{'dateformat'},
'LASTN_EVENT' => $commonprop->{'event'},
'LASTN_EVENT_PRIVATE' => $commonprop->{'event'},
'LASTN_EVENT_PROTECTED' => $commonprop->{'event'},
'LASTN_NEW_DAY' => $commonprop->{'newday'},
'LASTN_PAGE' => {
'urlfriends' => 'u',
'urlcalendar' => 'u',
},
'LASTN_RANGE_HISTORY' => {
'numitems' => 'd',
'skip' => 'd',
},
'LASTN_RANGE_MOSTRECENT' => {
'numitems' => 'd',
},
'LASTN_SKIP_BACKWARD' => $commonprop->{'skip'},
'LASTN_SKIP_FORWARD' => $commonprop->{'skip'},
'LASTN_TALK_LINKS' => $commonprop->{'talklinks'},
'LASTN_TALK_READLINK' => $commonprop->{'talkreadlink'},
'LASTN_USERPIC' => {
'src' => 'u',
'width' => 'd',
'height' => 'd',
},
};
# <LJFUNC>
# name: LJ::S1::get_themeid
# des: Loads or returns cached version of given color theme data.
# returns: Hashref with color names as keys
# args: dbarg?, themeid
# des-themeid: S1 themeid.
# </LJFUNC>
sub get_themeid
{
&LJ::nodb;
my $themeid = shift;
return $LJ::S1::CACHE_THEMEID{$themeid} if $LJ::S1::CACHE_THEMEID{$themeid};
my $dbr = LJ::get_db_reader();
my $ret = {};
my $sth = $dbr->prepare("SELECT coltype, color FROM themedata WHERE themeid=?");
$sth->execute($themeid);
$ret->{$_->{'coltype'}} = $_->{'color'} while $_ = $sth->fetchrow_hashref;
return $LJ::S1::CACHE_THEMEID{$themeid} = $ret;
}
# returns: hashref of vars (cleaned)
sub load_style
{
&LJ::nodb;
my ($styleid, $viewref) = @_;
# first try local cache for this process
my $cch = $LJ::S1::CACHE_STYLE{$styleid};
if ($cch && $cch->{'cachetime'} > time() - 300) {
$$viewref = $cch->{'type'} if ref $viewref eq "SCALAR";
return $cch->{'style'};
}
# try memcache
my $memkey = [$styleid, "s1styc:$styleid"];
my $styc = LJ::MemCache::get($memkey);
# database handle we'll use if we have to rebuild the cache
my $db;
# function to return a given a styleid
my $find_db = sub {
my $sid = shift;
# should we work with a global or clustered table?
my $userid = LJ::S1::get_style_userid($sid);
# if the user's style is clustered, need to get a $u
my $u = $userid ? LJ::load_userid($userid) : undef;
# return appropriate db handle
if ($u && $u->{'dversion'} >= 5) { # users' styles are clustered
return LJ::S1::get_s1style_writer($u);
}
return @LJ::MEMCACHE_SERVERS ? LJ::get_db_writer() : LJ::get_db_reader();
};
# get database stylecache
unless ($styc) {
$db = $find_db->($styleid);
$styc = $db->selectrow_hashref("SELECT * FROM s1stylecache WHERE styleid=?",
undef, $styleid);
LJ::MemCache::set($memkey, $styc, time()+60*30) if $styc;
}
# no stylecache in db, built a new one
if (! $styc || $styc->{'vars_cleanver'} < $LJ::S1::CLEANER_VERSION) {
my $style = LJ::S1::get_style($styleid);
return {} unless $style;
$db ||= $find_db->($styleid);
$styc = {
'type' => $style->{'type'},
'opt_cache' => $style->{'opt_cache'},
'vars_stor' => LJ::CleanHTML::clean_s1_style($style->{'formatdata'}),
'vars_cleanver' => $LJ::S1::CLEANER_VERSION,
};
# do this query on the db handle we used above
$db->do("REPLACE INTO s1stylecache (styleid, cleandate, type, opt_cache, vars_stor, vars_cleanver) ".
"VALUES (?,NOW(),?,?,?,?)", undef, $styleid,
map { $styc->{$_} } qw(type opt_cache vars_stor vars_cleanver));
}
my $ret = Storable::thaw($styc->{'vars_stor'});
$$viewref = $styc->{'type'} if ref $viewref eq "SCALAR";
if ($styc->{'opt_cache'} eq "Y") {
$LJ::S1::CACHE_STYLE{$styleid} = {
'style' => $ret,
'cachetime' => time(),
'type' => $styc->{'type'},
};
}
return $ret;
}
# LJ::S1::get_public_styles
#
# LJ::load_user_props calls LJ::S1::get_public_styles and since
# a lot of cron jobs call LJ::load_user_props, we've moved
# LJ::S1::get_public_styles to ljlib so that it can be used
# without including ljviews.pl
sub get_s1style_writer {
my $u = shift;
return undef unless LJ::isu($u);
# special case system, its styles live on
# the global master's s1style table alone
if ($u->{'user'} eq 'system') {
return LJ::get_db_writer();
}
return $u->writer;
}
sub get_s1style_reader {
my $u = shift;
return undef unless LJ::isu($u);
# special case system, its styles live on
# the global master's s1style table alone
if ($u->{'user'} eq 'system') {
return @LJ::MEMCACHE_SERVERS ? LJ::get_db_writer() : LJ::get_db_reader();
}
return @LJ::MEMCACHE_SERVERS ? LJ::get_cluster_def_reader($u) : LJ::get_cluster_reader($u);
}
# takes either $u object or userid
sub get_user_styles {
my $u = shift;
$u = LJ::isu($u) ? $u : LJ::load_user($u);
return undef unless $u;
my %styles;
# all cols *except* formatdata, which is big and unnecessary for most uses.
# it'll be loaded by LJ::S1::get_style
my $cols = "styleid, styledes, type, is_public, is_embedded, ".
"is_colorfree, opt_cache, has_ads, lastupdate";
# new clustered table
my ($db, $sth);
if ($u->{'dversion'} >= 5) {
$db = LJ::S1::get_s1style_reader($u);
$sth = $db->prepare("SELECT userid, $cols FROM s1style WHERE userid=?");
$sth->execute($u->{'userid'});
# old global table
} else {
$db = @LJ::MEMCACHE_SERVERS ? LJ::get_db_writer() : LJ::get_db_reader();
$sth = $db->prepare("SELECT user, $cols FROM style WHERE user=?");
$sth->execute($u->{'user'});
}
# build data structure
while (my $row = $sth->fetchrow_hashref) {
# fix up both userid and user values for consistency
$row->{'userid'} = $u->{'userid'};
$row->{'user'} = $u->{'user'};
$styles{$row->{'styleid'}} = $row;
next unless @LJ::MEMCACHE_SERVERS;
# now update memcache while we have this data?
LJ::MemCache::set([$row->{'styleid'}, "s1style:$row->{'styleid'}"], $row);
}
return \%styles;
}
# includes formatdata row.
sub get_style {
my $styleid = shift;
return unless $styleid;
my $memkey = [$styleid, "s1style_all:$styleid"];
my $style = LJ::MemCache::get($memkey);
return $style if $style;
# query global mapping table, returns undef if style isn't clustered
my $userid = LJ::S1::get_style_userid($styleid);
my $u;
$u = LJ::load_userid($userid) if $userid;
# new clustered table
if ($u && $u->{'dversion'} >= 5) {
my $db = LJ::S1::get_s1style_reader($u);
$style = $db->selectrow_hashref("SELECT * FROM s1style WHERE styleid=?", undef, $styleid);
# fill in user since the caller may expect it
$style->{'user'} = $u->{'user'};
# old global table
} else {
my $db = @LJ::MEMCACHE_SERVERS ? LJ::get_db_writer() : LJ::get_db_reader();
$style = $db->selectrow_hashref("SELECT * FROM style WHERE styleid=?", undef, $styleid);
# fill in userid since the caller may expect it
$style->{'userid'} = LJ::get_userid($style->{'user'});
}
return unless $style;
LJ::MemCache::set($memkey, $style);
return $style;
}
sub check_dup_style {
my ($u, $type, $styledes) = @_;
return unless $type && $styledes;
$u = LJ::isu($u) ? $u : LJ::load_user($u);
# new clustered table
if ($u && $u->{'dversion'} >= 5) {
# get writer since this function is to check duplicates. as such,
# the write action we're checking for probably happened recently
my $db = LJ::S1::get_s1style_writer($u);
return $db->selectrow_hashref("SELECT * FROM s1style WHERE userid=? AND type=? AND styledes=?",
undef, $u->{'userid'}, $type, $styledes);
# old global table
} else {
my $dbh = LJ::get_db_writer();
return $dbh->selectrow_hashref("SELECT * FROM style WHERE user=? AND type=? AND styledes=?",
undef, $u->{'user'}, $type, $styledes);
}
}
# returns undef if style isn't clustered
sub get_style_userid {
my $styleid = shift;
# check cache
my $userid = $LJ::S1::REQ_CACHE_STYLEMAP{$styleid};
return $userid if $userid;
my $memkey = [$styleid, "s1stylemap:$styleid"];
my $style = LJ::MemCache::get($memkey);
return $style if $style;
# fetch from db
my $dbr = LJ::get_db_reader();
$userid = $dbr->selectrow_array("SELECT userid FROM s1stylemap WHERE styleid=?",
undef, $styleid);
return unless $userid;
# set cache
$LJ::S1::REQ_CACHE_STYLEMAP{$styleid} = $userid;
LJ::MemCache::set($memkey, $userid);
return $userid;
}
sub create_style {
my ($u, $opts) = @_;
return unless LJ::isu($u) && ref $opts eq 'HASH';
my $dbh = LJ::get_db_writer();
return undef unless $dbh;
my $styleid = LJ::alloc_global_counter('S');
return undef unless $styleid;
my (@cols, @bind, @vals);
foreach (qw(styledes type formatdata is_public is_embedded is_colorfree opt_cache has_ads)) {
next unless $opts->{$_};
push @cols, $_;
push @bind, "?";
push @vals, $opts->{$_};
}
my $cols = join(",", @cols);
my $bind = join(",", @bind);
return unless @cols;
if ($u->{'dversion'} >= 5) {
my $db = LJ::S1::get_s1style_writer($u);
$db->do("INSERT INTO s1style (styleid,userid,$cols) VALUES (?,?,$bind)",
undef, $styleid, $u->{'userid'}, @vals);
my $insertid = LJ::User::mysql_insertid($db);
die "Couldn't allocate insertid for s1style for userid $u->{userid}" unless $insertid;
$dbh->do("INSERT INTO s1stylemap (styleid, userid) VALUES (?,?)", undef, $insertid, $u->{'userid'});
return $insertid;
} else {
$dbh->do("INSERT INTO style (styleid, user,$cols) VALUES (?,?,$bind)",
undef, $styleid, $u->{'user'}, @vals);
return $dbh->{'mysql_insertid'};
}
}
sub update_style {
my ($styleid, $opts) = @_;
return unless $styleid && ref $opts eq 'HASH';
# query global mapping table, returns undef if style isn't clustered
my $userid = LJ::S1::get_style_userid($styleid);
my $u;
$u = LJ::load_userid($userid) if $userid;
my @cols = qw(styledes type formatdata is_public is_embedded
is_colorfree opt_cache has_ads lastupdate);
# what table to operate on ?
my ($db, $table);
# clustered table
if ($u && $u->{'dversion'} >= 5) {
$db = LJ::S1::get_s1style_writer($u);
$table = "s1style";
# global table
} else {
$db = LJ::get_db_writer();
$table = "style";
}
my (@sets, @vals);
foreach (@cols) {
if ($opts->{$_}) {
push @sets, "$_=?";
push @vals, $opts->{$_};
}
}
# update style
my $now_lastupdate = $opts->{'lastupdate'} ? ", lastupdate=NOW()" : '';
my $rows = $db->do("UPDATE $table SET " . join(", ", @sets) . "$now_lastupdate WHERE styleid=?",
undef, @vals, $styleid);
# clear out stylecache
$db->do("UPDATE s1stylecache SET vars_stor=NULL, vars_cleanver=0 WHERE styleid=?",
undef, $styleid);
# update memcache keys
LJ::MemCache::delete([$styleid, "s1style:$styleid"]);
LJ::MemCache::delete([$styleid, "s1style_all:$styleid"]);
LJ::MemCache::delete([$styleid, "s1styc:$styleid"]);
return $rows;
}
sub delete_style {
my $styleid = shift;
return unless $styleid;
# query global mapping table, returns undef if style isn't clustered
my $userid = LJ::S1::get_style_userid($styleid);
my $u;
$u = LJ::load_userid($userid) if $userid;
my $dbh = LJ::get_db_writer();
# new clustered table
if ($u && $u->{'dversion'} >= 5) {
$dbh->do("DELETE FROM s1stylemap WHERE styleid=?", undef, $styleid);
my $db = LJ::S1::get_s1style_writer($u);
$db->do("DELETE FROM s1style WHERE styleid=?", undef, $styleid);
$db->do("DELETE FROM s1stylecache WHERE styleid=?", undef, $styleid);
# old global table
} else {
# they won't have an s1stylemap entry
$dbh->do("DELETE FROM style WHERE styleid=?", undef, $styleid);
$dbh->do("DELETE FROM s1stylecache WHERE styleid=?", undef, $styleid);
}
# clear out some memcache space
LJ::MemCache::delete([$styleid, "s1style:$styleid"]);
LJ::MemCache::delete([$styleid, "s1style_all:$styleid"]);
LJ::MemCache::delete([$styleid, "s1stylemap:$styleid"]);
LJ::MemCache::delete([$styleid, "s1styc:$styleid"]);
return;
}
sub get_overrides {
my $u = shift;
return unless LJ::isu($u);
# try memcache
my $memkey = [$u->{'userid'}, "s1overr:$u->{'userid'}"];
my $overr = LJ::MemCache::get($memkey);
return $overr if $overr;
# new clustered table
if ($u->{'dversion'} >= 5) {
my $db = @LJ::MEMCACHE_SERVERS ? LJ::get_cluster_def_reader($u) : LJ::get_cluster_reader($u);
$overr = $db->selectrow_array("SELECT override FROM s1overrides WHERE userid=?", undef, $u->{'userid'});
# old global table
} else {
my $dbh = @LJ::MEMCACHE_SERVERS ? LJ::get_db_writer() : LJ::get_db_reader();
$overr = $dbh->selectrow_array("SELECT override FROM overrides WHERE user=?", undef, $u->{'user'});
}
# set in memcache
LJ::MemCache::set($memkey, $overr);
return $overr;
}
sub clear_overrides {
my $u = shift;
return unless LJ::isu($u);
my $overr;
my $db;
# new clustered table
if ($u->{'dversion'} >= 5) {
$overr = $u->do("DELETE FROM s1overrides WHERE userid=?", undef, $u->{'userid'});
$db = $u;
# old global table
} else {
my $dbh = LJ::get_db_writer();
$overr = $dbh->do("DELETE FROM overrides WHERE user=?", undef, $u->{'user'});
$db = $dbh;
}
# update s1usercache
$db->do("UPDATE s1usercache SET override_stor=NULL WHERE userid=?",
undef, $u->{'userid'});
LJ::MemCache::delete([$u->{'userid'}, "s1uc:$u->{'userid'}"]);
LJ::MemCache::delete([$u->{'userid'}, "s1overr:$u->{'userid'}"]);
return $overr;
}
sub save_overrides {
my ($u, $overr) = @_;
return unless LJ::isu($u) && $overr;
# new clustered table
my $insertid;
if ($u->{'dversion'} >= 5) {
$u->do("REPLACE INTO s1overrides (userid, override) VALUES (?, ?)",
undef, $u->{'userid'}, $overr);
$insertid = $u->mysql_insertid;
# old global table
} else {
my $dbh = LJ::get_db_writer();
$dbh->do("REPLACE INTO overrides (user, override) VALUES (?, ?)",
undef, $u->{'user'}, $overr);
$insertid = $dbh->{'mysql_insertid'};
}
# update s1usercache
my $override_stor = LJ::CleanHTML::clean_s1_style($overr);
$u->do("UPDATE s1usercache SET override_stor=?, override_cleanver=? WHERE userid=?",
undef, $override_stor, $LJ::S1::CLEANER_VERSION, $u->{'userid'});
LJ::MemCache::delete([$u->{'userid'}, "s1uc:$u->{'userid'}"]);
LJ::MemCache::delete([$u->{'userid'}, "s1overr:$u->{'userid'}"]);
return $insertid;
}
package LJ;
# <LJFUNC>
# name: LJ::alldateparts_to_hash
# class: s1
# des: Given a date/time format from MySQL, breaks it into a hash.
# info: This is used by S1.
# args: alldatepart
# des-alldatepart: The output of the MySQL function
# DATE_FORMAT(sometime, "%a %W %b %M %y %Y %c %m %e %d
# %D %p %i %l %h %k %H")
# returns: Hash (whole, not reference), with keys: dayshort, daylong,
# monshort, monlong, yy, yyyy, m, mm, d, dd, dth, ap, AP,
# ampm, AMPM, min, 12h, 12hh, 24h, 24hh
# </LJFUNC>
sub alldateparts_to_hash
{
my $alldatepart = shift;
my @dateparts = split(/ /, $alldatepart);
return (
'dayshort' => $dateparts[0],
'daylong' => $dateparts[1],
'monshort' => $dateparts[2],
'monlong' => $dateparts[3],
'yy' => $dateparts[4],
'yyyy' => $dateparts[5],
'm' => $dateparts[6],
'mm' => $dateparts[7],
'd' => $dateparts[8],
'dd' => $dateparts[9],
'dth' => $dateparts[10],
'ap' => substr(lc($dateparts[11]),0,1),
'AP' => substr(uc($dateparts[11]),0,1),
'ampm' => lc($dateparts[11]),
'AMPM' => $dateparts[11],
'min' => $dateparts[12],
'12h' => $dateparts[13],
'12hh' => $dateparts[14],
'24h' => $dateparts[15],
'24hh' => $dateparts[16],
);
}
# <LJFUNC>
# class: s1
# name: LJ::fill_var_props
# args: vars, key, hashref
# des: S1 utility function to interpolate %%variables%% in a variable. If
# a modifier is given like %%foo:var%%, then [func[LJ::fvp_transform]]
# is called.
# des-vars: hashref with keys being S1 vars
# des-key: the variable in the vars hashref we're expanding
# des-hashref: hashref of values that could interpolate.
# returns: Expanded string.
# </LJFUNC>
sub fill_var_props
{
my ($vars, $key, $hashref) = @_;
$_ = $vars->{$key};
s/%%([\w:]+:)?([\w\-\']+)%%/$1 ? LJ::fvp_transform(lc($1), $vars, $hashref, $2) : $hashref->{$2}/eg;
return $_;
}
# <LJFUNC>
# class: s1
# name: LJ::fvp_transform
# des: Called from [func[LJ::fill_var_props]] to do trasformations.
# args: transform, vars, hashref, attr
# des-transform: The transformation type.
# des-vars: hashref with keys being S1 vars
# des-hashref: hashref of values that could interpolate. (see
# [func[LJ::fill_var_props]])
# des-attr: the attribute name that's being interpolated.
# returns: Transformed interpolated variable.
# </LJFUNC>
sub fvp_transform
{
my ($transform, $vars, $hashref, $attr) = @_;
my $ret = $hashref->{$attr};
while ($transform =~ s/(\w+):$//) {
my $trans = $1;
if ($trans eq "color") {
return $vars->{"color-$attr"};
}
elsif ($trans eq "ue") {
$ret = LJ::eurl($ret);
}
elsif ($trans eq "cons") {
if ($attr eq "img") { return $LJ::IMGPREFIX; }
if ($attr eq "siteroot") { return $LJ::SITEROOT; }
if ($attr eq "sitename") { return $LJ::SITENAME; }
}
elsif ($trans eq "attr") {
$ret =~ s/\"/&quot;/g;
$ret =~ s/\'/&\#39;/g;
$ret =~ s/</&lt;/g;
$ret =~ s/>/&gt;/g;
$ret =~ s/\]\]//g; # so they can't end the parent's [attr[..]] wrapper
}
elsif ($trans eq "lc") {
$ret = lc($ret);
}
elsif ($trans eq "uc") {
$ret = uc($ret);
}
elsif ($trans eq "xe") {
$ret = LJ::exml($ret);
}
elsif ($trans eq 'ljuser' or $trans eq 'ljcomm') {
my $user = LJ::canonical_username($ret);
$ret = LJ::ljuser($user);
}
elsif ($trans eq 'userurl') {
my $u = LJ::load_user($ret);
$ret = LJ::journal_base($u) if $u;
}
}
return $ret;
}
# <LJFUNC>
# class: s1
# name: LJ::parse_vars
# des: Parses S1 style data into hashref.
# returns: Nothing. Modifies a hashref.
# args: dataref, hashref
# des-dataref: Reference to scalar with data to parse. Format is
# a BML-style full block, as used in the S1 style system.
# des-hashref: Hashref to populate with data.
# </LJFUNC>
sub parse_vars
{
my ($dataref, $hashref) = @_;
my @data = split(/\n/, $$dataref);
my $curitem = "";
foreach (@data)
{
$_ .= "\n";
s/\r//g;
if ($curitem eq "" && /^([A-Z0-9\_]+)=>([^\n\r]*)/)
{
$hashref->{$1} = $2;
}
elsif ($curitem eq "" && /^([A-Z0-9\_]+)<=\s*$/)
{
$curitem = $1;
$hashref->{$curitem} = "";
}
elsif ($curitem && /^<=$curitem\s*$/)
{
chop $hashref->{$curitem}; # remove the false newline
$curitem = "";
}
else
{
$hashref->{$curitem} .= $_ if ($curitem =~ /\S/);
}
}
}
sub current_mood_str {
my ($themeid, $moodid, $mood) = @_;
# ideal behavior: if there is a moodid, that defines the picture.
# if there is a current_mood, that overrides as the mood name,
# otherwise show the mood name associated with current_moodid
my $moodname;
my $moodpic;
# favor custom mood over system mood
if (my $val = $mood) {
LJ::CleanHTML::clean_subject(\$val);
$moodname = $val;
}
if (my $val = $moodid) {
$moodname ||= LJ::mood_name($val);
my %pic;
if (LJ::get_mood_picture($themeid, $val, \%pic)) {
$moodpic = "<img src=\"$pic{'pic'}\" align='absmiddle' width='$pic{'w'}' " .
"height='$pic{'h'}' vspace='1' alt='' /> ";
}
}
return "$moodpic$moodname";
}
sub current_music_str {
my $val = shift;
LJ::CleanHTML::clean_subject(\$val);
return $val;
}
# <LJFUNC>
# class: s1
# name: LJ::prepare_currents
# des: do all the current music/mood/weather/whatever stuff. only used by ljviews.pl.
# args: dbarg, args
# des-args: hashref with keys: 'props' (a hashref), 'vars' hashref with
# keys being S1 variables.
# </LJFUNC>
sub prepare_currents
{
my $args = shift;
my %currents = ();
my $val;
if ($val = $args->{'props'}->{'current_music'}) {
$currents{'Music'} = LJ::current_music_str($val);
}
$currents{'Mood'} = LJ::current_mood_str($args->{'user'}->{'moodthemeid'},
$args->{'props'}->{'current_moodid'},
$args->{'props'}->{'current_mood'});
delete $currents{'Mood'} unless $currents{'Mood'};
if (%currents) {
if ($args->{'vars'}->{$args->{'prefix'}.'_CURRENTS'})
{
### PREFIX_CURRENTS is defined, so use the correct style vars
my $fvp = { 'currents' => "" };
foreach (sort keys %currents) {
$fvp->{'currents'} .= LJ::fill_var_props($args->{'vars'}, $args->{'prefix'}.'_CURRENT', {
'what' => $_,
'value' => $currents{$_},
});
}
$args->{'event'}->{'currents'} =
LJ::fill_var_props($args->{'vars'}, $args->{'prefix'}.'_CURRENTS', $fvp);
} else
{
### PREFIX_CURRENTS is not defined, so just add to %%events%%
$args->{'event'}->{'event'} .= "<br />&nbsp;";
foreach (sort keys %currents) {
$args->{'event'}->{'event'} .= "<br /><b>Current $_</b>: " . $currents{$_} . "\n";
}
}
}
}
package LJ::S1;
use strict;
require "$ENV{'LJHOME'}/cgi-bin/ljconfig.pl";
require "$ENV{'LJHOME'}/cgi-bin/ljlang.pl";
require "$ENV{'LJHOME'}/cgi-bin/cleanhtml.pl";
# the creator for the 'lastn' view:
sub create_view_lastn
{
my ($ret, $u, $vars, $remote, $opts) = @_;
my $user = $u->{'user'};
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'});
return 1;
}
foreach ("name", "url", "urlname", "journaltitle") { LJ::text_out(\$u->{$_}); }
my $get = $opts->{'getargs'};
if ($opts->{'pathextra'}) {
$opts->{'badargs'} = 1;
return 1;
}
LJ::load_user_props($remote, "opt_ljcut_disable_lastn");
my %lastn_page = ();
$lastn_page{'name'} = LJ::ehtml($u->{'name'});
$lastn_page{'name-\'s'} = ($u->{'name'} =~ /s$/i) ? "'" : "'s";
$lastn_page{'username'} = $user;
$lastn_page{'title'} = LJ::ehtml($u->{'journaltitle'} ||
$u->{'name'} . $lastn_page{'name-\'s'} . " Journal");
$lastn_page{'numitems'} = $vars->{'LASTN_OPT_ITEMS'} || 20;
my $journalbase = LJ::journal_base($user, $opts->{'vhost'});
$lastn_page{'urlfriends'} = "$journalbase/friends";
$lastn_page{'urlcalendar'} = "$journalbase/calendar";
if ($u->{'url'} =~ m!^https?://!) {
$lastn_page{'website'} =
LJ::fill_var_props($vars, 'LASTN_WEBSITE', {
"url" => LJ::ehtml($u->{'url'}),
"name" => LJ::ehtml($u->{'urlname'} || "My Website"),
});
}
$lastn_page{'events'} = "";
$lastn_page{'head'} = "";
if (LJ::are_hooks('s2_head_content_extra')) {
$lastn_page{'head'} .= LJ::run_hook('s2_head_content_extra', $remote, $opts->{r});
}
# if user has requested, or a skip back link has been followed, don't index or follow
if ($u->{'opt_blockrobots'} || $get->{'skip'}) {
$lastn_page{'head'} .= LJ::robot_meta_tags()
}
if ($LJ::UNICODE) {
$lastn_page{'head'} .= '<meta http-equiv="Content-Type" content="text/html; charset='.$opts->{'saycharset'}."\" />\n";
}
# Automatic Discovery of RSS/Atom
$lastn_page{'head'} .= qq{<link rel="alternate" type="application/rss+xml" title="RSS" href="$journalbase/data/rss" />\n};
$lastn_page{'head'} .= qq{<link rel="alternate" type="application/atom+xml" title="Atom" href="$journalbase/data/atom" />\n};
$lastn_page{'head'} .= qq{<link rel="service.feed" type="application/atom+xml" title="AtomAPI-enabled feed" href="$LJ::SITEROOT/interface/atom/feed" />\n};
$lastn_page{'head'} .= qq{<link rel="service.post" type="application/atom+xml" title="Create a new post" href="$LJ::SITEROOT/interface/atom/post" />\n};
$lastn_page{'head'} .= qq{<link rel="openid.server" href="$LJ::OPENID_SERVER" />\n}
if LJ::OpenID::server_enabled();
# FOAF autodiscovery
my $real_user = 1;
foreach (@LJ::PROTECTED_USERNAMES) {
$real_user = 0 if ($u->{'user'} =~ $_);
}
if ($real_user) {
my $foafurl = $u->{external_foaf_url} ? LJ::eurl($u->{external_foaf_url}) : "$journalbase/data/foaf";
my $digest = Digest::SHA1::sha1_hex('mailto:' . $u->{email});
$lastn_page{head} .= qq{<link rel="meta" type="application/rdf+xml" title="FOAF" href="$foafurl" />\n};
$lastn_page{head} .= qq{<meta name="foaf:maker" content="foaf:mbox_sha1sum '$digest'" />\n};
}
$lastn_page{'head'} .=
$vars->{'GLOBAL_HEAD'} . "\n" . $vars->{'LASTN_HEAD'};
my $events = \$lastn_page{'events'};
# to show
my $itemshow = $vars->{'LASTN_OPT_ITEMS'} + 0;
if ($itemshow < 1) { $itemshow = 20; }
if ($itemshow > 50) { $itemshow = 50; }
my $skip = $get->{'skip'}+0;
my $maxskip = $LJ::MAX_HINTS_LASTN-$itemshow;
if ($skip < 0) { $skip = 0; }
if ($skip > $maxskip) { $skip = $maxskip; }
my $dayskip = $get->{'dayskip'}+0;
# do they have the viewall priv?
my $viewall = 0;
my $viewsome = 0;
if ($get->{'viewall'} && LJ::check_priv($remote, "canview")) {
LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
"viewall", "lastn: $user, statusvis: $u->{'statusvis'}");
$viewall = LJ::check_priv($remote, 'canview', '*');
$viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended');
}
## load the items
my $err;
my @items = LJ::get_recent_items({
'u' => $u,
'clustersource' => 'slave',
'viewall' => $viewall,
'remote' => $remote,
'itemshow' => $itemshow,
'skip' => $skip,
'dayskip' => $dayskip,
'tags' => $opts->{tags},
# 'order' => ($u->{'journaltype'} eq "C" || $u->{'journaltype'} eq "Y") # community or syndicated
# ? "logtime" : "",
'order' => ($u->{'journaltype'} eq "Y") # community or syndicated
? "logtime" : "",
'err' => \$err,
});
if ($err) {
$opts->{'errcode'} = $err;
$$ret = "";
return 0;
}
my $lastday = -1;
my $lastmonth = -1;
my $lastyear = -1;
my $eventnum = 0;
my %posteru = (); # map posterids to u objects
LJ::load_userids_multiple([map { $_->{'posterid'}, \$posteru{$_->{'posterid'}} }
@items], [$u]);
# pre load things in a batch (like userpics) to minimize db calls
my @userpic_load;
push @userpic_load, [ $u, $u->{'defaultpicid'} ] if $u->{'defaultpicid'};
foreach my $item (@items) {
next if $item->{'posterid'} == $u->{'userid'};
my $itemid = $item->{'itemid'};
my $pu = $posteru{$item->{'posterid'}};
my $picid = LJ::get_picid_from_keyword($pu, $item->{'props'}->{'picture_keyword'});
$item->{'_picid'} = $picid;
push @userpic_load, [ $pu, $picid ] if ($picid && ! grep { $_ eq $picid } @userpic_load);
}
my %userpics;
LJ::load_userpics(\%userpics, \@userpic_load);
if (my $picid = $u->{'defaultpicid'}) {
$lastn_page{'userpic'} =
LJ::fill_var_props($vars, 'LASTN_USERPIC', {
"src" => "$LJ::USERPIC_ROOT/$picid/$u->{'userid'}",
"width" => $userpics{$picid}->{'width'},
"height" => $userpics{$picid}->{'height'},
});
}
# spit out the S1
my $can_manage = LJ::can_manage($remote, $u);
ENTRY:
foreach my $item (@items)
{
my ($posterid, $itemid, $security, $alldatepart) =
map { $item->{$_} } qw(posterid itemid security alldatepart);
my $pu = $posteru{$posterid};
next ENTRY if $pu && $pu->{'statusvis'} eq 'S' && !$viewsome;
my $props = $item->{'props'};
my $replycount = $props->{'replycount'};
my $subject = $item->{'text'}->[0];
my $event = $item->{'text'}->[1];
if ($get->{'nohtml'}) {
# quote all non-LJ tags
$subject =~ s{<(?!/?lj)(.*?)>} {&lt;$1&gt;}gi;
$event =~ s{<(?!/?lj)(.*?)>} {&lt;$1&gt;}gi;
}
my %lastn_date_format = LJ::alldateparts_to_hash($alldatepart);
if ($lastday != $lastn_date_format{'d'} ||
$lastmonth != $lastn_date_format{'m'} ||
$lastyear != $lastn_date_format{'yyyy'})
{
my %lastn_new_day = ();
foreach (qw(dayshort daylong monshort monlong m mm yy yyyy d dd dth))
{
$lastn_new_day{$_} = $lastn_date_format{$_};
}
unless ($lastday==-1) {
$$events .= LJ::fill_var_props($vars, 'LASTN_END_DAY', {});
}
$$events .= LJ::fill_var_props($vars, 'LASTN_NEW_DAY', \%lastn_new_day);
$lastday = $lastn_date_format{'d'};
$lastmonth = $lastn_date_format{'m'};
$lastyear = $lastn_date_format{'yyyy'};
}
my %lastn_event = ();
$eventnum++;
$lastn_event{'eventnum'} = $eventnum;
$lastn_event{'itemid'} = $itemid;
$lastn_event{'datetime'} = LJ::fill_var_props($vars, 'LASTN_DATE_FORMAT', \%lastn_date_format);
if ($subject ne "") {
LJ::CleanHTML::clean_subject(\$subject);
$lastn_event{'subject'} = LJ::fill_var_props($vars, 'LASTN_SUBJECT', {
"subject" => $subject,
});
}
my $ditemid = $itemid * 256 + $item->{'anum'};
my $itemargs = "journal=$user&amp;itemid=$ditemid";
$lastn_event{'itemargs'} = $itemargs;
LJ::CleanHTML::clean_event(\$event, { 'preformatted' => $props->{'opt_preformatted'},
'cuturl' => LJ::item_link($u, $itemid, $item->{'anum'}),
'ljcut_disable' => $remote->{'opt_ljcut_disable_lastn'}, });
LJ::expand_embedded($u, $ditemid, $remote, \$event);
$lastn_event{'event'} = $event;
if ($u->{'opt_showtalklinks'} eq "Y" &&
! $props->{'opt_nocomments'} || $props->{'syn_link'} ne "")
{
my $nc;
$nc = "nc=$replycount" if $replycount; # && $remote && $remote->{'opt_nctalklinks'};
my $permalink = "$journalbase/$ditemid.html";
my $posturl = LJ::Talk::talkargs($permalink, "mode=reply");
my $readurl = LJ::Talk::talkargs($permalink, $nc);
my $dispreadlink = $replycount ||
($can_manage && $props->{'hasscreened'});
if ($ljr && $props->{'syn_link'}) {
my $rs = LJR::Distributed::match_remote_server($props->{'syn_link'});
if ($rs->{"servertype"} eq "lj") {
$posturl = $props->{'syn_link'} . "?mode=reply";
$dispreadlink = 1;
$readurl = $props->{'syn_link'};
$replycount = 'Read';
}
else {
$posturl = $props->{'syn_link'};
$dispreadlink = 0;
$replycount = undef;
}
}
$lastn_event{'talklinks'} = LJ::fill_var_props($vars, 'LASTN_TALK_LINKS', {
'itemid' => $ditemid,
'itemargs' => $itemargs,
'urlpost' => $posturl,
'urlread' => $readurl,
'messagecount' => $replycount,
'readlink' => $dispreadlink ? LJ::fill_var_props($vars, 'LASTN_TALK_READLINK', {
'urlread' => $readurl,
'messagecount' => $replycount,
'mc-plural-s' => $replycount == 1 ? "" : "s",
'mc-plural-es' => $replycount == 1 ? "" : "es",
'mc-plural-ies' => $replycount == 1 ? "y" : "ies",
}) : "",
});
}
## current stuff
LJ::prepare_currents({
'props' => $props,
'vars' => $vars,
'prefix' => "LASTN",
'event' => \%lastn_event,
'user' => $u,
});
if ($u->{'userid'} != $posterid)
{
my %lastn_altposter = ();
my $poster = $pu->{'user'};
$lastn_altposter{'poster'} = $poster;
$lastn_altposter{'owner'} = $user;
if (my $picid = $item->{'_picid'}) {
my $pic = $userpics{$picid};
$lastn_altposter{'pic'} = LJ::fill_var_props($vars, 'LASTN_ALTPOSTER_PIC', {
"src" => "$LJ::USERPIC_ROOT/$picid/$pic->{'userid'}",
"width" => $pic->{'width'},
"height" => $pic->{'height'},
});
}
$lastn_event{'altposter'} =
LJ::fill_var_props($vars, 'LASTN_ALTPOSTER', \%lastn_altposter);
}
my $var = 'LASTN_EVENT';
if ($security eq "private" &&
$vars->{'LASTN_EVENT_PRIVATE'}) { $var = 'LASTN_EVENT_PRIVATE'; }
if ($security eq "usemask" &&
$vars->{'LASTN_EVENT_PROTECTED'}) { $var = 'LASTN_EVENT_PROTECTED'; }
$$events .= LJ::fill_var_props($vars, $var, \%lastn_event);
} # end huge while loop
$$events .= LJ::fill_var_props($vars, 'LASTN_END_DAY', {});
my $item_shown = $eventnum;
my $item_total = @items;
my $item_hidden = $item_total - $item_shown;
if ($skip) {
$lastn_page{'range'} =
LJ::fill_var_props($vars, 'LASTN_RANGE_HISTORY', {
"numitems" => $item_shown,
"skip" => $skip,
});
} else {
$lastn_page{'range'} =
LJ::fill_var_props($vars, 'LASTN_RANGE_MOSTRECENT', {
"numitems" => $item_shown,
});
}
#### make the skip links
my ($skip_f, $skip_b) = (0, 0);
my %skiplinks;
### if we've skipped down, then we can skip back up
if ($skip) {
$skip_f = 1;
my $newskip = $skip - $itemshow;
$newskip = 0 if $newskip <= 0;
my $url = LJ::make_link("$journalbase/", { skip => ($newskip || ""), dayskip => ($dayskip || "") });
$skiplinks{'skipforward'} =
LJ::fill_var_props($vars, 'LASTN_SKIP_FORWARD', {
"numitems" => $itemshow,
"url" => $url,
});
}
## unless we didn't even load as many as we were expecting on this
## page, then there are more (unless there are exactly the number shown
## on the page, but who cares about that)
unless ($item_total != $itemshow) {
$skip_b = 1;
if ($skip==$maxskip) {
$skiplinks{'skipbackward'} =
LJ::fill_var_props($vars, 'LASTN_SKIP_BACKWARD', {
"numitems" => "Day",
"url" => "$journalbase/" . sprintf("%04d/%02d/%02d/", $lastyear, $lastmonth, $lastday),
});
} else {
my $newskip = $skip + $itemshow;
my $url = LJ::make_link("$journalbase/", { skip => ($newskip || ""), dayskip => ($dayskip || "") });
$skiplinks{'skipbackward'} =
LJ::fill_var_props($vars, 'LASTN_SKIP_BACKWARD', {
"numitems" => $itemshow,
"url" => $url,
});
}
}
### if they're both on, show a spacer
if ($skip_b && $skip_f) {
$skiplinks{'skipspacer'} = $vars->{'LASTN_SKIP_SPACER'};
}
### if either are on, put skiplinks into lastn_page
if ($skip_b || $skip_f) {
$lastn_page{'skiplinks'} =
LJ::fill_var_props($vars, 'LASTN_SKIP_LINKS', \%skiplinks);
}
$$ret = LJ::fill_var_props($vars, 'LASTN_PAGE', \%lastn_page);
return 1;
}
# the creator for the 'friends' view:
sub create_view_friends
{
my ($ret, $u, $vars, $remote, $opts) = @_;
my $sth;
my $user = $u->{'user'};
# see how often the remote user can reload this page.
# "friendsviewupdate" time determines what granularity time
# increments by for checking for new updates
my $nowtime = time();
# update delay specified by "friendsviewupdate"
my $newinterval = LJ::get_cap_min($remote, "friendsviewupdate") || 1;
# when are we going to say page was last modified? back up to the
# most recent time in the past where $time % $interval == 0
my $lastmod = $nowtime;
$lastmod -= $lastmod % $newinterval;
# see if they have a previously cached copy of this page they
# might be able to still use.
my $ims = $opts->{'r'}->header_in('If-Modified-Since');
if ($ims) {
my $theirtime = LJ::http_to_time($ims);
# send back a 304 Not Modified if they say they've reloaded this
# document in the last $newinterval seconds:
unless ($theirtime < $lastmod) {
$opts->{'handler_return'} = 304;
return 1;
}
}
$opts->{'r'}->header_out('Last-Modified', LJ::time_to_http($lastmod));
$$ret = "";
my $get = $opts->{'getargs'};
my $journalbase = LJ::journal_base($user, $opts->{'vhost'});
if ($get->{'mode'} eq "live") {
$$ret .= "<html><head><title>${user}'s friends: live!</title></head>\n";
$$ret .= "<frameset rows=\"100%,0%\" border=0>\n";
$$ret .= " <frame name=livetop src=\"$journalbase/friends?mode=framed\">\n";
$$ret .= " <frame name=livebottom src=\"$journalbase/friends?mode=livecond&amp;lastitemid=0\">\n";
$$ret .= "</frameset></html>\n";
return 1;
}
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'}) . "/friends";
return 1;
}
foreach ("name", "url", "urlname", "friendspagetitle") { LJ::text_out(\$u->{$_}); }
my %friends_page = ();
$friends_page{'name'} = LJ::ehtml($u->{'name'});
$friends_page{'name-\'s'} = ($u->{'name'} =~ /s$/i) ? "'" : "'s";
$friends_page{'username'} = $user;
$friends_page{'title'} = LJ::ehtml($u->{'friendspagetitle'} ||
$u->{'name'} . $friends_page{'name-\'s'} . " Friends");
$friends_page{'numitems'} = $vars->{'FRIENDS_OPT_ITEMS'} || 20;
$friends_page{'head'} = "";
if (LJ::are_hooks('s2_head_content_extra')) {
$friends_page{'head'} .= LJ::run_hook('s2_head_content_extra', $remote, $opts->{r});
}
## never have spiders index friends pages (change too much, and some
## people might not want to be indexed)
$friends_page{'head'} .= LJ::robot_meta_tags();
if ($LJ::UNICODE) {
$friends_page{'head'} .= '<meta http-equiv="Content-Type" content="text/html; charset='.$opts->{'saycharset'}.'" />';
}
$friends_page{'head'} .=
$vars->{'GLOBAL_HEAD'} . "\n" . $vars->{'FRIENDS_HEAD'};
if ($u->{'url'} =~ m!^https?://!) {
$friends_page{'website'} =
LJ::fill_var_props($vars, 'FRIENDS_WEBSITE', {
"url" => LJ::ehtml($u->{'url'}),
"name" => LJ::ehtml($u->{'urlname'} || "My Website"),
});
}
$friends_page{'urlcalendar'} = "$journalbase/calendar";
$friends_page{'urllastn'} = "$journalbase/";
$friends_page{'events'} = "";
my $itemshow = $vars->{'FRIENDS_OPT_ITEMS'} + 0;
if ($itemshow < 1) { $itemshow = 20; }
if ($itemshow > 50) { $itemshow = 50; }
my $skip = $get->{'skip'}+0;
my $maxskip = ($LJ::MAX_SCROLLBACK_FRIENDS || 1000) - $itemshow;
if ($skip > $maxskip) { $skip = $maxskip; }
if ($skip < 0) { $skip = 0; }
my $filter;
my $group;
my $common_filter = 1;
if (defined $get->{'filter'} && $remote && $remote->{'user'} eq $user) {
$filter = $get->{'filter'};
$common_filter = 0;
} else {
if ($opts->{'pathextra'}) {
$group = $opts->{'pathextra'};
$group =~ s!^/!!;
$group =~ s!/$!!;
if ($group) { $group = LJ::durl($group); $common_filter = 0;}
}
my $grp = LJ::get_friend_group($u, { 'name' => $group || "Default View" });
my $bit = $grp ? $grp->{'groupnum'} : 0;
my $public = $grp ? $grp->{'is_public'} : 0;
if ($bit && ($public || ($remote && $remote->{'user'} eq $user))) {
$filter = (1 << $bit);
} elsif ($group) {
$opts->{'badfriendgroup'} = 1;
return 1;
}
}
if ($get->{'mode'} eq "livecond")
{
## load the items
my @items = LJ::get_friend_items({
'u' => $u,
'remote' => $remote,
'itemshow' => 1,
'skip' => 0,
'filter' => $filter,
'common_filter' => $common_filter,
});
my $first = @items ? $items[0]->{'itemid'} : 0;
$$ret .= "time = " . scalar(time()) . "<br />";
$opts->{'headers'}->{'Refresh'} = "30;URL=$LJ::SITEROOT/users/$user/friends?mode=livecond&lastitemid=$first";
if ($get->{'lastitemid'} == $first) {
$$ret .= "nothing new!";
} else {
if ($get->{'lastitemid'}) {
$$ret .= "<b>New stuff!</b>\n";
$$ret .= "<script language=\"JavaScript\">\n";
$$ret .= "window.parent.livetop.location.reload(true);\n";
$$ret .= "</script>\n";
$opts->{'trusted_html'} = 1;
} else {
$$ret .= "Friends Live! started.";
}
}
return 1;
}
## load the items
my %friends;
my %friends_row;
my @items = LJ::get_friend_items({
'u' => $u,
'remote' => $remote,
'itemshow' => $itemshow,
'skip' => $skip,
'dayskip' => $get->{'dayskip'},
'filter' => $filter,
'common_filter' => $common_filter,
'friends_u' => \%friends,
'friends' => \%friends_row,
'showtypes' => $get->{'show'},
'friendsoffriends' => $opts->{'view'} eq "friendsfriends",
});
while ($_ = each %friends) {
# we expect fgcolor/bgcolor to be in here later
$friends{$_}->{'fgcolor'} = $friends_row{$_}->{'fgcolor'} || '#000000';
$friends{$_}->{'bgcolor'} = $friends_row{$_}->{'bgcolor'} || '#ffffff';
}
unless (%friends)
{
$friends_page{'events'} = LJ::fill_var_props($vars, 'FRIENDS_NOFRIENDS', {
"name" => LJ::ehtml($u->{'name'}),
"name-\'s" => ($u->{'name'} =~ /s$/i) ? "'" : "'s",
"username" => $user,
});
$$ret .= "<base target='_top'>" if ($get->{'mode'} eq "framed");
$$ret .= LJ::fill_var_props($vars, 'FRIENDS_PAGE', \%friends_page);
return 1;
}
my %aposter; # alt-posterid -> u object (if not in friends already)
LJ::load_userids_multiple([map { $_->{'posterid'}, \$aposter{$_->{'posterid'}} }
grep { $friends{$_->{'ownerid'}} &&
! $friends{$_->{'posterid'}} } @items],
[ $u, $remote ]);
# load the pictures for the user
my %userpics;
my @picids = map { [$friends{$_}, $friends{$_}->{'defaultpicid'}] } keys %friends;
LJ::load_userpics(\%userpics, [ @picids, map { [ $_, $_->{'defaultpicid'} ] } values %aposter ]);
# load 'opt_stylemine' prop for $remote. don't need to load opt_nctalklinks
# because that was already faked in LJ::make_journal previously
LJ::load_user_props($remote, "opt_stylemine", "opt_imagelinks", "opt_ljcut_disable_friends");
# load options for image links
my ($maximgwidth, $maximgheight) = (undef, undef);
($maximgwidth, $maximgheight) = ($1, $2)
if ($remote && $remote->{'userid'} == $u->{'userid'} &&
$remote->{'opt_imagelinks'} =~ m/^(\d+)\|(\d+)$/);
my %friends_events = ();
my $events = \$friends_events{'events'};
my $lastday = -1;
my $eventnum = 0;
ENTRY:
foreach my $item (@items)
{
my ($friendid, $posterid, $itemid, $security, $alldatepart) =
map { $item->{$_} } qw(ownerid posterid itemid security alldatepart);
my $pu = $friends{$posterid} || $aposter{$posterid};
next ENTRY if $pu && $pu->{'statusvis'} eq 'S';
# counting excludes skipped entries
$eventnum++;
my $clusterid = $item->{'clusterid'}+0;
my $props = $item->{'props'};
my $replycount = $props->{'replycount'};
my $subject = $item->{'text'}->[0];
my $event = $item->{'text'}->[1];
if ($get->{'nohtml'}) {
# quote all non-LJ tags
$subject =~ s{<(?!/?lj)(.*?)>} {&lt;$1&gt;}gi;
$event =~ s{<(?!/?lj)(.*?)>} {&lt;$1&gt;}gi;
}
my ($fru, $friend, $poster);
$fru = $friends{$friendid};
$friend = $poster = $fru->{'user'};
$poster = $pu->{'user'};
my %friends_date_format = LJ::alldateparts_to_hash($alldatepart);
if ($lastday != $friends_date_format{'d'})
{
my %friends_new_day = ();
foreach (qw(dayshort daylong monshort monlong m mm yy yyyy d dd dth))
{
$friends_new_day{$_} = $friends_date_format{$_};
}
unless ($lastday==-1) {
$$events .= LJ::fill_var_props($vars, 'FRIENDS_END_DAY', {});
}
$$events .= LJ::fill_var_props($vars, 'FRIENDS_NEW_DAY', \%friends_new_day);
$lastday = $friends_date_format{'d'};
}
my %friends_event = ();
$friends_event{'itemid'} = $itemid;
$friends_event{'datetime'} = LJ::fill_var_props($vars, 'FRIENDS_DATE_FORMAT', \%friends_date_format);
if ($subject ne "") {
LJ::CleanHTML::clean_subject(\$subject);
$friends_event{'subject'} = LJ::fill_var_props($vars, 'FRIENDS_SUBJECT', {
"subject" => $subject,
});
} else {
$friends_event{'subject'} = LJ::fill_var_props($vars, 'FRIENDS_NO_SUBJECT', {
"friend" => $friend,
"name" => $fru->{'name'},
});
}
my $ditemid = $itemid * 256 + $item->{'anum'};
my $itemargs = "journal=$friend&amp;itemid=$ditemid";
$friends_event{'itemargs'} = $itemargs;
my $stylemine = "";
$stylemine .= "style=mine" if $remote && $remote->{'opt_stylemine'} &&
$remote->{'userid'} != $friendid;
LJ::CleanHTML::clean_event(\$event, { 'preformatted' => $props->{'opt_preformatted'},
'cuturl' => LJ::item_link($fru, $itemid, $item->{'anum'}, $stylemine),
'maximgwidth' => $maximgwidth,
'maximgheight' => $maximgheight,
'ljcut_disable' => $remote->{'opt_ljcut_disable_friends'}, });
LJ::expand_embedded($fru, $ditemid, $remote, \$event);
$friends_event{'event'} = $event;
# do the picture
{
my $picid = $fru->{'defaultpicid'}; # this could be the shared journal pic
my $picuserid = $friendid;
if ($friendid != $posterid && ! $u->{'opt_usesharedpic'}) {
if ($pu->{'defaultpicid'}) {
$picid = $pu->{'defaultpicid'};
$picuserid = $posterid;
}
}
if ($props->{'picture_keyword'} &&
(! $u->{'opt_usesharedpic'} || ($posterid == $friendid)))
{
my $alt_picid = LJ::get_picid_from_keyword($posterid, $props->{'picture_keyword'});
if ($alt_picid) {
LJ::load_userpics(\%userpics, [ $pu, $alt_picid ]);
$picid = $alt_picid;
$picuserid = $posterid;
}
}
if ($picid) {
$friends_event{'friendpic'} =
LJ::fill_var_props($vars, 'FRIENDS_FRIENDPIC', {
"src" => "$LJ::USERPIC_ROOT/$picid/$picuserid",
"width" => $userpics{$picid}->{'width'},
"height" => $userpics{$picid}->{'height'},
});
}
}
if ($friend ne $poster) {
$friends_event{'altposter'} =
LJ::fill_var_props($vars, 'FRIENDS_ALTPOSTER', {
"poster" => $poster,
"owner" => $friend,
"fgcolor" => $fru->{'fgcolor'} || "#000000",
"bgcolor" => $fru->{'bgcolor'} || "#ffffff",
});
}
# friends view specific:
$friends_event{'user'} = $friend;
$friends_event{'fgcolor'} = $fru->{'fgcolor'} || "#000000";
$friends_event{'bgcolor'} = $fru->{'bgcolor'} || "#ffffff";
if ($fru->{'opt_showtalklinks'} eq "Y" &&
! $props->{'opt_nocomments'} || $props->{'syn_link'} ne "")
{
my $dispreadlink = $replycount ||
($remote && LJ::can_manage($remote, $fru) && $props->{'hasscreened'});
my $journalbase = LJ::journal_base($fru);
my $nc = "";
$nc .= "nc=$replycount" if $replycount; # && $remote && $remote->{'opt_nctalklinks'};
my $permalink = "$journalbase/$ditemid.html";
my $readurl = LJ::Talk::talkargs($permalink, $nc, $stylemine);
my $posturl = LJ::Talk::talkargs($permalink, "mode=reply", $stylemine);
if ($ljr && $props->{'syn_link'}) {
my $rs = LJR::Distributed::match_remote_server($props->{'syn_link'});
if ($rs->{"servertype"} eq "lj") {
$posturl = $props->{'syn_link'} . "?mode=reply";
$dispreadlink = 1;
$readurl = $props->{'syn_link'};
$replycount = 'Read';
}
else {
$posturl = $props->{'syn_link'};
$dispreadlink = 0;
$replycount = undef;
}
}
$friends_event{'talklinks'} = LJ::fill_var_props($vars, 'FRIENDS_TALK_LINKS', {
'itemid' => $ditemid,
'itemargs' => $itemargs,
'urlpost' => $posturl,
'urlread' => $readurl,
'messagecount' => $replycount,
'readlink' => $dispreadlink ? LJ::fill_var_props($vars, 'FRIENDS_TALK_READLINK', {
'urlread' => $readurl,
'messagecount' => $replycount,
'mc-plural-s' => $replycount == 1 ? "" : "s",
'mc-plural-es' => $replycount == 1 ? "" : "es",
'mc-plural-ies' => $replycount == 1 ? "y" : "ies",
}) : "",
});
}
## current stuff
LJ::prepare_currents({
'props' => $props,
'vars' => $vars,
'prefix' => "FRIENDS",
'event' => \%friends_event,
'user' => ($u->{'opt_forcemoodtheme'} eq "Y" ? $u :
$fru),
});
my $var = 'FRIENDS_EVENT';
if ($security eq "private" &&
$vars->{'FRIENDS_EVENT_PRIVATE'}) { $var = 'FRIENDS_EVENT_PRIVATE'; }
if ($security eq "usemask" &&
$vars->{'FRIENDS_EVENT_PROTECTED'}) { $var = 'FRIENDS_EVENT_PROTECTED'; }
$$events .= LJ::fill_var_props($vars, $var, \%friends_event);
} # end while
$$events .= LJ::fill_var_props($vars, 'FRIENDS_END_DAY', {});
$friends_page{'events'} = LJ::fill_var_props($vars, 'FRIENDS_EVENTS', \%friends_events);
my $item_shown = $eventnum;
my $item_total = @items;
my $item_hidden = $item_total - $item_shown;
### set the range property (what entries are we looking at)
if ($skip) {
$friends_page{'range'} =
LJ::fill_var_props($vars, 'FRIENDS_RANGE_HISTORY', {
"numitems" => $item_shown,
"skip" => $skip,
});
} else {
$friends_page{'range'} =
LJ::fill_var_props($vars, 'FRIENDS_RANGE_MOSTRECENT', {
"numitems" => $item_shown,
});
}
my ($skip_f, $skip_b) = (0, 0);
my %skiplinks;
my $base = "$journalbase/$opts->{'view'}";
if ($group) {
$base .= "/" . LJ::eurl($group);
}
# $linkfilter is distinct from $filter: if user has a default view,
# $filter is now set according to it but we don't want it to show in the links.
# $incfilter may be true even if $filter is 0: user may use filter=0 to turn
# off the default group
my $linkfilter = $get->{'filter'} + 0;
my $incfilter = defined $get->{'filter'};
# if we've skipped down, then we can skip back up
if ($skip) {
$skip_f = 1;
my %linkvars;
$linkvars{'filter'} = $linkfilter if $incfilter;
$linkvars{'show'} = $get->{'show'} if $get->{'show'} =~ /^\w+$/;
my $newskip = $skip - $itemshow;
if ($newskip > 0) { $linkvars{'skip'} = $newskip; }
$linkvars{'dayskip'} = $get->{'dayskip'} if $get->{'dayskip'};
$skiplinks{'skipforward'} =
LJ::fill_var_props($vars, 'FRIENDS_SKIP_FORWARD', {
"numitems" => $itemshow,
"url" => LJ::make_link($base, \%linkvars),
});
}
## unless we didn't even load as many as we were expecting on this
## page, then there are more (unless there are exactly the number shown
## on the page, but who cares about that)
unless ($item_total != $itemshow || $skip == $maxskip) {
$skip_b = 1;
my %linkvars;
$linkvars{'filter'} = $linkfilter if $incfilter;
$linkvars{'show'} = $get->{'show'} if $get->{'show'} =~ /^\w+$/;
my $newskip = $skip + $itemshow;
$linkvars{'skip'} = $newskip;
$linkvars{'dayskip'} = $get->{'dayskip'} if $get->{'dayskip'};
$skiplinks{'skipbackward'} =
LJ::fill_var_props($vars, 'FRIENDS_SKIP_BACKWARD', {
"numitems" => $itemshow,
"url" => LJ::make_link($base, \%linkvars),
});
}
### if they're both on, show a spacer
if ($skip_f && $skip_b) {
$skiplinks{'skipspacer'} = $vars->{'FRIENDS_SKIP_SPACER'};
}
### if either are on, put skiplinks into lastn_page
if ($skip_b || $skip_f) {
$friends_page{'skiplinks'} =
LJ::fill_var_props($vars, 'FRIENDS_SKIP_LINKS', \%skiplinks);
}
$$ret .= "<base target='_top' />" if ($get->{'mode'} eq "framed");
$$ret .= LJ::fill_var_props($vars, 'FRIENDS_PAGE', \%friends_page);
return 1;
}
# the creator for the 'calendar' view:
sub create_view_calendar
{
my ($ret, $u, $vars, $remote, $opts) = @_;
my $user = $u->{'user'};
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'}) .
"/calendar" . $opts->{'pathextra'};
return 1;
}
foreach ("name", "url", "urlname", "journaltitle") { LJ::text_out(\$u->{$_}); }
my $get = $opts->{'getargs'};
my %calendar_page = ();
$calendar_page{'name'} = LJ::ehtml($u->{'name'});
$calendar_page{'name-\'s'} = ($u->{'name'} =~ /s$/i) ? "'" : "'s";
$calendar_page{'username'} = $user;
$calendar_page{'title'} = LJ::ehtml($u->{'journaltitle'} ||
$u->{'name'} . $calendar_page{'name-\'s'} . " Journal");
$calendar_page{'head'} = "";
if (LJ::are_hooks('s2_head_content_extra')) {
$calendar_page{'head'} .= LJ::run_hook('s2_head_content_extra', $remote, $opts->{r});
}
if ($u->{'opt_blockrobots'}) {
$calendar_page{'head'} .= LJ::robot_meta_tags();
}
if ($LJ::UNICODE) {
$calendar_page{'head'} .= '<meta http-equiv="Content-Type" content="text/html; charset='.$opts->{'saycharset'}.'" />';
}
$calendar_page{'head'} .=
$vars->{'GLOBAL_HEAD'} . "\n" . $vars->{'CALENDAR_HEAD'};
$calendar_page{'months'} = "";
if ($u->{'url'} =~ m!^https?://!) {
$calendar_page{'website'} =
LJ::fill_var_props($vars, 'CALENDAR_WEBSITE', {
"url" => LJ::ehtml($u->{'url'}),
"name" => LJ::ehtml($u->{'urlname'} || "My Website"),
});
}
my $journalbase = LJ::journal_base($user, $opts->{'vhost'});
$calendar_page{'urlfriends'} = "$journalbase/friends";
$calendar_page{'urllastn'} = "$journalbase/";
my $months = \$calendar_page{'months'};
my $quserid = int($u->{'userid'});
my $maxyear = 0;
my $daycts = LJ::get_daycounts($u, $remote);
unless ($daycts) {
$opts->{'errcode'} = "nodb";
$$ret = "";
return 0;
}
my (%count, %dayweek);
foreach my $dy (@$daycts) {
my ($year, $month, $day, $count) = @$dy;
# calculate day of week
my $time = eval { Time::Local::timegm(0, 0, 0, $day, $month-1, $year) } ||
eval { Time::Local::timegm(0, 0, 0, LJ::days_in_month($month, $year), $month-1, $year) } ||
0;
next unless $time;
my $dayweek = (gmtime($time))[6] + 1;
$count{$year}->{$month}->{$day} = $count;
$dayweek{$year}->{$month}->{$day} = $dayweek;
if ($year > $maxyear) { $maxyear = $year; }
}
my @allyears = sort { $b <=> $a } keys %count;
if ($vars->{'CALENDAR_SORT_MODE'} eq "forward") { @allyears = reverse @allyears; }
my @years = ();
my $dispyear = $get->{'year'}; # old form was /users/<user>/calendar?year=1999
# but the new form is purtier: */calendar/2001
# but the NEWER form is purtier: */2001
unless ($dispyear) {
if ($opts->{'pathextra'} =~ m!^/(\d\d\d\d)/?\b!) {
$dispyear = $1;
}
}
# else... default to the year they last posted.
$dispyear ||= $maxyear;
# we used to show multiple years. now we only show one at a time: (hence the @years confusion)
if ($dispyear) { push @years, $dispyear; }
if (scalar(@allyears) > 1) {
my $yearlinks = "";
foreach my $year (@allyears) {
my $yy = sprintf("%02d", $year % 100);
my $url = "$journalbase/$year/";
if ($year != $dispyear) {
$yearlinks .= LJ::fill_var_props($vars, 'CALENDAR_YEAR_LINK', {
"url" => $url, "yyyy" => $year, "yy" => $yy });
} else {
$yearlinks .= LJ::fill_var_props($vars, 'CALENDAR_YEAR_DISPLAYED', {
"yyyy" => $year, "yy" => $yy });
}
}
$calendar_page{'yearlinks'} =
LJ::fill_var_props($vars, 'CALENDAR_YEAR_LINKS', { "years" => $yearlinks });
}
foreach my $year (@years)
{
$$months .= LJ::fill_var_props($vars, 'CALENDAR_NEW_YEAR', {
'yyyy' => $year,
'yy' => substr($year, 2, 2),
});
my @months = sort { $b <=> $a } keys %{$count{$year}};
if ($vars->{'CALENDAR_SORT_MODE'} eq "forward") { @months = reverse @months; }
foreach my $month (@months)
{
my $daysinmonth = LJ::days_in_month($month, $year);
# this picks a random day there were journal entries (thus, we know
# the %dayweek from above) from that we go backwards and forwards
# to find the rest of the days of week
my $firstday = (%{$count{$year}->{$month}})[0];
# go backwards from first day
my $dayweek = $dayweek{$year}->{$month}->{$firstday};
for (my $i=$firstday-1; $i>0; $i--)
{
if (--$dayweek < 1) { $dayweek = 7; }
$dayweek{$year}->{$month}->{$i} = $dayweek;
}
# go forwards from first day
$dayweek = $dayweek{$year}->{$month}->{$firstday};
for (my $i=$firstday+1; $i<=$daysinmonth; $i++)
{
if (++$dayweek > 7) { $dayweek = 1; }
$dayweek{$year}->{$month}->{$i} = $dayweek;
}
my %calendar_month = ();
$calendar_month{'monlong'} = LJ::Lang::month_long($month);
$calendar_month{'monshort'} = LJ::Lang::month_short($month);
$calendar_month{'yyyy'} = $year;
$calendar_month{'yy'} = substr($year, 2, 2);
$calendar_month{'weeks'} = "";
$calendar_month{'urlmonthview'} = sprintf("$journalbase/%04d/%02d/", $year, $month);
my $weeks = \$calendar_month{'weeks'};
my %calendar_week = ();
$calendar_week{'emptydays_beg'} = "";
$calendar_week{'emptydays_end'} = "";
$calendar_week{'days'} = "";
# start the first row and check for its empty spaces
my $rowopen = 1;
if ($dayweek{$year}->{$month}->{1} != 1)
{
my $spaces = $dayweek{$year}->{$month}->{1} - 1;
$calendar_week{'emptydays_beg'} =
LJ::fill_var_props($vars, 'CALENDAR_EMPTY_DAYS',
{ 'numempty' => $spaces });
}
# make the days!
my $days = \$calendar_week{'days'};
for (my $i=1; $i<=$daysinmonth; $i++)
{
$count{$year}->{$month}->{$i} += 0;
if (! $rowopen) { $rowopen = 1; }
my %calendar_day = ();
$calendar_day{'d'} = $i;
$calendar_day{'eventcount'} = $count{$year}->{$month}->{$i};
if ($count{$year}->{$month}->{$i})
{
$calendar_day{'dayevent'} = LJ::fill_var_props($vars, 'CALENDAR_DAY_EVENT', {
'eventcount' => $count{$year}->{$month}->{$i},
'dayurl' => "$journalbase/" . sprintf("%04d/%02d/%02d/", $year, $month, $i),
});
}
else
{
$calendar_day{'daynoevent'} = $vars->{'CALENDAR_DAY_NOEVENT'};
}
$$days .= LJ::fill_var_props($vars, 'CALENDAR_DAY', \%calendar_day);
if ($dayweek{$year}->{$month}->{$i} == 7)
{
$$weeks .= LJ::fill_var_props($vars, 'CALENDAR_WEEK', \%calendar_week);
$rowopen = 0;
$calendar_week{'emptydays_beg'} = "";
$calendar_week{'emptydays_end'} = "";
$calendar_week{'days'} = "";
}
}
# if rows is still open, we have empty spaces
if ($rowopen)
{
if ($dayweek{$year}->{$month}->{$daysinmonth} != 7)
{
my $spaces = 7 - $dayweek{$year}->{$month}->{$daysinmonth};
$calendar_week{'emptydays_end'} =
LJ::fill_var_props($vars, 'CALENDAR_EMPTY_DAYS',
{ 'numempty' => $spaces });
}
$$weeks .= LJ::fill_var_props($vars, 'CALENDAR_WEEK', \%calendar_week);
}
$$months .= LJ::fill_var_props($vars, 'CALENDAR_MONTH', \%calendar_month);
} # end foreach months
} # end foreach years
######## new code
$$ret .= LJ::fill_var_props($vars, 'CALENDAR_PAGE', \%calendar_page);
return 1;
}
# the creator for the 'day' view:
sub create_view_day
{
my ($ret, $u, $vars, $remote, $opts) = @_;
my $sth;
my $user = $u->{'user'};
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'}) .
"/day" . $opts->{'pathextra'};
return 1;
}
foreach ("name", "url", "urlname", "journaltitle") { LJ::text_out(\$u->{$_}); }
my %day_page = ();
$day_page{'username'} = $user;
$day_page{'head'} = "";
if (LJ::are_hooks('s2_head_content_extra')) {
$day_page{'head'} .= LJ::run_hook('s2_head_content_extra', $remote, $opts->{r});
}
if ($u->{'opt_blockrobots'}) {
$day_page{'head'} .= LJ::robot_meta_tags();
}
if ($LJ::UNICODE) {
$day_page{'head'} .= '<meta http-equiv="Content-Type" content="text/html; charset='.$opts->{'saycharset'}.'" />';
}
$day_page{'head'} .=
$vars->{'GLOBAL_HEAD'} . "\n" . $vars->{'DAY_HEAD'};
$day_page{'name'} = LJ::ehtml($u->{'name'});
$day_page{'name-\'s'} = ($u->{'name'} =~ /s$/i) ? "'" : "'s";
$day_page{'title'} = LJ::ehtml($u->{'journaltitle'} ||
$u->{'name'} . $day_page{'name-\'s'} . " Journal");
if ($u->{'url'} =~ m!^https?://!) {
$day_page{'website'} =
LJ::fill_var_props($vars, 'DAY_WEBSITE', {
"url" => LJ::ehtml($u->{'url'}),
"name" => LJ::ehtml($u->{'urlname'} || "My Website"),
});
}
my $journalbase = LJ::journal_base($user, $opts->{'vhost'});
$day_page{'urlfriends'} = "$journalbase/friends";
$day_page{'urlcalendar'} = "$journalbase/calendar";
$day_page{'urllastn'} = "$journalbase/";
my $initpagedates = 0;
my $get = $opts->{'getargs'};
my $month = $get->{'month'};
my $day = $get->{'day'};
my $year = $get->{'year'};
my @errors = ();
if ($opts->{'pathextra'} =~ m!^(?:/day)?/(\d\d\d\d)/(\d\d)/(\d\d)\b!) {
($month, $day, $year) = ($2, $3, $1);
}
if ($year !~ /^\d+$/) { push @errors, "Corrupt or non-existant year."; }
if ($month !~ /^\d+$/) { push @errors, "Corrupt or non-existant month."; }
if ($day !~ /^\d+$/) { push @errors, "Corrupt or non-existant day."; }
if ($month < 1 || $month > 12 || int($month) != $month) { push @errors, "Invalid month."; }
if ($year < 1970 || $year > 2038 || int($year) != $year) { push @errors, "Invalid year: $year"; }
if ($day < 1 || $day > 31 || int($day) != $day) { push @errors, "Invalid day."; }
if (scalar(@errors)==0 && $day > LJ::days_in_month($month, $year)) { push @errors, "That month doesn't have that many days."; }
if (@errors) {
$$ret .= "Errors occurred processing this page:\n<ul>\n";
foreach (@errors) {
$$ret .= "<li>$_</li>\n";
}
$$ret .= "</ul>\n";
return 0;
}
my $logdb = LJ::get_cluster_reader($u);
unless ($logdb) {
$opts->{'errcode'} = "nodb";
$$ret = "";
return 0;
}
my $optDESC = $vars->{'DAY_SORT_MODE'} eq "reverse" ? "DESC" : "";
my $secwhere = "AND security='public'";
my $viewall = 0;
my $viewsome = 0;
if ($remote) {
# do they have the viewall priv?
if ($get->{'viewall'} && LJ::check_priv($remote, "canview")) {
LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
"viewall", "day: $user, statusvis: $u->{'statusvis'}");
$viewall = LJ::check_priv($remote, 'canview', '*');
$viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended');
}
if ($remote->{'userid'} == $u->{'userid'} || $viewall) {
$secwhere = ""; # see everything
} elsif ($remote->{'journaltype'} eq 'P' || $remote->{'journaltype'} eq 'I') {
my $gmask = LJ::get_groupmask($u, $remote);
$secwhere = "AND (security='public' OR (security='usemask' AND allowmask & $gmask))"
if $gmask;
}
}
# load the log items
my $dateformat = "%a %W %b %M %y %Y %c %m %e %d %D %p %i %l %h %k %H";
$sth = $logdb->prepare("SELECT jitemid AS itemid, posterid, security, ".
" DATE_FORMAT(eventtime, \"$dateformat\") AS 'alldatepart', anum " .
"FROM log2 " .
"WHERE journalid=? AND year=? AND month=? AND day=? $secwhere " .
"ORDER BY eventtime $optDESC, logtime $optDESC LIMIT 200");
$sth->execute($u->{'userid'}, $year, $month, $day);
my @items;
push @items, $_ while $_ = $sth->fetchrow_hashref;
my @itemids = map { $_->{'itemid'} } @items;
# load 'opt_ljcut_disable_lastn' prop for $remote.
LJ::load_user_props($remote, "opt_ljcut_disable_lastn");
### load the log properties
LJ::fill_items_with_text_props(\@items, $u);
my %posteru = (); # map posterids to u objects
LJ::load_userids_multiple([map { $_->{'posterid'}, \$posteru{$_->{'posterid'}} } @items], [$u]);
my $events = "";
ENTRY:
foreach my $item (@items) {
my ($itemid, $posterid, $security, $alldatepart, $anum) =
map { $item->{$_} } qw(itemid posterid security alldatepart anum);
next ENTRY if $posteru{$posterid} && $posteru{$posterid}->{'statusvis'} eq 'S' && !$viewsome;
my $props = $item->{'props'};
my $replycount = $props->{'replycount'};
my $subject = $item->{'text'}->[0];
my $event = $item->{'text'}->[1];
my %day_date_format = LJ::alldateparts_to_hash($alldatepart);
unless ($initpagedates++) {
foreach (qw(dayshort daylong monshort monlong yy yyyy m mm d dd dth)) {
$day_page{$_} = $day_date_format{$_};
}
}
my %day_event = ();
$day_event{'itemid'} = $itemid;
$day_event{'datetime'} = LJ::fill_var_props($vars, 'DAY_DATE_FORMAT', \%day_date_format);
if ($subject ne "") {
LJ::CleanHTML::clean_subject(\$subject);
$day_event{'subject'} = LJ::fill_var_props($vars, 'DAY_SUBJECT', {
"subject" => $subject,
});
}
my $ditemid = $itemid*256 + $anum;
my $itemargs = "journal=$user&amp;itemid=$ditemid";
$day_event{'itemargs'} = $itemargs;
LJ::CleanHTML::clean_event(\$event, { 'preformatted' => $props->{'opt_preformatted'},
'cuturl' => LJ::item_link($u, $itemid, $anum),
'ljcut_disable' => $remote->{'opt_ljcut_disable_lastn'}, });
LJ::expand_embedded($u, $ditemid, $remote, \$event);
$day_event{'event'} = $event;
if ($u->{'opt_showtalklinks'} eq "Y" &&
! $props->{'opt_nocomments'} || $props->{'syn_link'} )
{
my $nc;
$nc = "nc=$replycount" if $replycount; # && $remote && $remote->{'opt_nctalklinks'};
my $permalink = "$journalbase/$ditemid.html";
my $posturl = LJ::Talk::talkargs($permalink, "mode=reply");
my $readurl = LJ::Talk::talkargs($permalink, $nc);
my $dispreadlink = $replycount ||
($props->{'hasscreened'} &&
($remote->{'user'} eq $user
|| LJ::can_manage($remote, $u)));
if ($ljr && $props->{'syn_link'}) {
my $rs = LJR::Distributed::match_remote_server($props->{'syn_link'});
if ($rs->{"servertype"} eq "lj") {
$posturl = $props->{'syn_link'} . "?mode=reply";
$dispreadlink = 1;
$readurl = $props->{'syn_link'};
$replycount = 'Read';
}
else {
$posturl = $props->{'syn_link'};
$dispreadlink = 0;
$replycount = undef;
}
}
$day_event{'talklinks'} = LJ::fill_var_props($vars, 'DAY_TALK_LINKS', {
'itemid' => $ditemid,
'itemargs' => $itemargs,
'urlpost' => $posturl,
'urlread' => $readurl,
'messagecount' => $replycount,
'readlink' => $dispreadlink ? LJ::fill_var_props($vars, 'DAY_TALK_READLINK', {
'urlread' => $readurl,
'messagecount' => $replycount,
'mc-plural-s' => $replycount == 1 ? "" : "s",
'mc-plural-es' => $replycount == 1 ? "" : "es",
'mc-plural-ies' => $replycount == 1 ? "y" : "ies",
}) : "",
});
}
## current stuff
LJ::prepare_currents({
'props' => $props,
'vars' => $vars,
'prefix' => "DAY",
'event' => \%day_event,
'user' => $u,
});
my $var = 'DAY_EVENT';
if ($security eq "private" &&
$vars->{'DAY_EVENT_PRIVATE'}) { $var = 'DAY_EVENT_PRIVATE'; }
if ($security eq "usemask" &&
$vars->{'DAY_EVENT_PROTECTED'}) { $var = 'DAY_EVENT_PROTECTED'; }
$events .= LJ::fill_var_props($vars, $var, \%day_event);
}
if (! $initpagedates)
{
# if no entries were on that day, we haven't populated the time shit!
# FIXME: don't use the database for this. it can be done in Perl.
my $dbr = LJ::get_db_reader();
$sth = $dbr->prepare("SELECT DATE_FORMAT('$year-$month-$day', '%a %W %b %M %y %Y %c %m %e %d %D') AS 'alldatepart'");
$sth->execute;
my @dateparts = split(/ /, $sth->fetchrow_arrayref->[0]);
foreach (qw(dayshort daylong monshort monlong yy yyyy m mm d dd dth))
{
$day_page{$_} = shift @dateparts;
}
$day_page{'events'} = LJ::fill_var_props($vars, 'DAY_NOEVENTS', {});
}
else
{
$day_page{'events'} = LJ::fill_var_props($vars, 'DAY_EVENTS', { 'events' => $events });
$events = ""; # free some memory maybe
}
# calculate previous day
my $pdyear = $year;
my $pdmonth = $month;
my $pdday = $day-1;
if ($pdday < 1)
{
if (--$pdmonth < 1)
{
$pdmonth = 12;
$pdyear--;
}
$pdday = LJ::days_in_month($pdmonth, $pdyear);
}
# calculate next day
my $nxyear = $year;
my $nxmonth = $month;
my $nxday = $day+1;
if ($nxday > LJ::days_in_month($nxmonth, $nxyear))
{
$nxday = 1;
if (++$nxmonth > 12) { ++$nxyear; $nxmonth=1; }
}
$day_page{'prevday_url'} = "$journalbase/" . sprintf("%04d/%02d/%02d/", $pdyear, $pdmonth, $pdday);
$day_page{'nextday_url'} = "$journalbase/" . sprintf("%04d/%02d/%02d/", $nxyear, $nxmonth, $nxday);
$$ret .= LJ::fill_var_props($vars, 'DAY_PAGE', \%day_page);
return 1;
}
1;