Андрей (andy) wrote in changelog,
Андрей
andy
changelog

[ljcom] r9893: LJINT-362 (Comments for side projects): ...

Committer: ailyin
LJINT-362 (Comments for side projects): checkpoint commit
U   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>

Tags: andy, bml, ljcom, pl, pm, tmpl
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments