LJSUP-17669: Login.bml form refactoring
[livejournal.git] / cgi-bin / ljviews.pl
blobc8f234aa93da868ff722fd07170683923e657054
1 #!/usr/bin/perl
3 # <LJDEP>
4 # lib: cgi-bin/ljconfig.pl, cgi-bin/LJ/Lang.pm, cgi-bin/cleanhtml.pl
5 # </LJDEP>
7 use strict;
9 package LJ::S1;
11 use vars qw(@themecoltypes);
12 use Class::Autouse qw(LJ::LastFM);
13 use LJ::TimeUtil;
14 use LJ::Setting::Music;
15 use LJ::Entry::Repost;
17 # this used to be in a table, but that was kinda useless
18 @themecoltypes = (
19 [ 'page_back', 'Page background' ],
20 [ 'page_text', 'Page text' ],
21 [ 'page_link', 'Page link' ],
22 [ 'page_vlink', 'Page visited link' ],
23 [ 'page_alink', 'Page active link' ],
24 [ 'page_text_em', 'Page emphasized text' ],
25 [ 'page_text_title', 'Page title' ],
26 [ 'weak_back', 'Weak accent' ],
27 [ 'weak_text', 'Text on weak accent' ],
28 [ 'strong_back', 'Strong accent' ],
29 [ 'strong_text', 'Text on strong accent' ],
30 [ 'stronger_back', 'Stronger accent' ],
31 [ 'stronger_text', 'Text on stronger accent' ],
34 # updated everytime new S1 style cleaning rules are added,
35 # so cached cleaned versions are invalidated.
36 $LJ::S1::CLEANER_VERSION = 14;
38 # PROPERTY Flags:
40 # /a/:
41 # safe in styles as sole attributes, without any cleaning. for
42 # example: <a href="%%urlread%%"> is okay, # if we're in
43 # LASTN_TALK_READLINK, because the system generates # %%urlread%%.
44 # by default, if we don't declare things trusted here, # we'll
45 # double-check all attributes at the end for potential XSS #
46 # problems.
48 # /u/:
49 # is a URL. implies /a/.
52 # /d/:
53 # is a number. implies /a/.
55 # /t/:
56 # tainted! User controls via other some other variable.
58 # /s/:
59 # some system string... probably safe. but maybe possible to coerce it
60 # alongside something else.
62 my $commonprop = {
63 'dateformat' => {
64 'yy' => 'd', 'yyyy' => 'd',
65 'm' => 'd', 'mm' => 'd',
66 'd' => 'd', 'dd' => 'd',
67 'min' => 'd',
68 '12h' => 'd', '12hh' => 'd',
69 '24h' => 'd', '24hh' => 'd',
71 'talklinks' => {
72 'messagecount' => 'd',
73 'urlread' => 'u',
74 'urlpost' => 'u',
75 'itemid' => 'd',
77 'talkreadlink' => {
78 'messagecount' => 'd',
79 'urlread' => 'u',
81 'event' => {
82 'itemid' => 'd',
84 'pic' => {
85 'src' => 'u',
86 'width' => 'd',
87 'height' => 'd',
89 'newday' => {
90 yy => 'd', yyyy => 'd', m => 'd', mm => 'd',
91 d => 'd', dd => 'd',
93 'skip' => {
94 'numitems' => 'd',
95 'url' => 'u',
99 $LJ::S1::PROPS = {
100 'CALENDAR_DAY' => {
101 'd' => 'd',
102 'eventcount' => 'd',
103 'dayevent' => 't',
104 'daynoevent' => 't',
106 'CALENDAR_DAY_EVENT' => {
107 'eventcount' => 'd',
108 'dayurl' => 'u',
110 'CALENDAR_DAY_NOEVENT' => {
112 'CALENDAR_EMPTY_DAYS' => {
113 'numempty' => 'd',
115 'CALENDAR_MONTH' => {
116 'monlong' => 's',
117 'monshort' => 's',
118 'yy' => 'd',
119 'yyyy' => 'd',
120 'weeks' => 't',
121 'urlmonthview' => 'u',
123 'CALENDAR_NEW_YEAR' => {
124 'yy' => 'd',
125 'yyyy' => 'd',
127 'CALENDAR_PAGE' => {
128 'name' => 't',
129 "name-'s" => 's',
130 'yearlinks' => 't',
131 'months' => 't',
132 'username' => 's',
133 'website' => 't',
134 'head' => 't',
135 'urlfriends' => 'u',
136 'urllastn' => 'u',
138 'CALENDAR_WEBSITE' => {
139 'url' => 't',
140 'name' => 't',
142 'CALENDAR_WEEK' => {
143 'days' => 't',
144 'emptydays_beg' => 't',
145 'emptydays_end' => 't',
147 'CALENDAR_YEAR_DISPLAYED' => {
148 'yyyy' => 'd',
149 'yy' => 'd',
151 'CALENDAR_YEAR_LINK' => {
152 'yyyy' => 'd',
153 'yy' => 'd',
154 'url' => 'u',
156 'CALENDAR_YEAR_LINKS' => {
157 'years' => 't',
159 'CALENDAR_SKYSCRAPER_AD' => {
160 'ad' => 't',
162 'CALENDAR_5LINKUNIT_AD' => {
163 'ad' => 't',
166 # day
167 'DAY_DATE_FORMAT' => $commonprop->{'dateformat'},
168 'DAY_EVENT' => $commonprop->{'event'},
169 'DAY_EVENT_PRIVATE' => $commonprop->{'event'},
170 'DAY_EVENT_PROTECTED' => $commonprop->{'event'},
171 'DAY_PAGE' => {
172 'prevday_url' => 'u',
173 'nextday_url' => 'u',
174 'yy' => 'd', 'yyyy' => 'd',
175 'm' => 'd', 'mm' => 'd',
176 'd' => 'd', 'dd' => 'd',
177 'urllastn' => 'u',
178 'urlcalendar' => 'u',
179 'urlfriends' => 'u',
181 'DAY_TALK_LINKS' => $commonprop->{'talklinks'},
182 'DAY_TALK_READLINK' => $commonprop->{'talkreadlink'},
183 'DAY_SKYSCRAPER_AD' => {
184 'ad' => 't',
186 'DAY_5LINKUNIT_AD' => {
187 'ad' => 't',
190 # friends
191 'FRIENDS_DATE_FORMAT' => $commonprop->{'dateformat'},
192 'FRIENDS_EVENT' => $commonprop->{'event'},
193 'FRIENDS_EVENT_PRIVATE' => $commonprop->{'event'},
194 'FRIENDS_EVENT_PROTECTED' => $commonprop->{'event'},
195 'FRIENDS_FRIENDPIC' => $commonprop->{'pic'},
196 'FRIENDS_NEW_DAY' => $commonprop->{'newday'},
197 'FRIENDS_RANGE_HISTORY' => {
198 'numitems' => 'd',
199 'skip' => 'd',
201 'FRIENDS_RANGE_MOSTRECENT' => {
202 'numitems' => 'd',
204 'FRIENDS_SKIP_BACKWARD' => $commonprop->{'skip'},
205 'FRIENDS_SKIP_FORWARD' => $commonprop->{'skip'},
206 'FRIENDS_TALK_LINKS' => $commonprop->{'talklinks'},
207 'FRIENDS_TALK_READLINK' => $commonprop->{'talkreadlink'},
208 'FRIENDS_SKYSCRAPER_AD' => {
209 'ad' => 't',
211 'FRIENDS_5LINKUNIT_AD' => {
212 'ad' => 't',
215 # lastn
216 'LASTN_ALTPOSTER' => {
217 'poster' => 's',
218 'owner' => 's',
219 'pic' => 't',
221 'LASTN_ALTPOSTER_PIC' => $commonprop->{'pic'},
222 'LASTN_CURRENT' => {
223 'what' => 's',
224 'value' => 't',
226 'LASTN_CURRENTS' => {
227 'currents' => 't',
229 'LASTN_DATEFORMAT' => $commonprop->{'dateformat'},
230 'LASTN_EVENT' => $commonprop->{'event'},
231 'LASTN_EVENT_PRIVATE' => $commonprop->{'event'},
232 'LASTN_EVENT_PROTECTED' => $commonprop->{'event'},
233 'LASTN_NEW_DAY' => $commonprop->{'newday'},
234 'LASTN_PAGE' => {
235 'urlfriends' => 'u',
236 'urlcalendar' => 'u',
237 'skyscraper_ad' => 't',
239 'LASTN_RANGE_HISTORY' => {
240 'numitems' => 'd',
241 'skip' => 'd',
243 'LASTN_RANGE_MOSTRECENT' => {
244 'numitems' => 'd',
246 'LASTN_SKIP_BACKWARD' => $commonprop->{'skip'},
247 'LASTN_SKIP_FORWARD' => $commonprop->{'skip'},
248 'LASTN_TALK_LINKS' => $commonprop->{'talklinks'},
249 'LASTN_TALK_READLINK' => $commonprop->{'talkreadlink'},
250 'LASTN_USERPIC' => {
251 'src' => 'u',
252 'width' => 'd',
253 'height' => 'd',
255 'LASTN_SKYSCRAPER_AD' => {
256 'ad' => 't',
258 'LASTN_5LINKUNIT_AD' => {
259 'ad' => 't',
263 sub get_public_styles {
265 my $opts = shift;
267 # Try process cache/memcache if no extra options are requested
268 my $memkey = "s1pubstyc";
269 my $pubstyc;
271 unless ($opts) {
272 # check process cache
273 $pubstyc = $LJ::CACHED_S1_PUBLIC_LAYERS;
274 return $pubstyc if $pubstyc;
276 # check memcache, set in process cache if we got it
277 $pubstyc = LJ::MemCache::get($memkey);
278 $LJ::CACHED_S1_PUBLIC_LAYERS = $pubstyc;
279 return $pubstyc if $pubstyc;
281 $pubstyc = {};
283 # not cached, build from db
284 my $sysid = LJ::get_userid("system");
286 # all cols *except* formatdata, which is big and unnecessary for most uses.
287 # it'll be loaded by LJ::S1::get_style
288 my $cols = "styleid, styledes, type, is_public, is_embedded, ".
289 "is_colorfree, opt_cache, has_ads, lastupdate";
290 $cols .= ", formatdata" if $opts && $opts->{'formatdata'};
292 # first try new table
293 my $dbh = LJ::get_db_writer();
294 my $sth = $dbh->prepare("SELECT userid, $cols FROM s1style WHERE userid=? AND is_public='Y'");
295 $sth->execute($sysid);
296 while (my $row = $sth->fetchrow_hashref) {
297 $pubstyc->{$row->{'styleid'}} = $row;
300 # fall back to old table
301 unless (%$pubstyc) {
302 $sth = $dbh->prepare("SELECT user, $cols FROM style WHERE user='system' AND is_public='Y'");
303 $sth->execute();
304 while (my $row = $sth->fetchrow_hashref) {
305 $pubstyc->{$row->{'styleid'}} = $row;
308 return undef unless %$pubstyc;
310 # set in process cache/memcache
311 unless ($opts) {
312 $LJ::CACHED_S1_PUBLIC_LAYERS = $pubstyc;
314 my $expire = time() + 60*30; # 30 minutes
315 LJ::MemCache::set($memkey, $pubstyc, $expire);
318 return $pubstyc;
321 # <LJFUNC>
322 # name: LJ::S1::get_themeid
323 # des: Loads or returns cached version of given color theme data.
324 # returns: Hashref with color names as keys
325 # args: dbarg?, themeid
326 # des-themeid: S1 themeid.
327 # </LJFUNC>
328 sub get_themeid
330 &LJ::nodb;
331 my $themeid = shift;
332 return $LJ::S1::CACHE_THEMEID{$themeid} if $LJ::S1::CACHE_THEMEID{$themeid};
333 my $dbr = LJ::get_db_reader();
334 my $ret = {};
335 my $sth = $dbr->prepare("SELECT coltype, color FROM themedata WHERE themeid=?");
336 $sth->execute($themeid);
337 $ret->{$_->{'coltype'}} = $_->{'color'} while $_ = $sth->fetchrow_hashref;
338 return $LJ::S1::CACHE_THEMEID{$themeid} = $ret;
341 # returns: hashref of vars (cleaned)
342 sub load_style
344 &LJ::nodb;
345 my ($styleid, $viewref) = @_;
347 # first try local cache for this process
348 my $cch = $LJ::S1::CACHE_STYLE{$styleid};
349 if ($cch && $cch->{'cachetime'} > time() - 300) {
350 $$viewref = $cch->{'type'} if ref $viewref eq "SCALAR";
351 return $cch->{'style'};
354 # try memcache
355 my $memkey = [$styleid, "s1styc:$styleid"];
356 my $styc = LJ::MemCache::get($memkey);
358 # database handle we'll use if we have to rebuild the cache
359 my $db;
361 # function to return a given a styleid
362 my $find_db = sub {
363 my $sid = shift;
365 # should we work with a global or clustered table?
366 my $userid = LJ::S1::get_style_userid($sid);
368 # if the user's style is clustered, need to get a $u
369 my $u = $userid ? LJ::load_userid($userid) : undef;
371 # return appropriate db handle
372 if ($u && $u->{'dversion'} >= 5) { # users' styles are clustered
373 return LJ::S1::get_s1style_writer($u);
376 return @LJ::MEMCACHE_SERVERS ? LJ::get_db_writer() : LJ::get_db_reader();
379 # get database stylecache
380 unless ($styc) {
382 $db = $find_db->($styleid);
383 $styc = $db->selectrow_hashref("SELECT * FROM s1stylecache WHERE styleid=?",
384 undef, $styleid);
385 LJ::MemCache::set($memkey, $styc, time()+60*30) if $styc;
388 # no stylecache in db, built a new one
389 if (! $styc || $styc->{'vars_cleanver'} < $LJ::S1::CLEANER_VERSION) {
390 my $style = LJ::S1::get_style($styleid);
391 return {} unless $style;
393 $db ||= $find_db->($styleid);
395 $styc = {
396 'type' => $style->{'type'},
397 'opt_cache' => $style->{'opt_cache'},
398 'vars_stor' => LJ::CleanHTML::clean_s1_style($style->{'formatdata'}),
399 'vars_cleanver' => $LJ::S1::CLEANER_VERSION,
402 # do this query on the db handle we used above
403 $db->do("REPLACE INTO s1stylecache (styleid, cleandate, type, opt_cache, vars_stor, vars_cleanver) ".
404 "VALUES (?,NOW(),?,?,?,?)", undef, $styleid,
405 map { $styc->{$_} } qw(type opt_cache vars_stor vars_cleanver));
408 my $ret = eval { Storable::thaw($styc->{'vars_stor'}) };
409 warn "Deserialization error: $@" if $@;
410 $$viewref = $styc->{'type'} if ref $viewref eq "SCALAR";
412 if ($styc->{'opt_cache'} eq "Y") {
413 $LJ::S1::CACHE_STYLE{$styleid} = {
414 'style' => $ret,
415 'cachetime' => time(),
416 'type' => $styc->{'type'},
420 return $ret;
423 # LJ::S1::get_public_styles
425 # LJ::load_user_props calls LJ::S1::get_public_styles and since
426 # a lot of cron jobs call LJ::load_user_props, we've moved
427 # LJ::S1::get_public_styles to ljlib so that it can be used
428 # without including ljviews.pl
430 sub get_s1style_writer {
431 my $u = shift;
432 return undef unless LJ::isu($u);
434 # special case system, its styles live on
435 # the global master's s1style table alone
436 if ($u->{'user'} eq 'system') {
437 return LJ::get_db_writer();
440 return $u->writer;
443 sub get_s1style_reader {
444 my $u = shift;
445 return undef unless LJ::isu($u);
447 # special case system, its styles live on
448 # the global master's s1style table alone
449 if ($u->{'user'} eq 'system') {
450 return @LJ::MEMCACHE_SERVERS ? LJ::get_db_writer() : LJ::get_db_reader();
453 return @LJ::MEMCACHE_SERVERS ? LJ::get_cluster_def_reader($u) : LJ::get_cluster_reader($u);
456 # takes either $u object or userid
457 sub get_user_styles {
458 my $u = shift;
459 $u = LJ::isu($u) ? $u : LJ::load_user($u);
460 return undef unless $u;
462 my %styles;
464 # all cols *except* formatdata, which is big and unnecessary for most uses.
465 # it'll be loaded by LJ::S1::get_style
466 my $cols = "styleid, styledes, type, is_public, is_embedded, ".
467 "is_colorfree, opt_cache, has_ads, lastupdate";
469 # new clustered table
470 my ($db, $sth);
471 if ($u->{'dversion'} >= 5) {
472 $db = LJ::S1::get_s1style_reader($u);
473 $sth = $db->prepare("SELECT userid, $cols FROM s1style WHERE userid=?");
474 $sth->execute($u->{'userid'});
476 # old global table
477 } else {
478 $db = @LJ::MEMCACHE_SERVERS ? LJ::get_db_writer() : LJ::get_db_reader();
479 $sth = $db->prepare("SELECT user, $cols FROM style WHERE user=?");
480 $sth->execute($u->{'user'});
483 # build data structure
484 while (my $row = $sth->fetchrow_hashref) {
486 # fix up both userid and user values for consistency
487 $row->{'userid'} = $u->{'userid'};
488 $row->{'user'} = $u->{'user'};
490 $styles{$row->{'styleid'}} = $row;
491 next unless @LJ::MEMCACHE_SERVERS;
493 # now update memcache while we have this data?
494 LJ::MemCache::set([$row->{'styleid'}, "s1style:$row->{'styleid'}"], $row);
497 return \%styles;
500 # includes formatdata row.
501 sub get_style {
502 my $styleid = shift;
503 return unless $styleid;
505 my $memkey = [$styleid, "s1style_all:$styleid"];
506 my $style = LJ::MemCache::get($memkey);
507 return $style if $style;
509 # query global mapping table, returns undef if style isn't clustered
510 my $userid = LJ::S1::get_style_userid($styleid);
512 my $u;
513 $u = LJ::load_userid($userid) if $userid;
515 # new clustered table
516 if ($u && $u->{'dversion'} >= 5) {
517 my $db = LJ::S1::get_s1style_reader($u);
518 $style = $db->selectrow_hashref("SELECT * FROM s1style WHERE styleid=?", undef, $styleid);
520 # fill in user since the caller may expect it
521 $style->{'user'} = $u->{'user'};
523 # old global table
524 } else {
525 my $db = @LJ::MEMCACHE_SERVERS ? LJ::get_db_writer() : LJ::get_db_reader();
526 $style = $db->selectrow_hashref("SELECT * FROM style WHERE styleid=?", undef, $styleid);
528 # fill in userid since the caller may expect it
529 $style->{'userid'} = LJ::get_userid($style->{'user'});
531 return unless $style;
533 LJ::MemCache::set($memkey, $style);
535 return $style;
538 sub check_dup_style {
539 my ($u, $type, $styledes) = @_;
540 return unless $type && $styledes;
542 $u = LJ::isu($u) ? $u : LJ::load_user($u);
544 # new clustered table
545 if ($u && $u->{'dversion'} >= 5) {
546 # get writer since this function is to check duplicates. as such,
547 # the write action we're checking for probably happened recently
548 my $db = LJ::S1::get_s1style_writer($u);
549 return $db->selectrow_hashref("SELECT * FROM s1style WHERE userid=? AND type=? AND styledes=?",
550 undef, $u->{'userid'}, $type, $styledes);
552 # old global table
553 } else {
554 my $dbh = LJ::get_db_writer();
555 return $dbh->selectrow_hashref("SELECT * FROM style WHERE user=? AND type=? AND styledes=?",
556 undef, $u->{'user'}, $type, $styledes);
560 # returns userid of styleid, regardless of it being clusterd or not
561 sub get_style_userid_always {
562 my $styleid = shift;
564 my $uid = get_style_userid($styleid);
565 return $uid if $uid;
567 my $style = get_style($styleid)
568 or return 0;
570 return $style->{userid} or
571 die "S1 style \#$styleid has no userid field?";
574 # returns undef if style isn't clustered
575 sub get_style_userid {
576 my $styleid = shift;
578 # check cache
579 my $userid = $LJ::S1::REQ_CACHE_STYLEMAP{$styleid};
580 return $userid if $userid;
582 my $memkey = [$styleid, "s1stylemap:$styleid"];
583 my $style = LJ::MemCache::get($memkey);
584 return $style if $style;
586 # fetch from db
587 my $dbr = LJ::get_db_reader();
588 $userid = $dbr->selectrow_array("SELECT userid FROM s1stylemap WHERE styleid=?",
589 undef, $styleid);
590 return unless $userid;
592 # set cache
593 $LJ::S1::REQ_CACHE_STYLEMAP{$styleid} = $userid;
594 LJ::MemCache::set($memkey, $userid);
596 return $userid;
599 sub create_style {
600 my ($u, $opts) = @_;
601 return unless LJ::isu($u) && ref $opts eq 'HASH';
603 my $dbh = LJ::get_db_writer();
604 return undef unless $dbh;
606 my $styleid = LJ::alloc_global_counter('S');
607 return undef unless $styleid;
609 my (@cols, @bind, @vals);
610 foreach (qw(styledes type formatdata is_public is_embedded is_colorfree opt_cache has_ads)) {
611 next unless $opts->{$_};
613 push @cols, $_;
614 push @bind, "?";
615 push @vals, $opts->{$_};
617 my $cols = join(",", @cols);
618 my $bind = join(",", @bind);
619 return unless @cols;
621 if ($u->{'dversion'} >= 5) {
622 my $db = LJ::S1::get_s1style_writer($u);
623 $db->do("INSERT INTO s1style (styleid,userid,$cols) VALUES (?,?,$bind)",
624 undef, $styleid, $u->{'userid'}, @vals);
625 my $insertid = LJ::User::mysql_insertid($db);
626 die "Couldn't allocate insertid for s1style for userid $u->{userid}" unless $insertid;
628 $dbh->do("INSERT INTO s1stylemap (styleid, userid) VALUES (?,?)", undef, $insertid, $u->{'userid'});
629 return $insertid;
631 } else {
632 $dbh->do("INSERT INTO style (styleid, user,$cols) VALUES (?,?,$bind)",
633 undef, $styleid, $u->{'user'}, @vals);
634 return $dbh->{'mysql_insertid'};
638 sub update_style {
639 my ($styleid, $opts) = @_;
640 return unless $styleid && ref $opts eq 'HASH';
642 # query global mapping table, returns undef if style isn't clustered
643 my $userid = LJ::S1::get_style_userid($styleid);
645 my $u;
646 $u = LJ::load_userid($userid) if $userid;
648 my @cols = qw(styledes type formatdata is_public is_embedded
649 is_colorfree opt_cache has_ads lastupdate);
651 # what table to operate on ?
652 my ($db, $table);
654 # clustered table
655 if ($u && $u->{'dversion'} >= 5) {
656 $db = LJ::S1::get_s1style_writer($u);
657 $table = "s1style";
659 # global table
660 } else {
661 $db = LJ::get_db_writer();
662 $table = "style";
665 my (@sets, @vals);
666 foreach (@cols) {
667 if ($opts->{$_}) {
668 push @sets, "$_=?";
669 push @vals, $opts->{$_};
673 # update style
674 my $now_lastupdate = $opts->{'lastupdate'} ? ", lastupdate=NOW()" : '';
675 my $rows = $db->do("UPDATE $table SET " . join(", ", @sets) . "$now_lastupdate WHERE styleid=?",
676 undef, @vals, $styleid);
678 # clear out stylecache
679 $db->do("UPDATE s1stylecache SET vars_stor=NULL, vars_cleanver=0 WHERE styleid=?",
680 undef, $styleid);
682 # update memcache keys
683 LJ::MemCache::delete([$styleid, "s1style:$styleid"]);
684 LJ::MemCache::delete([$styleid, "s1style_all:$styleid"]);
685 LJ::MemCache::delete([$styleid, "s1styc:$styleid"]);
687 return $rows;
690 sub delete_style {
691 my $styleid = shift;
692 return unless $styleid;
694 # query global mapping table, returns undef if style isn't clustered
695 my $userid = LJ::S1::get_style_userid($styleid);
697 my $u;
698 $u = LJ::load_userid($userid) if $userid;
700 my $dbh = LJ::get_db_writer();
702 # new clustered table
703 if ($u && $u->{'dversion'} >= 5) {
704 $dbh->do("DELETE FROM s1stylemap WHERE styleid=?", undef, $styleid);
706 my $db = LJ::S1::get_s1style_writer($u);
707 $db->do("DELETE FROM s1style WHERE styleid=?", undef, $styleid);
708 $db->do("DELETE FROM s1stylecache WHERE styleid=?", undef, $styleid);
710 # old global table
711 } else {
712 # they won't have an s1stylemap entry
714 $dbh->do("DELETE FROM style WHERE styleid=?", undef, $styleid);
715 $dbh->do("DELETE FROM s1stylecache WHERE styleid=?", undef, $styleid);
718 # clear out some memcache space
719 LJ::MemCache::delete([$styleid, "s1style:$styleid"]);
720 LJ::MemCache::delete([$styleid, "s1style_all:$styleid"]);
721 LJ::MemCache::delete([$styleid, "s1stylemap:$styleid"]);
722 LJ::MemCache::delete([$styleid, "s1styc:$styleid"]);
724 return;
727 sub get_overrides {
728 my $u = shift;
729 return unless LJ::isu($u);
731 # try memcache
732 my $memkey = [$u->{'userid'}, "s1overr:$u->{'userid'}"];
733 my $overr = LJ::MemCache::get($memkey);
734 return $overr if $overr;
736 # new clustered table
737 if ($u->{'dversion'} >= 5) {
738 my $db = @LJ::MEMCACHE_SERVERS ? LJ::get_cluster_def_reader($u) : LJ::get_cluster_reader($u);
739 $overr = $db->selectrow_array("SELECT override FROM s1overrides WHERE userid=?", undef, $u->{'userid'});
741 # old global table
742 } else {
743 my $dbh = @LJ::MEMCACHE_SERVERS ? LJ::get_db_writer() : LJ::get_db_reader();
744 $overr = $dbh->selectrow_array("SELECT override FROM overrides WHERE user=?", undef, $u->{'user'});
747 # set in memcache
748 LJ::MemCache::set($memkey, $overr);
750 return $overr;
753 sub clear_overrides {
754 my $u = shift;
755 return unless LJ::isu($u);
757 my $overr;
758 my $db;
760 # new clustered table
761 if ($u->{'dversion'} >= 5) {
762 $overr = $u->do("DELETE FROM s1overrides WHERE userid=?", undef, $u->{'userid'});
763 $db = $u;
765 # old global table
766 } else {
767 my $dbh = LJ::get_db_writer();
768 $overr = $dbh->do("DELETE FROM overrides WHERE user=?", undef, $u->{'user'});
769 $db = $dbh;
772 # update s1usercache
773 $db->do("UPDATE s1usercache SET override_stor=NULL WHERE userid=?",
774 undef, $u->{'userid'});
776 LJ::MemCache::delete([$u->{'userid'}, "s1uc:$u->{'userid'}"]);
777 LJ::MemCache::delete([$u->{'userid'}, "s1overr:$u->{'userid'}"]);
779 return $overr;
782 sub save_overrides {
783 my ($u, $overr) = @_;
784 return unless LJ::isu($u) && $overr;
786 # new clustered table
787 my $insertid;
788 if ($u->{'dversion'} >= 5) {
789 $u->do("REPLACE INTO s1overrides (userid, override) VALUES (?, ?)",
790 undef, $u->{'userid'}, $overr);
791 $insertid = $u->mysql_insertid;
793 # old global table
794 } else {
795 my $dbh = LJ::get_db_writer();
796 $dbh->do("REPLACE INTO overrides (user, override) VALUES (?, ?)",
797 undef, $u->{'user'}, $overr);
798 $insertid = $dbh->{'mysql_insertid'};
801 # update s1usercache
802 my $override_stor = LJ::CleanHTML::clean_s1_style($overr);
803 $u->do("UPDATE s1usercache SET override_stor=?, override_cleanver=? WHERE userid=?",
804 undef, $override_stor, $LJ::S1::CLEANER_VERSION, $u->{'userid'});
806 LJ::MemCache::delete([$u->{'userid'}, "s1uc:$u->{'userid'}"]);
807 LJ::MemCache::delete([$u->{'userid'}, "s1overr:$u->{'userid'}"]);
809 return $insertid;
812 package LJ;
814 # <LJFUNC>
815 # name: LJ::alldateparts_to_hash
816 # class: s1
817 # des: Given a date/time format from MySQL, breaks it into a hash.
818 # info: This is used by S1.
819 # args: alldatepart
820 # des-alldatepart: The output of the MySQL function
821 # DATE_FORMAT(sometime, "%a %W %b %M %y %Y %c %m %e %d
822 # %D %p %i %l %h %k %H")
823 # returns: Hash (whole, not reference), with keys: dayshort, daylong,
824 # monshort, monlong, yy, yyyy, m, mm, d, dd, dth, ap, AP,
825 # ampm, AMPM, min, 12h, 12hh, 24h, 24hh
827 # </LJFUNC>
828 sub alldateparts_to_hash
830 my $alldatepart = shift;
831 my @dateparts = split(/ /, $alldatepart);
832 return (
833 'dayshort' => $dateparts[0],
834 'daylong' => $dateparts[1],
835 'monshort' => $dateparts[2],
836 'monlong' => $dateparts[3],
837 'yy' => $dateparts[4],
838 'yyyy' => $dateparts[5],
839 'm' => $dateparts[6],
840 'mm' => $dateparts[7],
841 'd' => $dateparts[8],
842 'dd' => $dateparts[9],
843 'dth' => $dateparts[10],
844 'ap' => substr(lc($dateparts[11]),0,1),
845 'AP' => substr(uc($dateparts[11]),0,1),
846 'ampm' => lc($dateparts[11]),
847 'AMPM' => $dateparts[11],
848 'min' => $dateparts[12],
849 '12h' => $dateparts[13],
850 '12hh' => $dateparts[14],
851 '24h' => $dateparts[15],
852 '24hh' => $dateparts[16],
856 # <LJFUNC>
857 # class: s1
858 # name: LJ::fill_var_props
859 # args: vars, key, hashref
860 # des: S1 utility function to interpolate %%variables%% in a variable. If
861 # a modifier is given like %%foo:var%%, then [func[LJ::fvp_transform]]
862 # is called.
863 # des-vars: hashref with keys being S1 vars
864 # des-key: the variable in the vars hashref we're expanding
865 # des-hashref: hashref of values that could interpolate.
866 # returns: Expanded string.
867 # </LJFUNC>
868 sub fill_var_props
870 my ($vars, $key, $hashref) = @_;
871 $_ = $vars->{$key};
872 s/%%([\w:]+:)?([\w\-\']+)%%/$1 ? LJ::fvp_transform(lc($1), $vars, $hashref, $2) : $hashref->{$2}/eg;
873 return $_;
876 # <LJFUNC>
877 # class: s1
878 # name: LJ::fvp_transform
879 # des: Called from [func[LJ::fill_var_props]] to do transformations.
880 # args: transform, vars, hashref, attr
881 # des-transform: The transformation type.
882 # des-vars: hashref with keys being S1 vars
883 # des-hashref: hashref of values that could interpolate. (see
884 # [func[LJ::fill_var_props]])
885 # des-attr: the attribute name that's being interpolated.
886 # returns: Transformed interpolated variable.
887 # </LJFUNC>
888 sub fvp_transform
890 my ($transform, $vars, $hashref, $attr) = @_;
891 my $ret = $hashref->{$attr};
892 while ($transform =~ s/(\w+):$//) {
893 my $trans = $1;
894 if ($trans eq "color") {
895 return $vars->{"color-$attr"};
897 elsif ($trans eq "ue") {
898 $ret = LJ::eurl($ret);
900 elsif ($trans eq "cons") {
901 if ($attr eq "img") { return $LJ::IMGPREFIX; }
902 if ($attr eq "siteroot") { return $LJ::SITEROOT; }
903 if ($attr eq "sitename") { return $LJ::SITENAME; }
905 elsif ($trans eq "attr") {
906 $ret =~ s/\"/&quot;/g;
907 $ret =~ s/\'/&\#39;/g;
908 $ret =~ s/</&lt;/g;
909 $ret =~ s/>/&gt;/g;
910 $ret =~ s/\]\]//g; # so they can't end the parent's [attr[..]] wrapper
912 elsif ($trans eq "lc") {
913 $ret = lc($ret);
915 elsif ($trans eq "uc") {
916 $ret = uc($ret);
918 elsif ($trans eq "xe") {
919 $ret = LJ::exml($ret);
921 elsif ($trans eq 'ljuser' or $trans eq 'ljcomm') {
922 my $user = LJ::canonical_username($ret);
923 $ret = LJ::ljuser($user);
925 elsif ($trans eq 'userurl') {
926 my $u = LJ::load_user($ret);
927 $ret = LJ::journal_base($u) if $u;
930 return $ret;
933 # <LJFUNC>
934 # class: s1
935 # name: LJ::parse_vars
936 # des: Parses S1 style data into hashref.
937 # returns: Nothing. Modifies a hashref.
938 # args: dataref, hashref
939 # des-dataref: Reference to scalar with data to parse. Format is
940 # a BML-style full block, as used in the S1 style system.
941 # des-hashref: Hashref to populate with data.
942 # </LJFUNC>
943 sub parse_vars
945 my ($dataref, $hashref) = @_;
946 my @data = split(/\n/, $$dataref);
947 my $curitem = "";
949 foreach (@data)
951 $_ .= "\n";
952 s/\r//g;
953 if ($curitem eq "" && /^([A-Z0-9\_]+)=>([^\n\r]*)/)
955 $hashref->{$1} = $2;
957 elsif ($curitem eq "" && /^([A-Z0-9\_]+)<=\s*$/)
959 $curitem = $1;
960 $hashref->{$curitem} = "";
962 elsif ($curitem && /^<=$curitem\s*$/)
964 chop $hashref->{$curitem}; # remove the false newline
965 $curitem = "";
967 else
969 $hashref->{$curitem} .= $_ if ($curitem =~ /\S/);
974 sub current_mood_str {
975 my ($themeid, $moodid, $mood) = @_;
977 # ideal behavior: if there is a moodid, that defines the picture.
978 # if there is a current_mood, that overrides as the mood name,
979 # otherwise show the mood name associated with current_moodid
981 my $moodname;
982 my $moodpic;
984 # favor custom mood over system mood
985 if (my $val = $mood) {
986 LJ::CleanHTML::clean_subject(\$val);
987 $moodname = $val;
990 if (my $val = $moodid) {
991 $moodname ||= LJ::mood_name($val);
992 my %pic;
993 if (LJ::get_mood_picture($themeid, $val, \%pic)) {
994 $moodpic = "<img src=\"$pic{'pic'}\" align='absmiddle' " .
995 LJ::mood_size_attributes(%pic) .
996 "vspace='1' alt='' /> ";
1000 my $extra = LJ::run_hook("current_mood_extra", $themeid) || "";
1001 return $moodpic || $moodname ? "$moodpic$moodname$extra" : "";
1004 sub current_music_str {
1005 my $val = shift;
1006 $val = LJ::Setting::Music::format_current_music_string($val);
1007 LJ::CleanHTML::clean_subject(\$val);
1008 return $val;
1011 # <LJFUNC>
1012 # class: s1
1013 # name: LJ::prepare_currents
1014 # des: do all the current music/mood/weather/whatever stuff. only used by ljviews.pl.
1015 # args: dbarg, args
1016 # des-args: hashref with keys: 'props' (a hashref with itemid keys), 'vars' hashref with
1017 # keys being S1 variables.
1018 # </LJFUNC>
1019 sub prepare_currents
1021 my $args = shift;
1023 my $datakey = $args->{'datakey'} || $args->{'itemid'}; # new || old
1024 my $entry_obj = $args->{'entry_obj'};
1026 my %currents = ();
1027 my $val;
1028 $currents{'Music'} = LJ::current_music_str($val);
1029 $currents{'Mood'} = LJ::current_mood_str($args->{'user'}->{'moodthemeid'},
1030 $args->{'props'}->{$datakey}->{'current_moodid'},
1031 $args->{'props'}->{$datakey}->{'current_mood'});
1032 delete $currents{'Mood'} unless $currents{'Mood'};
1033 delete $currents{'Music'} unless $currents{'Music'};
1035 if (%currents) {
1036 if ($args->{'vars'}->{$args->{'prefix'}.'_CURRENTS'})
1038 ### PREFIX_CURRENTS is defined, so use the correct style vars
1040 my $fvp = { 'currents' => "" };
1041 foreach (sort keys %currents) {
1042 $fvp->{'currents'} .= LJ::fill_var_props($args->{'vars'}, $args->{'prefix'}.'_CURRENT', {
1043 'what' => $_,
1044 'value' => $currents{$_},
1047 $args->{'event'}->{'currents'} =
1048 LJ::fill_var_props($args->{'vars'}, $args->{'prefix'}.'_CURRENTS', $fvp);
1049 } else
1051 ### PREFIX_CURRENTS is not defined, so just add to %%events%%
1052 $args->{'event'}->{'event'} .= "<br />&nbsp;";
1053 foreach (sort keys %currents) {
1054 $args->{'event'}->{'event'} .= "<br /><b>Current $_</b>: " . $currents{$_} . "\n";
1061 package LJ::S1;
1062 use strict;
1063 use LJ::Config;
1064 LJ::Config->load;
1066 use lib "$ENV{LJHOME}/cgi-bin";
1068 use LJ::Lang;
1069 require "cleanhtml.pl";
1070 use LJ::FriendsTags;
1072 # the creator for the 'lastn' view:
1073 sub create_view_lastn
1075 my ($ret, $u, $vars, $remote, $opts) = @_;
1077 my $user = $u->{'user'};
1079 foreach ("name", "url", "urlname", "journaltitle") { LJ::text_out(\$u->{$_}); }
1081 my $get = $opts->{'getargs'};
1083 if ($opts->{'pathextra'}) {
1084 $opts->{'badargs'} = 1;
1085 return 1;
1088 LJ::load_user_props($remote, "opt_ljcut_disable_lastn");
1090 my %lastn_page = ();
1091 $lastn_page{'name'} = LJ::ehtml($u->{'name'});
1092 $lastn_page{'name-\'s'} = ($u->{'name'} =~ /s$/i) ? "'" : "'s";
1093 $lastn_page{'username'} = $user;
1094 $lastn_page{'title'} = LJ::ehtml($u->{'journaltitle'} ||
1095 $u->{'name'} . $lastn_page{'name-\'s'} . " Journal");
1096 $lastn_page{'numitems'} = $vars->{'LASTN_OPT_ITEMS'} || 20;
1098 my $journalbase = LJ::journal_base($user, $opts->{'vhost'});
1099 $lastn_page{'urlfriends'} = "$journalbase/friends";
1100 $lastn_page{'urlcalendar'} = "$journalbase/calendar";
1102 if ($u->{'url'} =~ m!^https?://!) {
1103 $lastn_page{'website'} =
1104 LJ::fill_var_props($vars, 'LASTN_WEBSITE', {
1105 "url" => LJ::ehtml($u->{'url'}),
1106 "name" => LJ::ehtml($u->{'urlname'} || "My Website"),
1110 $lastn_page{'events'} = "";
1111 $lastn_page{'head'} = "";
1113 if (LJ::are_hooks('s2_head_content_extra')) {
1114 LJ::run_hooks('s2_head_content_extra', \$lastn_page{'head'}, $remote, $opts->{r});
1116 LJ::run_hooks('head_content', \$lastn_page{'head'});
1118 # if user has requested, or a skip back link has been followed, don't index or follow
1119 if ($u->should_block_robots || $get->{'skip'}) {
1120 $lastn_page{'head'} .= LJ::robot_meta_tags()
1122 if ($LJ::UNICODE) {
1123 $lastn_page{'head'} .= '<meta http-equiv="Content-Type" content="text/html; charset='.$opts->{'saycharset'}."\" />\n";
1126 # Automatic Discovery of RSS/Atom
1127 unless ($u->is_syndicated) {
1128 # don't show RSS/Atom of something we're syndicating.
1129 $lastn_page{'head'} .= qq{<link rel="alternate" type="application/rss+xml" title="RSS" href="$journalbase/data/rss" />\n};
1130 $lastn_page{'head'} .= qq{<link rel="alternate" type="application/atom+xml" title="Atom" href="$journalbase/data/atom" />\n};
1133 $lastn_page{'head'} .= qq{<link rel="service.feed" type="application/atom+xml" title="AtomAPI-enabled feed" href="$LJ::SITEROOT/interface/atom/feed" />\n};
1134 $lastn_page{'head'} .= qq{<link rel="service.post" type="application/atom+xml" title="Create a new post" href="$LJ::SITEROOT/interface/atom/post" />\n};
1136 $lastn_page{'head'} .= $u->openid_tags;
1138 # Link to the friends page as a "group", for use with OpenID "Group Membership Protocol"
1140 my $is_comm = $u->is_community;
1141 my $friendstitle = $LJ::SITENAMESHORT." ".($is_comm ? "members" : "friends");
1142 my $rel = "group ".($is_comm ? "members" : "friends made");
1143 my $friendsurl = $u->journal_base."/friends"; # We want the canonical form here, not the vhost form
1144 $lastn_page{'head'} .= '<link rel="'.$rel.'" title="'.LJ::ehtml($friendstitle).'" href="'.LJ::ehtml($friendsurl)."\" />\n";
1147 my $show_control_strip = LJ::run_hook('show_control_strip', {
1148 user => $u->{user},
1150 if ($show_control_strip) {
1151 LJ::run_hook('control_strip_stylesheet_link', {
1152 user => $u->{user},
1154 LJ::control_strip_js_inject( user => $u->{user} );
1156 LJ::journal_js_inject();
1158 LJ::run_hooks("need_res_for_journals", $u);
1159 my $graphicpreviews_obj = LJ::graphicpreviews_obj();
1160 $graphicpreviews_obj->need_res($u);
1161 my $extra_js = LJ::statusvis_message_js($u);
1162 $lastn_page{'head'} .= LJ::res_includes() . LJ::res_includes({only_needed => 1, only_tmpl => 1}) . $extra_js;
1164 # FOAF autodiscovery
1165 my $foafurl = $u->{external_foaf_url} ? LJ::eurl($u->{external_foaf_url}) : "$journalbase/data/foaf";
1166 $lastn_page{head} .= qq{<link rel="meta" type="application/rdf+xml" title="FOAF" href="$foafurl" />\n};
1168 if ($u->email_visible($remote)) {
1169 my $digest = Digest::SHA1::sha1_hex('mailto:' . $u->email_raw);
1170 $lastn_page{head} .= qq{<meta name="foaf:maker" content="foaf:mbox_sha1sum '$digest'" />\n};
1173 $lastn_page{'head'} .=
1174 $vars->{'GLOBAL_HEAD'} . "\n" . $vars->{'LASTN_HEAD'};
1176 my $events = \$lastn_page{'events'};
1178 # to show
1179 my $itemshow = $vars->{'LASTN_OPT_ITEMS'} + 0;
1180 if ($itemshow < 1) { $itemshow = 20; }
1181 if ($itemshow > 50) { $itemshow = 50; }
1183 my $skip = $get->{'skip'}+0;
1184 my $maxskip = $LJ::MAX_SCROLLBACK_LASTN-$itemshow;
1185 if ($skip < 0) { $skip = 0; }
1186 if ($skip > $maxskip) { $skip = $maxskip; }
1188 # do they have the viewall priv?
1189 my $viewall = 0;
1190 my $viewsome = 0;
1191 if ($get->{'viewall'} && LJ::check_priv($remote, "canview", "suspended")) {
1192 LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
1193 "viewall", "lastn: $user, statusvis: $u->{'statusvis'}");
1194 $viewall = LJ::check_priv($remote, 'canview', '*');
1195 $viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended');
1198 ## load the itemids
1199 my @itemids;
1200 my $err;
1201 my @items = LJ::get_recent_items({
1202 'clusterid' => $u->{'clusterid'},
1203 'clustersource' => 'slave',
1204 'viewall' => $viewall,
1205 'viewsome' => $viewsome,
1206 'userid' => $u->{'userid'},
1207 'remote' => $remote,
1208 'itemshow' => $itemshow + 1,
1209 'skip' => $skip,
1210 'itemids' => \@itemids,
1211 'order' => ($u->{'journaltype'} eq "C" || $u->{'journaltype'} eq "Y") # community or syndicated
1212 ? "logtime" : "",
1213 'err' => \$err,
1214 'show_sticky_on_top' => 1,
1215 'poster' => $get->{'poster'},
1218 my $is_prev_exist = scalar @items - $itemshow > 0 ? 1 : 0;
1219 if ($is_prev_exist) {
1220 pop @items;
1223 if ($err) {
1224 $opts->{'errcode'} = $err;
1225 $$ret = "";
1226 return 0;
1229 ### load the log properties
1230 my %logprops = ();
1231 my $logtext;
1232 LJ::load_log_props2($u->{'userid'}, \@itemids, \%logprops);
1233 $logtext = LJ::get_logtext2($u, @itemids);
1235 my $lastday = -1;
1236 my $lastmonth = -1;
1237 my $lastyear = -1;
1238 my $eventnum = 0;
1240 my %posteru = (); # map posterids to u objects
1241 LJ::load_userids_multiple([map { $_->{'posterid'}, \$posteru{$_->{'posterid'}} }
1242 @items], [$u]);
1244 # pre load things in a batch (like userpics) to minimize db calls
1245 my @userpic_load;
1246 push @userpic_load, [ $u, $u->{'defaultpicid'} ] if $u->{'defaultpicid'};
1247 foreach my $item (@items) {
1248 next if $item->{'posterid'} == $u->{'userid'};
1249 my $itemid = $item->{'itemid'};
1250 my $pu = $posteru{$item->{'posterid'}};
1252 my $pickw = LJ::Entry->userpic_kw_from_props($logprops{$itemid});
1253 my $picid = LJ::get_picid_from_keyword($pu, $pickw);
1254 $item->{'_picid'} = $picid;
1255 push @userpic_load, [ $pu, $picid ] if ($picid && ! grep { $_ eq $picid } @userpic_load);
1257 my %userpics;
1258 LJ::load_userpics(\%userpics, \@userpic_load);
1260 if (my $picid = $u->{'defaultpicid'}) {
1261 $lastn_page{'userpic'} =
1262 LJ::fill_var_props($vars, 'LASTN_USERPIC', {
1263 "src" => "$LJ::USERPIC_ROOT/$picid/$u->{'userid'}",
1264 "width" => $userpics{$picid}->{'width'},
1265 "height" => $userpics{$picid}->{'height'},
1269 my $ljcut_disable = $remote ? $remote->prop("opt_ljcut_disable_lastn") : undef;
1270 my $replace_video = $remote ? $remote->opt_embedplaceholders : 0;
1272 ENTRY:
1273 foreach my $item (@items) {
1274 my ($posterid, $itemid, $security, $alldatepart) =
1275 map { $item->{$_} } qw(posterid itemid security alldatepart);
1277 my $journalu = $u;
1279 my $ditemid = $itemid * 256 + $item->{'anum'};
1280 my $entry_obj = LJ::Entry->new($u, ditemid => $ditemid);
1282 next ENTRY unless $entry_obj->visible_to($remote, {'viewall' => $viewall, 'viewsome' => $viewsome});
1284 $entry_obj->handle_prefetched_props($logprops{$itemid});
1286 my $replycount = $logprops{$itemid}->{'replycount'};
1287 my $subject = $logtext->{$itemid}->[0];
1288 my $event = $logtext->{$itemid}->[1];
1290 my $username = $user;
1291 my $repost_entry_obj;
1292 my $removed;
1294 my $content = { 'original_post_obj' => \$entry_obj,
1295 'repost_obj' => \$repost_entry_obj,
1296 'ditemid' => \$ditemid,
1297 'journalu' => \$journalu,
1298 'posterid' => \$posterid,
1299 'security' => \$security,
1300 'event' => \$event,
1301 'subject' => \$subject,
1302 'removed' => \$removed,
1303 'reply_count' => \$replycount };
1305 my $repost_props = { 'use_repost_signature' => 1};
1307 if (LJ::Entry::Repost->substitute_content( $entry_obj, $content, $repost_props )) {
1308 next ENTRY if $removed && !LJ::u_equals($u, $remote);
1309 next ENTRY unless $entry_obj->visible_to($remote, {'viewall' => $viewall,
1310 'viewsome' => $viewsome});
1312 $username = $entry_obj->poster->username;
1313 $posteru{$posterid} = $entry_obj->poster;
1314 $logprops{$itemid} = $entry_obj->props;
1317 if ($get->{'nohtml'}) {
1318 # quote all non-LJ tags
1319 $subject =~ s{<(?!/?lj)(.*?)>} {&lt;$1&gt;}gi;
1320 $event =~ s{<(?!/?lj)(.*?)>} {&lt;$1&gt;}gi;
1323 if ( $LJ::UNICODE &&
1324 ( $entry_obj->prop("unknown8bit") ||
1325 $logprops{$itemid}->{'unknown8bit'} ) ) {
1326 LJ::item_toutf8($journalu, \$subject, \$event, $logprops{$itemid});
1329 my %lastn_date_format = LJ::alldateparts_to_hash($alldatepart);
1331 if ( $lastday != $lastn_date_format{'d'} ||
1332 $lastmonth != $lastn_date_format{'m'} ||
1333 $lastyear != $lastn_date_format{'yyyy'} )
1335 my %lastn_new_day = ();
1336 foreach (qw(dayshort daylong monshort monlong m mm yy yyyy d dd dth)) {
1337 $lastn_new_day{$_} = $lastn_date_format{$_};
1339 unless ($lastday==-1) {
1340 $$events .= LJ::fill_var_props($vars, 'LASTN_END_DAY', {});
1342 $$events .= LJ::fill_var_props($vars, 'LASTN_NEW_DAY', \%lastn_new_day);
1344 $lastday = $lastn_date_format{'d'};
1345 $lastmonth = $lastn_date_format{'m'};
1346 $lastyear = $lastn_date_format{'yyyy'};
1349 my %lastn_event = ();
1350 $eventnum++;
1351 $lastn_event{'eventnum'} = $eventnum;
1352 $lastn_event{'itemid'} = $itemid;
1353 $lastn_event{'itemid'} = $itemid;
1354 $lastn_event{'datetime'} = LJ::fill_var_props($vars, 'LASTN_DATE_FORMAT', \%lastn_date_format);
1355 if ($subject ne "") {
1356 LJ::CleanHTML::clean_subject(\$subject);
1357 $lastn_event{'subject'} = LJ::fill_var_props($vars, 'LASTN_SUBJECT', {
1358 "subject" => $subject,
1362 my $itemargs = "journal=$username&amp;itemid=$ditemid";
1363 $lastn_event{'itemargs'} = $itemargs;
1365 my $suspend_msg = $entry_obj && $entry_obj->should_show_suspend_msg_to($remote) ? 1 : 0;
1367 LJ::CleanHTML::clean_event(
1368 \$event,
1370 'preformatted' => $logprops{$itemid}->{'opt_preformatted'},
1371 'cuturl' => $entry_obj->url,
1372 'entry_url' => $entry_obj->url,
1373 'ljcut_disable' => $ljcut_disable,
1374 'suspend_msg' => $suspend_msg,
1375 'unsuspend_supportid' => $suspend_msg ? $entry_obj->prop("unsuspend_supportid") : 0,
1376 'journalid' => $entry_obj->journalid,
1377 'posterid' => $entry_obj->posterid,
1378 'video_placeholders' => $replace_video,
1382 LJ::expand_embedded(
1383 $journalu,
1384 $ditemid,
1385 $remote,
1386 \$event,
1387 'video_placeholders' => $replace_video,
1390 $event = LJ::ContentFlag->transform_post(
1391 'post' => $event,
1392 'journal' => $journalu,
1393 'remote' => $remote,
1394 'entry' => $entry_obj,
1397 $lastn_event{'event'} = $event;
1399 my $permalink = $entry_obj->url;
1400 $lastn_event{'permalink'} = $permalink;
1402 if($subject !~ /[<>]/) {
1403 $lastn_event{'subject'} = "<a href='$permalink'>" . $lastn_event{'subject'} . "</a>";
1406 if ($entry_obj->comments_shown) {
1407 my $nc;
1408 $nc = "nc=$replycount" if $replycount && $remote && $remote->{'opt_nctalklinks'};
1410 my $posturl = LJ::Talk::talkargs($permalink, "mode=reply");
1411 my $readurl = LJ::Talk::talkargs($permalink, $nc);
1413 my $dispreadlink = $replycount ||
1414 ($logprops{$itemid}->{'hasscreened'} && $remote && $remote->can_manage($journalu));
1416 $lastn_event{'talklinks'} = LJ::fill_var_props($vars, 'LASTN_TALK_LINKS', {
1417 'itemid' => $ditemid,
1418 'itemargs' => $itemargs,
1419 'urlpost' => $posturl,
1420 'urlread' => $readurl,
1421 'messagecount' => $replycount,
1422 'readlink' => $dispreadlink ? LJ::fill_var_props($vars, 'LASTN_TALK_READLINK', {
1423 'urlread' => $readurl,
1424 'messagecount' => $replycount,
1425 'mc-plural-s' => $replycount == 1 ? "" : "s",
1426 'mc-plural-es' => $replycount == 1 ? "" : "es",
1427 'mc-plural-ies' => $replycount == 1 ? "y" : "ies",
1428 }) : "",
1432 ## current stuff
1433 LJ::prepare_currents({
1434 'props' => \%logprops,
1435 'itemid' => $itemid,
1436 'vars' => $vars,
1437 'prefix' => "LASTN",
1438 'event' => \%lastn_event,
1439 'user' => $journalu,
1440 'entry_obj' => $entry_obj,
1443 if ($journalu->userid != $posterid)
1445 my %lastn_altposter = ();
1447 my $pu = $posteru{$posterid};
1448 my $poster = $pu->{'user'};
1449 $lastn_altposter{'poster'} = $poster;
1450 $lastn_altposter{'owner'} = $username;
1452 if (my $picid = $item->{'_picid'}) {
1453 my $pic = $userpics{$picid};
1454 $lastn_altposter{'pic'} = LJ::fill_var_props($vars, 'LASTN_ALTPOSTER_PIC', {
1455 "src" => "$LJ::USERPIC_ROOT/$picid/$pic->{'userid'}",
1456 "width" => $pic->{'width'},
1457 "height" => $pic->{'height'},
1460 $lastn_event{'altposter'} =
1461 LJ::fill_var_props($vars, 'LASTN_ALTPOSTER', \%lastn_altposter);
1464 if ($security eq "public") {
1465 $LJ::REQ_GLOBAL{'text_of_first_public_post'} ||= $event;
1468 my $var = 'LASTN_EVENT';
1469 if ($security eq "private" &&
1470 $vars->{'LASTN_EVENT_PRIVATE'}) { $var = 'LASTN_EVENT_PRIVATE'; }
1471 if ($security eq "usemask" &&
1472 $vars->{'LASTN_EVENT_PROTECTED'}) { $var = 'LASTN_EVENT_PROTECTED'; }
1474 if (!$repost_entry_obj) {
1475 if (LJ::is_enabled("delayed_entries")) {
1476 $var .= '_STICKY' if $entry_obj->is_sticky();
1478 } else {
1479 if (LJ::is_enabled("entry_reference")) {
1480 # $var .= '_REPOST';
1482 my $reposter = $repost_entry_obj->poster;
1483 my $ref_text = LJ::Lang::ml( 'entry.reference.reposter',
1484 { 'reposter' => LJ::ljuser($reposter) } );
1485 $lastn_event{'reposted_by'} = $ref_text;
1489 my $display_metadata = 1;
1490 LJ::run_hooks( 'substitute_entry_content', $entry_obj,
1491 \$lastn_event{'event'},
1493 'subject' => \$lastn_event{'subject'},
1494 'display_metadata' => \$display_metadata,
1498 if ( !$display_metadata ) {
1499 delete $lastn_event{'Mood'};
1500 delete $lastn_event{'Music'};
1503 LJ::run_hooks( 'blocked_entry_content', $entry_obj,
1505 'text' => \$lastn_event{'event'},
1506 'subject' => \$lastn_event{'subject'},
1510 $$events .= LJ::fill_var_props($vars, $var, \%lastn_event);
1511 LJ::run_hook('notify_event_displayed', $entry_obj);
1513 my $ads = LJ::get_ads({
1514 location => 's1.ebox',
1515 s1_view => 'lastn',
1516 journalu => $journalu,
1517 current_post_number => $eventnum,
1518 total_posts_number => scalar @items,
1520 if ($ads) {
1521 my $var_name = (exists $vars->{ADS_EVENT}) ? 'ADS_EVENT' : 'LASTN_EVENT';
1522 $$events .= LJ::fill_var_props($vars, $var_name, {event => $ads});
1524 } # end huge while loop
1526 $$events .= LJ::fill_var_props($vars, 'LASTN_END_DAY', {});
1528 my $item_shown = $eventnum;
1529 my $item_total = @items;
1530 my $item_hidden = $item_total - $item_shown;
1532 if ($skip) {
1533 $lastn_page{'range'} =
1534 LJ::fill_var_props($vars, 'LASTN_RANGE_HISTORY', {
1535 "numitems" => $item_shown,
1536 "skip" => $skip,
1538 } else {
1539 $lastn_page{'range'} =
1540 LJ::fill_var_props($vars, 'LASTN_RANGE_MOSTRECENT', {
1541 "numitems" => $item_shown,
1545 #### make the skip links
1546 my ($skip_f, $skip_b) = (0, 0);
1547 my %skiplinks;
1549 ### filter by poster in skip links
1550 my $poster_filter = ($get->{'poster'}) ? "poster=" . LJ::eurl($get->{'poster'}) : "";
1552 ### if we've skipped down, then we can skip back up
1554 if ($skip) {
1555 $skip_f = 1;
1556 my $newskip = $skip - $itemshow;
1557 if ($newskip <= 0) { $newskip = ""; }
1558 else { $newskip = "?skip=$newskip"; }
1560 if ($poster_filter) {
1561 if ($newskip) {
1562 $newskip .= "&$poster_filter";
1563 } else {
1564 $newskip = "?$poster_filter";
1567 $skiplinks{'skipforward'} =
1568 LJ::fill_var_props($vars, 'LASTN_SKIP_FORWARD', {
1569 "numitems" => $itemshow,
1570 "url" => "$journalbase/$newskip",
1574 ## unless we didn't even load as many as we were expecting on this
1575 ## page, then there are more (unless there are exactly the number shown
1576 ## on the page, but who cares about that)
1578 unless ($item_total != $itemshow) {
1579 $skip_b = 1 if $is_prev_exist;
1581 if ($skip>=$maxskip) {
1582 my $url = "$journalbase/" . sprintf("%04d/%02d/%02d/", $lastyear, $lastmonth, $lastday);
1583 $url .= "?$poster_filter" if $poster_filter;
1584 $skiplinks{'skipbackward'} =
1585 LJ::fill_var_props($vars, 'LASTN_SKIP_BACKWARD', {
1586 "numitems" => "Day",
1587 "url" => $url,
1589 } else {
1590 my $newskip = $skip + $itemshow;
1591 $newskip = "?skip=$newskip";
1592 $newskip .= "&$poster_filter" if $poster_filter;
1593 $skiplinks{'skipbackward'} =
1594 LJ::fill_var_props($vars, 'LASTN_SKIP_BACKWARD', {
1595 "numitems" => $itemshow,
1596 "url" => "$journalbase/$newskip",
1601 ### if they're both on, show a spacer
1602 if ($skip_b && $skip_f) {
1603 $skiplinks{'skipspacer'} = $vars->{'LASTN_SKIP_SPACER'};
1606 ### if either are on, put skiplinks into lastn_page
1607 if ($skip_b || $skip_f) {
1608 $lastn_page{'skiplinks'} =
1609 LJ::fill_var_props($vars, 'LASTN_SKIP_LINKS', \%skiplinks);
1612 # ads and control strip
1613 my $vetical_ad = LJ::get_ads({
1614 location => 's1.vertical',
1615 s1_view => 'lastn',
1616 journalu => $u,
1617 pubtext => $LJ::REQ_GLOBAL{'text_of_first_public_post'},
1618 total_posts_number => scalar @items,
1620 my $bottom_ad = LJ::get_ads({
1621 location => 's1.bottom',
1622 s1_view => 'lastn',
1623 journalu => $u,
1624 pubtext => $LJ::REQ_GLOBAL{'text_of_first_public_post'},
1625 total_posts_number => scalar @items,
1627 if ($vetical_ad || $bottom_ad) {
1628 $lastn_page{'skyscraper_ad'} = LJ::fill_var_props($vars, 'LASTN_SKYSCRAPER_AD', { ad => $vetical_ad });
1629 $lastn_page{'5linkunit_ad'} = LJ::fill_var_props($vars, 'LASTN_5LINKUNIT_AD', { ad => $bottom_ad });
1630 $lastn_page{'open_skyscraper_ad'} = $vars->{'LASTN_OPEN_SKYSCRAPER_AD'};
1631 $lastn_page{'close_skyscraper_ad'} = $vars->{'LASTN_CLOSE_SKYSCRAPER_AD'};
1634 if ($LJ::USE_CONTROL_STRIP && $show_control_strip) {
1635 my $control_strip = LJ::control_strip(user => $u->{user});
1636 $lastn_page{'control_strip'} = $control_strip . LJ::get_ads({location => 'common.banner'});
1639 $$ret = LJ::fill_var_props($vars, 'LASTN_PAGE', \%lastn_page);
1640 return 1;
1643 # the creator for the 'friends' view:
1644 sub create_view_friends {
1645 my ($ret, $u, $vars, $remote, $opts) = @_;
1646 my $sth;
1647 my $user = $u->{'user'};
1648 my %reposts;
1650 # Check if we should redirect due to a bad password
1651 $opts->{'redir'} = LJ::bad_password_redirect({ 'returl' => 1 });
1652 return 1 if $opts->{'redir'};
1654 # see how often the remote user can reload this page.
1655 # "friendsviewupdate" time determines what granularity time
1656 # increments by for checking for new updates
1657 my $nowtime = time();
1659 # update delay specified by "friendsviewupdate"
1660 my $newinterval = LJ::get_cap_min($remote, "friendsviewupdate") || 1;
1662 # when are we going to say page was last modified? back up to the
1663 # most recent time in the past where $time % $interval == 0
1664 my $lastmod = $nowtime;
1665 $lastmod -= $lastmod % $newinterval;
1667 # see if they have a previously cached copy of this page they
1668 # might be able to still use.
1669 if ($opts->{'header'}->{'If-Modified-Since'}) {
1670 my $theirtime = LJ::TimeUtil->http_to_time($opts->{'header'}->{'If-Modified-Since'});
1672 # send back a 304 Not Modified if they say they've reloaded this
1673 # document in the last $newinterval seconds:
1674 my $uniq = LJ::Request->notes('uniq');
1675 if ($theirtime > $lastmod && !($uniq && LJ::MemCache::get("loginout:$uniq"))) {
1676 $opts->{'handler_return'} = 304;
1677 return 1;
1680 $opts->{'headers'}->{'Last-Modified'} = LJ::TimeUtil->time_to_http($lastmod);
1682 $$ret = "";
1684 my $get = $opts->{'getargs'};
1685 my $journalbase = LJ::journal_base($user, $opts->{'vhost'});
1687 foreach ("name", "url", "urlname", "friendspagetitle") { LJ::text_out(\$u->{$_}); }
1689 my %friends_page = ();
1690 $friends_page{'name'} = LJ::ehtml($u->{'name'});
1691 $friends_page{'name-\'s'} = ($u->{'name'} =~ /s$/i) ? "'" : "'s";
1692 $friends_page{'username'} = $user;
1693 $friends_page{'title'} = LJ::ehtml($u->{'friendspagetitle'} ||
1694 $u->{'name'} . $friends_page{'name-\'s'} . " Friends");
1695 $friends_page{'numitems'} = $vars->{'FRIENDS_OPT_ITEMS'} || 20;
1697 $friends_page{'head'} = "";
1699 if (LJ::are_hooks('s2_head_content_extra')) {
1700 LJ::run_hooks('s2_head_content_extra', \$friends_page{'head'}, $remote, $opts->{r});
1702 LJ::run_hooks('head_content', \$friends_page{'head'});
1704 ## never have spiders index friends pages (change too much, and some
1705 ## people might not want to be indexed)
1706 $friends_page{'head'} .= LJ::robot_meta_tags();
1707 if ($LJ::UNICODE) {
1708 $friends_page{'head'} .= '<meta http-equiv="Content-Type" content="text/html; charset='.$opts->{'saycharset'}.'" />';
1710 # Add a friends-specific XRDS reference
1711 $friends_page{'head'} .= qq{<meta http-equiv="X-XRDS-Location" content="}.LJ::ehtml($u->journal_base).qq{/data/yadis/friends" />\n};
1713 my $show_control_strip = LJ::run_hook('show_control_strip', {
1714 user => $u->{user},
1716 if ($show_control_strip) {
1717 LJ::run_hook('control_strip_stylesheet_link', {
1718 user => $u->{user},
1720 LJ::control_strip_js_inject( user => $u->{user} );
1723 LJ::journal_js_inject();
1725 LJ::run_hooks("need_res_for_journals", $u);
1726 my $graphicpreviews_obj = LJ::graphicpreviews_obj();
1727 $graphicpreviews_obj->need_res($u);
1728 my $extra_js = LJ::statusvis_message_js($u);
1729 $friends_page{'head'} .= LJ::res_includes() . LJ::res_includes({only_needed => 1, only_tmpl => 1}) . $extra_js;
1731 $friends_page{'head'} .=
1732 $vars->{'GLOBAL_HEAD'} . "\n" . $vars->{'FRIENDS_HEAD'};
1734 if ($u->{'url'} =~ m!^https?://!) {
1735 $friends_page{'website'} =
1736 LJ::fill_var_props($vars, 'FRIENDS_WEBSITE', {
1737 "url" => LJ::ehtml($u->{'url'}),
1738 "name" => LJ::ehtml($u->{'urlname'} || "My Website"),
1742 $friends_page{'urlcalendar'} = "$journalbase/calendar";
1743 $friends_page{'urllastn'} = "$journalbase/";
1745 $friends_page{'events'} = "";
1747 my $itemshow = $vars->{'FRIENDS_OPT_ITEMS'} + 0;
1748 if ($itemshow < 1) { $itemshow = 20; }
1749 if ($itemshow > 50) { $itemshow = 50; }
1751 my $skip = $get->{'skip'}+0;
1752 my $maxskip = ($LJ::MAX_SCROLLBACK_FRIENDS || 1000) - $itemshow;
1753 if ($skip > $maxskip) { $skip = $maxskip; }
1754 if ($skip < 0) { $skip = 0; }
1755 my $itemload = $itemshow+$skip;
1757 my $base = "$journalbase/$opts->{'view'}";
1759 my $filter;
1760 my $group;
1761 my $common_filter = 1;
1763 my $events_date = 0;
1764 my $pathextra = $opts->{pathextra};
1765 if ($pathextra && $pathextra =~ m/^\/(\d\d\d\d)\/(\d\d)\/(\d\d)\/?$/) {
1766 $events_date = LJ::TimeUtil->mysqldate_to_time("$1-$2-$3");
1767 $base .= $pathextra;
1768 $pathextra = '';
1769 $get->{date} = '';
1771 elsif ($get->{date} =~ m!^(\d{4})-(\d\d)-(\d\d)$!) {
1772 $events_date = LJ::TimeUtil->mysqldate_to_time("$1-$2-$3");
1775 if (defined $get->{'filter'} && $remote && $remote->{'user'} eq $user) {
1776 $filter = $get->{'filter'};
1777 $common_filter = 0;
1778 } else {
1779 if ($pathextra) {
1780 $group = $pathextra;
1781 $group =~ s!^/!!;
1782 $group =~ s!/$!!;
1783 if ($group) {
1784 $group = LJ::durl($group);
1785 $common_filter = 0;
1786 $base .= "/" . LJ::eurl($group);
1789 my $grp = LJ::get_friend_group($u, { 'name' => $group || "Default View" });
1790 my $bit = $grp ? $grp->{'groupnum'} : 0;
1791 my $public = $grp ? $grp->{'is_public'} : 0;
1792 if ($bit && ($public || ($remote && $remote->{'user'} eq $user))) {
1793 $filter = (1 << $bit);
1794 } elsif ($group) {
1795 $opts->{'badfriendgroup'} = 1;
1796 return 1;
1800 ## load the itemids
1801 my %friends;
1802 my %friends_row;
1803 my %idsbycluster;
1805 my @items = LJ::get_friend_items({
1806 'u' => $u,
1807 'userid' => $u->{'userid'},
1808 'remote' => $remote,
1809 'itemshow' => $itemshow,
1810 'skip' => $skip,
1811 'filter' => $filter,
1812 'common_filter' => $common_filter,
1813 'friends_u' => \%friends,
1814 'friends' => \%friends_row,
1815 'idsbycluster' => \%idsbycluster,
1816 'showtypes' => $get->{'show'},
1817 'friendsoffriends' => $opts->{'view'} eq "friendsfriends",
1818 'events_date' => $events_date,
1819 'filter_by_tags' => ($get->{notags} ? 0 : 1),
1822 for ( keys %friends ) {
1823 # while ($_ = each %friends) {
1824 # we expect fgcolor/bgcolor to be in here later
1825 unless (defined $friends_row{$_}->{'fgcolor'}) {
1826 $friends{$_}->{'fgcolor'} = '#000000';
1827 } else {
1828 $friends{$_}->{'fgcolor'} = LJ::color_fromdb(
1829 $friends_row{$_}->{'fgcolor'}
1833 unless (defined $friends_row{$_}->{'bgcolor'}) {
1834 $friends{$_}->{'bgcolor'} = '#ffffff';
1835 } else {
1836 $friends{$_}->{'bgcolor'} = LJ::color_fromdb(
1837 $friends_row{$_}->{'bgcolor'}
1842 unless ( %friends ) {
1843 $friends_page{'events'} = LJ::fill_var_props($vars, 'FRIENDS_NOFRIENDS', {
1844 "name" => LJ::ehtml($u->{'name'}),
1845 "name-\'s" => ($u->{'name'} =~ /s$/i) ? "'" : "'s",
1846 "username" => $user,
1849 $$ret .= "<base target='_top'>" if ($get->{'mode'} eq "framed");
1850 $$ret .= LJ::fill_var_props($vars, 'FRIENDS_PAGE', \%friends_page);
1851 return 1;
1854 my %aposter; # alt-posterid -> u object (if not in friends already)
1855 LJ::load_userids_multiple([map { $_->{'posterid'}, \$aposter{$_->{'posterid'}} }
1856 grep { $friends{$_->{'ownerid'}} &&
1857 ! $friends{$_->{'posterid'}} } @items],
1858 [ $u, $remote ]);
1860 ### load the log properties
1861 my %logprops = (); # key is "$owneridOrZero $[j]itemid"
1862 LJ::load_log_props2multi(\%idsbycluster, \%logprops);
1864 # load the pictures for the user
1865 my %userpics;
1866 my @picids = map { [$friends{$_}, $friends{$_}->{'defaultpicid'}] } keys %friends;
1867 LJ::load_userpics(\%userpics, [ @picids, map { [ $_, $_->{'defaultpicid'} ] } values %aposter ]);
1869 # load the text of the entries
1870 my $logtext = LJ::get_logtext2multi(\%idsbycluster);
1872 # load 'opt_stylemine' prop for $remote. don't need to load opt_nctalklinks
1873 # because that was already faked in LJ::make_journal previously
1874 LJ::load_user_props($remote, "opt_stylemine", "opt_imagelinks", "opt_ljcut_disable_friends");
1876 # load options for image links
1877 my $replace_images_in_friendspage = 0;
1878 if( $u->equals($remote) ) {
1879 $replace_images_in_friendspage = $remote->opt_placeholders_friendspage;
1882 my $ljcut_disable = $remote ? $remote->prop("opt_ljcut_disable_lastn") : undef;
1883 my $replace_video = $remote ? $remote->opt_embedplaceholders : 0;
1885 my %friends_events = ();
1886 my $events = \$friends_events{'events'};
1888 my $lastday = -1;
1889 my $eventnum = 0;
1891 ENTRY:
1892 foreach my $item (@items)
1894 my ($friendid, $posterid, $itemid, $security, $alldatepart) =
1895 map { $item->{$_} } qw(ownerid posterid itemid security alldatepart);
1897 my $ditemid = $itemid * 256 + $item->{'anum'};
1898 my $entry_obj = LJ::Entry->new($friends{$friendid}, ditemid => $ditemid);
1900 my $pu = $friends{$posterid} || $aposter{$posterid};
1901 next ENTRY if $pu && $pu->{'statusvis'} eq 'S';
1902 next ENTRY if $entry_obj && $entry_obj->is_suspended_for($remote);
1904 if ( $pu->is_deleted
1905 && !$LJ::JOURNALS_WITH_PROTECTED_CONTENT{$pu->username} )
1907 my ($purge_comments, $purge_community_entries)
1908 = split /:/, $pu->prop("purge_external_content");
1910 next ENTRY if $purge_community_entries;
1913 # counting excludes skipped entries
1914 $eventnum++;
1916 my $clusterid = $item->{'clusterid'}+0;
1918 my $datakey = "$friendid $itemid";
1919 my $replycount = $logprops{$datakey}->{'replycount'};
1920 my $subject = $logtext->{$datakey}->[0];
1921 my $event = $logtext->{$datakey}->[1];
1923 my $repost_entry_obj;
1924 my $journalu = $entry_obj->journal;
1926 my $content = { 'original_post_obj' => \$entry_obj,
1927 'repost_obj' => \$repost_entry_obj,
1928 'ditemid' => \$ditemid,
1929 'journalu' => \$journalu,
1930 'posterid' => \$posterid,
1931 'security' => \$security,
1932 'event' => \$event,
1933 'subject' => \$subject,
1934 'reply_count' => \$replycount,
1935 'cluster_id' => \$clusterid, };
1937 my $repost_props = { 'use_repost_signature' => 1};
1939 if (LJ::Entry::Repost->substitute_content( $entry_obj, $content, $repost_props )) {
1940 next ENTRY unless LJ::Entry::Repost->is_visible_in_friendsfeed($entry_obj, $u);
1941 $friendid = $journalu->userid;
1942 $logprops{$itemid} = $entry_obj->props;
1943 $friends{$friendid} = $journalu;
1944 $pu = $entry_obj->poster;
1946 $datakey = "repost $friendid $itemid";
1948 if (!$reposts{$datakey}) {
1949 $reposts{$datakey} = 1;
1950 } else {
1951 $reposts{$datakey}++;
1954 if (!$logprops{$datakey}) {
1955 $logprops{$datakey} = $entry_obj->props;
1957 # mark as repost
1958 $logprops{$datakey}->{'repost'} = 'e';
1959 $logprops{$datakey}->{'repost_author'} = $pu && $pu->user;
1960 $logprops{$datakey}->{'repost_subject'} = $entry_obj->subject_html;
1961 $logprops{$datakey}->{'repost_url'} = $entry_obj->url;
1965 if ( ($logprops{$datakey}->{'repost'} &&
1966 $remote &&
1967 $remote->prop('hidefriendsreposts') && \
1968 ! $remote->prop('opt_ljcut_disable_friends')) ||
1969 $reposts{$datakey} > 1 )
1971 $event = LJ::Lang::ml(
1972 'friendsposts.reposted',
1974 'user' => $logprops{$datakey}->{'repost_author'},
1975 'subject' => $logprops{$datakey}->{'repost_subject'},
1976 'orig_url' => $logprops{$datakey}->{'repost_url'},
1977 'url' => $entry_obj->url,
1981 $entry_obj->handle_prefetched_props($logprops{$datakey});
1983 if ($get->{'nohtml'}) {
1984 # quote all non-LJ tags
1985 $subject =~ s{<(?!/?lj)(.*?)>} {&lt;$1&gt;}gi;
1986 $event =~ s{<(?!/?lj)(.*?)>} {&lt;$1&gt;}gi;
1989 if ($LJ::UNICODE && $logprops{$datakey}->{'unknown8bit'}) {
1990 LJ::item_toutf8($friends{$friendid}, \$subject, \$event, $logprops{$datakey});
1993 my ($friend, $poster);
1994 $friend = $poster = $friends{$friendid}->user;
1995 $poster = $pu && $pu->user;
1997 my %friends_date_format = LJ::alldateparts_to_hash($alldatepart);
1999 if ($lastday != $friends_date_format{'d'})
2001 my %friends_new_day = ();
2002 foreach (qw(dayshort daylong monshort monlong m mm yy yyyy d dd dth))
2004 $friends_new_day{$_} = $friends_date_format{$_};
2006 unless ($lastday==-1) {
2007 $$events .= LJ::fill_var_props($vars, 'FRIENDS_END_DAY', {});
2009 $$events .= LJ::fill_var_props($vars, 'FRIENDS_NEW_DAY', \%friends_new_day);
2010 $lastday = $friends_date_format{'d'};
2013 my %friends_event = ();
2014 $friends_event{'itemid'} = $itemid;
2015 $friends_event{'datetime'} = LJ::fill_var_props($vars, 'FRIENDS_DATE_FORMAT', \%friends_date_format);
2016 if ($subject ne "") {
2017 LJ::CleanHTML::clean_subject(\$subject);
2018 $friends_event{'subject'} = LJ::fill_var_props($vars, 'FRIENDS_SUBJECT', {
2019 "subject" => $subject,
2021 } else {
2022 $friends_event{'subject'} = LJ::fill_var_props($vars, 'FRIENDS_NO_SUBJECT', {
2023 "friend" => $friend,
2024 "name" => $friends{$friendid}->{'name'},
2028 my $itemargs = "journal=$friend&amp;itemid=$ditemid";
2029 $friends_event{'itemargs'} = $itemargs;
2031 my %urlopts_style;
2032 if ( $remote && $remote->{'opt_stylemine'}
2033 && $remote->userid != $friendid )
2035 $urlopts_style{'style'} = 'mine';
2038 my $suspend_msg = $entry_obj && $entry_obj->should_show_suspend_msg_to($remote) ? 1 : 0;
2040 LJ::CleanHTML::clean_event(
2041 \$event, {
2042 'preformatted' => $logprops{$datakey}->{'opt_preformatted'},
2043 'cuturl' => $entry_obj->url(%urlopts_style),
2044 'entry_url' => $entry_obj->url,
2045 'ljcut_disable' => $ljcut_disable,
2046 'suspend_msg' => $suspend_msg,
2047 'unsuspend_supportid' => $suspend_msg ? $entry_obj->prop("unsuspend_supportid") : 0,
2048 'journalid' => $entry_obj->journalid,
2049 'posterid' => $entry_obj->posterid,
2050 'img_placeholders' => $replace_images_in_friendspage,
2051 'video_placeholders' => $replace_video,
2054 LJ::expand_embedded(
2055 $friends{$friendid},
2056 $ditemid,
2057 $remote,
2058 \$event,
2059 'video_placeholders' => $replace_video,
2062 $event = LJ::ContentFlag->transform_post(
2063 'post' => $event,
2064 'journal' => $friends{$friendid},
2065 'remote' => $remote,
2066 'entry' => $entry_obj,
2069 $friends_event{'event'} = $event;
2071 # do the picture
2073 my $picid = $friends{$friendid}->{'defaultpicid'}; # this could be the shared journal pic
2074 my $picuserid = $friendid;
2075 if ($friendid != $posterid && ! $u->{'opt_usesharedpic'}) {
2076 if ($pu->{'defaultpicid'}) {
2077 $picid = $pu->{'defaultpicid'};
2078 $picuserid = $posterid;
2082 if (! $u->{'opt_usesharedpic'} || ($posterid == $friendid)) {
2083 my $pickw = LJ::Entry->userpic_kw_from_props($logprops{$datakey});
2084 my $alt_picid = LJ::get_picid_from_keyword($posterid, $pickw);
2085 if ($alt_picid) {
2086 LJ::load_userpics(\%userpics, [ $pu, $alt_picid ]);
2087 $picid = $alt_picid;
2088 $picuserid = $posterid;
2091 if ($picid) {
2092 $friends_event{'friendpic'} =
2093 LJ::fill_var_props($vars, 'FRIENDS_FRIENDPIC', {
2094 "src" => "$LJ::USERPIC_ROOT/$picid/$picuserid",
2095 "width" => $userpics{$picid}->{'width'},
2096 "height" => $userpics{$picid}->{'height'},
2101 if ($friend ne $poster) {
2102 $friends_event{'altposter'} =
2103 LJ::fill_var_props($vars, 'FRIENDS_ALTPOSTER', {
2104 "poster" => $poster,
2105 "owner" => $friend,
2106 "fgcolor" => $friends{$friendid}->{'fgcolor'} || "#000000",
2107 "bgcolor" => $friends{$friendid}->{'bgcolor'} || "#ffffff",
2111 # friends view specific:
2112 $friends_event{'user'} = $friend;
2113 $friends_event{'fgcolor'} = $friends{$friendid}->{'fgcolor'} || "#000000";
2114 $friends_event{'bgcolor'} = $friends{$friendid}->{'bgcolor'} || "#ffffff";
2116 my $journalbase = LJ::journal_base($friends{$friendid});
2117 my $permalink = $entry_obj->url;
2118 $friends_event{'permalink'} = $permalink;
2120 $friends_event{'subject'} = "<a href='$permalink'>" . $friends_event{'subject'} . "</a>";
2122 if ($entry_obj->comments_shown)
2124 my $dispreadlink = $replycount ||
2125 ($logprops{$datakey}->{'hasscreened'} && $remote && $remote->can_manage($friendid));
2127 my %urlopts_nc;
2128 if ( $replycount && $remote && $remote->{'opt_nctalklinks'} ) {
2129 $urlopts_nc{'nc'} = $replycount;
2132 my $readurl = $entry_obj->url( %urlopts_style, %urlopts_nc );
2133 my $posturl = $entry_obj->url( %urlopts_style, 'mode' => 'reply' );
2135 $friends_event{'talklinks'} = LJ::fill_var_props($vars, 'FRIENDS_TALK_LINKS', {
2136 'itemid' => $ditemid,
2137 'itemargs' => $itemargs,
2138 'urlpost' => $posturl,
2139 'urlread' => $readurl,
2140 'messagecount' => $replycount,
2141 'readlink' => $dispreadlink ? LJ::fill_var_props($vars, 'FRIENDS_TALK_READLINK', {
2142 'urlread' => $readurl,
2143 'messagecount' => $replycount,
2144 'mc-plural-s' => $replycount == 1 ? "" : "s",
2145 'mc-plural-es' => $replycount == 1 ? "" : "es",
2146 'mc-plural-ies' => $replycount == 1 ? "y" : "ies",
2147 }) : "",
2151 ## current stuff
2152 LJ::prepare_currents({
2153 'props' => \%logprops,
2154 'datakey' => $datakey,
2155 'vars' => $vars,
2156 'prefix' => "FRIENDS",
2157 'event' => \%friends_event,
2158 'user' => ($u->{'opt_forcemoodtheme'} eq "Y" ? $u :
2159 $friends{$friendid}),
2160 'entry_obj' => $entry_obj,
2163 if ($security eq "public") {
2164 $LJ::REQ_GLOBAL{'text_of_first_public_post'} ||= $event;
2167 my $var = 'FRIENDS_EVENT';
2168 if ($security eq "private" &&
2169 $vars->{'FRIENDS_EVENT_PRIVATE'}) { $var = 'FRIENDS_EVENT_PRIVATE'; }
2170 if ($security eq "usemask" &&
2171 $vars->{'FRIENDS_EVENT_PROTECTED'}) { $var = 'FRIENDS_EVENT_PROTECTED'; }
2173 if (!$repost_entry_obj) {
2174 if (LJ::is_enabled("delayed_entries")) {
2175 $var .= '_STICKY' if $entry_obj->is_sticky();
2177 } else {
2178 if (LJ::is_enabled("entry_reference")) {
2179 # $var .= '_REPOST';
2181 my $reposter = $repost_entry_obj->poster;
2182 my $ref_text = LJ::Lang::ml( 'entry.reference.reposter',
2183 { 'reposter' => LJ::ljuser($reposter) } );
2187 my $display_metadata = 1;
2188 LJ::run_hooks( 'substitute_entry_content', $entry_obj,
2189 \$friends_event{'event'},
2191 'subject' => \$friends_event{'subject'},
2192 'display_metadata' => \$display_metadata,
2196 if ( !$display_metadata ) {
2197 delete $friends_event{'Mood'};
2198 delete $friends_event{'Music'};
2201 LJ::run_hooks( 'blocked_entry_content', $entry_obj,
2203 'text' => \$friends_event{'event'},
2204 'subject' => \$friends_event{'subject'},
2208 $$events .= LJ::fill_var_props($vars, $var, \%friends_event);
2209 LJ::run_hook('notify_event_displayed', $entry_obj);
2211 my $ads = LJ::get_ads({
2212 location => 's1.ebox',
2213 s1_view => 'friends',
2214 journalu => $u,
2215 current_post_number => $eventnum,
2216 total_posts_number => scalar @items,
2218 if ($ads) {
2219 my $var_name = (exists $vars->{ADS_EVENT}) ? 'ADS_EVENT' : 'FRIENDS_EVENT';
2220 $$events .= LJ::fill_var_props($vars, $var_name, {event => $ads});
2222 } # end while
2224 $$events .= LJ::fill_var_props($vars, 'FRIENDS_END_DAY', {});
2225 $friends_page{'events'} = LJ::fill_var_props($vars, 'FRIENDS_EVENTS', \%friends_events);
2227 my $item_shown = $eventnum;
2228 my $item_total = @items;
2229 my $item_hidden = $item_total - $item_shown;
2231 ### set the range property (what entries are we looking at)
2233 if ($skip) {
2234 $friends_page{'range'} =
2235 LJ::fill_var_props($vars, 'FRIENDS_RANGE_HISTORY', {
2236 "numitems" => $item_shown,
2237 "skip" => $skip,
2239 } else {
2240 $friends_page{'range'} =
2241 LJ::fill_var_props($vars, 'FRIENDS_RANGE_MOSTRECENT', {
2242 "numitems" => $item_shown,
2246 my ($skip_f, $skip_b) = (0, 0);
2247 my %skiplinks;
2249 # $linkfilter is distinct from $filter: if user has a default view,
2250 # $filter is now set according to it but we don't want it to show in the links.
2251 # $incfilter may be true even if $filter is 0: user may use filter=0 to turn
2252 # off the default group
2253 my $linkfilter = $get->{'filter'} + 0;
2254 my $incfilter = defined $get->{'filter'};
2256 # if we've skipped down, then we can skip back up
2257 if ($skip) {
2258 $skip_f = 1;
2259 my %linkvars;
2261 $linkvars{'filter'} = $linkfilter if $incfilter;
2262 $linkvars{'show'} = $get->{'show'} if $get->{'show'} =~ /^\w+$/;
2263 $linkvars{'date'} = $get->{'date'} if $get->{'date'};
2265 my $newskip = $skip - $itemshow;
2266 if ($newskip > 0) { $linkvars{'skip'} = $newskip; }
2268 $skiplinks{'skipforward'} =
2269 LJ::fill_var_props($vars, 'FRIENDS_SKIP_FORWARD', {
2270 "numitems" => $itemshow,
2271 "url" => LJ::make_link($base, \%linkvars),
2275 ## unless we didn't even load as many as we were expecting on this
2276 ## page, then there are more (unless there are exactly the number shown
2277 ## on the page, but who cares about that)
2279 unless ($item_total != $itemshow || $skip == $maxskip) {
2280 $skip_b = 1;
2281 my %linkvars;
2283 $linkvars{'filter'} = $linkfilter if $incfilter;
2284 $linkvars{'show'} = $get->{'show'} if $get->{'show'} =~ /^\w+$/;
2285 $linkvars{'date'} = $get->{'date'} if $get->{'date'};
2287 my $newskip = $skip + $itemshow;
2288 $linkvars{'skip'} = $newskip;
2290 $skiplinks{'skipbackward'} =
2291 LJ::fill_var_props($vars, 'FRIENDS_SKIP_BACKWARD', {
2292 "numitems" => $itemshow,
2293 "url" => LJ::make_link($base, \%linkvars),
2297 ### if they're both on, show a spacer
2298 if ($skip_f && $skip_b) {
2299 $skiplinks{'skipspacer'} = $vars->{'FRIENDS_SKIP_SPACER'};
2302 ### if either are on, put skiplinks into lastn_page
2303 if ($skip_b || $skip_f) {
2304 $friends_page{'skiplinks'} =
2305 LJ::fill_var_props($vars, 'FRIENDS_SKIP_LINKS', \%skiplinks);
2308 ## ads and control strip
2309 my $vetical_ad = LJ::get_ads({
2310 location => 's1.vertical',
2311 s1_view => 'friends',
2312 journalu => $u,
2313 pubtext => $LJ::REQ_GLOBAL{'text_of_first_public_post'},
2314 total_posts_number => scalar @items,
2316 my $bottom_ad = LJ::get_ads({
2317 location => 's1.bottom',
2318 s1_view => 'friends',
2319 journalu => $u,
2320 pubtext => $LJ::REQ_GLOBAL{'text_of_first_public_post'},
2321 total_posts_number => scalar @items,
2323 if ($vetical_ad || $bottom_ad) {
2324 $friends_page{'skyscraper_ad'} = LJ::fill_var_props($vars, 'FRIENDS_SKYSCRAPER_AD', { ad => $vetical_ad });
2325 $friends_page{'5linkunit_ad'} = LJ::fill_var_props($vars, 'FRIENDS_5LINKUNIT_AD', { ad => $bottom_ad });
2326 $friends_page{'open_skyscraper_ad'} = $vars->{'FRIENDS_OPEN_SKYSCRAPER_AD'};
2327 $friends_page{'close_skyscraper_ad'} = $vars->{'FRIENDS_CLOSE_SKYSCRAPER_AD'};
2330 if ($LJ::USE_CONTROL_STRIP && $show_control_strip) {
2331 my $control_strip = LJ::control_strip(user => $u->{user});
2332 $friends_page{'control_strip'} = $control_strip . LJ::get_ads({location => 'common.banner'});
2335 $$ret .= "<base target='_top' />" if ($get->{'mode'} eq "framed");
2336 $$ret .= LJ::fill_var_props($vars, 'FRIENDS_PAGE', \%friends_page);
2338 return 1;
2341 # the creator for the 'calendar' view:
2342 sub create_view_calendar
2344 my ($ret, $u, $vars, $remote, $opts) = @_;
2346 my $user = $u->{'user'};
2348 foreach ("name", "url", "urlname", "journaltitle") { LJ::text_out(\$u->{$_}); }
2350 my $get = $opts->{'getargs'};
2352 my %calendar_page = ();
2353 $calendar_page{'name'} = LJ::ehtml($u->{'name'});
2354 $calendar_page{'name-\'s'} = ($u->{'name'} =~ /s$/i) ? "'" : "'s";
2355 $calendar_page{'username'} = $user;
2356 $calendar_page{'title'} = LJ::ehtml($u->{'journaltitle'} ||
2357 $u->{'name'} . $calendar_page{'name-\'s'} . " Journal");
2359 $calendar_page{'head'} = "";
2361 if (LJ::are_hooks('s2_head_content_extra')) {
2362 LJ::run_hooks('s2_head_content_extra', \$calendar_page{'head'}, $remote, $opts->{r});
2364 LJ::run_hooks('head_content', \$calendar_page{'head'});
2366 if ($u->should_block_robots) {
2367 $calendar_page{'head'} .= LJ::robot_meta_tags();
2369 if ($LJ::UNICODE) {
2370 $calendar_page{'head'} .= '<meta http-equiv="Content-Type" content="text/html; charset='.$opts->{'saycharset'}.'" />';
2373 my $show_control_strip = LJ::run_hook('show_control_strip', {
2374 user => $u->{user},
2376 if ($show_control_strip) {
2377 LJ::run_hook('control_strip_stylesheet_link', {
2378 user => $u->{user},
2380 LJ::control_strip_js_inject( user => $u->{user} );
2382 LJ::journal_js_inject();
2384 $calendar_page{'head'} .=
2385 $vars->{'GLOBAL_HEAD'} . "\n" . $vars->{'CALENDAR_HEAD'};
2387 LJ::run_hooks("need_res_for_journals", $u);
2388 my $graphicpreviews_obj = LJ::graphicpreviews_obj();
2389 $graphicpreviews_obj->need_res($u);
2390 my $extra_js = LJ::statusvis_message_js($u);
2391 $calendar_page{'head'} .= LJ::res_includes() . LJ::res_includes({only_needed => 1, only_tmpl => 1}) . $extra_js;
2393 $calendar_page{'months'} = "";
2395 if ($u->{'url'} =~ m!^https?://!) {
2396 $calendar_page{'website'} =
2397 LJ::fill_var_props($vars, 'CALENDAR_WEBSITE', {
2398 "url" => LJ::ehtml($u->{'url'}),
2399 "name" => LJ::ehtml($u->{'urlname'} || "My Website"),
2403 my $journalbase = LJ::journal_base($user, $opts->{'vhost'});
2405 $calendar_page{'urlfriends'} = "$journalbase/friends";
2406 $calendar_page{'urllastn'} = "$journalbase/";
2408 ## ads and control strip
2409 my $vertical_ad = LJ::get_ads({
2410 location => 's1.vertical',
2411 s1_view => 'calendar',
2412 journalu => $u,
2413 pubtext => $LJ::REQ_GLOBAL{'text_of_first_public_post'},
2415 my $bottom_ad = LJ::get_ads({
2416 location => 's1.bottom',
2417 s1_view => 'calendar',
2418 journalu => $u,
2419 pubtext => $LJ::REQ_GLOBAL{'text_of_first_public_post'},
2421 if ($vertical_ad || $bottom_ad) {
2422 $calendar_page{'skyscraper_ad'} = LJ::fill_var_props($vars, 'CALENDAR_SKYSCRAPER_AD', { ad => $vertical_ad });
2423 $calendar_page{'5linkunit_ad'} = LJ::fill_var_props($vars, 'CALENDAR_5LINKUNIT_AD', { ad => $bottom_ad });
2424 $calendar_page{'open_skyscraper_ad'} = $vars->{'CALENDAR_OPEN_SKYSCRAPER_AD'};
2425 $calendar_page{'close_skyscraper_ad'} = $vars->{'CALENDAR_CLOSE_SKYSCRAPER_AD'};
2427 if ($LJ::USE_CONTROL_STRIP && $show_control_strip) {
2428 my $control_strip = LJ::control_strip(user => $u->{user});
2429 $calendar_page{'control_strip'} = $control_strip . LJ::get_ads({location => 'common.banner'});
2432 my $months = \$calendar_page{'months'};
2434 my $quserid = int($u->{'userid'});
2435 my $maxyear = 0;
2437 my $daycts = LJ::get_daycounts($u, $remote);
2438 unless ($daycts) {
2439 $opts->{'errcode'} = "nodb";
2440 $$ret = "";
2441 return 0;
2444 my (%count, %dayweek);
2445 foreach my $dy (@$daycts) {
2446 my ($year, $month, $day, $count) = @$dy;
2448 # calculate day of week
2449 my $time = eval { Time::Local::timegm(0, 0, 0, $day, $month-1, $year) } ||
2450 eval { Time::Local::timegm(0, 0, 0, LJ::TimeUtil->days_in_month($month, $year), $month-1, $year) } ||
2452 next unless $time;
2454 my $dayweek = (gmtime($time))[6] + 1;
2456 $count{$year}->{$month}->{$day} = $count;
2457 $dayweek{$year}->{$month}->{$day} = $dayweek;
2458 if ($year > $maxyear) { $maxyear = $year; }
2461 my @allyears = sort { $b <=> $a } keys %count;
2462 if ($vars->{'CALENDAR_SORT_MODE'} eq "forward") { @allyears = reverse @allyears; }
2464 my @years = ();
2465 my $dispyear = $get->{'year'}; # old form was /users/<user>/calendar?year=1999
2467 # but the new form is purtier: */calendar/2001
2468 # but the NEWER form is purtier: */2001
2469 unless ($dispyear) {
2470 if ($opts->{'pathextra'} =~ m!^/(\d\d\d\d)/?\b!) {
2471 $dispyear = $1;
2475 # else... default to the year they last posted.
2476 $dispyear ||= $maxyear;
2478 # we used to show multiple years. now we only show one at a time: (hence the @years confusion)
2479 if ($dispyear) { push @years, $dispyear; }
2481 if (scalar(@allyears) > 1) {
2482 my $yearlinks = "";
2483 foreach my $year (@allyears) {
2484 my $yy = sprintf("%02d", $year % 100);
2485 my $url = "$journalbase/$year/";
2486 if ($year != $dispyear) {
2487 $yearlinks .= LJ::fill_var_props($vars, 'CALENDAR_YEAR_LINK', {
2488 "url" => $url, "yyyy" => $year, "yy" => $yy });
2489 } else {
2490 $yearlinks .= LJ::fill_var_props($vars, 'CALENDAR_YEAR_DISPLAYED', {
2491 "yyyy" => $year, "yy" => $yy });
2494 $calendar_page{'yearlinks'} =
2495 LJ::fill_var_props($vars, 'CALENDAR_YEAR_LINKS', { "years" => $yearlinks });
2498 foreach my $year (@years)
2500 $$months .= LJ::fill_var_props($vars, 'CALENDAR_NEW_YEAR', {
2501 'yyyy' => $year,
2502 'yy' => substr($year, 2, 2),
2505 my @months = sort { $b <=> $a } keys %{$count{$year}};
2506 if ($vars->{'CALENDAR_SORT_MODE'} eq "forward") { @months = reverse @months; }
2507 foreach my $month (@months)
2509 my $daysinmonth = LJ::TimeUtil->days_in_month($month, $year);
2511 # this picks a random day there were journal entries (thus, we know
2512 # the %dayweek from above) from that we go backwards and forwards
2513 # to find the rest of the days of week
2514 my $firstday = (%{$count{$year}->{$month}})[0];
2516 # go backwards from first day
2517 my $dayweek = $dayweek{$year}->{$month}->{$firstday};
2518 for (my $i=$firstday-1; $i>0; $i--)
2520 if (--$dayweek < 1) { $dayweek = 7; }
2521 $dayweek{$year}->{$month}->{$i} = $dayweek;
2523 # go forwards from first day
2524 $dayweek = $dayweek{$year}->{$month}->{$firstday};
2525 for (my $i=$firstday+1; $i<=$daysinmonth; $i++)
2527 if (++$dayweek > 7) { $dayweek = 1; }
2528 $dayweek{$year}->{$month}->{$i} = $dayweek;
2531 my %calendar_month = ();
2532 $calendar_month{'monlong'} = LJ::Lang::month_long($month);
2533 $calendar_month{'monshort'} = LJ::Lang::month_short($month);
2534 $calendar_month{'yyyy'} = $year;
2535 $calendar_month{'yy'} = substr($year, 2, 2);
2536 $calendar_month{'weeks'} = "";
2537 $calendar_month{'urlmonthview'} = sprintf("$journalbase/%04d/%02d/", $year, $month);
2538 my $weeks = \$calendar_month{'weeks'};
2540 my %calendar_week = ();
2541 $calendar_week{'emptydays_beg'} = "";
2542 $calendar_week{'emptydays_end'} = "";
2543 $calendar_week{'days'} = "";
2545 # start the first row and check for its empty spaces
2546 my $rowopen = 1;
2547 if ($dayweek{$year}->{$month}->{1} != 1)
2549 my $spaces = $dayweek{$year}->{$month}->{1} - 1;
2550 $calendar_week{'emptydays_beg'} =
2551 LJ::fill_var_props($vars, 'CALENDAR_EMPTY_DAYS',
2552 { 'numempty' => $spaces });
2555 # make the days!
2556 my $days = \$calendar_week{'days'};
2558 for (my $i=1; $i<=$daysinmonth; $i++)
2560 $count{$year}->{$month}->{$i} += 0;
2561 if (! $rowopen) { $rowopen = 1; }
2563 my %calendar_day = ();
2564 $calendar_day{'d'} = $i;
2565 $calendar_day{'eventcount'} = $count{$year}->{$month}->{$i};
2566 if ($count{$year}->{$month}->{$i})
2568 $calendar_day{'dayevent'} = LJ::fill_var_props($vars, 'CALENDAR_DAY_EVENT', {
2569 'eventcount' => $count{$year}->{$month}->{$i},
2570 'dayurl' => "$journalbase/" . sprintf("%04d/%02d/%02d/", $year, $month, $i),
2573 else
2575 $calendar_day{'daynoevent'} = $vars->{'CALENDAR_DAY_NOEVENT'};
2578 $$days .= LJ::fill_var_props($vars, 'CALENDAR_DAY', \%calendar_day);
2580 if ($dayweek{$year}->{$month}->{$i} == 7)
2582 $$weeks .= LJ::fill_var_props($vars, 'CALENDAR_WEEK', \%calendar_week);
2583 $rowopen = 0;
2584 $calendar_week{'emptydays_beg'} = "";
2585 $calendar_week{'emptydays_end'} = "";
2586 $calendar_week{'days'} = "";
2590 # if rows is still open, we have empty spaces
2591 if ($rowopen)
2593 if ($dayweek{$year}->{$month}->{$daysinmonth} != 7)
2595 my $spaces = 7 - $dayweek{$year}->{$month}->{$daysinmonth};
2596 $calendar_week{'emptydays_end'} =
2597 LJ::fill_var_props($vars, 'CALENDAR_EMPTY_DAYS',
2598 { 'numempty' => $spaces });
2600 $$weeks .= LJ::fill_var_props($vars, 'CALENDAR_WEEK', \%calendar_week);
2603 $$months .= LJ::fill_var_props($vars, 'CALENDAR_MONTH', \%calendar_month);
2604 } # end foreach months
2606 } # end foreach years
2608 ######## new code
2610 $$ret .= LJ::fill_var_props($vars, 'CALENDAR_PAGE', \%calendar_page);
2612 return 1;
2615 # the creator for the 'day' view:
2616 sub create_view_day
2618 my ($ret, $u, $vars, $remote, $opts) = @_;
2619 my $sth;
2621 my $user = $u->{'user'};
2623 foreach ("name", "url", "urlname", "journaltitle") { LJ::text_out(\$u->{$_}); }
2625 my %day_page = ();
2626 $day_page{'username'} = $user;
2627 $day_page{'head'} = "";
2629 if (LJ::are_hooks('s2_head_content_extra')) {
2630 LJ::run_hooks('s2_head_content_extra', \$day_page{'head'}, $remote, $opts->{r});
2632 LJ::run_hooks('head_content', \$day_page{'head'});
2634 if ($u->should_block_robots) {
2635 $day_page{'head'} .= LJ::robot_meta_tags();
2637 if ($LJ::UNICODE) {
2638 $day_page{'head'} .= '<meta http-equiv="Content-Type" content="text/html; charset='.$opts->{'saycharset'}.'" />';
2641 my $show_control_strip = LJ::run_hook('show_control_strip', {
2642 user => $u->{user},
2644 if ($show_control_strip) {
2645 LJ::run_hook('control_strip_stylesheet_link', {
2646 user => $u->{user},
2648 LJ::control_strip_js_inject( user => $u->{user} );
2650 LJ::journal_js_inject();
2652 LJ::run_hooks("need_res_for_journals", $u);
2653 my $graphicpreviews_obj = LJ::graphicpreviews_obj();
2654 $graphicpreviews_obj->need_res($u);
2655 my $extra_js = LJ::statusvis_message_js($u);
2656 $day_page{'head'} .= LJ::res_includes() . LJ::res_includes({only_needed => 1, only_tmpl => 1}) . $extra_js;
2658 $day_page{'head'} .=
2659 $vars->{'GLOBAL_HEAD'} . "\n" . $vars->{'DAY_HEAD'};
2660 $day_page{'name'} = LJ::ehtml($u->{'name'});
2661 $day_page{'name-\'s'} = ($u->{'name'} =~ /s$/i) ? "'" : "'s";
2662 $day_page{'title'} = LJ::ehtml($u->{'journaltitle'} ||
2663 $u->{'name'} . $day_page{'name-\'s'} . " Journal");
2665 if ($u->{'url'} =~ m!^https?://!) {
2666 $day_page{'website'} =
2667 LJ::fill_var_props($vars, 'DAY_WEBSITE', {
2668 "url" => LJ::ehtml($u->{'url'}),
2669 "name" => LJ::ehtml($u->{'urlname'} || "My Website"),
2673 my $journalbase = LJ::journal_base($user, $opts->{'vhost'});
2674 $day_page{'urlfriends'} = "$journalbase/friends";
2675 $day_page{'urlcalendar'} = "$journalbase/calendar";
2676 $day_page{'urllastn'} = "$journalbase/";
2678 my $initpagedates = 0;
2680 my $get = $opts->{'getargs'};
2682 my $month = $get->{'month'};
2683 my $day = $get->{'day'};
2684 my $year = $get->{'year'};
2685 my @errors = ();
2687 if ($opts->{'pathextra'} =~ m!^(?:/day)?/(\d\d\d\d)/(\d\d)/(\d\d)\b!) {
2688 ($month, $day, $year) = ($2, $3, $1);
2691 if ($year !~ /^\d+$/) { push @errors, "Corrupt or non-existant year."; }
2692 if ($month !~ /^\d+$/) { push @errors, "Corrupt or non-existant month."; }
2693 if ($day !~ /^\d+$/) { push @errors, "Corrupt or non-existant day."; }
2694 if ($month < 1 || $month > 12 || int($month) != $month) { push @errors, "Invalid month."; }
2695 if ($year < 1970 || $year > 2038 || int($year) != $year) { push @errors, "Invalid year: $year"; }
2696 if ($day < 1 || $day > 31 || int($day) != $day) { push @errors, "Invalid day."; }
2697 if (scalar(@errors)==0 && $day > LJ::TimeUtil->days_in_month($month, $year)) { push @errors, "That month doesn't have that many days."; }
2699 if (@errors) {
2700 $$ret .= "Errors occurred processing this page:\n<ul>\n";
2701 foreach (@errors) {
2702 $$ret .= "<li>$_</li>\n";
2704 $$ret .= "</ul>\n";
2705 return 0;
2708 my $logdb = LJ::get_cluster_reader($u);
2709 unless ($logdb) {
2710 $opts->{'errcode'} = "nodb";
2711 $$ret = "";
2712 return 0;
2715 my $optDESC = $vars->{'DAY_SORT_MODE'} eq "reverse" ? "DESC" : "";
2717 my $secwhere = "AND security='public'";
2718 my $viewall = 0;
2719 my $viewsome = 0;
2720 if ($remote) {
2722 # do they have the viewall priv?
2723 if ($get->{'viewall'} && LJ::check_priv($remote, "canview", "suspended")) {
2724 LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
2725 "viewall", "day: $user, statusvis: $u->{'statusvis'}");
2726 $viewall = LJ::check_priv($remote, 'canview', '*');
2727 $viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended');
2730 if ($remote && $remote->can_manage($u) || $viewall) {
2731 $secwhere = ""; # see everything
2732 } elsif ($remote->{'journaltype'} eq 'P') {
2733 my $gmask = LJ::get_groupmask($u, $remote);
2734 $secwhere = "AND (security='public' OR (security='usemask' AND allowmask & $gmask))"
2735 if $gmask;
2739 # load the log items
2740 my $dateformat = "%a %W %b %M %y %Y %c %m %e %d %D %p %i %l %h %k %H";
2741 $sth = $logdb->prepare("SELECT jitemid AS itemid, posterid, security, ".
2742 " DATE_FORMAT(eventtime, \"$dateformat\") AS 'alldatepart', anum " .
2743 "FROM log2 " .
2744 "WHERE journalid=? AND year=? AND month=? AND day=? $secwhere " .
2745 "ORDER BY eventtime $optDESC, logtime $optDESC LIMIT 200");
2746 $sth->execute($u->{'userid'}, $year, $month, $day);
2747 my @items;
2748 push @items, $_ while $_ = $sth->fetchrow_hashref;
2749 my @itemids = map { $_->{'itemid'} } @items;
2751 # load 'opt_ljcut_disable_lastn' prop for $remote.
2752 LJ::load_user_props($remote, "opt_ljcut_disable_lastn");
2754 ### load the log properties
2755 my %logprops = ();
2756 LJ::load_log_props2($logdb, $u->{'userid'}, \@itemids, \%logprops);
2757 my $logtext = LJ::get_logtext2($u, @itemids);
2759 my %posteru = (); # map posterids to u objects
2760 LJ::load_userids_multiple([map { $_->{'posterid'}, \$posteru{$_->{'posterid'}} } @items], [$u]);
2762 my $events = "";
2763 my $eventnum = 0;
2765 my $ljcut_disable = $remote ? $remote->prop("opt_ljcut_disable_lastn") : undef;
2766 my $replace_video = $remote ? $remote->opt_embedplaceholders : 0;
2768 ENTRY:
2769 foreach my $item (@items) {
2770 my ($itemid, $posterid, $security, $alldatepart, $anum) =
2771 map { $item->{$_} } qw(itemid posterid security alldatepart anum);
2773 my $ditemid = $itemid*256 + $anum;
2774 my $entry_obj = LJ::Entry->new($u, ditemid => $ditemid);
2775 $entry_obj->handle_prefetched_props($logprops{$itemid});
2777 my $pu = $posteru{$posterid};
2778 next ENTRY if $pu && $pu->{'statusvis'} eq 'S' && !$viewsome;
2779 next ENTRY if $entry_obj && $entry_obj->is_suspended_for($remote);
2781 my $username = $user;
2782 my $journalu = $u;
2784 if ( !$viewsome && $pu && $pu->is_deleted
2785 && !$LJ::JOURNALS_WITH_PROTECTED_CONTENT{$pu->username} )
2787 my ($purge_comments, $purge_community_entries)
2788 = split /:/, $pu->prop("purge_external_content");
2790 next ENTRY if $purge_community_entries;
2793 $eventnum++;
2794 my $replycount = $logprops{$itemid}->{'replycount'};
2795 my $subject = $logtext->{$itemid}->[0];
2796 my $event = $logtext->{$itemid}->[1];
2798 my $repost_entry_obj;
2799 my $removed;
2800 my $content = { 'original_post_obj' => \$entry_obj,
2801 'repost_obj' => \$repost_entry_obj,
2802 'ditemid' => \$ditemid,
2803 'journalu' => \$journalu,
2804 'posterid' => \$posterid,
2805 'security' => \$security,
2806 'event' => \$event,
2807 'subject' => \$subject,
2808 'removed' => \$removed,
2809 'reply_count' => \$replycount };
2811 my $repost_props = { 'use_repost_signature' => 1};
2813 if (LJ::Entry::Repost->substitute_content( $entry_obj, $content, $repost_props )) {
2814 next ENTRY if $removed;
2815 $username = $entry_obj->poster->user;
2816 $logprops{$itemid} = $entry_obj->props;
2819 if ($LJ::UNICODE &&
2820 ( $entry_obj->prop("unknown8bit") || $logprops{$itemid}->{'unknown8bit'} )) {
2821 LJ::item_toutf8($journalu, \$subject, \$event, $logprops{$itemid});
2824 my %day_date_format = LJ::alldateparts_to_hash($alldatepart);
2826 unless ($initpagedates++) {
2827 foreach (qw(dayshort daylong monshort monlong yy yyyy m mm d dd dth)) {
2828 $day_page{$_} = $day_date_format{$_};
2832 my %day_event = ();
2833 $day_event{'itemid'} = $itemid;
2834 $day_event{'datetime'} = LJ::fill_var_props($vars, 'DAY_DATE_FORMAT', \%day_date_format);
2836 if ($subject ne "") {
2837 LJ::CleanHTML::clean_subject(\$subject);
2838 $day_event{'subject'} = LJ::fill_var_props($vars, 'DAY_SUBJECT', {
2839 "subject" => $subject,
2843 my $itemargs = "journal=$username&amp;itemid=$ditemid";
2845 $day_event{'itemargs'} = $itemargs;
2847 my $suspend_msg = $entry_obj && $entry_obj->should_show_suspend_msg_to($remote) ? 1 : 0;
2849 LJ::CleanHTML::clean_event(
2850 \$event,
2852 'preformatted' => $logprops{$itemid}->{'opt_preformatted'},
2853 'cuturl' => $entry_obj->url,
2854 'entry_url' => $entry_obj->url,
2855 'ljcut_disable' => $ljcut_disable,
2856 'suspend_msg' => $suspend_msg,
2857 'unsuspend_supportid' => $suspend_msg ? $entry_obj->prop("unsuspend_supportid") : 0,
2858 'journalid' => $entry_obj->journalid,
2859 'posterid' => $entry_obj->posterid,
2860 'video_placeholders' => $replace_video,
2863 LJ::expand_embedded(
2864 $journalu,
2865 $ditemid,
2866 $remote,
2867 \$event,
2868 'video_placeholders' => $replace_video,
2871 $event = LJ::ContentFlag->transform_post(
2872 'post' => $event,
2873 'journal' => $u,
2874 'remote' => $remote,
2875 'entry' => $entry_obj,
2878 $day_event{'event'} = $event;
2880 my $permalink = $entry_obj->url;
2881 $day_event{'permalink'} = $permalink;
2883 $day_event{'subject'} = "<a href='$permalink'>" . $day_event{'subject'} . "</a>";
2885 if ($entry_obj->comments_shown)
2887 my $nc;
2888 $nc = "nc=$replycount" if $replycount && $remote && $remote->{'opt_nctalklinks'};
2890 my $posturl = LJ::Talk::talkargs($permalink, "mode=reply");
2891 my $readurl = LJ::Talk::talkargs($permalink, $nc);
2893 my $dispreadlink = $replycount ||
2894 ($logprops{$itemid}->{'hasscreened'} && $remote && $remote->can_manage($u));
2895 $day_event{'talklinks'} = LJ::fill_var_props($vars, 'DAY_TALK_LINKS', {
2896 'itemid' => $ditemid,
2897 'itemargs' => $itemargs,
2898 'urlpost' => $posturl,
2899 'urlread' => $readurl,
2900 'messagecount' => $replycount,
2901 'readlink' => $dispreadlink ? LJ::fill_var_props($vars, 'DAY_TALK_READLINK', {
2902 'urlread' => $readurl,
2903 'messagecount' => $replycount,
2904 'mc-plural-s' => $replycount == 1 ? "" : "s",
2905 'mc-plural-es' => $replycount == 1 ? "" : "es",
2906 'mc-plural-ies' => $replycount == 1 ? "y" : "ies",
2907 }) : "",
2911 ## current stuff
2912 LJ::prepare_currents({
2913 'props' => \%logprops,
2914 'itemid' => $itemid,
2915 'vars' => $vars,
2916 'prefix' => "DAY",
2917 'event' => \%day_event,
2918 'user' => $journalu,
2919 'entry_obj' => $entry_obj,
2922 my $var = 'DAY_EVENT';
2923 if ($security eq "private" &&
2924 $vars->{'DAY_EVENT_PRIVATE'}) { $var = 'DAY_EVENT_PRIVATE'; }
2925 if ($security eq "usemask" &&
2926 $vars->{'DAY_EVENT_PROTECTED'}) { $var = 'DAY_EVENT_PROTECTED'; }
2928 if (!$repost_entry_obj) {
2929 if (LJ::is_enabled("delayed_entries")) {
2930 $var .= '_STICKY' if $entry_obj->is_sticky();
2932 } else {
2933 if (LJ::is_enabled("entry_reference")) {
2934 # $var .= '_REPOST';
2936 my $reposter = $repost_entry_obj->poster;
2937 my $ref_text = LJ::Lang::ml( 'entry.reference.reposter',
2938 { 'reposter' => LJ::ljuser($reposter) } );
2942 my $display_metadata = 1;
2943 LJ::run_hooks( 'substitute_entry_content', $entry_obj,
2944 \$day_event{'event'},
2946 'subject' => \$day_event{'subject'},
2947 'display_metadata' => \$display_metadata,
2951 if ( !$display_metadata ) {
2952 delete $day_event{'Mood'};
2953 delete $day_event{'Music'};
2956 LJ::run_hooks( 'blocked_entry_content', $entry_obj,
2958 'text' => \$day_event{'event'},
2959 'subject' => \$day_event{'subject'},
2963 $events .= LJ::fill_var_props($vars, $var, \%day_event);
2965 my $ads = LJ::get_ads({
2966 location => 's1.ebox',
2967 s1_view => 'day',
2968 journalu => $journalu,
2969 current_post_number => $eventnum,
2970 total_posts_number => scalar @items,
2972 if ($ads) {
2973 my $var_name = (exists $vars->{ADS_EVENT}) ? 'ADS_EVENT' : 'DAY_EVENT';
2974 $events .= LJ::fill_var_props($vars, $var_name, {event => $ads});
2977 LJ::run_hook('notify_event_displayed', $entry_obj); ## ---
2980 ## ads and control strip
2981 my $vetical_ad = LJ::get_ads({
2982 location => 's1.vertical',
2983 s1_view => 'day',
2984 journalu => $u,
2985 pubtext => $LJ::REQ_GLOBAL{'text_of_first_public_post'},
2986 total_posts_number => scalar @items,
2988 my $bottom_ad = LJ::get_ads({
2989 location => 's1.bottom',
2990 s1_view => 'day',
2991 journalu => $u,
2992 pubtext => $LJ::REQ_GLOBAL{'text_of_first_public_post'},
2993 total_posts_number => scalar @items,
2995 if ($vetical_ad || $bottom_ad) {
2996 $day_page{'skyscraper_ad'} = LJ::fill_var_props($vars, 'DAY_SKYSCRAPER_AD', { ad => $vetical_ad });
2997 $day_page{'5linkunit_ad'} = LJ::fill_var_props($vars, 'DAY_5LINKUNIT_AD', { ad => $bottom_ad });
2998 $day_page{'open_skyscraper_ad'} = $vars->{'DAY_OPEN_SKYSCRAPER_AD'};
2999 $day_page{'close_skyscraper_ad'} = $vars->{'DAY_CLOSE_SKYSCRAPER_AD'};
3002 if ($LJ::USE_CONTROL_STRIP && $show_control_strip) {
3003 my $control_strip = LJ::control_strip(user => $u->{user});
3004 $day_page{'control_strip'} = $control_strip . LJ::get_ads({location => 'common.banner'});
3006 if (! $initpagedates)
3008 # if no entries were on that day, we haven't populated the time shit!
3009 # FIXME: don't use the database for this. it can be done in Perl.
3010 my $dbr = LJ::get_db_reader();
3011 $sth = $dbr->prepare("SELECT DATE_FORMAT('$year-$month-$day', '%a %W %b %M %y %Y %c %m %e %d %D') AS 'alldatepart'");
3012 $sth->execute;
3013 my @dateparts = split(/ /, $sth->fetchrow_arrayref->[0]);
3014 foreach (qw(dayshort daylong monshort monlong yy yyyy m mm d dd dth))
3016 $day_page{$_} = shift @dateparts;
3019 $day_page{'events'} = LJ::fill_var_props($vars, 'DAY_NOEVENTS', {});
3021 else
3023 $day_page{'events'} = LJ::fill_var_props($vars, 'DAY_EVENTS', { 'events' => $events });
3024 $events = ""; # free some memory maybe
3027 # find near days
3028 my $days = LJ::get_daycounts($u, $remote) || [];
3029 my $numeric = ($year * 12 + $month) * 31 + $day; # applicable for date compare
3030 my $prev = 0; # numeric
3031 my ($pyear, $pmonth, $pday);
3032 my $next = (2038 * 12 + 12) * 31 + 31; # max impossible
3033 my ($nyear, $nmonth, $nday);
3034 foreach my $count (@$days) {
3035 my ($hyear, $hmonth, $hday, $hcount) = @$count;
3037 my $here = ($hyear * 12 + $hmonth) * 31 + $hday; # applicable for date compare
3038 if ($here < $numeric) { # candidate for prev role
3039 next if $here <= $prev; # $prev is max of all previous
3040 ($pyear, $pmonth, $pday) = ($hyear, $hmonth, $hday);
3041 $prev = $here;
3042 } elsif ($here > $numeric) { # candidate for next role
3043 next if $here >= $next; # $next is min of all next
3044 ($nyear, $nmonth, $nday) = ($hyear, $hmonth, $hday);
3045 $next = $here;
3049 $day_page{'prevday_url'} = defined $pyear ? ("$journalbase/" . sprintf("%04d/%02d/%02d/", $pyear, $pmonth, $pday)) : '';
3050 $day_page{'nextday_url'} = defined $nyear ? ("$journalbase/" . sprintf("%04d/%02d/%02d/", $nyear, $nmonth, $nday)) : '';
3052 $day_page{'prevday_link'} = defined $pyear ? "<li><a href=\"$day_page{'prevday_url'}\">Previous Day</a></li>" : '';
3053 $day_page{'nextday_link'} = defined $nyear ? "<li><a href=\"$day_page{'nextday_url'}\">Next Day</a></li>" : '';
3054 $day_page{'prevday_2link'} = defined $pyear ? "<A HREF=\"$day_page{'prevday_url'}\">&lt;&lt; Previous Day</A>" : '';
3055 $day_page{'nextday_2link'} = defined $nyear ? "<A HREF=\"$day_page{'nextday_url'}\">Next Day &gt;&gt;</A>" : '';
3056 $day_page{'prevday_3link'} = defined $pyear ? "<a href=\"$day_page{'prevday_url'}\">Previous</a>" : '';
3057 $day_page{'nextday_3link'} = defined $nyear ? "<a href=\"$day_page{'nextday_url'}\">Next</a>" : '';
3058 $day_page{'prevday_4link'} = defined $pyear ? "&larr; <a href=\"$day_page{'prevday_url'}\">Previous day</a>" : '';
3059 $day_page{'nextday_4link'} = defined $nyear ? "<a href=\"$day_page{'nextday_url'}\">Next day</a> &rarr;" : '';
3060 $day_page{'prevday_5link'} = defined $pyear ? "<A HREF=\"$day_page{'prevday_url'}\">&lt;&lt; previous day</A>" : '';
3061 $day_page{'nextday_5link'} = defined $nyear ? "<A HREF=\"$day_page{'nextday_url'}\">next day &gt;&gt;</A>" : '';
3062 $day_page{'prevday_6link'} = defined $pyear ? "<A HREF=\"$day_page{'prevday_url'}\">previous day</A>" : '';
3063 $day_page{'nextday_6link'} = defined $nyear ? "<A HREF=\"$day_page{'nextday_url'}\">next day</A>" : '';
3064 if (defined $pyear and not defined $nyear) {
3065 $day_page{'prevnextday_link'} = "<a href=\"$day_page{'prevday_url'}\">previous day</a>";
3066 $day_page{'prevnextday_2link'} = "<a href=\"$day_page{'prevday_url'}\">Previous&nbsp;day</a>";
3067 $day_page{'prevnextday_3link'} = "<A HREF=\"$day_page{'prevday_url'}\">Back A Day</A>";
3068 $day_page{'prevnextday_4link'} = "<a href=\"$day_page{'prevday_url'}\">previous day</a>";
3069 } elsif (not defined $pyear and defined $nyear) {
3070 $day_page{'prevnextday_link'} = "<a href=\"$day_page{'nextday_url'}\">next day</a>";
3071 $day_page{'prevnextday_2link'} = "<a href=\"$day_page{'nextday_url'}\">Next&nbsp;day</a>";
3072 $day_page{'prevnextday_3link'} = "<A HREF=\"$day_page{'nextday_url'}\">Forward A Day</A>";
3073 $day_page{'prevnextday_4link'} = "<a href=\"$day_page{'nextday_url'}\">next day</a>";
3074 } elsif (defined $pyear and defined $nyear) {
3075 $day_page{'prevnextday_link'} = "<a href=\"$day_page{'prevday_url'}\">previous day</a>|<a href=\"$day_page{'nextday_url'}\">next day</a>";
3076 $day_page{'prevnextday_2link'} = "<a href=\"$day_page{'prevday_url'}\">Previous&nbsp;day</a>&nbsp;|&nbsp;<a href=\"$day_page{'nextday_url'}\">Next&nbsp;day</a>";
3077 $day_page{'prevnextday_3link'} = "<A HREF=\"$day_page{'prevday_url'}\">Back A Day</A> - <A HREF=\"$day_page{'nextday_url'}\">Forward A Day</A>";
3078 $day_page{'prevnextday_4link'} = "<a href=\"$day_page{'prevday_url'}\">previous day</a> or the <a href=\"$day_page{'nextday_url'}\">next day</a>";
3081 $$ret .= LJ::fill_var_props($vars, 'DAY_PAGE', \%day_page);
3082 return 1;