#!/usr/bin/perl
use strict;
package LJ::Feed;
my %feedtypes = (
rss => \&create_view_rss,
atom => \&create_view_atom,
foaf => \&create_view_foaf,
);
sub make_feed
{
my ($r, $u, $remote, $opts) = @_;
$opts->{pathextra} =~ s!^/(\w+)!!;
my $feedtype = $1;
my $viewfunc = $feedtypes{$feedtype};
unless ($viewfunc) {
$opts->{'handler_return'} = 404;
return undef;
}
$opts->{noitems} = 1 if $feedtype eq 'foaf';
$r->notes('codepath' => "feed.$feedtype") if $r;
my $dbr = LJ::get_db_reader();
my $user = $u->{'user'};
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'}) . "/data/$feedtype";
return undef;
}
LJ::load_user_props($u, qw/ journaltitle journalsubtitle opt_synlevel /);
LJ::text_out(\$u->{$_})
foreach ("name", "url", "urlname");
# opt_synlevel will default to 'full'
$u->{'opt_synlevel'} = 'full'
unless $u->{'opt_synlevel'} =~ /^(?:full|summary|title)$/;
# some data used throughout the channel
my $journalinfo = {
u => $u,
link => LJ::journal_base($u) . "/",
title => $u->{journaltitle} || $u->{name} || $u->{user},
subtitle => $u->{journalsubtitle} || $u->{name},
builddate => LJ::time_to_http(time()),
};
# if we do not want items for this view, just call out
return $viewfunc->($journalinfo, $u, $opts)
if ($opts->{'noitems'});
# for syndicated accounts, redirect to the syndication URL
# However, we only want to do this if the data we're returning
# is similar. (Not FOAF, for example)
if ($u->{'journaltype'} eq 'Y') {
my $synurl = $dbr->selectrow_array("SELECT synurl FROM syndicated WHERE userid=$u->{'userid'}");
unless ($synurl) {
return 'No syndication URL available.';
}
$opts->{'redir'} = $synurl;
return undef;
}
## load the itemids
my @itemids;
my @items = LJ::get_recent_items({
'clusterid' => $u->{'clusterid'},
'clustersource' => 'slave',
'remote' => $remote,
'userid' => $u->{'userid'},
'itemshow' => 25,
'order' => "logtime",
'itemids' => \@itemids,
'friendsview' => 1, # this returns rlogtimes
'dateformat' => "S2", # S2 format time format is easier
});
$opts->{'contenttype'} = 'text/xml; charset='.$opts->{'saycharset'};
### load the log properties
my %logprops = ();
my $logtext;
my $logdb = LJ::get_cluster_reader($u);
LJ::load_log_props2($logdb, $u->{'userid'}, \@itemids, \%logprops);
$logtext = LJ::get_logtext2($u, @itemids);
# set last-modified header, then let apache figure out
# whether we actually need to send the feed.
my $lastmod = 0;
foreach my $item (@items) {
# revtime of the item.
my $revtime = $logprops{$item->{itemid}}->{revtime};
$lastmod = $revtime if $revtime > $lastmod;
# if we don't have a revtime, use the logtime of the item.
unless ($revtime) {
my $itime = $LJ::EndOfTime - $item->{rlogtime};
$lastmod = $itime if $itime > $lastmod;
}
}
$r->set_last_modified($lastmod) if $lastmod;
# use this $lastmod as the feed's last-modified time
# we would've liked to use something like
# LJ::get_timeupdate_multi instead, but that only changes
# with new updates and doesn't change on edits.
$journalinfo->{'modtime'} = $lastmod;
# regarding $r->set_etag:
# http://perl.apache.org/docs/general/correct_headers/correct_headers.html#Entity_Tags
# It is strongly recommended that you do not use this method unless you
# know what you are doing. set_etag() is expecting to be used in
# conjunction with a static request for a file on disk that has been
# stat()ed in the course of the current request. It is inappropriate and
# "dangerous" to use it for dynamic content.
if ((my $status = $r->meets_conditions) != Apache::Constants::OK()) {
$opts->{handler_return} = $status;
return undef;
}
# email address of journal owner, but respect their privacy settings
if ($u->{'allow_contactshow'} eq "Y" && $u->{'opt_whatemailshow'} ne "N" && $u->{'opt_mangleemail'} ne "Y") {
my $cemail;
# default to their actual email
$cemail = $u->{'email'};
# use their livejournal email if they have one
if ($LJ::USER_EMAIL && $u->{'opt_whatemailshow'} eq "L" &&
LJ::get_cap($u, "useremail") && ! $u->{'no_mail_alias'}) {
$cemail = "$u->{'user'}\@$LJ::USER_DOMAIN";
}
# clean it up since we know we have one now
$journalinfo->{email} = $cemail;
}
# load tags now that we have no chance of jumping out early
my $logtags = LJ::Tags::get_logtags($u, \@itemids);
my %posteru = (); # map posterids to u objects
LJ::load_userids_multiple([map { $_->{'posterid'}, \$posteru{$_->{'posterid'}} } @items], [$u]);
my @cleanitems;
ENTRY:
foreach my $it (@items)
{
# load required data
my $itemid = $it->{'itemid'};
my $ditemid = $itemid*256 + $it->{'anum'};
next ENTRY if $posteru{$it->{'posterid'}} && $posteru{$it->{'posterid'}}->{'statusvis'} eq 'S';
if ($LJ::UNICODE && $logprops{$itemid}->{'unknown8bit'}) {
LJ::item_toutf8($u, \$logtext->{$itemid}->[0],
\$logtext->{$itemid}->[1], $logprops{$itemid});
}
# see if we have a subject and clean it
my $subject = $logtext->{$itemid}->[0];
if ($subject) {
$subject =~ s/[\r\n]/ /g;
LJ::CleanHTML::clean_subject_all(\$subject);
}
# an HTML link to the entry. used if we truncate or summarize
my $readmore = "({link}$ditemid.html\">Read more ...)";
# empty string so we don't waste time cleaning an entry that won't be used
my $event = $u->{'opt_synlevel'} eq 'title' ? '' : $logtext->{$itemid}->[1];
# clean the event, if non-empty
my $ppid = 0;
if ($event) {
# users without 'full_rss' get their logtext bodies truncated
# do this now so that the html cleaner will hopefully fix html we break
unless (LJ::get_cap($u, 'full_rss')) {
my $trunc = LJ::text_trim($event, 0, 80);
$event = "$trunc $readmore" if $trunc ne $event;
}
LJ::CleanHTML::clean_event(\$event,
{ 'preformatted' => $logprops{$itemid}->{'opt_preformatted'} });
# do this after clean so we don't have to about know whether or not
# the event is preformatted
if ($u->{'opt_synlevel'} eq 'summary') {
# assume the first paragraph is terminated by two or a
# valid XML tags should be handled, even though it makes an uglier regex
if ($event =~ m!(( ()?\s*){2})|()!i) {
# everything before the matched tag + the tag itself
# + a link to read more
$event = $` . $& . $readmore;
}
}
if ($event =~ //) {
my $pollid = $1;
my $name = $dbr->selectrow_array("SELECT name FROM poll WHERE pollid=?",
undef, $pollid);
if ($name) {
LJ::Poll::clean_poll(\$name);
} else {
$name = "#$pollid";
}
$event =~ s!!