This commit is contained in:
2019-02-06 00:49:12 +03:00
commit 8dbb1bb605
4796 changed files with 506072 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
<html>
<body>
<h1>Not Found</h1>
The requested URL was not found on this server.
<p />
You may be receiving this error as a result from maintenance or other site problems; please refer to <a href="http://status.livejournal.org/">http://status.livejournal.org</a> to keep updated on the status of the site.
</body>
<!--
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-->
</html>

View File

@@ -0,0 +1,65 @@
<html>
<body>
<h1>Sorry...</h1>
We're currently experiencing some technical difficulties. Please try again in a few moments.
<p />
You may be receiving this error as a result from maintenance or other site problems; please refer to <a href="http://status.livejournal.org/">http://status.livejournal.org</a> to keep updated on the status of the site.
</body>
<!--
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-->
</html>

View File

@@ -0,0 +1,4 @@
DoGZIP 1
DefaultLanguage en_LJ
DefaultScheme xcolibur

View File

@@ -0,0 +1,30 @@
<?_code
use strict;
use vars qw($body $title);
$title = "Abuse Center";
my $remote = LJ::get_remote();
unless (LJ::check_priv($remote, "supportread", "abuse")) {
$title = "Restricted";
$body .= "<?p This tool is for members of our abuse team.<br />
If you need to file an abuse request, please do so at:
<a href='/abuse/report.bml'>http://www.livejournal.com/abuse/report.bml</a> p?>";
} else {
$body .= <<"BLURB";
<strong>Current Tools:</strong><br />
<ul>
<li><a href="./send_mail.bml">Send an Email</a></li>
<li><a href="./query.bml">Query Sent Emails</a></li>
</ul>
BLURB
}
return;
_code?><?page
title=><?_code return $title; _code?>
body=> <?_code return $body; _code?>
page?><?_c <LJDEP>
link: htdocs/admin/abuse/mail.bml
link: htdocs/support/submit.bml
</LJDEP> _c?>

View File

@@ -0,0 +1,10 @@
<?page
body<=
<?_code
# This file is now out of date!
BML::redirect('/admin/sendmail/query.bml');
_code?>
<=body
page?>

View File

@@ -0,0 +1,12 @@
<?page
body<=
<?_code
# This file is now out of date!
BML::redirect('/admin/sendmail/send.bml');
_code?>
<=body
page?><?_c <LJDEP>
link: htdocs/admin/abuse/index.bml
</LJDEP> _c?>

View File

@@ -0,0 +1,309 @@
<?_code
{
use strict;
use vars qw(%GET %POST);
my $remote = LJ::get_remote();
return "You must first <a href=\"$LJ::SITEROOT/login.bml?ret=1\">log in</a>."
unless $remote;
return LJ::no_access_error("You don't have access to use this tool.", "moneyenter")
unless LJ::remote_has_priv($remote, "moneyenter");
# heading
my $ret = "<h2>Account Management</h2>";
# no user specified, get one
unless ($GET{'user'}) {
$ret .= "<form method='get'>";
$ret .= "User: " . LJ::html_text({ 'name' => 'user', 'size' => 15, 'maxlength' => 15 }) . " ";
$ret .= LJ::html_submit('Load');
$ret .= "</form>";
return $ret;
}
# load user
my $user = LJ::canonical_username($GET{'user'});
my $u = LJ::load_user($user, "force");
return "Invalid user: '$user'" unless $u;
# establish some cap bit -> item mappings
my %bonus_caps = map { $LJ::Pay::bonus{$_}->{'cap'}, $_ } keys %LJ::Pay::bonus;
my $zerodate = "0000-00-00 00:00:00";
my $dbh = LJ::get_db_writer();
# save chanes
if (LJ::did_post()) {
# 'notes' field is required
return "<?h1 Error h1?><?p The 'notes' fields is required. Please enter a description of " .
"the action you are performing, why it was done, etc. p?>" unless $POST{'notes'};
my @bits_set;
my @bits_del;
my $logmsg;
# save bit-only features
foreach my $bit (0..14) {
# make sure $bit is a valid cap as specified by %LJ::CAP (for general caps),
# or either %LJ::Pay::capinf or %LJ::Pay::bonus (for local caps)
next unless
( ref $LJ::CAP{$bit} eq 'HASH' ||
(grep { defined $_->{cap} && $_->{cap} == $bit } values %LJ::Pay::bonus) ||
(grep { defined $_->{cap} && $_->{cap} == $bit } values %LJ::Pay::capinf) );
# build bit mask to set at the end
unless ($POST{"cap_${bit}_set"}) {
push @bits_del, $bit;
next;
}
push @bits_set, $bit;
}
# save paid account expiration
{
my $exp = $POST{'paid_exp'};
# check expiration date format
if (defined $exp) {
return "<b>Error:</b> Invalid expiration date format, expecting: yyyy-mm-dd hh:mm:ss"
unless $exp =~ /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
}
# does a paiduser row already exist?
my $paiduntil = $dbh->selectrow_array("SELECT paiduntil FROM paiduser WHERE userid=?",
undef, $u->{'userid'});
# update existing row
if ($paiduntil) {
# if expdate is 0000-00-00 00:00:00, just delete the row
if ($exp eq $zerodate) {
$dbh->do("DELETE FROM paiduser WHERE userid=?", undef, $u->{'userid'});
$logmsg .= "[delete] item: paid_account, paiduntil: $exp\n";
}
# unnecessary query?
next if $paiduntil eq $exp;
# otherwise do an update
$dbh->do("UPDATE paiduser SET paiduntil=? WHERE userid=?",
undef, $exp, $u->{'userid'});
$logmsg .= "[update] item: paid_account, paiduntil: $exp\n";
# insert new, non-blank, row
} elsif ($exp ne $zerodate) {
$dbh->do("INSERT INTO paiduser (userid, paiduntil) " .
"VALUES (?, ?)", undef, $u->{'userid'}, $exp);
$logmsg .= "[insert] item: paid_account, paiduntil: $exp\n";
}
}
# update bonus feature
foreach my $itemname (sort keys %LJ::Pay::bonus) {
my $bitem = $LJ::Pay::bonus{$itemname};
next unless ref $bitem eq 'HASH';
my ($exp, $size, $days) = map { $POST{"${itemname}_$_"} } qw(exp size daysleft);
# check expiration date format
if (defined $exp) {
return "<b>Error:</b> Invalid expiration date format, expecting: yyyy-mm-dd hh:mm:ss"
unless $exp =~ /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
}
# see if row exists
my $dbrow = $dbh->selectrow_hashref("SELECT expdate, size, daysleft FROM paidexp " .
"WHERE userid=? AND item=?",
undef, $u->{'userid'}, $itemname);
# row exists, do an update
if ($dbrow) {
# if a zero row, just delete
if ($exp eq $zerodate && ! $size && ! $days) {
$dbh->do("DELETE FROM paidexp WHERE userid=? AND item=?", undef, $u->{'userid'}, $itemname);
$logmsg .= "[delete] item: $itemname, expdate: $exp, size: $size, daysleft: $days\n";
next;
}
# prepare update query
my $sets;
$sets .= "expdate=" . $dbh->quote($exp) . "," if defined $exp && $dbrow->{'expdate'} ne $exp;
$sets .= "size=" . $dbh->quote($size) . "," if defined $size && $dbrow->{'size'} != $size;
$sets .= "daysleft=" . $dbh->quote($days) . "," if defined $days && $dbrow->{'daysleft'} != $days;
chop $sets if $sets;
# unnecessary query?
next unless $sets;
# otherwise do an update
$dbh->do("UPDATE paidexp SET $sets WHERE userid=? AND item=?",
undef, $u->{'userid'}, $itemname);
$logmsg .= "[update] item: $itemname, expdate: $exp, size: $size, daysleft: $days\n";
# if no rows, then we need to insert a new row, but not an empty one
} elsif ($exp ne $zerodate || $size > 0 || $days > 0) {
$exp ||= $zerodate;
$size ||= 0;
$days ||= 0;
$dbh->do("INSERT INTO paidexp (userid, item, size, expdate, daysleft) VALUES (?, ?, ?, ?, ?)",
undef, $u->{'userid'}, $itemname, $size, $exp, $days);
$logmsg .= "[insert] item: $itemname, expdate: $exp, size: $size, daysleft: $days\n";
}
# call any necessary apply_hooks
my $apply_hook = $bitem->{apply_hook};
if ($apply_hook && ref $apply_hook eq 'CODE') {
$apply_hook->($u, $itemname);
}
}
# note which caps were changed and log $logmsg to statushistory
{
my $caps_add = join(",", @bits_set);
my $caps_del = join(",", @bits_del);
$logmsg .= "[caps] add: $caps_add, del: $caps_del\n";
$logmsg .= "[notes] $POST{'notes'}";
LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
"acctedit", $logmsg);
}
# done looping through possible bits
LJ::modify_caps($u, \@bits_set, \@bits_del);
return "<?h1 Success! h1?><?p Changes to this account have been saved. p?>";
}
### update form
$ret .= "<form method='post' action='acctedit.bml?user=$u->{'user'}'>";
$ret .= "<table border='1' cellspacing='0' cellpadding='5'>";
$ret .= "<tr><td>Bit</td><td>Class</td><td>Set?</td><td>Expiration</td></tr>";
# so we know which bits to skip when going through %LJ::CAP hash
my %special_bit = ($LJ::Pay::capinf{'paid'}->{'bit'} => 1);
while (my ($itemname, $ref) = each %LJ::Pay::bonus) {
$special_bit{$ref->{'cap'}} = 1;
}
# do bit-only features
foreach my $bit (sort { $a <=> $b } keys %LJ::CAP) {
next unless ref $LJ::CAP{$bit} eq 'HASH';
next if $special_bit{$bit};
my $has_cap = $u->{'caps'} & 1 << $bit || 0;
my $name = $LJ::CAP{$bit}->{'_name'} || "<i>(no name)</i>";
$name = "<b>$name</b>" if $has_cap;
$ret .= "<tr><td align='middle'>$bit</td><td><label for='cap_${bit}_set'>$name</label></td><td>";
$ret .= LJ::html_check({ 'type' => 'check', 'name' => "cap_${bit}_set",
'id' => "cap_${bit}_set", 'value' => 1,
'selected' => $has_cap }) . "</td>";
# expiration
$ret .= "<td>&nbsp;</td></tr>";
}
# paid account status
{
my $bit = $LJ::Pay::capinf{'paid'}->{'bit'};
my $has_cap = $u->{'caps'} & 1 << $bit || 0;
my $name = "Paid Account";
$name = "<b>$name</b>" if $has_cap;
$ret .= "<tr><td align='middle'>$bit</td><td><label for='cap_${bit}_set'>$name</label></td>";
$ret .= "<td>";
$ret .= LJ::html_check({ 'type' => 'check', 'name' => "cap_${bit}_set",
'id' => "cap_${bit}_set", 'value' => 1,
'selected' => $has_cap }) . "</td>";
# get paid account status from database
my $exp = $dbh->selectrow_array("SELECT paiduntil FROM paiduser WHERE userid=?",
undef, $u->{'userid'});
# expiration text box
$ret .= "<td>";
$ret .= LJ::html_text({ 'name' => "paid_exp", 'value' => $exp || $zerodate,
'size' => 19, 'maxlength' => 19 });
$ret .= "</td></tr>";
}
# bonus features
foreach my $itemname (sort { $LJ::Pay::bonus{$a}->{'bit'} <=> $LJ::Pay::bonus{$b}->{'bit'} } keys %LJ::Pay::bonus) {
my $bitem = $LJ::Pay::bonus{$itemname};
next unless ref $bitem eq 'HASH';
my $bit = $bitem->{'cap'};
my $has_cap = $u->{'caps'} & 1 << $bit || 0;
my $name = $bitem->{'name'};
$name = "<b>$name</b>" if $has_cap;
$ret .= "<tr><td align='middle'>$bit</td><td><label for='cap_${bit}_set'>$name</label></td>";
$ret .= "<td>";
if (defined $bit) {
$ret .= LJ::html_check({ 'type' => 'check', 'name' => "cap_${bit}_set",
'id' => "cap_${bit}_set", 'value' => 1,
'selected' => $has_cap });
} else {
$ret .= "&nbsp;";
}
$ret .= "</td>";
# get activation status from the db
my ($exp, $size, $daysleft) =
$dbh->selectrow_array("SELECT expdate, size, daysleft FROM paidexp " .
"WHERE userid=? AND item=?",
undef, $u->{'userid'}, $itemname);
$size += 0;
$daysleft += 0;
# expire text box
$ret .= "<td>";
$ret .= LJ::html_text({ 'name' => "${itemname}_exp", 'value' => $exp || $zerodate,
'size' => 19, 'maxlength' => 19 });
# need a size box?
if (LJ::Pay::is_bonus($bonus_caps{$bit}, 'sized')) {
$ret .= "<br />Size: ";
$ret .= LJ::html_text({ 'name' => => "${itemname}_size", 'value' => $size,
'size' => 5, 'maxlength' => 5 });
}
# daysleft text box
$ret .= "<br />Daysleft: ";
$ret .= LJ::html_text({ 'name' => => "${itemname}_daysleft", 'value' => $daysleft,
'size' => 3, 'maxlength' => 3 });
$ret .= "</td></tr>";
}
$ret .= "<tr><td colspan='4' align='left'><b>Notes: </b> <small><i>(required)</i></small><br />";
$ret .= LJ::html_textarea({ 'name' => 'notes', 'rows' => 3, 'cols' => 60, 'wrap' => 'soft' });
$ret .= "</td></tr><tr><td colspan='4' align='right'>";
$ret .= LJ::html_submit('Update') . "</td></tr>";
$ret .= "</table>";
$ret .= "</form>";
return $ret;
}
_code?>

View File

@@ -0,0 +1,43 @@
<?_code
{
use strict;
use vars qw(%POST);
my $remote = LJ::get_remote();
return "You must first <a href=\"/login.bml?ret=1\">log in</a>."
unless $remote;
return "You don't have access to use this tool."
unless LJ::remote_has_priv($remote, "moneyenter");
if ($POST{'payid'} && $POST{'piid'}) {
my $dbh = LJ::get_db_writer();
my ($piid, $status) =
$dbh->selectrow_array("SELECT piid, status FROM payitems " .
"WHERE piid=? AND payid=?",
undef, $POST{'piid'}, $POST{'payid'});
return "<b>Error:</b> Payid/Piid pair not found!"
unless $piid;
return "<b>Error:</b> Status = '$status' (not 'pending')"
unless $status eq 'pend';
$dbh->do("UPDATE payitems SET giveafter=NULL " .
"WHERE piid=? AND payid=? AND status='pend'",
undef, $POST{'piid'}, $POST{'payid'});
return "<b>Success:</b> Delivery date set to now. " .
"(PAYID: $POST{'payid'}, PIID: $POST{'piid'})";
}
return "This tool will will set an item's delivery date to be now." .
"<form method='post' action='delivernow.bml'>\n" .
"<p>Payid: " . LJ::html_text({ 'name' => 'payid', 'size' => 10 }) . "\n" .
"Piid: " . LJ::html_text({ 'name' => 'piid', 'size' => 10 }) . "</p>\n" .
LJ::html_submit(undef, 'Change');
"</form>\n";
}
_code?>

View File

@@ -0,0 +1,88 @@
<html>
<head><title>Deposit Slip</title>
<style>
body, td { font-size: 10pt; font-family: arial, helvetica; }
</style>
</head>
<body>
<?_code
{
use strict;
use vars qw(%GET);
my $dbh = LJ::get_db_writer();
my ($ret, $sth);
my $remote = LJ::get_remote();
unless (LJ::remote_has_priv($remote, "moneyview")) {
if ($remote) {
return "You don't have access to see this.";
} else {
return "You must first <a href=\"/login.bml?ret=1\">log in</A>.";
}
}
my $from = $GET{'from'};
unless (defined $from) {
$ret .= "<form method='get'>after (yyyy-mm-dd[ hh:mm[:ss]]): <input name='from' size='20'> Opt. end: <input name='to' size='20'> <input type='submit' value='Make Report'> </form>";
return $ret;
}
my $to = $GET{'to'} || $dbh->selectrow_array("SELECT NOW()");
$sth = $dbh->prepare("SELECT p.payid, u.user, p.daterecv, p.amount, p.months, p.forwhat, p.used, p.mailed, p.method FROM payments p LEFT JOIN useridmap u ON u.userid=p.userid WHERE p.mailed<>'C' AND method IN ('cash', 'check', 'moneyorder') AND p.daterecv > ? AND p.daterecv <= ? ORDER BY p.daterecv");
$sth->execute($from, $to);
my @pays;
push @pays, $_ while $_ = $sth->fetchrow_hashref;
return "(none)" unless @pays;
my $in = join(',', map { $_->{'payid'} } @pays);
$sth = $dbh->prepare("SELECT payid, pval FROM payvars WHERE payid IN ($in) AND pkey='notes'");
$sth->execute;
my %notes;
while (my ($id, $v) = $sth->fetchrow_array) {
$notes{$id} .= ", " if $notes{$id};
$notes{$id} = $v;
}
$ret .= "<h1>Received Payments</h1><span style='font-size: 13pt'><b>" . $pays[0]->{'daterecv'} . " to " .
$pays[-1]->{'daterecv'} . "</b></span>";
$ret .= "<p><table cellpadding='4' cellspacing='1' border='1'>\n";
$ret .= "<tr><td><b>Order#</b></td>";
$ret .= "<td><b>Date</b></td>";
$ret .= "<td><b>Type</b></td>";
$ret .= "<td><b>User</b></td>";
$ret .= "<td><b>Notes</b></td>";
$ret .= "<td><b>Amount</b></td></tr>\n";
my $tot = 0;
foreach my $p (@pays)
{
my $amount = sprintf("\$%.02f", $p->{'amount'});
$tot += $p->{'amount'};
my $date = substr($p->{'daterecv'}, 0, 10);
$ret .= "<tr valign='top'><td>$p->{'payid'}</td>";
$ret .= "<td><nobr>$date</nobr></td>";
$ret .= "<td>$p->{'method'}</td>";
$ret .= "<td>$p->{'user'}</td>";
$ret .= "<td>$notes{$p->{'payid'}}</td>";
$ret .= "<td align='right'>$amount</td>";
$ret .= "</tr>\n";
}
$ret .= "<tr><td colspan='5'></td><td align='right'><b>\$" . sprintf("%.02f", $tot) . "</b></td></tr>\n";
$ret .= "</table>";
return $ret;
}
_code?>
</body>
</html>

View File

@@ -0,0 +1,115 @@
<html>
<head><title>Enter Batch</title></head>
<body>
<?_code
{
use strict;
use vars qw(%POST);
my $ret;
my $remote = LJ::get_remote();
unless (LJ::remote_has_priv($remote, "moneyenter")) {
return "You don't have access to enter payments: need 'moneyenter' priv."
if $remote;
return "<?needlogin?>";
}
my $do_proc = LJ::did_post() && ! $POST{'new'};
my $dbh = LJ::get_db_writer();
my $row = sub {
my $i = shift;
my ($cart, $amt, $meth, $country, $state, $notes) =
$POST{'new'} ? () : map { LJ::trim($POST{"${_}_$i"}) } qw(cart amt meth country state notes);
my $rowhtml = sub {
my $col = shift;
$ret .= $col ? "<tr bgcolor='$col'>" : "<tr>";
$ret .= "<td>#" . LJ::html_text({ name => "cart_$i", value => $cart, size => 13 }) . "</td>";
$ret .= "<td>\$" . LJ::html_text({ name => "amt_$i", value => $amt, size => 6 }) . "</td>";
$ret .= "<td>" . LJ::html_select({ name => "meth_$i", selected => $meth, },
qw(check check cash cash moneyorder moneyorder)) . "</td>";
$ret .= "<td>" . LJ::html_text({ name => "country_$i", value => defined $country ? $country : 'US',
size => 2, maxlength => 70 }) . "</td>";
$ret .= "<td>" . LJ::html_text({ name => "state_$i", value => $state,
size => 2, maxlength => 70 }) . "</td>";
$ret .= "<td>" . LJ::html_text({ name => "notes_$i", value => $notes,
size => 60, maxlength => 255 }) . "</td>";
$ret .= "</tr>\n";
return undef;
};
my $err = sub {
my $errmsg = shift;
$rowhtml->("#ff5050");
$ret .= "<tr bgcolor='#ff9090'><td colspan='6'>$errmsg</td></tr>\n";
};
return $rowhtml->() unless $do_proc && $cart;
return $err->("Invalid order format (should be like 1234-342)")
unless $cart =~ /^\d+-\d+$/;
return $err->("Invalid payment amount")
unless $amt =~ /^\d+(\.\d\d)?$/;
my $cartobj = LJ::Pay::load_cart($cart);
return $err->("Cannot find order number") unless $cartobj;
return $err->("Order price of \$$cartobj->{'amount'} doesn't match paid amount")
unless $cartobj->{'amount'}*100 == $amt*100;
# make sure that the cart is valid and ready for processing, but don't do
# checks if the cart is already completely processed, since it doesn't matter
# in that case anyway and errors will likely be found
unless ($cartobj->{'used'} eq 'Y') {
return $err->("Cart is no longer valid. Cannot process payment.")
unless LJ::Pay::is_valid_cart($cartobj);
}
# validate state/country
{
my $errstr;
my ($ctry, $st) = LJ::Pay::check_country_state($country, $state, \$errstr);
return $err->("Error: $errstr") if $errstr;
LJ::Pay::payid_set_state($cartobj->{payid}, $ctry, $st);
}
# only update once (from cart to 'N' (pending))
$dbh->do("UPDATE payments SET used='N', mailed='N', daterecv=NOW() ".
"WHERE payid=? AND mailed='C'", undef, $cartobj->{'payid'});
# allow method to be updated multiple times (to fix error)
$dbh->do("UPDATE payments SET method=? WHERE payid=?", undef,
$meth, $cartobj->{'payid'});
# likewise, keep letting notes be added (as long as they're different)
if ($notes &&
! $dbh->selectrow_array("SELECT COUNT(*) FROM payvars WHERE ".
"payid=? AND pkey='notes' AND pval=?",
undef, $cartobj->{'payid'}, $notes))
{
$dbh->do("INSERT INTO payvars (payid, pkey, pval) VALUES (?,?,?)", undef,
$cartobj->{'payid'}, "notes", $notes);
}
# Note that we've received a valid payment from this user
# * FIXME: could be faster, but this page is seldom-used
if (my $u = LJ::load_userid($cartobj->{userid})) {
LJ::Pay::note_payment_from_user($u);
}
return $rowhtml->("#c0ffc0");
};
$ret .= "<form method='post'>";
$ret .= "<table><tr valign='bottom'><td>order number</td><td>amt paid</td><td>method</td><td colspan=2>country,<br />state</td><td>internal notes (name, return addr)</td></tr>\n";
for (1..20) { $row->($_); }
$ret .= "</table>";
$ret .= "<p><input type='submit' value='Process'> <input type='submit' name='new' value='Blank Page'></p></form>";
return $ret;
}
_code?>
</body>
</html>

View File

@@ -0,0 +1,371 @@
<?_code
{
use strict;
use vars qw(%POST);
my $remote = LJ::get_remote();
return "<?needlogin?>" unless $remote;
return "You don't have access to enter payments: need 'moneyenter' priv."
unless LJ::remote_has_priv($remote, "moneyenter");
my $grant_perm = LJ::check_priv($remote, "grantperm");
my %methods =
( 'paypal' => 'PayPal',
'moneybookers' => 'Money Bookers',
'cash' => 'Cash',
'check' => 'Check',
'moneyorder' => 'Money Order',
'free' => 'Free',
);
if (LJ::did_post() && $POST{'submit'}) {
# determine purchase user and recipient user/email
my $user = LJ::canonical_username($POST{'user'});
my $giftfrom = LJ::canonical_username($POST{'giftfrom'});
my $rcptemail = $POST{'email'};
my $userid = 0;
my $rcptuser = $user;
my $rcptuserid = 0;
# user, no email
unless ($rcptemail) {
return LJ::bad_input("Invalid user specified.")
unless $user;
$userid = LJ::get_userid($user)
or return LJ::bad_input("User <b>$user</b> doesn't exist.");
$rcptuserid = $userid;
}
if ($giftfrom) {
$rcptuser = $user;
$rcptuserid = $userid;
$user = $giftfrom;
$userid = LJ::get_userid($giftfrom);
return LJ::bad_input("Gift user <b>$giftfrom</b> doesn't exist.")
unless $userid;
}
return LJ::bad_input("Invalid recipient specified")
unless $rcptuserid || $rcptemail;
my %pay; # payments row
my %payit; # payitems row
return LJ::bad_input("Must enter a dollar amount for this order.")
unless defined $POST{'amount'};
# handle $11.11 as well as '11.11'
$POST{'amount'} =~ s/^\$//;
$POST{'amount'} += 0;
$pay{'amount'} = $POST{'amount'};
$payit{'amt'} = $POST{'amount'};
# check for valid method
$pay{'method'} = lc($POST{'method'});
return LJ::bad_input("Invalid payment method: $pay{'method'}")
unless grep { $pay{'method'} } keys %methods;
# check datesent format
return LJ::bad_input("Invalid date format.")
unless $POST{'datesent'} =~ /^\d\d\d\d-\d\d-\d\d/;
$pay{'datesent'} = $POST{'datesent'};
# paid account
if ($POST{'item'} eq "paidacct") {
return LJ::bad_input("No months specified or auto-detected. Payment <b>not</b> entered.")
unless $POST{'paidacct_mo'};
$payit{'subitem'} = undef;
$payit{'qty'} = $POST{'paidacct_mo'};
# perm account
} elsif ($POST{'item'} eq 'perm') {
# need a special priv to grant perm accounts
return LJ::bad_input("You do not have permission to create permanent accounts.")
unless $grant_perm;
# coupons
} elsif ($POST{'item'} eq 'coupon') {
return LJ::bad_input("You selected a coupon but didn't enter a dollar amount.")
unless $POST{'amount'};
$payit{'subitem'} = "dollaroff";
$payit{'subitem'} .= $POST{'coupon_type'} =~ /^(tan|int)$/ ? $POST{'coupon_type'} : '';
$payit{'qty'} = undef;
# userpics
} elsif ($POST{'item'} eq 'userpic') {
return LJ::bad_input("Cannot send userpics to an email address")
unless $rcptuserid;
return LJ::bad_input("Must specify a number of months for userpics")
unless $POST{'userpic_mo'};
return LJ::bad_input("Cannot apply userpics to the account.")
unless LJ::Pay::can_apply_bool_bonus($rcptuserid, undef, 'userpic');
$payit{'qty'} = $POST{'userpic_mo'};
$payit{'subitem'} = undef;
# disk quota
} elsif ($POST{'item'} eq 'diskquota') {
return LJ::bad_input("Cannot send disk quota to an email address")
unless $rcptuserid;
return LJ::bad_input("Must specify a number of months for disk quota.")
unless $POST{'diskquota_mo'};
return LJ::bad_input("Must specify a size (in megabytes) for disk quota.")
unless $POST{'diskquota_size'};
return LJ::bad_input("Cannot apply disk quota to account.")
unless LJ::Pay::can_apply_sized_bonus($rcptuserid, undef, 'diskquota',
$POST{'diskquota_size'}, $POST{'diskquota_mo'});
$payit{'qty'} = $POST{'diskquota_mo'};
my ($prev_exp, $prev_size) = LJ::Pay::get_bonus_dim($rcptuserid, 'diskquota');
$payit{'subitem'} = "$POST{'diskquota_size'}-$prev_exp-$prev_size";
# rename token
} elsif ($POST{'item'} eq 'rename') {
# subitem, qty need to be undef, so that's already fine
# verify it's a valid item
} else {
return LJ::bad_input("Must select the item the user is paying for.");
}
$payit{'item'} = $POST{'item'};
$payit{'rcptemail'} = $rcptemail || undef;
$payit{'rcptid'} = $rcptuserid || 0;
# at this point, the following should be properly set and validated:
# - %pay: (datesent, amount)
# - %payit: (rcptid, rcptemail, amt, item, subitem, qty)
### now, insert a payment
my $dbh = LJ::get_db_writer();
$dbh->do("INSERT INTO payments (anum, userid, datesent, daterecv, amount, " .
"used, mailed, notes, method, forwhat) " .
"VALUES (0, ?, ?, NOW(), ?, 'N', 'N', ?, ?, 'cart')",
undef, $userid, $pay{'datesent'}, $pay{'amount'},
$POST{'notes'}, $pay{'method'});
return "<?h1 Database Error! h1?><?p " . $dbh->errstr . " p?>" if $dbh->err;
my $payid = $dbh->{'mysql_insertid'};
$payit{'payid'} = $payid;
$dbh->do("INSERT INTO payvars (payid, pkey, pval) VALUES (?, 'notes', ?)",
undef, $payid, $POST{'inote'}) if $POST{'inote'};
# create a coupon if necessary
if ($payit{'item'} eq "coupon") {
my $type = "dollaroff";
my $cptype = $POST{'coupon_type'} =~ /^(tan|int)$/ ? $POST{'coupon_type'} : '';
$type .= $cptype;
($payit{'tokenid'}, $payit{'token'}) =
LJ::Pay::new_coupon($type, $payit{'amt'}, $rcptuserid, $payid);
return "<?h1 Error h1?><?p Error generating coupon. p?>"
unless $payit{'tokenid'} && $payit{'token'};
my $cpemail = $rcptemail;
if ($rcptuserid) {
my $u = LJ::load_userid($rcptuserid);
$cpemail = $u->{'email'} if $u;
# we kindasorta trust this user now
LJ::Pay::note_payment_from_user($u);
}
LJ::Pay::send_coupon_email($cpemail, $payit{'token'}, $payit{'amt'}, $cptype);
}
# now that we've optionally created a coupon token, log a payitem row
{
my $cartobj = LJ::Pay::load_cart("$payid-0");
LJ::Pay::add_cart_item($cartobj, \%payit)
or return "<?h1 Error h1?><?p Error generating cart item. p?>";
}
# log a statushistory row if there's a userid to associate it with
if ($userid) {
my $mo = $POST{'months'}+0;
my $rcpt = "rcptemail=$rcptemail";
if ($rcptuserid) {
my $u = LJ::load_userid($rcptuserid);
$rcpt = "rcptuser=$u->{'user'}" if $u;
}
LJ::statushistory_add($userid, $remote->{'userid'}, "payenter",
"item=$payit{'item'}, subitem=$payit{'subitem'}, qty=$payit{'qty'}, amt=$payit{'amt'}, $rcpt");
}
# send email notification of this action
my $rcpt = $rcptuser || $rcptemail;
my $msgbody = "Entered by $remote->{'user'}: payment \#$payid for $rcpt\n\n";
foreach my $k (sort keys %POST) {
$msgbody .= "$k:\n===============\n$POST{$k}\n\n";
}
LJ::send_mail({ 'to' => "paypal\@$LJ::DOMAIN", # TODO: not paypal
'from' => $LJ::BOGUS_EMAIL,
'subject' => "Payment \#$payid -- $rcpt",
'body' => $msgbody,
});
$dbh->do("INSERT INTO paymentsearch (payid, ikey, ival) VALUES (?,?,?)",
undef, $payid, "handemail", $rcptemail)
unless $userid;
return "<?h1 Success h1?><?p Payment \#$payid entered for <b>$rcpt</b> for \$$pay{'amount'} for: p?><ul>" .
join("", map { "<li>$_->[0]=$_->[1]</li>" }
(['user' => $user], ['rcptuser' => $rcptuser], ['rcptemail' => $rcptemail],
['method' => lc($POST{'method'})], ['item' => $payit{'item'}],
['subitem' => $payit{'subitem'}], ['qty' => $payit{'qty'}], ['token' => $payit{'token'} ])
) .
"</ul>";
}
# payment form
my $ret;
$ret = "Hello, $remote->{'user'}! Enter a payment:";
$ret .= "<hr /><form method='post'>";
$ret .= "<table align='left'><tr valign='top'><td align='left'>";
$ret .= "<table><tr><td align='right'>Payment Type:</td>";
$ret .= "<td>" . LJ::html_select({ 'name' => 'method' },
'', '(select)', map { $_ => $methods{$_} } keys %methods) . "</td></tr>";
$ret .= "<tr>";
$ret .= $GET{'newacct'} ?
("<td align='right'>Rcpt Email:</td>" .
"<td>" . LJ::html_text({ 'name' => 'email', 'size' => '40', 'maxlength' => 50 }) .
"(<a href='enternew.bml'>back</a>)</td>") :
("<td align='right'>Rcpt Username:</td>" .
"<td>" . LJ::html_text({ 'name' => 'user', 'size' => 15, 'maxlength' => 15 }) .
"(<a href='enternew.bml?newacct=1'>new account?</a>)</td>");
$ret .= "</tr>";
$ret .= "<tr valign='top'><td align='right'>Gift From <sup>(Opt)</sup>:</td><td>";
$ret .= LJ::html_text({ 'name' => 'giftfrom', 'size' => 15, 'maxlength' => 15 });
$ret .= "</td></tr>";
$ret .= "<tr valign='top'><td align='right'>Date Sent:</td><td>";
$ret .= LJ::html_text({ 'name' => 'datesent', 'size' => 23, 'maxlength' => 19, 'value' => LJ::mysql_time() });
$ret .= "<br /><tt>yyyy-mm-dd <font color='#909090'>[hh:mm:ss]</font></tt></td></tr>";
$ret .= "<tr><td align='right'>Amount:</td><td>";
$ret .= "\$" . LJ::html_text({ 'name' => 'amount', 'size' => 6, 'maxlength' => 6 }) . " USD</td></tr>";
# notes
$ret .= "<tr><td align='right' valign='top'>Internal note:</td><td>";
$ret .= LJ::html_text({ 'name' => 'inote', 'size' => 40, 'maxlength' => 255 });
$ret .= "</td></tr>";
$ret .= "<tr><td align='right' valign='top'>Note to user:</td><td>";
$ret .= LJ::html_textarea({ 'name' => 'notes', 'rows' => 10, 'cols' => 40, 'wrap' => 'soft' });
$ret .= "</td></tr>";
$ret .= "<tr><td>&nbsp;</td><td>";
$ret .= LJ::html_submit('submit', "Process Payment");
$ret .= "</td></tr></table>";
$ret .= "</td><td align='left'>";
# indivual item types
$ret .= "<table cellspacing=0 cellpadding=0>";
my $sep = "<tr><td>&nbsp;</td><td><hr></td></tr>";
# paid time
$ret .= "<tr valign='top'><td align='right'>";
$ret .= "<label for='item-paidacct'>Paid time:</label> ";
$ret .= LJ::html_check({ 'type' => 'radio', 'name' => 'item', 'value' => 'paidacct',
'id' => 'item-paidacct' }) . "</td>";
$ret .= "<td>Months: ";
$ret .= LJ::html_text({ 'name' => 'paidacct_mo', 'size' => 2, 'maxlength' => 2 });
$ret .= "</td></tr>";
$ret .= $sep;
# permanent account
if ($grant_perm) {
$ret .= "<tr valign='top'><td align='right'>";
$ret .= "<label for='item-perm'>Permanent Acct:</label> ";
$ret .= LJ::html_check({ 'type' => 'radio', 'name' => 'item', 'value' => 'perm',
'id' => 'item-perm' }) . "</td>";
$ret .= "<td>&nbsp;</td></tr>";
$ret .= $sep;
}
# userpics
$ret .= "<tr valign='top'>";
$ret .= "<td align='right'><label for='item-userpic'>Userpics:</label> ";
$ret .= LJ::html_check({ 'type' => 'radio', 'name' => 'item', 'value' => 'userpic',
'id' => 'item-userpic' }) . "</td>";
$ret .= "<td> Months: ";
$ret .= LJ::html_text({ 'name' => 'userpic_mo', 'size' => 2, 'maxlength' => 2 });
$ret .= "</td></tr>";
$ret .= $sep;
# quota
$ret .= "<tr valign='top'>";
$ret .= "<td align='right'><label for='item-diskquota'>Disk Quota:</label> ";
$ret .= LJ::html_check({ 'type' => 'radio', 'name' => 'item', 'value' => 'diskquota',
'id' => 'item-diskquota' }) . "</td>";
$ret .= "<td><table cellspacing=0><tr valign='top'><td>Size:</td><td>";
$ret .= LJ::html_text({ 'name' => 'diskquota_size', 'size' => 4, 'maxlength' => 4 });
$ret .= "</td></tr><tr valign='top'><td>Months:</td><td>";
$ret .= LJ::html_text({ 'name' => 'diskquota_mo', 'size' => 2, 'maxlength' => 2 });
$ret .= "</td></tr></table";
$ret .= "</td></tr>";
$ret .= $sep;
# coupons
$ret .= "<td align='right'><label for='item-coupon'>Coupon:</label> ";
$ret .= LJ::html_check({ 'type' => 'radio', 'name' => 'item', 'value' => 'coupon',
'id' => 'item-coupon' }) . "</td>";
$ret .= "<td> ". LJ::html_select({ 'type' => 'check', 'name' => 'coupon_type',
'id' => 'coupon_type', 'value' => 'gen' },
'gen' => "General",
'int' => "Intangible only",
'tan' => "Tangible only", );
$ret .= "</td></tr>";
$ret .= $sep;
# rename
$ret .= "<tr valign='top'>";
$ret .= "<td align='right'><label for='item-rename'>Rename:</label> ";
$ret .= LJ::html_check({ 'type' => 'radio', 'name' => 'item', 'value' => 'rename',
'id' => 'item-rename' }) . "</td>";
$ret .= "<td>&nbsp;</td></tr>";
$ret .= "</table>";
return $ret;
}
_code?>

View File

