#!/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' ? "" : "[Invalid user]" unless $u; my $userid = $u->{'userid'}; my $ppe = get_phonepost_entry($u, $phonepostid); return $mode eq 'rss' ? "" : "[Invalid audio link]" 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 = "[Access to audio link denied]"; # 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' ? "" : ""; 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 = "{length}\" " . "type=\"$datatypes->{ $ppe->{filetype} }->{mime}\" />"; return $link; } # return full table my $ret = ""; $ret .= ""; $ret .= ""; unless ($mode eq 'notrans') { my $trans_url = "$LJ::SITEROOT/phonepost/transcribe.bml?user=$u->{user}&ppid=$dppid"; $ret .= ""; 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/
/g; $ret .= ""; } elsif (LJ::PhonePost::may_transcribe($u, $remote)) { $ret .= ""; } else { $ret .= ""; } } $ret .= "
$link"; $ret .= $ppe->{location} eq 'none' ? "PhonePost" : "PhonePost"; $ret .= "
$K $duration
(Help)
“$text”

". "Transcribed by: $by
(transcribe)(no transcription available)
"; 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 # new tag 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 "[Invalid audio link]" unless $phid and $userid; return make_link($remote, $userid, $phid, 0, $u_embed, $ditemid); }; $$eventref =~ s!]*)>!$replace->($1)!eg; } 1;