[ljcom] r9893: LJINT-362 (Comments for side projects): ...
Committer: ailyin
LJINT-362 (Comments for side projects): checkpoint commitU trunk/bin/upgrading/update-db-local.pl A trunk/cgi-bin/LJ/PartnerSite.pm U trunk/htdocs/tools/endpoints/comments.bml A trunk/templates/Identity/ExternalLogin.tmpl A trunk/templates/Identity/TwitterInterstitialCompact.tmpl
Modified: trunk/bin/upgrading/update-db-local.pl
===================================================================
--- trunk/bin/upgrading/update-db-local.pl 2010-12-30 08:26:48 UTC (rev 9892)
+++ trunk/bin/upgrading/update-db-local.pl 2010-12-30 12:27:19 UTC (rev 9893)
@@ -1603,6 +1603,30 @@
) TYPE=InnoDB
EOC
+register_tablecreate("exturl2entry_map", <<'EOC');
+CREATE TABLE exturl2entry_map (
+ userid int(11) NOT NULL,
+ url_md5 varchar(22) default NULL,
+ jitemid int(11) NOT NULL,
+
+ KEY userid (userid,url_md5)
+
+ ) ENGINE=InnoDB
+EOC
+
+## SEE LJ::PartnerSite
+register_tablecreate('external_sites_articles_map', qq{
+ CREATE TABLE external_sites_articles_map (
+ partnerid INT NOT NULL DEFAULT 0,
+ docid_hash CHAR(22) NOT NULL DEFAULT '',
+ docid CHAR(150) NOT NULL DEFAULT '',
+ jitemid MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
+
+ PRIMARY KEY ( partnerid, jitemid ),
+ UNIQUE KEY ( partnerid, docid_hash, docid )
+ ) ENGINE=InnoDB
+});
+
# *************************************************************
register_alter(sub {
Added: trunk/cgi-bin/LJ/PartnerSite.pm
===================================================================
--- trunk/cgi-bin/LJ/PartnerSite.pm (rev 0)
+++ trunk/cgi-bin/LJ/PartnerSite.pm 2010-12-30 12:27:19 UTC (rev 9893)
@@ -0,0 +1,270 @@
+package LJ::PartnerSite;
+use strict;
+use warnings;
+
+use Digest::MD5 qw();
+use URI qw();
+use Class::Accessor;
+
+use base qw( Class::Accessor );
+
+__PACKAGE__->mk_accessors qw( id name journal_username journalid journal
+ api_key domain link_pattern custom_css_url
+ xdreceiver_url rate_limits encoding );
+
+### CLASS METHODS ###
+
+my %by_id_map;
+
+sub new {
+ my ( $class, %params ) = @_;
+
+ if ( my $self = $by_id_map{ $params{'id'} } ) {
+ return $self;
+ }
+
+ $params{'journal'} = LJ::load_user( $params{'journal_username'} );
+ $params{'journalid'} = $params{'journal'}->userid;
+
+ my $self = bless \%params, $class;
+ $by_id_map{ $params{'id'} } = $self;
+ return $self;
+}
+
+my ( %find_cache, $find_cache_initialized );
+
+my @SEARCHABLE_FIELDS = qw( id api_key journal_username );
+
+sub _init_find_cache {
+ my ($class) = @_;
+
+ my @all_partners;
+ foreach my $row ( @LJ::PARTNER_SITES ) {
+ push @all_partners, $class->new(%$row);
+ }
+
+ foreach my $field ( @SEARCHABLE_FIELDS ) {
+ $find_cache{$field} = {};
+ foreach my $partner ( @all_partners ) {
+ $find_cache{$field}->{ $partner->$field } = $partner;
+ }
+ }
+
+ $find_cache_initialized = 1;
+}
+
+foreach my $field ( @SEARCHABLE_FIELDS ) {
+ $find_cache{$field} = {};
+
+ no strict 'refs';
+
+ *{"find_by_$field"} = sub {
+ my ( $class, $v ) = @_;
+
+ $class->_init_find_cache
+ unless $find_cache_initialized;
+
+ return $find_cache{$field}->{$v};
+ };
+}
+
+### INSTANCE METHODS ###
+
+sub _hashfunc {
+ my ($value) = @_;
+ return Digest::MD5::md5_base64($value);
+}
+
+sub find_entry_by_docid {
+ my ( $self, $docid ) = @_;
+
+ my $memc_key = 'extarticlesmap_jitemid:' . $self->id . ':' . $docid;
+
+ if ( my $jitemid = LJ::MemCache::get($memc_key) ) {
+ return LJ::Entry->new( $self->journal, 'jitemid' => $jitemid );
+ }
+
+ my $dbr = LJ::get_db_reader();
+ my $row = $dbr->selectrow_hashref( qq{
+ SELECT jitemid
+ FROM external_sites_articles_map
+ WHERE partnerid = ? AND docid_hash = ? AND docid = ?
+ }, undef, $self->id, _hashfunc($docid), $docid );
+
+ my $jitemid;
+
+ if ($row) {
+ $jitemid = $row->{'jitemid'};
+ } else {
+ # there is no entry, let's create one
+ my $link = $self->article_link($docid);
+
+ my (undef, $min, $hour, $day, $month, $year) = gmtime;
+ $month++;
+ $year += 1900;
+
+ my $err_out;
+
+ my $protocol_res = LJ::Protocol::do_request(
+ 'postevent',
+ {
+ event => $link,
+ subject => '',
+ allowmask => 0,
+ username => $self->journal_username,
+ security => 'public',
+
+ year => $year,
+ mon => $month,
+ day => $day,
+ hour => $hour,
+ min => $min,
+ }, \$err_out, {
+ u => $self->journal,
+ noauth => 1,
+ }
+ );
+
+ unless ( $protocol_res ) {
+ Carp::croak "failed to create journal entry for " .
+ "partner=" . $self->id . ", docid=" . $docid .
+ "; error details: $err_out";
+ }
+
+ $jitemid = $protocol_res->{'itemid'};
+
+ my $dbh = LJ::get_db_writer();
+ $dbh->do( qq{
+ INSERT IGNORE INTO external_sites_articles_map
+ SET
+ partnerid = ?,
+ docid_hash = ?,
+ docid = ?,
+ jitemid = ?
+ }, undef, $self->id, _hashfunc($docid), $docid, $jitemid );
+ }
+
+ LJ::MemCache::set( $memc_key, $jitemid );
+
+ return LJ::Entry->new( $self->journal, 'jitemid' => $jitemid );
+}
+
+sub article_link {
+ my ( $self, $docid, $extend ) = @_;
+
+ my $link = $self->link_pattern;
+ $link =~ s/\Q[[docid]]\E/$docid/g;
+
+ return $link unless defined $extend;
+
+ my $uri = URI->new($link);
+ $uri->query_form( $uri->query_form, %$extend );
+ return $uri->as_string;
+}
+
+sub docid_from_entry {
+ my ( $self, $entry ) = @_;
+
+ my $memc_key = 'extarticlesmap_docid:' . $self->id .
+ ':' . $entry->jitemid;
+
+ my $docid = LJ::MemCache::get($memc_key);
+ if ( defined $docid ) {
+ return $docid;
+ }
+
+ my $dbr = LJ::get_db_reader();
+ ( $docid ) = $dbr->selectrow_array( qq{
+ SELECT docid
+ FROM external_sites_articles_map
+ WHERE partnerid = ? AND jitemid = ?
+ }, undef, $self->id, $entry->jitemid );
+
+ unless ( $docid ) {
+ # TODO: throw exception?
+ return;
+ }
+
+ LJ::MemCache::set( $memc_key, $docid );
+ return $docid;
+}
+
+sub article_link_from_entry {
+ my ( $self, $entry, $extend ) = @_;
+
+ my $docid = $self->docid_from_entry($entry);
+ return $self->article_link( $docid, $extend );
+}
+
+sub domain_check_js {
+ my ($self, $opts) = @_;
+
+ my $domains_out = LJ::JSON->to_json([ $self->domain ]);
+
+ if ( $opts->{'mode'} eq 'logcom' ) {
+ return qq[
+ <script type="text/javascript">
+ var trustedDomains = $domains_out;
+
+ var domainMatch = checkDomain(top.window.location.href, trustedDomains);
+
+ if (!domainMatch) {
+ window.location.href = 'about:blank';
+ }
+
+ function checkDomain(href, trustedDomains) {
+ var currentDomain = href.match(] .q{/(http\:\/\/)(?:www\.)?([^\/]*)/} . qq[)[2];
+
+ for (var i = 0, l = trustedDomains.length; i < l; i++) {
+ if (trustedDomains[i] == currentDomain) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ </script>
+ ];
+ } elsif ( $opts->{'mode'} eq 'jsonp' ) {
+ my $code = $opts->{'code'};
+
+ return qq[
+ var trustedDomains = $domains_out;
+
+ var domainMatch = checkDomain(window.location.href, trustedDomains);
+
+ if (domainMatch) {
+ $code
+ } else {
+ window.location.href = 'about:blank';
+ }
+
+ function checkDomain(href, trustedDomains) {
+ var currentDomain = href.match(] .q{/(http\:\/\/)(?:www\.)?([^\/]*)/} . qq[)[2];
+
+ for (var i = 0, l = trustedDomains.length; i < l; i++) {
+ if (trustedDomains[i] == currentDomain) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ ];
+ }
+}
+
+sub request_logcom_resources {
+ my ($self) = @_;
+
+ LJ::need_res(qw( js/partners/placeholder.js
+ js/jquery.js
+ stc/lj_base.css
+ stc/partners/login.css ));
+
+ LJ::include_raw( 'css_link' => $self->custom_css_url )
+ if $self->custom_css_url;
+
+}
+
+1;
Modified: trunk/htdocs/tools/endpoints/comments.bml
===================================================================
--- trunk/htdocs/tools/endpoints/comments.bml 2010-12-30 08:26:48 UTC (rev 9892)
+++ trunk/htdocs/tools/endpoints/comments.bml 2010-12-30 12:27:19 UTC (rev 9893)
@@ -4,15 +4,16 @@
use strict;
use warnings;
use Data::Dumper;
- use LJ::ExternalComments;
+ use LJ::PartnerSite;
+ use Encode qw();
use vars qw/%GET/;
return 'This feature is disabled'
unless LJ::is_enabled('external_comments');
- my $url = $GET{url};
- my $rskey = $GET{rsk};
+ my $docid = $GET{'docid'};
+ my $api_key = $GET{'rsk'};
my $page = int ( $GET{'page'} || 0 );
my $thread = int ( $GET{'thread'} || 0 );
my $view = int ( $GET{'view'} || 0 ) || undef;
@@ -21,69 +22,58 @@
$format = 'JSONP' unless defined $format
and $format =~ /^(?:JSON|JSONP)$/;
- my $username;
+ ## convert remote-site key to lj.com's community
+ my $partner = LJ::PartnerSite->find_by_api_key($api_key);
- ## wrapper and serialazer.
+ my $recode = sub {
+ my ($content) = @_;
+
+ my $mime = $format eq 'JSON' ? 'application/json'
+ : 'text/javascript';
+
+ my $encoding = LJ::Request->get_param('encoding')
+ || ( $partner ? $partner->encoding : undef )
+ || 'utf-8';
+
+ my $content_type = $mime . '; charset=' . $encoding;
+ LJ::Request->content_type( $content_type );
+ BML::set_content_type( $content_type );
+
+ Encode::from_to( $content, 'utf8', $encoding );
+ return $content;
+ };
+
+ ## wrapper and serializer.
my $answer = sub {
my $data = shift;
my $data_out = LJ::JSON->to_json($data);
if ( $format eq 'JSON' ) {
- return $data_out;
+ return $recode->($data_out);
} elsif ( $format eq 'JSONP' ) {
- my $domains = $LJ::PARTNER_DOMAINS{$username};
- my $domains_out = LJ::JSON->to_json($domains);
-
my $funcname = LJ::Request->get_param('callback');
$funcname = 'JSONP'
unless defined $funcname && $funcname;
- return qq[
- var trustedDomains = $domains_out;
-
- var domainMatch = checkDomain(window.location.href, trustedDomains);
-
- if (domainMatch) {
- $funcname($data_out);
- } else {
- window.location.href = 'about:blank';
- }
-
- function checkDomain(href, trustedDomains) {
- var currentDomain = href.match(] .q{/(http\:\/\/)(?:www\.)?([^\/]*)/} . qq[)[2];
-
- for (var i = 0, l = trustedDomains.length; i < l; i++) {
- if (trustedDomains[i] == currentDomain) {
- return true;
- }
- }
-
- return false;
- }
- ];
+ return $recode->( $partner->domain_check_js( {
+ 'mode' => 'jsonp',
+ 'code' => "$funcname($data_out);",
+ } ) );
}
};
- ## convert remote-site key to lj.com's community
- $username = LJ::ExternalComments->key_to_username($rskey);
- return $answer->({error => "unknown key"})
- unless $username;
+ return 'unknown key' unless $partner;
- my $journal = LJ::load_user($username);
- return $answer->({error => "unknown user"})
+ my $journal = $partner->journal;
+ return $answer->( { 'error' => 'unknown user' } )
unless $journal;
- return 'rate limit exceeded'
- unless LJ::RateLimit->check( $journal,
- $LJ::PARTNER_RATE_LIMITS{$username} );
+ return $answer->( { 'error' => 'rate limit exceeded' } )
+ unless LJ::RateLimit->check( $journal, $partner->rate_limits );
- ## convert article's uri on partners site to entryid on LJ.com
- my $jitemid = LJ::ExternalComments->url_to_jitemid($journal, $url);
- return $answer->({ comments => [] })
- unless $jitemid; ## url that has no mapping to entry has no comments too.
+ my $entry = $partner->find_entry_by_docid($docid);
+ my $jitemid = $entry->jitemid;
- my $entry = LJ::Entry->new($journal, jitemid => $jitemid);
-
my $userpics = {};
my %user = ();
@@ -100,7 +90,7 @@
my $remote = undef;
## load data
- my @comments = LJ::Talk::load_comments($journal, $remote, "L", $entry->jitemid, $opts);
+ my @comments = LJ::Talk::load_comments($journal, $remote, "L", $jitemid, $opts);
## convert into new structure
my $to_export_format = sub {
Added: trunk/templates/Identity/ExternalLogin.tmpl
===================================================================
--- trunk/templates/Identity/ExternalLogin.tmpl (rev 0)
+++ trunk/templates/Identity/ExternalLogin.tmpl 2010-12-30 12:27:19 UTC (rev 9893)
@@ -0,0 +1,123 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">+<html>+<head>+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />+<head>+<title>Log in</title>+<TMPL_VAR js_check_domain>+<TMPL_VAR lj_res_includes>+</head>+<body>+<div class="b-identity">+<ul class="m-auth">+ <TMPL_LOOP type_user>+ <li class="m-auth-item m-auth-<TMPL_VAR type> current"><a href="?type=<TMPL_VAR type>"><span><TMPL_VAR ml_tab_heading></span></a><i class="m-auth-bl"></i><i class="m-auth-br"></i></li>+ </TMPL_LOOP>++ <TMPL_LOOP type_openid>+ <li class="m-auth-item m-auth-<TMPL_VAR type>"><a href="?type=<TMPL_VAR type>"><span><TMPL_VAR ml_tab_heading></span></a><i class="m-auth-bl"></i><i class="m-auth-br"></i></li>+ </TMPL_LOOP>++ <TMPL_LOOP type_facebook>+ <li class="m-auth-item m-auth-<TMPL_VAR type>"><a href="?type=<TMPL_VAR type>"><span><TMPL_VAR ml_tab_heading></span></a><i class="m-auth-bl"></i><i class="m-auth-br"></i></li>+ </TMPL_LOOP>++ <TMPL_LOOP type_twitter>+ <li class="m-auth-item m-auth-<TMPL_VAR type>"><a href="?type=<TMPL_VAR type>"><span><TMPL_VAR ml_tab_heading></span></a><i class="m-auth-bl"></i><i class="m-auth-br"></i></li>+ </TMPL_LOOP>++</ul>+<ul class="b-auth">+ <TMPL_LOOP type_user>+ <li id="tab-<TMPL_VAR type>" class="b-auth-item">+ <form action="<tmpl_var action>" method="post" target="">+ <p class="b-auth-desc"><TMPL_VAR expr="ml('/identity/login.bml.user.desc')"></p>+ <p class="b-auth-user-form">+ <span class="b-auth-input-wrapper">+ <label for="ljusername"><TMPL_VAR expr="ml('/identity/login.bml.user.label.name')"></label>+ <input type="text" name="user" id="ljusername" value="" class="b-auth-user-input" />+ </span>+ <span class="b-auth-input-wrapper">+ <label for="ljuserpassword"><TMPL_VAR expr="ml('/identity/login.bml.user.label.pass')"></label>+ <input type="password" name="password" id="ljuserpassword" value="" class="b-auth-user-input" />+ <a href="<TMPL_VAR expr="ml('/identity/login.bml.user.help.link')">"><TMPL_VAR expr="ml('/identity/login.bml.user.help')"></a>+ </span>+ <button type="submit"><TMPL_VAR expr="ml('/identity/login.bml.user.btn.login')"></button>+ </p>+ <TMPL_IF errors><TMPL_LOOP errors><p class="b-auth-error"><span class="i-message i-message-error"><TMPL_VAR error></span></p></TMPL_LOOP></TMPL_IF>++ <input type="hidden" name="type" value="<TMPL_VAR type>" />+ <input type="hidden" name="returnto" value="<TMPL_VAR returnto>" />+ </form>+ </li>+ </TMPL_LOOP>++ <TMPL_LOOP type_openid>+ <li id="tab-<TMPL_VAR type>" class="b-auth-item" style="display:none">+ <form action="<tmpl_var action>" method="post" target="lj-identity-auth">+ <p class="b-auth-desc"><TMPL_VAR expr="ml('/identity/login.bml.openid.desc')"></p>+ <p class="b-auth-openid-form">+ <span class="b-auth-input-wrapper">+ <label for="openid_url"><TMPL_VAR expr="ml('/identity/login.bml.openid.label.url')"></label>+ <input type="url" name="openid:url" id="openid_url" value="" class="b-auth-openid-input" />+ <span><TMPL_VAR expr="ml('/identity/login.bml.openid.label.sample')"></span>+ </span>+ <button type="submit"><TMPL_VAR expr="ml('/identity/login.bml.openid.btn.login')"></button>+ </p>+ <TMPL_IF errors><TMPL_LOOP errors><p class="b-auth-error"><span class="i-message i-message-error"><TMPL_VAR error></span></p></TMPL_LOOP></TMPL_IF>++ <input type="hidden" name="type" value="<TMPL_VAR type>" />+ </form>+ </li>+ </TMPL_LOOP>++ <TMPL_LOOP type_facebook>+ <li id="tab-<TMPL_VAR type>" class="b-auth-item" style="display:none">+ <form action="<tmpl_var action>" method="post" target="lj-identity-auth">+ <button type="submit" class="b-facebookbtn" title="<TMPL_VAR expr="ml('/identity/login.bml.facebook.btn.connect')">"><span><i></i><TMPL_VAR expr="ml('/identity/login.bml.facebook.btn.connect')"></span></button>+ <p class="b-auth-desc"><TMPL_VAR expr="ml('/identity/login.bml.facebook.desc')"></p>+ <TMPL_IF errors><TMPL_LOOP errors><p class="b-auth-error"><span class="i-message i-message-error"><TMPL_VAR error></span></p></TMPL_LOOP></TMPL_IF>++ <input type="hidden" name="type" value="<TMPL_VAR type>" />+ </form>+ </li>+ </TMPL_LOOP>++ <TMPL_LOOP type_twitter>+ <li id="tab-<TMPL_VAR type>" class="b-auth-item" style="display:none">+ <form action="<tmpl_var action>" method="post" target="lj-identity-auth">+ <button type="submit" class="b-twitterbtn"><span><i></i><TMPL_VAR expr="ml('/identity/login.bml.twitter.btn.connect')"></span></button>+ <p class="b-auth-desc"><TMPL_VAR expr="ml('/identity/login.bml.twitter.desc')"></p>+ <TMPL_IF errors><TMPL_LOOP errors><p class="b-auth-error"><span class="i-message i-message-error"><TMPL_VAR error></span></p></TMPL_LOOP></TMPL_IF>++ <input type="hidden" name="type" value="<TMPL_VAR type>" />+ </form>+ </li>+ </TMPL_LOOP>+</ul>+</div>++<script type="text/javascript">+ jQuery(function($) {+ $(".m-auth a").click(function(e) {+ e.preventDefault();++ var type = $(this).attr("href").replace(/.*type=/, "");++ $(".m-auth-item").removeClass("current");+ $(".b-auth-item").hide();+ $(this).parent('li').addClass("current");+ $("#tab-" + type).show();+ });++ $("form").submit(function(e) {+ var form = $(this);++ if (form.attr("target") == "lj-identity-auth") {+ window.open("about:blank", "lj-identity-auth", "width=800,height=600");+ }+ });+ });+</script>+</body>+</html>
Added: trunk/templates/Identity/TwitterInterstitialCompact.tmpl
===================================================================
--- trunk/templates/Identity/TwitterInterstitialCompact.tmpl (rev 0)
+++ trunk/templates/Identity/TwitterInterstitialCompact.tmpl 2010-12-30 12:27:19 UTC (rev 9893)
@@ -0,0 +1,65 @@
+<html>
+<head>
+<title>Twitter Interstitial</title>
+<TMPL_VAR lj_res_includes>
+</head>
+
+<body>
+
+<form action="" method="post" class="b-twinterstitial-form">
+<TMPL_VAR form_intro>
+<TMPL_IF step2>
+ <div class="warningbar">
+ <p><TMPL_VAR ml_validation></p>
+ </div>
+ <p class="b-twinterstitial-submit"><input type="submit" name="next" value="<TMPL_VAR expr="ml('.btn.next')">"></p>
+<TMPL_ELSE>
+ <h4><TMPL_VAR ml_congrats></h4>
+ <p><TMPL_VAR expr="ml('.desc')"></p>
+ <h4><TMPL_VAR expr="ml('.heading.subscribe')"></h4>
+ <ul>
+ <li>
+ <input type="radio" name="subscribe" value="1" id="subscribe_yes" checked="checked">
+ <label for="subscribe_yes"><TMPL_VAR expr="ml('.label.subscribe.yes')"></label>
+ </li>
+ <li>
+ <input type="radio" name="subscribe" value="0" id="subscribe_no">
+ <label for="subscribe_no"><TMPL_VAR expr="ml('.label.subscribe.no')"></label>
+ </li>
+ <li>
+ <table class="b-form-columns">
+ <tr>
+ <td>
+ <label for="email"><strong><TMPL_VAR expr="ml('.label.email')"></strong></label>
+ </td>
+ <td>
+ <input type="text" name="email" id="email" value="<TMPL_VAR form_email>">
+ </td>
+ <td>
+ <TMPL_IF errors>
+ <TMPL_LOOP errors>
+ <span class="i-bubble b-bubble-warning">
+ <i class="i-bubble-arrow-border"></i>
+ <i class="i-bubble-arrow"></i>
+ <TMPL_VAR error>
+ </span>
+ </TMPL_LOOP>
+ <TMPL_ELSE>
+ <span class="i-bubble b-bubble-alert">
+ <i class="i-bubble-arrow-border"></i>
+ <i class="i-bubble-arrow"></i>
+ <TMPL_VAR expr="ml('.tip.email')">
+ </span>
+ </TMPL_IF>
+ </td>
+ </tr>
+ </table>
+ </li>
+ </ul>
+ <p class="b-twinterstitial-submit">
+ <button type="submit"><TMPL_VAR expr="ml('.btn.proceed')"></button>
+ </p>
+</TMPL_IF>
+</form>
+
+</body>