@@ -0,0 +1,66 @@
<html><head>
<title>Fraud suspects</title>
<style>
h1 { font-size: 20pt; }
h2 { font-size: 17pt; }
.label { background: #ccc; text-align: right; vertical-align: top; }
.data { background: #eee; padding-left: 10px; }
.tbl td { border-bottom: 1px solid #aaa; }
.tbl
{
font-family: Verdana, sans-serif;
font-size: 11px;
border-top: 1px solid #aaa;
border-right: 1px solid #aaa;
border-left: 1px solid #aaa;
width: 500px; margin-bottom: 10px;
}
</style>
</head><body>
<?_code
my $dbh = LJ::get_db_writer();
my $ret;
my $remote = LJ::get_remote();
my $viewall = LJ::remote_has_priv($remote, "moneyview");
my $viewsearch = 0;
if (! $viewall) {
$viewsearch = LJ::remote_has_priv($remote, "moneysearch");
}
unless ($viewall || $viewsearch) {
return "You don't have access to see this, or you're not logged in.";
}
my $sql = q{
SELECT * FROM fraudsuspects
};
my $data = $dbh->selectall_hashref($sql, 'payid', undef);
$ret .= "<h1>Possible fraudulent payments</h1>";
foreach my $row (sort { $a->{dateadd} <=> $b->{dateadd} } values %$data) {
my $added = gmtime($row->{dateadd});
my $reason = $row->{reason};
$reason =~ s#\n#<br />#mg;
$ret .= <<EOF;
<table border='0' cellspacing='0' class='tbl'>
<tr>
<td class='label'>Payid:</td>
<td class='data'><a href='paiddetails.bml?payid=$row->{payid}'>$row->{payid}</a></td>
</tr>
<td class='label'>Date added:</td>
<td class='data'>$added</td>
</tr>
<td class='label'>Reason:</td>
<td class='data'>$reason</td>
</tr>
</table>
EOF
}
return $ret;
_code?>
</body></html>

View File

@@ -0,0 +1,3 @@
<h1>Other resources to know:</h1>
<a href="http://www.livejournal.com/paidaccounts/apply.bml">http://www.livejournal.com/paidaccounts/apply.bml</a> -- apply a code towards an account (the logged in user must do it)

View File

@@ -0,0 +1,213 @@
<html><head>
<title>Paid Details</title>
<style>
h1 { font-size: 20pt; }
h2 { font-size: 17pt; }
.fraud
{
position: absolute;
top: 10px;
left: 600px;
border: 2px solid #842020;
padding: 4px;
background: #eed9d9;
width: 400px;
}
</style>
</head><body>
<?_code
my $dbh = LJ::get_db_writer();
my ($ret, $sth);
my $remote = LJ::get_remote();
my $viewall = LJ::remote_has_priv($remote, "moneyview");
my $viewsearch = 0;
if (! $viewall) {
$viewsearch = LJ::remote_has_priv($remote, "moneysearch");
}
unless ($viewall || $viewsearch) {
return "You don't have access to see this, or you're not logged in.";
}
$FORM{'payid'} =~ s/\-\d+//;
unless ($FORM{'payid'}) {
return "<form method='get'>Enter payid (or order number): <input name='payid' size='10'> <input type='submit' value='View'></form>";
}
my $payid = $FORM{'payid'}+0;
## for people without moneyview priv, they have to have userid arg
my $extrawhere = "";
if (! $viewall) {
my $userid = $FORM{'userid'}+0;
$extrawhere = "AND p.userid=$userid";
}
if ($FORM{'userid'} eq "0") { # not == 0
$sth = $dbh->prepare("SELECT * FROM payments WHERE payid=$payid AND userid=0");
} else {
$sth = $dbh->prepare("SELECT p.*, u.user FROM payments p LEFT JOIN useridmap u ON u.userid=p.userid WHERE p.payid=$payid $extrawhere");
}
$sth->execute;
my $pm = $sth->fetchrow_hashref;
return "Invalid payment ID, or missing arguments" unless $pm;
# see if a code is associated with this payment:
my $cd = $dbh->selectrow_hashref("SELECT ac.* FROM acctpay ap, acctcode ac ".
"WHERE ap.payid=$payid AND ap.acid=ac.acid");
if ($cd) {
my $code = LJ::acct_code_encode($cd->{'acid'}, $cd->{'auth'});
$ret .= "<b>From code: </b> <tt>$code</tt>";
if ($cd->{'userid'}) {
$ret .= " (created by " . LJ::ljuser(LJ::get_username($dbh, $cd->{'userid'})) . ")";
}
if ($cd->{'rcptid'}) {
$ret .= " (used by " . LJ::ljuser(LJ::get_username($dbh, $cd->{'rcptid'})) . ")";
} else {
$ret .= " (code is unused)";
}
}
# see if a rename is associated with this payment
if ($pm->{'forwhat'} eq "rename") {
my $rn = $dbh->selectrow_hashref("SELECT renid, token, fromuser, touser, rendate ".
"FROM renames WHERE payid=?", undef, $payid);
if ($rn) {
my $code = sprintf("%06x%s", $rn->{'renid'}, $rn->{'token'});
$ret .= "<p><b>Rename Code</b>: <tt>$code</tt> (from: $rn->{'fromuser'}, to: $rn->{'touser'}, rendate: $rn->{'rendate'})</p>";
}
}
$ret .= "<h1>Payment \#$pm->{'payid'}</h1>";
$ret .= "<b>Amount:</b> \$$pm->{'amount'} <b>Method:</b> $pm->{'method'} <b>For:</b> $pm->{'forwhat'} ";
if ($pm->{'giftafter'}) {
$ret .= " (to be delivered: " . scalar(gmtime($pm->{'giftafter'})) . " (GMT)";
}
$ret .= "<br /><b>Date sent:</b> $pm->{'datesent'} <b>Recv:</b> $pm->{'daterecv'}";
$ret .= "<br /><b>Used:</b> $pm->{'used'} <b>Mailed:</b> $pm->{'mailed'}";
$ret .= "<br /><b>Buyer:</b> ";
if ($pm->{'user'}) {
$ret .= LJ::ljuser($pm->{'user'});
}
if ($pm->{'notes'}) {
my $not = LJ::eall($pm->{'notes'});
$not =~ s/\n/<br>\n/g;
$ret .= "<br /><b>Notes:</b> $not";
}
# clear fraud flag
if (LJ::did_post() && $FORM{fraudclear}) {
LJ::Pay::payvar_set($payid, "fraud_status", "clear");
$dbh->do("DELETE FROM fraudsuspects WHERE payid=?", undef, $payid);
}
# vars
$ret .= "<p>";
$sth = $dbh->prepare("SELECT pkey, pval FROM payvars WHERE payid=?");
$sth->execute($payid);
my ($refund, $fraud_status);
while (my ($k, $v) = $sth->fetchrow_array) {
if ($k eq "an-refund") {
my @parts = split(/,/, $v);
$refund = $v; $v = "<i>(hidden)</i> expir=$parts[1]";
}
$fraud_status = $v if $k eq 'fraud_status';
$ret .= "<tt><b>$k</b></tt> = $v<br />\n";
}
if ($fraud_status eq 'suspect') {
my $sql = q{
SELECT dateadd, reason
FROM fraudsuspects
WHERE payid=?
};
my ($added, $reason) = $dbh->selectrow_array($sql, undef, $payid);
$added = $added ? gmtime($added) . ' GMT' : 'unknown';
$reason ||= '?';
$reason =~ s#\n#<br />#mg;
$ret .= <<EOF;
<form method='post' action='paiddetails.bml'>
<div class='fraud'>
This payment has been flagged as possible fraud.
<br /><br />
<strong>Date added: </strong>$added<br />
<strong>Reason(s): </strong><br />
<div style='margin-left: 20px'>$reason</div>
<br />
<input type='submit' name='fraudclear' value='Clear'>
<input type='hidden' value='$payid' name='payid'>
</div>
</form>
EOF
}
$sth = $dbh->prepare("SELECT ikey, ival FROM paymentsearch WHERE payid=?");
$sth->execute($payid);
while (my ($k, $v) = $sth->fetchrow_array) {
$ret .= "<tt><b>$k</b></tt> = $v<br />\n";
}
$ret .= "</p>";
my $cartobj;
if ($pm->{'forwhat'} eq "cart") {
my $cart = "$pm->{'payid'}-$pm->{'anum'}";
$ret .= "<h1>Order $cart</h1>";
$cartobj = LJ::Pay::load_cart($cart);
LJ::Pay::render_cart($cartobj, \$ret, {
'tokens' => 1,
'piids' => 1,
});
$ret .= "<small><b>all piids:</b> " . join(", ", map { $_->{'piid'} } @{$cartobj->{'items'}}) . "</small>";
}
$ret .= "<h1>Authorize.net Transaction Log</h1>";
my @anet;
$sth = $dbh->prepare("SELECT cmd, datesent, ip, amt, result, response, cmdnotes ".
"FROM authnetlog WHERE payid=?");
$sth->execute($payid);
push @anet, $_ while $_ = $sth->fetchrow_hashref;
if (@anet) {
$ret .= "<table border='1' cellpadding='2'><tr>";
foreach (qw(date/ip cmd amt result extra)) {
$ret .= "<td><b>$_</b></td>";
}
$ret .= "</tr>";
foreach my $an (@anet) {
my @fields = split(/,/, $an->{'response'});
my $extra;
if ($an->{'cmd'} eq "authcap") {
$extra = "authnet_txn = $fields[6]";
}
$ret .= "<tr><td><small>$an->{'datesent'}<br />$an->{'ip'}</small></td><td>$an->{'cmd'}</td><td>\$$an->{'amt'}</td><td><b>$an->{'result'}</b>: $fields[3]</td><td>$extra</td></tr>\n";
}
$ret .= "</table>";
} else {
$ret .= "<i>No Authorize.net history</i>";
}
$ret .= "<h1>Revoke & Refund</h1>";
$ret .= "<form method='post' action='rr.bml'>";
$ret .= LJ::html_hidden("cart", "${payid}-$cartobj->{'anum'}");
$ret .= "Item piids to revoke/refund: <input name='plist' size='30'> (comma or space separated)";
if ($cartobj->{'method'} eq "cc") {
if (! $refund) {
$ret .= "<br />Partial Card Number: <input name='partialnum' size='12'> (1234***5678) Exp. Date: <input name='expdate' size='7'> (mm/yyyy)";
}
$ret .= "<br /><input type='checkbox' value='1' name='no_refund' id='no_refund'> <label for='no_refund'>Don't refund, just revoke (if chargeback, and bank already did it)</label>\n";
}
$ret .= "<br />Opt. notes: <input name='refreason' size='40' />\n";
$ret .= "<br /><input type='submit' value='Revoke+Refund'>\n";
$ret .= "<small>[ <b>Only press once and wait!</b> ]</small>";
$ret .= "</form>";
return $ret;
_code?>
</body></html>

View File

@@ -0,0 +1,161 @@
<html>
<head><title>Paid Search</title></head>
<body>
<?_code
{
use strict;
use vars qw(%GET);
my $remote = LJ::get_remote();
return "You must first <a href=\"/login.bml?ret=1\">login</a>."
unless $remote;
unless (LJ::remote_has_priv($remote, "moneysearch") ||
LJ::remote_has_priv($remote, "moneyview"))
{
return "You don't have access to see this.";
}
my $ret;
my $user = $GET{'user'};
$ret .= "<h1>Search for payments.</h1>\n";
$ret .= "<form method='get'>";
$ret .= "Search method: ";
$ret .= LJ::html_select({ 'name' => 'method', 'selected' => $GET{'method'} },
'user' => "Username",
'email' => "Email",
'lastname' => "Last Name",
'pptxnid' => "PayPal - transaction ID",
'cpid' => "Coupon",
# 'ppemail' => "Email",
# 'pplastname' => "PayPal - last name",
# 'handemail' => "Manually entered email",
);
$ret .= " Search value: ";
$ret .= LJ::html_text({ 'name' => 'value',
'value' => $GET{'value'},
'size' => 30 });
$ret .= "<input type=\"submit\" value=\"Search\"></form><hr>";
return $ret unless $GET{'method'};
my $dbh = LJ::get_db_writer();
my $sth;
my %matched;
my @ps_vars; # payment search vars;
# by-user search
if ($GET{'method'} eq "user") {
my $user = $GET{'value'};
my $userid = LJ::get_userid($user);
unless ($userid) {
$ret .= "<p><b>Error:</b> Username not found.";
return $ret;
}
# include payments created by the user
$sth = $dbh->prepare("SELECT payid FROM payments WHERE userid=?");
$sth->execute($userid);
$matched{$_} = 1 while $_ = $sth->fetchrow_array;
# include payments with payment items for that user
$sth = $dbh->prepare("SELECT payid FROM payitems WHERE rcptid=?");
$sth->execute($userid);
$matched{$_} = 1 while $_ = $sth->fetchrow_array;
# HACK: mysql doesn't optimize these queries properly, so we'll do it by hand: much faster
{
my @acid = (
@{ $dbh->selectcol_arrayref
("SELECT acid FROM acctcode WHERE userid=? LIMIT 5000", undef, $userid)||[] },
@{ $dbh->selectcol_arrayref
("SELECT acid FROM acctcode WHERE rcptid=? LIMIT 5000", undef, $userid)||[] },
);
my $bind = join(",", map { "?" } @acid);
# include payments tied to account codes either purchased by or used by the user (new payment system)
$sth = $dbh->prepare("SELECT pi.payid FROM acctpayitem p, payitems pi " .
"WHERE pi.piid=p.piid AND p.acid IN ($bind) LIMIT 5000");
$sth->execute(@acid);
$matched{$_} = 1 while $_ = $sth->fetchrow_array;
# include payments tied to account codes either purchased by or used by the user (new payment system)
$sth = $dbh->prepare("SELECT payid FROM acctpay WHERE acid IN ($bind) LIMIT 5000");
$sth->execute(@acid);
$matched{$_} = 1 while $_ = $sth->fetchrow_array;
}
}
# by-email search
if ($GET{'method'} eq "email") {
my $email = $GET{'value'};
# payment search vars: ppemail (from a paypal payment notification)
# and 'handemail' (manually entered (before cart system))
push @ps_vars, qw(ppemail handemail);
# from rcptemail
$sth = $dbh->prepare("SELECT payid FROM payitems WHERE ".
"rcptemail=?");
$sth->execute($email);
$matched{$_} = 1 while $_ = $sth->fetchrow_array;
}
# coupon search
if ($GET{'method'} eq "cpid") {
my $cpid = $GET{'value'};
# accept $cpid-$auth, but only care about $cpid
$cpid =~ s/^(\d+).*/$1/;
# get the payid that used/bought this coupon
my ($payid, $ppayid) =
$dbh->selectrow_array("SELECT payid, ppayid FROM coupon " .
"WHERE cpid=?", undef, $1);
$matched{$payid} = 1 if $payid; # transaction coupon was used on
$matched{$ppayid} = 1 if $ppayid; # transaction where coupon was purchased
}
# paypal transaction ID or last name
push @ps_vars, "pplastname" if $GET{'method'} eq "lastname";
push @ps_vars, "pptxnid" if $GET{'method'} eq "pptxnid";
# include any paymentsearch vars the above modes might want
for my $var (@ps_vars) {
$sth = $dbh->prepare("SELECT payid FROM paymentsearch WHERE ".
"ikey=? AND ival=?");
$sth->execute($var, $GET{'value'});
$matched{$_} = 1 while $_ = $sth->fetchrow_array;
}
return $ret. "<i>No matches</i>" unless %matched;
my $in = join(',', keys %matched);
$sth = $dbh->prepare("SELECT p.*, u.user ".
"FROM payments p LEFT JOIN useridmap u ".
"ON p.userid=u.userid ".
"WHERE p.payid IN ($in) ORDER BY p.payid");
$sth->execute;
$ret .= "<table cellpadding=4 cellspacing=1 border=1><tr><td><b>Pay ID#</b></td><td><b>User</b></td><td><b>Date Sent/Recv</b><td><b>Amount</b></td><td><b>Months</b></td><td><b>Used/Mailed</b></td><td><b>Method</b></td></tr>\n";
while (my $row = $sth->fetchrow_hashref)
{
my $amount = sprintf("\$%.02f", $row->{'amount'});
my $usedmailed = "$row->{'used'}/$row->{'mailed'}";
if ($row->{'mailed'} eq "C") {
$usedmailed = "Unpaid! Still in cart!";
}
$ret .= "<TR VALIGN=TOP><TD ALIGN=CENTER><A HREF=\"paiddetails.bml?payid=$row->{'payid'}&userid=$row->{'userid'}\">#$row->{'payid'}</A></TD><TD><B><A HREF=\"/userinfo.bml?user=$row->{'user'}\">$row->{'user'}</A></B></TD><TD>$row->{'datesent'}<BR>$row->{'daterecv'}</TD><TD ALIGN=RIGHT>$amount</TD><TD ALIGN=RIGHT>$row->{'months'}</TD><TD ALIGN=CENTER>$usedmailed</TD><TD>$row->{'method'}</TD></TR>";
}
$ret .= "</table>\n";
return $ret;
}
_code?>
</body>
</html>

View File

@@ -0,0 +1,209 @@
<?_code
{
use strict;
use vars qw(%GET);
my $remote = LJ::get_remote();
return "You must first <a href=\"/login.bml?ret=1\">log in</a>."
unless $remote;
return "You don't have access to see this."
unless LJ::remote_has_priv($remote, "moneyview");
my $dbh = LJ::get_dbh("slow", "slave", "master")
or return "database unavailable";
my ($ret, $sth);
my $wholemonth = 0;
if ($GET{'day'} eq "*") { $wholemonth = 1; }
my $year = $GET{'year'}+0;
my $month = $GET{'month'}+0;
my $day = $GET{'day'}+0;
unless ($year && $month) {
my @time = localtime();
$year = $time[5]+1900;
$month = $time[4]+1;
$day = $time[3];
}
if ($wholemonth) { $day = "*"; }
$ret .= "<form method='GET'>";
$ret .= "Year: " . LJ::html_text({ 'name' => 'year', 'size' => 4, 'value' => $year }) . " ";
$ret .= "Month: " . LJ::html_text({ 'name' => 'month', 'size' => 2, 'value' => $month }) . " ";
$ret .= "Day: " . LJ::html_text({ 'name' => 'day', 'size' => 2, 'value' => $day }) . " ";
$ret .= LJ::html_submit('View') . "</form> (enter * for day to get month report)";
my ($date_low, $date_high);
# whole month
my $fmt = sub { $dbh->quote(sprintf("%02d-%02d-%02d 00:00:00", @_)) };
if ($day eq '*') {
$date_low = $fmt->($year, $month, '01');
if ($month+1 > 12) {
$date_high = $fmt->($year+1, 1, '01');
} else {
$date_high = $fmt->($year, $month+1, '01');
}
} else {
$date_low = $fmt->($year, $month, $day);
if ($day+1 > LJ::days_in_month($month, $year)) {
if ($month+1 > 12) {
$date_high = $fmt->($year+1, 1, '01');
} else {
$date_high = $fmt->($year, $month+1, '01');
}
} else {
$date_high = $fmt->($year, $month, $day+1);
}
}
$sth = $dbh->prepare("SELECT * FROM payments WHERE mailed<>'C' AND daterecv>$date_low AND daterecv<$date_high");
$sth->execute;
my @rows = ();
push @rows, $_ while $_ = $sth->fetchrow_hashref;
my $u = LJ::load_userids( map { $_->{userid} } @rows );
$ret .= "<table style='margin-top: 10px;' cellpadding='4' cellspacing='1' border='1'><tr><td><b>Pay ID#</b></td><td><b>User</b></td><td><b>Date Sent/Recv</b><td><b>Amount</b></td><td><b>Used/Mailed</b></td><td><b>Method</b></td></tr>\n";
my $totalmoney = 0;
my %methodcount = ();
my %methodtotal = ();
my %daycount = ();
my %daytotal = ();
my $row_ct = 0;
my $row_show = 0;
my $row_skip = 0;
my $row_html;
foreach my $row (@rows)
{
my $amount = sprintf("\$%.02f", $row->{'amount'});
$totalmoney += $row->{'amount'};
$methodcount{$row->{'method'}}++;
$methodtotal{$row->{'method'}} += $row->{'amount'};
if ($row->{'daterecv'} =~ /^(\d\d\d\d-\d\d-\d\d)/) {
my $day = $1;
$daycount{$day}++;
$daytotal{$day} += $row->{'amount'};
}
$row_ct++;
next if $GET{'skip'} && ++$row_skip <= $GET{'skip'};
if ($row_show < 500) {
my $user = $u->{$row->{userid}}->{user};
$row_show++;
$row_html .= "<tr valign='top'><td align='center'><a href=\"paiddetails.bml?payid=$row->{'payid'}\">#$row->{'payid'}</a></td><td><b><a href=\"/userinfo.bml?user=$user\">$user</a></b></td><td>$row->{'datesent'}<br />$row->{'daterecv'}</td><td align='right'>$amount</td><td align='center'>$row->{'used'}/$row->{'mailed'}</td><td>$row->{'method'}</td></tr>";
}
}
my $slinks;
if ($GET{'skip'}) {
$slinks .= "<a href=\"" . BML::self_link({ 'skip' => $GET{'skip'} - 500}) . "\">&lt;&lt; Back</a> ";
}
if ($row_show != $row_ct) {
my $from = $GET{'skip'}+1;
my $to = $row_show+$GET{'skip'};
$slinks .= "(Records $from-$to of $row_ct) ";
}
if ($GET{'skip'} + $row_show < $row_ct) {
$slinks .= "<a href=\"" . BML::self_link({ 'skip' => $GET{'skip'} + 500}) . "\">Forward &gt;&gt;</a> ";
}
my $bar_html;
$bar_html .= "<tr><td colspan='7' align='center' bgcolor='#c0c0c0'><i>$slinks</i></td></tr>\n"
if $slinks;
$ret .= $bar_html;
$ret .= $row_html;
$ret .= $bar_html;
$ret .= "</table>\n";
return $ret unless @rows;
$ret .= "<p><b>Statistics:</b><ul>";
$ret .= "<li>Total money: <b>" . sprintf("\$%.02f", $totalmoney) . "</b></li>\n";
$ret .= "<li>Break-down by payment method:<ul>";
foreach my $method (sort keys %methodcount) {
$ret .= "<li>$method: <b>$methodcount{$method} = " . sprintf("\$%.02f", $methodtotal{$method}) . "</b></li>\n";
}
$ret .= "</ul></li>";
$ret .= "<li>Break-down by day:<ul>";
foreach my $day (sort keys %daycount) {
$ret .= "<li>$day: <b>$daycount{$day} = " . sprintf("\$%.02f", $daytotal{$day}) . "</b></li>\n";
}
$ret .= "</ul></li>";
$ret .= "<li>Break-down by item type:<ul>";
my @payid_in = map { $_->{payid} } @rows;
my $payid_bind = join(",", map { '?' } @rows);
$sth = $dbh->prepare("SELECT * FROM payitems WHERE status='done' AND payid IN ($payid_bind)");
$sth->execute(@payid_in);
die $dbh->errstr if $dbh->err;
my %idata = ();
while (my $it = $sth->fetchrow_hashref) {
my $item = $it->{item};
my $subkey = $item . (LJ::Pay::is_bonus($it, 'sized') ? ('-' . (split('-', $it->{subitem}))[0]) : '') . ($it->{qty} ? "-$it->{qty}" : '');
foreach my $ref ($idata{$item}, $idata{$item}->{sub}->{$subkey}) {
$ref->{ct}++;
$ref->{sum_pos} += $it->{amt} if $it->{amt} > 0;
$ref->{sum_neg} += $it->{amt} if $it->{amt} < 0;
}
delete $idata{$item}->{sub} if $item eq $subkey;
}
# sorts with proper string/integer comparisons on key parts
my $sort_sub = sub {
my ($aname, $asize, $aqty) = split('-', $a);
if ($asize && ! $aqty) { $aqty = $asize; $asize = 0; }
my ($bname, $bsize, $bqty) = split('-', $b);
if ($bsize && ! $bqty) { $bqty = $bsize; $bsize = 0; }
return $bname cmp $aname || $bsize <=> $asize || $bqty <=> $aqty;
};
# recursive closure to display items, counts, totals
my $show_item;
$show_item = sub {
my ($itemname, $ref) = @_;
return '' unless $ref;
my $r = "<li>$itemname: <b>$ref->{ct}</b> = " . sprintf("\$%.02f", $ref->{sum_pos});
$r .= ", " . sprintf("\$%.02f", $ref->{sum_neg}) if $ref->{sum_neg};
if (%{$ref->{sub}||{}}) {
$r .= "<ul>";
$r .= $show_item->($_, $ref->{sub}->{$_})
foreach sort $sort_sub keys %{$ref->{sub}};
$r .= "</ul>";
}
$r .= "</li>";
return $r;
};
# build tree of items
foreach my $item (sort $sort_sub keys %idata) {
$ret .= $show_item->($item, $idata{$item});
}
$ret .= "</ul></li>\n";
$ret .= "</ul></p>";
return $ret;
}
_code?>

View File

@@ -0,0 +1,199 @@
<?_code
{
use strict;
use vars qw(%POST);
use LWP;
use LWP::UserAgent;
my $dbh = LJ::get_db_writer();
my $sth;
my $remote = LJ::get_remote();
return "<?needlogin?>" unless $remote;
unless (LJ::remote_has_priv($remote, "moneyenter")) {
return "You don't have rights to refund/revoke payments.";
}
return "POST required" unless LJ::did_post();
my $cartobj = LJ::Pay::load_cart($POST{'cart'});
return "Invalid cart." unless $cartobj;
return "Can't refund/revoke items that weren't paid for."
unless $cartobj->{'used'} eq "Y";
my %refund;
foreach my $n (split(/[\s\,]+/, $POST{'plist'})) {
next if $n =~ /\D/;
unless (grep { $_->{'piid'} == $n } @{$cartobj->{'items'}}) {
return "Invalid piid ($n) for this order.";
}
$refund{$n} = 1;
}
my @revoke;
my $refund_amt;
my $total_refund = 1;
foreach my $it (@{$cartobj->{'items'}}) {
if ($refund{$it->{'piid'}} && ($it->{'status'} eq "done" || $it->{'status'} eq "pend")) {
push @revoke, $it;
$refund_amt += $it->{'amt'};
# note we've already refunded shipping for this item
$it->{'ship_refund_done'}++;
} else {
$total_refund = 0;
}
}
unless (@revoke) {
return "No items selected to refund/revoke";
}
# if we revoked any items in need of shipping, just refund all shipping costs
foreach my $it (@revoke) {
next unless LJ::Pay::item_needs_shipping($it);
foreach (@{$cartobj->{'items'}}) {
next unless $_->{'item'} eq 'shipping';
next if $_->{'ship_refund_done'};
$refund_amt += $_->{'amt'};
push @revoke, $_;
last;
}
last;
}
my $cardnum = $POST{'partialnum'};
my $expdate = $POST{'expdate'};
# refund if Auth.net
my $transid;
my $refund = sub {
my $type = shift; # "VOID" or "CREDIT";
unless (defined $transid) {
my $res;
$res = $dbh->selectrow_array("SELECT pval FROM payvars WHERE payid=? AND pkey='an-refund'",
undef, $cartobj->{'payid'});
if ($res) {
# if payment is relatively new, we still have the refund data around
my @f = split(/,/, $res);
$transid = $f[0];
$expdate = $f[1];
# $cardnum = "$f[2]***$f[3]"; # old way.
$cardnum = "$f[3]"; # new way. stupid authorize.net.
} else {
# otherwise the refund info's probably been purged, so we'll get the transid
# and card fingerprint from the user
$res = $dbh->selectrow_array("SELECT response FROM authnetlog WHERE payid=? AND cmd='authcap' AND result='pass' ORDER BY datesent DESC LIMIT 1", undef, $cartobj->{'payid'});
return 0 unless $res;
$transid = (split(/,/, $res))[6];
}
}
my $ua = new LWP::UserAgent;
$ua->agent("LJ-AuthNet/1.0");
my $vars = {
'x_Version' => '3.1',
'x_Delim_Data' => 'True',
'x_Login' => $LJ::AUTHNET_USER,
'x_Password' => $LJ::AUTHNET_PASS,
'x_Type' => $type,
'x_Merchant_Email' => $LJ::AUTHNET_MERCHANT,
'x_Trans_ID' => $transid,
'x_Card_Num' => $cardnum,
#'x_Exp_Date' => $expdate, # no longer required
};
if ($type eq "CREDIT") {
$vars->{'x_Amount'} = $refund_amt;
}
my $req = new HTTP::Request POST => 'https://secure.authorize.net/gateway/transact.dll';
$req->content_type('application/x-www-form-urlencoded');
$req->content(join("&", map { LJ::eurl($_) . "=" . LJ::eurl($vars->{$_}) } keys %$vars));
# Pass request to the user agent and get a response back
my $res = $ua->request($req);
my ($ct, $err);
if ($res->is_success) {
$ct = $res->content;
} else {
return 0;
}
my @fields = split(/,/, $ct);
my $pass = $fields[0] == 1;
$dbh->do("INSERT INTO authnetlog (cmd, payid, datesent, amt, result, response) ".
"VALUES (?,?,NOW(),?,?,?)", undef, lc($type),
$cartobj->{'payid'}, $refund_amt, $pass ? "pass" : "fail", $ct);
die $dbh->errstr if $dbh->err;
return $pass;
};
if ($cartobj->{'method'} eq "cc" && ! $POST{'no_refund'}) {
return "Error: merchant gateway is currently down, please try again later."
if $LJ::AUTHNET_DOWN > 0.5;
unless (($total_refund && $refund->("VOID")) ||
$refund->("CREDIT")) {
return "Error: unable to refund. Check error messages. Not revoking items.";
}
}
my $piids = join(",", map { $_->{'piid'} } @revoke);
LJ::Pay::payvar_add($cartobj->{'payid'}, "revoke",
LJ::mysql_time(time()) . ": (piid $piids) $POST{'refreason'}");
# group revoked items by userid for locking
my %revoke_uid = ();
foreach my $it (@revoke) {
my $uid = $it->{'rcptid'} || 'anon';
push @{$revoke_uid{$uid}}, $it;
}
# remove items from account, one userid at a time
foreach my $uid (keys %revoke_uid) {
unless ($uid eq 'anon') {
LJ::Pay::get_lock($uid)
or return "Could not obtain lock on account, please try again later";
}
LJ::Pay::revoke_payitems(@{$revoke_uid{$uid}});
unless ($uid eq 'anon') {
LJ::Pay::release_lock($uid);
}
}
# if any coupons were revoked, display the payids they were used on
# so the admin can make a human judgement if anything else needs to
# be revoked
my @cp_rev = map { $_->{'tokenid'} } grep { $_->{'item'} eq 'coupon' && $_->{'amt'} > 0 } @revoke;
my $bind = join(",", map { "?" } @cp_rev);
my $cp_ids = $dbh->selectcol_arrayref("SELECT payid FROM coupon WHERE cpid IN ($bind)",
undef, @cp_rev) || [];
my $cp_ret;
if (@$cp_ids) {
$cp_ret = "<p>Coupons have revoked. Below are the payment IDs on which they were used. " .
"You may wish to review these payments to determine if further action should " .
"be taken.</p>";
$cp_ret .= "<ul>" .
join("", map { "<li><a href='paiddetails.bml?payid=$_'>Payment #$_</a></li>" } @$cp_ids) .
"</ul>";
}
# if this was a fraudulent payment, mark it as refunded.
my $fraud = $dbh->selectrow_array("SELECT pval FROM fraudstatus ".
"WHERE payid=? AND pkey='fraud_status'",
undef, $cartobj->{'payid'});
LJ::Pay::payvar_set($cartobj->{'payid'}, "fraud_status", "refunded") if $fraud eq 'suspect';
return "Success. Press back and reload for details.$cp_ret";
}
_code?>

View File

@@ -0,0 +1,82 @@
<html>
<head>
<style>
strong { font-weight: bold; color: red; font-size: 14pt; }
</style>
</head>
<body>
<?_code
{
use strict;
use vars qw(%POST);
my $remote = LJ::get_remote();
return "<?needlogin?>" unless $remote;
return "You don't have access to finish payments"
unless LJ::remote_has_priv($remote, "moneyenter") || LJ::remote_has_priv($remote, "shipping");
my $ret;
unless (LJ::did_post() && $POST{'ids'}) {
$ret .= "<form method='post'>Enter order numbers that have been shipped:<br />";
$ret .= "<textarea name='ids' rows='40' cols='20' /></textarea>\n";
$ret .= "<br /><input type='submit' value='Shipped' /></form>";
return $ret;
}
my $dbh = LJ::get_db_writer();
my $ids = $POST{'ids'};
$ids =~ s/\r//g;
my @ids = split(/\n/, $ids);
foreach my $id (@ids) {
next unless $id =~ /\S/;
$id =~ s/\s+//g;
unless ($id =~ /^(\d+)-(\d+)$/) {
$ret .="<strong>Error!</strong> -- invalid order number: $id<br />";
next;
}
my ($payid, $anum) = ($1, $2);
my $pay = $dbh->selectrow_hashref("SELECT * FROM payments WHERE payid=? AND anum=?",
undef, $payid, $anum);
unless ($pay) {
$ret .="<strong>Error!</strong> -- invalid order number: $id<br />";
next;
}
my ($status, $date) = $dbh->selectrow_array("SELECT status, dateshipped FROM shipping ".
"WHERE payid=?", undef, $payid);
unless ($status eq "needs") {
if ($status eq "shipped") {
$ret .="<strong>Error!</strong> -- Order already shipped on $date<br />";
} else {
$ret .="<strong>Error!</strong> -- Order is valid, but has no physical items<br />";
}
next;
}
my $rv = $dbh->do("UPDATE shipping SET status='shipped', dateshipped=NOW() WHERE payid=?", undef,
$payid);
if ($rv > 0) {
$ret .= "Order $payid-$anum marked as shipped.<br />\n";
# decrement inventory
my $sth = $dbh->prepare("SELECT item, subitem FROM payitems ".
"WHERE payid=? AND item IN ('clothes')");
$sth->execute($payid);
while (my ($item, $subitem) = $sth->fetchrow_array) {
$dbh->do("UPDATE inventory SET qty=qty-1 WHERE item=? AND subitem=?",
undef, $item, $subitem);
}
} else {
$ret .="<strong>Error!</strong> -- some db error updating $payid-$anum<br />\n";
}
}
return $ret;
}
_code?>
</body>
</html>

View File

@@ -0,0 +1,193 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head><title></title>
<style>
@media print {
@page {
size: 8.5in 11in; /* width height */
// margin: 0.5in;
}
body { margin: 0; }
a { color: black; }
div.page, div.newpage {
width: 7.0in;
border: 0; margin: 0; padding: 0;
position: relative;
}
div.newpage {
page-break-before: always;
}
div.littlelabel {
// border: 1px solid black;
background: transparent;
font-size: 25pt; font-weight: bold;
position: absolute;
left: 1.6in;
width: 3.75in;
top: 5.7in;
height: 0.75in;
text-align: center;
}
div.littlelabel p {
margin-top: 0.2in;
}
div.returnaddr {
// border: 1px solid black;
background: transparent;
position: absolute;
left: 1.6in;
top: 6.8in;
width: 3.75in;
height: 1.5in;
font-family: sans-serif;
font-size: 11pt;
}
div.toaddr {
// border: 1px solid black;
background: transparent;
position: absolute;
left: 2.25in;
top: 7.85in;
width: 3.1in;
height: 1.5in;
font-size: 15pt;
font-weight: bold;
font-family: sans-serif;
}
div.lilorder {
font-size: 8pt;
font-family: sans-serif;
position: absolute;
left: 1.6in;
top: 9.3in;
}
div.shdate { display: none; }
}
@media screen {
a { color: black; }
h1 { border: 2px solid black; }
div.shdate { color: blue; font-size: 10pt; font-family: sans-serif; margin-top: 0; }
div.returnaddr, div.littlelabel,
div.lilorder { display: none; }
div.toaddr { margin-left: 2in; font-size: 15pt; font-weight: bold; }
}
</style>
</head>
<body>
<?_code
{
use strict;
use vars qw(%POST);
my $remote = LJ::get_remote();
return "You must first <a href=\"/login.bml?ret=1\">log in</a>."
unless $remote;
return "You don't have access to see this."
unless LJ::remote_has_priv($remote, "moneyview") || LJ::remote_has_priv($remote, "shipping");
my $ret;
unless (LJ::did_post()) {
$ret .= "<form method='post'>";
$ret .= "All labels past date: <input name='date' value='0000-00-00 00:00:00' size='20' /> <input type='submit' value='Generate' />";
$ret .= "<p>(be sure to set printer margins to 0.5\" at top and left, with no header or footer.)</p>";
$ret .= "</form>";
return $ret;
}
my $dbh = LJ::get_db_writer();
my $sth;
my %country;
LJ::load_codes($dbh, { "country" => \%country });
$sth = $dbh->prepare("SELECT payid, dateready FROM shipping ".
"WHERE dateready > ? AND status='needs' ".
"ORDER BY dateready");
$sth->execute($POST{'date'});
my @ship;
push @ship, $_ while $_ = $sth->fetchrow_hashref;
my $ct;
foreach my $sh (@ship) {
$ct++;
my $cartobj = $dbh->selectrow_hashref("SELECT * FROM payments WHERE payid=?",
undef, $sh->{'payid'});
next unless $cartobj;
# load all the cart
my $cart = "$cartobj->{'payid'}-$cartobj->{'anum'}";
$cartobj = LJ::Pay::load_cart($cart);
next unless $cartobj;
if ($ct == 1) {
$ret .= "<div class='page'>";
} else {
$ret .= "<div class='newpage'>";
}
$ret .= "<h1>Order \#$cart</h1>";
$ret .= "<div class='shdate'>$sh->{'dateready'}</div>";
$ret .= "<p style='margin-bottom: 20px'>Enclosed are the items you ordered. If you have any questions, email accounts\@livejournal.com and reference the order number above.</p>";
LJ::Pay::render_cart($cartobj, \$ret, { shipping_labels => 1 });
$ret .= "<div class='littlelabel'><p>$cart</p></div>";
$ret .= "<div class='returnaddr'>" . LJ::Pay::postal_address_html() . "</div>\n";
$ret .= "<div class='toaddr'>";
my %payvar;
my $sth = $dbh->prepare("SELECT pkey, pval FROM payvars WHERE payid=? AND pkey LIKE 'ship%'");
$sth->execute($cartobj->{'payid'});
while (my ($k, $v)= $sth->fetchrow_array) { $payvar{$k} = $v; }
my $ctry = uc($payvar{'ship_country'});
# Canadian shipping labels need to be printed in all caps, ugh
if ($ctry eq 'CA') {
$payvar{$_} = uc($payvar{$_})
foreach grep { $_ =~ /^ship_/ } keys %payvar;
$country{'CA'} = uc($country{'CA'});
}
$ret .= "$payvar{'ship_name'}<br />";
$ret .= "$payvar{'ship_addr1'}<br />";
$ret .= "$payvar{'ship_addr2'}<br />" if $payvar{'ship_addr2'};
$ret .= "$payvar{'ship_city'}, $payvar{'ship_state'} $payvar{'ship_zip'}<br />";
if ($ctry ne "US") {
$ret .= $country{$ctry};
}
$ret .= "</div>";
$ret .= "<div class='lilorder'>[$cart]</div>";
$ret .= "</div>"; # end page
}
$ret .= "no orders found past $POST{'date'}" unless $ct;
return $ret;
}
_code?>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<?_code
my $dbh = LJ::get_db_writer();
my ($ret, $sth);
my $remote = LJ::get_remote();
unless (LJ::remote_has_priv($remote, "moneysearch") ||
LJ::remote_has_priv($remote, "moneyview"))
{
if ($remote) {
return "You don't have access to see this.";
} else {
return "You must first <A HREF=\"/login.bml?ret=1\">log in</A>.";
}
}
$sth = $dbh->prepare("SELECT p.payid, a.acid, ac.auth FROM acctcode ac, acctpay a, payments p WHERE p.userid=0 AND a.payid=p.payid AND ac.acid=a.acid");
$sth->execute;
while (my ($payid, $acid, $auth) = $sth->fetchrow_array)
{
my $code = LJ::acct_code_encode($acid, $auth);
$ret .= "<p><a href=\"paiddetails.bml?payid=$payid&userid=0\">#$payid</a> - $code\n";
}
return $ret;
_code?>

View File

@@ -0,0 +1,8 @@
<?_code
{
# tool used to live here, but was later change to offer
# non-admin capabilities as well for permanent account
# sales - whitaker
BML::redirect("$LJ::SITEROOT/tools/xfer_remaining.bml")
}
_code?>

View File

@@ -0,0 +1,172 @@
<?_code
use strict;
use vars qw(%FORM);
my $dbr = LJ::get_db_reader();
my ($ev, $sth, $ret);
my $mode = "intro";
sub p { $ret .= join('', @_); }
my $remote = LJ::get_remote();
return "<b>Error:</b> You don't have finduser(codetrace) priv."
unless LJ::check_priv($remote, "finduser", "codetrace");
if ($FORM{'user'} ne "") { $mode = "user"; $FORM{'code'} = ""; }
elsif ($FORM{'code'}) { $mode = "code"; }
p("<h1>code tracer</h1>\n");
p("<form method='get'>");
$ev = LJ::ehtml($FORM{'user'});
p("User: <input name='user' size='15' value='$ev'> or ");
$ev = LJ::ehtml($FORM{'code'});
p("Code: <input name='code' size='15' value='$ev'> <input type='submit' value=\"Trace\">");
p("</form>");
return $ret if $mode eq "intro";
my $do_how = sub {
my $self = shift;
my $u = shift;
my $whyhere = $dbr->selectrow_hashref(qq{
SELECT acid, userid FROM acctcode WHERE rcptid=$u->{'userid'} LIMIT 1
});
$whyhere->{'user'} = LJ::get_username($whyhere->{'userid'})
if $whyhere && $whyhere->{'userid'};
p("How $u->{'user'} joined: ");
unless ($whyhere) {
p("<i>No invite code</i>");
} else {
my $acid = LJ::acid_encode($whyhere->{'acid'});
p("<a href='codetrace.bml?code=$acid'>$acid</a>");
my $reason = $dbr->selectrow_array(qq{
SELECT reason FROM acctinvite WHERE acid=$whyhere->{'acid'}
});
p(" ($reason)") if defined $reason;
if (exists $whyhere->{'user'}) {
p(" from <a href='codetrace.bml?user=$whyhere->{'user'}'><b>$whyhere->{'user'}</b></a>.");
p("<br />");
$self->($self, $whyhere);
return;
}
}
p("<br />");
};
if ($mode eq "user")
{
my $user = LJ::canonical_username($FORM{'user'});
my $u = LJ::load_user($user) if $user;
return "Unknown user" unless $u;
$do_how->($do_how, $u);
my %invite;
$sth = $dbr->prepare(qq{
SELECT acid, reason, dateadd FROM acctinvite WHERE userid=$u->{'userid'}
LIMIT 5000
});
$sth->execute;
$invite{$_->{'acid'}} = $_ while $_ = $sth->fetchrow_hashref;
my $total_children = 0;
# limit recursion
my %did_userid;
my $total_calls = 0;
my $rec_user = sub {
my $self = shift;
my $userid = shift;
my $unused = shift;
# limit recursion (we were seeing runaways)
return if ++$total_calls > 50;
return if $did_userid{$userid}++;
my $sth = $dbr->prepare(qq{
SELECT a.acid, a.rcptid, u.user FROM acctcode a LEFT JOIN useridmap u ON a.rcptid=u.userid
WHERE a.userid=$userid
LIMIT 5000
});
$sth->execute;
my $open;
while (my ($acid, $rcptid, $user) = $sth->fetchrow_array)
{
next unless $unused || $user;
$total_children++ if defined $user;
unless ($open++) { p("<ul>"); }
my $acide = LJ::acid_encode($acid);
p("<li>");
p("<a href='codetrace.bml?user=$user'><b>$user</b></a> ") if $user;
p("(<a href='codetrace.bml?code=$acide'>$acide</a>)");
if ($invite{$acid}) {
p(" ($invite{$acid}->{'reason'}; $invite{$acid}->{'dateadd'})");
}
$self->($self, $rcptid, 0) if $rcptid;
p("</li>");
}
p("</ul>") if $open;
};
p("<p>Codes made/used by $user:");
$rec_user->($rec_user, $u->{'userid'}, 1);
p("<p><b>Total children:</b> $total_children");
return $ret;
}
if ($mode eq "code")
{
my $code = $FORM{'code'};
my $acid;
if ($code =~ /^\#(\d+)$/) {
$acid = $1;
$code = LJ::acid_encode($acid);
} else {
return "Bogus code." if length($code) != 7 && length($code) != 12;
$code =~ s/^.....(.......)$/$1/;
$acid = LJ::acid_decode($code);
}
p("Code: $code = $acid<br />");
my $ac = $dbr->selectrow_hashref("SELECT userid, rcptid FROM acctcode WHERE acid=$acid");
unless ($ac) {
p("Code doesn't exist");
return $ret;
}
my $ai = $dbr->selectrow_hashref("SELECT reason, dateadd FROM acctinvite WHERE acid=$acid");
$ac->{'user'} = LJ::get_username($ac->{'userid'})
if $ac->{'userid'};
$ac->{'ruser'} = LJ::get_username($ac->{'rcptid'})
if $ac->{'rcptid'};
p("Creator of code: <a href='codetrace.bml?user=$ac->{'user'}'>$ac->{'user'}</a> ($ai->{'reason'}, $ai->{'dateadd'})<br />")
if $ac->{'user'};
unless ($ac->{'userid'}) {
my $ap = $dbr->selectrow_hashref(qq{
SELECT p.userid, p.payid FROM payments p, acctpay ap
WHERE ap.payid=p.payid AND ap.acid=$acid
});
$ap ||= $dbr->selectrow_hashref(qq{
SELECT p.userid, p.payid FROM payments p, payitems pi, acctpayitem api
WHERE api.piid=pi.piid AND api.acid=$acid AND pi.payid=p.payid
});
if ($ap) {
p("Payment which generated code: <a href='/admin/accounts/paiddetails.bml?payid=$ap->{'payid'}&amp;userid=$ap->{'userid'}'>$ap->{'payid'}</a><br />");
}
}
p("Code recipient: <a href='codetrace.bml?user=$ac->{'ruser'}'>$ac->{'ruser'}</a><br />");
return $ret;
}
$ret;
_code?>

View File

@@ -0,0 +1,139 @@
<?_code
{
use strict;
use vars qw($body $title %GET);
$title = "Feedback Survey Results";
my $remote = LJ::get_remote();
unless (LJ::check_priv($remote, "siteadmin", "feedback") || LJ::check_priv($remote, "siteadmin", "*")) {
$title = "Restricted";
$body = "<?p This is the feedback review tool for LiveJournal administrators. p?>";
return BML::redirect("/feedback/");
}
$body .= "<?h1 Retrieve Surveys h1?>\n";
$body .= '<form method="GET" action="./"><input type="hidden" name="mode" value="list" />';
$body .= "<table cellpadding='4' cellspacing='2' class='search'>";
$body .= "<tr><th>URL:</th><td>" .
LJ::html_text({ 'name' => 'url',
'value' => $GET{'url'},
'size' => 30, 'maxlength' => 50 }) . "</td></tr>";
$body .= "<tr><th>Username:</th><td>" .
LJ::html_text({ 'name' => 'username',
'value' => $GET{'username'},
'size' => 15, 'maxlength' => 15 }) . "</td></tr>";
$body .= "<tr><th>State:</th><td>" .
LJ::html_select({'name' => 'state', 'selected' => $GET{'state'}},
'', "", 'N', "New", 'D', "Deleted", 'Z', "Zilla item") . "</td></tr>";
$body .= "<tr><td></td><td>" . LJ::html_submit("", "Search") . "</td></tr>";
$body .= "</table></form>";
return unless $GET{'mode'};
if ($GET{'mode'} eq "list") {
my @where;
my $dbr = LJ::get_db_reader();
$body .= "<?hr?><?h1 Results h1?>";
if ($GET{'viewall'} ne "") {
# Do nothing extra
}
if ($GET{'username'} ne "") {
my $userid = LJ::get_userid($GET{'username'});
unless ($userid)
{
$body .= "<?h2 Error: h2?> <?p No results for '$GET{'username'}'. p?>";
return;
}
push @where, "userid=$userid";
}
if ($GET{'url'} ne "") {
my $qvalue = $dbr->quote($GET{'url'});
push @where, "url=$qvalue";
}
if ($GET{'state'} ne "") {
my $qvalue = $dbr->quote($GET{'state'});
push @where, " state=$qvalue";
}
my $where; my $i;
if (@where > 0) {
$where = "WHERE ";
foreach (@where) {
$i++;
$where .= $i == 1 ? $_ : " && " . $_;
}
}
my $sth = $dbr->prepare("SELECT * FROM fotobilder_feedback $where");
$sth->execute;
my $show_total = 50;
my $row_ct = 0;
my $row_show = 0;
my $row_skip = 0;
my $row_html;
my @rows; while (my $row = $sth->fetchrow_hashref) { push @rows, $row; }
foreach my $row ( reverse @rows ) {
next if LJ::trim($row->{'body'}) eq "";
$row_ct++;
next if $GET{'skip'} && ++$row_skip <= $GET{'skip'};
if ($row_show < $show_total) {
$row_show++;
my $username = LJ::get_username($row->{'userid'});
$row_html .= "<tr><td style='white-space: nowrap'>" . LJ::ljuser($username) . "<br /><br />";
if ($row->{'datetime'} ne "0000-00-00 00:00:00") {
$row_html .= "<strong>Filed at:</strong> $row->{'datetime'}<br />";
}
$row_html .= "<ul><li><a href='./?mode=list&amp;url=" . LJ::ehtml($row->{'url'}) . "'>$row->{'url'}</a></li>";
$row_html .= "<li><a href='./?mode=list&amp;username=$username'>Other feedback</a></li>";
$row_html .= "<li><a href='./?mode=list&amp;state=$row->{'state'}'>State: $row->{'state'}</a></li></ul></td>";
$row_html .= "<td style='border: 1px solid #000' valign='top'>";
my $abstract = LJ::ehtml($row->{'body'});
$abstract =~ s/\n/<br \/>/g;
$row_html .= "$abstract</td></tr>";
}
}
if ($row_ct eq 0) { $body .= "<?p No Results Returned p?>"; return; }
$body .= "<table cellpadding='4' cellspacing='1' border='0' class='feedback'>";
$body .= "<tr><th style='width: 175px;'>Links</th><th>Feedback</th></tr>";
$body .= $row_html;
my $slinks;
if ($GET{'skip'}) {
$slinks .= "<a href=\"" . BML::self_link({ 'skip' => $GET{'skip'} - $show_total}) . "\">&lt;&lt; Back</a> ";
}
if ($row_show != $row_ct) {
my $from = $GET{'skip'}+1;
my $to = $row_show + $GET{'skip'};
$slinks .= "(Records $from-$to of $row_ct) ";
}
if ($GET{'skip'} + $row_show < $row_ct) {
$slinks .= "<a href=\"" . BML::self_link({ 'skip' => $GET{'skip'} + $show_total}) . "\">Forward &gt;&gt;</a> ";
}
$body .= "</table>";
if ($slinks ne "") { $body .= "<?h1 Tally h1?> <?p $slinks p?>"; }
} else {
$body .= "<?p Please select a search criteria p?>";
}
return;
}
_code?>
<?page
title=><?_code return $title; _code?>
body=> <?_code return $body; _code?>
head<=
<style type='text/css'>
.feedback {
width: 100%;
}
.feedback th, .search th {
text-align: left;
}
</style>
<=head
page?>

View File

@@ -0,0 +1,230 @@
<?page
title=>Query Sent Mail
body<=
<?_code
use strict;
use vars qw(%GET);
my ($ret, $sth, $where, $body);
my $status = {
'S' => "Sent (does not guarantee that the message didn't bounce)",
'F' => "Failed to send", };
my $remote = LJ::get_remote();
my %canview;
$canview{'abuse'} = 1 if (LJ::check_priv($remote, "supportread", "abuse"));
$canview{'support'} = 1 if (LJ::check_priv($remote, "supportread", "support"));
# Grouping this check for now, but leaving it so
# it could be split up in the future should a need
# arise
if (LJ::check_priv($remote, "supportread", "accounts")) {
$canview{'accounts'} = 1;
$canview{'coppa'} = 1;
}
unless ($canview{'abuse'} || $canview{'support'} || $canview{'accounts'}) {
return "<?p This tool is for members of our abuse and support teams.<br />
If you need to file an abuse request, please do so at:
<a href='/abuse/report.bml'>$LJ::SITEROOT/abuse/report.bml</a> <br />
If you need to file a support request, please do so at:
<a href='/support/submit.bml'>$LJ::SITEROOT/support/submit.bml</a> p?>";
}
if ($GET{'mode'} eq "view") {
my $dbr = LJ::get_db_reader();
my $qmailid = $dbr->quote($GET{'mailid'});
$sth = $dbr->prepare("SELECT mailid, userid, spid, status, timesent, mailto, " .
"subject, message, type FROM abuse_mail " .
"WHERE mailid=$qmailid");
$sth->execute;
my $mail = $sth->fetchrow_hashref;
if ($canview{$mail->{'type'}}) {
$ret .= "<?h1 Viewing Message #$mail->{'mailid'} h1?>";
$ret .= "<table style='border-spacing: 5px'>";
$ret .= "<tr><th style='text-align: left; white-space: nowrap'>Mail ID:</th>";
$ret .= "<td>$mail->{'mailid'}</td></tr>";
$ret .= "<tr><th style='text-align: left'>Status:</th>";
$ret .= "<td>$status->{$mail->{'status'}}</td></tr>";
$ret .= "<tr><th style='text-align: left; white-space: nowrap'>Request #:</th>";
if ($mail->{'spid'} != 0) {
$ret .= "<td><a href='/support/see_request.bml?id=$mail->{'spid'}'>";
$ret .= "$mail->{'spid'}</a></td>";
} else {
$ret .= "<td>N/A</td>";
}
$ret .= "</tr>";
$ret .= "<tr><th style='text-align: left; white-space: nowrap'>Sent By:</th>";
$ret .= "<td>" . LJ::ljuser(LJ::get_username($mail->{'userid'})) . "</td></tr>";
$ret .= "<tr><th style='text-align: left'>From:</th>";
$ret .= "<td>$mail->{'type'}\@$LJ::DOMAIN</td></tr>";
$ret .= "<tr><th style='text-align: left'>Recipient:</th>";
$ret .= "<td>$mail->{'mailto'}</td></tr>";
$ret .= "<tr><th style='text-align: left'>Sent:</th>";
$ret .= "<td>$mail->{'timesent'}</td></tr>";
$ret .= "<tr><th style='text-align: left'>Subject:</th>";
$ret .= "<td>$mail->{'subject'}</td></tr>";
$ret .= "<tr><th style='text-align: left; vertical-align: top'>Message:</th>";
my $message = $mail->{message};
$message = LJ::auto_linkify($message);
$message =~ s/\r?\n/<br \/>\n/g;
$ret .= "<td>$message</td></tr>";
$ret .= "</table>";
$ret .= "<?hr?><a href='/admin/sendmail/query.bml' onclick='history.back();return false;'>";
$ret .= "&lt;&lt; View Results</a>";
} else {
$ret .= LJ::bad_input('You are not authorized to view this message');
}
} else {
$ret .= "<?h1 Search Sent Emails h1?>\n";
$ret .= '<form method="GET" action="query.bml">';
$ret .= '<input type="hidden" name="mode" value="list" />';
$ret .= "<table cellpadding='4' cellspacing='2'><tr valign='top'>";
$ret .= "<tr valign='top'><th align='right'>Restrict:</th><td>";
my @type = ("", "All");
push @type, ('abuse' => "abuse\@$LJ::DOMAIN") if $canview{'abuse'};
push @type, ('accounts' => "accounts\@$LJ::DOMAIN") if $canview{'accounts'};
push @type, ('coppa' => "coppa\@$LJ::DOMAIN") if $canview{'coppa'};
push @type, ('support' => "support\@$LJ::DOMAIN") if $canview{'support'};
$ret .= LJ::html_select({ 'name' => 'restrict', 'selected' => $GET{'restrict'} }, @type);
$ret .= "</td></tr><tr valign='top'><th align='right'>Method:</th><td>";
$ret .= LJ::html_select({'name' => 'method', 'selected' => $GET{'method'}},
'sender' => "Username of Sender",
'spid' => "Tied to Request #",
'mailto' => "Sent to address or user",
);
$ret .= "</td></tr><tr valign='top'><th align='right'>Value:</th><td>";
$ret .= LJ::html_text({ 'name' => 'value',
'value' => $GET{'value'},
'size' => 30 });
$ret .= "</td></tr>";
$ret .= "<tr><td align='right'>&nbsp;</td><td><input type='submit' value='Search'></td>";
$ret .= "</tr></table></form>";
return $ret unless $GET{'mode'};
if ($GET{'mode'} eq "list") {
my $dbr = LJ::get_db_reader();
$ret .= "<?hr?><?h1 Results h1?>";
# Default condition of nothing versus everything
my $where = "WHERE 0";
if ($GET{'method'} eq "sender") {
my $userid = LJ::get_userid($GET{'value'});
unless ($userid) {
$ret .= "<?h2 Error: h2?> <?p The username '$GET{'value'}' is not currently in use. p?>";
return $ret;
}
$where = "WHERE userid=$userid";
} elsif ($GET{'method'} eq "spid") {
$where = "WHERE spid=" . $dbr->quote($GET{'value'});
} elsif ($GET{'method'} eq "mailto") {
my $email;
my $u = LJ::load_user($GET{'value'});
if ($u) {
$email = $u->{'email'};
} else { # Assume we got an email address
$email = $GET{'value'};
my @email_errors;
LJ::check_email($email, \@email_errors);
if (@email_errors) {
$ret .= "<?h2 Error: h2?> <?p " . join(', ', @email_errors) . " p?>";
return $ret;
}
}
$where = "WHERE mailto=" . $dbr->quote($email);
}
# See if they are limiting the search and
# make sure they are able to view that type
if ($GET{'restrict'} ne '') {
return LJ::bad_input('Not authorized to view that type')
unless $canview{$GET{'restrict'}};
my $r = $dbr->quote($GET{'restrict'});
$where .= " AND type=$r";
} else { #Limit them to the types they can see
$where .= " AND type IN(" . join(',', map { $dbr->quote($_) } keys %canview) . ')';
}
$sth = $dbr->prepare("SELECT mailid, userid, spid, status, " .
"timesent, mailto, subject, type " .
"FROM abuse_mail $where");
$sth->execute;
my $show_total = 50;
my $row_ct = 0;
my $row_show = 0;
my $row_skip = 0;
my $row_html;
while (my $row = $sth->fetchrow_hashref) {
$row_ct++;
next if $GET{'skip'} && ++$row_skip <= $GET{'skip'};
if ($row_show < $show_total) {
$row_show++;
$row_html .= "<tr><td><a href='./query.bml?mode=view&mailid=$row->{'mailid'}'>(link)</a></td>";
my $username = LJ::get_username($row->{'userid'});
$row_html .= "<td>" . LJ::ljuser($username) . "</td>";
if ($row->{'spid'} != 0) {
$row_html .= "<td><a href='/support/see_request.bml?id=$row->{'spid'}'>$row->{'spid'}</a></td>";
} else {
$row_html .= "<td>N/A</td>";
}
$row_html .= "<td>$row->{'status'}</td>";
$row_html .= "<td>$row->{'type'}</td>";
$row_html .= "<td>$row->{'timesent'}</td><td>$row->{'mailto'}</td>";
$row_html .= "<td>$row->{'subject'}</td></tr>";
}
}
if ($row_ct eq 0) { $ret .= "<?p No Results Returned p?>"; return $ret; }
$ret .= "<?p <table cellpadding='4' cellspacing='1' border='1'>";
$ret .= "<tr><th>Details</th><th>Sent By</th><th>Request #</th>";
$ret .= "<th>Status</th><th>From</th><th>Sent</th><th>Recipient</th><th>Subject</th></tr>";
$ret .= $row_html;
my $slinks;
if ($GET{'skip'}) {
$slinks .= "<a href=\"" . BML::self_link({ 'skip' => $GET{'skip'} - $show_total}) . "\">&lt;&lt; Back</a> ";
}
if ($row_show != $row_ct) {
my $from = $GET{'skip'}+1;
my $to = $row_show+$GET{'skip'};
$slinks .= "(Records $from-$to of $row_ct) ";
}
if ($GET{'skip'} + $row_show < $row_ct) {
$slinks .= "<a href=\"" . BML::self_link({ 'skip' => $GET{'skip'} + $show_total}) . "\">Forward &gt;&gt;</a> ";
}
$ret .= "</table> p?>";
if ($slinks ne "") { $ret .= "<?h1 Tally h1?> <?p $slinks p?>"; }
} else {
$ret .= "<?p Please select a search criteria p?>";
}
}
return $ret;
_code?>
<=body
page?>

View File

@@ -0,0 +1,381 @@
<?page
head<=
<script language='javascript'>
function form_switch()
{
var form = document.getElementById('preview');
form.action='send.bml?action=edit';
form.submit();
}
function fill_subject()
{
var request = document.getElementById('request');
var subject = document.getElementById('subject');
var id = request.value;
subject.value = 'Your <?_code return LJ::ejs($LJ::SITENAMESHORT); _code?> Account [##-'+id+']';
}
</script>
<=head
body<=
<?_code
{
use strict;
my $body = "";
my $remote = LJ::get_remote();
my %cansend;
$cansend{'abuse'} = 1 if LJ::check_priv($remote, "supportread", "abuse");
$cansend{'support'} = 1 if LJ::check_priv($remote, "supportread", "support");
# Grouping this check for now, but leaving it so
# it could be split up in the future should a need
# arise
if (LJ::check_priv($remote, "supportread", "accounts")) {
$cansend{'accounts'} = 1;
$cansend{'coppa'} = 1;
}
unless (%cansend) {
return "<?p This tool is for members of our abuse and support teams.<br />
If you need to file an abuse request, please do so at:
<a href='/abuse/report.bml'>$LJ::SITEROOT/abuse/report.bml</a> <br />
If you need to file a support request, please do so at:
<a href='/support/submit.bml'>$LJ::SITEROOT/support/submit.bml</a> p?>";
}
my $compose = sub {
my $edit_mode = shift;
$body .= "<?h1 Compose Message h1?>\n";
$body .= "<form action='send.bml?action=preview' method='post'>\n";
$body .= "<table>\n";
$body .= "<tr><td><label for='from'>From:</label></td><td>";
my @from = ("", "-- Select One --");
push @from, ('abuse' => "abuse\@$LJ::DOMAIN") if $cansend{'abuse'};
push @from, ('accounts' => "accounts\@$LJ::DOMAIN") if $cansend{'accounts'};
push @from, ('coppa' => "coppa\@$LJ::DOMAIN") if $cansend{'coppa'};
push @from, ('support' => "support\@$LJ::DOMAIN") if $cansend{'support'};
my $selfrom = $edit_mode == 1 ? $POST{'from'} : $GET{'from'};
$selfrom = "abuse" if !$selfrom && $cansend{'abuse'};
$selfrom = "support" if !$selfrom && $cansend{'support'};
$selfrom = "accounts" if !$selfrom && $cansend{'accounts'};
$body .= LJ::html_select({ 'name' => 'from', 'selected' => $selfrom }, @from);
$body .= "<br /><br /></td></tr>";
$body .= "<tr><td>Mail to:</td><td>";
$body .= "<table><tr><td>";
# Since we expose the address we'd be sending to
# don't let them mail by user or community maints
# if they couldn't go look it up themselves
if (LJ::check_priv($remote, 'finduser')) {
$body .= "<label for='user'>User:</label></td><td>";
my $user = $edit_mode == 1 ? $POST{'user'} : $GET{'user'};
$body .= LJ::html_text({ 'name' => 'user', 'type' => 'text',
'raw' => 'style="background: url('.$LJ::IMGPREFIX.'/userinfo.gif) no-repeat; background-position: 0px 1px; padding-left: 18px;" size="37"',
'value' => $user});
$body .= "<font color='gray'> (OR)</font>";
$body .= "</td></tr>";
$body .= "<tr><td><label for='user'>Maints:</label></td><td>";
my $maints = $edit_mode == 1 ? $POST{'maints'} : $GET{'maints'};
$body .= LJ::html_text({ 'name' => 'maints', 'type' => 'text',
'raw' => 'style="background: url('.$LJ::IMGPREFIX.'/community.gif) no-repeat; background-position: 0px 1px; padding-left: 18px;" size="37"',
'value' => $maints});
$body .= "<font color='gray'> (OR)</font>";
$body .= "</td></tr><tr><td>";
}
$body .= "<label for='email'>Email:</label></td><td>";
my $email = $edit_mode == 1 ? $POST{'email'} : $GET{'email'};
$body .= LJ::html_text({ 'name' => 'email', 'type' => 'text',
'raw' => 'size="40"', 'value' => $email});
$body .= "</td></tr></table>";
$body .= "<br /></td></tr>";
$body .= "<tr><td><label for='bcc'>BCC:</label></td><td>";
my $bcc = $edit_mode == 1 ? $POST{'bcc'} : $GET{'bcc'};
if ($edit_mode == 0 && ! $bcc) {
$bcc = $remote->{'email'};
}
$body .= LJ::html_text({'name' => 'bcc', 'type' => 'text',
'raw' => "size='45' maxlength='100'",
'value' => $bcc});
$body .= " <font color='gray'>Limit One</font></td></tr>";
$body .= "<tr><td><label for='request'>Request #:</label></td><td>";
my $request = $edit_mode == 1 ? $POST{'request'} : $GET{'request'};
$body .= LJ::html_text({'name' => 'request', 'type' => 'text',
'raw' => "size='45' maxlength='100'",
'value' => $request,
'id' => 'request'});
$body .= " <input type='button' value='Fill Subject' onclick='fill_subject()' />";
$body .= "</td></tr>";
$body .= "<tr><td><label for='subject'>Subject:</label></td><td>";
my $subject = $edit_mode == 1 ? $POST{'subject'} : $GET{'subject'};
$body .= LJ::html_text({'name' => 'subject', 'type' => 'text',
'raw' => "size='45' maxlength='100'",
'value' => $subject,
'id' => 'subject'});
$body .= "</td></tr>";
$body .= "<tr><td valign='top'><label for='body'>Message:</label></td><td>";
my $message = $edit_mode == 1 ? $POST{'message'} : $GET{'message'};
$body .= LJ::html_textarea({'name' => 'message',
'raw' => "rows='20' cols='80' wrap='soft'",
'value' => $message});
$body .= "</td></tr>";
$body .= "<tr><td>&nbsp;</td><td>";
my $extra = $edit_mode == 1 ? $POST{'extra'} : $GET{'extra'};
$body .= LJ::html_hidden('extra', $extra) if $extra;
$body .= LJ::html_submit ('reset', '<- Reset', {'type' => 'reset'});
$body .= "&nbsp;&nbsp;";
$body .= LJ::html_submit ('submit', 'Preview ->');
$body .= "</td></tr>";
$body .= "</table>";
$body .= "</form>";
};
my $preview = sub {
my $errors = 0;
$body .= "<table style='border-spacing: 5px'>";
$body .= "<tr><th style='text-align: left'>From:</th><td>";
my $from = LJ::trim($POST{'from'});
if ($cansend{$from}) {
$body .= "$from\@$LJ::DOMAIN";
} else {
$errors = 1;
$body .= "<font color='red'>Invalid address chosen</font>";
}
$body .= "</tr><tr><th style='text-align: left'>To:</th><td>";
my @to;
if (LJ::trim($POST{'user'}) ne '') {
my $u = LJ::load_user($POST{'user'});
unless ($u) {
$errors = 1;
$body .= "<font color='red'>Invalid username $POST{user}</font>";
} else {
push @to, $u->{'email'};
$body .= LJ::ljuser($u) . " ($u->{email})";;
}
} elsif (LJ::trim($POST{'maints'}) ne '') {
my $u = LJ::load_user($POST{'maints'});
if ($u->{journaltype} ne 'C') {
$body .= "<font color='red'>Community specified is not a community</font>";
} else {
my $ids = LJ::load_rel_user($u->{userid}, 'A');
foreach (@$ids) {
my $maint = LJ::load_userid($_);
push @to, $maint->{'email'}
}
$body .= "Maintainers of ";
$body .= LJ::ljuser($u) . " (";
$body .= join(", ", @to) . ")";
}
} elsif (LJ::trim($POST{'email'}) ne '') {
my $addr = LJ::trim($POST{'email'});
my @email_errors;
LJ::check_email($addr, \@email_errors);
if (@email_errors) {
$errors = 1;
$body .= "<font color='red'>";
$body .= join(", ", @email_errors);
$body .= "</font>";
} else {
push @to, $addr;
$body .= $addr;
}
} else {
$errors = 1;
$body .= "<font color='red'>You must enter a recipient</font>";
}
my $bcc = LJ::trim($POST{'bcc'});
if ($bcc ne '') {
$body .= "<tr><th style='text-align: left'>Bcc:</th><td>";
my @bcc_errors;
LJ::check_email($bcc, \@bcc_errors);
if (@bcc_errors) {
$errors = 1;
$body .= "<font color='red'>";
$body .= join(", ", @bcc_errors);
$body .= "</font>";
} else {
$body .= $bcc;
}
$body .= "</td></tr>";
}
my $request = LJ::trim($POST{'request'});
$body .= "<tr><th style='text-align: left; white-space: nowrap'>Request #:</th><td>";
if ($request ne '') {
unless ($request =~ /^\d+$/) {
$body .= "<font color='red'>Request id must be numeric</font>";
$errors = 1;
} else {
$body .= $request;
}
} else {
$body .= "<font color='orange'>No request specified</font>";
}
$body .= "</td></tr>";
my $subject = LJ::trim($POST{'subject'});
$body .= "<tr><th style='text-align: left'>Subject:</th><td>";
if ($subject eq '') {
$body .= "<font color='red'>You must specify a subject</font>";
$errors = 1;
} else {
$body .= $subject;
}
$body .= "</td></tr>";
my $message = LJ::trim($POST{'message'});
$body .= "<tr><th style='vertical-align: top; text-align: left'>Message:</th><td>";
if ($message eq '') {
$body .= "<font color='red'>You must specify a message</font>";
$errors = 1;
} else {
my $tmp_mess = $message;
$tmp_mess =~ s/\r?\n/<br \/>\n/g;
$body .= $tmp_mess;
}
$body .= "</td></tr>";
$body .= "<tr><td colspan='2'>";
$body .= "<form action='send.bml?action=send' method='post' id='preview'>";
$body .= LJ::html_hidden('from', $from, 'to', join(',', @to), 'bcc', $bcc,
'subject', $subject, 'message', $message, 'email', $POST{'email'},
'maints', $POST{'maints'}, 'user', $POST{'user'}, 'request',
$request);
$body .= LJ::html_hidden('extra', $POST{'extra'}) if $POST{'extra'};
$body .= LJ::form_auth();
$body .= '<br />';
$body .= LJ::html_submit('edit', '<- Edit', {'raw' => "onclick='form_switch()'"});
$body .= LJ::html_submit('send', 'Send ->', {'disabled' => $errors});
$body .= "</form>";
$body .= "</table>";
};
my $send = sub {
my @errors;
my $dbh = LJ::get_db_writer();
return $body = LJ::bad_input('No database connection present. Please go back and try again.')
unless $dbh;
return $body = LJ::bad_input($ML{'error.invalidform'})
unless LJ::check_form_auth();
return $body = LJ::bad_input('Invalid sender')
unless $cansend{$POST{'from'}};
# Already did sanity checking in the previous step
my @addrs = split(',', $POST{'to'});
my %prettynames;
$prettynames{'abuse'} = "$LJ::SITENAMESHORT Abuse Team";
$prettynames{'accounts'} = "$LJ::SITENAMESHORT Accounts";
$prettynames{'coppa'} = "$LJ::SITENAMESHORT COPPA Enforcement";
$prettynames{'support'} = "$LJ::SITENAMESHORT Support Team";
my $fromname = $prettynames{$POST{'from'}};
foreach my $email (@addrs) {
my $status = "S";
# status "S" means send_mail returned true, but this does *not* guarantee
# that the message didn't bounce, only that the sendmail process didn't croak
if(!LJ::send_mail({
'to' => $email,
'bcc' => $POST{'bcc'},
'from' => "$POST{'from'}\@$LJ::DOMAIN",
'fromname' => $fromname,
'charset' => "utf-8",
'subject' => $POST{'subject'},
'body' => $POST{'message'},
}))
{
$status = "F";
push @errors, "<strong>Error:</strong><br />Mail not sent to $email";
}
my $query = "INSERT INTO abuse_mail (mailid, userid, spid, status, timesent, mailto, " .
"subject, message, type) " .
"VALUES (NULL, ?, ?, ?, NOW(), ?, ?, ?, ?)";
$dbh->do($query, undef, $remote->{'userid'}, $POST{'request'}, $status,
$email, $POST{'subject'}, $POST{'message'}, $POST{'from'});
if ($dbh->err) {
my $error = $dbh->errstr;
push @errors, "<strong>Error:</strong> Unable to record mailing to $email<br />$error";
}
# take extra actions if necessary
if ($POST{'extra'} =~ /^spam-notification;(\d+)$/) {
my $u = LJ::load_userid($1+0);
push @errors, "<?h1 Error h1?><?p Invalid userid passed. " .
"<b>Email was sent, statushistory not logged.</b> p?>"
unless $u;
LJ::statushistory_add($u->{userid}, $remote->{userid}, 'spam_warning', 'Sent email warning')
if $u;
}
}
if (@errors) {
$body .= join("<br /><br />", @errors);
} else {
$body .= "Email(s) sent succesfully";
}
};
# What to do aka "What it is"
if (LJ::did_post()) {
if ($GET{'action'} eq 'preview') {
$preview->();
} elsif ($GET{'action'} eq 'edit') {
$compose->(1);
} elsif ($GET{'action'} eq 'send') {
$send->();
}
} else {
$compose->(0);
}
}
_code?>
<=body
title=>Send User Mail
page?>

View File

@@ -0,0 +1,151 @@
<?_code
{
use strict;
use vars qw(%GET);
use YAML ();
use Storable ();
use Data::Dumper ();
my $remote = LJ::get_remote();
return "<b>Error:</b> You don't have access to see servers."
unless LJ::check_priv($remote, "siteadmin", "serverview");
# YAML parser is slow as fuck.
my $realfile = "$LJ::HOME/cgi-bin/servers.yaml";
my $cached = "$LJ::HOME/var/servers.yaml.cache";
my $servers;
if (-e $cached && (stat(_))[9] > (stat($realfile))[9]) {
# use the pre-parsed version from Storable, which doesn't suck.
$servers = Storable::retrieve($cached);
} else {
$servers = YAML::LoadFile($realfile);
Storable::store($servers, $cached);
}
my $ret;
$ret .= "<h1>Servers</h1>";
my $name = $GET{'name'};
my $mode = $GET{'mode'};
my $job = $GET{'job'};
$mode = "1job" if $job;
$mode = "1name" if $name;
foreach (['', 'By Name'],
['job', 'By Job'],
['ip', 'By IP'],
['cab', 'By Cabinet'],
) {
if ($mode eq $_->[0]) {
$ret .= "[<b>$_->[1]</b>]\n";
} else {
$ret .= "[<a href='servers.bml?mode=$_->[0]'>$_->[1]</a>]\n";
}
}
# sanitize the data structure a bit, and pick up on jobs/etc
my %jobs;
my %ip;
foreach my $name (keys %$servers) {
my $s = $servers->{$name};
$s->{'jobs'} = [ $s->{'jobs'} ] unless ref $s->{'jobs'} eq "ARRAY";
foreach (@{$s->{'jobs'}}) { $jobs{$_}->{$name} = 1; }
my $ip = $s->{'ip'};
my $hip = join(':', map { sprintf("%02x", $_) } split(/\./, $ip));
$ip{$hip} = $name;
}
# show a single server
if ($name) {
unless ($servers->{$name}) { $ret .= "bogus name"; return $ret; }
my $dp = Data::Dumper::Dumper($servers->{$name});
my $link = sub {
my $roles = shift;
$roles =~ s/\'(.+?)\'/\'<a href="servers.bml?job=$1">$1<\/a>\'/g;
return "'jobs' => [$roles]";
};
$dp =~ s/\'jobs\' => \[(.+?)\]/$link->($1)/se;
$ret .= "<h2>$name</h2><pre>$dp</pre>";
return $ret;
}
my $serv_line = sub {
my $name = shift;
my $text = shift;
unless ($text) {
my $s = $servers->{$name};
my $pip = $s->{'ip'};
my $jobs = join(', ', map { "<a href='servers.bml?job=$_'>$_</a>" } @{$s->{'jobs'}});
$text = "[$pip] $jobs";
}
return "<p><b><a href='servers.bml?name=$name'>$name</a></b> $text</p>\n";
};
# show a single job
if ($job) {
unless ($jobs{$job}) { $ret .= "bogus job"; return $ret; }
$ret .= "<h2>Job: $job</h2><ul>";
foreach my $name (sort keys %{$jobs{$job}}) {
$ret .= $serv_line->($name);
}
$ret .= "</ul>";
return $ret;
}
# by job
if ($mode eq "job") {
foreach my $job (sort keys %jobs) {
$ret .= "<h2>Job: $job</h2><ul>\n";
foreach my $name (sort keys %{$jobs{$job}}) {
$ret .= $serv_line->($name);
}
$ret .= "</ul>";
}
return $ret;
}
# by cabinet
if ($mode eq "cab") {
my %cab;
my %u;
foreach my $name (keys %$servers) {
my $s = $servers->{$name};
next unless $s->{'rack'};
$cab{$s->{'rack'}->{'cabinet'}}->{$name} = 0; # not sure where for now
next unless $s->{'rack'}->{'size'} =~ /\d+/;
$u{$s->{'rack'}->{'cabinet'}} += $&;
}
foreach my $cab (sort { $a <=> $b } keys %cab) {
$ret .= "<h2>Cabinet: $cab ($u{$cab}U)</h2><ul>\n";
my $ch = $cab{$cab};
foreach my $name (sort { $ch->{$a} <=> $ch->{$b} } keys %$ch) {
my $s = $servers->{$name};
$ret .= $serv_line->($name, $s->{'rack'}->{'size'});
}
$ret .= "</ul>";
}
return $ret;
}
# by ip
if ($mode eq "ip") {
foreach my $ip (sort keys %ip) {
my $dip = join('.', map { hex $_ } split(/:/, $ip));
my $name = $ip{$ip};
$ret .= $serv_line->($name, "[$dip]");
}
return $ret;
}
# by name
foreach my $name (sort keys %$servers) {
$ret .= $serv_line->($name);
}
return $ret;
}
_code?>

View File

@@ -0,0 +1,70 @@
<?page
title=>LiveJournal Support Tools
body<=
<?_code
{
use strict;
my $remote = LJ::get_remote();
my $canhelp = LJ::check_priv($remote, 'supporthelp') ||
LJ::check_priv($remote, 'supportviewscreened');
return "This page is intended for LiveJournal Support" .
" Volunteer use only. If you need support, please" .
" visit <a href='http://www.livejournal.com/support/'>" .
"http://www.livejournal.com/support/</a>." unless $remote && $canhelp;
my $ret;
$ret .= "<?p This is a collection of various tools and communities used by LiveJournal Support Volunteers. You may not have privileges for all tools listed. p?>";
$ret .= "<?h2 General Tools h2?>";
$ret .= "<ul>";
$ret .= "<li><a href='/support/help.bml?state=youreplied'>You Replied Filter</a></li>";
$ret .= "<li><a href='/admin/console/'>Admin Console</a> (<a href='/admin/console/reference.bml'>Reference</a>)</li>";
$ret .= "<li><a href='/support/changenotify.bml'>Change Notification Options</a></li>";
$ret .= "<li><a href='/support/see_overrides.bml'>See Overrides</a></li>";
$ret .= "<li><a href='/support/history.bml'>User Request History</a></li>";
$ret .= "<li><a href='/betatest.bml'>Beta Test Options</a></li>";
$ret .= "<li><a href='/admin/faq/index.bml'>FAQ Edit</a></li>";
$ret .= "<li><a href='/admin/memcache_purge.bml'>Memcache Purge</a></li>";
$ret .= "<li><a href='/admin/clusterstatus.bml'>Cluster Status</a></li>";
$ret .= "<li><a href='/tools/recent_emailposts.bml'>Email Post History</a></li>";
$ret .= "</ul>";
my @scomms = (
'lj_support', 'helpscreening', 'support_interim',
'support_clients', 'support_comms', 'support_embed',
'support_general', 'support_mobile', 'support_ssystem',
'support_syn', 'support_upi', 'support_web', 'web_ui',
'web_training', 'lj_supportadmin'
);
$ret .= "<?h2 Support Communities h2?>";
$ret .= "<ul>";
foreach (@scomms) {
$ret .= "<li>" . LJ::ljuser("$_") .
" (<a href='/update.bml?usejournal=$_'>Post</a>)</li>";
}
$ret .= "</ul>";
$ret .= "<?h2 Support Admin Tools h2?>";
$ret .= "<ul>";
$ret .= "<li><a href='/admin/priv/index.bml'>Privilege Management</a></li>";
$ret .= "<li><a href='/support/stock_answers.bml'>Manage Stock Answers</a></li>";
$ret .= "<li><a href='/admin/statushistory.bml'>Status History</a></li>";
$ret .= "<li><a href='/admin/fileedit/index.bml?file=support-currentproblems'>Edit BBB</a></li>";
$ret .= "<li><a href='/admin/sendmail/send.bml?from=support'>Send Support Note</a></li>";
$ret .= "<li><a href='/admin/sendmail/query.bml?restrict=support'>Query Support Notes</a></li>";
$ret .= "</ul>";
return $ret;
}
_code?>
<=body
page?>

View File

@@ -0,0 +1,154 @@
<html>
<head>
<title>Alternate Age Verification (for COPPA Compliance)</title>
<style>
@media screen {
body {
background-color: #ffffff;
color: #000000;
}
#Directions { margin-bottom: 0; }
#AffixDirections { margin-bottom: 0; }
h1 {
text-align: right;
}
p#Affix {
margin-top: 0.2in;
border: 1px solid #000000;
height: 4.9in;
}
.Label {
font-weight: bold;
}
#Info {
margin: 0;
}
#Info th, #Info td {
margin-top: 1pt;
line-height: 14pt;
}
#Info th {
text-align: left;
width: 4em;
}
#Username td, #Time td, #Notes td, .Notes td {
margin-top: 0;
margin-bottom: .125in;
border-bottom: 1px solid #000000;
}
table, p, h1 {
width: 6.5in;
}
.Label { font-weight: bold; }
#ContactAddress { display: none; }
#Print { display: inline; }
}
@media print {
@page {
size: 8.5in 11in; /* width height */
margin: 0.5in;
}
body {
background-color: #ffffff;
color: #000000;
font: 12pt "Times", "Times New Roman", serif;
margin: 0;
}
#AffixDirections { margin-bottom: 0; }
h1 {
text-align: right;
}
p#Affix {
margin-top: 0.2in;
border: 1px solid #000000;
height: 4.9in;
}
.Label {
font-weight: bold;
}
#Info {
margin: 0;
}
#Info th, #Info td {
margin-top: 1pt;
line-height: 14pt;
}
#Info th {
text-align: left;
width: 4em;
}
#Username td, #Time td, #Notes td, .Notes td {
margin-top: 0;
margin-bottom: .125in;
border-bottom: 1px solid #000000;
}
#ContactInfo a {
color: #000000;
text-decoration: none;
}
table, p, h1 {
width: 6.5in;
}
#Print { display: none; }
}
</style>
</head>
<body>
<?_code
{
use strict;
use vars qw(%GET);
my $remote = LJ::get_remote();
return BML::redirect("$LJ::SITEROOT/agecheck/")
unless $remote && $remote->underage;
my $ljuser = LJ::ljuser($remote);
my $datetime = LJ::time_to_http();
my $postal_addr = LJ::Pay::postal_address_html('Attention: COPPA Verifications');
return qq{
<h1>Alternate Age Verification</h1>
<span id="Directions">Please mail, fax, or scan & email this form using the contact information below.</span>
<form id="Print"><input type="button" onclick="javascript:window.print()" Value="Print now" /></form>
<p id="Address">
<b>Fax:</b> 415-294-5054<br />
<b>Email:</b> <a href='mailto:coppa\@$LJ::DOMAIN'>coppa\@$LJ::DOMAIN</a> <small>(Please attach either PNG or JPG)</small><br /><br />
$postal_addr
</p>
<table id="Info" cellspacing="0" cellpadding="0">
<tr id="Username">
<th class="Label">User:</th><td>$ljuser</td><td align='right'><b style='font-size: 1.5em;'>$remote->{user}</b></td>
</tr>
<tr id="Time">
<th class="Label">Time:</th><td colspan='2'>$datetime</td>
</tr>
<tr id="Notes">
<th class="Label">Notes:</th><td colspan='2'>&nbsp;</td>
</tr>
<tr class="Notes">
<th class="Label"></th><td colspan='2'>&nbsp;</td>
</tr>
<tr class="Notes">
<th class="Label"></th><td colspan='2'>&nbsp;</td>
</tr>
<tr class="Notes">
<th class="Label"></th><td colspan='2'>&nbsp;</td>
</tr>
</table>
<p id="AffixDirections"><span class="Label">Affix a <u>copy</u> of your government-issued photo ID or birth certificate here:</span></p>
<p id="Affix"></p>
<p id="ContactInfo">Please direct all inquiries to <a href="http://www.livejournal.com/support/">$LJ::SITENAME Support</a>. <span id="ContactAddress">(http://www.livejournal.com/support/)</span></p>
}; # end qq{}
}
_code?>
</body>
</html>

View File

@@ -0,0 +1,58 @@
<?page
title=>Age Verification (for COPPA Compliance)
body<=
<?_code
{
use strict;
use vars qw(%GET);
my $c = $GET{c};
unless ($c =~ /^(\d+)[;\.](.+)$/) {
return "<?h1 Error h1?><?p You did not get here with a valid authorization code. Please try clicking the link in your email again. p?>";
}
my ($aaid, $auth) = ($1, $2);
my $aa = LJ::is_valid_authaction($aaid, $auth);
unless ($aa) {
return "<?h1 Error h1?><?p The authorization code provided does not appear valid or has expired. If it has been more than 72 hours since you verified your child's account, you will need to do so again. p?>";
}
# so, it's verified
my $u = LJ::load_userid($aa->{userid});
unless ($u) {
return "<?h1 Error h1?><?p The account specified for this authorization was not found. You will need to perform the age verification for your child's account again. p?>";
}
# now do the work
LJ::mark_authaction_used($aa);
LJ::set_userprop($u, 'parent_email', $aa->{arg1});
$u->underage(0, undef, "parent email verification; email=$aa->{arg1}");
# and now we have to send an email validation notice to the child's email
my $aa = LJ::register_authaction($u->{userid}, "validateemail", $u->{email});
my $body = BML::ml('email.newacct2.body', {
"email" => $u->{email},
"regurl" => "$LJ::SITEROOT/confirm/$aa->{'aaid'}.$aa->{'authcode'}",
"username" => $u->{user},
"sitename" => $LJ::SITENAME,
"siteroot" => $LJ::SITEROOT,
"admin_email" => $LJ::ADMIN_EMAIL,
"bogus_email" => $LJ::BOGUS_EMAIL,
});
LJ::send_mail({
'to' => $u->{email},
'from' => $LJ::ADMIN_EMAIL,
'fromname' => $LJ::SITENAME,
'charset' => 'utf-8',
'subject' => BML::ml('email.newacct.subject', {'sitename' => $LJ::SITENAME}),
'body' => $body,
});
# queue up a message
return "<?h1 Success h1?><?p Thank you for verifying your child's access. Their account has been activated and they can start using it now. p?><?p Additionally, we have sent a welcome email to your child welcoming them to the site. p?>";
}
_code?>
<=body
page?>

View File

@@ -0,0 +1,177 @@
<?page
title=>Age Verification
body<=
<?_code
{
use strict;
use vars qw(%GET);
my $remote = LJ::get_remote();
return "<?needlogin?>"
unless $remote;
return "<?h1 Error h1?><?p Your account is not marked as being underage or has already been verified. You do not need to do this again. p?>"
unless $remote->underage;
if (LJ::did_post()) {
# make the cart and add our verification item
unless ($POST{cartid}) {
my $cart = LJ::Pay::new_cart($remote);
LJ::Pay::add_cart_item($cart, {
item => 'coppa',
rcptid => $remote->{userid},
});
$POST{cartid} = "$cart->{payid}-$cart->{anum}";
}
# redirect to it
return BML::redirect("$LJ::SSLROOT/pay/cc.bml?c=$POST{cartid}");
}
# throw in extra boxes
my ($extra, $cartid);
if ($GET{o}) {
$extra = "<?standout " .
"Even though you specified a birthday that makes you at least 13 years old, the computer " .
"you are using was previously used to create an account that is under the age of 13. " .
"Therefore, the account you have just created is also required to verify its age." .
" standout?>";
} elsif ($GET{s}) {
$extra = "<?standout " .
"We're sorry, the action you were trying to perform requires you to verify that you are " .
"authorized to have this account. Please ask your parent to continue the registration " .
"process with you." .
" standout?>";
} else {
$extra = "<?standout " .
"You appear to be under the age of 13. Before you can continue to use LiveJournal.com, " .
"we must verify that your parents have read and agree to our privacy policy and Terms of " .
"Service. Please ask your parent to continue the registration process with you." .
" standout?>";
}
if ($GET{c}) {
# verify input before putting in our output
$cartid = $1
if $GET{c} =~ /^(\d+-\d+)$/;
}
return qq{
$extra
<?h1 Information Relating to Children h1?>
<?p The Children's Online Privacy Protection Act ("COPPA") requires that we
inform parents about how we collect, use, and disclose personal information
from children under 13 years of age. LiveJournal.com is not directed at
children under 13 but we recognize that with proper adult supervision
some parents might permit their children to visit LiveJournal.com and use
our Service. COPPA also requires that we obtain the consent of parents
and guardians in order for children under 13 years of age to use certain
features of LiveJournal.com. p?>
<?p When your child attempts to register and/or provide personal information
to LiveJournal.com, we require a parent or legal guardian to: p?>
<ol>
<li>complete the registration;</li>
<li>review our privacy policy and to submit a valid credit card number to
verify that the child's parent or guardian knows about and authorizes our
information practices related to children protected by COPPA; and</li>
<li>verify through the use of an email confirmation and second
authorization that the parent or guardian consents to the Terms of
Service and LiveJournal.com's privacy policy.</li>
</ol>
<?p <strong>Once parental notice and consent has been verified, the
information we collect will be treated the same as information collected
from any other user of LiveJournal.com.</strong> p?>
<?p Without verified parental notice and consent, we will not knowingly
collect personal information of children under 13, and if we learn that we
have inadvertently collected such information, we will promptly delete it.
p?>
<?p Some highlights of our privacy policy include: p?>
<ul>
<li>We <strong>do not</strong> transfer personal information we collect to people or
organizations outside the LiveJournal.com family of companies for their
direct marketing purposes.</li>
<li>We <strong>do</strong> share this information within the LiveJournal.com family of
companies but any use or disclosure of this information is controlled by
our privacy policy.</li>
<li>We may use or share account users' personal information where it
is necessary for us to complete a transaction, to operate or improve
LiveJournal.com and related products and services, to do something that the
user has asked us to do, or to tell the user of products and services that
we think may be of interest.</li>
<li>We may occasionally contact our users with offers. When we do this, we
will do our best to provide you with an opportunity to opt-out of receiving
such further communications at the time you are contacted.</li>
</ul>
Detailed information on our complete privacy practices is available
<a href="/legal/privacy.bml">here</a>.
<?h1 How Parents can Access their Children's Personal Information h1?>
<?p In compliance with COPPA, parents and legal guardians may request
from us to review, delete or stop the collection of the personally
identifiable information of their child. You may do so in one of two
ways. p?>
<?p If you know the user name and password, follow the instructions below
regarding "Changing your Preferences and Personal Information", or you
may contact our privacy officer by phone, mail or email using the contact
information disclosed in our <a href="/legal/privacy.bml#contact">privacy
policy</a>. p?>
<?p If you request that no further information about your child be
collected or used, we will be required to terminate your child's ability to
use LiveJournal.com's products and services that require a user to "sign
in." p?>
<?h1 Changing Preferences and Personal Information h1?>
<ul>
<li> You can edit your child's LiveJournal.com Account Information at any
time. Most personal information provide to LiveJournal.com is entirely
optional. For example, to the extent that parents are considering whether
to permit their children to use the Service, we do not condition a child's
participation based upon their provision of any more personal information
than is necessary to operate the Service. </li>
<li> You can delete your LiveJournal.com account by visiting our Account
Deletion page; however, please note that some personal information,
primarily your contact information, may remain in LiveJournal.com's records
to the extent necessary to protect LiveJournal's legal interests or
document compliance with regulatory requirements. </li>
</ul>
<?h1 Continue with Account Verification h1?>
<?p If you have access to a credit card, you may continue with electronic
account verification by clicking the button below. This is the preferred
method. Alternatively, you can mail or fax a copy of your government-issued
ID or birth vertificate using our
<a href='$LJ::SITEROOT/agecheck/altform.bml'>printable form</a>. p?>
<form method="post" action="$LJ::SITEROOT/agecheck/index.bml" style='margin: 20px 0 0 20px'>
} .
LJ::html_submit("Continue Electronic Verification") . "\n" .
LJ::html_hidden(cartid => $cartid) . "\n" .
"</form>\n";
}
_code?>
<=body
page?>

250
ljcom/htdocs/banners.bml Normal file
View File

@@ -0,0 +1,250 @@
<?page
title=>LiveJournal Banners
body<=
<?_code
LJ::set_active_crumb('banners');
_code?>
<?h1 Help <?sitename?>! h1?>
<?p
Help spread the word about how cool <?sitename?> is by placing one of these banners on your website! The HTML is below, all you have to do is copy & paste into your HTML...
p?>
<?hr?>
<?bh Sexy Banner bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner1.gif" WIDTH=468
HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner1.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
<?bh Elegant Banner bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner2.gif" WIDTH=468
HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner2.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
<?bh Captivating Banner bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner3.gif" WIDTH=468
HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner3.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
<?bh Impacting Society Banner bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner4.gif" WIDTH=468
HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner4.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
<?bh Narcissicm Banner bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner5.gif" WIDTH=468
HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner5.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
<?bh Goat Banner bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner6.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner6.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
<?bh Informational Banner bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner7.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner7.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
<?bh Business Ethics Banner (or, We-Don't-Crash-Twice-Daily Banner) bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner8.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner8.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
<?bh Another Banner bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner9.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner9.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
<?bh Metal Banner bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner_metal.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner_metal.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
<?bh Pastel Banner bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner_pastel.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/LJbanner_pastel.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
<?bh FireBindy1 bh?>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/banners/firebindy1.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out <?sitename?>! It's Free!</A>
</CENTER>
<FONT COLOR=#0000FF><B><XMP>
<CENTER>
<A HREF="<?siteroot?>/"><IMG
SRC="<?imgprefix?>/banners/firebindy1.gif"
WIDTH=468 HEIGHT=60 BORDER=0></A><BR>
<A HREF="<?siteroot?>">Check out
<?sitename?>! It's Free!</A>
</CENTER>
</XMP></B></FONT>
<P>
</CENTER>
<=body
page?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,110 @@
<?page
title=>Your Balance
body<=
<p>[&lt;&lt; <a href="./">Back</a>]</p>
<?_code
{
use strict;
my $dbh = LJ::get_db_writer();
my $remote = LJ::get_remote();
unless ($remote) { return $ML{'error.noremote'}; }
LJ::load_user_props($dbh, $remote, "legal_assignagree");
my ($ret, $sth);
LJ::Pay::bazaar_do_expirations($remote->{'userid'});
$sth = $dbh->prepare("SELECT date, amt, method, note FROM bzrpayout WHERE userid=?");
$sth->execute($remote->{'userid'});
my $hist;
while (my $po = $sth->fetchrow_hashref) {
unless ($hist) {
$hist .= "<?h1 Payment History h1?>";
$hist .= "<table cellpadding='3' border='1'><tr><th>Date</th><th>Amount</th><th>Method</th><th>Note</th></tr>\n";
}
$hist .= "<tr><td>$po->{'date'}</td><td>\$" . sprintf("%0.02f", $po->{'amt'}) . "</td><td>$po->{'method'}</td><td>$po->{'note'}</td></tr>\n";
}
if ($hist) {
$ret .= $hist;
$ret .= "</table>\n";
}
$sth = $dbh->prepare("SELECT bzid, date, amt, owed, expired ".
"FROM bzrbalance WHERE userid=? ORDER BY bzid");
$sth->execute($remote->{'userid'});
$ret .= "<?h1 Amount owed h1?>";
$ret .= "<table cellpadding='3' border='1'>";
$ret .= "<tr><th>Date</th><th>Amount</th><th>Balance</th><th>Note</th></tr>\n";
my $owed;
my $rows = 0;
my $now = time();
while (my $r = $sth->fetchrow_hashref) {
$rows++;
$ret .= "<tr><td>$r->{'date'}</td>";
$owed += $r->{'owed'};
$ret .= sprintf("<td align='right'>\$%0.02f</td>", $r->{'amt'});
$ret .= sprintf("<td align='right'>\$%0.02f</td>", $r->{'owed'});
my $extra;
if ($r->{'expired'} > 0) {
$extra .= sprintf(" (\$%0.02f expired after 3 months unclaimed)", $r->{'expired'});
}
my $time = LJ::mysqldate_to_time($r->{'date'});
my $exptime = $time + 86400*93;
if ($r->{'owed'} > 0 && $now < $exptime) {
$extra .= ", balance will expire in " . int(($exptime - $now) / 86400) . " days";
}
$ret .= "<td><a href='status.bml?bzid=$r->{'bzid'}'>Bazaar \#$r->{'bzid'}</a>$extra</td>";
$ret .= "</tr>\n";
}
unless ($rows) {
return "No balance or history.";
}
$ret .= "<tr><td colspan='2' align='right'><b>Amount owed:</b></td><td align='right'>" .
sprintf("\$%0.02f", $owed) . "</td><td></td></tr>";
$ret .= "</table>";
return $ret unless $owed;
unless ($remote->{'legal_assignagree'}) {
$ret .= "<?h1 Paperwork h1?>";
$ret .= "<?p The following paperwork needs to be completed and mailed to LiveJournal to get paid: p?><ul>";
$ret .= "<li><a href='$LJ::STATPREFIX/misc/assignment_agreement.doc'>Assignment Agreement</a> -- to assign copyright to Danga Interactive</li>\n";
$ret .= "<li>If in the United States:<ul><li><a href='http://www.irs.gov/pub/irs-pdf/fw9.pdf'>W-9</a> -- tax form</li></ul></li>";
$ret .= "<li>If outside the United States:<ul>";
$ret .= "<li><a href='http://www.irs.gov/pub/irs-fill/fw8ben.pdf'>W-8BEN</a> -- Certificate of Foreign Status of Beneficial Owner for United States Tax Withholding.</li>";
$ret .= "<li><a href='http://www.irs.gov/pub/irs-pdf/iw8ben.pdf'>Instructions for above form</a></li>\n";
$ret .= "<li><a href='http://www.irs.gov/businesses/small/international/article/0,,id=96696,00.html'>Obtaining an ITIN from Abroad</a></li>\n";
$ret .= "<li><a href='http://www.irs.gov/businesses/corporations/article/0,,id=96739,00.html'>Tax treaty list</a> -- the list of who the US has tax treaties with</li>";
$ret .= "</ul></li>";
$ret .= "</ul>";
$ret .= "Mailing address:<blockquote><pre>" . LJ::Pay::postal_address_text() . "</pre></blockquote>";
$ret .= "Include your LiveJournal username, as well as a mailing address to send your check if your balance is over \$50. If you'd prefer to not get paid (and instead generate coupons later at this URL), indicate as such in your letter.";
$ret .= "<?h1 Coupon Generation h1?>";
$ret .= "<?p Until we have your paperwork on file, you cannot generate coupons. p?>";
return $ret;
}
if ($owed >= 50) {
$ret .= "<?h1 Request payment h1?><?p To get paid, email sandy\@livejournal.com requesting a check. Include your LiveJournal username and mailing address. p?>";
} else {
$ret .= "<?h1 Balance under \$50 h1?><?p Your balance is under \$50. We can send out checks once your balance is \$50 or higher. For now, you can generate LiveJournal coupons instead. p?>";
}
$ret .= "<?h1 Generate coupon h1?><?p This form will let you generate a LiveJournal coupon which can be used by anybody, not just your account. <b>Important note:</b> coupons are either used or unused... they don't maintain a balance. As such, only generate a coupon for the amount you intend to use. For your protection, we only allow coupons to be generated for the amounts \$5.00 - \$25.00. You may use multiple coupons in an order, if your order will be more than \$25.00. p?>";
$ret .= "<form method='post' action='gencoupon.bml' style='margin-left: 40px'>\n";
$ret .= "Amount: \$<input type='text' size='6' name='amt' value='' /> <input type='submit' value='Generate' />";
$ret .= "</form>";
return $ret;
}
_code?>
<=body
page?>

View File

@@ -0,0 +1,97 @@
<?page
title=>Bazaar Details
body<=
<p>[&lt;&lt; <a href="./">Back</a>]</p>
<?h1 Seed Voters h1?>
<?p
To kick-start the system, the voters for the first month are the LiveJournal employees.
p?>
<?h1 Voting for yourself h1?>
<?p
You can't explictly vote for yourself. To prevent oscillations,
however, the system assigns a percentage of your votes back to
yourself, based on the weighted average of what the other voters for the
current month gave you.
p?>
<?h1 LiveJournal.com employees h1?><?p
Full-time LiveJournal.com employees receive votes and use their voting
power, but don't take any percentage of the pot. Part-time employees
likewise receive votes and use their voting power, but they can
receive some money from the pot past a certain per-employee threshold.
For instance, if the threshold for part-time employee "Bob" were
$1,000 and he earned $800, he'd get nothing. If he earned
$1,200 of the pot, he'd actually get $200, and the other $1,000 would
stay in the pot, to be divided amongst everybody else.
p?>
<?h1 Requirement to receive money h1?><?p Before you're eligible to
receive money from the pot, you must first complete the necessary tax
forms and a copyright assignment agreement. If you're outside of the
United States, things may be tedious or impossible, depending on your
country and its relation with the United States. We won't mail out
checks smaller than $50. However, see the next section.
p?>
<?h1 Cash-out alternative h1?><?p If it's too difficult for you to
receive money, or your balance is too low, we'll also let you apply
your pending balance towards LiveJournal <a href="/pay/">payment</a>
coupons. A minimum $5 coupon may be generated. p?>
<?h1 Participant Types h1?><?p
All participants in the bazaar fall into one of the following types:
<ul>
<li><b>Voter only</b> -- not eligible to receive money (full-time LiveJournal employees, and anybody who contacts us and tells us that they don't wish to receive money)</li>
<li><b>Part-time employees</b> -- details above. can vote, and can get some of the earned pot.</li>
<li><b>Money Queuing</b> -- (default state) You haven't done the paperwork to receive money yet, but your earnings are being held for up to three months. If after 5 months you do the paperwork, you'll receive the money you earned in the previous three months.</li>
<li><b>Money Recipient</b> -- you've done the paperwork and you'll get paid at the end of the month, after votes are tallied and reviewed. Money will be held indefinitely until you earn at least $25. (except if you live outside of the US and wire fees are high, in which case we won't pay until you've earned at least double what it'd cost to wire the money)
</ul>
p?>
<?h1 Privacy h1?>
<?p
Each user's state (above) is private. They may share it if others if they like, but we won't reveal it.
p?>
<?p
The voting weight you give to other people's contributions is private, except
to LiveJournal staff for the purpose of review.
p?>
<?p
LiveJournal will make public the current month's voters, and their voting power,
but not how much money they made in the previous month. Because the type of each
user is also hidden, it's not possible to accurately infer the money received.
p?>
<?h1 Minimum Percentages h1?>
<?p
Certain LiveJournal employees retain a guaranteed minimum voting percentage each month.
p?>
<?h1 Cheating h1?><?p
LiveJournal reserves the right to review voting, and disqualify people
if questionable voting is taking place. (people voting for each other
exclusively each month, etc)
p?>
<?h1 Pot increases h1?><?p
LiveJournal decides when projects are completed and when the pot amount increases. Completion of a project marked with $n increase doesn't necessarily mean you'll earn $n. You'll earn whatever the community decides it's worth.
p?>
<?h1 Acknowledgements vs. Voting h1?>
<?p
The default voting weight for a contribution acknowledgement is zero.
This lets you acknowledge that somebody did something (so they get
listed on the contributors page) but without giving them a percentage
of the pot. It's up to every voting user to decide what's important to
them and what deserves the pot (and voting rights).
p?>
<=body
page?>

View File

@@ -0,0 +1,46 @@
<?page
title=>Generate Coupon
body<=
<?_code
{
use strict;
use vars qw(%POST %GET);
my $dbh = LJ::get_db_writer();
my $remote = LJ::get_remote();
unless ($remote) { return $ML{'error.noremote'}; }
LJ::load_user_props($dbh, $remote, "legal_assignagree");
return BML::redirect("/bazaar/balance.bml") unless $remote->{'legal_assignagree'};
if ($GET{'cpid'}) {
my $cp = $dbh->selectrow_hashref("SELECT * FROM coupon WHERE cpid=? AND rcptid=?",
undef, $GET{'cpid'}, $remote->{'userid'});
return "<b>Error:</b> Coupon not found" unless $cp;
return "<?h1 Success h1?><?p The following coupon has been generated for <b>\$$cp->{'arg'}</b>:<center><span style='font-size: 20pt'>$cp->{'cpid'}-$cp->{'auth'}</span></center> p?><?p You'll be able to find this coupon back at any time from your <a href='balance.bml'>payment history</a>. p?>";
}
my $amt = $POST{'amt'};
unless ($amt =~ /^(\d+)(\.(\d\d))?$/) {
return "<b>Error:</b> Invalid format.";
}
if ($amt < 5 || $amt > 25) {
return "<b>Error:</b> Amount must be between \$5.00 and \$25.00.";
}
if (LJ::Pay::bazaar_remove_balance($remote, $amt)) {
my $auth = LJ::make_auth_code(10);
$dbh->do("INSERT INTO coupon (auth, type, arg, rcptid, locked, payid) VALUES (?, 'dollaroff', ?, ?, '0', 0)",
undef, $auth, $amt, $remote->{'userid'});
die $dbh->errstr if $dbh->err;
my $id = $dbh->{'mysql_insertid'};
my $coupon = "$id-$auth";
$dbh->do("INSERT INTO bzrpayout (userid, date, amt, method, note) VALUES (?, NOW(), ?, 'coupon', ?)",
undef, $remote->{'userid'}, $amt, "coupon: $coupon");
return BML::redirect("gencoupon.bml?cpid=$id");
} else {
return "You don't have \$$amt in your balance. If you did a double submit, you can get your coupon code on your <a href='balance.bml'>payment history</a>.";
}
}
_code?>
<=body
page?>

View File

@@ -0,0 +1,43 @@
<?page
title=>The Bazaar
body<=
The bazaar isn't really used anymore because it was too tedious to
maintain. We'd like to fix it and bring it back, but fixing it would
require analyzing what went wrong.
<?_c
<?h1 What is The Bazaar? h1?>
<?p
The Bazaar is a system to foster LiveJournal.com development goals and improve communication amongst the people who are doing work on the site.
p?>
<?p
There are a lot of <a href="details.bml">details</a>, but the basic gist is that each month there is a virtual pot, with so much money in it. At the end of the month the pot is divided up between the different <a href="/site/contributors.bml">contributors</a>, based on weightings determined by the pot recipients of the previous month. The LiveJournal staff increases the pot amount as the month goes on when key goals are met and projects completed.
p?>
<?p
This system has the following benefits:<ul>
<li style='margin-top: 10px'>It gets money out to people who deserve it, without LiveJournal.com needing to hire and manage tons of people, who might have fluctuating amounts of free time, with school or other life commitments.</li>
<li style='margin-top: 10px'>It lets us assign rough dollar amounts to projects we'd like to see done, without having to figure out exactly what everything's worth. We just commit to increasing the pot by, say, $200 for some project. The community's votes may end up making it worth $150 or $300.</li>
<li style='margin-top: 10px'>It encourages communication. People have to let other people know what they're doing, and when they've finished it, to get acknowledged.</li>
<li style='margin-top: 10px'>It encourages projects getting done. If somebody disappears for a few weeks, it's likely somebody else will finish their project first. Maybe the second person did it from scratch so the first person gets nothing, or maybe the second person worked off the former's ideas/code, and the community awards the first person some fraction. In any case, it relieves the need for anybody to hassle people to finish projects.</li>
<li style='margin-top: 10px'>It encourages people to <a href="/site/contributors.bml">list their contributions</a>. Even if they choose to not work for money, getting acknowledged at least gives them voting power for the next month.</il>
</ul>
p?>
<?h1 Areas h1?>
<?choices
items<=
<?choice Status|status.bml|Current status: who has voting power, and what the pot's at choice?>
<?choice Vote|vote.bml|Weight contributions for this month. choice?>
<=items
itemsb<=
<?choice Details|details.bml|Details & rules choice?>
<?choice Balance|balance.bml|Your Balance choice?>
<=itemsb
choices?>
_c?>
<=body
page?>

View File

@@ -0,0 +1,112 @@
<?page
title=>Bazaar Status
body<=
<p>[&lt;&lt; <a href="./">Back</a>]</p>
<?_code
{
use strict;
use vars qw(%GET %POST);
my $dbh = LJ::get_db_writer();
my ($ret, $sth);
my $remote = LJ::get_remote($dbh);
my $bzid = $GET{'bzid'}+0;
my $bz;
unless ($bzid) {
$bz = $dbh->selectrow_hashref("SELECT * FROM bzrs WHERE open='1' ".
"ORDER BY datestart LIMIT 1");
return "No bazaar session is currently active." unless $bz;
} else {
$bz = $dbh->selectrow_hashref("SELECT * FROM bzrs WHERE bzid=?",
undef, $bzid);
return "Invalid bazaar ID" unless $bz;
}
$bzid = $bz->{'bzid'};
my (@pot, $pot);
$sth = $dbh->prepare("SELECT dateadd, amt, reason FROM bzrpot ".
"WHERE bzid=? ORDER BY dateadd");
$sth->execute($bzid);
while ($_ = $sth->fetchrow_hashref) {
push @pot, $_;
$pot += $_->{'amt'};
}
my $is_voter;
$sth = $dbh->prepare("SELECT u.user, u.name, v.weight FROM bzrvoter v, user u ".
"WHERE u.userid=v.userid AND v.bzid=? ".
"ORDER BY v.weight DESC");
$sth->execute($bzid);
my @voters;
while ($_ = $sth->fetchrow_hashref) {
push @voters, $_;
$_->{'_votelink'} = 1 if $bz->{'open'} && $remote &&
$remote->{'user'} eq $_->{'user'};
}
$ret .= "<table>\n";
$ret .= "<tr><td align='right'><b>Bazaar Session:</b></td><td><a href=\"status.bml?bzid=$bzid\">\#$bzid</a>: $bz->{'name'}</td></tr>\n";
$ret .= "<tr><td align='right'><b>Start Time:</b></td><td>$bz->{'datestart'}</td></tr>\n";
my $state = $bz->{'open'} ? "In progress" : "Completed";
$ret .= "<tr><td align='right'><b>State:</b></td><td>$state</td></tr>\n";
$ret .= "<tr valign='top'><td align='right'><b>Pot:</b></td><td><span style='font-size: 15pt; font-weight: bold; font-family: arial,helvetica; color: #008000; background-color: #80ff80'>\$" . sprintf("%0.02f", $pot) . "</span>";
if (@pot > 1) {
$ret .= "<p><b>Details:</b><table>";
foreach (@pot) {
my $reason = $_->{'reason'};
if ($LJ::ZILLA_ROOT) {
$reason =~ s/bug (\d+)/<a href="$LJ::ZILLA_ROOT\/$1">$&<\/a>/gi;
}
$ret .= sprintf("<tr><td>%s</td><td align='right'><b>\$%0.02f</b></td><td>%s</td></tr>\n",
substr($_->{'dateadd'}, 0, 10),
$_->{'amt'}, $reason);
}
$ret .= "</table>";
}
$ret .= "</td></tr>\n";
$ret .= "</table>\n";
$ret .= "<?h1 Voters h1?><?p The following users have voting power for this Bazaar session, based on their contributions in the previous month. Some users below are employees who have only earned voting rights, and not money from last month's pot. View <a href='details.bml'>the details</a> to find out how the system works. p?>";
$ret .= "<div style='margin-left: 30px'><table>";
$ret .= "<tr><td align='left'><b>User</b></td><td align='right' width='100'><b>Weight</b></td><td></td></tr>\n";
foreach my $v (@voters) {
my $name = LJ::eall($v->{'name'});
$ret .= "<tr><td>" . LJ::ljuser($v->{'user'}) . " - $name</td><td align='right'>" .
sprintf("%0.02f%%", $v->{'weight'}*100) . "</td>";
$ret .= "<td>";
if ($v->{'_votelink'}) {
$ret .= "[<a href='vote.bml'>Vote</a>]";
}
$ret .= "</td></tr>\n";
}
$ret .= "</table></div>";
$ret .= "<?h1 Recognized Contributors & Contributions h1?><?p This month's voters have so far recognized the following contributions, sorted by contribution date. p?>";
$ret .= "<div style='margin-left: 30px'>";
$sth = $dbh->prepare("SELECT DISTINCT u.user, c.coid, c.cat, c.des, c.url, c.dateadd ".
"FROM contributed c, useridmap u, bzrvote v, bzrvoter vr ".
"WHERE u.userid=c.userid AND v.bzid=? AND vr.bzid=v.bzid ".
"AND vr.userid=v.userid AND v.coid=c.coid AND v.weight > 0 ".
"ORDER BY c.dateadd");
$sth->execute($bzid);
while (my $c = $sth->fetchrow_hashref) {
my $des = LJ::eall($c->{'des'});
$des = "<a href='$c->{'url'}'>$des</a>" if $c->{'url'};
$ret .= "<p>[<a href='/site/contributors.bml?mode=detail&coid=$c->{'coid'}'>$c->{'coid'}</a>] ";
$ret .= LJ::ljuser($c->{'user'}) . ": $des ($c->{'cat'}; $c->{'dateadd'})</p>\n";
}
$ret .= "</div>";
return $ret;
}
_code?>
<=body
page?>

View File

@@ -0,0 +1,154 @@
<?page
title=>Bazaar Voting
body<=
<p>[&lt;&lt; <a href="./">Back</a>]</p>
<?_code
{
use strict;
use vars qw(%GET %POST);
my $dbh = LJ::get_db_writer();
my ($ret, $sth);
my $remote = LJ::get_remote();
return "You must first <a href='/login.bml?ret=1'>login</a>." unless $remote;
my $bz = $dbh->selectrow_hashref("SELECT * FROM bzrs WHERE open='1' ".
"ORDER BY datestart LIMIT 1");
return "No bazaar session is currently active." unless $bz;
my $bzid = $bz->{'bzid'};
return "You aren't a voter for the <a href='status.bml?bzid=$bzid'>current bazaar</a> session."
unless $dbh->selectrow_array("SELECT weight FROM bzrvoter WHERE bzid=? AND userid=?",
undef, $bzid, $remote->{'userid'});
# load votes (if voter)
my %votes;
$sth = $dbh->prepare("SELECT u.user, v.coid, v.weight, c.cat, c.des, c.url, c.dateadd ".
"FROM bzrvote v, contributed c, useridmap u ".
"WHERE v.bzid=? AND v.userid=? AND v.coid=c.coid ".
"AND u.userid=c.userid");
$sth->execute($bzid, $remote->{'userid'});
while (my $v = $sth->fetchrow_hashref) {
$votes{$v->{'coid'}} = $v;
}
my @unvote;
$sth = $dbh->prepare("SELECT u.user, c.coid, c.cat, c.des, c.url, c.dateadd ".
"FROM contributed c, useridmap u ".
"WHERE u.userid=c.userid AND c.dateadd > ? ".
"AND c.userid <> ?");
$sth->execute($bz->{'datestart'}, $remote->{'userid'});
while (my $v = $sth->fetchrow_hashref) {
next if defined $votes{$v->{'coid'}};
push @unvote, $v;
}
if (LJ::did_post()) {
foreach my $id (keys %votes) {
next if $POST{"weight_$id"} eq "";
my $new = int($POST{"weight_$id"});
$new = 0 if $new < 0;
next if $POST{"weight_$id"} eq $votes{$id}->{'weight'};
$votes{$id}->{'weight'} = $new;
$dbh->do("REPLACE INTO bzrvote (bzid, userid, coid, weight) VALUES (?,?,?,?)",
undef, $bzid, $remote->{'userid'}, $id, $new);
}
foreach my $v (@unvote) {
my $id = $v->{'coid'};
next if $POST{"weight_$id"} eq "";
# delete contribution (so much stupid crap get submitted, like:
# "I gave my friend a code!")
if ($POST{"weight_$id"} eq "d") {
if (LJ::check_priv($dbh, $remote, "contrib_delete")) {
$dbh->do("DELETE FROM contributed WHERE coid=?", undef, $id);
$dbh->do("DELETE FROM contributedack WHERE coid=?", undef, $id);
}
$v->{'_deleted'} = 1;
next;
}
$votes{$id} = $v;
my $new = int($POST{"weight_$id"});
$new = 0 if $new < 0;
$votes{$id}->{'weight'} = $new;
$dbh->do("REPLACE INTO bzrvote (bzid, userid, coid, weight) VALUES (?,?,?,?)",
undef, $bzid, $remote->{'userid'}, $id, $new);
if ($new) {
$dbh->do("INSERT IGNORE INTO contributedack (coid, ackuserid) VALUES (?,?)",
undef, $v->{'coid'}, $remote->{'userid'});
}
}
# remove items that were just voted for
@unvote = grep { ! $votes{$_->{'coid'}} && ! $_->{'_deleted'} } @unvote;
if ($POST{'new_id'}) {
my $c = $dbh->selectrow_hashref("SELECT u.user, c.coid, c.cat, c.des, c.url, c.dateadd ".
"FROM contributed c, useridmap u WHERE c.coid=? ".
"AND c.dateadd > DATE_SUB(NOW(), INTERVAL 60 DAY) ".
"AND u.userid=c.userid",
undef, $POST{'new_id'});
return "Error: invalid contribution ID. Either does not exist, or is too old."
unless $c;
return "Error: can't vote for your own contributions"
if $c->{'user'} eq $remote->{'user'};
$c->{'weight'} = ($POST{'new_weight'}+0) || 1;
$dbh->do("REPLACE INTO bzrvote (bzid, userid, coid, weight) VALUES (?,?,?,?)",
undef, $bzid, $remote->{'userid'}, $c->{'coid'}, $c->{'weight'});
LJ::Contrib::ack($c->{'coid'}, $remote->{'userid'});
$votes{$c->{'coid'}} = $c;
}
}
$ret .= "<?h1 Your Votes h1?><?p As a voter in <a href='status.bml?bzid=$bzid'>this bazaar session</a>, you can add contributions you'd like to recognize here, and weight them all appropriately in regards to each other. p?>";
$ret .= "<form method='post' action='vote.bml' style='margin-left: 30px'>";
$ret .= "<table><tr><td width='250'><b>Contribution</b></td><td><b>Weight</b></td><td></td></tr>\n";
my $tw = 0;
foreach (values %votes) { $tw += $_->{'weight'}; }
my $row = sub {
my $v = shift;
my $des = LJ::eall($v->{'des'});
if ($v->{'url'}) {
$v->{'url'} = LJ::eall($v->{'url'});
$des = "<a href='$v->{'url'}'>$des</a>";
}
my $per = $v->{'weight'} ne "" ? sprintf("%0.02f%%", $v->{'weight'}*100/($tw||1)) : "";
$ret .= "<tr valign='top'><td>[<a href='/site/contributors.bml?mode=detail&coid=$v->{'coid'}'>$v->{'coid'}</a>] ";
$ret .= LJ::ljuser($v->{'user'}) . ": ";
$ret .= "$des<br />$v->{'cat'}, $v->{'dateadd'}</td><td><input name='weight_$v->{'coid'}' value='$v->{'weight'}' size='4'></td><td>$per</td></tr>";
};
# the ones that have been voted for, skipping zero weight
foreach my $v (sort { $b->{'weight'} <=> $a->{'weight'} } values %votes) {
next unless $v->{'weight'};
$row->($v);
}
$ret .= "<tr><td><i>New vote item:</i> <a href='/site/contributors.bml'>Contribution</a> ID: <input size='4' name='new_id'></td><td><input size='4' name='new_weight'></td><td></td></tr>\n";
$ret .= "<tr><td></td><td><input type='submit' value='Save'></td><td></td></tr>\n";
# unweighted contributions
if (@unvote) {
$ret .= "<tr><td colspan='2'><b>Contributions you haven't weighted:</b><br />(set to 0 to remove from this list)</td></tr>\n";
foreach my $v (@unvote) {
$row->($v);
}
$ret .= "<tr><td></td><td><input type='submit' value='Save'></td><td></td></tr>\n";
}
$ret .= "</table></form>";
return $ret;
}
_code?>
<=body
page?>

87
ljcom/htdocs/betatest.bml Normal file
View File

@@ -0,0 +1,87 @@
<?page
title=>Beta Test Page
body<=
<?_code
{
use strict;
use vars qw(%GET %POST);
my $betatext;
my $set;
my $capname;
my $remote = LJ::get_remote();
my $is_admin = $remote && LJ::check_rel(LJ::get_userid("lj_core"), $remote->{'userid'}, "P");
if (LJ::did_post()) {
my $u;
if ($POST{fb_on} || $POST{pb_off}) {
return "Not allowed." unless $is_admin;
$u = LJ::load_user($POST{fbbeta});
$betatext = "<p><font color='red'>No such user <i>$POST{fbbeta}</i>.</font></p>" unless $u;
$capname = '_fbbeta';
if ($u && $POST{fb_on}) {
$set = 1;
$betatext = "<p><font color='green'>" . LJ::ljuser($u) . " is now in the beta.</font>";
} elsif ($u && $POST{fb_off}) {
$set = 0;
$betatext = "<p><font color='green'>" . LJ::ljuser($u) . " is no longer in the beta.</font>";
}
} else {
$capname = '_betatest';
if ($POST{'no'}) {
BML::set_cookie("betatest", "", undef, $LJ::COOKIE_PATH, $LJ::COOKIE_DOMAIN);
$set = 0;
}
if ($POST{'yes'}) {
BML::set_cookie("betatest", "1", time()+60*60*24, $LJ::COOKIE_PATH, $LJ::COOKIE_DOMAIN);
$set = 1;
}
$u = $remote;
}
# now set any sort of cap needed
if (defined $set) {
return "No such user to set cap for." unless $u;
my @cap = grep { $LJ::CAP{$_}->{'_name'} eq $capname } keys %LJ::CAP;
if ($u && scalar @cap == 1) {
my $capnum = $cap[0]+0;
if ($set) {
LJ::update_user($u, { raw => "caps=caps|(1<<$capnum)" });
return "<a href='betatest.bml'>Beta-test mode</a> is now <b>ON</b>.";
} else {
LJ::update_user($u, { raw => "caps=caps&~(1<<$capnum)" });
return "<a href='betatest.bml'>Beta-test mode</a> is now off.";
}
}
}
}
my $ret = <<EOF;
<table width="100%"><tr><td valign='top'>
<?h1 Want to beta-test? h1?>
<form method='post'>
<p><input type='submit' name='yes' value='Yes' /> - Turn me on.
<p><input type='submit' name='no' value='No' /> - No, stop the pain!
</form>
</td><td valign='top'>
EOF
if ($is_admin) {
$ret .= <<EOF;
<?h1 FotoBilder Beta Test h1?>
<form method='post'>
<p>Username: <input type='text' name='fbbeta' value='' />
<p><input type='submit' name='fb_on' value='Turn On' /> <input type='submit' name='fb_off' value='Turn Off' />
$betatext
</form>
EOF
}
$ret .= "</td></tr></table>";
return $ret;
}
_code?>
<=body
page?>

View File

@@ -0,0 +1,28 @@
<?page
title=>LiveJournal Bot Policy
body<=
<?p All automated bots, spiders, crawlers, data-miners, etc that access information on <?sitename?> are subject to the following policy. If you have been redirected to this page it is because we believe you to be in control of an automated bot. If that is not the case, please contact us at <tt>webmaster@livejournal.com</tt>. p?>
<?h1 Data Formats h1?>
<?p We provide a variety of user data in standard XML formats, namely: p?>
<dl>
<dt><a href="http://blogs.law.harvard.edu/tech/rss">RSS:</a></dt><dd>A user's recent entries syndicated using the <em>Real Simple Syndication</em> XML format. Available at the URL http://www.livejournal.com/users/<var>username</var>/data/rss</dd>
<dt><a href="http://www.atomenabled.org/developers/syndication/atom-format-spec.php">Atom:</a></dt><dd>A user's recent entries syndicated using the <em>Atom</em> XML format. Available at the URL http://www.livejournal.com/users/<var>username</var>/data/atom</dd>
<dt><a href="http://xmlns.com/foaf/0.1/">FOAF:</a></dt><dd>A user's information page using the <em>Friend of a Friend</em> XML format. Available at the URL http://www.livejournal.com/users/<var>username</var>/data/foaf</dd>
<dt>Friend-Data</dt><dd>A line separated list of usernames which are friends or friends-of a user. Available at the URL http://www.livejournal.com/misc/fdata.bml?user=<var>username</var></dd>
<dt>Interest-Data</dt><dd>Interests in a line separated format. Available at the URL http://www.livejournal.com/misc/interestdata.bml?user=<var>username</var></dd>
</dl>
<?p You are encouraged to use these resources instead of "screen-scraping" user pages. p?>
<?h1 Rates &amp; Limits h1?>
<?p You are encouraged to cache the results of your bot's requests, which saves us bandwidth and CPU time. Bots making repeated requests on the same resource (URL) in a short amount of time will be blocked. Please do not multithread your bot to access multiple resources at the same time. p?>
<?h1 Well-Formed User Agents h1?>
<?p All bots are required to have a well-formed user agent which includes a contact email address for the bot maintainer,
and preferrably a URL to the organization running the bot. Bots without this information have a higher chance of being blocked. An example of a well-formed user agent is: p?><blockquote><tt>http://example.com/ljtoy.html; bob@example.com</tt></blockquote>
<?h1 Contact Information h1?>
<?p If you need specific information for research or data-collection purposes, feel free to contact us at <tt>webmaster@livejournal.com</tt>. If we've blocked your bot and you'd like to contact us about it, please email us at <tt>webmaster@livejournal.com</tt>. p?>
<=body
head<=
<style type="text/css">
dt { font-weight: bold; }
</style>
<=head
page?>

75
ljcom/htdocs/code/cache/html vendored Normal file
View File

@@ -0,0 +1,75 @@
<HTML>
<HEAD>
<TITLE>LJ::Cache - LRU Cache</TITLE>
<LINK REV="made" HREF="mailto:hackers@FreeBSD.org">
</HEAD>
<BODY>
<!-- INDEX BEGIN -->
<UL>
<LI><A HREF="#NAME">NAME</A>
<LI><A HREF="#SYNOPSIS">SYNOPSIS</A>
<LI><A HREF="#DESCRIPTION">DESCRIPTION</A>
<LI><A HREF="#AUTHOR">AUTHOR</A>
<LI><A HREF="#SEE_ALSO">SEE ALSO</A>
</UL>
<!-- INDEX END -->
<HR>
<P>
<H1><A NAME="NAME">NAME</A></H1>
<P>
LJ::Cache - LRU Cache
<P>
<HR>
<H1><A NAME="SYNOPSIS">SYNOPSIS</A></H1>
<P>
<PRE> use LJ::Cache;
my $cache = new LJ::Cache { 'maxsize' =&gt; 20 };
my $value = $cache-&gt;get($key);
unless (defined $value) {
$val = &quot;load some value&quot;;
$cache-&gt;set($key, $value);
}
</PRE>
<P>
<HR>
<H1><A NAME="DESCRIPTION">DESCRIPTION</A></H1>
<P>
This class implements an LRU dictionary cache. The two operations on it are
<CODE>get()</CODE> and <CODE>set(),</CODE> both of which promote the key
being referenced to the ``top'' of the cache, so it will stay alive
longest.
<P>
When the cache is full and and a new item needs to be added, the oldest one
is thrown away.
<P>
You should be able to regenerate the data at any time, if
<CODE>get()</CODE> returns undef.
<P>
This class is useful for caching information from a slower data source
while also keeping a bound on memory usage.
<P>
<HR>
<H1><A NAME="AUTHOR">AUTHOR</A></H1>
<P>
Brad Fitzpatrick, <A
HREF="mailto:bradfitz@bradfitz.com">bradfitz@bradfitz.com</A>
<P>
<HR>
<H1><A NAME="SEE_ALSO">SEE ALSO</A></H1>
<P>
<CODE>perl(1).</CODE>
</BODY>
</HTML>

25
ljcom/htdocs/code/cache/index.bml vendored Normal file
View File

@@ -0,0 +1,25 @@
<?page
title=>LJ::Cache
body<=
<a href="../"><B>&lt;&lt; Code</b></a> |
<b>Download:</b> <?dl code/cache/ dl?>
<?h1 License h1?>
<?p
LJ::Cache is licensed under the <a href="http://www.gnu.org/copyleft/library.txt">LGPL</a>, also available in the distribution.
p?>
<?_code
my $doc = `perldoc -u LJ::Cache | pod2html`;
$doc =~ s/<H1>(.+?)<\/H1>/<?h1 $1 h1?>/g;
$doc =~ s/<H2>(.+?)<\/H2>/<?h2 $1 h2?>/g;
$doc =~ s/<HR>/<?hr?>/g;
return $doc;
_code?>
<=body
page?>

View File

@@ -0,0 +1,18 @@
<?page
title=>Windows Client Source
body<=
<?h1 Win32 client source h1?>
<?p
You can download the source to the windows client application here:
<?dl code/clients/win32/ dl?>.
p?>
<?p
It is licensed under the <A HREF="http://www.gnu.org/copyleft/gpl.html">GPL</A>.
p?>
<?p
Send <?ljuser visions ljuser?> changes to include in the core distribution.
p?>
<=body
page?>

View File

@@ -0,0 +1,38 @@
<?page
title=>Code
body<=
<?_code
return LJ::set_active_crumb('code');
_code?>
<?h1 Open Source h1?>
<?p
Nearly all the source code to run the LiveJournal.com server is <a href="http://www.opensource.org/">Open Source</a> / <a href="http://www.gnu.org/philosophy/free-sw.html">Free Software</a> (depending which label you prefer), as are most the <a href="/download/">client apps</a>.
p?>
<?h1 Server Code h1?>
<?p
You can get the LiveJournal server code either from the <a href="http://www.livejournal.org/download/code/">latest snapshots</a> in *.tar.gz format, or <a href="http://cvs.livejournal.org/">from CVS</a>. The snapshots are taken pretty regularly, and CVS is almost always stable, so use whatever's easiest for you. Whatever you do, though, <a href="http://www.livejournal.com/doc/server/">read the documentation</a>. You can consult current issues and pending feature requests in <a href="http://zilla.livejournal.org/">our bug database</a>.
p?>
<?h1 Libraries h1?>
<?p
The server distribution above includes the following reusable components:
<p><table cellpadding=4 border=1>
<tr bgcolor=<?emcolor?>><td><b>Name</b></td></td><td><b>Description</b></td></tr>
<tr><td>LJ::Cache</td><td>Perl module to do caching of images and other data from the database, while keeping a bound on memory usage.</tr>
<tr><td>LJ::TextMessage</td><td>Perl module to send people text messages on their cellphones and pagers</td></tr>
<tr><td>LJ::SpellCheck</td><td>Perl module to check spelling, using ispell or aspell</td></tr>
<tr><td>HTML Cleaner</td><td>Removes JavaScript and other harmful markup from HTML.</td></tr>
<tr><td>BML</td><td>Server-side markup language and templating engine.</td></tr>
</table>
p?>
<=body
page?>

View File

@@ -0,0 +1,287 @@
#!/usr/bin/perl
# -*-perl-*-
#
# LiveJournal Sync Client
# (see http://www.livejournal.com/)
# (protocol information at http://www.livejournal.com/developer/)
#
# Brad Fitzpatrick
# bradfitz@bradfitz.com
#
# For this to work, make a ~/.livejournal.rc file like:
#
# user: username
# password: password
# syncdir: /path/to/syncdir/
#
# use_proxy: 1 (optional)
# proxy_host: my.proxy.com (optional)
# proxy_port: 81 (optional)
#
# And, if not using livejournal.com's servers:
#
# server_host: ...
# server_port: ...
# server_uri: ...
use strict;
my $VERSION = "0.1";
my $SERVER_HOST = "www.livejournal.com";
my $SERVER_PORT = 80;
my $SERVER_URI = "/cgi-bin/log.cgi";
##########################################################
use URI::Escape;
use LWP::UserAgent;
# load the ~/.livejournal.rc file
my %rc = ();
load_rc_file(\%rc);
$rc{'server_host'} ||= $SERVER_HOST;
$rc{'server_port'} ||= $SERVER_PORT;
$rc{'server_uri'} ||= $SERVER_URI;
unless ($rc{'user'}) {
die "Error: No username (user) specified in ~/.livejournal.rc\n";
}
unless ($rc{'password'}) {
die "Error: No password specified in ~/.livejournal.rc\n";
}
unless ($rc{'syncdir'}) {
die "Error: No sync directory specified in ~/.livejournal.rc\n";
}
unless (-d $rc{'syncdir'}) {
die "Sync dir does not exist ($rc{'syncdir'})\n";
}
unless (-w $rc{'syncdir'}) {
die "Sync dir is not writable ($rc{'syncdir'})\n";
}
print "Starting sync.\n";
my %last;
if (open (LAST, "$rc{'syncdir'}/lastsyncs.dat")) {
print "lastsync file opened.\n";
while (<LAST>) {
chomp;
if (/^(\w+):\s*(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)/) {
$last{$1} = $2;
}
}
close LAST;
} else {
print "no lastsync file found (first use?)\n";
}
my $ua = make_lj_agent();
my $get_sync_items = 1;
while ($get_sync_items)
{
print "Getting some sync items...\n";
print " last{L} = $last{'L'}\n";
my %ljres = lj_request($ua, {
"mode" => "syncitems",
"user" => $rc{'user'},
"password" => $rc{'password'},
"lastsync" => $last{'L'},
});
if ($ljres{'success'} eq "OK")
{
print "Got $ljres{'sync_count'} sync items of $ljres{'sync_total'} total.\n";
if ($ljres{'sync_count'} == $ljres{'sync_total'}) {
print "Done getting sync items!\n";
$get_sync_items = 0;
}
my %syncitem;
for (my $i=1; $i<=$ljres{'sync_count'}; $i++) {
next unless ($ljres{"sync_${i}_item"} =~ /^(.+?)-(\d+)/);
my $type = $1;
my $id = $2;
my $time = $ljres{"sync_${i}_time"};
$syncitem{$type}->{$id} = $time;
}
# currently there's only type "L" (journal entries), but
# this later could be "C"omment or "T"odo, etc..
foreach my $type (keys %syncitem)
{
print "Syncing type: $type\n";
# journal entries
if ($type eq "L") {
# rebuild journal entry here ($entry{$itemid}->{$key} = $val)
# before writing it to disk.
my %entry;
# keep track of the most recent journal entry saved,
# but only use in the case when there are no left to
# find the minimum from. (for sending "lastsync")
my $maxtime = "0000-00-00 00:00:00";
# we want to keep fetching more items until no more of
# this type exist (we'll delete the key after we write
# it to disk)
while (keys %{$syncitem{$type}})
{
print "Getting a batch of log entries...\n";
print " keys = ", scalar(keys %{$syncitem{$type}}), "\n";
print " lastsync = $last{'L'}\n";
my %sres = lj_request($ua, {
"mode" => "getevents",
"selecttype" => "syncitems",
"user" => $rc{'user'},
"password" => $rc{'password'},
"lastsync" => $last{'L'},
});
if ($sres{"success"} ne "OK") {
die "getevents failed: $sres{'errmsg'}\n";
}
# these next two loops reconstruct the journal entry
# from the response
for (my $i=1; $i<=$sres{'events_count'}; $i++) {
my $itemid = $sres{"events_${i}_itemid"};
$entry{$itemid} = {
'itemid' => $itemid,
'eventtime' => $sres{"events_${i}_eventtime"},
'event' => $sres{"events_${i}_event"},
'security' => $sres{"events_${i}_security"},
'allowmask' => $sres{"events_${i}_allowmask"},
};
}
for (my $i=1; $i<=$sres{'prop_count'}; $i++) {
my $itemid = $sres{"prop_${i}_itemid"};
my $prop = $sres{"prop_${i}_name"};
my $value = $sres{"prop_${i}_value"};
$entry{$itemid}->{"prop_$prop"} = $value;
}
# now, write each journal entry to disk, then erase
# its from the $syncitem{'L'} hash
print "Writing journal entries to disk...\n";
print "Wrote: ";
foreach my $itemid (sort { $a <=> $b } keys %entry)
{
if (open(E, ">$rc{'syncdir'}/$itemid.entry")) {
foreach (sort keys %{$entry{$itemid}}) {
print E "$_: $entry{$itemid}->{$_}\n";
}
close E;
print "$itemid, ";
# increment maxtime if this sync item was newer.
if ($syncitem{'L'}->{$itemid} gt $maxtime) {
$maxtime = $syncitem{'L'}->{$itemid};
}
delete $entry{$itemid};
delete $syncitem{'L'}->{$itemid};
} else {
die "Couldn't open $itemid.entry for write!\n";
}
}
print "\n";
# now that's stuff written to disk, we need to update
# the $last{'L'} time
print "Find new LastL...\n";
if (keys %{$syncitem{'L'}}) {
# find the earliest that isn't yet synced
my @times = sort values %{$syncitem{'L'}};
$last{'L'} = $times[0];
print "New LastL (keys) = $last{'L'}\n";
} else {
$last{'L'} = $maxtime; # FIXME: subtract a second
# in case two entries were on
# same second.
print "New LastL (maxtime) = $last{'L'}\n";
}
if (open (LAST, ">>$rc{'syncdir'}/lastsyncs.dat")) {
print LAST "L: $last{'L'}\n";
close LAST;
} else {
die "Couldn't append lastsyncs.dat file.\n";
}
}
}
}
}
else
{
die "Error getting sync items: $ljres{'errmsg'}\n";
}
}
print "DONE!\n";
sub load_rc_file
{
my $rcref = shift;
my $file = "$ENV{'HOME'}/.livejournal.rc";
return unless (-e $file);
open (RC, $file);
while (<RC>)
{
s/^\s+//;
s/\s+$//;
next unless /\S/;
my ($var, $val) = split(/\s*:\s*/, $_);
$rcref->{$var} = $val;
}
close RC;
}
sub make_lj_agent
{
my $ua = new LWP::UserAgent;
$ua->agent("PerlLiveJournalClient/$VERSION");
$ua->timeout(10);
return $ua;
}
sub lj_request
{
my $ua = shift;
my $vars = shift;
my %ljres = ();
# Create a request
my $req = new HTTP::Request POST => "http://$SERVER_HOST:$SERVER_PORT/$SERVER_URI";
$req->content_type('application/x-www-form-urlencoded');
$req->content(request_string($vars));
# Pass request to the user agent and get a response back
my $res = $ua->request($req);
# Check the outcome of the response
if ($res->is_success) {
%ljres = split(/\n/, $res->content);
} else {
$ljres{'success'} = "FAIL";
$ljres{'errmsg'} = "Client error: Error contacing server.";
}
return %ljres;
}
sub request_string
{
my ($vars) = shift;
my $req = "";
foreach (sort keys %{$vars})
{
my $val = uri_escape($vars->{$_},"\+\=\&");
$val =~ s/ /+/g;
$req .= "&" if $req;
$req .= "$_=$val";
}
return $req;
}

View File

@@ -0,0 +1,25 @@
<?page
title=>LJ::SpellCheck
body<=
<a href="../"><B>&lt;&lt; Code</b></a> |
<b>Download:</b> <?dl code/spellcheck/ dl?>
<?h1 License h1?>
<?p
LJ::SpellCheck is licensed under the <a href="http://www.gnu.org/copyleft/library.txt">LGPL</a>, also available in the distribution.
p?>
<?_code
my $doc = `perldoc -u LJ::SpellCheck | pod2html`;
$doc =~ s/<H1>(.+?)<\/H1>/<?h1 $1 h1?>/g;
$doc =~ s/<H2>(.+?)<\/H2>/<?h2 $1 h2?>/g;
$doc =~ s/<HR>/<?hr?>/g;
return $doc;
_code?>
<=body
page?>

View File

@@ -0,0 +1,25 @@
<?page
title=>LJ::TextMessage
body<=
<a href="../"><B>&lt;&lt; Code</b></a> |
<b>Download:</b> <?dl code/textmessage/ dl?>
<?h1 License h1?>
<?p
LJ::TextMessage is licensed under the <a href="http://www.gnu.org/copyleft/library.txt">LGPL</a>, also available in the distribution.
p?>
<?_code
my $doc = `perldoc -u LJ::TextMessage | pod2html`;
$doc =~ s/<H1>(.+?)<\/H1>/<?h1 $1 h1?>/g;
$doc =~ s/<H2>(.+?)<\/H2>/<?h2 $1 h2?>/g;
$doc =~ s/<HR>/<?hr?>/g;
return $doc;
_code?>
<=body
page?>

View File

@@ -0,0 +1,104 @@
<?page
title=>Comments Posted
body<=
<?_code
return "<B>Down for Maintenance</B><BR>This page used database queries that were horribly unoptimized and killed the rest of the site. This is currently being fixed on the LiveJournal development server, and when it's fixed, the main server will be updated and this page will be available again.";
my $dbh = LJ::get_db_writer();
my $ret = "";
my $user = lc($FORM{'user'});
my $quser = $dbh->quote($user);
my $month = $FORM{'month'};
my $year;
if ($month =~ /^(\d\d\d\d)-(\d\d)$/) {
$year = $1;
$month = $2;
} else {
my @nowtime = localtime();
$year = $nowtime[5]+1900;
$month = sprintf("%02d", $nowtime[4]+1);
}
my $qyear = $dbh->quote($year);
my $qmonth = $dbh->quote($month);
$sth = $dbh->prepare("SELECT * FROM user WHERE user=$quser");
$sth->execute;
my $u = $sth->fetchrow_hashref;
unless ($u) {
return "<?h1 Unknown user h1?><?p There doth not exist a user with the username <B>$user</B>. Sorry. p?>";
}
# get number of posts per month
$sth = $dbh->prepare("SELECT DATE_FORMAT(datepost, '%Y-%m') AS 'month', DATE_FORMAT(datepost, '%Y-%M') AS 'monthpretty', COUNT(*) AS 'count' FROM talk WHERE posterid=$u->{'userid'} GROUP BY 1, 2 ORDER BY 1");
$sth->execute;
%permonth = ();
while ($_ = $sth->fetchrow_hashref) {
$permonth{$_->{'month'}} = { format => $_->{'monthpretty'},
count => $_->{'count'} };
}
$ret .= "<?h1 Comments Posted in $year/$month h1?><?p Below are a list of comments left in journals by <A HREF=\"/userinfo.bml?user=$user\">$u->{'name'}</A>. You can also view other months this user has posted comments: p?>";
$ret .= "<FORM><INPUT TYPE=HIDDEN NAME=user VALUE=$user><CENTER>Month: <SELECT NAME=month>";
foreach (reverse sort keys %permonth) {
my $sel = $_ eq $FORM{'month'} ? "SELECTED" : "";
$ret .= "<OPTION VALUE=\"$_\" $sel>$permonth{$_}->{'format'}: $permonth{$_}->{'count'}\n";
}
$ret .= "</SELECT><INPUT TYPE=SUBMIT VALUE=\"View\"></CENTER><P>";
# load the comment numbers
$sth = $dbh->prepare("SELECT t.datepost, uj.user as 'userjournal', t.nodeid AS 'itemid', t.parenttalkid, tt.subject FROM talk t, talktext tt, user uj WHERE t.talkid=tt.talkid AND t.journalid=uj.userid AND t.posterid=$u->{'userid'} AND t.state<>'D' AND t.nodetype='L' AND YEAR(t.datepost)=$qyear AND MONTH(t.datepost)=$qmonth ORDER BY t.datepost");
$sth->execute;
if ($dbh->err) { return $dbh->errstr; }
@posts = ();
push @posts, $_ while ($_ = $sth->fetchrow_hashref);
$TRUNCATE_SIZE = 50;
# load the comments
my $itemid_in = join(", ", 0, map { $_->{'itemid'} } @posts);
$sth = $dbh->prepare("SELECT l.itemid, l.eventtime, LEFT(lt.event, $TRUNCATE_SIZE) AS 'eventtrunc', LENGTH(lt.event) as 'eventlength' FROM log l, logtext lt WHERE l.itemid IN ($itemid_in) AND l.itemid=lt.itemid");
$sth->execute;
if ($dbh->err) { return $dbh->errstr; }
my %item = ();
$item{$_->{'itemid'}} = $_ while ($_ = $sth->fetchrow_hashref);
$ret .= "<P><TABLE CELLPADDING=4 BORDER=1>\n";
$ret .= "<TR BGCOLOR=#E0E0E0><TD><B>Date</B></TD><TD><B>Entry</B></TD><TD NOWRAP><B>Subject</B></TD></TR>\n";
foreach my $p (@posts)
{
$id = $p->{'itemid'};
$date = $p->{'datepost'};
$date =~ s/ /<BR>/;
$ret .= "<TR><TD ALIGN=CENTER NOWRAP><FONT SIZE=-1>$date</FONT></TD>";
if ($p->{'parenttalkid'} == 0) {
$event = $item{$id}->{'eventtrunc'};
$event =~ s/\<[^\>]*$//;
LJ::CleanHTML::clean_event(\$event);
if ($item{$id}->{'eventlength'} > $TRUNCATE_LENGTH) { $event .= "..."; }
$ret .= "<TD><B><A HREF=\"/userinfo.bml?user=$p->{'userjournal'}\">$p->{'userjournal'}</A></B>: $event</TD>";
} else {
$ret .= "<TD><FONT SIZE=-2 COLOR=#C0C0C0>(reply to other comment)</FONT></TD>";
}
$p->{'subject'} ||= "(no subject)";
$ret .= "<TD><A HREF=\"/talkread.bml?itemid=$id\">$p->{'subject'}</A></TD></TR>\n";
}
$ret .= "</TABLE>\n";
return $ret;
_code?>
<=body
page?>

View File

@@ -0,0 +1,102 @@
<?page
title=>Comments Received
body<=
<?_code
return "<B>Down for Maintenance</B><BR>This page used database queries that were horribly unoptimized and killed the rest of the site. This is currently being fixed on the LiveJournal development server, and when it's fixed, the main server will be updated and this page will be available again.";
my $dbh = LJ::get_db_writer();
my $ret = "";
my $user = lc($FORM{'user'});
my $quser = $dbh->quote($user);
my $month = $FORM{'month'};
my $year;
if ($month =~ /^(\d\d\d\d)-(\d\d)$/) {
$year = $1;
$month = $2;
} else {
my @nowtime = localtime();
$year = $nowtime[5]+1900;
$month = sprintf("%02d", $nowtime[4]+1);
}
my $qyear = $dbh->quote($year);
my $qmonth = $dbh->quote($month);
$sth = $dbh->prepare("SELECT * FROM user WHERE user=$quser");
$sth->execute;
my $u = $sth->fetchrow_hashref;
unless ($u) {
return "<?h1 Unknown user h1?><?p There doth not exist a user with the username <B>$user</B>. Sorry. p?>";
}
# get number of posts per month
$sth = $dbh->prepare("SELECT DATE_FORMAT(datepost, '%Y-%m') AS 'month', DATE_FORMAT(datepost, '%Y-%M') AS 'monthpretty', COUNT(*) AS 'count' FROM talkpost WHERE userjournal=$quser GROUP BY 1, 2 ORDER BY 1");
$sth->execute;
%permonth = ();
while ($_ = $sth->fetchrow_hashref) {
$permonth{$_->{'month'}} = { format => $_->{'monthpretty'},
count => $_->{'count'} };
}
$ret .= "<?h1 Comments Received in $year/$month h1?><?p Below are a list of journal entries of <A HREF=\"/userinfo.bml?user=$user\">$u->{'name'}</A> which have been commented on by others, in reverse chronological order. You can also view other months this user has received comments: p?>";
$ret .= "<FORM><INPUT TYPE=HIDDEN NAME=user VALUE=$user><CENTER>Month: <SELECT NAME=month>";
foreach (reverse sort keys %permonth) {
my $sel = $_ eq $FORM{'month'} ? "SELECTED" : "";
$ret .= "<OPTION VALUE=\"$_\" $sel>$permonth{$_}->{'format'}\n";
}
$ret .= "</SELECT><INPUT TYPE=SUBMIT VALUE=\"View\"></CENTER><P>";
# load the comment numbers
$sth = $dbh->prepare("SELECT itemid, COUNT(*) AS 'count' FROM talkpost WHERE userjournal=$quser AND YEAR(datepost)=$qyear AND MONTH(datepost)=$qmonth GROUP BY 1 ORDER BY datepost DESC");
$sth->execute;
@items = ();
push @items, $_ while ($_ = $sth->fetchrow_hashref);
$TRUNCATE_SIZE = 100;
# load the comments
my $itemid_in = join(", ", 0, map { $_->{'itemid'} } @items);
$sth = $dbh->prepare("SELECT l.itemid, l.subject, l.eventtime, LEFT(l.event, $TRUNCATE_SIZE) AS 'eventtrunc', LENGTH(l.event) as 'eventlength' FROM log l LEFT JOIN friends f ON l.ownerid=f.userid AND f.friendid=$qremuid WHERE l.ownerid=$u->{'userid'} AND l.itemid IN ($itemid_in) AND ((l.security='public') OR (l.security='usemask' AND l.allowmask & f.groupmask) OR (l.ownerid=$qremuid))");
$sth->execute;
if ($dbh->err) { return "<B>I'm working on this work now, I know it's an error.....</B>: " . $dbh->errstr; }
my %item = ();
$item{$_->{'itemid'}} = $_ while ($_ = $sth->fetchrow_hashref);
$ret .= "<P><TABLE CELLPADDING=4 BORDER=1>\n";
$ret .= "<TR BGCOLOR=#E0E0E0><TD><B>Date</B></TD><TD><B>Comment</B></TD><TD NOWRAP><B># Comments</B></TD></TR>\n";
foreach my $i (@items)
{
$id = $i->{'itemid'};
next unless (defined $item{$id}->{'eventtime'});
$date = $item{$id}->{'eventtime'};
$date =~ s/ /<BR>/;
$ret .= "<TR><TD ALIGN=CENTER NOWRAP><FONT SIZE=-1>$date</FONT></TD><TD>";
$event = $item{$id}->{'eventtrunc'};
$event =~ s/\<[^\>]*$//;
LJ::CleanHTML::clean_event(\$event);
if ($item{$id}->{'eventlength'} > $TRUNCATE_LENGTH) { $event .= "..."; }
if ($item{$id}->{'subject'}) {
$ret .= "<B>$item{$id}->{'subject'}</B><BR>";
}
$ret .= "$event</TD>";
$ret .= "<TD ALIGN=CENTER><B><A HREF=\"/talkread.bml?itemid=$id\">$i->{'count'}</A></B></TD></TR>\n";
}
$ret .= "</TABLE>\n";
return $ret;
_code?>
<=body
page?>

View File

@@ -0,0 +1,14 @@
<?_code
if ($FORM{'mail'} == 1) {
LJ::send_mail({ 'to' => 'bradfitz@bradfitz.com',
'from' => 'lj_dontreply@livejournal.com',
'fromname' => 'Happy Happy Place',
'subject' => 'this is but a test',
'body' => 'this is all.'
});
return "mailed";
}
return "enter code.";
_code?>

View File

@@ -0,0 +1,287 @@
# -*-perl-*-
#
# Miscalleneous notes on S2, the 2nd major
# version of the LiveJournal style system.
#
# Brad Fitzpatrick <bradfitz@livejournal.com>
# 2001-02-03
#
###########
## goals
#
# 1. flexible/extensible:
# - layers
# - full programming language (*)
# - allows for internationalization
# - not specific to one view:
# - describe how to format objects
# - makes new views automatically supported,
# at least mostly.
# 2. safe:
# - (*) programming language has no unsafe
# looping/branching. only foreach
# on finite lists, ifs, etc.
# 3. fast:
# - precompiled
# 4. easy:
# - wizards/tools to generate each layer
# of S2
#
#
###########
## layers
#
#
# core (definitions, defaults)
# style - the "style"
# colors - tied to a style.
# i18n - not tied to a style
# custom - changing comment links, counters,
# page.insert_html shit.
#
# layers are parsed and compiled before use.
# compiled into native language of appserver
# this is perl now, but easy to change later:
# parser -> AST stays the same.
# just change AST -> pretty printer.
# S2 is not tied to perl, then. can move to
# something faster later... C, anybody?
# S2 parser will be written in java, and
# tools will be provided for users to run and
# test styles on their own machines
#
###########
## core layer
#
# only layer where classes can be declared.
set layertype "core";
set author "webmaster";
class image {
var string url,
var int w,
var int h,
method void print (), # allow trailing commas: so nice.
};
method image::print {
"""<img src="$url" width=$w height=$h border=0>""";
};
class comment_read {
var string urlread,
var int comment_count,
method void print (),
};
method comment_read::print ()
{
$noun = "Comments";
if ($comment_count ==1 ) {
$noun = "Comment";
}
"""<a href="$urlread">Read $comment_count $noun</a>""";
};
class comment_post {
var string urlpost,
method void print (),
};
method comment_post::print ()
{
"""<a href="$urlpost">Leave a comment!</a>""";
}
class comment_info {
var bool can_comment,
var comment_post post,
var comment_read read,
method void print (),
};
method comment_info::print ()
{
if ($can_comment == false) {
return;
}
if ($comment_count != 0) {
call $read print;
print " | ";
}
call $post print;
}
# ack! There's English in there! ^^^^^^^^^
#
# English will be default, everywhere, since it
# is anyway, right? Later we can have our
# master team of translators make new i18n layers
# that override these defaults. still, users
# can then override those i18n layers later, with
# their own words. users won't write in S2:
# we'll have stupid little wizards that ask them
# questions and generate/compile the S2 behind
# the scenes for them. for them, the process will
# be like:
#
# Pick Style: [ \/ ]
# Pick Language: [ \/ ]
#
# [ Continue--> ]
#
# Pick Style color theme: [ \/]
# or, enter your own:
#
# [ Finish ] [ More Customization -->]
#
# What do you want the comment links to say?
# ____________________
# How many items on a page at once? _______
# More stupid questions: _____________
class journal_entry {
var string event,
var string subject,
var datetime eventtime,
var string current_mood,
var string mood_image,
var string current_music,
var bool opt_nocomments,
var user userpost,
var user userjournal,
var image userpic,
var comment_info comments,
var string urlself,
var string head,
method void print,
};
method journal_entry::print {
print "<p><table><tr><td>\n";
"<b>"; call $eventtime print_long; "</b><br>";
print $event;
if ($comments.can_comment) {
"<p>";
call $comments print;
}
"</td></tr></table>\n";
};
class page {
var string view,
var bool remote_logged_in, # logged in user?
var bool remote_is_owner, # is it the journal owner?
var journal_entry[] entries, #
method void print,
};
method page::print
{
local string title;
if ($view == "lastn") {
}
"""<html><head><title>$title</title>$head""";
print "<body>\n";
print "</body></html>";
}
########
### style layer
##
set layertype "style";
set layername "Generator";
set author "evan";
var string colitemborder = "#00f033";
var string colpageback = "#a0a0a0";
########
### color layer
##
set layertype "colors";
set overlays "Generator"; # only for style layers
set author "bradfitz";
var string colitemborder = "#ff0000";
var string colpageback = "#00cc00";
########
### i18n layer
##
set layertype "i18n";
set layerlang "de";
set author "mausal";
method comment_read::print ()
{
$noun = "Kommentar"; # warning: probably wrong.
if ($comment_count ==1 ) {
$noun = "Kommentar"; # probably wrong.
}
"""<a href="$urlread">Lesen $comment_count $noun</a>""";
};
method comment_post::print ()
{
"""<a href="$urlpost">Schrieben eine Kommentar!</a>""";
}
######
### custom layer
##
## - auto-generated from a wizard
## ... or written by hand. (unlikely)
set layertype "custom";
set author "ibrad";
# ibrad-style popcorn links
method comment_read::print ()
{
$noun = "Kernels";
if ($comment_count ==1 ) {
$noun = "Kernel";
}
"""<a href="$urlread">$comment_count $noun.</a>""";
};
# ibrad-style popcorn links
method comment_post::print ()
{
"""<a href="$urlpost">Pop!</a>""";
}
########
### implementation
#
#
request in: /users/bradfitz/
make_journal("bradfitz", "lastn");
my %methods;
foreach $layers ("core", @user_layers) {
load_layer($layers,
}
$pageob = new S2::ob::page;
$pageob->setup_all_datastructures($dbh, $user, $view);
$journal = $methods{'page'}->{'print'}->($pageob);

View File

@@ -0,0 +1,80 @@
<?page
title=>Design Goals
body<=
<a href="./">&lt;&lt; Back</a>
<?h1 Design Goals h1?>
<?p
Here is a table summarizing the primary design goals of S2 and how they contrast with the existing style system, which I guess we can call S1:
p?>
<p><table border=0 cellpadding=6>
<tr bgcolor=<?emcolor?>><td><b>Feature</b></td><td><b>S1</b></td><td><b>S2</b></td></tr>
<tr valign=top>
<td><b>Flexible & Extensible</b></td>
<td>
The current style system forces users to define a template
for each new type of view: recent entries, friends page,
day view, calendar, etc. If we want to add a new view
type, users have to basically write their style.
</td>
<td>
In the new style system you describe the formatting of objects,
not the formatting of views. Thus, we can easily add new views
in the future, and making a "lastn" view pretty much makes ally
your other views automatically.
</td>
</tr>
<tr valign=top>
<td><b>Safe</b></td>
<td>
The current system is completely safe. It's so brain-dead
that it can't be anything <i>but</i> but safe. You have
to have some intelligence to be harmful.
</td>
<td>
The new style system is its own language that gets compiled into another language, and run on the webserver. Letting users run code on the server is inherently dangerous... it obviously has to be free of things "rm -rf /", but also, it has to be free from infinite loops, and anything that could consume large amounts of resources.
<p>The S2 language does not have while loops, gotos, or any other control construct that would permit an infinite loops. It only has <tt>if/else</tt> blocks, and <tt>foreach</tt> over finite lists. Also, you can only call methods and functions defined in the core layer, so users can't write functions <tt>foo</tt> and <tt>bar</tt> which simply call each other.
</td>
</tr>
<tr valign=top>
<td><b>Fast</b></td>
<td>
The current system is a CPU hog, doing tons of parsing, text munging and substitutions and run-time.
</td>
<td>
In the new system, S2 code will be parsed, checked, and compiled before the page is loaded. When a page is loaded in the future, the code will just be run, which will already be in the language that the LiveJournal server runs on. For LiveJournal.com, this will be Perl but in the future we could write a PHP or Java servlet backend to the S2 compiler.
</td>
</tr>
<tr valign=top>
<td><b>Internationalizablilty</b></td>
<td>
The current style system can support non-English languages and locales, but hardly:
<p>-- The server injects a lot of English into S1 variables, especially in dates.
<br>-- The calendar always begins weeks on Sunday.
<br>-- The system to make nouns plural (2 Comments, 3 Replies) is very English-specific.
<br>-- Porting a style to a new language involves forking the style and modifying away, losing all future changes to the original style.
</td>
<td>
The new style system is being design for internationalization. One of the S2 layers is an "i18n layer", which overrides English method definitions from the core and sets properties, like the day weeks start on.
</td>
</tr>
<tr valign=top>
<td><b>Easy</b></td>
<td>
Hahah ... the currently style system was never designed to be easy. It was designed for a few people (me, maybe a friend or two) to make styles, which we'd then make public. I never envisioned everybody using LiveJournal to begin with, much less trying to make styles.
</td>
<td>
Wizards and tools will generate S2 behind the scenes for most users. The hard-core users can write their styles and overlay layers in the raw S2 language.
</td>
</tr>
</table>
<=body
page?>

View File

@@ -0,0 +1,19 @@
<?page
title=>S2
body<=
<?h1 What is S2 h1?>
<?p
S2 is going to be LiveJournal's new style system language, used to control the format of journals. S2 is currently the planning and development stage. If you're interested in helping, contact <A href="/userinfo.bml?user=bradfitz">bradfitz</a> to join the <a href="/userinfo.bml?user=lj_dev">lj_dev</a> group.
p?>
<?h1 Documents h1?>
<?p
<ul>
<li><a href="goals.bml">Design Goals</a> -- with a comparison to the existing style language
<li><a href="2001-02-03.txt">2001-02-03.txt</a> -- misc notes, with some hints as to what S2's syntax may look like
</ul>
p?>
<=body
page?>

View File

@@ -0,0 +1,470 @@
<?_code
use strict;
use vars qw(%FORM $body $title);
$title = "Embedding LiveJournal";
$body = "";
my $ret = "";
# until this is in a database somewhere, here is the basic data structure:
# @methods =
# {
# 'group' => "short lowercase 'group of methods' name",
# 'title' => "full title",
# 'detail' => "optional extra description",
# 'methods' =>
# [
# 'name' => "optional method name",
# 'author' => "optional method author",
# 'text' => "mandatory paragraph",
# 'explanation' => "optional 'why this does such and such'",
# 'note' => "optional 'warning' or 'caution'",
# 'example' => "mandatory example",
# ]
# };
my $method_javascript =
{
'text' =>
"<?p The simplest way to embed your journal into your website is to just insert ".
"this JavaScript wherever in your webpage HTML that you want your journal to ".
"show up. In browsers where JavaScript is unavailable, a link to your journal can ".
"be shown instead. p?>",
'explanation' =>
"<?p This JavaScript fragment loads JavaScript from LiveJournal. ".
"The special thing to note here is the &amp;enc=js at the end of the customview.cgi arguments ".
'that tells LiveJournal to encode the page as one big <tt>document.write("....");</tt> JavaScript command. p?>',
'example' => <<"EOT",
&lt;script language="JavaScript"
src="http://www.livejournal.com/customview.cgi?username=[[username]]&amp;amp;styleid=101&amp;amp;enc=js"&gt;
&lt;noscript&gt;&lt;a href="http://[[username]].livejournal.com/"&gt;View my LiveJournal&lt;/a&gt;&lt;/noscript&gt;
&lt;/script&gt;
EOT
};
my $method_frameslayers =
{
'text' =>
"<?p One way to hide <tt>www.livejournal.com</tt> from the URL and make your journal ".
"look like part of your site is using frames... put the HTML page with the ".
"<tt>&lt;frameset&gt;</tt> on your own server, and then make one of the frames be ".
"your journal. This method will work with any frame-supporting browser, ".
"including Netscape and Internet Explorer. p?>".
"<?p Additionally, HTML-4.0 compliant browsers are supposed to let you embed a ".
"frame inline with your page, so it doesn't have to be on the far side. ".
"This is called an <code>&lt;iframe&gt;</code>. Internet Explorer supports this, as do ".
"Netscape 6, Mozilla and all recent Opera versions. Inline frames ".
"aren't as compatible as normal frames, and won't work in Netscape 4. ".
"The following example shows an <tt>iframe</tt>: p?>",
'note' =>
"<?p For more information on <code>&lt;iframe&gt;</code>, see ".
"<a href=\"http://www.w3.org/TR/REC-html40/present/frames.html\#h-16.5\">the HTML 4.0 spec</a>. p?>",
'example' => <<"EOT",
&lt;center&gt;
&lt;iframe name="livejournal"
src="http://www.livejournal.com/users/[[username]]/"
frameborder="0"
scrolling="auto" height="400" width="460"&gt;
&lt;a href="http://[[username]].livejournal.com/"&gt;View my LiveJournal&lt;/a&gt;
&lt;/iframe&gt;
&lt;/center&gt;
EOT
};
my $method_cgi_script =
{
'name' => "Basic CGI Script",
'text' =>
"<?p You can write a CGI script (a program that runs on your server) to download ".
"your journal and then spit it back out to your clients. You could write a CGI ".
"script like this in any language, but by far the easiest language would be Perl, mostly ".
"because just about every hosting provider supports it. p?>",
'explanation' =>
"<?p The client will never see <tt>www.livejournal.com</tt>, because your server is ".
"actually the one that's downloading it. p?>",
'note' =>
"<?p This example uses the <em>LibWWW</em> module for perl, which you may need to install on your server, ".
"or have your admin do it. p?>",
'example' =>
<<"EOT",
#!/usr/bin/perl
use LWP::Simple;
print "Content-type: text/html\\n\\n";
print get('http://www.livejournal.com/customview.cgi' .
'?username=[[username]]&styleid=101');
EOT
};
my $method_cgi_ssi =
{
'name' => "Server Side Includes and CGI",
'text' =>
"<?p If you already have some existing content that you don't want to modify and you ".
"just want your LiveJournal inserted into an existing HTML document on your server ".
"everytime a client requests it, you can create the CGI script in the previous example ".
"and then place the example below into a server-parsed HTML document. p?>",
'note' =>
"<?p To get an HTML file to be server parsed, you usually have to name it <tt>.shtml</tt>, ".
"or set its execute bit; it depends on your webserver and how it's configured. ".
"In order to figure that out you might need to talk to your sysadmin. p?>",
'example' =>
<<"EOT",
&lt;!--#exec cgi="/cgi-bin/livejournal.cgi" --&gt;
EOT
};
my $method_bml =
{
'text' =>
"<?p If you're using <a href=\"http://www.bradfitz.com/bml/\">BML</a> on your server, you need ".
"to do two things in your document. First, you need to set the NOCACHE flag (example included), so that the visitor's ".
"browser doesn't store old states of the page in cache. Then you simply need to add in the given ".
"_CODE block somewhere on your page. p?>\n".
"<?p Since BML evaluates markup blocks returned from code blocks, you can include BML markup in your ".
"embedding style in order to make your embedded journal fit in with your BML scheme. p?>",
'note' =>
"<?p This uses the <em>LibWWW</em> module for Perl, which you may need to install on your server, ".
"or have your admin do it. p?>",
'example' =>
"<?_info\nNOCACHE=>1\n_info?>\n\n".
BML::ehtml("<?_code\n\n use LWP::Simple;\n".
" return get('http://www.livejournal.com/customview.cgi' .\n".
" '?username=[[username]]&styleid=101');\n\n".
"_code?>"),
};
my $method_python =
{
'author' => " Jeremy Tribby",
'text'=>
"<?p Embedding your LiveJournal is easy with Python; just use the urllib class. p?>",
'example' => <<"EOT",
&lt;%
import urllib
u = urllib.open('http://www.livejournal.com/customview.cgi?username=[[username]]&styleid=101')
print 'Content-type: text/html\\n\\n'
print u.read()
%&gt;
EOT
};
my $method_flash =
{
'text' =>
"One of our users informed us that it is possible to have Flash download a list of ".
"variables to prefill into text elements in a Flash file. The formatting of these ".
"variable=value pairs are the same as in URLs. To accomodate this need, we provide ".
"a style which does this formatting for you. As an example, see the URL below.",
'example' =>
'http://www.livejournal.com/customview.cgi?username=[[username]]&styleid=103',
};
my $method_php_fpassthru =
{
'name' => "Using <code>fpassthru()</code>",
'author' => "<a href=\"http://www.whump.com/moreLikeThis/\">Bill Humpries</a>",
'text' => "<?p This method simply opens the journal URL, and then prints the content. p?>",
'explanation' =>
"<?p This method uses <tt>fopen()</tt> to open the journal URL, and then uses <tt>fpassthru()</tt> ".
"to pass the journal content to stdout. p?>",
'example' => <<"EOT",
&lt;?php
\$journalURL = "http://www.livejournal.com/".
"customview.cgi?username=[[username]]&styleid=101";
if (\$fh = fopen(\$journalURL,"r")) {
fpassthru(\$fh);
} else {
echo "&lt;p&gt;Unable to load journal.&lt;/p&gt;\\n";
}
?&gt;
EOT
};
my $method_php_fsockopen =
{
'name' => "Using <code>fsockopen()</code>",
'author' => "<a href=\"mailto:elliot\@rightnowtech.com\">Elliot Schlegelmilch</a>",
'text' => "<?p This method is slightly different, and may work even if URL fopen wrappers aren't enabled on your server. p?>",
'explanation' =>
"<?p This method uses <code>fsockopen()</code> to open a network socket to the journal site, and then uses the HTTP protocol to ".
"request the journal's content. Given that it doesn't fail, it will simply fetch each line of the server's response. p?>",
'example' => <<"EOT",
&lt;?php
\$fp = fsockopen("www.livejournal.com", 80, &\$errno, \&\$errstr, 30);
if(\$fp) {
fputs(\$fp,"GET /customview.cgi?".
"username=[[username]]&styleid=101 HTTP/1.0\\n\\n");
while(!feof(\$fp)) {
echo fgets(\$fp,128);
}
fclose(\$fp);
}
?&gt;
EOT
};
my $method_php_file =
{
'name' => "Using <code>file()</code>",
'author' => "<a href=\"mailto:jay\@fudge.org\">Jay Cuthrell</a>",
'text' => "This method is useful for those that want a line by line parse of their journal, with line number references.",
'explanation' =>
"<?p This method uses the <code>file()</code> function to read in the journal content as a large array, and then it prints ".
"it back out line by line. p?>",
'example' => <<"EOT",
&lt;?php
\$page = "http://www.livejournal.com/customview.cgi".
"?username=[[username]]&styleid=101";
\$content = file(\$page);
\$slurp = "";
while (list(\$foo,\$bar) = each(\$content)) {
\$slurp .= \$bar;
}
echo \$slurp;
?&gt;
EOT
};
my $method_php_include =
{
'name' => "Using <code>include()</code>",
'author' => "<a href=\"mailto:jon\@csh.rit.edu\">Jon Parise</a>",
'text' => "<?p This simply includes the journal page inside of the PHP page. p?>",
'note' => "<?p This requires you have URL fopen wrappers enabled (they're on by default in PHP 4). p?>",
'example' => <<"EOT",
&lt;?php
include "http://www.livejournal.com/customview.cgi".
"?username=[[username]]&styleid=101";
?&gt;
EOT
};
my $method_asp_xmlhttp =
{
'name' => "Using the <tt>Microsoft.XMLHTTP</tt> component",
'author' => "<a href=\"mailto:Pavel.Titov\@mtu-net.ru\">Pavel Titov</a>",
'text' => "<?p This is an easy way to embed your journal using the <tt>Microsoft.XMLHTTP</tt> component and IIS. p?>",
'example' => <<"EOT",
&lt;%
Response.Buffer = True
Dim xml
' Create an xmlhttp object:
Set xml = Server.CreateObject("Microsoft.XMLHTTP")
' Or, for version 3.0 of XMLHTTP, use:
' Set xml = Server.CreateObject("MSXML2.ServerXMLHTTP")
' Opens the connection to the remote server.
xml.Open "GET",
"http://www.livejournal.com/customview.cgi?username=[[username]]&styleid=101",
False
' Actually Sends the request and returns the data:
xml.Send
Response.Write xml.responseText
Set xml = Nothing
%&gt;
EOT
};
my $method_asp_perlscript =
{
'name' => "Using PerlScript",
'author' => "<a href=\"mailto:ansley\@net-impact.net\">Ansley Ingram</a>",
'text' => "<?p Here is how to embed your LiveJournal on your site using ASP and PerlScript. p?>",
'note' =>
"<?p Unfortuately, many WinNT hosting providers don't offer PerlScript. ".
"PerlScript is included in the <a href=\"http://www.activestate.com/ActivePerl/\">ActivePerl</a> ".
"installation for NT from ActiveState. p?>",
'example' => <<"EOT",
&lt;%\@language=perlscript%&gt;
&lt;%
use LWP::Simple;
\$Response->Write(get 'http://www.livejournal.com/customview.cgi?' .
'username=[[username]]&styleid=101');
%&gt;
EOT
};
my $method_aolserver =
{
'author' => "<?ljuser jackal ljuser?>",
'text' => "<?p You can embed your LiveJournal on your site using AOLServer's .adp pages. p?>",
'note' =>
"<?p This is <strong>not</strong> for people using AOL's Internet Access service; ".
"it is for people using the AOLServer web server. p?>",
'example' => <<"EOT",
&lt;%
ns_puts "[ns_geturl http://www.livejournal.com/customview.cgi?username=[[username]]&styleid=101 ]"
%&gt;
EOT
};
my $method_coldfusion =
{
'author' => "<?ljuser mrh ljuser?>",
'text' => "<?p Cold Fusion users can embed their journals as follows. p?>",
'example' => <<"EOT",
&lt;CFHTTP
URL="http://www.livejournal.com/customview.cgi?username=[[username]]&styleid=101"
METHOD="GET"&gt;
&lt;/CFHTTP&gt;
&lt;CFOUTPUT&gt;\#cfhttp.filecontent\#&lt;/CFOUTPUT&gt;
EOT
};
my @methods =
(
{'group' => "js",
'title' => "JavaScript",
'detail' => "The easiest way to embed",
'methods' => [$method_javascript],
},
{'group' => "frameslayers",
'title' => "HTML Frames",
'detail' => "The next easiest way",
'methods' => [$method_frameslayers],
},
{'group' => "cgi",
'title' => "CGI Scripts",
'detail' => "Easy server side programming",
'methods' => [$method_cgi_script, $method_cgi_ssi,],
},
{'group' => "bml",
'title' => "Better Markup Language",
'detail' => "Using the language of LiveJournal",
'methods' => [$method_bml],
},
{'group' => "flash",
'title' => "Macromedia Flash",
'methods' => [$method_flash],
},
{'group' => "php",
'title' => "PHP",
'methods' => [$method_php_fpassthru, $method_php_fsockopen, $method_php_file, $method_php_include],
},
{'group' => "python",
'title' => "Python",
'methods' => [$method_python],
},
{'group' => "asp",
'title' => "Active Server Pages",
'methods' => [$method_asp_xmlhttp, $method_asp_perlscript],
},
{'group' => "coldfusion",
'title' => "Cold Fusion",
'methods' => [$method_coldfusion],
},
{'group' => "adp",
'title' => "AOLServer .adp pages",
'methods' => [$method_aolserver],
},
);
my $selected_method_group = $FORM{'method'};
my $valid_selection = (defined $selected_method_group and
($selected_method_group eq "all" or
grep { $selected_method_group eq $_->{'group'} } @methods));
unless ($valid_selection)
{
$ret .= <<"EOT";
<?h1 Introduction h1?>
<?p Not everyone likes to link to LiveJournal.com to show their LiveJournal;
a lot of people prefer to embed it directly into their own home page. p?>
<?p However, doing so isn't always easy. There are a lot of differences
between servers, and outside of these basic instructions below,
we don't have much time to help everybody configure their own servers correctly.
Even if you don't have control over your server, there are still some
HTML-only ways to do it, although less transparently and with other additional
caveats. p?>
<?p Note that embedding is a <a href="/paidaccounts/">paid account</a> feature,
and so these instructions will not work for users with free accounts. p?>
Listed below are some of the different ways that you can embed your journal.
<ul>
EOT
foreach my $method (@methods) {
my $group = LJ::eurl($method->{'group'});
$ret .= "<li><a href=\"embedding.bml?method=$group\"><b>$method->{'title'}</b></a>";
if (defined $method->{'detail'}) {
$ret .= " - $method->{detail}\n";
}
}
$ret .= <<"EOT";
</ul>
<?p
Optionally, you can <a href="embedding.bml?method=all">view all</a> of the methods on
one page.
p?>
<?hr?>
<a href="./">&lt;&lt; Back to the developer area</a>
EOT
$body = $ret;
return;
}
my $remote = LJ::get_remote_noauth();
my $ru=$remote->{'user'} || "<var>username</var>";
my $display_method = sub
{
my $method = shift;
my $heading = (shift() ? "H1" : "H2");
next if not defined $method->{'methods'};
foreach $method (@{$method->{'methods'}}) {
if (defined $method->{'name'}) {
$ret .="<?$heading $method->{'name'} $heading?>\n";
}
if (defined $method->{'author'}) {
$ret .="<?p <span style=\"font-weight: bold;\">Contributed by:</span> ";
$ret .= $method->{'author'} . " p?>";
}
$ret .= "$method->{text}\n";
if (defined $method->{'explanation'}) {
$ret .= "<?h2 Explanation h2?>$method->{'explanation'}\n";
}
if (defined $method->{'note'}) {
$ret .= "<?h2 Note h2?>$method->{'note'}\n";
}
my $example=$method->{'example'};
$example=~s/\[\[username\]\]/$ru/gs;
$ret .= '<pre style="color: #0000ff">';
$ret .= $example;
$ret .= '</pre>';
}
};
if ($selected_method_group eq "all") {
$title .= " - All Methods";
foreach my $method (@methods) {
$ret .= "<?h1 $method->{'title'} h1?>\n";
$display_method->($method);
$ret .= "<?hr?>";
}
} else {
foreach my $method (@methods) {
if ($method->{'group'} eq $selected_method_group) {
$title .= " - $method->{'title'}";
# $ret .= "<?h1 $method->{'title'} h1?>\n";
$display_method->($method,1);
$ret .= "<?hr?>";
}
}
}
$ret .= '<a href="./embedding.bml">&lt;&lt; Back to the embedding page</a>';
$body = $ret;
return;
_code?>
<?page
title=><?_code return $title _code?>
body=><?_code return $body _code?>
page?>

View File

@@ -0,0 +1,155 @@
<?page
title=>Exporting Comments
body<=
<?p LiveJournal provides an interface for exporting comments using an XML format that makes it easy
for people to write utilities to use the information. A user is allowed to download comments for
any journal they administrate. p?>
<?p Please read the <a href="/bots/">LiveJournal Bot Policy</a> page, which discusses more general
rules on how to download information from our servers without getting yourself banned. Also please
follow the directions contained in this guide. p?>
<?p In order to use the comment exporter, you will need to have a valid session cookie. This can
be obtained with the <tt>sessiongenerate</tt> protocol mode or by posting login information to the
login.bml page. p?>
<?h2 Comment Data Summary h2?>
<table border="1">
<tr><th>Element</th><th>Attribute</th><th>Mode</th><th>Mutable</th><th>Description</th></tr>
<?_code
{
my @elements = (
[ 'maxid', '', 'meta', 'yes', 'This element gives you an integer value of the maximum comment id currently available in the user\'s journal. This is the endpoint, inclusive.' ],
[ 'comment', 'id', 'meta, body', 'no', 'The id of this particular comment.' ],
[ 'comment', 'posterid', 'meta, body', 'yes', 'The id of the poster of this comment. This can only change from 0 (anonymous) to some non-zero number. It will never go the other way, nor will it change from some non-zero number to another non-zero number. Anonymous (0) is the default if no posterid is supplied.' ],
[ 'comment', 'state', 'meta, body', 'yes', 'S = screened comment, D = deleted comment, A = active (visible) comment. If the state is not explicitly defined, it is assumed to be A.' ],
[ 'comment', 'jitemid', 'body', 'no', 'Journal itemid this comment was posted in.' ],
[ 'comment', 'parentid', 'body', 'no', '0 if this comment is top-level, else, it is the id of the comment this one was posted in response to. Top-level (0) is the default if no parentid is supplied.' ],
[ 'usermap', 'id', 'meta', 'no', 'Poster id part of pair.' ],
[ 'usermap', 'user', 'meta', 'yes', 'Username part of poster id + user pair. This can change if a user renames.' ],
[ 'body', '', 'body', 'no', 'The text of the comment.' ],
[ 'subject', '', 'body', 'no', 'The subject of the comment. This may not be present with every comment.' ],
[ 'date', '', 'body', 'no', 'The time this comment was posted at. This is in the <a href="http://www.w3.org/TR/NOTE-datetime">W3C Date and Time</a> format.' ],
[ 'property', '', 'body', 'no', 'The property tag has one attribute, name, that indicates the name of this property. The content of the tag is the value of that property.' ],
);
my $ret = '';
foreach my $r (@elements) {
$ret .= "<tr>\n";
$ret .= "<td>$_</td>\n" foreach @$r;
$ret .= "</tr>\n";
}
return $ret;
}
_code?>
</table>
<?h2 Fetching Metadata h2?>
<?standout
<span style="color: red;">NOTE:</span> Please cache metadata, but note that it does contain things that
can change about a comment. You should follow these instructions to update your cache once in a while.
standout?>
<?p Comment metadata includes only information that is subject to change on a comment. It
is a lightweight call that returns a small XML file that provides basic information on each comment
posted in a journal. Step 1 of any export should look like this: p?>
<?p <pre> GET /export_comments.bml?get=comment_meta&startid=0</pre> p?>
<?p After you have made the above request, you will get back a response that looks something like this: p?>
<?p <pre>
&lt;?xml version="1.0" encoding='utf-8'?&gt;
&lt;livejournal&gt;
&lt;maxid&gt;100&lt;/maxid&gt;
&lt;comments&gt;
&lt;comment id='71' posterid='3' state='D' /&gt;
&lt;comment id='70' state='D' /&gt;
&lt;comment id='99' /&gt;
&lt;comment id='100' posterid='3' /&gt;
&lt;comment id='92' state='D' /&gt;
&lt;comment id='69' posterid='3' state='S' /&gt;
&lt;comment id='98' posterid='3' /&gt;
&lt;comment id='73' state='D' /&gt;
&lt;comment id='86' state='S' /&gt;
&lt;/comments&gt;
&lt;usermaps&gt;
&lt;usermap id='6' user='test2' /&gt;
&lt;usermap id='3' user='test' /&gt;
&lt;usermap id='2' user='xb95' /&gt;
&lt;/usermaps&gt;
&lt;/livejournal&gt;</pre>
p?>
<?p The first part is the actual comment metadata. Each row will contain the mutable information
about a single comment. After this data is the list of users and their ids. These mappings will never change,
so feel free to completely cache these. p?>
<?p You should also notice the maxid line. This shows you the maximum comment id that is in this user's
journal. You should use this number to determine if you are done downloading or not. So, in pseudocode,
you should use something like this to get metadata: p?>
<?p <pre>
sub gather_metadata
get largest comment id known about from my cache
GET /export_comments.bml?get=comment_meta&startid=<i>maxid+1</i>
add results to metadata cache
if maximum id returned is less than maxid returned, call gather_metadata again
end sub
</pre> p?>
<?h2 Downloading the Comments h2?>
<?standout
<span style="color: red;">WARNING:</span> Comment body data is to be <b>heavily cached</b>. None of
this data can change. Once you have downloaded a comment, you do not need to do so again.
standout?>
<?p Once you have the entire list of metadata, you can begin downloading comments. The steps you will
use are much the same as for getting metadata. Again, here is some pseudocode: p?>
<?p <pre>
sub download_comments
get largest comment id we have fully downloaded
GET /export_comments.bml?get=comment_body&startid=<i>maxid+1</i>
add results to comment cache
if maximum id returned is less than maxid in metadata cache, call download_comments again
if nothing was returned, and startid+1000 < maxid from metadata, call download_comments again
end sub
</pre> p?>
<?p The resulting format each time you hit export_comments.bml will look like this: p?>
<?p <pre>
&lt;?xml version="1.0" encoding='utf-8'?&gt;
&lt;livejournal&gt;
&lt;comments&gt;
&lt;comment id='68' posterid='3' state='S' jitemid='34'&gt;
&lt;body&gt;we should all comment all day&lt;/body&gt;
&lt;date&gt;2004-03-02T18:14:06Z&lt;/date&gt;
&lt;/comment&gt;
&lt;comment id='69' posterid='3' state='S' jitemid='34'&gt;
&lt;body&gt;commenting is fun&lt;/body&gt;
&lt;date&gt;2004-03-02T18:16:08Z&lt;/date&gt;
&lt;/comment&gt;
&lt;comment id='99' jitemid='43' parentid='98'&gt;
&lt;body&gt;anonynote!&lt;/body&gt;
&lt;date&gt;2004-03-16T19:06:31Z&lt;/date&gt;
&lt;property name='poster_ip'&gt;127.0.0.1&lt;/property&gt;
&lt;/comment&gt;
&lt;comment id='100' posterid='3' jitemid='43' parentid='98'&gt;
&lt;subject&gt;subject!#@?&lt;/subject&gt;
&lt;body&gt;&amp;lt;b&amp;gt;BOLD!&amp;lt;/b&amp;gt;&lt;/body&gt;
&lt;date&gt;2004-03-16T19:19:16Z&lt;/date&gt;
&lt;/comment&gt;
&lt;/comments&gt;
&lt;/livejournal&gt;
</pre> p?>
<?p That concludes this brief tutorial on exporting comment data in an appropriate manner
so as not to be overly hard on the LiveJournal servers. Thanks for your cooperation, and
don't forget to read the <a href="/bots/">Bot Policy</a> page. p?>
<=body
page?>

View File

@@ -0,0 +1,52 @@
<?page
title=><?_ml .title _ml?>
body<=
<?h1 <?_ml .notice.header _ml?> h1?>
<?p <?_ml .notice1 _ml?> p?>
<?p <?_ml .notice2 _ml?> p?>
<?h1 <?_ml .code.header _ml?> h1?>
<?p <?_ml .code _ml?> p?>
<?h1 <?_ml .dbschema.header _ml?> h1?>
<?p <?_ml .dbschema _ml?> p?>
<?h1 <?_ml .styles.header _ml?> h1?>
<?p <?_ml .styles _ml?> p?>
<?h2 <?_ml .styles.s1.header _ml?> h2?>
<dl>
<dt><a href="styles.bml"><?_ml .styles.s1.system _ml?></a></dt>
<dd><?_ml .styles.s1.system.about _ml?></dd>
<dt><a href="views.bml"><?_ml .styles.s1.views _ml?></a></dt>
<dd><?_ml .styles.s1.views.about _ml?></dd>
<dt><a href="varlist.bml"><?_ml .styles.s1.varlist _ml?></a></dt>
<dd><?_ml .styles.s1.varlist.about _ml?></dd>
</dl>
<?h2 <?_ml .styles.s2.header _ml?> h2?>
<dl>
<dt><a href="/doc/s2/"><?_ml .styles.s2.manual _ml?></a></dt>
<dd><?_ml .styles.s2.manual.about _ml?></dd>
<dt><a href="/customize/advanced/layerbrowse.bml"><?_ml .styles.s2.layerbrowse _ml?></a></dt>
<dd><?_ml .styles.s2.layerbrowse.about _ml?></dd>
</dl>
<?h1 <?_ml .embedding.header _ml?> h1?>
<?p <?_ml .embedding _ml?> p?>
<?h1 <?_ml .clients.header _ml?> h1?>
<?p <?_ml .clients _ml?> p?>
<?_ml .clients.links _ml?>
<?h1 Exporting Comments h1?>
<?p Exporting comments made on a journal or in a community can be accomplished with the comment
exporting tool. Please see our <a href="exporting.bml">comment exporting tutorial</a> for more
information. p?>
<=body
head<=
<style type="text/css">
dt { font-weight: bold; }
</style>
<=head
page?>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -0,0 +1,67 @@
<?_info
nocache=>1
_info?><?page
title=><?_code $v = LJ::ehtml(BML::get_query_string()); return "Mode: $v"; _code?>
body<=
<?h1 Mode Details... h1?>
<?p
The following are details on the <?_code return $v; _code?> request mode.
p?>
<?_code
my @vars;
LJ::load_objects_from_file("protocol.dat", \@vars);
my $ret = "";
foreach my $v (grep { $_->{'name'} eq $v } @vars)
{
my $mode = $v->{'name'};
$ret .= "<p><table width='100%' bgcolor='#C0C0C0'><tr><td><font size='+1'><b>$mode</b></font></td></tr></table>\n\n";
$ret .= "<table cellspacing='2'>\n";
$ret .= "<tr valign='top'><td align='left' colspan='2' bgcolor='#e0e0e0'><i>Description</i></td></tr>\n";
$ret .= "<tr><td colspan='2'>$v->{'props'}->{'des'}</td></tr>\n";
unshift (@{$v->{'props'}->{'request'}},
{ 'name' => "mode", 'props' => { 'des' => "The protocol request mode: <b><tt>$mode</tt></b>", } },
{ 'name' => "user", 'props' => { 'des' => "Username. Leading and trailing whitespace is ignored, as is case.", } },
{ 'name' => "password", 'props' => { 'des' => "Password in plain-text. Either this needs to be sent, or <tt>hpassword</tt>.", } },
{ 'name' => "hpassword", 'props' => { 'des' => "Alternative to plain-text <tt>password</tt>. Password as an MD5 hex digest. Not perfectly secure, but defeats the most simple of network sniffers.", } },
{ 'name' => "ver", 'props' => { 'des' => "Protocol version supported by the client; assumed to be 0 if not specified. See <a href='versions.bml'>this document</a> for details on the protocol version.", 'optional' => 1, } },
);
unshift (@{$v->{'props'}->{'response'}},
{ 'name' => "success", 'props' => { 'des' => "<b><tt>OK</tt></b> on success or <b><tt>FAIL</tt></b> when there's an error. When there's an error, see <tt>errmsg</tt> for the error text. The absence of this variable should also be considered an error.", } },
{ 'name' => "errmsg", 'props' => { 'des' => "The error message if <tt>success</tt> was <tt>FAIL</tt>, not present if <tt>OK</tt>. If the success variable isn't present, this variable most likely won't be either (in the case of a server error), and clients should just report \"Server Error, try again later.\".", } },
);
foreach my $rr (qw(request response))
{
$ret .= "<tr valign='top'><td align='left' colspan='2' bgcolor='#e0e0e0'><i>" . ucfirst($rr) . "</i></td></tr>\n";
foreach (@{$v->{'props'}->{$rr}})
{
my $des = $_->{'props'}->{'des'};
$des =~ s!\[special\[logprops\]\]!<a href="/doc/server/ljp.csp.proplist.html">logprops</a>!;
$ret .= "<tr valign='top'><td align='left' nowrap='nowrap' bgcolor='#f0f0f0'><b>$_->{'name'}</b></td>";
$ret .= "<td>";
if ($_->{'props'}->{'optional'}) {
$ret .= "<i>(Optional)</i> ";
}
$ret .= $des;
$ret .= "</td>";
$ret .= "</tr>\n";
}
}
$ret .= "</table>\n";
}
return $ret;
_code?>
<?hr?>
Back to <a href="modelist.bml">Protocol Modes</a>.<br/>
Back to <a href="protocol.bml">Protocol Documentation</a>.<br/>
Back to <a href="/developer/">Developer Area</a>.
<=body
page?>

View File

@@ -0,0 +1,37 @@
<?_info
nocache=>1
_info?><?page
title=>Protocol Mode List
body<=
<?h1 Introduction h1?>
<?p
The following information is a list of the different request modes you can make to the LiveJournal server using the <A HREF="protocol.bml">LiveJournal protocol</A>. Click one for more information, including request and response variables.
p?>
<?h1 Variables h1?>
<?p
Click a variable name for more information about how and where to use it.
<DL>
<?_code
my @vars;
LJ::load_objects_from_file("protocol.dat", \@vars);
my $ret = "";
foreach my $v (sort { $a->{'name'} cmp $b->{'name'} } @vars)
{
$ret .= "<DT><A HREF=\"modeinfo.bml?$v->{'name'}\"><B>$v->{'name'}</B></A>";
$ret .= "<DD>" . $v->{'props'}->{'des'} . "\n";
}
return $ret;
_code?>
</DL>
p?>
<?hr?>
Back to <A HREF="/developer/">Developer Area</A>.
<=body
page?>

105
ljcom/htdocs/developer/moods.txt Executable file
View File

@@ -0,0 +1,105 @@
###
### server-supported moods vs. custom moods
###
users will be able to use either server-supported moods or their
own custom moods.
to use a server-supported mood, clients will use metadata property:
current_moodid (numeric)
to use a custom mood, clients will use metadata property:
current_mood
Or, use them both. Why? The current_moodid will indicte the picture to use, the current_mood will have the text to display. Clients may decide how they'd like to do it (using neither, one, or both)
If only a current_moodid is given, the text will be from the server.
### a list of all server-supported moods
### each mood has a parent (base) mood. at the top of the tree would be
### "positive" or "negative", but since those are boring, maybe "happy" and "sad"
CREATE TABLE moods (
moodid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
mood VARCHAR(40),
parentmood INT UNSIGNED NOT NULL DEFAULT '0'
);
### I think the moods should be displayed with a picture with ALIGN=ABSMIDDLE, a space,
### and then the mood in text.
### people will of course want to customize the images for the moods, so there
### will be mood themes
CREATE TABLE moodthemes (
moodthemeid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
ownerid INT UNSIGNED NOT NULL
name VARCHAR(50),
des VARCHAR(100),
is_public ENUM('Y', 'N') NOT NULL DEFAULT 'N'
);
### users don't have to define pictures for every mood ... just certain base ones
### LiveJournal will find the picture by working up the tree until it finds one
### with a picture defined that's either gender neutral or matching gender
CREATE TABLE moodthemedata (
moodthemeid INT UNSIGNED NOT NULL,
KEY (moodthemeid),
moodid INT UNSIGNED NOT NULL,
gender ENUM('m', 'f', 'n') NOT NULL DEFAULT 'n',
UNIQUE (moodthemeid, moodid, gender),
picurl VARCHAR(100),
width TINYINT UNSIGNED NOT NULL,
height TINYINT UNSIGNED NOT NULL
);
### how to get moods:
###
extending the "login" protocol:
if you send the request key "getmoods" with a value of the highest moodid you have
cached locally, the server will send you back the newer ones, if any:
mood_count
mood_n_id -- mood ID number
mood_n_name -- mood text
if you're lazy, you can send getmoods=0 to get all the moods everytime, or you can
send no getmoods key at all, and not have them returned, so it doesn't slow old
clients.
#### some moods (these need to be put into a tree, showing which moods
#### are base moods of each other..... volunteers?)
aggravated
angry
annoyed
anxious
bored
confused
depressed
exhausted
happy
lonely
pissed
sad
stressed
tired
sore
energetic
enraged
infuriated
jubilant
horny
hungry
discontent
thirsty
satisfied
thoughtful
.....
tons more needed. if you send me some, don't just rip off another site's list... use your head and think of them independently.

View File

@@ -0,0 +1,154 @@
<?page
title=>LiveJournal Protocol
body<=
<?h1 Introduction h1?>
<?p
The following information is intended for developers interested in writing their own
LiveJournal clients to talk to the LiveJournal server. End users <I>do not</I> need
to know any of this, and are probably better off not knowing it.&nbsp;&nbsp; ;)
p?>
<?h1 Prerequisites h1?>
<?p
Before reading this document, it is assumed you know at least some basics about network programming,
at least the whole idea of opening sockets and reading/writing to them. If not, this might
be kinda confusing.
p?>
<?h1 It's really HTTP h1?>
<?p
If you already know the HTTP protocol, this is going to be really easy. For those of you
who don't know, HTTP is the protocol that web browsers talk to web servers with. For simplicity
of writing the LiveJournal server, we've decided to just use HTTP as our protocol transport.
This way, we can also go through proxies at schools and corporations really easily and
without drawing any attention.
p?>
<?h1 The Basics h1?>
<?p
Basically, sending a LiveJournal request is like this:
p?>
<OL>
<LI>Open a socket to <B>www.livejournal.com</B> on port <B>80</B>
<LI>Send an HTTP POST request, containing the request variables (mode, user, password, etc...)
<LI>Read the socket to get the response. The response is really easy to parse.
<LI>Close the socket. Do any approriate action based on the server's response.
</OL>
<?h1 Encoding the request h1?>
<?p
As mentioned previously, the request is sent as an HTTP POST request. Open your socket, and send a request looking like:
p?>
<UL>
<FONT COLOR=#0000FF><B>
<XMP>
POST /interface/flat HTTP/1.0
Host: www.livejournal.com
Content-type: application/x-www-form-urlencoded
Content-length: 34
mode=login&user=test&password=test
</XMP>
</B></FONT>
</UL>
As you can pretty easily see, the variables TO the webserver are encoded in the form <B>var1=val1&var2=val2&....</B>. Note
that you <B>must quote all values</B> or the values can interfere with the encoding form. For example, what if somebody's
password was "blah&=2+&something=yeah". It's an ugly password, sure, but somebody may have it. And if they do, it'll mess
up the pretty format of the encoding format. So, here are the rules on how to encode values:
<UL>
<LI>Leave all values from a-z, A-Z, and 0-9 alone. These are fine.
<LI>Convert spaces to a <B>+</B> sign.
<LI>Convert everything else to <B>%<I>hh</I></B> where <I>hh</I> is the hex representation of the character's ASCII value.
</UL>
So, for example, the phrase "I'm going to the mall" could encoded as "I%27m+going+to+the+mall". There should
be CGI libraries for all major languages which do this encoding for you. If not, it isn't that hard to do it yourself.
<?p
After you construct the big long ugly string of variables/values, find the length of it and send it in the
<I>Content-length</I> field, as in the example above. Then send a blank line, then the big long ugly string.
p?>
<?p
<B>Note about line endings: </B> Please note that the end of lines should be a carriage return (ASCII 13, 0x0D) and then a newline (ASCII 10, 0x0A).
In Perl, C/C++ or Java this is "\r\n". In Basic, this is Chr(13) & Chr(10). Sending just the newline may work too, but
it's generally better to send both.
p?>
<?p
Here is a typical response from the web server after sending your request:
<UL>
<FONT COLOR=#0000FF><B>
<XMP>
HTTP/1.1 200 OK
Date: Sat, 23 Oct 1999 21:32:35 GMT
Server: Apache/1.3.4 (Unix)
Connection: close
Content-Type: text/plain
name
Mr. Test Account
success
OK
message
Hello Test Account!
</XMP></B></FONT>
</UL>
The top stuff is headers from the HTTP request. There may be a lot of other stuff in there too.
First thing to do is make sure the first lines <B>ends with "200 OK"</B>. If the first line
does not end with 200 OK, tell the user that an error occured on the server and that it's not their fault.
If you see 200 OK at the end, proceed with parsing the output. The format is as follows:
<UL>
<XMP>
variable
value
someothervariable
someothervalue
</XMP>
</UL>
The ordering of the variable/value pairs does not matter. As you read them in, read them into a hash
structure. (associative array, dictionary, collection... whatever it's called in your language. Just
a data structure that links one string variable key to another string variable value.)
p?>
<?p
After your hash is loaded, proceed with the logic of reporting errors if needed, as governed by the
variables and logic above.
p?>
<?h1 Protocol modes h1?>
<?p
Of course, knowing all the above isn't useful unless you actually know what operations
the server supports....
p?>
<P><CENTER>
<A HREF="modelist.bml"><FONT SIZE=+1>Protocol Mode Documentation</FONT></A>
</CENTER>
<?h1 Proxies h1?>
<?p
As a final feature, once you get that stuff working, is to implement support for HTTP proxies. This
is <I>very</I> easy. Give the user a checkbox if they want to use a proxy or not, and if so, ask
the proxy host and proxy port. Now, if they selected to use a proxy, do not connect to
www.livejournal.com and port 80, but instead connect to their proxy host on whatever proxy
port they specified. The rest is basically the same, except for one difference. Instead of doing:
<UL>
<XMP>
POST /interface/flat HTTP/1.0
</XMP>
</UL>
<p>You would do...
<UL>
<FONT COLOR=#0000FF><B><XMP>
POST http://www.livejournal.com/interface/flat HTTP/1.0
</XMP></B></FONT>
</UL>
<p>That's it! That line tells the proxy what host it needs to connect to in order to make the real request.
The rest of the HTTP you should leave just as you did before.
This should be all you need to know to make a LiveJournal client.
p?>
<?h1 Need more help? h1?>
<?p
If anything is unclear, join the <?ljuser lj_clients ljuser?> community, where all the client authors hang out.
p?>
<=body
page?>

View File

@@ -0,0 +1,49 @@
<?page
title=>Raw Mode
body<=
<?h1 Raw mode? h1?>
<?p
Before LiveJournal had the currently fancy style editor it has now, users had to edit the style files by hand. This is the old documentation on how that was done....
p?>
<?hr?>
<?h1 Defining Variables h1?>
<?p
When creating a style or overriding other styles, you must define (or re-define) variables in a very specific format that the server can understand. You have two options, depending on whether the value of your variable is going to be very short (a single line) or multiple lines.
p?>
<?p For a single line element, like the number of items to be shown in your most-recent journal history, you specify the variable name (which is always in ALL-CAPS) and then the characters <B><tt>=&gt;</tt></B>, and the value. For example:
<UL>
<FONT COLOR=#0000FF><XMP>
lastn_opt_items=>25
</XMP></FONT>
</UL>
That will tell the server that for the "lastn" page (the recent journal history) that you want 25 items to be displayed.
p?>
<?p For multiple line elements, the format is similiar. First you specify the variable name, but instead of <tt>=&gt;</tt> you do a <B><tt>&lt;=</tt></B> and go to the next line. Then, type all the lines that you want in that variable and go to a new line and type <B><tt>&lt;=<I>VARIABLE</I></tt></B>. For example:
<UL>
<FONT COLOR=#0000FF><XMP>
lastn_page<=
<HTML>
<BODY BGCOLOR=yellow>
Here's what I've been doing lately... <P>
%%events%%
<HR>
Back to my
<A HREF="http://www.myserver.com/">
my home page
</A>.
</HTML>
<=lastn_page
</XMP></FONT>
</UL>
The variable begins at the &lt;HTML&gt; line and ends with the &lt;/HTML&gt; line.
p?>
<?p
One thing interesting to note in the preceeding example is the <B>%%events%%</B> property. All things enclosed in double percent signs are properties that the server will fill in. All variables and properties are documented. See the <B><A HREF="varlist.bml">list of variables</A></B> for more information.
page.
p?>
<=body
page?>

View File

@@ -0,0 +1,51 @@
<?page
title=>Style System
body<=
<?h1 Introduction h1?>
<?p
When LiveJournal was designed, we wanted it to be both easy to use, and still extremely flexible for the power users that like to tweak everything. We took some time and developed a system that we hope will accomodate everyone.
p?>
<?p
In LiveJournal, there are different view modes. The most common one is named "lastn", a view that displays the last 'n' number of events in one's journal. Additionally, there is a calendar view, a day view, a search page, and a search results page, and possibly more modes we may add in the future. Knowing that we're going to want to extend this in the future, and knowing some users will never be happy if we hard-code the style of the pages (and knowing that we suck at making things really pretty), our system lets users pick a base style for each mode, and override individual elements of the style at will.
p?>
<?p To better explain how things work, let's walk through things step by step, from the perspective of different users, explaining what's going on behind the scenes... p?>
<?h1 The casual user... h1?>
<?p
The casual user is one that doesn't know much about programming, if any, and just wants to get their journal up and running as quick as possible, and they want it to look pretty. What we're going to offer the casual user is a way to pick from pre-made styles. The pre-made styles define a bunch of variables used internally by the LiveJournal server code that generates the pages. Users will see syles names like "Classic Layout", "Simple Layout", "Modern Look" and then they'll be able to specify their colors for that layout ("style"). But behind the scenes the server will load the user's record, see they've selected style ID# 1043, and preload all the variables necessary to make the page look a certain way. By default, we'll provide a few styles and in time we'll let users submit their self-created styles to be approved to be system-wide styles that anybody can use.
p?>
<?h1 The advanced user... h1?>
<?p
The advanced user will more than likely start as being a casual user, but will soon grow annoyed at the way a certain thing about their journal pages are. The "overrides" section will let these advanced users research the variables that control the page layout and then redefine as much or as little as they want, without having to recreate their whole own style. In time, though, the user may get really good at the system and simply recreate everything.
p?>
<?h1 Variables and Properties h1?>
<?p
It took us awhile to settle on some terminology that we were happy with, but what we finally decided on was variables and properties. A <B>variable</B> is something that a style defines (and that a user can override) and is either:
<OL>
<LI>A <B>page setup option</B>, like how many items to show, how to sort something, or the format of dates and times.
<LI>Some <B>HTML</B> that the page contruction engine will use to make the page, with properties that it will fill in.
</OL>
A <B>property</B> is something that the server will generate (usually based on other variables) and prefill into your variables. Properties are mixed in variables like <NOBR><tt><B>%%property%%</B></tt></NOBR> and will be replaced by what they represent before being sent to the users' web-browsers.
p?>
<?h1 So, what next? h1?>
<?p To customize your journal beyond just changing colors, you'll need to make a new
style. Here are some links:
<UL>
<LI><A HREF="/styles/create.bml">Create Style</A>
<LI><A HREF="/styles/edit.bml">Edit Style</A> -- (for changing it later)
</UL>
p?>
<?h1 Questions? h1?>
<?p
If you have any questions or comments, ask the always-eager <a href="/support/submit.bml">support team</a>.
p?>
<?hr?>
Back to <A HREF="/developer/">Developer Area</A>.
<=body
page?>

View File

@@ -0,0 +1,173 @@
===============================
LIVEJOURNAL'S TIME PROBLEM
===============================
Brad Fitzpatrick
brad@livejournal.com
August 10, 2000
+------+
| Goal |
+------+
LiveJournal.com needs to handle time better, in particular to be aware of time
zones. There are too many problems that arise by neglecting them. If I knew of
an easy solution, I'd have implemented it already. This document is a plea for
help. All suggestions (however seemingly stupid) are likely to help, if only to
spark ideas for others.
Confession: I know very little about the details of time zones, daylight
saving's time, etc... but I've read tons on the topic, enough to know there's
a lot to know. In other words, the solution is likely complex as well.
+---------------+
| Current Setup |
+---------------+
LiveJournal's servers, and the primary developers are all on the west coast of
the United States, in the PST/PDT (Pacific Standard/Daylight Time) timezone,
which is -0800 GMT (and sometimes -0700 GMT, in daylight savings time, I
believe). Here is how LiveJournal currently stores the dates/time for different
events:
* journal creation date, update date --- PST/PDT local time.
* comment post dates --- PST/PDT local time.
* journal entries:
1) the "eventtime" is stored, whatever time you say it is wherever you're
at. you specify the year, month, day, hour and minute. no time zone
is recorded.
2) the "logtime" ... the time LiveJournal logs it to the database, in
PST/PDT local time.
+-------------------------+
| Description of problems |
+-------------------------+
Outlined below are the problems that LiveJournal currently faces:
1. Friends view ordering
2. Incorrect user clocks
3. Offline journaling/queueing
4. Comments post times
1. Friends view ordering
~~~~~~~~~~~~~~~~~~~~~~~~
The friends view is currently sorted according to the "logtime". In other
words, regardless of what time you said the journal event happened, the server
will sort them in the reverse order they were received. This has the advantages
that if you have friends scattered all over different timezones the journal
entries will still show in the order they actually happened.
The problem occurs though near midnight ... you can have friends in one
timezone posting entries on one day interleaved with friends in another
timezone posting commments on the previous day. The resultant friends view
looks really messed up, going back and forth between days.
This leads me to topic 2 ...
2. Incorrect user clocks
~~~~~~~~~~~~~~~~~~~~~~~~
The friends view shows the time the user reported (the "eventtime"), even
though it's sorted by the "logtime". So if a friend in your same timezone
is posting comments near the same time as you, it's possible their post
that happened after you has an earlier displayed time, because their computer
clock is messed up.
The worst is when their computer clock is months/years behind/ahead (very very
common because Windows users like to double-click their clock and use it as
a calendar ... then hit "OK" instead of "Cancel"). Then, the users complain
that "my journal entries are showing up on my friends page, but not on my
recent events view". This is because the recent events view (and the day
view, and the calendar) are all sorted by the "eventtime", the time the user
said the event happened.
3. Offline journaling/queueing
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Many people have been asking for and patiently awaiting the "offline journaling"
feature ... being able to enter journal entries on your no-Internet laptop or
PDA and HotSync (or whatever) them later to LiveJournal's servers.
Now, how should the friends view be formatted? Say the user submits 10 journal
entries ranging over a two week vacation. Should all 10 show up at once on
all of that user's friends' friends views? Because under the current scheme,
the "logtime" of all those entries would be seconds apart, and that's what the
friends view uses to sort.
Should offline/queued entries not show up on friends lists? Should posts
with eventtimes more than 'n' days in the past not show up on friends lists?
Both those solutions suck, because they're both ugly hacks that avoid fixing the
real problem -- adding timezone support to livejournal.
4. Comment times
~~~~~~~~~~~~~~~~
Blah. You know the problem. Keep reading ... time for solutions:
+----------------+
| Ideal Solution |
+----------------+
Ideally what would happen is that LiveJournal would store all posts with a
GMT time (that doesn't have daylight savings time) and the friends views
would sort by the GMT time of the eventtime ... so the user that went on
vacation and is dumping 10 entries into the system has them all intermixed
with her friends' old entries. Then, the friends view should display both
the remote browsing users's time and the time it was when the user posted the
item.
So the problem turns into -- how do we convert the "eventtime" the user
submits into a GMT time? But ... let's assume the user's computer clock
is wrong, or we don't want to use it because it's not perfectly synchronized.
So then we need to pass along to LiveJournal a timezone field.
So the problem is:
--- I need a list of all timezones. What format are these in?
When does daylight savings time occur? Each country and states
within those countries have different rules.
--- given a timezone code, how do I convert it to GMT? Are there
good packages already in existence (preferrably in Perl) to
do these conversions? I've looked around a lot, but haven't
found anything perfect.
--- how do we deal with legacy clients? it'll be some time
before all clients start sending timezone info.
Once the database is storing the eventtime in GMT and the timezone
code as well, then the style system needs to be updated to allow style
authors to show times in both the poster's time, and the journal owner
or remote user's time.
But perhaps there's a better solution? I really don't know. I hate the
dependency on users' system clocks, and I hate the misordering of friends
items.
+----------------------+
| Good example problem |
+----------------------+
I have a journal I wrote from 1990 that I typed up, but I don't want
to post it, because it'll show up on all my friends' friends views. I'd
prefer it just went to my calendar, and I can make a single post to tell
my friends to go read it (and link to the day it starts).
Sure, I could add a meta-data flag or something new on the protocol that
says, "don't add this to my friends view" but then people will start using
that for other purposes, and start asking for stupid stuff like, "can I have
it not show up on certain people's friends view?", not understanding the
point of it. It's just not clean. :/
_____________________________________________________________________________
Feedback requested:
brad@livejournal.com

View File

@@ -0,0 +1,79 @@
<?_info
nocache=>1
_info?><?page
title=><?_code $v = LJ::ehtml(BML::get_query_string()); return $v; _code?>
body<=
<?h1 Variable Details h1?>
<?p
The following are details on the <?_code return $v; _code?> variable. For more information on modifying this variable, see the information about the <A HREF="styles.bml">style system</A>.
p?>
<?_code
my @vars;
LJ::load_objects_from_file("vars.dat", \@vars);
my $ret = "";
foreach my $v (grep { $_->{'name'} eq $v } @vars)
{
LJ::xlinkify(\$v->{'props'}->{'des'});
$ret .= "<P><TABLE WIDTH=100% BGCOLOR=#C0C0C0><TR><TD><FONT FACE=\"Arial,Helvetica\" SIZE=+1><B>$v->{'name'}</B></FONT></TD></TR></TABLE>\n\n";
$ret .= "<TABLE CELLSPACING=2>\n";
$ret .= "<TR VALIGN=TOP><TD ALIGN=RIGHT NOWRAP><I>view types:</I></TD><TD>";
foreach (split (/\s*\,\s*/, $v->{'props'}->{'scope'}))
{
$ret .= "<A HREF=\"/developer/views.bml#$_\">$_</A>, ";
}
chop $ret; chop $ret;
$ret .= "</TD></TR>\n";
$ret .= "<TR VALIGN=TOP><TD ALIGN=RIGHT><I>description:</I></TD><TD>$v->{'props'}->{'des'}</TD></TR>\n";
# overrideable?
$ret .= "<TR VALIGN=TOP><TD ALIGN=RIGHT NOWRAP><I>can override:</I></TD><TD>";
if ($v->{'props'}->{'override'} eq "yes") {
$ret .= "<B>Yes</B>; users of this style may override this";
} elsif ($v->{'props'}->{'override'} eq "only") {
$ret .= "<B>Only</B>; Only users of this style may override this, it cannot be defined in a style.";
} else {
$ret .= "<B>No</B>; users of the style cannot override this. It may only be defined in the style.";
}
$ret .= "</TD></TR>\n";
if (defined $v->{'props'}->{'type'})
{
$ret .= "<TR VALIGN=TOP><TD ALIGN=RIGHT NOWRAP><I>variable type:</I></TD><TD>$v->{'props'}->{'type'}</TD></TR>\n";
}
if (defined $v->{'props'}->{'default'})
{
$ret .= "<TR VALIGN=TOP><TD ALIGN=RIGHT NOWRAP><I>default value:</I></TD><TD>$v->{'props'}->{'default'}</TD></TR>\n";
}
if (defined $v->{'props'}->{'props'})
{
$ret .= "<TR VALIGN=TOP><TD ALIGN=RIGHT><I>properties:</I></TD><TD>\n";
$ret .= "<TABLE CELLSPACING=1 BORDER=1 CELLPADDING=2>\n";
foreach my $p (@{$v->{'props'}->{'props'}})
{
LJ::xlinkify(\$p->{'props'}->{'des'});
$ret .= "<TR VALIGN=TOP><TD ALIGN=RIGHT><B>$p->{'name'}</B></TD>\n";
$ret .= "<TD>$p->{'props'}->{'des'} ";
if ($p->{'props'}->{'min'} > 0)
{
$ret .= "<FONT COLOR=#FF0000>[required]</FONT>";
}
$ret .= "</TD></TR>\n";
}
$ret .= "</TABLE></TD></TR>\n";
}
$ret .= "</TABLE>\n";
}
return $ret;
_code?>
<?hr?>
Back to <A HREF="/developer/">Developer Area</A>.
<=body
page?>

View File

@@ -0,0 +1,34 @@
<?_info
nocache=>1
_info?><?page
title=>Variable List
body<=
<?h1 Introduction h1?>
<?p
The following information is a list of the variables that users may define to control the look of their journal pages. Click a variable name to see more information about it, including its properties, if any. None of this will probably make sense unless you read the <a href="styles.bml">information about the style system</a>, which includes information on how to define variables and values.
p?>
<?h1 Variables h1?>
<?p
Click a variable name for more information about how and where to use it.
<UL>
<?_code
my @vars;
LJ::load_objects_from_file("vars.dat", \@vars);
my $ret = "";
foreach my $v (sort { $a->{'name'} cmp $b->{'name'} } @vars) {
$ret .= "<li><a href=\"varinfo.bml?$v->{'name'}\"><b>$v->{'name'}</b></a></li>\n";
}
return $ret;
_code?>
</UL>
p?>
<?hr?>
Back to <A HREF="/developer/">Developer Area</A>.
<=body
page?>

View File

@@ -0,0 +1,40 @@
<?page
title=>LiveJournal Protocol Versions
body<=
<?h1 Protocol Versions h1?>
<?p
The LiveJournal protocol (so far) has been more or less static; while new
modes have been added, the basic operation has not changed much. However, recent
introduction of Unicode support in LiveJournal necessitated changes in the way
text is encoded in protocol requests and responses. In order to allow new clients
to take advantage of Unicode support and at the same time avoid breaking existing
clients, a versioning scheme has been put into the protocol. The client sends
the number of the highest protocol version it supports in every request, inside
a <b><tt>ver</tt></b> attribute; version 0 is implicit if the client does not
send the <b><tt>ver</tt></b> attribute. Currently there are two versions of the protocol,
and the Unicode-enabled server code supports both of them.
p?>
<ul>
<li><b>Version 0</b><br/>
If a client does not send a <b><tt>ver</tt></b> key on a request, it assumed to support
protocol Version 0. In protocol Version 0, textual information transmitted from or to the
server is always assumed to be a stream of 8-bit bytes, not necessarily ASCII, but without
any guarantee that the non-ASCII bytes are presented in any particular encoding.
<li><b>Version 1</b><br/>
Version 1 differs from Version 0 only by imposing additional requirements on the text
transmitted through requests and responses; there aren't any changes in protocol modes.
The additional requirements are that in a Version 1 request, the client <b>must</b> transmit
all textual information as a stream of Unicode data encoded in UTF-8; the server <b>must</b>
respond to Version 1 requests with Version 1 responses; in such Version 1 responses, the server
<b>must</b> also transmit all textual information encoded in UTF-8; and the client must expect
that and handle such responses correctly.
In other words, all information transmitted via protocol when Version 1 is used is always encoded
in UTF-8. UTF-8 is a representation of Unicode in a bytestream format compatible with ASCII. See
<a href="http://www.unicode.org">the Unicode Consortium website</a> for more information on Unicode
and UTF-8.</li>
</ul>
<=body
page?>

View File

@@ -0,0 +1,51 @@
<?_info
nocache=>1
_info?><?page
title=>View Types
body<=
<?h1 Introduction h1?>
<?p
A <B>view</B> is just our terminology for a page type, or a mode. There are a lot of different ways to look at one's journal --- the default way is the "lastn" view, where it shows the last 'n' most recent events, in reverse order. However, there's also a calendar view, a day view when you click a day on the calendar, and more coming soon (a search page, and a search results page). Each and every view type is customizable through the <A HREF="styles.bml">style system</A>, which you should read up on if you haven't already.
p?>
<?p
Below is a list of all the view types, descriptions of them, and the <A HREF="varlist.bml">variables</A> that affect them.
p?>
<?_code
my @views;
my @vars;
LJ::load_objects_from_file("views.dat", \@views);
LJ::load_objects_from_file("vars.dat", \@vars);
my $ret = "";
foreach my $vi (@views)
{
$ret .= "<P><A NAME=\"$vi->{'name'}\"><TABLE WIDTH=100% BGCOLOR=#C0C0C0><TR><TD><FONT FACE=\"Arial,Helvetica\" SIZE=+1><B>$vi->{'name'}: <I>$vi->{'props'}->{'name'}</B></I></FONT></TD></TR></TABLE>\n\n";
LJ::xlinkify(\$vi->{'props'}->{'des'});
$ret .= $vi->{'props'}->{'des'};
$ret .= " <A HREF=\"$vi->{'props'}->{'url'}\">Example page</A>.\n"
if (defined $vi->{props}->{url});
$ret .= "<P><B>Variables affecting this view:</B><UL>\n";
foreach my $v (sort { $a->{'name'} cmp $b->{'name'} } @vars)
{
next unless ($v->{'props'}->{'scope'} =~ /\b$vi->{'name'}\b/);
$ret .= "<LI><A HREF=\"varinfo.bml?$v->{'name'}\"><B>$v->{'name'}</B></A>\n";
}
$ret .= "</UL>\n";
}
return $ret;
_code?>
<?hr?>
Back to <A HREF="/developer/">Developer Area</A>.
<=body
page?>

View File

@@ -0,0 +1,42 @@
// gets playing music from WinAmp, and returns
BOOL CPostOptionsDlg::GetPlayingMusic(CString &song)
{
// is WinAMP open?
HWND hwndWinamp = ::FindWindow("Winamp v1.x",NULL);
if (hwndWinamp == NULL) return FALSE;
// in WinAMP playing?
int ret = ::SendMessage(hwndWinamp, WM_USER, 0, 104);
if (ret != 1) return FALSE;
// it is, let's find out what the title bar is:
char this_title[2048],*p;
::GetWindowText(hwndWinamp,this_title,sizeof(this_title));
p = this_title+strlen(this_title)-8;
while (p >= this_title)
{
if (!strnicmp(p,"- Winamp",8)) break;
p--;
}
if (p >= this_title) p--;
while (p >= this_title && *p == ' ') p--;
*++p=0;
char *iter, *start;
start = this_title;
iter = start;
// remove leading s/^\d+\. //;
int numhead = 0;
while (*iter) {
if (isdigit(*iter)) { iter++; numhead++; }
else break;
}
if (numhead && *iter=='.' && *(iter+1)==' ') {
start = iter+2;
}
song = start;
return TRUE;
}

35
ljcom/htdocs/doc/find/index.bml Executable file
View File

@@ -0,0 +1,35 @@
<?_code
$title = $body = "";
my $guide = $FORM{'guide'};
unless ($guide) {
$title = "Not Found";
$body = qq{
To locate a Guide, use a URL of the form:
<blockquote>$LJ::SITEROOT/doc/find/?guide=<i>docname</i></blockquote>
where <i>docname</i> is a document ID consisting only of ASCII letters, digits,
underscores, and hyphens.
};
return;
}
if ($guide =~ /[^\w\-]/) {
$title = "Error";
$body = 'Illegal characters in identifier of requested guide.';
return;
}
my $location = "$LJ::SITEROOT/doc/html/$guide.html"; # temporary
return BML::redirect($location);
_code?><?_info
noheaders=>1
_info?><?page
title=><?_code return $title; _code?>
body=><?_code return $body; _code?>
page?><?_c <LJDEP>
link: htdocs/doc/find/index.bml
</LJDEP> _c?>

View File

@@ -0,0 +1,11 @@
<?page
title=>LiveJournal Guides
body<=
<dl>
<dt><a href="./support.bml">So You Want to be a Support Volunteer</a></dt>
<dd>LiveJournal can always use more volunteers, and helping out in Support is a great way to learn more about how LiveJournal works. However, it is a time-consuming task that requires patience and professionalism. Don't expect to just jump in answering questions and get a bunch of points. For many volunteers it will take weeks to earn their first support point.</dd>
<dt><a href="./moodicons.bml">Mood Icons</a></dt>
<dd>You can indicate your mood in any of your journal entries. The mood you enter will normally be displayed with your entry, and if it's one of the server-supported moods, a matching picture (a mood icon) will usually appear too.</dd>
</dl>
<=body
page?>

View File

@@ -0,0 +1,98 @@
<?page
title=>Mood Icons
body<=
<?h1 Table of Contents h1?>
<ul>
<li><a href="#display">How mood icons are displayed</a></li>
<li><a href="#choosetheme">Choosing a mood theme</a></li>
<li><a href="#createicons">Creating mood icons</a></li>
<li><a href="#newthemes">Turning your icons into a mood theme</a></li>
</ul>
<?hr?>
<?h1 <a name="display" id="display">How mood icons are displayed</a> h1?>
<?p You can indicate your mood in any of your journal entries. The mood you enter will normally be displayed with your entry, and if it's one of the <a href="http://www.livejournal.com/moodlist.bml">server-supported moods</a>, a matching picture (a <i>mood icon</i>) will usually appear too. p?>
<?p To indicate a mood when you post a journal entry from your Web browser, use the &#8220;Current Mood&#8221; box on the <a href="http://www.livejournal.com/update.bml?mode=full">full</a> version of the Update Journal page. You can choose a server-supported mood by scrolling through the list. Alternatively, you can leave the selection set to &#8220;None, or other&#8221;; then your mood will be whatever you type in the box labelled &#8220;Other&#8221; (which you can leave empty if you don't wish to indicate your mood). Later, you can edit your mood selection, the same way you would edit any other part of your journal entry. p?>
<?p Exactly how your mood is displayed depends on the style of the page where it appears. When your entry is viewed as part of your journal, you control the style and the set of icons used to represent moods. When your entry is viewed on someone else's Friends page, its appearance is determined by the style of their page, and by whether they decided to impose their own choice of mood icons or let each Friend choose their own. p?>
<?p The rest of this Guide explains how to choose a set of mood icons (called a <i>mood theme</i>), and how to create your own if you want to. p?>
<?h1 <a name="choosetheme" id="choosetheme">Choosing a mood theme</a> h1?>
<?p LiveJournal provides a number of public mood themes, which are available to all users. You can preview any of them on the <a href="http://www.livejournal.com/moodlist.bml">LiveJournal Moods</a> page. p?>
<?p To choose the mood theme you want to use for your journal, go to the <a href="http://www.livejournal.com/modify.bml">Modify Journal</a> page. Make sure you're logged in under the correct user name, click &#8220;Proceed...&#8221;, and on the next page scroll down to &#8220;Mood Icons&#8221;. Select a mood icon set. After you click &#8220;Save Changes&#8221;, icons from the set you selected should appear on your Recent Entries page, your Day view, and your message boards, wherever one of the server-supported moods is indicated. Optionally, you can also force the same icons to appear on your Friends page, in place of the icons selected by your Friends. p?>
<?p If the mood icons don't appear where they should, the problem could be that you're using a custom style which doesn't support mood icons, or that you've chosen single-color mood icons which are the same color as the background. p?>
<?p If you've created a personal (non-public) mood theme, as described below, you'll be able to select it from the <a href="http://www.livejournal.com/modify.bml">Modify Journal</a> page, the same way you would select a public theme. However, personal themes aren't listed on the <a href="http://www.livejournal.com/moodlist.bml">LiveJournal Moods</a> page, so they can't be previewed. p?>
<?h1 <a name="createicons" id="createicons">Creating mood icons</a> h1?>
<?p You're reading this (hopefully) because you want to create your own mood theme. Please read the whole document through before beginning! p?>
<?h2 The images you need to create h2?>
<?p Each icon must be stored in an image file. The server currently supports over 130 moods for which you can create icons, and it is possible that more moods will be added in the future. p?>
<?p That doesn't mean you need to create 130 images! Instead, the moods are sorted in a hierarchical structure, and as long as the basic (top level) moods are defined, there will be an image for each mood. Look at the <a href="http://www.livejournal.com/moodlist.bml">LiveJournal Moods</a> page to see the relationships. p?>
<?p For example, because &#8220;aggravated&#8221; is below &#8220;angry&#8221;, the icon you specify for &#8220;angry&#8221; will automatically be used for &#8220;aggravated&#8221; unless you explicitly pick another icon for &#8220;aggravated&#8221;. p?>
<?p Also, each mood has an associated mood ID (in parentheses on the <a href="http://www.livejournal.com/moodlist.bml">LiveJournal Moods</a> page). Later in this process, you'll need to know the ID of the mood to which each of your images should be assigned. p?>
<?h2 Considerations for your images h2?>
<ul>
<li>GIFs are a popular format, because they allow both transparency and animation.</li>
<li>The images can be any size, though large images are both visually obtrusive and time-consuming to load.</li>
<li>They don't all need to be the same size.</li>
<li>Remember that your images can be used on a variety of backgrounds! Don't assume that everyone has their text over white (or black). Or, you can decide that your icons will only work against a certain background, and design accordingly... but this will restrict your user base.</li>
<li>Remember that these images will be imported into the LiveJournal database by name, either by you or by someone else. Give descriptive names to your image files (the mood name will suffice: for example, &#8216;aggravated.gif&#8217; for the &#8220;aggravated&#8221; mood).</li>
</ul>
<?p Also, read the next section before beginning. p?>
<?h1 <a name="newthemes" id="newthemes">Turning your icons into a mood theme</a> h1?>
<?p One of the features of <a href="http://www.livejournal.com/paidaccounts/">paid LiveJournal accounts</a> is the ability to make <i>personal</i> mood themes, special to your journal, by entering data about the images yourself. If you don't want your theme to be a public theme, or if you want to see how your theme will look before it becomes a public theme, then you'll need to create it yourself. p?>
<?p First of all, your pictures must be available on a separate server. (You can't upload your mood images directly to LiveJournal's site; see <a href="http://www.livejournal.com/support/faqbrowse.bml?faqid=6">FAQ #6</a> for more information). Also, you must know the dimensions (width and height, in pixels) of each image. p?>
<?p When you're ready to continue, make sure you're logged in, and go to LiveJournal's <a href="http://www.livejournal.com/admin/console">command console</a>, where you can enter the commands to define your mood theme. (There is a <a href="http://www.livejournal.com/admin/console/reference.bml">reference</a>, in case you need it, which describes all of the console commands, and explains how to escape double-quote and backslash characters.) p?>
<?p The first thing you're going to do is create the theme. Execute the command: p?>
<blockquote><pre>
moodtheme_create <i>name</i> <i>des</i>
</pre></blockquote>
<?p where p?>
<blockquote>
<i>name</i> = the name of your new theme, which will be displayed in the lists from which users select.<br>
<i>des</i> = a short description for your theme, which will be accessible through the console's <kbd>moodtheme_list</kbd> command.<br>
</blockquote>
<?p For example: p?>
<blockquote><pre>
moodtheme_create "Munch's Screamers" "You've seen 'distressed'. Now see the rest."
</pre></blockquote>
<?p If you're successful, you'll see a page which tells you so and gives you a number (the theme ID) identifying your new mood theme. Remember this number, as it will be used every time you enter information for a mood. (If you forget it, you can execute the <kbd>moodtheme_list</kbd> command to find out the IDs of all the themes available to you.) p?>
<?p Now it's time for the tedious part. You'll assign each of your image files to a mood, by executing a command of the form: p?>
<blockquote><pre>
moodtheme_setpic <i>themeID</i> <i>moodID</i> <i>picURL</i> <i>width</i> <i>height</i>
</pre></blockquote>
<?p where p?>
<blockquote>
<i>themeID</i> = the number given to you after you created the mood theme.<br>
<i>moodID</i> = the number of a mood for which you created an icon; you can find these numbers in parentheses on the <a href="http://www.livejournal.com/moodlist.bml">LiveJournal Moods</a> page (example: <kbd>15</kbd> for &#8220;happy&#8221;).<br>
<i>picURL</i> = the full URL (Web address) of your mood icon.<br>
<i>width</i> = width of the image, in pixels.<br>
<i>height</i> = height of the image, in pixels.<br>
</blockquote>
<?p For the assignment to take effect, you must provide all of the information listed. For example: p?>
<blockquote><pre>
moodtheme_setpic 180 15 http://www.yoursite.com/happy.gif 20 20
</pre></blockquote>
<?p It might be daunting to think of specifying over 100 images this way, but remember that the images are nested. It's possible to represent all of the moods by specifying only 15 images. p?>
<?p When you've finished creating your theme, you can go to the <a href="http://www.livejournal.com/modify.bml">Modify Journal</a> page, and select the new theme as the mood icon set for your journal. p?>
<=body
page?>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
<?page
title=><?_ml .title _ml?>
body<=
<?h1 <?_ml .about.header _ml?> h1?>
<?p <?_ml .about _ml?> p?>
<?h1 <?_ml .docs.header _ml?> h1?>
<ul>
<li>
<?h2 <a href="/doc/tour/"><?_ml .docs.tour.title _ml?></a> h2?>
<?p <?_ml .docs.tour.about _ml?> p?>
</li>
<li>
<?h2 <a href="/support/faq.bml"><?_ml .docs.faq.title _ml?></a> h2?>
<?p <?_ml .docs.faq.about _ml?> p?>
</li>
<li>
<?h2 <a href="/doc/guide"><?_ml .docs.guides.title _ml?></a> h2?>
<?p <?_ml .docs.guides.about _ml?> p?>
<li>
<?ljuser howto ljuser?>
<?p <?_ml .docs.howto.about _ml?> p?>
</li>
<li>
<?h2 <a href="/doc/server/"><?_ml .docs.server.title _ml?></a> h2?>
<?p <?_ml .docs.server.about _ml?> p?>
</li>
</ul>
<?h1 <?_ml .volunteering.header _ml?> h1?>
<?p <?_ml .volunteering.about _ml?> p?>
<ul>
<li><?ljcomm lj_userdoc ljcomm?> - <?_ml .volunteering.ljuserdoc _ml?></li>
<li><?ljcomm lj_sysdoc ljcomm?> - <?_ml .volunteering.ljsysdoc _ml?></li>
</ul>
<=body
page?>

View File

@@ -0,0 +1,9 @@
<?_code
my $dbr = LJ::get_dbh("slave");
my $r = LJ::get_remote($dbr, $r);
unless (LJ::check_priv($dbr, $r, "siteadmin", "internaldocs")) {
return BML::http_response(401, "You don't get access")
}
_code?>

View File

@@ -0,0 +1,85 @@
<?page
title=>Disk Space Upgrades
body<=
<?p As a part of our <a href="http://www.livejournal.com/paidaccounts/">Paid
Account</a> package, we offer a certain amount of
<a href="http://www.livejournal.com/manage/files.bml">disk space for
hosting things</a> like
<a href="http://pics.livejournal.com/">ScrapBook images</a>,
<a href="http://www.livejournal.com/phonepost/">Phone Posts</a>, and
<a href="http://www.livejournal.com/editpics.bml">User Picture Icons</a>.
Every paid account starts with 100 MiB of disk space, with the option
to purchase more later on.
p?>
<?p
We offer upgrades at fixed sizes so it's easy to figure out which upgrade works
for you, but you still have the option of purchasing more space. Our current
upgrade sizes are 250 MiB, 500 MiB, and 1 GiB of disk space.
p?>
<div style='text-align: justify; padding-right: -1em'>
<?h2 Overlapping Upgrades h2?>
<img src='<?imgprefix?>/overlap2.png' style='float: right; margin-left: 1em' alt='Disk space
upgrades that overlap will receive a prorated rate for unused time.' />
<?p You'll not likely purchase another upgrade on the same day that your current
upgrade expires, so we account for "overlaps" in upgrades. Upgrades that overlap
are prorated based on the time unused before the current upgrade's expiration date
and disk space used.
p?>
<?p <strong>Note:</strong> We do not offer "downgrades" for accounts. You cannot
purchase a smaller amount of disk space (or more disk space for a shorter amount
of time) until after your current upgrade expires.
p?>
<?p
When paid accounts and/or upgrades expire, certain features are disabled over time.
<a href="http://www.livejournal.com/support/faqbrowse.bml?faqid=217">More
details</a> can be found in our
<a href="http://www.livejournal.com/support/">Frequently Asked Questions</a>.
p?>
</div>
<div style='clear: both'>
<?h2 Extensions h2?>
<?p
Multiple upgrades of the same amount of disk space (extensions) will accumulate
based on the upgrade's time. A 2 month 250 MiB upgrade purchased with a 6 month
250 MiB upgrade will result in an 8 month upgrade of 250 MiB. Disk space upgrades
can be extended by a maximum of 1 year.
p?>
</div>
<?h2 Price Chart h2?>
<?p The prices for disk space upgrades are as follows: p?>
<dl style='width: 30em'>
<?_code
{
use strict;
my $ret;
foreach my $size (sort { $a <=> $b } keys %{$LJ::Pay::bonus{'diskquota'}->{'items'}} ) {
my $sizeit = $LJ::Pay::bonus{'diskquota'}->{'items'}->{$size};
$ret .= "<dt style='border-bottom: 3px solid #000;'>$sizeit->{'name'}</dt><dd style='margin-bottom: 1.5em'><ul>";
foreach my $qty (sort { $a <=> $b } keys %{$sizeit->{'qty'}}) {
# will be interpretted as item-subitem-qty
my $amt = $sizeit->{'qty'}->{$qty}->{'amount'};
$ret .= "<li><label style='float: left; text-align: left'>";
$ret .= $sizeit->{'qty'}->{$qty}->{'name'} . ":</label>";
$ret .= "<div style='text-align: right; font-weight: bold; font-size: .9em'>\$$amt.00 USD</div></li>";
}
$ret .= "</ul></dd>";
}
return $ret;
}
_code?>
</dl>
<?h2 Where to Purchase h2?>
<?p All disk space upgrades require a <a href="http://www.livejournal.com/paidaccounts/">Paid Account</a>. Upgrades can be purchased directly from <a href="http://www.livejournal.com/pay/">our payment center</a>. p?>
<=body
head<=
<style type="text/css">
dl { color: #333333; }
dt { font-weight: bold; }
dd { margin-left: 0; }
dd ul { list-style-type: none; margin-left: 0; padding-left: 2em; }
dd ul li { border-bottom: 1px dotted #000000; }
dd ul li label { margin-bottom: 0; padding-bottom: 0; display: inline; }
</style>
<=head
page?>

View File

@@ -0,0 +1,118 @@
<?_code
use strict;
use vars qw(%FORM $body $title %ML);
$title = "";
$body = "";
my $ret;
my $tour =
{
intro =>
{
source => "intro.png",
href => "/",
next => 'create',
},
create =>
{
source => "create.png",
href => "/create.bml",
prev => 'about',
next => 'profile',
},
profile =>
{
source => "profile.png",
href => "/editinfo.bml",
prev => 'create',
next => 'update',
},
update =>
{
source => "update.png",
href => "/update.bml",
prev => 'profile',
next => 'modify',
},
modify =>
{
source => "modify.png",
href => "/modify.bml",
prev => 'update',
next => 'friends',
},
friends =>
{
source => "friends.jpg",
href => "/friends/",
prev => 'modify',
next => 'comms',
},
comms =>
{
source => "comms.jpg",
href => "/community/",
prev => 'friends',
next => 'clients',
},
clients =>
{
source => "clients.png",
href => "/download/",
prev => 'comms',
next => 'support',
},
support =>
{
source => "support.png",
href => "/support/",
prev => 'clients',
next => 'sitemap',
},
sitemap =>
{
source => "sitemap.png",
href => "/site/",
prev => 'support',
},
};
my $display = sub
{
my ($page, $code) = @_;
$ret .= '<table width="500" align="center" summary="">';
$ret .= "<tr><td colspan='3' align='center'><a href='$page->{'href'}'>";
$ret .= "<img src='$LJ::IMGPREFIX/tour/$page->{'source'}' border='1' width='350'/></a>";
$ret .= "<br /></td></tr>\n<tr>";
if ($page->{'prev'}) {
$ret .= "<td align='right'><a href=\"./?page=$page->{'prev'}\">";
$ret .= $ML{'.nav.prev'} . "</a>";
}
$ret .= "<td align='center'>" . $ML{".$code.title"} . "</td>";
if ($page->{'next'}) {
$ret .= "<td align='left'><a href=\"./?page=$page->{'next'}\">";
$ret .= $ML{'.nav.next'} . "</a>";
}
$ret .= "<tr><td align='justify' colspan='3'><?hr?><blockquote>" . $ML{".$code.caption"};
$ret .= "</blockquote></td></tr></table>";
return $ret;
};
unless (defined $FORM{'page'} and exists $tour->{$FORM{'page'}})
{
$body = $display->($tour->{'intro'}, "intro");
$title = BML::ml('.title', { 'title' => $ML{'.intro.title'} });
} else {
$body = $display->($tour->{$FORM{'page'}}, $FORM{'page'});
$title = BML::ml('.title', { 'title' => $ML{".$FORM{'page'}.title"} });
}
return;
_code?>
<?page
title=><?_code return $title _code?>
body=><?_code return $body; _code?>
page?>

View File

@@ -0,0 +1,452 @@
<?_code
#line 2
use strict;
use vars qw(%FORM $body $title);
LJ::set_active_crumb('download');
$title = "Download a Client";
$body = "";
# this structure just screams to be in a database,
# but it'll do for now.
# to do:
# put trademarks/restricted symbols where neccesary
# remove english!
# WINDOWS SECTION
my $c_win32_sema =
{
name => 'Semagic',
author => '<?ljuser quirrc ljuser?>, <?ljuser sema ljuser?>, <?ljuser visions ljuser?>, <?ljuser bradfitz ljuser?>',
text => "<?ljuser quirrc ljuser?> has been updating and enhancing the Visions Client with various features. For example, this client supports posting in non-Roman languages, and lets you preview your entry before you post it. Pick up a copy from the journal listed below.",
journal => 'ljwin32_sema',
};
my $c_win32_visions =
{
name => 'Visions Client',
author => '<?ljuser visions ljuser?>, <?ljuser bradfitz ljuser?>',
text => "This is one branch of the first Windows client. Instructions on how to download it are offered in its journal. <br /><strong>Note:</strong> This client is no longer being actively developed.",
journal => 'lj_win32',
};
my $c_win32_lochj =
{
name => 'LochJournal',
author => '<?ljuser xb95 ljuser?>',
text => "LochJournal is a client that lets you use LiveJournal more easily. A popular feature of LochJournal is that it has support for many accounts on different LiveJournal-based sites. Please see the <?ljcomm lochj_announce ljcomm?> community to download this client.",
journal => 'lochj_announce',
};
my $c_win32_ljnet =
{
name => 'LJ.NET',
author => '<?ljuser browren ljuser?>',
text => "LJ.NET provides a simple and powerful user interface to LiveJournal's services. It is currently in beta testing, and is not feature complete yet.",
homepage => 'http://lj-net.sourceforge.net/',
journal => 'ljnetdev',
};
# MAC SECTION
my $c_mac_phoenix =
{
name => 'Phoenix',
author => '<?ljuser thorshammer ljuser?>',
text => "A LiveJournal client for both PowerPC and 68k Macs. Phoenix works on Macs running anything from System 6 through OS 9; a Carbon version is also available for Mac OS X users.",
homepage => 'http://homepage.mac.com/thorshammer/phoenix.html',
journal => 'phoenix_lj',
};
my $c_mac_ijournal =
{
name => 'iJournal',
author => '<?ljuser cryo ljuser?>',
text => "iJournal is a client for Mac OS X. This client can auto-detect the music that you are currently listening to, if you are using iTunes or Audion. It also supports custom friends groups, friends list editing, and can periodically check your friends page for new posts.",
homepage => 'http://www.os10.org/osx/iJournal.html',
journal => 'ijournal',
};
my $c_mac_xjournal =
{
name => 'Xjournal',
author => '<?ljuser fraserspeirs ljuser?>',
text => "A full-featured client for Mac OS X. It supports offline operation, local saving of posts, history browsing, checking for changes in specific friends groups and multiple-group security. Also supports all the other things you might expect, like music auto-detection and friends list editing.",
homepage => 'http://www.speirs.org/xjournal/',
journal => 'xjournal',
};
my $c_mac_journalert =
{
name => 'Journalert',
author => '<?ljuser sprote ljuser?>',
text => "A Mac OS X client, featuring WYSIWYG (What You See Is What You Get) editing and immediate notification of friends' posts.",
homepage => 'http://www.sprote.com/journalert/',
journal => 'sprote',
};
my $c_mac_zljpost =
{
name => 'zlj post',
author => '<?ljuser zloba ljuser?>',
text => 'A lightweight Dashboard Widget designed to quickly update your journal. Supports other common features such as mood setting, iTunes music detection, community posting, and security settings.',
homepage => 'http://www.dmitrykirillov.com/',
journal => 'zlj',
};
# X WINDOW SYSTEM SECTION
my $c_xwin_gtk_logjam =
{
name => 'LogJam',
author => '<?ljuser evan ljuser?>',
text => "A feature-loaded <a href=\"http://www.gtk.org/\">GTK+</a> client which runs under many different flavors of UNIX, and Linux. Binary packages for Debian and Red Hat are available. A limited-functionality, unsupported version of LogJam is also available for Windows <a href=\"http://logjam.danga.com/windows/\">here</a>.",
homepage => 'http://logjam.danga.com/',
journal => 'logjam',
};
my $c_xwin_kde_kluje =
{
name => 'KLuJe',
author => '<?ljuser bbrewer ljuser?>, <?ljuser billybreen ljuser?>',
text => "A LiveJournal client for the <a href=\"http://www.kde.org/\">K Desktop Environment</a>. Requires the <a href=\"http://www.trolltech.com/products/qt/\">Qt</a> 3 graphics toolkit, and can be run independently from KDE.",
homepage => 'http://kluje.sourceforge.net/',
journal => 'kluje',
};
my $c_xwin_gnome_drivel =
{
name => 'Drivel',
author => '<?ljuser fflewddur ljuser?>',
text => "Drivel is a LiveJournal client for the <a href=\"http://www.gnome.org/\">GNOME</a> desktop environment. It is designed to utilize some of the new features of GNOME 2 including GConf, GnomeVFS, and GTK+ 2.",
homepage => 'http://sourceforge.net/projects/drivel/',
};
# HANDHELD SECTION
my $c_handheld_palm_pocketlj =
{
name => 'PocketLJ',
author => '<?ljuser thelovebug ljuser?>',
text => "With this, you can update your LiveJournal from your Palm/PocketPC/WinCE device. This client also supports offline posting for when you don't have an Internet connection, sending your posts when you sync (HotSync/ActiveSync) or directly connect to the Internet. Requires <a href=\"http://avantgo.com/\">AvantGo</a>.",
homepage => 'http://www.pocketlj.com/',
journal => 'pocketlj',
};
my $c_handheld_wap_mojo =
{
name => 'Mojo',
author => '<?ljuser camdez ljuser?>',
text => "Mojo is a LiveJournal client which can be used to update your journal from your WAP-enabled phone. Visit the URL below from your phone to use it.",
homepage => 'http://www.binaryuprising.com/mojo/',
};
my $c_handheld_wap_tapjam =
{
name => 'TapJam',
author => '<?ljuser sol3 ljuser?>',
text => "With this client you can update your LiveJournal from your WAP cell phone. Visit the URL below from your phone to use it.",
homepage => 'http://www.tapjam.net/lj/',
};
my $c_handheld_java_mobilelj =
{
name => 'MobileLJ',
author => '<?ljuser brienigma ljuser?>',
text => "A J2ME (Java 2 Micro Edition) LiveJournal client, which can be used on mobile devices which support the language.",
homepage => 'http://netninja.com/files/mobilelj/',
};
my $c_handheld_java_lj2me =
{
name => 'LJ2ME',
author => '<?ljuser xfyre ljuser?>',
text => "A J2ME LiveJournal client that offers a variety of useful features and has internal support for UTF-8 character encoding.",
homepage => 'http://www.xfyre.com/sw/lj2me/',
};
# COMMAND-LINE INTERFACE SECTION
my $c_cli_python_charm =
{
name => 'Charm',
author => '<?ljuser evilhat ljuser?>',
text => "Charm is a menu-driven, text-only, cross-platform client written in <a href=\"http://www.python.org/\">Python</a>. It supports the full array of posting and editing options, can run in a polling check-friend-updates-only mode, supports multiple usernames and other various features.",
homepage => 'http://ljcharm.sourceforge.net/',
journal => 'ljcharm',
};
my $c_cli_perl_jlj =
{
name => 'JLJ',
author => '<?ljuser jerronimo ljuser?>',
text => "JLJ is an interactive Perl client with numerous features, including friends-list checking, multiple profiles and several offline posting options. It can be run in a non-interactive mode for automated posts. Requires perl 5.002.",
homepage => 'http://www.cis.rit.edu/~jerry/Software/perl/#jlj',
journal => 'jlj',
};
my $c_cli_perl_sclj =
{
name => 'SCLJ',
author => '<?ljuser sapphirecat ljuser?>',
text => "SCLJ is a Perl program, based on the original Perl client by <?ljuser bradfitz ljuser?>, which can be used to update your LiveJournal from your Linux or BSD-based box. Requires perl 5.005 and the URI and LWP Perl modules.",
homepage => 'http://sclj.sourceforge.net/',
journal => 'sclj',
};
my $c_cli_posix_clive =
{
name => 'Clive',
author => '<?ljuser stesla ljuser?>',
text => "clive is a console-based UNIX client for LiveJournal, written in C. It can be used in a number of ways: on a pipe (like a filter), interactively, or just as a command. It supports both file-based and command-line configuration.",
homepage => 'http://sourceforge.net/projects/ljclive/',
};
my $c_cli_posix_centericq =
{
name => 'centericq',
author => '<?ljuser thekonst ljuser?>',
text => "centericq is a text-mode window-driven instant messaging application that supports many different IM networks, and integrates LiveJournal support into its IM interface. It supports posting, watching for friend view updates, announcements about friends' birthdays, reading journals via an internal RSS reader and many other options. It runs under a variety of UNIX variants, Windows, and Mac OS/X.",
homepage => 'http://thekonst.net/centericq/',
};
# APPLICATION SECTION
my $c_app_mozilla_livelizard =
{
name => 'Livelizard',
author => '<?ljuser drbrain ljuser?>',
text => "Livelizard is a client for LiveJournal-based sites that integrates into the popular <a href=\"http://www.mozilla.org/\">Mozilla</a> web browser.",
homepage => 'http://livelizard.mozdev.org/',
journal => 'livelizard',
};
my $c_app_mozilla_deepest_sender =
{
name => 'Deepest Sender',
author => '<?ljuser evildoive ljuser?>',
text => "Deepest Sender is a LiveJournal client plugin for Mozilla and Mozilla Firefox.",
homepage => 'http://deepestsender.mozdev.org',
journal => 'deepestsender',
};
my $c_app_emacs_ljupdate =
{
name => 'ljupdate',
author => '<?ljuser hober ljuser?>',
text => "Update your journal from within Emacs with this lisp extension.",
homepage => 'http://www.freesoftware.fsf.org/ljupdate/',
journal => 'ljupdate',
};
my $c_app_mirc_mirc =
{
name => 'mIRC Client',
author => '<?ljuser mart ljuser?>',
text => "You can update your LiveJournal from within the popular <a href=\"http://www.mirc.com/\">mIRC</a> IRC client with this script.<br /><strong>Note:</strong> This client is no longer being actively developed.",
journal => 'mirclj',
};
# MISCELLANEOUS SECTION
my $c_other_beos_alivejournal =
{
name => 'AliveJournal',
author => '<?ljuser grahams ljuser?>, <?ljuser simon ljuser?>',
text => "For users of the now-defunct <a href=\"http://www.beincorporated.com/\">BeOS operating system</a>.<br /><strong>Note:</strong> This client is no longer being actively developed.",
homepage => 'http://codeninja.net/alivejournal/',
journal => 'alivejournal',
};
# INDEXES
my $p_win32 =
{
name => 'Windows',
detail => '95 / 98 / Me / NT / 2000 / XP',
clients => [ $c_win32_sema, $c_win32_visions, $c_win32_lochj, $c_win32_ljnet ],
};
my $p_mac =
{
name => 'Macintosh',
detail => 'System 6 - OS 9, OS X',
clients => [ $c_mac_phoenix, $c_mac_ijournal, $c_mac_xjournal, $c_mac_journalert, $c_mac_zljpost ],
};
my $p_xwindow =
{
name => 'X Window System',
detail => 'GTK+, Qt / GNOME, KDE',
clients => [ $c_xwin_gtk_logjam, $c_xwin_kde_kluje, $c_xwin_gnome_drivel ],
};
my $p_handheld =
{
name => 'Handhelds',
detail => 'Palm OS&trade;, Windows CE, cell phones',
clients => [ $c_handheld_palm_pocketlj, $c_handheld_wap_mojo, $c_handheld_wap_tapjam, $c_handheld_java_lj2me, $c_handheld_java_mobilelj ],
};
my $p_cli =
{
name => 'Command-line',
detail => 'Multiplatform',
clients => [ $c_cli_python_charm, $c_cli_posix_centericq, $c_cli_perl_jlj, $c_cli_perl_sclj, $c_cli_posix_clive ],
};
my $p_app =
{
name => 'Application-level',
detail => 'Plug-ins, extensions',
clients => [ $c_app_mozilla_deepest_sender, $c_app_emacs_ljupdate, $c_app_mirc_mirc ],
};
my $p_other =
{
name => 'Miscellaneous',
clients => [ $c_other_beos_alivejournal ],
};
my @platforms =
(
$p_win32,
$p_mac,
$p_xwindow,
$p_handheld,
$p_cli,
$p_app,
$p_other,
);
my $form_platform = $FORM{'platform'};
my $ret;
my $valid_selection = (defined $form_platform and
($form_platform eq "all" or
grep { $form_platform eq $_->{'name'} } @platforms));
# mode: pick platform
unless ($valid_selection) {
$ret .= <<"EOT";
<?h1 Introduction h1?>
<?p While it's possible to use LiveJournal with just a web browser, it's convenient to download a small program that lets you work with your LiveJournal directly.
For more information on what a client is, <a href="http://www.livejournal.com/support/faqbrowse.bml?faqid=158">check out the FAQ</a>. p?>
<?h1 Choose a Platform h1?>
<?p First, pick your platform:
<ul>
EOT
foreach my $platform (@platforms) {
my $uplat = LJ::eurl($platform->{'name'});
$ret .= "<li><a href=\"?platform=$uplat\"><strong>$platform->{name}</strong>";
if (defined $platform->{detail}) {
$ret .= " ($platform->{detail})";
}
$ret .= "</a>\n";
}
$ret .= <<"EOT";
</ul>
Or, view <a href="?platform=all">all of the clients on one page</a>.
p?>
<?h1 Don't see your platform listed? h1?>
<?p LiveJournal clients are available for a variety of platforms. If no version is available for your computer, or you don't want to download anything, you can still use the <a href="/update.bml">Web update page</a>. p?>
<?h1 Alternative Clients h1?>
<?p Clients written for the Blogger and Atom <acronym title="Application Programming Interface">API</acronym>s should be able to work
with LiveJournal, provided you can change which server they post to. p?>
<?h1 Want to port to other languages/platforms? h1?>
<?p For information on how to develop your own client, check out the <a href="http://www.livejournal.com/developer/">developer section</a>. p?>
EOT
$body = $ret;
return;
}
# mode: list clients
my $display_platform = sub
{
my $platform = shift;
next if not defined $platform->{clients};
foreach my $client (@{$platform->{clients}}) {
$ret .= "<?h1 $client->{name} h1?>\n";
$ret .= '<table width="100%" cellpadding="5">';
my ($t1, $t2, $t3);
$t1 = '<tr valign="top"><td align="right" style="white-space: nowrap"><strong>';
$t2 = '</strong></td><td width="100%" align="left">';
$t3 = "</td></tr>\n";
$ret .= ($t1 . "Name:" . $t2 .
"$client->{name}<br /><small>by $client->{author}</small>" .
$t3);
$ret .= $t1 . "Description:" . $t2 . $client->{text} . $t3;
if (defined $client->{downloads}) {
$ret .= $t1 . "Downloads:" . $t2 . "<ul style='margin-left: 0px'>";
foreach my $download (@{$client->{downloads}}) {
$ret .= "<li style='list-style: none'><p><a href=\"$download->{url}\"><img border='0' align='absmiddle' src='$LJ::IMGPREFIX/download.gif' hspace='5' width='16' height='16'>$download->{title}</a> $download->{text}</p></li>\n";
}
$ret .= "</ul>" . $t3;
}
if (defined $client->{homepage}) {
$ret .= $t1 . "Home Page:" . $t2 . "<a href=\"$client->{homepage}\">$client->{homepage}</a>" . $t3;
}
if (defined $client->{journal}) {
$ret .= "$t1 Journal: $t2 Watch " . LJ::ljuser($client->{journal}) . " for updates.$t3";
}
$ret .= "</table>";
$ret .= "<?hr?>";
}
};
if ($form_platform eq "all") {
$title = "All Clients";
foreach my $platform (@platforms) {
$display_platform->($platform);
}
} else {
$title = "$form_platform Clients";
foreach my $platform (@platforms) {
if ($platform->{'name'} eq $form_platform) {
$display_platform->($platform);
}
}
}
$ret .= "Back to the <a href=\"./\">Platform Selection page</a>.";
$body = $ret;
return;
_code?><?page
title=><?_code return $title _code?>
body=><?_code return $body _code?>
page?><?_c <LJDEP>
img: htdocs/img/download.gif
link: htdocs/download/index.bml, htdocs/update.bml, htdocs/developer/index.bml
</LJDEP> _c?>

BIN
ljcom/htdocs/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1,50 @@
<?_code
{
use strict;
my $body = \$_[1]->{'body'};
my $remote = LJ::get_remote();
my $userid = $remote && $remote->{'userid'} ? $remote->{'userid'} : 0;
if ($POST{'action:submit'}) {
if (LJ::trim($POST{'survey'}) ne "") {
my $dbh = LJ::get_db_writer();
my $sth = $dbh->prepare("INSERT INTO fotobilder_feedback VALUES (?,?,'N',NOW(),?)");
$POST{'survey'} .= "\n\nUser-agent: " . BML::get_client_header('User-Agent');
$sth->execute($POST{'url'}, $userid, $POST{'survey'});
$$body .= $dbh->err ? "<?h1 Database Error h1?><?p There was an error trying to file your survey. Please report the following back to us: " . $dbh->errstr . " p?>" :
"<?h1 Thank You h1?><?p Thank you for giving us your feedback. If you see anything else you'd like to comment on, ".
"you can fill out as many surveys as you'd like, or post to the community <?ljcomm fotobilder_user ljcomm?>. p?>";
return;
}
}
unless ($POST{'url'}) {
$$body = "<?h1 Oops! h1?><?p The only way to fill out this survey is to press the \"Provide Feedback\" button on ".
"<a href='http://pics.livejournal.com/'>http://pics.livejournal.com</a>, provided at the bottom of each page. ".
"If there wasn't a button for you to press, or you have some other related feedback, please post about it to ".
"the community <?ljcomm fotobilder_user ljcomm?>. Thank you! p?>";
} else {
$$body .= "<?p Please be as descriptive as possible in providing your feedback about the page http://pics.livejournal.com$POST{'url'}. ".
"We're trying to gather as much information as we can, in particular: p?><ul><li>Was the page easy to use?</li>".
"<li>Were any aspects of the page confusing to follow?</li><li>Was it easy to stop and pick up where you left off?</li>".
"<li>Did the page keep your interest until you were done?</li></ul>";
$$body .= "<?p If you're reporting an error, please copy and paste the exact text of any error messages you received. ";
$$body .= "These are extremely useful for developers trying to track down any problems you may be having. p?>";
$$body .= "<form method='POST'>";
$$body .= LJ::html_hidden('url',$POST{'url'});
$$body .= LJ::html_textarea({ 'name' => "survey", 'rows' => 10, 'cols' => 40, 'style' => "width: 100%; border-top: 1px solid #000; border-left: 1px solid #000; border-bottom: 1px solid #ddd; border-right: 1px solid #ddd", });
$$body .= "<div style='text-align: right'>" . LJ::html_submit('action:submit',"Send Survey") . "</div>";
$$body .= "</form>";
}
return;
}
_code?>
<?page
title=>FotoBilder Feedback Survey
body=><?_code return $_[1]->{'body'}; _code?>
page?>

View File

@@ -0,0 +1,25 @@
<?page
title=>Feedback
body<=
<?h1 Let us know what you think h1?>
<?p We're trying hard to make LiveJournal.com the best journaling site on the web.
Your views on what we're doing and how we could make things even better are important to us.
So if there's anything you want to tell us, please check out the options below: p?>
<dl>
<dt style="font-weight: bold"><a href="/suggestions/">Have a suggestion?</a></dt>
<dd>
For suggestions about our site or service, feel free to check out the suggestions area.
</dd>
<dt style="font-weight: bold"><a href="/support/">Have a technical problem?</a></dt>
<dd>
If you have a technical problem with the site or service, check out the support area.
Here you can check out the list of Frequently Asked Questions, or submit your own question.
</dd>
<dt style="font-weight: bold"><a href="/support/submit.bml">Report a Bug</a></dt>
<dd>
If you feel you have come across a bug on our pages or in our service, please report it to our
support team for verification.
</dd>
</dl>
<=body
page?>

View File

@@ -0,0 +1,89 @@
<?page
title=><?_ml .title _ml?>
body<=
<?_code
{
use strict;
use vars qw(%GET);
# GET{own} = bool: include your own friends in the list
my $remote = LJ::get_remote();
unless ($remote) {
return $ML{'error.noremote'};
}
if ($LJ::DISABLED{'friendspopwithfriends'}) {
# FIXME: memcache this page. (memcache friendof)
return "This feature is disabled.";
}
unless (LJ::get_cap($remote, "friendspopwithfriends")) {
return $ML{'.account_type'};
}
# load remote's friends
my @friends;
my %type;
# TAG:fr:bml_friends_popwith:get_friends
my $fr = LJ::get_friends($remote);
return "Error fetching your friends" unless $fr;
my $LIMIT = 500;
my @ids = keys %$fr;
splice(@ids, 0, $LIMIT) if @ids > $LIMIT;
my $fus = LJ::load_userids(@ids);
@ids = grep { $fus->{$_}{journaltype} eq "P" } @ids;
my %count;
my $MAX_DELAY = 4;
my $start = time();
while (@ids && time() < $start + $MAX_DELAY) {
my $fid = shift @ids;
my $fr = LJ::get_friends($fid);
next unless $fr;
$count{$_}++ foreach (keys %$fr);
}
my @pop = sort { $count{$b} <=> $count{$a} } keys %count;
my $ret = $GET{'own'} ? "<?p $ML{'.intro_own'} p?>" : "<?p $ML{'.intro'} p?>";
my $rows;
my $MAX_DISPLAY = 50;
my $shown;
foreach my $popid (@pop) {
next if $fr->{$popid} && ! $GET{'own'};
last if ++$shown > $MAX_DISPLAY;
my $u = LJ::load_userid($popid);
my $count = $count{$popid};
$rows .= "<tr><td>" . LJ::ljuser($u) . " - " . LJ::ehtml($u->{name}) .
"</td><td align='right'>$count</td></tr>\n";
}
if ($rows) {
$ret .= "<table cellpadding='3'>\n";
$ret .= "<tr><td><b>$ML{'.user'}</b></td><td><b>$ML{'.count'}</b></td></tr>\n";
$ret .= $rows;
$ret .= "</table>\n";
$ret .= $GET{'own'} ? "<?p $ML{'.exclude_own'} p?>" : "<?p $ML{'.include_own'} p?>";
} else {
$ret .= "<div style='margin-left: 30px;'><i>$ML{'.no_users'}</i></div>\n";
$ret .= "<?p $ML{'.include_own'} p?>" unless $GET{'own'};
}
return $ret;
}
_code?>
<=body
page?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 B

BIN
ljcom/htdocs/img/download.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

BIN
ljcom/htdocs/img/dys/5x5.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 B

Some files were not shown because too many files have changed in this diff Show More