ljr/local/cgi-bin/phonepost.pl

321 lines
12 KiB
Perl
Executable File

#!/usr/bin/perl
# vim: set ts=4 sw=4 et :
# some variable names:
# - phonepostid, bid, blobid: all refer to a blob id.
# - dppid: display phonepost id; comparable to a ditemid, but for a phone post.
use strict;
use lib "$ENV{'LJHOME'}/cgi-bin";
use LJ::Blob;
package LJ::PhonePost;
my $datatypes = {
0 => { ext => 'mp3', mime => 'audio/mp3' },
1 => { ext => 'ogg', mime => 'application/ogg' },
2 => { ext => 'wav', mime => 'audio/wav' },
};
sub may_transcribe {
my ($u, $remote) = @_;
return 0 if $remote && $remote->{journaltype} ne 'P';
return 1 if $remote && $remote->{userid} == $u->{userid};
LJ::load_user_props($u, 'pp_transallow');
return 0 if $u->{pp_transallow} == -1;
my $groupmask = LJ::get_groupmask($u, $remote);
return 1 if ! $u->{pp_transallow} && $groupmask;
return ($groupmask & (1 << $u->{pp_transallow})) ? 1 : 0;
}
sub get_phonepost_entry {
my ($u, $bid) = @_;
my ($ppe, $memkey);
$memkey = [$u->{userid}, "ppe:$u->{userid}:$bid"];
$ppe = LJ::MemCache::get($memkey);
return $ppe if $ppe;
my $dbcr = LJ::get_cluster_def_reader($u);
$ppe = $dbcr->selectrow_hashref(qq{
SELECT ppe.jitemid, ppe.posttime, ppe.anum,
ppe.filetype, ub.length, ppe.lengthsecs, ppe.location
FROM phonepostentry ppe, userblob ub
WHERE ub.journalid=? AND ppe.userid=ub.journalid
AND ub.domain=? AND ub.blobid=? AND ppe.blobid=ub.blobid
}, undef, $u->{'userid'}, LJ::get_blob_domainid("phonepost"), $bid);
LJ::MemCache::set($memkey, $ppe || 0);
return $ppe;
}
sub apache_content {
my ($r, $u, $dppid) = @_;
my $bid = $dppid >> 8;
my $ppe = get_phonepost_entry($u, $bid);
return 404 unless $ppe && $ppe->{jitemid} && $ppe->{anum} == $dppid % 256;
# check security of item
my $logrow = LJ::get_log2_row($u, $ppe->{jitemid});
return 404 unless $logrow;
if ($u->{statusvis} eq 'S' || $logrow->{security} ne "public") {
# get the remote, ignoring IP, since the request is coming
# from Akamai/Speedera/etc and the IP won't match
my $remote = LJ::get_remote({ ignore_ip => 1 });
my %GET = $r->args;
my $viewall = 0;
my $viewsome = 0;
if ($remote && $GET{viewall} && LJ::check_priv($remote, "canview")) {
LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
"viewall", "phonepost: $u->{user}, itemid: $ppe->{jitemid}, statusvis: $u->{'statusvis'}");
$viewall = LJ::check_priv($remote, 'canview', '*');
$viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended');
}
unless ($viewall || $viewsome && $logrow->{security} eq 'public') {
return 403 unless LJ::can_view($remote, $logrow);
}
}
# future: if length is NULL, then it's an external reference and we redirect
$r->header_out("Cache-Control", "must-revalidate, private");
$r->header_out("Content-Length", $ppe->{length});
$r->content_type( $datatypes->{ $ppe->{filetype} }->{mime} );
# handle IMS requests
my $last_mod = LJ::time_to_http($ppe->{posttime});
if (my $ims = $r->header_in("If-Modified-Since")) {
return 304 if $ims eq $last_mod;
}
$r->header_out("Last-Modified", $last_mod);
if ($r->header_only) {
$r->send_http_header();
return 200;
}
my $buffer;
if ($ppe->{location} eq 'mogile') {
# Mogile
if ( !$LJ::REPROXY_DISABLE{phoneposts} &&
$r->header_in('X-Proxy-Capabilities') &&
$r->header_in('X-Proxy-Capabilities') =~ /\breproxy-file\b/i )
{
my @paths = LJ::mogclient()->get_paths( "pp:$u->{userid}:$bid", 1 );
# reproxy url
if ($paths[0] =~ m/^http:/) {
$r->header_out('X-REPROXY-URL', join(' ', @paths));
}
# reproxy file
else {
$r->header_out('X-REPROXY-FILE', $paths[0]);
}
$r->send_http_header();
}
else {
$buffer = LJ::mogclient()->get_file_data("pp:$u->{userid}:$bid");
$r->send_http_header();
return 500 unless $buffer && ref $buffer;
$r->print($$buffer);
}
}
else {
# BlobServer
$r->send_http_header();
my $ret = LJ::Blob::get_stream($u, 'phonepost',
$datatypes->{ $ppe->{filetype} }->{ext}, $bid, sub {
$buffer .= $_[0];
if (length($buffer) > 50_000) {
$r->print($buffer);
undef $buffer;
}
});
$r->print($buffer) if length($buffer);
return 500 unless $ret;
}
return 200;
}
# if $u_embed and $ditemid are given, they represent an entry
# in which a phonepost tag has been embedded.
sub make_link {
my ($remote, $uuid, $phonepostid, $mode, $u_embed, $ditemid) = @_;
$phonepostid += 0;
# mode can either be 'notrans', 'bare', or 'rss'
$mode = "notrans" if $mode && $mode !~ /bare|rss/;
my $u = ref $uuid ? $uuid : LJ::load_userid($uuid);
return $mode eq 'rss' ? "" : "<b>[Invalid user]</b>" unless $u;
my $userid = $u->{'userid'};
my $ppe = get_phonepost_entry($u, $phonepostid);
return $mode eq 'rss' ? "" : "<b>[Invalid audio link]</b>" unless $ppe;
if ($u_embed && $ditemid) {
# have to check whether the link is embeddable in this entry
if ($u_embed->{'userid'} == $userid &&
$ditemid>>8 == $ppe->{'jitemid'}) {
# it's the original entry, we're ok
} else {
my $accdenied = "<b>[Access to audio link denied]</b>";
# log2 row in which the tag is embedded
my $row = LJ::get_log2_row($u_embed, $ditemid >> 8);
# the original log2 row of this tag
my $row_orig = LJ::get_log2_row($u, $ppe->{'jitemid'});
return $mode eq 'rss' ? "" : $accdenied unless $row && $row_orig;
if ($row_orig->{'security'} eq "public" &&
$row->{'posterid'} == $userid &&
$u_embed->{'userid'} != $userid) {
# it's public and moved by the same
# user to a different journal, we're okay
} else {
return $mode eq 'rss' ? "" : $accdenied;
}
}
}
my $link;
my $dppid = ($phonepostid << 8) + $ppe->{anum};
my $ext = $datatypes->{ $ppe->{filetype} }->{ext};
my $path = LJ::run_hook("url_phonepost", $u, $dppid, $ext) ||
LJ::journal_base($u) . "/data/phonepost/$dppid.$ext";
# make link and just return that if in bare mode
$link = $ppe->{location} eq 'none' ?
"<img src='$LJ::IMGPREFIX/phonepost2.gif' alt='' width='35' height='33' />" :
"<a href='$path'><img src='$LJ::IMGPREFIX/phonepost2.gif' alt='' " .
"width='35' height='33' border='0' /></a>";
return $link if $mode eq 'bare';
my $K = $ppe->{length} ? int($ppe->{length} / 1024) . "K" : "";
my $secs = $ppe->{lengthsecs};
my $duration = $secs ? sprintf("%d:%02d", int($secs/60), $secs%60) : "";
# support rss 'enclosures' - podcasting.
if ($mode eq 'rss') {
$link = "<enclosure url=\"$path\" length=\"$ppe->{length}\" " .
"type=\"$datatypes->{ $ppe->{filetype} }->{mime}\" />";
return $link;
}
# return full table
my $ret = "<table cellspacing='5' cellpadding='0' border='0' class='ljphonepost'><tr>";
$ret .= "<td valign='top'>$link</td>";
$ret .= "<td valign='top'><strong>";
$ret .= $ppe->{location} eq 'none' ? "PhonePost" : "<a href='$path'>PhonePost</a>";
$ret .= "</strong><br /><em>$K</em>&nbsp;$duration</td>";
unless ($mode eq 'notrans') {
my $trans_url = "$LJ::SITEROOT/phonepost/transcribe.bml?user=$u->{user}&amp;ppid=$dppid";
$ret .= "<td valign='top'><a href='$LJ::SITEROOT/phonepost/about.bml'><img src='$LJ::IMGPREFIX/help.gif' width='14' height='14' border='0' alt='(Help)' /></a></td>";
my $trans = LJ::PhonePost::get_latest_trans($u, $phonepostid);
if ($trans) {
my $by;
if ($trans->{revid} == 1) {
$by = LJ::ljuser(LJ::get_username($trans->{posterid}));
} else {
# multiple users transcribing, or just multiple transcriptions of one user?
my $memkey = [$u->{userid},"ppetu:$u->{userid}:$phonepostid"];
my $tu = LJ::MemCache::get($memkey);
unless (defined $tu) {
my $dbr = LJ::get_cluster_reader($u);
$tu = $dbr->selectrow_array("SELECT COUNT(DISTINCT(posterid)) " .
"FROM phoneposttrans " .
"WHERE journalid=? AND blobid=?",
undef, $u->{'userid'}, $phonepostid + 0);
LJ::MemCache::set($memkey, $tu);
}
$by = ($tu == 1) ? LJ::ljuser(LJ::get_username($trans->{posterid})) : "multiple users";
}
my $text = LJ::ehtml($trans->{body});
$text =~ s/\n/<br \/>/g;
$ret .= "<td valign='top'><blockquote cite='$path'>&ldquo;$text&rdquo;</blockquote><br />".
"<a href='$trans_url'>Transcribed</a> by: $by</td>";
} elsif (LJ::PhonePost::may_transcribe($u, $remote)) {
$ret .= "<td valign='top'>(<a href='$trans_url'>transcribe</a>)</td>";
} else {
$ret .= "<td valign='top'>(no transcription available)</td>";
}
}
$ret .= "</tr></table>";
return $ret;
}
sub get_latest_trans {
my ($u, $id) = @_;
$id += 0;
my $memkey = [$u->{userid},"ppelt:$u->{userid}:$id"];
my $lt = LJ::MemCache::get($memkey);
unless (defined $lt) {
my $dbcr = LJ::get_cluster_def_reader($u);
return undef unless $dbcr;
$lt = "";
my $latest = $dbcr->selectrow_array("SELECT MAX(revid) FROM phoneposttrans ".
"WHERE journalid=? AND blobid=?", undef,
$u->{userid}, $id);
if ($latest) {
$lt = $dbcr->selectrow_hashref("SELECT revid,posterid,posttime,subject,body ".
"FROM phoneposttrans WHERE journalid=? ".
"AND blobid=? AND revid=?", undef,
$u->{userid}, $id, $latest);
}
LJ::MemCache::set($memkey, $lt);
}
return $lt;
}
sub show_phoneposts {
my ($u_embed, $ditemid, $remote, $eventref) = @_;
my $replace = sub {
my $tag = shift;
my ($user, $userid, $uobj, $phid, $blobid, $dpid);
# old tag <lj-phonepost user='foo' phonepostid='1' />
# new tag <lj-phonepost journalid='1234567' dpid='10000422' />
if ($tag =~ m!^journalid=['"](\d+)['"]\s*dpid=['"](\d+)['"]\s*/?$!) {
($userid, $dpid)=($1,$2);
# prefer phonepostid and userid
$phid = $dpid >> 8 if $dpid;
} elsif ($tag =~ m!^(user=['"](\S+)['"])?\s*(phonepostid=['"](\d+)['"])?\s*(userid=['"](\d+)['"])?\s*(blobid=['"](\d+)['"])?\s*/?$!) {
($user,$phid,$userid,$blobid)=($2,$4,$6,$8);
# prefer phonepostid and userid
$phid = $blobid >> 8
unless $phid or not $blobid;
do {
$uobj = LJ::load_user($user);
$userid = $uobj->{'userid'};
} unless $userid or $user eq "";
}
return "<b>[Invalid audio link]</b>" unless $phid and $userid;
return make_link($remote, $userid, $phid, 0, $u_embed, $ditemid);
};
$$eventref =~ s!<lj-phonepost\s*([^>]*)>!$replace->($1)!eg;
}
1;