init
This commit is contained in:
2068
local/cgi-bin/Apache/BML.pm
Executable file
2068
local/cgi-bin/Apache/BML.pm
Executable file
File diff suppressed because it is too large
Load Diff
1618
local/cgi-bin/Apache/LiveJournal.pm
Executable file
1618
local/cgi-bin/Apache/LiveJournal.pm
Executable file
File diff suppressed because it is too large
Load Diff
152
local/cgi-bin/CSS/Cleaner.pm
Normal file
152
local/cgi-bin/CSS/Cleaner.pm
Normal file
@@ -0,0 +1,152 @@
|
||||
#
|
||||
# Note: this is a very early version of a CSS cleaner. The plan is to eventually
|
||||
# make it a white-listing CSS cleaner (deny by default) with a nice
|
||||
# interface where you can build policy about what's allowed, like
|
||||
# HTML::Sanitize/::Scrub/etc, but for now this is almost a null cleaner,
|
||||
# just parsing and reserializing the CSS, removing two trivial ways to
|
||||
# inject javascript.
|
||||
#
|
||||
# The plan now is to integrate this interface into LiveJournal, then improve
|
||||
# this module over time.
|
||||
#
|
||||
# Note2: we tried 4 different CSS parsers for this module to use, and all 4 sucked.
|
||||
# so for now this module sucks, until we can find a suitable parser. for the
|
||||
# record, CSS::Tiny, CSS, and CSS::SAC all didn't work. and csstidy wasn't
|
||||
# incredibly hot either. CSS.pm's grammar was buggy, and CSS::SAC had the
|
||||
# best interface (SAC) but terrible parsing of selectors. we'll probably
|
||||
# have to write our own, based on the Mozilla CSS parsing code.
|
||||
|
||||
|
||||
package CSS::Cleaner;
|
||||
use strict;
|
||||
use vars qw($VERSION);
|
||||
$VERSION = '0.01';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my %opts = @_;
|
||||
|
||||
my $self = bless {}, $class;
|
||||
|
||||
if (defined( $opts{rule_handler} )) {
|
||||
my $rule_handler = $opts{rule_handler};
|
||||
die "rule_handler needs to be a coderef if supplied" unless ref( $rule_handler ) eq 'CODE';
|
||||
$self->{rule_handler} = $rule_handler;
|
||||
}
|
||||
|
||||
if (defined( $opts{pre_hook} )) {
|
||||
my $pre_hook = $opts{pre_hook};
|
||||
die "pre_hook needs to be a coderef if supplied" unless ref( $pre_hook ) eq 'CODE';
|
||||
$self->{pre_hook} = $pre_hook;
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# cleans CSS
|
||||
sub clean {
|
||||
my ($self, $target) = @_;
|
||||
$self->_stupid_clean(\$target);
|
||||
return $target;
|
||||
}
|
||||
|
||||
# cleans CSS properties, as if it were in a style="" attribute
|
||||
sub clean_property {
|
||||
my ($self, $target) = @_;
|
||||
$self->_stupid_clean(\$target);
|
||||
return $target;
|
||||
}
|
||||
|
||||
# this is so stupid. see notes at top.
|
||||
# returns 1 if it was okay, 0 if possibly malicious
|
||||
sub _stupid_clean {
|
||||
my ($self, $ref) = @_;
|
||||
|
||||
my $reduced = $$ref;
|
||||
if (defined( $self->{pre_hook} )) {
|
||||
$self->{pre_hook}->( \$reduced );
|
||||
}
|
||||
|
||||
$reduced =~ s/&\#(\d+);?/chr($1)/eg;
|
||||
$reduced =~ s/&\#x(\w+);?/chr(hex($1))/eg;
|
||||
|
||||
if ($reduced =~ /[\x00-\x08\x0B\x0C\x0E-\x1F]/) {
|
||||
$$ref = "/* suspect CSS: low bytes */";
|
||||
return;
|
||||
}
|
||||
|
||||
if ($reduced =~ /[\x7f-\xff]/) {
|
||||
$$ref = "/* suspect CSS: high bytes */";
|
||||
return;
|
||||
}
|
||||
|
||||
# returns 1 if something bad was found
|
||||
my $check_for_bad = sub {
|
||||
if ($reduced =~ m!<\w!) {
|
||||
$$ref = "/* suspect CSS: start HTML tag? */";
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $with_white = $reduced;
|
||||
$reduced =~ s/[\s\x0b]+//g;
|
||||
|
||||
if ($reduced =~ m!\\[a-f0-9]!i) {
|
||||
$$ref = "/* suspect CSS: backslash hex */";
|
||||
return;
|
||||
}
|
||||
|
||||
$reduced =~ s/\\//g;
|
||||
|
||||
if ($reduced =~ /\@(import|charset)([\s\x0A\x0D]*[^\x0A\x0D]*)/i) {
|
||||
my $what = $1;
|
||||
my $value = $2;
|
||||
if (defined( $self->{rule_handler} )) {
|
||||
return $self->{rule_handler}->( $ref, $what, $value );
|
||||
} else {
|
||||
$$ref = "/* suspect CSS: $what rule */";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($reduced =~ /&\#/) {
|
||||
$$ref = "/* suspect CSS: found irregular &# */";
|
||||
return;
|
||||
}
|
||||
|
||||
if ($reduced =~ m!</!) {
|
||||
$$ref = "/* suspect CSS: close HTML tag */";
|
||||
return;
|
||||
}
|
||||
|
||||
# returns 1 if bad phrases found
|
||||
my $check_phrases = sub {
|
||||
my $str = shift;
|
||||
if ($$str =~ m/(\bdata:\b|javascript|jscript|livescript|vbscript|expression|eval|cookie
|
||||
|\bwindow\b|\bparent\b|\bthis\b|behaviou?r|moz-binding)/ix) {
|
||||
my $what = lc $1;
|
||||
$$ref = "/* suspect CSS: potential scripting: $what */";
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
return 1 if $check_phrases->(\$reduced);
|
||||
|
||||
# restore whitespace
|
||||
$reduced = $with_white;
|
||||
$reduced =~ s!/\*.*?\*/!!sg;
|
||||
$reduced =~ s!\<\!--.*?--\>!!sg;
|
||||
$reduced =~ s/[\s\x0b]+//g;
|
||||
$reduced =~ s/\\//g;
|
||||
return 1 if $check_phrases->(\$reduced);
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
# check for bad stuff before/after removing comment lines
|
||||
return 0 if $check_for_bad->();
|
||||
$reduced =~ s!//.*!!g;
|
||||
return 0 if $check_for_bad->();
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
1164
local/cgi-bin/Cache/Memcached.pm
Executable file
1164
local/cgi-bin/Cache/Memcached.pm
Executable file
File diff suppressed because it is too large
Load Diff
94
local/cgi-bin/Golem.pm
Normal file
94
local/cgi-bin/Golem.pm
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
package Golem;
|
||||
|
||||
use strict;
|
||||
|
||||
require "$ENV{'LJHOME'}/cgi-bin/Golem/dblib.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/Golem/loglib.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/Golem/netlib.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/Golem/proplib.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/Golem/textlib.pl";
|
||||
|
||||
# *** LJR apis conversion layer
|
||||
#
|
||||
# check out
|
||||
|
||||
our $on = 1;
|
||||
our $counter_prefix = "golem_";
|
||||
|
||||
sub get_db {
|
||||
return LJ::get_db_writer();
|
||||
}
|
||||
|
||||
# Golem tags are not ported to LJR
|
||||
sub unset_row_tag {
|
||||
return 1;
|
||||
}
|
||||
# *** LJR apis conversion layer
|
||||
|
||||
sub get_callstack {
|
||||
my $cstack;
|
||||
my $i = 0;
|
||||
while ( 1 ) {
|
||||
my $tfunc = (caller($i))[3];
|
||||
if ($tfunc && $tfunc ne "") {
|
||||
if ($tfunc !~ /\_\_ANON\_\_/ &&
|
||||
$tfunc !~ /.*::get_callstack/) {
|
||||
$cstack .= "\t" . $tfunc . "\n";
|
||||
}
|
||||
$i = $i + 1;
|
||||
}
|
||||
else {
|
||||
last;
|
||||
}
|
||||
}
|
||||
return "\nCallstack:\n" . $cstack . "\n";
|
||||
}
|
||||
|
||||
sub err {
|
||||
if (ref($_[0])) {
|
||||
my $dbh = shift;
|
||||
$dbh->rollback;
|
||||
}
|
||||
|
||||
my $errstr = shift || "";
|
||||
my $previous_object;
|
||||
|
||||
if (ref($_[0]) eq 'HASH') {
|
||||
$previous_object = shift;
|
||||
}
|
||||
|
||||
if ($previous_object) {
|
||||
$previous_object->{'err'} = 1;
|
||||
$previous_object->{'errstr'} = $errstr . Golem::get_callstack();
|
||||
|
||||
return $previous_object;
|
||||
}
|
||||
else {
|
||||
my %res = (
|
||||
"err" => 1,
|
||||
"errstr" => $errstr . Golem::get_callstack(),
|
||||
);
|
||||
|
||||
return \%res;
|
||||
}
|
||||
}
|
||||
|
||||
sub die {
|
||||
my ($message, $suppress_callstack) = @_;
|
||||
|
||||
print STDERR "$message";
|
||||
|
||||
unless ($suppress_callstack) {
|
||||
print STDERR Golem::get_callstack();
|
||||
}
|
||||
else {
|
||||
print "\n";
|
||||
}
|
||||
|
||||
exit 1;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
746
local/cgi-bin/Golem/dblib.pl
Normal file
746
local/cgi-bin/Golem/dblib.pl
Normal file
@@ -0,0 +1,746 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# Generic database routines
|
||||
#
|
||||
|
||||
|
||||
package Golem;
|
||||
use strict;
|
||||
|
||||
use Golem;
|
||||
|
||||
# courtesy of LiveJournal.org
|
||||
sub disconnect_dbs {
|
||||
foreach my $h (($Golem::DB, $Golem::PlanerDB, $Golem::CalendarDB, $Golem::OtrsDB, $Golem::SwerrsDB)) {
|
||||
if ($h) {
|
||||
$h->disconnect();
|
||||
$h = undef;
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR localtime() . " [$$]: closed db connections\n" if $ENV{'GOLEM_DEBUG'};
|
||||
}
|
||||
|
||||
# build DSN connection string based on database info hashref,
|
||||
# courtesy of livejournal.org
|
||||
#
|
||||
# $DBINFO = {
|
||||
# 'master' => {
|
||||
# 'dbname' => "golem_kohts",
|
||||
# 'host' => "localhost",
|
||||
# 'port' => 3306,
|
||||
# 'user' => "root",
|
||||
# 'pass' => "",
|
||||
# 'sock' => "",
|
||||
# 'encoding' => "utf8",
|
||||
# },
|
||||
# };
|
||||
#
|
||||
sub make_dbh_fdsn {
|
||||
my ($db) = @_;
|
||||
|
||||
my $fdsn = "DBI:mysql";
|
||||
$fdsn .= ":$db->{'dbname'}";
|
||||
$fdsn .= ";host=$db->{'host'}" if $db->{'host'};
|
||||
$fdsn .= ";port=$db->{'port'}" if $db->{'port'};
|
||||
$fdsn .= ";mysql_socket=$db->{'sock'}" if $db->{'sock'};
|
||||
$fdsn .= "|$db->{'user'}|$db->{'pass'}";
|
||||
|
||||
return $fdsn;
|
||||
}
|
||||
|
||||
# test if connection is still available
|
||||
# (should check for replication, etc. here)
|
||||
#
|
||||
sub connection_bad {
|
||||
my ($dbh, $try) = @_;
|
||||
|
||||
return 1 unless $dbh;
|
||||
|
||||
my $ss = eval {
|
||||
#
|
||||
# $dbh->selectrow_hashref("SHOW SLAVE STATUS");
|
||||
#
|
||||
# on a real slave
|
||||
#
|
||||
# $ss = {
|
||||
# 'Skip_counter' => '0',
|
||||
# 'Master_Log_File' => 'ararita-bin.882',
|
||||
# 'Connect_retry' => '60',
|
||||
# 'Master_Host' => 'ararita.lenin.ru',
|
||||
# 'Relay_Master_Log_File' => 'ararita-bin.882',
|
||||
# 'Relay_Log_File' => 'laylah-relay-bin.323',
|
||||
# 'Slave_IO_Running' => 'Yes',
|
||||
# 'Slave_SQL_Running' => 'Yes',
|
||||
# 'Master_Port' => '3306',
|
||||
# 'Exec_master_log_pos' => '17720151',
|
||||
# 'Relay_log_space' => '19098333',
|
||||
# 'Relay_Log_Pos' => '19098333',
|
||||
# 'Last_errno' => '0',
|
||||
# 'Last_error' => '',
|
||||
# 'Replicate_do_db' => 'prod_livejournal,prod_livejournal',
|
||||
# 'Read_Master_Log_Pos' => '17720151',
|
||||
# 'Master_User' => 'replication',
|
||||
# 'Replicate_ignore_db' => ''
|
||||
# };
|
||||
|
||||
$dbh->selectrow_hashref("select name from _dbi");
|
||||
};
|
||||
|
||||
if ($dbh->err && $dbh->err != 1227) {
|
||||
print STDERR localtime() . " [$$]: " . $dbh->errstr . "\n" if $ENV{'GOLEM_DEBUG'};
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($ss && $ss->{'name'} ne '??') {
|
||||
return 0;
|
||||
}
|
||||
elsif ($ss && $ss->{'name'} eq '??') {
|
||||
print STDERR localtime() . " [$$]: DBI returned garbage: $ss->{'name'}\n" if $ENV{'GOLEM_DEBUG'};
|
||||
return 1;
|
||||
}
|
||||
elsif (!$ss) {
|
||||
print STDERR localtime() . " [$$]: DBI returned nothing\n" if $ENV{'GOLEM_DEBUG'};
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
# LJR modification; redefined in cgi-bin/Golem.pmGolem.pm
|
||||
# so it works correctly with original LJ code
|
||||
#
|
||||
sub golem_get_db {
|
||||
my ($params, $opts) = @_;
|
||||
|
||||
$opts = {} unless $opts;
|
||||
$params = {} unless $params;
|
||||
|
||||
if ($Golem::DB) {
|
||||
if (! connection_bad($Golem::DB)) {
|
||||
return $Golem::DB;
|
||||
}
|
||||
else {
|
||||
print STDERR localtime() . " [$$]: new connection: was bad\n" if $ENV{'GOLEM_DEBUG'};
|
||||
$Golem::DB->disconnect;
|
||||
}
|
||||
}
|
||||
else {
|
||||
print STDERR localtime() . " [$$]: new connection: had none\n" if $ENV{'GOLEM_DEBUG'};
|
||||
}
|
||||
undef $Golem::DB;
|
||||
|
||||
# DB connection defaults (unless programmer specified them)
|
||||
#
|
||||
$params->{'RaiseError'} = 0 unless defined($params->{'RaiseError'});
|
||||
$params->{'PrintError'} = 1 unless defined($params->{'PrintError'});
|
||||
$params->{'AutoCommit'} = 1 unless defined($params->{'AutoCommit'});
|
||||
|
||||
Golem::die("No Golem::DBINFO master defined")
|
||||
unless $Golem::DBINFO->{'master'};
|
||||
|
||||
my $dbinfo = $Golem::DBINFO->{'master'};
|
||||
my $fdsn = make_dbh_fdsn($dbinfo);
|
||||
|
||||
$Golem::DB = DBI->connect($fdsn, $dbinfo->{'user'}, $dbinfo->{'pass'}, $params);
|
||||
while (!$Golem::DB && $opts->{'retry_forever'}) {
|
||||
Golem::do_log("database not available, retrying", {"stderr" => 1});
|
||||
sleep 1;
|
||||
$Golem::DB = DBI->connect($fdsn, $dbinfo->{'user'}, $dbinfo->{'pass'}, $params);
|
||||
}
|
||||
Golem::die("Unable to connect to database: " . DBI->errstr)
|
||||
unless $Golem::DB;
|
||||
|
||||
$Golem::DB->do("SET NAMES " . $dbinfo->{'encoding'})
|
||||
if $dbinfo->{'encoding'};
|
||||
|
||||
if (connection_bad($Golem::DB)) {
|
||||
print STDERR "got fresh new bad handle, retrying\n" if $ENV{'GOLEM_DEBUG'};
|
||||
$Golem::DB = undef;
|
||||
$Golem::DB = Golem::get_db();
|
||||
}
|
||||
|
||||
$Golem::default_dc_obj = Golem::get_dc($Golem::default_dc);
|
||||
return $Golem::DB;
|
||||
}
|
||||
|
||||
sub get_planer_db {
|
||||
my ($params) = @_;
|
||||
|
||||
return $Golem::PlanerDB if $Golem::PlanerDB;
|
||||
|
||||
$params = {RaiseError => 0, PrintError => 1, AutoCommit => 1}
|
||||
unless $params;
|
||||
|
||||
$Golem::PlanerDB = DBI->connect("DBI:Sybase:server=argo3.yandex.ru;database=planer;",
|
||||
"helpdesk", "gkfyshfcnfvfys123", $params);
|
||||
|
||||
return $Golem::PlanerDB;
|
||||
}
|
||||
|
||||
sub get_calendar_db {
|
||||
my ($params) = @_;
|
||||
|
||||
return $Golem::CalendarDB if $Golem::CalendarDB;
|
||||
|
||||
$params = {RaiseError => 0, PrintError =>1, AutoCommit => 1}
|
||||
unless $params;
|
||||
|
||||
$Golem::CalendarDB = DBI->connect("DBI:Sybase:server=argo3.yandex.ru;database=momdb;",
|
||||
"staffreader", "cegthgfhjkm678", $params);
|
||||
|
||||
return $Golem::CalendarDB;
|
||||
}
|
||||
|
||||
sub get_otrs_db {
|
||||
my ($params) = @_;
|
||||
|
||||
return $Golem::OtrsDB if $Golem::OtrsDB;
|
||||
|
||||
$params = {RaiseError => 0, PrintError =>1, AutoCommit => 1}
|
||||
unless $params;
|
||||
|
||||
$Golem::OtrsDB = DBI->connect("DBI:mysql:database=otrs_utf8:host=casa.yandex.ru:port=3306",
|
||||
"userorder", "xuo9Bahf", $params);
|
||||
|
||||
return $Golem::OtrsDB;
|
||||
}
|
||||
|
||||
sub get_swerrs_db {
|
||||
my ($params) = @_;
|
||||
|
||||
return $Golem::SwerrsDB if $Golem::SwerrsDB;
|
||||
|
||||
$params = { RaiseError => 0, PrintError => 1, AutoCommit => 1 }
|
||||
unless $params;
|
||||
|
||||
$Golem::SwerrsDB = DBI->connect("DBI:mysql:racktables:localhost:3306", "swerrs", "V7Hl}O]Usr", $params);
|
||||
|
||||
return $Golem::SwerrsDB;
|
||||
}
|
||||
|
||||
|
||||
sub sth_bind_array {
|
||||
my ($sth, $bound_values) = @_;
|
||||
|
||||
my $i = 0;
|
||||
foreach my $b (@{$bound_values}) {
|
||||
$i++;
|
||||
|
||||
Golem::die("error binding params")
|
||||
unless $sth->bind_param($i, $b) ;
|
||||
}
|
||||
}
|
||||
|
||||
# courtesy of LiveJournal.org
|
||||
# see also: http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_last-insert-id
|
||||
#
|
||||
sub alloc_global_counter {
|
||||
my ($tag, $recurse) = @_;
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $newmax;
|
||||
|
||||
# in case name `counter` is already occupied
|
||||
# by some user table
|
||||
my $counter_prefix = "";
|
||||
$counter_prefix = $Golem::counter_prefix
|
||||
if defined($Golem::counter_prefix);
|
||||
|
||||
my $rs = $dbh->do("UPDATE ${counter_prefix}counter SET max=LAST_INSERT_ID(max+1) WHERE tag=?", undef, $tag);
|
||||
if ($rs > 0) {
|
||||
$newmax = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");
|
||||
return $newmax;
|
||||
}
|
||||
|
||||
return undef if $recurse;
|
||||
|
||||
# no prior counter rows - initialize one.
|
||||
|
||||
# if this is a table then trying default id column
|
||||
if ($Golem::SCHEMA_CACHE->{'tables'}->{$tag}) {
|
||||
$newmax = $dbh->selectrow_array("SELECT MAX(id) FROM `$tag`");
|
||||
}
|
||||
else {
|
||||
Golem::die("alloc_global_counter: unknown tag [$tag], unable to get max value.");
|
||||
}
|
||||
|
||||
$newmax += 0;
|
||||
|
||||
$dbh->do("INSERT IGNORE INTO ${counter_prefix}counter (tag, max) VALUES (?,?)",
|
||||
undef, $tag, $newmax) || return undef;
|
||||
|
||||
return Golem::alloc_global_counter($tag, 1);
|
||||
}
|
||||
|
||||
# get schema table definition,
|
||||
# prepare in-memory table structure
|
||||
#
|
||||
sub get_schema_table {
|
||||
my ($table_name, $opts) = @_;
|
||||
|
||||
return $Golem::SCHEMA_CACHE->{'tables'}->{$table_name}
|
||||
if $Golem::SCHEMA_CACHE->{'tables'}->{$table_name} &&
|
||||
!$opts->{'force'};
|
||||
|
||||
delete($Golem::SCHEMA_CACHE->{'tables'}->{$table_name})
|
||||
if $Golem::SCHEMA_CACHE->{'tables'}->{$table_name};
|
||||
|
||||
$Golem::SCHEMA_CACHE->{'tables'}->{$table_name}->{'fields'} = {};
|
||||
my $t = $Golem::SCHEMA_CACHE->{'tables'}->{$table_name};
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
Golem::debug_sql("describe `$table_name`");
|
||||
my $sth = $dbh->prepare("describe `$table_name`");
|
||||
$sth->execute();
|
||||
Golem::die("Error describing table [$table_name]: " . $dbh->errstr)
|
||||
if $dbh->err;
|
||||
|
||||
my $select_all_sql = "";
|
||||
while (my $r = $sth->fetchrow_hashref) {
|
||||
my $field_name = $r->{'Field'};
|
||||
|
||||
$t->{'fields'}->{$field_name} = $r;
|
||||
|
||||
if ($r->{'Type'} =~ /^enum\((.+)\)/o) {
|
||||
my $enums = $1;
|
||||
foreach my $etype (split(/,/o, $enums)) {
|
||||
$etype =~ s/'//go;
|
||||
$t->{'fields'}->{$field_name}->{'enum'}->{$etype} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($r->{'Type'} eq 'timestamp') {
|
||||
$select_all_sql .= "UNIX_TIMESTAMP(`$field_name`) as `$field_name`, ";
|
||||
}
|
||||
else {
|
||||
$select_all_sql .= "`$field_name`, ";
|
||||
}
|
||||
|
||||
if ($r->{'Key'} eq 'PRI') {
|
||||
$t->{'primary_key'}->{$field_name} = 1;
|
||||
}
|
||||
}
|
||||
chop($select_all_sql);
|
||||
chop($select_all_sql);
|
||||
|
||||
$Golem::SCHEMA_CACHE->{'tables'}->{$table_name}->{'select_all_sql'} = $select_all_sql;
|
||||
|
||||
return $Golem::SCHEMA_CACHE->{'tables'}->{$table_name};
|
||||
}
|
||||
|
||||
# function tells whether field is data field or some special field
|
||||
# like host.id (incremented with alloc_global_counter) or
|
||||
# like host.last_updated (automatically updated when record is updated)
|
||||
# maybe we should filter them by name instead of using db structure hints?
|
||||
sub is_data_field {
|
||||
my ($table_name, $field_name, $opts) = @_;
|
||||
|
||||
$opts = {} unless $opts;
|
||||
|
||||
my $table = Golem::get_schema_table($table_name);
|
||||
my $table_fields = $table->{'fields'};
|
||||
|
||||
if ($table_fields->{$field_name}) {
|
||||
if ($table_fields->{$field_name}->{'Default'} &&
|
||||
$table_fields->{$field_name}->{'Default'} eq 'CURRENT_TIMESTAMP' &&
|
||||
!$opts->{'ignore_default_current_timestamp'} ) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
# if ($table_fields->{$field_name}->{'Key'} &&
|
||||
# $table_fields->{$field_name}->{'Key'} eq 'PRI') {
|
||||
#
|
||||
# return 0;
|
||||
# }
|
||||
|
||||
# we have to distinguish between host.id and host_rackmap.host;
|
||||
# both are PRIMARY keys, but
|
||||
# 1) we shouldn't ever update host.id
|
||||
# 2) we have to update host_rackmap.host with host.id
|
||||
# when creating record corresponding to host record
|
||||
if ($field_name eq "id" && !$opts->{'manual_id_management'}) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub __insert {
|
||||
my ($table_name, $record_hashref, $opts) = @_;
|
||||
|
||||
Golem::die("Severe programmer error: __insert expects table name as first parameter!")
|
||||
unless $table_name;
|
||||
Golem::die("Severe programmer error: __insert expects record hashref as second parameter!")
|
||||
unless ref($record_hashref) eq 'HASH';
|
||||
|
||||
$opts = {} unless ref($opts) eq 'HASH';
|
||||
|
||||
my $dbh;
|
||||
if ($opts->{'dbh'}) {
|
||||
$dbh = $opts->{'dbh'};
|
||||
}
|
||||
else {
|
||||
$dbh = Golem::get_db();
|
||||
}
|
||||
|
||||
$dbh->{'PrintError'} = $opts->{'PrintError'}
|
||||
if defined($opts->{'PrintError'});
|
||||
|
||||
my $sth;
|
||||
|
||||
my $table = Golem::get_schema_table($table_name);
|
||||
my $table_fields = $table->{'fields'};
|
||||
|
||||
# continue only if there's data for the table
|
||||
# or if there's a flag saying we should create
|
||||
# empty record with defaults
|
||||
my $have_data_for_the_table = 0;
|
||||
while (my ($o, $v) = each(%{$record_hashref})) {
|
||||
if (Golem::is_data_field($table_name, $o, $opts)) {
|
||||
$have_data_for_the_table = 1;
|
||||
}
|
||||
}
|
||||
unless ($have_data_for_the_table || $opts->{'create_empty_record'}) {
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
my @record_fields;
|
||||
my @record_values;
|
||||
|
||||
foreach my $o (keys %{$record_hashref}) {
|
||||
# $record_hashref might contain more fields than present in database.
|
||||
# we only choose those which are in db
|
||||
|
||||
if ($table_fields->{$o} && Golem::is_data_field($table_name, $o, $opts)) {
|
||||
|
||||
# enum validation
|
||||
if ($table_fields->{$o}->{'enum'}) {
|
||||
Golem::die("Enum [$table_name.$o] value is not specified and doesn't have default value")
|
||||
if !defined($record_hashref->{$o}) && $table_fields->{$o}->{'Default'} eq '';
|
||||
|
||||
Golem::die("Enum [$table_name.$o] can't be [$record_hashref->{$o}]")
|
||||
if $record_hashref->{$o} && !$table_fields->{$o}->{'enum'}->{$record_hashref->{$o}};
|
||||
|
||||
# if they passed empty value for enum
|
||||
# and there's some default -- silently
|
||||
# decide to use it
|
||||
unless ($record_hashref->{$o}) {
|
||||
delete($record_hashref->{$o});
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
push @record_fields, $o;
|
||||
push @record_values, $record_hashref->{$o};
|
||||
}
|
||||
}
|
||||
|
||||
if ($table_fields->{"id"} && !$opts->{'manual_id_management'}) {
|
||||
if ($record_hashref->{"id"}) {
|
||||
Golem::die("Severe database structure or programmer error: __insert got id [$record_hashref->{'id'}]
|
||||
when creating record for table [$table_name]; won't overwrite.\n");
|
||||
}
|
||||
|
||||
$record_hashref->{"id"} = Golem::alloc_global_counter($table_name);
|
||||
|
||||
# check that id is not taken and
|
||||
# die with severe error otherwise
|
||||
#
|
||||
|
||||
my $t_id = $dbh->selectrow_array("select id from `$table_name` where id = ?",
|
||||
undef, $record_hashref->{"id"});
|
||||
if ($t_id && $t_id eq $record_hashref->{"id"}) {
|
||||
Golem::die("Severe database error: __insert got [$t_id] for table [$table_name] " .
|
||||
"from alloc_global_counter which already exists!\n" .
|
||||
"Probable somebody is populating [$table_name] without Golem::__insert()\n");
|
||||
}
|
||||
|
||||
push @record_fields, "id";
|
||||
push @record_values, $record_hashref->{"id"};
|
||||
}
|
||||
|
||||
my $sql;
|
||||
my @bound_values;
|
||||
|
||||
$sql = "INSERT INTO `$table_name` ( ";
|
||||
foreach my $o (@record_fields) {
|
||||
$sql = $sql . " `$o`,";
|
||||
}
|
||||
chop($sql);
|
||||
|
||||
$sql .= " ) VALUES ( ";
|
||||
|
||||
my $i = 0;
|
||||
foreach my $o (@record_values) {
|
||||
|
||||
# we represent timestamp datatype as unixtime (http://en.wikipedia.org/wiki/Unix_time)
|
||||
# doing all the conversions almost invisible to the end user
|
||||
#
|
||||
# if the value being written is 0 then we're not using FROM_UNIXTIME(value)
|
||||
# (which generates warnings) just value
|
||||
#
|
||||
if ($table_fields->{$record_fields[$i]}->{'Type'} eq 'timestamp' && $o && $o != 0) {
|
||||
Golem::die("Programmer error: __insert got hashref with invalid data for $table_name.$record_fields[$i] (should be unixtime)")
|
||||
unless $o =~ /^[0-9]+$/o;
|
||||
|
||||
$sql = $sql . "FROM_UNIXTIME(?),";
|
||||
}
|
||||
else {
|
||||
$sql = $sql . "?,";
|
||||
}
|
||||
|
||||
push @bound_values, $o;
|
||||
$i++;
|
||||
}
|
||||
chop($sql);
|
||||
$sql .= " )";
|
||||
|
||||
Golem::debug_sql($sql, \@bound_values);
|
||||
|
||||
$sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
if ($dbh->err && $dbh->{'PrintError'}) {
|
||||
Golem::do_log("got error [" . $dbh->err . "] [" . $dbh->errstr . "]" .
|
||||
" while executing [$sql] with values (" . join(",", @bound_values) . ")",
|
||||
{'stderr' => 1});
|
||||
}
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
sub __update {
|
||||
my ($table_name, $record_hashref, $opts) = @_;
|
||||
|
||||
Golem::die("Severe programmer error: __update expects table name as first parameter!")
|
||||
unless $table_name;
|
||||
Golem::die("Severe programmer error: __update expects record hashref as second parameter!")
|
||||
unless ref($record_hashref) eq 'HASH';
|
||||
|
||||
$opts = {} unless ref($opts) eq 'HASH';
|
||||
|
||||
my $dbh;
|
||||
if ($opts->{'dbh'}) {
|
||||
$dbh = $opts->{'dbh'};
|
||||
}
|
||||
else {
|
||||
$dbh = Golem::get_db();
|
||||
}
|
||||
|
||||
my $sth;
|
||||
|
||||
my $table = Golem::get_schema_table($table_name);
|
||||
my $table_fields = $table->{'fields'};
|
||||
my $unique_fields_arrayref = [keys %{$table->{'primary_key'}}];
|
||||
|
||||
if ($opts->{'unique_fields'}) {
|
||||
$unique_fields_arrayref = $opts->{'unique_fields'};
|
||||
}
|
||||
|
||||
# continue only if there's data for the table
|
||||
# in the in-memory hash or if there's a flag
|
||||
# saying we should create empty record with defaults
|
||||
#
|
||||
my $have_data_for_the_table = 0;
|
||||
while (my ($o, $v) = each(%{$record_hashref})) {
|
||||
if (Golem::is_data_field($table_name, $o)) {
|
||||
my $is_unique = 0;
|
||||
|
||||
foreach my $u (@{$unique_fields_arrayref}) {
|
||||
if ($u eq $o) {
|
||||
$is_unique = 1;
|
||||
}
|
||||
}
|
||||
next if $is_unique;
|
||||
|
||||
$have_data_for_the_table = 1;
|
||||
}
|
||||
}
|
||||
unless ($have_data_for_the_table || $opts->{'create_empty_record'}) {
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
my $sql;
|
||||
my @bound_values;
|
||||
|
||||
$sql = "SELECT " . $table->{'select_all_sql'} . " from `$table_name` WHERE ";
|
||||
foreach my $f (@{$unique_fields_arrayref}) {
|
||||
if ($table_fields->{$f}->{'Type'} eq 'timestamp' && $record_hashref->{$f} != 0) {
|
||||
Golem::die("Programmer error: __update got hashref with invalid data for $table_name.$f (should be unixtime)")
|
||||
unless $record_hashref->{$f} =~ /^[0-9]+$/o;
|
||||
|
||||
$sql .= " `$f` = FROM_UNIXTIME(?) and ";
|
||||
}
|
||||
else {
|
||||
$sql .= " `$f` = ? and ";
|
||||
}
|
||||
push @bound_values, $record_hashref->{$f};
|
||||
}
|
||||
# remove last "and "
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
|
||||
$sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
# create record if it doesn't exist: useful when updating
|
||||
# records in dependent tables (hosts_resps, hosts_netmap, host_rackmap)
|
||||
# when master table exists.
|
||||
unless ($sth->rows) {
|
||||
if ($opts->{"create_nonexistent"}) {
|
||||
$dbh = Golem::__insert($table_name, $record_hashref, $opts);
|
||||
return $dbh;
|
||||
}
|
||||
else {
|
||||
Golem::die("Programmer error: requested to update non-existent record with no create_nonexistent option");
|
||||
}
|
||||
}
|
||||
|
||||
my $existing_row;
|
||||
while(my $r = $sth->fetchrow_hashref()) {
|
||||
Golem::debug_sql($sql, \@bound_values);
|
||||
Golem::die("more than 1 record fetched with should-be-unique lookup")
|
||||
if $existing_row;
|
||||
|
||||
$existing_row = $r;
|
||||
}
|
||||
|
||||
# check that existing record differs somehow from record to be written
|
||||
my $records_differ = 0;
|
||||
while (my ($k, $v) = each %{$existing_row}) {
|
||||
if (Golem::is_data_field($table_name, $k)) {
|
||||
|
||||
# what a mess!
|
||||
utf8::decode($record_hashref->{$k});
|
||||
utf8::decode($v);
|
||||
|
||||
if (
|
||||
($record_hashref->{$k} && $v && $v ne $record_hashref->{$k}) ||
|
||||
(! $record_hashref->{$k} && $v) ||
|
||||
($record_hashref->{$k} && ! $v)
|
||||
) {
|
||||
|
||||
Golem::debug_sql("in-memory [$table_name] object field [$k] differs: [" .
|
||||
($record_hashref->{$k} ? $record_hashref->{$k} : "") . "
|
||||
] -- [" .
|
||||
($v ? $v : "") .
|
||||
"]");
|
||||
|
||||
$records_differ = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# don't update database if that wouldn't actually
|
||||
# change any data; we should save A LOT of time here
|
||||
#
|
||||
return $dbh unless $records_differ;
|
||||
|
||||
@bound_values = ();
|
||||
$sql = "";
|
||||
while (my ($o, $v) = each(%{$record_hashref})) {
|
||||
# $record_hashref might contain more fields than present in database.
|
||||
# we only choose those which are in db
|
||||
if ($table_fields->{$o} && Golem::is_data_field($table_name, $o)) {
|
||||
if ($table_fields->{$o}->{'Type'} eq 'timestamp' && $record_hashref->{$o} && $record_hashref->{$o} != 0) {
|
||||
Golem::die("Programmer error: __update got hashref with invalid data for $table_name.$o (should be unixtime)")
|
||||
unless $record_hashref->{$o} =~ /^[0-9]+$/o;
|
||||
|
||||
$sql = $sql . " `$o` = FROM_UNIXTIME(?),";
|
||||
}
|
||||
else {
|
||||
$sql = $sql . " `$o` = ?,";
|
||||
}
|
||||
push @bound_values, $record_hashref->{$o};
|
||||
}
|
||||
}
|
||||
chop($sql);
|
||||
|
||||
$sql = "UPDATE `$table_name` SET " . $sql . " WHERE ";
|
||||
foreach my $f (@{$unique_fields_arrayref}) {
|
||||
$sql .= " `$f` = ? and ";
|
||||
push @bound_values, $record_hashref->{$f};
|
||||
}
|
||||
# remove last "and "
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
|
||||
Golem::debug_sql($sql, \@bound_values);
|
||||
|
||||
$sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
if ($dbh->err) {
|
||||
Golem::do_log("error executing: $sql; bound values: " . join(",", @bound_values), {"stderr" => 1});
|
||||
}
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
|
||||
sub __delete {
|
||||
my ($table_name, $record_hashref, $opts) = @_;
|
||||
|
||||
Golem::die("Severe programmer error: __delete expects table name as first parameter!")
|
||||
unless $table_name;
|
||||
Golem::die("Severe programmer error: __delete expects record hashref as second parameter!")
|
||||
unless ref($record_hashref) eq 'HASH';
|
||||
|
||||
$opts = {} unless ref($opts) eq 'HASH';
|
||||
|
||||
my $dbh;
|
||||
if ($opts->{'dbh'}) {
|
||||
$dbh = $opts->{'dbh'};
|
||||
}
|
||||
else {
|
||||
$dbh = Golem::get_db();
|
||||
}
|
||||
|
||||
my $sth;
|
||||
|
||||
my $table = Golem::get_schema_table($table_name);
|
||||
my $table_fields = $table->{'fields'};
|
||||
my $unique_fields_arrayref = [keys %{$table->{'primary_key'}}];
|
||||
|
||||
if ($opts->{'unique_fields'}) {
|
||||
$unique_fields_arrayref = $opts->{'unique_fields'};
|
||||
}
|
||||
|
||||
my @bound_values = ();
|
||||
my $sql = "DELETE FROM `$table_name` WHERE ";
|
||||
|
||||
foreach my $f (@{$unique_fields_arrayref}) {
|
||||
$sql .= " `$f` = ? and ";
|
||||
push @bound_values, $record_hashref->{$f};
|
||||
}
|
||||
# remove last "and "
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
chop($sql);
|
||||
|
||||
$sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
if ($dbh->err) {
|
||||
Golem::do_log("error executing: $sql; bound values: " . join(",", @bound_values), {"stderr" => 1});
|
||||
}
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
197
local/cgi-bin/Golem/loglib.pl
Normal file
197
local/cgi-bin/Golem/loglib.pl
Normal file
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# Logging related routines
|
||||
#
|
||||
|
||||
|
||||
package Golem;
|
||||
use strict;
|
||||
|
||||
use Golem;
|
||||
use Data::Dumper;
|
||||
|
||||
|
||||
sub dumper {
|
||||
my ($v) = @_;
|
||||
|
||||
return Dumper($v);
|
||||
}
|
||||
|
||||
sub debug_sql {
|
||||
my ($text, $bound_values, $opts) = @_;
|
||||
|
||||
if ($Golem::debug_sql) {
|
||||
$opts = {} unless $opts;
|
||||
|
||||
$text = "SQL: [" . ($text ? $text : "") . "]";
|
||||
|
||||
if ($bound_values && ref($bound_values) eq 'ARRAY') {
|
||||
$text .= " bound_values [" . Golem::safe_join(",", @{$bound_values}) . "]";
|
||||
}
|
||||
|
||||
if ($opts->{'stderr'}) {
|
||||
print STDERR localtime() . " " . $text . "\n";
|
||||
}
|
||||
else {
|
||||
print localtime() . " " . $text . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub debug2 {
|
||||
my ($text, $opts) = @_;
|
||||
|
||||
if ($Golem::debug2) {
|
||||
debug($text, $opts);
|
||||
}
|
||||
}
|
||||
|
||||
sub debug {
|
||||
my ($text, $opts) = @_;
|
||||
|
||||
$opts = {} unless $opts;
|
||||
|
||||
if ($Golem::debug) {
|
||||
my $stamp = localtime() . ": ";
|
||||
|
||||
utf8::encode($text) if utf8::is_utf8($text);
|
||||
|
||||
if ($opts->{'stderr'}) {
|
||||
if (ref($text)) {
|
||||
print STDERR join("", map { "$stamp$_\n" } Dumper($text));
|
||||
}
|
||||
else {
|
||||
print STDERR $stamp . ($text ? $text : "") . "\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (ref($text)) {
|
||||
print join("", map { "$stamp$_\n" } Dumper($text));
|
||||
}
|
||||
else {
|
||||
print $stamp . ($text ? $text : "") . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# safe_open and safe_close are copied
|
||||
# from ps_farm.pm (should be one library actually)
|
||||
sub safe_open {
|
||||
my ($filename, $mode, $timeout) = @_;
|
||||
|
||||
$timeout = 30 unless $timeout;
|
||||
$mode = "open" unless $mode;
|
||||
|
||||
if ($mode eq "overwrite" || $mode eq ">") {
|
||||
$mode = ">";
|
||||
}
|
||||
elsif ($mode eq "append" || $mode eq ">>") {
|
||||
$mode = ">>";
|
||||
}
|
||||
else {
|
||||
$mode = "";
|
||||
}
|
||||
|
||||
my $fh;
|
||||
my $i=0;
|
||||
while (! open($fh, "${mode}${filename}")) {
|
||||
if ($i > $timeout) {
|
||||
print STDERR "Unable to open $filename\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
print STDERR "still trying to open $filename\n";
|
||||
$i = $i + 1;
|
||||
sleep 1;
|
||||
}
|
||||
|
||||
while (! flock($fh, 2)) {
|
||||
if ($i > $timeout) {
|
||||
print STDERR "Unable to lock $filename\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
print STDERR "still trying to lock $filename\n";
|
||||
$i = $i + 1;
|
||||
sleep 1;
|
||||
}
|
||||
|
||||
my $fh1;
|
||||
if (!open($fh1, "${mode}${filename}")) {
|
||||
$i = $i + 1;
|
||||
|
||||
if ($i > $timeout) {
|
||||
print STDERR "Unable to open and lock $filename\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
print STDERR "Locked $filename, but it's gone. Retrying...\n";
|
||||
return safe_open($filename, $mode, $timeout - 1);
|
||||
}
|
||||
else {
|
||||
close($fh1);
|
||||
return $fh;
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_close {
|
||||
my ($fh) = @_;
|
||||
return flock($fh, 8) && close($fh);
|
||||
}
|
||||
|
||||
sub do_log {
|
||||
my ($message, $opts) = @_;
|
||||
|
||||
my $module;
|
||||
my $stderr = 0;
|
||||
|
||||
if (ref($opts) eq 'HASH') {
|
||||
if ($opts->{'module'}) {
|
||||
$module = "[$opts->{'module'}] ";
|
||||
}
|
||||
if ($opts->{'stderr'}) {
|
||||
$stderr = $opts->{'stderr'};
|
||||
}
|
||||
}
|
||||
else {
|
||||
$module = $opts;
|
||||
$module = "[$module] " if $module;
|
||||
}
|
||||
|
||||
$message = "" unless $message;
|
||||
|
||||
utf8::encode($message) if utf8::is_utf8($message);
|
||||
|
||||
$module = "[" . $0 . "] " unless $module;
|
||||
|
||||
my $message_eol = chop($message);
|
||||
|
||||
my $message_formatted =
|
||||
localtime() . " " . $module .
|
||||
$message .
|
||||
$message_eol .
|
||||
($message_eol eq "\n" ? "" : "\n");
|
||||
|
||||
if ($stderr) {
|
||||
print STDERR $message_formatted;
|
||||
}
|
||||
elsif ($Golem::debug) {
|
||||
print $message_formatted;
|
||||
}
|
||||
|
||||
if (defined($Golem::LOG)) {
|
||||
my $fh = safe_open($Golem::LOG, ">>");
|
||||
Golem::die ("Unable to open $Golem::LOG\n") unless $fh;
|
||||
# binmode($fh, ":utf8");
|
||||
print $fh $message_formatted;
|
||||
safe_close($fh);
|
||||
}
|
||||
else {
|
||||
print STDERR "No Golem::LOG is configured. Logging to STDERR\n";
|
||||
print STDERR $message_formatted;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
330
local/cgi-bin/Golem/netlib.pl
Normal file
330
local/cgi-bin/Golem/netlib.pl
Normal file
@@ -0,0 +1,330 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# networking related routines
|
||||
#
|
||||
|
||||
|
||||
package Golem;
|
||||
use strict;
|
||||
|
||||
use Golem;
|
||||
|
||||
sub get_external_ipinfo {
|
||||
my ($ip) = @_;
|
||||
|
||||
return undef unless $ip && $Golem::noc_nets_cgi;
|
||||
|
||||
my $ipinfo;
|
||||
my @net_info;
|
||||
|
||||
my $wget_options = "";
|
||||
$wget_options = "--timeout=${Golem::noc_nets_cgi_timeout}"
|
||||
if $Golem::noc_nets_cgi_timeout;
|
||||
|
||||
open(NOCNET,"wget $wget_options -q -O - ${Golem::noc_nets_cgi}?name=$ip |");
|
||||
|
||||
Golem::debug("wget $wget_options -q -O - ${Golem::noc_nets_cgi}?name=$ip |");
|
||||
|
||||
my $line;
|
||||
while($line=<NOCNET>) {
|
||||
if ($line=~/^<tr align=center>/) {
|
||||
chomp($line);
|
||||
$line =~ s/<\/td><td>/\|/g;
|
||||
$line =~ s/(<td>|<tr align=center>|<\/tr>|<\/td>)//g;
|
||||
@net_info= split(/\|/,$line,5);
|
||||
}
|
||||
}
|
||||
close(NOCNET);
|
||||
|
||||
if (@net_info) {
|
||||
$ipinfo->{'ip'} = $net_info[0];
|
||||
$ipinfo->{'subnet'} = $net_info[1];
|
||||
$ipinfo->{'router'} = $net_info[2];
|
||||
$ipinfo->{'iface'} = $net_info[3];
|
||||
|
||||
$ipinfo->{'vlan'} = $ipinfo->{'iface'};
|
||||
$ipinfo->{'vlan'} =~ /(\d+)/;
|
||||
$ipinfo->{'vlan'} = $1;
|
||||
|
||||
$ipinfo->{'router_if_addr'} = $net_info[4];
|
||||
}
|
||||
|
||||
return $ipinfo;
|
||||
}
|
||||
|
||||
# --- ghetto code
|
||||
sub int2maskpart {
|
||||
my ($i) = @_;
|
||||
return "0" unless defined($i);
|
||||
|
||||
my $j = 0;
|
||||
my $bits = "";
|
||||
while ($j < 8) {
|
||||
if ($i <= $j) {
|
||||
$bits .= "0";
|
||||
}
|
||||
else {
|
||||
$bits .= "1";
|
||||
}
|
||||
$j++;
|
||||
}
|
||||
return oct("0b" . $bits);
|
||||
}
|
||||
|
||||
|
||||
sub mask2netmask {
|
||||
my ($j) = @_;
|
||||
|
||||
my @ip;
|
||||
my $i;
|
||||
for ($i = 1; $i <= int($j / 8); $i++) {
|
||||
push @ip, int2maskpart(8);
|
||||
}
|
||||
while ($i < 5) {
|
||||
push @ip, int2maskpart($j % 8);
|
||||
$j = 0;
|
||||
$i++;
|
||||
}
|
||||
|
||||
return join(".", @ip);
|
||||
}
|
||||
|
||||
|
||||
# convert string representation of ipv4 address into integer
|
||||
sub ipv4_str2int {
|
||||
my ($ip_string) = @_;
|
||||
|
||||
if ($ip_string =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) {
|
||||
return (16777216 * $1) + (65536 * $2) + (256 * $3) + $4;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# convert integer representation of ipv4 address into string
|
||||
sub ipv4_int2str {
|
||||
my ($ip_int) = @_;
|
||||
|
||||
if ($ip_int >= 0 && $ip_int <= 4294967295) {
|
||||
my $w = ($ip_int / 16777216) % 256;
|
||||
my $x = ($ip_int / 65536) % 256;
|
||||
my $y = ($ip_int / 256) % 256;
|
||||
my $z = $ip_int % 256;
|
||||
|
||||
return $w . "." . $x . "." . $y . "." . $z;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# /24 -> +255
|
||||
# /27 -> +31
|
||||
# /28 -> +15
|
||||
# /29 -> +7
|
||||
sub ipv4_mask2offset {
|
||||
my ($mask) = @_;
|
||||
$mask ||= 0;
|
||||
my $offset = 2 ** (32 - $mask);
|
||||
return $offset - 1;
|
||||
}
|
||||
|
||||
sub get_net {
|
||||
my ($ip, $mask, $opts) = @_;
|
||||
|
||||
Golem::trim(\$ip);
|
||||
|
||||
Golem::die("Programmer error: get_net expects net and mask")
|
||||
unless
|
||||
($ip && $mask && Golem::is_digital($mask)) ||
|
||||
($ip =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/);
|
||||
|
||||
if ($ip =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,2})$/o) {
|
||||
$ip = ipv4_str2int($1);
|
||||
$mask = $2;
|
||||
}
|
||||
elsif ($ip =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/o) {
|
||||
$ip = ipv4_str2int($1);
|
||||
}
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $sth = $dbh->prepare("SELECT * FROM net_v4 WHERE ip = ? and mask = ?");
|
||||
$sth->execute($ip, $mask);
|
||||
my $net = $sth->fetchrow_hashref();
|
||||
$sth->finish();
|
||||
|
||||
if ($net->{'id'}) {
|
||||
return Golem::get_net_by_id($net->{'id'}, $opts);
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub get_net_by_id {
|
||||
my ($id, $opts) = @_;
|
||||
|
||||
Golem::die("Programmer error: get_net_by_id expects network id")
|
||||
unless $id;
|
||||
|
||||
$opts = {} unless $opts;
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $sth = $dbh->prepare("SELECT * FROM net_v4 WHERE id = ?");
|
||||
|
||||
$sth->execute($id);
|
||||
|
||||
my $r = $sth->fetchrow_hashref();
|
||||
if ($r->{'id'}) {
|
||||
$r->{'ip_str'} = Golem::ipv4_int2str($r->{'ip'});
|
||||
$r->{'net_with_mask'} = $r->{'ip_str'} . "/" . $r->{'mask'};
|
||||
if ($r->{'name'} =~ /VLAN([\d]+)/o) {
|
||||
$r->{'vlan'} = $1;
|
||||
}
|
||||
else {
|
||||
$r->{'vlan'} = "";
|
||||
}
|
||||
|
||||
if ($opts->{'with_props'} || $opts->{'with_all'}) {
|
||||
$r = Golem::load_props("net_v4", $r);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub insert_net {
|
||||
my ($net) = @_;
|
||||
|
||||
Golem::die("Programmer error: insert_net expects net object")
|
||||
unless $net && ($net->{'ip_str'} || $net->{'ip'}) &&
|
||||
$net->{'mask'} && $net->{'name'};
|
||||
|
||||
if ($net->{'ip_str'}) {
|
||||
$net->{'ip'} = Golem::ipv4_str2int($net->{'ip_str'});
|
||||
}
|
||||
|
||||
my $enet = Golem::get_net($net->{'ip'}, $net->{'mask'});
|
||||
return Golem::err("net already exists [$enet->{'ip_str'}/$enet->{'$mask'} ($enet->{'id'})]")
|
||||
if $enet;
|
||||
|
||||
my $dbh = Golem::__insert("net_v4", $net);
|
||||
return Golem::err($dbh->errstr, $net)
|
||||
if $dbh->err;
|
||||
|
||||
$net->{'id'} = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");
|
||||
|
||||
return Golem::get_net_by_id($net->{'id'});
|
||||
}
|
||||
|
||||
sub save_net {
|
||||
my ($net) = @_;
|
||||
|
||||
Golem::die("Programmer error: save_net expects net object")
|
||||
unless $net && $net->{'id'} &&
|
||||
($net->{'ip_str'} || $net->{'ip'}) &&
|
||||
$net->{'mask'} && $net->{'name'};
|
||||
|
||||
if ($net->{'ip_str'}) {
|
||||
$net->{'ip'} = Golem::ipv4_str2int($net->{'ip_str'});
|
||||
}
|
||||
|
||||
my $enet = Golem::get_net($net->{'ip'}, $net->{'mask'}, {"with_all" => 1});
|
||||
|
||||
if ($enet) {
|
||||
my $dbh = Golem::__update("net_v4", $net);
|
||||
return Golem::err($dbh->errstr, $net)
|
||||
if $dbh->err;
|
||||
|
||||
$net = Golem::save_props("net_v4", $net);
|
||||
}
|
||||
else {
|
||||
return Golem::insert_net($net);
|
||||
}
|
||||
|
||||
return $net;
|
||||
}
|
||||
|
||||
sub delete_net {
|
||||
my ($net) = @_;
|
||||
|
||||
Golem::die("Programmer error: delete_net expects net object")
|
||||
unless $net && $net->{'id'};
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
|
||||
Golem::unset_row_tag("net_v4", $net->{'id'});
|
||||
$dbh->do("DELETE from net_v4prop where net_v4id = ?", undef, $net->{'id'});
|
||||
$dbh->do("DELETE from net_v4propblob where net_v4id = ?", undef, $net->{'id'});
|
||||
|
||||
$dbh->do("DELETE FROM net_v4 WHERE net_v4.id = ?", undef, $net->{'id'});
|
||||
return Golem::err($dbh->errstr, $net)
|
||||
if $dbh->err;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
sub get_containing_net {
|
||||
my ($ip, $opts) = @_;
|
||||
|
||||
Golem::die("Programmer error: get_containing_net expects ip address")
|
||||
unless $ip;
|
||||
|
||||
Golem::trim(\$ip);
|
||||
if ($ip =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/o) {
|
||||
$ip = Golem::ipv4_str2int($1);
|
||||
}
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
|
||||
# choose nearest (by ip desc) and smallest (by mask desc) known network
|
||||
my $sth = $dbh->prepare("SELECT * FROM net_v4 WHERE ip < ? and mask <> 32
|
||||
order by ip desc, mask desc");
|
||||
|
||||
$sth->execute($ip);
|
||||
|
||||
my $net;
|
||||
|
||||
while(my $r = $sth->fetchrow_hashref()) {
|
||||
# choose first network that includes tested ip address if any
|
||||
if ($r->{'ip'} + ipv4_mask2offset($r->{'mask'}) ge $ip) {
|
||||
return Golem::get_net_by_id($r->{'id'}, $opts);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub get_net_by_vlan {
|
||||
my ($vlan, $opts) = @_;
|
||||
|
||||
Golem::die("Programmer error: get_net_by_vlan expects vlan name")
|
||||
unless $vlan;
|
||||
|
||||
$vlan =~ s/vlan//go;
|
||||
$vlan =~ s/\s//go;
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $sth = $dbh->prepare("SELECT * FROM net_v4 WHERE mask <> 32 and name like ? order by name");
|
||||
$sth->execute("VLAN${vlan}%");
|
||||
|
||||
while (my $r = $sth->fetchrow_hashref()) {
|
||||
if ($r->{'name'} =~ /VLAN${vlan}\s/o) {
|
||||
return Golem::get_net($r->{'ip'}, $r->{'mask'}, $opts);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub is_ipv4 {
|
||||
my ($str) = @_;
|
||||
return $str =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/o;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
393
local/cgi-bin/Golem/proplib.pl
Normal file
393
local/cgi-bin/Golem/proplib.pl
Normal file
@@ -0,0 +1,393 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# Properties manipulation routines
|
||||
#
|
||||
#
|
||||
# This is an object property library which
|
||||
# implements DB -> MEMORY (load_props)
|
||||
# and MEMORY -> DB (save_props) transition
|
||||
# of object properties
|
||||
#
|
||||
# Object is defined as a record in some TABLE
|
||||
# (called owner). To be able to use properties
|
||||
# for the given TABLE you should create
|
||||
# another two tables: TABLEprop and TABLEpropblob
|
||||
#
|
||||
# Example for host object:
|
||||
#
|
||||
# CREATE TABLE `hostprop` (
|
||||
# `hostid` int(11) NOT NULL,
|
||||
# `propid` smallint(6) NOT NULL default '0',
|
||||
# `propseq` int(11) NOT NULL default '0',
|
||||
# `value` varchar(1024) default NULL,
|
||||
# PRIMARY KEY (`hostid`,`propid`,`propseq`),
|
||||
# KEY `prop` (`propid`)
|
||||
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
||||
#
|
||||
# CREATE TABLE `hostpropblob` (
|
||||
# `hostid` int(11) NOT NULL,
|
||||
# `propid` smallint(6) NOT NULL default '0',
|
||||
# `propseq` int(11) NOT NULL default '0',
|
||||
# `value` blob,
|
||||
# PRIMARY KEY (`hostid`,`propid`,`propseq`),
|
||||
# KEY `prop` (`propid`)
|
||||
# ) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
||||
#
|
||||
# After that you should create "allowed" properties
|
||||
# for the owner using `gconsole.pl --create-proplist`
|
||||
#
|
||||
# You could see all the defined properties for the owner
|
||||
# using gconsole.pl --list-proplist OWNER
|
||||
#
|
||||
#
|
||||
# For the owner (tables) which primary key is not simple
|
||||
# id auto_increment (as in the example above) the following
|
||||
# TABLEprop and TABLEpropblob structure should be used:
|
||||
#
|
||||
|
||||
package Golem;
|
||||
use strict;
|
||||
|
||||
use Golem;
|
||||
|
||||
use Storable;
|
||||
|
||||
|
||||
# check that given property owner is valid
|
||||
# currently there are two valid property owners: host, user
|
||||
#
|
||||
sub check_prop_owner {
|
||||
my ($owner) = @_;
|
||||
|
||||
Golem::die("Programmer error: check_prop_owner got empty prop owner")
|
||||
unless $owner;
|
||||
|
||||
my $props = {
|
||||
"eventhistory" => 1,
|
||||
"host" => 1,
|
||||
"user" => 1,
|
||||
"net_v4" => 1,
|
||||
};
|
||||
|
||||
Golem::die("Programmer error: not valid property owner [$owner]")
|
||||
unless defined($props->{$owner});
|
||||
}
|
||||
|
||||
# get property definition record(s) from database
|
||||
# for the specified owner
|
||||
#
|
||||
sub get_proplist {
|
||||
my ($owner, $hpname) = @_;
|
||||
|
||||
Golem::die("Programmer error: get_proplist expects at least owner")
|
||||
unless $owner;
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $sth;
|
||||
my $ret;
|
||||
|
||||
if ($hpname) {
|
||||
$sth = $dbh->prepare("SELECT * FROM proplist WHERE owner = ? and name = ?");
|
||||
$sth->execute($owner, $hpname);
|
||||
$ret = $sth->fetchrow_hashref();
|
||||
$ret = 0 unless $ret->{'id'};
|
||||
}
|
||||
else {
|
||||
$sth = $dbh->prepare("SELECT * FROM proplist where owner = ?");
|
||||
$sth->execute($owner);
|
||||
while (my $r = $sth->fetchrow_hashref()) {
|
||||
$ret->{$r->{'name'}} = $r;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
sub create_proplist {
|
||||
my ($owner, $op) = @_;
|
||||
|
||||
Golem::die("Programmer error: create_proplist expects at least owner and property name")
|
||||
unless $owner && $op && $op->{'name'};
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
my $eop = Golem::get_proplist($owner, $op->{'name'});
|
||||
Golem::die("proplist record already exists [$eop->{'name'} ($eop->{'id'})]")
|
||||
if $eop;
|
||||
|
||||
$op->{'owner'} = $owner;
|
||||
|
||||
my $dbh = Golem::__insert("proplist", $op);
|
||||
Golem::die("Error creating proplist: " . $dbh->errstr, $op)
|
||||
if $dbh->err;
|
||||
|
||||
$op->{'id'} = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");
|
||||
|
||||
Golem::do_log("new proplist record: [$op->{'owner'}/$op->{'name'}] ($op->{'id'})");
|
||||
|
||||
return $op;
|
||||
}
|
||||
|
||||
sub delete_proplist {
|
||||
my ($owner, $op) = @_;
|
||||
|
||||
Golem::die("Programmer error: delete_proplist expects proplist record")
|
||||
unless $op && $op->{'id'} && $op->{'name'};
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
my $eop = Golem::get_proplist($owner, $op->{'name'});
|
||||
|
||||
return Golem::err("delete_proplist: invalid proplist record")
|
||||
unless $eop;
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $objs_with_prop =
|
||||
$dbh->selectrow_array("select count(*) from ${owner}prop where propid = ?", undef, $op->{'id'}) +
|
||||
$dbh->selectrow_array("select count(*) from ${owner}propblob where propid = ?", undef, $op->{'id'})
|
||||
;
|
||||
|
||||
return Golem::err("delete_proplist: unable to delete proplist record; $objs_with_prop records are using it")
|
||||
if $objs_with_prop;
|
||||
|
||||
$dbh->do("delete from proplist where id = ?", undef, $op->{'id'});
|
||||
|
||||
return Golem::err("error while deleting from proplist: " . $dbh->errstr)
|
||||
if $dbh->err;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
# read object properties for the given owner
|
||||
# (exactly matches table name)
|
||||
#
|
||||
sub load_props {
|
||||
my ($owner, $o) = @_;
|
||||
|
||||
Golem::die("Programmer error: load_props expects owner name and object")
|
||||
unless $owner && ref($o);
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
my $table = Golem::get_schema_table($owner);
|
||||
my $pk = $table->{'primary_key'};
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
my $sth;
|
||||
|
||||
$o->{'props'}->{'data'} = {};
|
||||
|
||||
foreach my $t ("${owner}prop", "${owner}propblob") {
|
||||
my $sql = "select * from $t inner join proplist on proplist.id = $t.propid WHERE 1 ";
|
||||
my @bound_values = ();
|
||||
while (my ($pk_field, $dummy) = each %{$table->{'primary_key'}}) {
|
||||
if ($pk_field eq 'id') {
|
||||
$sql .= " and `${owner}id` = ? ";
|
||||
}
|
||||
else {
|
||||
$sql .= " and `$pk_field` = ? ";
|
||||
}
|
||||
|
||||
Golem::die("Programmer error: load_props got empty value for primary key field [$pk_field] of table [$owner]")
|
||||
unless defined($o->{$pk_field});
|
||||
|
||||
push (@bound_values, $o->{$pk_field});
|
||||
}
|
||||
$sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
while (my $r = $sth->fetchrow_hashref()) {
|
||||
my $v;
|
||||
|
||||
if ($t eq "${owner}prop") {
|
||||
$v = $r->{'value'};
|
||||
}
|
||||
if ($t eq "${owner}propblob") {
|
||||
# print STDERR Storable::thaw($r->{'value'});
|
||||
$v = Storable::thaw($r->{'value'});
|
||||
}
|
||||
|
||||
if ($r->{'datatype'} eq 'array' || $r->{'datatype'} eq 'arrayblob') {
|
||||
$o->{'props'}->{'data'}->{$r->{'name'}} = []
|
||||
unless defined($o->{'props'}->{'data'}->{$r->{'name'}});
|
||||
|
||||
push (@{$o->{'props'}->{'data'}->{$r->{'name'}}}, $v);
|
||||
}
|
||||
else {
|
||||
$o->{'props'}->{'data'}->{$r->{'name'}} = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$o->{'props'}->{'loaded'} = 1;
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
# save properties from memory into database
|
||||
# checks that properties were loaded using load_props
|
||||
# (for advanced users: "loaded" is the key of check,
|
||||
# it is set by load_props which could be emulated)
|
||||
#
|
||||
sub save_props {
|
||||
my ($owner, $o) = @_;
|
||||
|
||||
Golem::die("Programmer error: save_props expects owner name and object")
|
||||
unless $owner && ref($o);
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
return Golem::err("Programmer error: save_props should be called only after calling load_props")
|
||||
unless $o->{'props'}->{'loaded'};
|
||||
|
||||
my $table = Golem::get_schema_table($owner);
|
||||
my $pk = $table->{'primary_key'};
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
|
||||
while (my ($k, $v) = each %{$o->{'props'}->{'data'}}) {
|
||||
my $op = Golem::get_proplist($owner, $k);
|
||||
|
||||
unless ($op) {
|
||||
Golem::do_log("Non-existent $owner property name [$k], skipping.", {"stderr" => 1});
|
||||
next;
|
||||
}
|
||||
|
||||
my $do_save = sub {
|
||||
my ($value, $seq) = @_;
|
||||
|
||||
$seq = 0 unless defined($seq);
|
||||
|
||||
my $tpref = "";
|
||||
my $db_value;
|
||||
|
||||
if ($op->{'datatype'} eq 'blob' || $op->{'datatype'} eq 'arrayblob') {
|
||||
$db_value = Storable::nfreeze($value);
|
||||
$tpref = "blob";
|
||||
}
|
||||
else {
|
||||
$db_value = $value;
|
||||
|
||||
if ($op->{'datatype'} eq 'bool') {
|
||||
$db_value = $value ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
my $prop_ref = {
|
||||
"propid" => $op->{'id'},
|
||||
"propseq" => $seq,
|
||||
"value" => $db_value,
|
||||
};
|
||||
|
||||
while (my ($pk_field, $dummy) = each %{$table->{'primary_key'}}) {
|
||||
if ($pk_field eq 'id') {
|
||||
$prop_ref->{"${owner}id"} = $o->{$pk_field};
|
||||
}
|
||||
else {
|
||||
$prop_ref->{$pk_field} = $o->{$pk_field};
|
||||
}
|
||||
}
|
||||
|
||||
$dbh = Golem::__update("${owner}prop${tpref}", $prop_ref, {"create_nonexistent" => 1});
|
||||
Golem::die("save_props: error while replacing ${owner} props: " . $dbh->errstr)
|
||||
if $dbh->err;
|
||||
};
|
||||
|
||||
if ($op->{'datatype'} eq 'array' || $op->{'datatype'} eq 'arrayblob') {
|
||||
my $i = 0;
|
||||
foreach my $array_value (@{$v}) {
|
||||
$do_save->($array_value, $i);
|
||||
$i = $i + 1;
|
||||
}
|
||||
|
||||
my $tpref = "";
|
||||
if ($op->{'datatype'} eq 'arrayblob') {
|
||||
$tpref = "blob";
|
||||
}
|
||||
|
||||
my $sql = "delete from ${owner}prop${tpref} where 1 ";
|
||||
my @bound_values = ();
|
||||
while (my ($pk_field, $dummy) = each %{$table->{'primary_key'}}) {
|
||||
if ($pk_field eq 'id') {
|
||||
$sql .= " and ${owner}id = ? ";
|
||||
}
|
||||
else {
|
||||
$sql .= " and $pk_field = ? ";
|
||||
}
|
||||
push (@bound_values, $o->{$pk_field});
|
||||
}
|
||||
$sql .= " and propid = ? and propseq >= ?";
|
||||
push (@bound_values, $op->{'id'});
|
||||
push (@bound_values, $i);
|
||||
|
||||
my $sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
}
|
||||
else {
|
||||
$do_save->($v, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
# deletes object property if no objects
|
||||
# are associated with the property
|
||||
#
|
||||
# input
|
||||
# owner type (for the listing see check_prop_owner)
|
||||
# object (with $obj->{'id'} defined)
|
||||
# property name to delete
|
||||
#
|
||||
sub delete_prop {
|
||||
my ($owner, $obj, $propname) = @_;
|
||||
|
||||
Golem::die("Programmer error: delete_prop expects owner and object")
|
||||
unless $owner && ref($obj);
|
||||
|
||||
Golem::check_prop_owner($owner);
|
||||
|
||||
my $op = Golem::get_proplist($owner, $propname);
|
||||
return Golem::err("delete_prop: invalid propname [$propname]")
|
||||
unless $op;
|
||||
|
||||
my $table = Golem::get_schema_table($owner);
|
||||
my $pk = $table->{'primary_key'};
|
||||
|
||||
my $dbh = Golem::get_db();
|
||||
|
||||
my $tpref = "";
|
||||
if ($op->{'datatype'} eq 'blob' || $op->{'datatype'} eq 'blobarray') {
|
||||
$tpref = "blob";
|
||||
}
|
||||
|
||||
my $sql = "delete from ${owner}prop${tpref} where 1 ";
|
||||
my @bound_values = ();
|
||||
while (my ($pk_field, $dummy) = each %{$table->{'primary_key'}}) {
|
||||
if ($pk_field eq 'id') {
|
||||
$sql .= " and ${owner}id = ? ";
|
||||
}
|
||||
else {
|
||||
$sql .= " and $pk_field = ? ";
|
||||
}
|
||||
push (@bound_values, $obj->{$pk_field});
|
||||
}
|
||||
$sql .= " and propid = ? ";
|
||||
push (@bound_values, $op->{'id'});
|
||||
|
||||
my $sth = $dbh->prepare($sql);
|
||||
Golem::sth_bind_array($sth, \@bound_values);
|
||||
$sth->execute();
|
||||
|
||||
Golem::die("delete_prop: error deleting $owner [$obj->{'id'}] property [$propname]: " . $dbh->errstr)
|
||||
if $dbh->err;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
222
local/cgi-bin/Golem/textlib.pl
Normal file
222
local/cgi-bin/Golem/textlib.pl
Normal file
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# Text manipulation routines
|
||||
#
|
||||
# parts courtesy of LiveJournal.org
|
||||
#
|
||||
|
||||
package Golem;
|
||||
use strict;
|
||||
|
||||
use Golem;
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::decode_url_string
|
||||
# class: web
|
||||
# des: Parse URL-style arg/value pairs into a hash.
|
||||
# args: buffer, hashref
|
||||
# des-buffer: Scalar or scalarref of buffer to parse.
|
||||
# des-hashref: Hashref to populate.
|
||||
# returns: boolean; true.
|
||||
# </LJFUNC>
|
||||
sub decode_url_string
|
||||
{
|
||||
my $a = shift;
|
||||
my $buffer = ref $a ? $a : \$a;
|
||||
my $hashref = shift; # output hash
|
||||
my $keyref = shift; # array of keys as they were found
|
||||
|
||||
my $pair;
|
||||
my @pairs = split(/&/, $$buffer);
|
||||
@$keyref = @pairs;
|
||||
my ($name, $value);
|
||||
foreach $pair (@pairs)
|
||||
{
|
||||
($name, $value) = split(/=/, $pair);
|
||||
$value =~ tr/+/ /;
|
||||
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
|
||||
$name =~ tr/+/ /;
|
||||
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
|
||||
$hashref->{$name} .= $hashref->{$name} ? ",$value" : $value;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::ehtml
|
||||
# class: text
|
||||
# des: Escapes a value before it can be put in HTML.
|
||||
# args: string
|
||||
# des-string: string to be escaped
|
||||
# returns: string escaped.
|
||||
# </LJFUNC>
|
||||
sub ehtml
|
||||
{
|
||||
# fast path for the commmon case:
|
||||
return $_[0] unless $_[0] =~ /[&\"\'<>]/o;
|
||||
|
||||
# this is faster than doing one substitution with a map:
|
||||
my $a = $_[0];
|
||||
$a =~ s/\&/&/g;
|
||||
$a =~ s/\"/"/g;
|
||||
$a =~ s/\'/&\#39;/g;
|
||||
$a =~ s/</</g;
|
||||
$a =~ s/>/>/g;
|
||||
return $a;
|
||||
}
|
||||
|
||||
sub esql {
|
||||
return $_[0] unless $_[0] =~ /[\'\"\\]/o;
|
||||
|
||||
my $a = $_[0];
|
||||
$a =~ s/\'//go;
|
||||
$a =~ s/\"//go;
|
||||
$a =~ s/\\//go;
|
||||
return $a;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::is_ascii
|
||||
# des: checks if text is pure ASCII.
|
||||
# args: text
|
||||
# des-text: text to check for being pure 7-bit ASCII text.
|
||||
# returns: 1 if text is indeed pure 7-bit, 0 otherwise.
|
||||
# </LJFUNC>
|
||||
sub is_ascii {
|
||||
my $text = shift;
|
||||
return ($text !~ m/[^\x01-\x7f]/o);
|
||||
}
|
||||
|
||||
sub is_digital {
|
||||
my $text = shift;
|
||||
return ( $text =~ /\d+/o );
|
||||
}
|
||||
|
||||
# tests if there's data in configuration file string:
|
||||
# i.e. if it's not empty and is not commented
|
||||
#
|
||||
sub have_data {
|
||||
my ($line) = @_;
|
||||
|
||||
if ($line =~ /^\s*#/o || $line =~ /^[\s]*$/o) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
# fixes user input strings (passed as array of references),
|
||||
# currently only trims
|
||||
#
|
||||
# used when importing:
|
||||
# - 1C data
|
||||
#
|
||||
sub fix_input {
|
||||
my (@i) = @_;
|
||||
|
||||
foreach my $v (@i) {
|
||||
Golem::die("Programmer error: check_input expects only scalar references")
|
||||
unless ref($v) eq 'SCALAR';
|
||||
|
||||
Golem::do_log("Inaccurate spacing trimmed [$$v]", {"stderr" => 1})
|
||||
if Golem::trim($v);
|
||||
}
|
||||
}
|
||||
|
||||
# given scalar string trims white space
|
||||
# at the beginning and in the end and
|
||||
# returns trimmed string
|
||||
#
|
||||
# given scalar reference trims white space
|
||||
# at the beginning and in the end and
|
||||
# returns true if trimming occured and false otherwise
|
||||
# NOTE: modifies the original string
|
||||
# reference to which was given as input parameter
|
||||
sub trim {
|
||||
my ($string) = @_;
|
||||
|
||||
if (ref($string) eq 'SCALAR') {
|
||||
my $tstr = $$string;
|
||||
|
||||
return 0 if $tstr eq ''; # nothing to trim, do not waste cpu cycles
|
||||
|
||||
$tstr =~ s/^\s+//so;
|
||||
$tstr =~ s/\s+$//so;
|
||||
|
||||
if ($tstr ne $$string) {
|
||||
$$string = $tstr;
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "" if $string eq ''; # nothing to trim, do not waste cpu cycles
|
||||
|
||||
$string =~ s/^\s+//so;
|
||||
$string =~ s/\s+$//so;
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
# same as standard perl join except for it doesn't
|
||||
# output "uninitialized value in join" when joining
|
||||
# list with undef values; and we use those lists
|
||||
# when binding params to DBI query
|
||||
#
|
||||
sub safe_join {
|
||||
my ($delimiter, @arr) = @_;
|
||||
|
||||
my $joined_text = "";
|
||||
$delimiter = "" unless $delimiter;
|
||||
|
||||
foreach my $bv (@arr) {
|
||||
$joined_text .= ($bv ? $bv : "") . $delimiter;
|
||||
}
|
||||
|
||||
my $i;
|
||||
for ($i = 0; $i < length($delimiter); $i++) {
|
||||
chop($joined_text);
|
||||
}
|
||||
|
||||
return $joined_text;
|
||||
}
|
||||
|
||||
# should be used when you need to concatenate string
|
||||
# which might be undefined and you want empty string ("")
|
||||
# instead of perl warnings about uninitialized values
|
||||
#
|
||||
sub safe_string {
|
||||
my ($str) = @_;
|
||||
|
||||
if ($str) {
|
||||
return $str;
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
# inserts $symbol every $range characters in a $line
|
||||
sub div_line {
|
||||
my ($line, $range, $symbol) = @_;
|
||||
|
||||
Golem::die("Programmer error: div_line expects at least string")
|
||||
unless $line;
|
||||
|
||||
$range = 70 unless $range;
|
||||
$symbol = ' ' unless $symbol;
|
||||
|
||||
my $result = '';
|
||||
for (my $i = 0 ; $i <= int(length($line)/$range) ; $i++) {
|
||||
$result .= substr($line,$i*$range,$range) . $symbol;
|
||||
}
|
||||
chop($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
207
local/cgi-bin/HTMLCleaner.pm
Normal file
207
local/cgi-bin/HTMLCleaner.pm
Normal file
@@ -0,0 +1,207 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
package HTMLCleaner;
|
||||
|
||||
use strict;
|
||||
use base 'HTML::Parser';
|
||||
use CSS::Cleaner;
|
||||
|
||||
sub new {
|
||||
my ($class, %opts) = @_;
|
||||
|
||||
my $p = new HTML::Parser('api_version' => 3);
|
||||
$p->handler('start' => \&start, 'self, tagname, attr, attrseq, text' );
|
||||
$p->handler('end' => \&end, 'self, tagname' );
|
||||
$p->handler('text' => \&text, 'self, text' );
|
||||
$p->handler('declaration' => \&decl, 'self, tokens' );
|
||||
|
||||
$p->{'output'} = $opts{'output'} || sub {};
|
||||
$p->{'cleaner'} = CSS::Cleaner->new;
|
||||
$p->{'valid_stylesheet'} = $opts{'valid_stylesheet'} || sub { 1 };
|
||||
$p->{'allow_password_input'} = $opts{'allow_password_input'} || 0;
|
||||
|
||||
bless $p, $class;
|
||||
}
|
||||
|
||||
my %bad_attr = (map { $_ => 1 }
|
||||
qw(datasrc datafld));
|
||||
|
||||
my %eat_tag = (map { $_ => 1 }
|
||||
qw(script iframe object applet embed param));
|
||||
|
||||
my @eating; # push tagname whenever we start eating a tag
|
||||
|
||||
sub start {
|
||||
my ($self, $tagname, $attr, $seq, $text) = @_;
|
||||
$tagname =~ s/<//;
|
||||
|
||||
my $slashclose = 0; # xml-style
|
||||
if ($tagname =~ s!/(.*)!!) {
|
||||
if (length($1)) { push @eating, "$tagname/$1"; } # basically halt parsing
|
||||
else { $slashclose = 1; }
|
||||
}
|
||||
|
||||
my @allowed_tags = ('lj-embed');
|
||||
|
||||
push @eating, $tagname if
|
||||
$eat_tag{$tagname} && ! grep { lc $tagname eq $_ } @allowed_tags;
|
||||
|
||||
return if @eating;
|
||||
|
||||
my $clean_res = eval {
|
||||
my $cleantag = $tagname;
|
||||
$cleantag =~ s/^.*://s;
|
||||
$cleantag =~ s/[^\w]//g;
|
||||
no strict 'subs';
|
||||
my $meth = "CLEAN_$cleantag";
|
||||
my $code = $self->can($meth)
|
||||
or return 1; # don't clean, if no element-specific cleaner method
|
||||
return $code->($self, $seq, $attr);
|
||||
};
|
||||
return if !$@ && !$clean_res;
|
||||
|
||||
my $ret = "<$tagname";
|
||||
foreach (@$seq) {
|
||||
if ($_ eq "/") { $slashclose = 1; next; }
|
||||
next if $bad_attr{lc($_)};
|
||||
next if /^on/i;
|
||||
next if /(?:^=)|[\x0b\x0d]/;
|
||||
|
||||
if ($_ eq "style") {
|
||||
$attr->{$_} = $self->{cleaner}->clean_property($attr->{$_});
|
||||
}
|
||||
|
||||
if ($tagname eq 'input' && $_ eq 'type' && $attr->{'type'} =~ /^password$/i && !$self->{'allow_password_input'}) {
|
||||
delete $attr->{'type'};
|
||||
}
|
||||
|
||||
my $nospace = $attr->{$_};
|
||||
$nospace =~ s/[\s\0]//g;
|
||||
|
||||
# IE is brain-dead and lets javascript:, vbscript:, and about: have spaces mixed in
|
||||
if ($nospace =~ /(?:(?:(?:vb|java)script)|about):/i) {
|
||||
delete $attr->{$_};
|
||||
}
|
||||
$ret .= " $_=\"" . ehtml($attr->{$_}) . "\"";
|
||||
}
|
||||
$ret .= " /" if $slashclose;
|
||||
$ret .= ">";
|
||||
|
||||
if ($tagname eq "style") {
|
||||
$self->{'_eating_style'} = 1;
|
||||
$self->{'_style_contents'} = "";
|
||||
}
|
||||
|
||||
$self->{'output'}->($ret);
|
||||
}
|
||||
|
||||
sub CLEAN_meta {
|
||||
my ($self, $seq, $attr) = @_;
|
||||
|
||||
# don't allow refresh because it can refresh to javascript URLs
|
||||
# don't allow content-type because they can set charset to utf-7
|
||||
# why do we even allow meta tags?
|
||||
my $equiv = lc $attr->{"http-equiv"};
|
||||
if ($equiv) {
|
||||
$equiv =~ s/[\s\x0b]//;
|
||||
return 0 if $equiv =~ /refresh|content-type|link|set-cookie/;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub CLEAN_link {
|
||||
my ($self, $seq, $attr) = @_;
|
||||
|
||||
if ($attr->{rel} =~ /\bstylesheet\b/i) {
|
||||
my $href = $attr->{href};
|
||||
return 0 unless $href =~ m!^https?://([^/]+?)(/.*)$!;
|
||||
my ($host, $path) = ($1, $2);
|
||||
|
||||
my $rv = $self->{'valid_stylesheet'}->($href, $host, $path);
|
||||
if ($rv == 1) {
|
||||
return 1;
|
||||
}
|
||||
if ($rv) {
|
||||
$attr->{href} = $rv;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
# Allow blank <link> tags through so RSS S2 styles can work again without the 'rel="alternate"' hack
|
||||
return 1 if (keys( %$attr ) == 0);
|
||||
|
||||
return 1 if $attr->{rel} =~ /^(?:service|openid)\.\w+$/;
|
||||
my %okay = map { $_ => 1 } (qw(icon shortcut alternate next prev index made start search top help up author edituri file-list previous home contents bookmark chapter section subsection appendix glossary copyright child));
|
||||
return 1 if $okay{lc($attr->{rel})};
|
||||
|
||||
# Allow link tags with only an href tag. This is an implied rel="alternate"
|
||||
return 1 if (exists( $attr->{href} ) and (keys( %$attr ) == 1));
|
||||
|
||||
# Allow combinations of rel attributes through as long as all of them are valid, most notably "shortcut icon"
|
||||
return 1 unless grep { !$okay{$_} } split( /\s+/, $attr->{rel} );
|
||||
|
||||
# unknown link tag
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub end {
|
||||
my ($self, $tagname) = @_;
|
||||
if (@eating) {
|
||||
pop @eating if $eating[-1] eq $tagname;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($self->{'_eating_style'}) {
|
||||
$self->{'_eating_style'} = 0;
|
||||
$self->{'output'}->($self->{cleaner}->clean($self->{'_style_contents'}));
|
||||
}
|
||||
|
||||
$self->{'output'}->("</$tagname>");
|
||||
}
|
||||
|
||||
sub text {
|
||||
my ($self, $text) = @_;
|
||||
return if @eating;
|
||||
|
||||
if ($self->{'_eating_style'}) {
|
||||
$self->{'_style_contents'} .= $text;
|
||||
return;
|
||||
}
|
||||
|
||||
# this string is magic [hack]. (See $out_straight in
|
||||
# cgi-bin/LJ/S2.pm) callers can print "<!-- -->" to HTML::Parser
|
||||
# just to make it flush, since HTML::Parser has no
|
||||
# ->flush_outstanding text tag.
|
||||
return if $text eq "<!-- -->";
|
||||
|
||||
# the parser gives us back text whenever it's confused
|
||||
# on really broken input. sadly, IE parses really broken
|
||||
# input, so let's escape anything going out this way.
|
||||
$self->{'output'}->(eangles($text));
|
||||
}
|
||||
|
||||
sub decl {
|
||||
my ($self, $tokens) = @_;
|
||||
$self->{'output'}->("<!" . join(" ", map { eangles($_) } @$tokens) . ">");
|
||||
}
|
||||
|
||||
sub eangles {
|
||||
my $a = shift;
|
||||
$a =~ s/</</g;
|
||||
$a =~ s/>/>/g;
|
||||
return $a;
|
||||
}
|
||||
|
||||
sub ehtml {
|
||||
my $a = shift;
|
||||
$a =~ s/\&/&/g;
|
||||
$a =~ s/\"/"/g;
|
||||
$a =~ s/\'/&\#39;/g;
|
||||
$a =~ s/</</g;
|
||||
$a =~ s/>/>/g;
|
||||
return $a;
|
||||
}
|
||||
|
||||
1;
|
||||
124
local/cgi-bin/LJ/Auth.pm
Normal file
124
local/cgi-bin/LJ/Auth.pm
Normal file
@@ -0,0 +1,124 @@
|
||||
# This is the LiveJournal Authentication module.
|
||||
# It contains useful authentication methods.
|
||||
|
||||
package LJ::Auth;
|
||||
use strict;
|
||||
use Digest::HMAC_SHA1 qw(hmac_sha1_hex);
|
||||
use Digest::SHA1 qw(sha1_hex);
|
||||
use Carp qw (croak);
|
||||
|
||||
# Generate an auth token for AJAX requests to use.
|
||||
# Arguments: ($remote, $action, %postvars)
|
||||
# $remote: remote user object
|
||||
# $uri: what uri this is for
|
||||
# %postvars: the expected post variables
|
||||
# Returns: Auth token good for the current hour
|
||||
sub ajax_auth_token {
|
||||
my ($class, $remote, $uri, %postvars) = @_;
|
||||
|
||||
$remote = LJ::want_user($remote) || LJ::get_remote();
|
||||
|
||||
croak "No URI specified" unless $uri;
|
||||
|
||||
my ($stime, $secret) = LJ::get_secret();
|
||||
my $postvars = join('&', map { $postvars{$_} } sort keys %postvars);
|
||||
my $remote_session_id = $remote && $remote->session ? $remote->session->id : LJ::UniqCookie->current_uniq;
|
||||
my $remote_userid = $remote ? $remote->id : 0;
|
||||
|
||||
my $chalbare = qq {ajax:$stime:$remote_userid:$remote_session_id:$uri:$postvars};
|
||||
my $chalsig = sha1_hex($chalbare, $secret);
|
||||
return qq{$chalbare:$chalsig};
|
||||
}
|
||||
|
||||
# Checks an auth token sent by an ajax request
|
||||
# Arguments: $remote, $uri, %POST variables
|
||||
# Returns: bool whether or not key is good
|
||||
sub check_ajax_auth_token {
|
||||
my ($class, $remote, $uri, %postvars) = @_;
|
||||
|
||||
$remote = LJ::want_user($remote) || LJ::get_remote();
|
||||
|
||||
# get auth token out of post vars
|
||||
my $auth_token = delete $postvars{auth_token} or return 0;
|
||||
|
||||
# recompute post vars
|
||||
my $postvars = join('&', map { $postvars{$_} } sort keys %postvars);
|
||||
|
||||
# get vars out of token string
|
||||
my ($c_ver, $stime, $remoteid, $sessid, $chal_uri, $chal_postvars, $chalsig) = split(':', $auth_token);
|
||||
|
||||
# get secret based on $stime
|
||||
my $secret = LJ::get_secret($stime);
|
||||
|
||||
# no time?
|
||||
return 0 unless $stime && $secret;
|
||||
|
||||
# right version?
|
||||
return 0 unless $c_ver eq 'ajax';
|
||||
|
||||
# in logged-out case $remoteid is 0 and $sessid is uniq_cookie
|
||||
my $req_remoteid = $remoteid > 0 ? $remote->id : 0;
|
||||
my $req_sessid = $remoteid > 0 ? $remote->session->id : LJ::UniqCookie->current_uniq;
|
||||
|
||||
|
||||
# do signitures match?
|
||||
my $chalbare = qq {$c_ver:$stime:$remoteid:$sessid:$chal_uri:$chal_postvars};
|
||||
my $realsig = sha1_hex($chalbare, $secret);
|
||||
return 0 unless $realsig eq $chalsig;
|
||||
|
||||
return 0 unless
|
||||
$remoteid == $req_remoteid && # remote id matches or logged-out 0=0
|
||||
$sessid == $req_sessid && # remote sessid or logged-out uniq cookie match
|
||||
$uri eq $chal_uri && # uri matches
|
||||
$postvars eq $chal_postvars; # post vars to uri
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
# this is similar to the above methods but doesn't require a session or remote
|
||||
sub sessionless_auth_token {
|
||||
my ($class, $uri, %reqvars) = @_;
|
||||
|
||||
croak "No URI specified" unless $uri;
|
||||
|
||||
my ($stime, $secret) = LJ::get_secret();
|
||||
my $reqvars = join('&', map { $reqvars{$_} } sort keys %reqvars);
|
||||
|
||||
my $chalbare = qq {sessionless:$stime:$uri:$reqvars};
|
||||
my $chalsig = sha1_hex($chalbare, $secret);
|
||||
return qq{$chalbare:$chalsig};
|
||||
}
|
||||
|
||||
sub check_sessionless_auth_token {
|
||||
my ($class, $uri, %reqvars) = @_;
|
||||
|
||||
# get auth token out of post vars
|
||||
my $auth_token = delete $reqvars{auth_token} or return 0;
|
||||
|
||||
# recompute post vars
|
||||
my $reqvars = join('&', map { $reqvars{$_} } sort keys %reqvars);
|
||||
|
||||
# get vars out of token string
|
||||
my ($c_ver, $stime, $chal_uri, $chal_reqvars, $chalsig) = split(':', $auth_token);
|
||||
|
||||
# get secret based on $stime
|
||||
my $secret = LJ::get_secret($stime);
|
||||
|
||||
# no time?
|
||||
return 0 unless $stime && $secret;
|
||||
|
||||
# right version?
|
||||
return 0 unless $c_ver eq 'sessionless';
|
||||
|
||||
# do signitures match?
|
||||
my $chalbare = qq {$c_ver:$stime:$chal_uri:$chal_reqvars};
|
||||
my $realsig = sha1_hex($chalbare, $secret);
|
||||
return 0 unless $realsig eq $chalsig;
|
||||
|
||||
# do other vars match?
|
||||
return 0 unless $uri eq $chal_uri && $reqvars eq $chal_reqvars;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
21
local/cgi-bin/LJ/CSS/Cleaner.pm
Normal file
21
local/cgi-bin/LJ/CSS/Cleaner.pm
Normal file
@@ -0,0 +1,21 @@
|
||||
package LJ::CSS::Cleaner;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
no warnings 'redefine';
|
||||
|
||||
use base 'CSS::Cleaner';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
return $class->SUPER::new( @_,
|
||||
pre_hook => sub {
|
||||
my $rref = shift;
|
||||
|
||||
$$rref =~ s/comment-bake-cookie/CLEANED/g;
|
||||
return;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
47
local/cgi-bin/LJ/ConvUTF8.pm
Normal file
47
local/cgi-bin/LJ/ConvUTF8.pm
Normal file
@@ -0,0 +1,47 @@
|
||||
# this is a small wrapper around Unicode::MapUTF8, just so we can lazily-load it easier
|
||||
# with Class::Autouse, and so we have a central place to init its charset aliases.
|
||||
# and in the future if we switch transcoding packages, we can just do it here.
|
||||
package LJ::ConvUTF8;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Unicode::MapUTF8 ();
|
||||
|
||||
BEGIN {
|
||||
# declare some charset aliases
|
||||
# we need this at least for cases when the only name supported
|
||||
# by MapUTF8.pm isn't recognized by browsers
|
||||
# note: newer versions of MapUTF8 know these
|
||||
{
|
||||
my %alias = ( 'windows-1251' => 'cp1251',
|
||||
'windows-1252' => 'cp1252',
|
||||
'windows-1253' => 'cp1253', );
|
||||
foreach (keys %alias) {
|
||||
next if Unicode::MapUTF8::utf8_supported_charset($_);
|
||||
Unicode::MapUTF8::utf8_charset_alias($_, $alias{$_});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub load {
|
||||
1;
|
||||
}
|
||||
|
||||
sub supported_charset {
|
||||
my ($class, $charset) = @_;
|
||||
return Unicode::MapUTF8::utf8_supported_charset($charset);
|
||||
}
|
||||
|
||||
sub from_utf8 {
|
||||
my ($class, $from_enc, $str) = @_;
|
||||
return Unicode::MapUTF8::from_utf8({ -string=> $str, -charset => $from_enc });
|
||||
}
|
||||
|
||||
sub to_utf8 {
|
||||
my ($class, $to_enc, $str) = @_;
|
||||
return Unicode::MapUTF8::to_utf8({ -string=> $str, -charset => $to_enc });
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
|
||||
466
local/cgi-bin/LJ/EmbedModule.pm
Normal file
466
local/cgi-bin/LJ/EmbedModule.pm
Normal file
@@ -0,0 +1,466 @@
|
||||
#!/usr/bin/perl
|
||||
package LJ::EmbedModule;
|
||||
use strict;
|
||||
use Carp qw (croak);
|
||||
use Class::Autouse qw (
|
||||
LJ::Auth
|
||||
HTML::TokeParser
|
||||
);
|
||||
|
||||
# states for a finite-state machine we use in parse()
|
||||
use constant {
|
||||
# reading plain html without <object>, <embed> or <lj-embed>
|
||||
REGULAR => 1,
|
||||
# inside <object> or <embed> tag
|
||||
IMPLICIT => 2,
|
||||
# inside explicit <lj-embed> tag
|
||||
EXPLICIT => 3,
|
||||
# maximum embed width and height
|
||||
MAX_WIDTH => 800,
|
||||
MAX_HEIGHT => 800,
|
||||
};
|
||||
|
||||
# can optionally pass in an id of a module to change its contents
|
||||
# returns module id
|
||||
sub save_module {
|
||||
my ($class, %opts) = @_;
|
||||
|
||||
my $contents = $opts{contents} || '';
|
||||
my $id = $opts{id};
|
||||
my $journal = $opts{journal}
|
||||
or croak "No journal passed to LJ::EmbedModule::save_module";
|
||||
my $preview = $opts{preview};
|
||||
|
||||
# are we creating a new entry?
|
||||
unless (defined $id) {
|
||||
$id = LJ::alloc_user_counter($journal, 'D')
|
||||
or die "Could not allocate embed module ID";
|
||||
}
|
||||
|
||||
my $cmptext = 'C-' . LJ::text_compress($contents);
|
||||
|
||||
## embeds for preview are stored in a special table,
|
||||
## where new items overwrites old ones
|
||||
my $table_name = ($preview) ? 'embedcontent_preview' : 'embedcontent';
|
||||
$journal->do("REPLACE INTO $table_name (userid, moduleid, content) VALUES ".
|
||||
"(?, ?, ?)", undef, $journal->{'userid'}, $id, $cmptext);
|
||||
die $journal->errstr if $journal->err;
|
||||
|
||||
# save in memcache
|
||||
my $memkey = $class->memkey($journal->{'userid'}, $id, $preview);
|
||||
LJ::MemCache::set($memkey, $contents);
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
# changes <div class="ljembed"... tags from the RTE into proper lj-embed tags
|
||||
sub transform_rte_post {
|
||||
my ($class, $txt) = @_;
|
||||
return $txt unless $txt && $txt =~ /ljembed/i;
|
||||
# ghetto... shouldn't use regexes to parse this
|
||||
$txt =~ s/<div\s*class="ljembed"\s*(embedid="(\d+)")?\s*>(((?!<\/div>).)*)<\/div>/<lj-embed id="$2">$3<\/lj-embed>/ig;
|
||||
$txt =~ s/<div\s*(embedid="(\d+)")?\s*class="ljembed"\s*>(((?!<\/div>).)*)<\/div>/<lj-embed id="$2">$3<\/lj-embed>/ig;
|
||||
return $txt;
|
||||
}
|
||||
|
||||
# takes a scalarref to entry text and expands lj-embed tags
|
||||
# REPLACE
|
||||
sub expand_entry {
|
||||
my ($class, $journal, $entryref, %opts) = @_;
|
||||
|
||||
$$entryref =~ s/(<lj\-embed[^>]+\/>)/$class->_expand_tag($journal, $1, $opts{edit}, %opts)/ge;
|
||||
}
|
||||
|
||||
sub _expand_tag {
|
||||
my $class = shift;
|
||||
my $journal = shift;
|
||||
my $tag = shift;
|
||||
my $edit = shift;
|
||||
my %opts = @_;
|
||||
|
||||
my %attrs = $tag =~ /(\w+)="?(\-?\d+)"?/g;
|
||||
|
||||
return '[invalid lj-embed, id is missing]' unless $attrs{id};
|
||||
|
||||
if ($edit) {
|
||||
return '<lj-embed ' . join(' ', map {"$_=\"$attrs{$_}\""} keys %attrs) . ">\n" .
|
||||
$class->module_content(moduleid => $attrs{id}, journalid => $journal->id) .
|
||||
"\n<\/lj-embed>";
|
||||
}
|
||||
elsif ($opts{'content_only'}) {
|
||||
return $class->module_content(moduleid => $attrs{id}, journalid => $journal->{'userid'});
|
||||
}
|
||||
else {
|
||||
@opts{qw /width height/} = @attrs{qw/width height/};
|
||||
return $class->module_iframe_tag($journal, $attrs{id}, %opts)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
# take a scalarref to a post, parses any lj-embed tags, saves the contents
|
||||
# of the tags and replaces them with a module tag with the id.
|
||||
# REPLACE
|
||||
sub parse_module_embed {
|
||||
my ($class, $journal, $postref, %opts) = @_;
|
||||
|
||||
return unless $postref && $$postref;
|
||||
|
||||
return if LJ::conf_test($LJ::DISABLED{embed_module});
|
||||
|
||||
# fast track out if we don't have to expand anything
|
||||
return unless $$postref =~ /lj\-embed|embed|object/i;
|
||||
|
||||
# do we want to replace with the lj-embed tags or iframes?
|
||||
my $expand = $opts{expand};
|
||||
|
||||
# if this is editing mode, then we want to expand embed tags for editing
|
||||
my $edit = $opts{edit};
|
||||
|
||||
# previews are a special case (don't want to permanantly save to db)
|
||||
my $preview = $opts{preview};
|
||||
|
||||
# deal with old-fashion calls
|
||||
if (($edit || $expand) && ! $preview) {
|
||||
return $class->expand_entry($journal, $postref, %opts);
|
||||
}
|
||||
|
||||
# ok, we can safely parse post text
|
||||
# machine state
|
||||
my $state = REGULAR;
|
||||
my $p = HTML::TokeParser->new($postref);
|
||||
my $newtxt = '';
|
||||
my %embed_attrs = (); # ($eid, $ewidth, $eheight);
|
||||
my $embed = '';
|
||||
my @stack = ();
|
||||
my $next_preview_id = 1;
|
||||
|
||||
while (my $token = $p->get_token) {
|
||||
my ($type, $tag, $attr) = @$token;
|
||||
$tag = lc $tag;
|
||||
my $newstate = undef;
|
||||
my $reconstructed = $class->reconstruct($token);
|
||||
|
||||
if ($state == REGULAR) {
|
||||
if ($tag eq 'lj-embed' && $type eq 'S' && ! $attr->{'/'}) {
|
||||
# <lj-embed ...>, not self-closed
|
||||
# switch to EXPLICIT state
|
||||
$newstate = EXPLICIT;
|
||||
# save embed id, width and height if they do exist in attributes
|
||||
$embed_attrs{id} = $attr->{id} if $attr->{id};
|
||||
$embed_attrs{width} = ($attr->{width} > MAX_WIDTH ? MAX_WIDTH : $attr->{width}) if $attr->{width};
|
||||
$embed_attrs{height} = ($attr->{height} > MAX_HEIGHT ? MAX_HEIGHT : $attr->{height}) if $attr->{height};
|
||||
} elsif (($tag eq 'object' || $tag eq 'embed') && $type eq 'S') {
|
||||
# <object> or <embed>
|
||||
# switch to IMPLICIT state unless it is a self-closed tag
|
||||
unless ($attr->{'/'}) {
|
||||
$newstate = IMPLICIT;
|
||||
# tag balance
|
||||
push @stack, $tag;
|
||||
}
|
||||
# append the tag contents to new embed buffer, so we can convert in to lj-embed later
|
||||
$embed .= $reconstructed;
|
||||
} else {
|
||||
# otherwise stay in REGULAR
|
||||
$newtxt .= $reconstructed;
|
||||
}
|
||||
} elsif ($state == IMPLICIT) {
|
||||
if ($tag eq 'object' || $tag eq 'embed') {
|
||||
if ($type eq 'E') {
|
||||
# </object> or </embed>
|
||||
# update tag balance, but only if we have a valid balance up to this moment
|
||||
pop @stack if $stack[-1] eq $tag;
|
||||
# switch to REGULAR if tags are balanced (stack is empty), stay in IMPLICIT otherwise
|
||||
$newstate = REGULAR unless @stack;
|
||||
} elsif ($type eq 'S') {
|
||||
# <object> or <embed>
|
||||
# mind the tag balance, do not update it in case of a self-closed tag
|
||||
push @stack, $tag unless $attr->{'/'};
|
||||
}
|
||||
}
|
||||
# append to embed buffer
|
||||
$embed .= $reconstructed;
|
||||
|
||||
} elsif ($state == EXPLICIT) {
|
||||
|
||||
if ($tag eq 'lj-embed' && $type eq 'E') {
|
||||
# </lj-embed> - that's the end of explicit embed block, switch to REGULAR
|
||||
$newstate = REGULAR;
|
||||
} else {
|
||||
# continue appending contents to embed buffer
|
||||
$embed .= $reconstructed;
|
||||
}
|
||||
} else {
|
||||
# let's be paranoid
|
||||
die "Invalid state: '$state'";
|
||||
}
|
||||
|
||||
# we decided to switch back to REGULAR and have something in embed buffer
|
||||
# so let's save buffer as an embed module and start all over again
|
||||
if (defined($newstate) && $newstate == REGULAR && $embed) {
|
||||
$embed_attrs{id} = $class->save_module(
|
||||
id => ($preview ? $next_preview_id++ : $embed_attrs{id}),
|
||||
contents => $embed,
|
||||
journal => $journal,
|
||||
preview => $preview,
|
||||
);
|
||||
|
||||
$newtxt .= "<lj-embed " . join(' ', map { exists $embed_attrs{$_} ? "$_=\"$embed_attrs{$_}\"" : () } qw / id width height /) . "/>";
|
||||
|
||||
$embed = '';
|
||||
%embed_attrs = ();
|
||||
}
|
||||
|
||||
# switch the state if we have a new one
|
||||
$state = $newstate if defined $newstate;
|
||||
|
||||
}
|
||||
|
||||
# update passed text
|
||||
$$postref = $newtxt;
|
||||
}
|
||||
|
||||
sub module_iframe_tag {
|
||||
my ($class, $u, $moduleid, %opts) = @_;
|
||||
|
||||
return '' if $LJ::DISABLED{embed_module};
|
||||
|
||||
my $journalid = $u->{'userid'};
|
||||
$moduleid += 0;
|
||||
|
||||
# parse the contents of the module and try to come up with a guess at the width and height of the content
|
||||
my $content = $class->module_content(moduleid => $moduleid, journalid => $journalid);
|
||||
my $preview = $opts{preview};
|
||||
my $width = 0;
|
||||
my $height = 0;
|
||||
my $p = HTML::TokeParser->new(\$content);
|
||||
my $embedcodes;
|
||||
|
||||
# if the content only contains a whitelisted embedded video
|
||||
# then we can skip the placeholders (in some cases)
|
||||
my $no_whitelist = 0;
|
||||
my $found_embed = 0;
|
||||
|
||||
# we don't need to estimate the dimensions if they are provided in tag attributes
|
||||
unless ($opts{width} && $opts{height}) {
|
||||
while (my $token = $p->get_token) {
|
||||
my $type = $token->[0];
|
||||
my $tag = $token->[1] ? lc $token->[1] : '';
|
||||
my $attr = $token->[2]; # hashref
|
||||
|
||||
if ($type eq "S") {
|
||||
my ($elewidth, $eleheight);
|
||||
|
||||
if ($attr->{width}) {
|
||||
$elewidth = $attr->{width}+0;
|
||||
$width = $elewidth if $elewidth > $width;
|
||||
}
|
||||
if ($attr->{height}) {
|
||||
$eleheight = $attr->{height}+0;
|
||||
$height = $eleheight if $eleheight > $height;
|
||||
}
|
||||
|
||||
my $flashvars = $attr->{flashvars};
|
||||
|
||||
if ($tag eq 'object' || $tag eq 'embed') {
|
||||
my $src;
|
||||
next unless $src = $attr->{src};
|
||||
|
||||
# we have an object/embed tag with src, make a fake lj-template object
|
||||
my @tags = (
|
||||
['S', 'lj-template', {
|
||||
name => 'video',
|
||||
(defined $elewidth ? ( width => $width ) : ()),
|
||||
(defined $eleheight ? ( height => $height ) : ()),
|
||||
(defined $flashvars ? ( flashvars => $flashvars ) : ()),
|
||||
}],
|
||||
[ 'T', $src, {}],
|
||||
['E', 'lj-template', {}],
|
||||
);
|
||||
|
||||
$embedcodes = LJ::run_hook('expand_template_video', \@tags);
|
||||
|
||||
$found_embed = 1 if $embedcodes;
|
||||
$found_embed &&= $embedcodes !~ /Invalid video/i;
|
||||
|
||||
$no_whitelist = !$found_embed;
|
||||
} elsif ($tag ne 'param') {
|
||||
$no_whitelist = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# add padding
|
||||
$width += 50 if $width;
|
||||
$height += 50 if $height;
|
||||
}
|
||||
|
||||
# use explicit values if we have them
|
||||
$width = $opts{width} if $opts{width};
|
||||
$height = $opts{height} if $opts{height};
|
||||
|
||||
$width ||= 240;
|
||||
$height ||= 200;
|
||||
|
||||
# some dimension min/maxing
|
||||
$width = 50 if $width < 50;
|
||||
$width = MAX_WIDTH if $width > MAX_WIDTH;
|
||||
$height = 50 if $height < 50;
|
||||
$height = MAX_HEIGHT if $height > MAX_HEIGHT;
|
||||
|
||||
# safari caches state of sub-resources aggressively, so give
|
||||
# each iframe a unique 'name' attribute
|
||||
my $id = qq(name="embed_${journalid}_$moduleid");
|
||||
|
||||
my $auth_token = LJ::eurl(LJ::Auth->sessionless_auth_token('embedcontent', moduleid => $moduleid, journalid => $journalid, preview => $preview,));
|
||||
my $iframe_url = qq {http://$LJ::EMBED_MODULE_DOMAIN/tools/embedcontent.bml?journalid=$journalid&moduleid=$moduleid&preview=$preview&auth_token=$auth_token};
|
||||
my $iframe_tag = qq {<iframe src="$iframe_url" } .
|
||||
qq{width="$width" height="$height" allowtransparency="true" frameborder="0" class="lj_embedcontent" $id></iframe>};
|
||||
|
||||
my $remote = LJ::get_remote();
|
||||
my $do_placeholder;
|
||||
|
||||
if ($remote) {
|
||||
return $iframe_tag if $opts{edit};
|
||||
|
||||
# show placeholder instead of iframe?
|
||||
LJ::load_user_props($remote, "opt_embedplaceholders");
|
||||
my $placeholder_prop = $remote->prop('opt_embedplaceholders');
|
||||
$do_placeholder = $placeholder_prop && $placeholder_prop ne 'N';
|
||||
|
||||
# if placeholder_prop is not set, then show placeholder on a friends
|
||||
# page view UNLESS the embedded content is only one embed/object
|
||||
# tag and it's whitelisted video.
|
||||
my $r = eval { Apache->request };
|
||||
my $view = $r ? $r->notes("view") : '';
|
||||
if (! $placeholder_prop && $view eq 'friends') {
|
||||
# show placeholder if this is not whitelisted video
|
||||
$do_placeholder = 1 if $no_whitelist;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$do_placeholder = $BML::COOKIE{'flashpref'};
|
||||
$do_placeholder = 0 unless $do_placeholder eq "0" || $do_placeholder eq "1";
|
||||
}
|
||||
|
||||
return $iframe_tag unless $do_placeholder;
|
||||
|
||||
my $tmpcontent = $class->module_content(
|
||||
journalid => $journalid,
|
||||
moduleid => $moduleid,
|
||||
0
|
||||
);
|
||||
$tmpcontent =~ s/.+param\s+name\s*?=\s*?"?movie"?\s*value\s*?=\s*?"?//sg; #"
|
||||
$tmpcontent =~ s/("|\s).+//sg; #"
|
||||
$tmpcontent = LJ::ehtml($tmpcontent);
|
||||
|
||||
# placeholder
|
||||
return LJ::placeholder_link(
|
||||
placeholder_html => $iframe_tag,
|
||||
placeholder_link => $iframe_url,
|
||||
width => $width,
|
||||
height => $height,
|
||||
img => "$LJ::IMGPREFIX/videoplaceholder.png",
|
||||
img_title => $tmpcontent,
|
||||
);
|
||||
}
|
||||
|
||||
sub module_content {
|
||||
my ($class, %opts) = @_;
|
||||
|
||||
my $moduleid = $opts{moduleid};
|
||||
croak "No moduleid" unless defined $moduleid;
|
||||
$moduleid += 0;
|
||||
|
||||
my $journalid = $opts{journalid}+0 or croak "No journalid";
|
||||
my $journal = LJ::load_userid($journalid) or die "Invalid userid $journalid";
|
||||
my $preview = $opts{preview};
|
||||
|
||||
# try memcache
|
||||
my $memkey = $class->memkey($journalid, $moduleid, $preview);
|
||||
my $content = LJ::MemCache::get($memkey);
|
||||
my ($dbload, $dbid); # module id from the database
|
||||
unless (defined $content) {
|
||||
my $table_name = ($preview) ? 'embedcontent_preview' : 'embedcontent';
|
||||
($content, $dbid) = $journal->selectrow_array("SELECT content, moduleid FROM $table_name WHERE " .
|
||||
"moduleid=? AND userid=?",
|
||||
undef, $moduleid, $journalid);
|
||||
die $journal->errstr if $journal->err;
|
||||
$dbload = 1;
|
||||
}
|
||||
|
||||
$content ||= '';
|
||||
|
||||
LJ::text_uncompress(\$content) if $content =~ s/^C-//;
|
||||
|
||||
# clean js out of content
|
||||
unless ($LJ::DISABLED{'embedmodule-cleancontent'}) {
|
||||
LJ::CleanHTML::clean(\$content, {
|
||||
addbreaks => 0,
|
||||
tablecheck => 0,
|
||||
mode => 'allow',
|
||||
allow => [qw(object embed)],
|
||||
deny => [qw(script iframe)],
|
||||
remove => [qw(script iframe)],
|
||||
ljcut_disable => 1,
|
||||
cleancss => 0,
|
||||
extractlinks => 0,
|
||||
noautolinks => 1,
|
||||
extractimages => 0,
|
||||
noexpandembedded => 1,
|
||||
transform_embed_nocheck => 1,
|
||||
});
|
||||
}
|
||||
|
||||
# if we got stuff out of database
|
||||
if ($dbload) {
|
||||
# save in memcache
|
||||
LJ::MemCache::set($memkey, $content);
|
||||
|
||||
# if we didn't get a moduleid out of the database then this entry is not valid
|
||||
return defined $dbid ? $content : "[Invalid lj-embed id $moduleid]";
|
||||
}
|
||||
|
||||
# get rid of whitespace around the content
|
||||
return LJ::trim($content) || '';
|
||||
}
|
||||
|
||||
sub memkey {
|
||||
my ($class, $journalid, $moduleid, $preview) = @_;
|
||||
my $pfx = $preview ? 'embedcontpreview' : 'embedcont';
|
||||
return [$journalid, "$pfx:$journalid:$moduleid"];
|
||||
}
|
||||
|
||||
# create a tag string from HTML::TokeParser token
|
||||
sub reconstruct {
|
||||
my $class = shift;
|
||||
my $token = shift;
|
||||
my ($type, $tag, $attr, $attord) = @$token;
|
||||
if ($type eq 'S') {
|
||||
my $txt = "<$tag";
|
||||
my $selfclose;
|
||||
|
||||
# preserve order of attributes. the original order is
|
||||
# in element 4 of $token
|
||||
foreach my $name (@$attord) {
|
||||
if ($name eq '/') {
|
||||
$selfclose = 1;
|
||||
next;
|
||||
}
|
||||
|
||||
# FIXME: ultra ghetto.
|
||||
$attr->{$name} = LJ::no_utf8_flag($attr->{$name});
|
||||
|
||||
$txt .= " $name=\"" . LJ::ehtml($attr->{$name}) . "\"";
|
||||
}
|
||||
$txt .= $selfclose ? " />" : ">";
|
||||
|
||||
} elsif ($type eq 'E') {
|
||||
return "</$tag>";
|
||||
} else { # C, T, D or PI
|
||||
return $tag;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
||||
1138
local/cgi-bin/LJ/Entry.pm
Normal file
1138
local/cgi-bin/LJ/Entry.pm
Normal file
File diff suppressed because it is too large
Load Diff
184
local/cgi-bin/LJ/OpenID.pm
Executable file
184
local/cgi-bin/LJ/OpenID.pm
Executable file
@@ -0,0 +1,184 @@
|
||||
package LJ::OpenID;
|
||||
|
||||
use strict;
|
||||
use Digest::SHA1 qw(sha1 sha1_hex);
|
||||
use LWPx::ParanoidAgent;
|
||||
|
||||
BEGIN {
|
||||
$LJ::OPTMOD_OPENID_CONSUMER = $LJ::OPENID_CONSUMER ? eval "use Net::OpenID::Consumer; 1;" : 0;
|
||||
$LJ::OPTMOD_OPENID_SERVER = $LJ::OPENID_SERVER ? eval "use Net::OpenID::Server; 1;" : 0;
|
||||
}
|
||||
|
||||
# returns boolean whether consumer support is enabled and available
|
||||
sub consumer_enabled {
|
||||
return 0 unless $LJ::OPENID_CONSUMER;
|
||||
return $LJ::OPTMOD_OPENID_CONSUMER || eval "use Net::OpenID::Consumer; 1;";
|
||||
}
|
||||
|
||||
# returns boolean whether consumer support is enabled and available
|
||||
sub server_enabled {
|
||||
return 0 unless $LJ::OPENID_SERVER;
|
||||
return $LJ::OPTMOD_OPENID_CONSUMER || eval "use Net::OpenID::Server; 1;";
|
||||
}
|
||||
|
||||
sub server {
|
||||
my ($get, $post) = @_;
|
||||
|
||||
return Net::OpenID::Server->new(
|
||||
compat => $LJ::OPENID_COMPAT,
|
||||
get_args => $get || {},
|
||||
post_args => $post || {},
|
||||
|
||||
get_user => \&LJ::get_remote,
|
||||
is_identity => sub {
|
||||
my ($u, $ident) = @_;
|
||||
return LJ::OpenID::is_identity($u, $ident, $get);
|
||||
},
|
||||
is_trusted => \&LJ::OpenID::is_trusted,
|
||||
|
||||
setup_url => "$LJ::SITEROOT/openid/approve.bml",
|
||||
|
||||
server_secret => \&LJ::OpenID::server_secret,
|
||||
secret_gen_interval => 3600,
|
||||
secret_expire_age => 86400 * 14,
|
||||
);
|
||||
}
|
||||
|
||||
# Returns a Consumer object
|
||||
# When planning to verify identity, needs GET
|
||||
# arguments passed in
|
||||
sub consumer {
|
||||
my $get_args = shift || {};
|
||||
|
||||
my $ua;
|
||||
unless ($LJ::IS_DEV_SERVER) {
|
||||
$ua = LWPx::ParanoidAgent->new(
|
||||
timeout => 10,
|
||||
max_size => 1024*300,
|
||||
);
|
||||
}
|
||||
|
||||
my $csr = Net::OpenID::Consumer->new(
|
||||
ua => $ua,
|
||||
args => $get_args,
|
||||
cache => eval { LJ::MemCache::get_memcache() },
|
||||
consumer_secret => \&LJ::OpenID::consumer_secret,
|
||||
debug => $LJ::IS_DEV_SERVER || 0,
|
||||
required_root => $LJ::SITEROOT,
|
||||
);
|
||||
|
||||
return $csr;
|
||||
}
|
||||
|
||||
sub consumer_secret {
|
||||
my $time = shift;
|
||||
return server_secret($time - $time % 3600);
|
||||
}
|
||||
|
||||
sub server_secret {
|
||||
my $time = shift;
|
||||
my ($t2, $secret) = LJ::get_secret($time);
|
||||
die "ASSERT: didn't get t2 (t1=$time)" unless $t2;
|
||||
die "ASSERT: didn't get secret (t2=$t2)" unless $secret;
|
||||
die "ASSERT: time($time) != t2($t2)\n" unless $t2 == $time;
|
||||
return $secret;
|
||||
}
|
||||
|
||||
sub is_trusted {
|
||||
my ($u, $trust_root, $is_identity) = @_;
|
||||
return 0 unless $u;
|
||||
# we always look up $is_trusted, even if $is_identity is false, to avoid timing attacks
|
||||
|
||||
my $dbh = LJ::get_db_writer();
|
||||
my ($endpointid, $duration) = $dbh->selectrow_array("SELECT t.endpoint_id, t.duration ".
|
||||
"FROM openid_trust t, openid_endpoint e ".
|
||||
"WHERE t.userid=? AND t.endpoint_id=e.endpoint_id AND e.url=?",
|
||||
undef, $u->{userid}, $trust_root);
|
||||
return 0 unless $endpointid;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub is_identity {
|
||||
my ($u, $ident, $get) = @_;
|
||||
return 0 unless $u && $u->{journaltype} eq "P";
|
||||
|
||||
my $user = $u->{user};
|
||||
return 1 if
|
||||
$ident eq "$LJ::SITEROOT/users/$user/" ||
|
||||
$ident eq "$LJ::SITEROOT/~$user/" ||
|
||||
$ident eq "http://$user.$LJ::USER_DOMAIN/";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub getmake_endpointid {
|
||||
my $site = shift;
|
||||
|
||||
my $dbh = LJ::get_db_writer()
|
||||
or return undef;
|
||||
|
||||
my $rv = $dbh->do("INSERT IGNORE INTO openid_endpoint (url) VALUES (?)", undef, $site);
|
||||
my $end_id;
|
||||
if ($rv > 0) {
|
||||
$end_id = $dbh->{'mysql_insertid'};
|
||||
} else {
|
||||
$end_id = $dbh->selectrow_array("SELECT endpoint_id FROM openid_endpoint WHERE url=?",
|
||||
undef, $site);
|
||||
}
|
||||
return $end_id;
|
||||
}
|
||||
|
||||
sub add_trust {
|
||||
my ($u, $site) = @_;
|
||||
|
||||
my $end_id = LJ::OpenID::getmake_endpointid($site)
|
||||
or return 0;
|
||||
|
||||
my $dbh = LJ::get_db_writer()
|
||||
or return undef;
|
||||
|
||||
my $rv = $dbh->do("REPLACE INTO openid_trust (userid, endpoint_id, duration, trust_time) ".
|
||||
"VALUES (?,?,?,UNIX_TIMESTAMP())", undef, $u->{userid}, $end_id, "always");
|
||||
return $rv;
|
||||
}
|
||||
|
||||
# From Digest::HMAC
|
||||
sub hmac_sha1_hex {
|
||||
unpack("H*", &hmac_sha1);
|
||||
}
|
||||
sub hmac_sha1 {
|
||||
hmac($_[0], $_[1], \&sha1, 64);
|
||||
}
|
||||
sub hmac {
|
||||
my($data, $key, $hash_func, $block_size) = @_;
|
||||
$block_size ||= 64;
|
||||
$key = &$hash_func($key) if length($key) > $block_size;
|
||||
|
||||
my $k_ipad = $key ^ (chr(0x36) x $block_size);
|
||||
my $k_opad = $key ^ (chr(0x5c) x $block_size);
|
||||
|
||||
&$hash_func($k_opad, &$hash_func($k_ipad, $data));
|
||||
}
|
||||
|
||||
# Returns 1 if destination identity server
|
||||
# is blocked
|
||||
sub blocked_hosts {
|
||||
my $csr = shift;
|
||||
|
||||
return do { my $dummy = 0; \$dummy; } if $LJ::IS_DEV_SERVER;
|
||||
|
||||
my $tried_local_id = 0;
|
||||
$csr->ua->blocked_hosts(
|
||||
sub {
|
||||
my $dest = shift;
|
||||
|
||||
if ($dest =~ /((^|\.)\Q$LJ::DOMAIN\E$|demotivation\.me|anonymity\.com)/i) {
|
||||
$tried_local_id = 1;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return \$tried_local_id;
|
||||
}
|
||||
|
||||
1;
|
||||
2739
local/cgi-bin/LJ/S2.pm
Executable file
2739
local/cgi-bin/LJ/S2.pm
Executable file
File diff suppressed because it is too large
Load Diff
232
local/cgi-bin/LJ/S2/DayPage.pm
Executable file
232
local/cgi-bin/LJ/S2/DayPage.pm
Executable file
@@ -0,0 +1,232 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
use strict;
|
||||
package LJ::S2;
|
||||
|
||||
sub DayPage
|
||||
{
|
||||
my ($u, $remote, $opts) = @_;
|
||||
|
||||
my $p = Page($u, $opts);
|
||||
$p->{'_type'} = "DayPage";
|
||||
$p->{'view'} = "day";
|
||||
$p->{'entries'} = [];
|
||||
|
||||
my $user = $u->{'user'};
|
||||
my $journalbase = LJ::journal_base($user, $opts->{'vhost'});
|
||||
|
||||
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
|
||||
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'}) .
|
||||
"/calendar" . $opts->{'pathextra'};
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($u->{'opt_blockrobots'}) {
|
||||
$p->{'head_content'} .= LJ::robot_meta_tags();
|
||||
}
|
||||
|
||||
my $get = $opts->{'getargs'};
|
||||
|
||||
my $month = $get->{'month'};
|
||||
my $day = $get->{'day'};
|
||||
my $year = $get->{'year'};
|
||||
my @errors = ();
|
||||
|
||||
if ($opts->{'pathextra'} =~ m!^/(\d\d\d\d)/(\d\d)/(\d\d)\b!) {
|
||||
($month, $day, $year) = ($2, $3, $1);
|
||||
}
|
||||
|
||||
$opts->{'errors'} = [];
|
||||
if ($year !~ /^\d+$/) { push @{$opts->{'errors'}}, "Corrupt or non-existant year."; }
|
||||
if ($month !~ /^\d+$/) { push @{$opts->{'errors'}}, "Corrupt or non-existant month."; }
|
||||
if ($day !~ /^\d+$/) { push @{$opts->{'errors'}}, "Corrupt or non-existant day."; }
|
||||
if ($month < 1 || $month > 12 || int($month) != $month) { push @{$opts->{'errors'}}, "Invalid month."; }
|
||||
if ($year < 1970 || $year > 2038 || int($year) != $year) { push @{$opts->{'errors'}}, "Invalid year: $year"; }
|
||||
if ($day < 1 || $day > 31 || int($day) != $day) { push @{$opts->{'errors'}}, "Invalid day."; }
|
||||
if (scalar(@{$opts->{'errors'}})==0 && $day > LJ::days_in_month($month, $year)) { push @{$opts->{'errors'}}, "That month doesn't have that many days."; }
|
||||
return if @{$opts->{'errors'}};
|
||||
|
||||
$p->{'date'} = Date($year, $month, $day);
|
||||
# mysqldate_to_time(): ^(\d\d\d\d)-(\d\d)-(\d\d)(?: (\d\d):(\d\d)(?::(\d\d))?)?$/ -> unixtime, with check.
|
||||
# why not using get_recent_items() ?
|
||||
|
||||
#use Time::HiRes qw(gettimeofday tv_interval);
|
||||
#my $t0 = [gettimeofday];
|
||||
#my @elapsed;
|
||||
#push @elapsed, (tv_interval ($t0));
|
||||
#print STDERR "@elapsed \n";
|
||||
|
||||
my $secwhere = "AND security='public'";
|
||||
my $viewall = 0;
|
||||
my $viewsome = 0; # see public posts from suspended users
|
||||
if ($remote) {
|
||||
|
||||
# do they have the viewall priv?
|
||||
if ($get->{'viewall'} && LJ::check_priv($remote, "canview")) {
|
||||
LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
|
||||
"viewall", "day: $user, statusvis: $u->{'statusvis'}");
|
||||
$viewall = LJ::check_priv($remote, 'canview', '*');
|
||||
$viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended');
|
||||
}
|
||||
|
||||
if ($remote->{'userid'} == $u->{'userid'} || $viewall) {
|
||||
$secwhere = ""; # see everything
|
||||
} elsif ($remote->{'journaltype'} eq 'P' || $remote->{'journaltype'} eq 'I') {
|
||||
my $gmask = LJ::get_groupmask($u, $remote);
|
||||
$secwhere = "AND (security='public' OR (security='usemask' AND allowmask & $gmask))"
|
||||
if $gmask;
|
||||
}
|
||||
}
|
||||
|
||||
my $dbcr = LJ::get_cluster_reader($u);
|
||||
unless ($dbcr) {
|
||||
push @{$opts->{'errors'}}, "Database temporarily unavailable";
|
||||
return;
|
||||
}
|
||||
|
||||
# load the log items
|
||||
my $dateformat = "%Y %m %d %H %i %s %w"; # yyyy mm dd hh mm ss day_of_week
|
||||
my $sth = $dbcr->prepare("SELECT jitemid AS itemid, posterid, security, DATE_FORMAT(eventtime, \"$dateformat\") AS 'alldatepart', anum ".
|
||||
"FROM log2 " .
|
||||
"WHERE journalid=$u->{'userid'} AND year=$year AND month=$month AND day=$day $secwhere " .
|
||||
"ORDER BY eventtime, logtime LIMIT 200");
|
||||
$sth->execute;
|
||||
|
||||
my @items;
|
||||
push @items, $_ while $_ = $sth->fetchrow_hashref;
|
||||
#push @elapsed, (tv_interval ($t0));
|
||||
|
||||
LJ::fill_items_with_text_props(\@items, $u);
|
||||
|
||||
# load 'opt_ljcut_disable_lastn' prop for $remote.
|
||||
LJ::load_user_props($remote, "opt_ljcut_disable_lastn");
|
||||
|
||||
my (%apu, %apu_lite); # alt poster users; UserLite objects
|
||||
foreach (@items) {
|
||||
next unless $_->{'posterid'} != $u->{'userid'};
|
||||
$apu{$_->{'posterid'}} = undef;
|
||||
}
|
||||
if (%apu) {
|
||||
LJ::load_userids_multiple([map { $_, \$apu{$_} } keys %apu], [$u]);
|
||||
$apu_lite{$_} = UserLite($apu{$_}) foreach keys %apu;
|
||||
}
|
||||
|
||||
my $userlite_journal = UserLite($u);
|
||||
#push @elapsed, (tv_interval ($t0));
|
||||
|
||||
ENTRY:
|
||||
foreach my $item (@items)
|
||||
{
|
||||
my ($posterid, $itemid, $security, $alldatepart, $anum) =
|
||||
map { $item->{$_} } qw(posterid itemid security alldatepart anum);
|
||||
|
||||
my $props = $item->{'props'};
|
||||
|
||||
my $replycount = $props->{'replycount'};
|
||||
my $subject = $item->{'text'}->[0];
|
||||
my $text = $item->{'text'}->[1];
|
||||
if ($get->{'nohtml'}) {
|
||||
# quote all non-LJ tags
|
||||
$subject =~ s{<(?!/?lj)(.*?)>} {<$1>}gi;
|
||||
$text =~ s{<(?!/?lj)(.*?)>} {<$1>}gi;
|
||||
}
|
||||
|
||||
# don't show posts from suspended users
|
||||
next ENTRY if $apu{$posterid} && $apu{$posterid}->{'statusvis'} eq 'S' && ! $viewsome;
|
||||
|
||||
LJ::CleanHTML::clean_subject(\$subject) if $subject;
|
||||
|
||||
my $ditemid = $itemid*256 + $anum;
|
||||
|
||||
LJ::CleanHTML::clean_event(\$text, { 'preformatted' => $props->{'opt_preformatted'},
|
||||
'cuturl' => LJ::item_link($u, $itemid, $anum),
|
||||
'ljcut_disable' => $remote->{'opt_ljcut_disable_lastn'}, });
|
||||
LJ::expand_embedded($u, $ditemid, $remote, \$text);
|
||||
|
||||
my $nc = "";
|
||||
$nc .= "nc=$replycount" if $replycount; # && $remote && $remote->{'opt_nctalklinks'};
|
||||
|
||||
my $permalink = "$journalbase/$ditemid.html";
|
||||
my $readurl = $permalink;
|
||||
$readurl .= "?$nc" if $nc;
|
||||
my $posturl = $permalink . "?mode=reply";
|
||||
|
||||
my $comments = CommentInfo({
|
||||
'read_url' => $readurl,
|
||||
'post_url' => $posturl,
|
||||
'count' => $replycount,
|
||||
'maxcomments' => ($replycount >= LJ::get_cap($u, 'maxcomments')) ? 1 : 0,
|
||||
'enabled' => ($u->{'opt_showtalklinks'} eq "Y" && ! $props->{'opt_nocomments'}) ? 1 : 0,
|
||||
'screened' => ($props->{'hasscreened'} && $remote &&
|
||||
($remote->{'user'} eq $u->{'user'} || LJ::can_manage($remote, $u))) ? 1 : 0,
|
||||
});
|
||||
|
||||
my $userlite_poster = $userlite_journal;
|
||||
my $pu = $u;
|
||||
if ($u->{'userid'} != $posterid) {
|
||||
$userlite_poster = $apu_lite{$posterid} or die "No apu_lite for posterid=$posterid";
|
||||
$pu = $apu{$posterid};
|
||||
}
|
||||
my $userpic = Image_userpic($pu, 0, $props->{'picture_keyword'});
|
||||
|
||||
my $entry = Entry($u, {
|
||||
'subject' => $subject,
|
||||
'text' => $text,
|
||||
'dateparts' => $alldatepart,
|
||||
'security' => $security,
|
||||
'props' => $props,
|
||||
'itemid' => $ditemid,
|
||||
'journal' => $userlite_journal,
|
||||
'poster' => $userlite_poster,
|
||||
'comments' => $comments,
|
||||
'userpic' => $userpic,
|
||||
'permalink_url' => $permalink,
|
||||
'enable_tags_compatibility' => [$opts->{enable_tags_compatibility}, $opts->{ctx}],
|
||||
});
|
||||
|
||||
push @{$p->{'entries'}}, $entry;
|
||||
}
|
||||
#push @elapsed, (tv_interval ($t0));
|
||||
#print STDERR "@elapsed \n";
|
||||
|
||||
|
||||
if (@{$p->{'entries'}}) {
|
||||
$p->{'has_entries'} = 1;
|
||||
$p->{'entries'}->[0]->{'new_day'} = 1;
|
||||
$p->{'entries'}->[-1]->{'end_day'} = 1;
|
||||
}
|
||||
|
||||
# calculate previous day
|
||||
my $pdyear = $year;
|
||||
my $pdmonth = $month;
|
||||
my $pdday = $day-1;
|
||||
if ($pdday < 1)
|
||||
{
|
||||
if (--$pdmonth < 1)
|
||||
{
|
||||
$pdmonth = 12;
|
||||
$pdyear--;
|
||||
}
|
||||
$pdday = LJ::days_in_month($pdmonth, $pdyear);
|
||||
}
|
||||
|
||||
# calculate next day
|
||||
my $nxyear = $year;
|
||||
my $nxmonth = $month;
|
||||
my $nxday = $day+1;
|
||||
if ($nxday > LJ::days_in_month($nxmonth, $nxyear))
|
||||
{
|
||||
$nxday = 1;
|
||||
if (++$nxmonth > 12) { ++$nxyear; $nxmonth=1; }
|
||||
}
|
||||
|
||||
$p->{'prev_url'} = "$u->{'_journalbase'}/" . sprintf("%04d/%02d/%02d/", $pdyear, $pdmonth, $pdday);
|
||||
$p->{'prev_date'} = Date($pdyear, $pdmonth, $pdday);
|
||||
$p->{'next_url'} = "$u->{'_journalbase'}/" . sprintf("%04d/%02d/%02d/", $nxyear, $nxmonth, $nxday);
|
||||
$p->{'next_date'} = Date($nxyear, $nxmonth, $nxday);
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
1;
|
||||
432
local/cgi-bin/LJ/S2/EntryPage.pm
Executable file
432
local/cgi-bin/LJ/S2/EntryPage.pm
Executable file
@@ -0,0 +1,432 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
use strict;
|
||||
package LJ::S2;
|
||||
|
||||
sub EntryPage
|
||||
{
|
||||
my ($u, $remote, $opts) = @_;
|
||||
|
||||
my $get = $opts->{'getargs'};
|
||||
|
||||
my $p = Page($u, $opts);
|
||||
$p->{'_type'} = "EntryPage";
|
||||
$p->{'view'} = "entry";
|
||||
$p->{'comments'} = [];
|
||||
$p->{'comment_pages'} = undef;
|
||||
|
||||
# setup viewall options
|
||||
my ($viewall, $viewsome) = (0, 0);
|
||||
if ($get->{viewall} && LJ::check_priv($remote, 'canview')) {
|
||||
# we don't log here, as we don't know what entry we're viewing yet. the logging
|
||||
# is done when we call EntryPage_entry below.
|
||||
$viewall = LJ::check_priv($remote, 'canview', '*');
|
||||
$viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended');
|
||||
}
|
||||
|
||||
my ($entry, $s2entry) = EntryPage_entry($u, $remote, $opts);
|
||||
return if $opts->{'suspendeduser'};
|
||||
return if $opts->{'handler_return'};
|
||||
|
||||
$p->{'multiform_on'} = $remote &&
|
||||
($remote->{'userid'} == $u->{'userid'} ||
|
||||
$remote->{'userid'} == $entry->{'posterid'} ||
|
||||
LJ::can_manage($remote, $u));
|
||||
|
||||
my $itemid = $entry->{'itemid'};
|
||||
my $ditemid = $entry->{'itemid'} * 256 + $entry->{'anum'};
|
||||
my $permalink = LJ::journal_base($u) . "/$ditemid.html";
|
||||
my $stylemine = $get->{'style'} eq "mine" ? "style=mine" : "";
|
||||
|
||||
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
|
||||
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'}) .
|
||||
"/$ditemid.html" . $opts->{'pathextra'};
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($u->{'opt_blockrobots'}) {
|
||||
$p->{'head_content'} .= LJ::robot_meta_tags();
|
||||
}
|
||||
if ($LJ::UNICODE) {
|
||||
$p->{'head_content'} .= '<meta http-equiv="Content-Type" content="text/html; charset='.$opts->{'saycharset'}."\" />\n";
|
||||
}
|
||||
|
||||
$p->{'entry'} = $s2entry;
|
||||
|
||||
# add the comments
|
||||
my %userpic;
|
||||
my %user;
|
||||
my $copts = {
|
||||
'thread' => ($get->{'thread'} >> 8),
|
||||
'page' => $get->{'page'},
|
||||
'view' => $get->{'view'},
|
||||
'userpicref' => \%userpic,
|
||||
'userref' => \%user,
|
||||
# user object is cached from call just made in EntryPage_entry
|
||||
'up' => LJ::load_user($s2entry->{'poster'}->{'username'}),
|
||||
'viewall' => $viewall,
|
||||
|
||||
# Okuklivanie tredov - daem upravlenie pol'zovatelyu -
|
||||
'page_size' => $get->{'page_size'},
|
||||
'max_subjects' => $get->{'max_subjects'},
|
||||
'threading_point' => $get->{'threading_point'},
|
||||
'uncollapse' => $get->{'uncollapse'},
|
||||
};
|
||||
|
||||
my $userlite_journal = UserLite($u);
|
||||
|
||||
my @comments = LJ::Talk::load_comments($u, $remote, "L", $itemid, $copts);
|
||||
|
||||
my $pics = LJ::Talk::get_subjecticons()->{'pic'}; # hashref of imgname => { w, h, img }
|
||||
my $convert_comments = sub {
|
||||
my ($self, $destlist, $srclist, $depth) = @_;
|
||||
|
||||
foreach my $com (@$srclist) {
|
||||
my $dtalkid = $com->{'talkid'} * 256 + $entry->{'anum'};
|
||||
my $text = $com->{'body'};
|
||||
if ($get->{'nohtml'}) {
|
||||
# quote all non-LJ tags
|
||||
$text =~ s{<(?!/?lj)(.*?)>} {<$1>}gi;
|
||||
}
|
||||
LJ::CleanHTML::clean_comment(\$text, { 'preformatted' => $com->{'props'}->{'opt_preformatted'},
|
||||
'anon_comment' => !$com->{posterid}});
|
||||
|
||||
# local time in mysql format to gmtime
|
||||
my $datetime = DateTime_unix(LJ::mysqldate_to_time($com->{'datepost'}));
|
||||
if ($datetime == 0) {
|
||||
$datetime = "Invalid date";
|
||||
}
|
||||
|
||||
my $subject_icon = undef;
|
||||
if (my $si = $com->{'props'}->{'subjecticon'}) {
|
||||
my $pic = $pics->{$si};
|
||||
$subject_icon = Image("$LJ::IMGPREFIX/talk/$pic->{'img'}",
|
||||
$pic->{'w'}, $pic->{'h'}) if $pic;
|
||||
}
|
||||
|
||||
my $comment_userpic;
|
||||
if (my $pic = $userpic{$com->{'picid'}}) {
|
||||
$comment_userpic = Image("$LJ::USERPIC_ROOT/$com->{'picid'}/$pic->{'userid'}",
|
||||
$pic->{'width'}, $pic->{'height'});
|
||||
}
|
||||
|
||||
my $reply_url = LJ::Talk::talkargs($permalink, "replyto=$dtalkid", $stylemine);
|
||||
|
||||
my $par_url;
|
||||
if ($com->{'parenttalkid'}) {
|
||||
my $dparent = ($com->{'parenttalkid'} << 8) + $entry->{'anum'};
|
||||
$par_url = LJ::Talk::talkargs($permalink, "thread=$dparent", $stylemine) . "#t$dparent";
|
||||
}
|
||||
|
||||
my $poster;
|
||||
if ($com->{'posterid'}) {
|
||||
if ($user{$com->{'posterid'}}) {
|
||||
$poster = UserLite($user{$com->{'posterid'}});
|
||||
} else {
|
||||
$poster = {
|
||||
'_type' => 'UserLite',
|
||||
'username' => $com->{'userpost'},
|
||||
'name' => $com->{'userpost'}, # we don't have this, so fake it
|
||||
'journal_type' => 'P', # fake too, but only people can post, so correct
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
my $s2com = {
|
||||
'_type' => 'Comment',
|
||||
'journal' => $userlite_journal,
|
||||
'metadata' => {
|
||||
'picture_keyword' => $com->{'props'}->{'picture_keyword'},
|
||||
},
|
||||
'permalink_url' => "$permalink?thread=$dtalkid#t$dtalkid",
|
||||
'reply_url' => $reply_url,
|
||||
'poster' => $poster,
|
||||
'replies' => [],
|
||||
'subject' => LJ::ehtml($com->{'subject'}),
|
||||
'subject_icon' => $subject_icon,
|
||||
'talkid' => $dtalkid,
|
||||
'text' => $text,
|
||||
'userpic' => $comment_userpic,
|
||||
'time' => $datetime,
|
||||
'tags' => [],
|
||||
'full' => $com->{'_loaded'} ? 1 : 0,
|
||||
'depth' => $depth,
|
||||
'parent_url' => $par_url,
|
||||
'screened' => $com->{'state'} eq "S" ? 1 : 0,
|
||||
'frozen' => $com->{'state'} eq "F" ? 1 : 0,
|
||||
'link_keyseq' => [ 'delete_comment' ],
|
||||
'anchor' => "t$dtalkid",
|
||||
'dom_id' => "ljcmt$dtalkid",
|
||||
};
|
||||
|
||||
# don't show info from suspended users
|
||||
# FIXME: ideally the load_comments should only return these
|
||||
# items if there are children, otherwise they should be hidden entirely
|
||||
my $pu = $com->{'posterid'} ? $user{$com->{'posterid'}} : undef;
|
||||
if ($pu && $pu->{'statusvis'} eq "S" && !$viewsome) {
|
||||
$s2com->{'text'} = "";
|
||||
$s2com->{'subject'} = "";
|
||||
$s2com->{'full'} = 0;
|
||||
$s2com->{'subject_icon'} = undef;
|
||||
$s2com->{'userpic'} = undef;
|
||||
}
|
||||
|
||||
# Conditionally add more links to the keyseq
|
||||
my $link_keyseq = $s2com->{'link_keyseq'};
|
||||
push @$link_keyseq, $s2com->{'screened'} ? 'unscreen_comment' : 'screen_comment';
|
||||
push @$link_keyseq, $s2com->{'frozen'} ? 'unfreeze_thread' : 'freeze_thread';
|
||||
|
||||
if (@{$com->{'children'}}) {
|
||||
$s2com->{'thread_url'} = LJ::Talk::talkargs($permalink, "thread=$dtalkid", $stylemine) . "#t$dtalkid";
|
||||
}
|
||||
|
||||
# add the poster_ip metadata if remote user has
|
||||
# access to see it.
|
||||
$s2com->{'metadata'}->{'poster_ip'} = $com->{'props'}->{'poster_ip'} if
|
||||
($com->{'props'}->{'poster_ip'} && $remote &&
|
||||
($remote->{'userid'} == $entry->{'posterid'} ||
|
||||
LJ::can_manage($remote, $u) || $viewall));
|
||||
|
||||
push @$destlist, $s2com;
|
||||
|
||||
$self->($self, $s2com->{'replies'}, $com->{'children'}, $depth+1);
|
||||
}
|
||||
};
|
||||
$p->{'comments'} = [];
|
||||
$convert_comments->($convert_comments, $p->{'comments'}, \@comments, 1);
|
||||
|
||||
# prepare the javascript data structure to put in the top of the page
|
||||
# if the remote user is a manager of the comments
|
||||
my $do_commentmanage_js = $p->{'multiform_on'};
|
||||
if ($LJ::DISABLED{'commentmanage'}) {
|
||||
if (ref $LJ::DISABLED{'commentmanage'} eq "CODE") {
|
||||
$do_commentmanage_js = $LJ::DISABLED{'commentmanage'}->($remote);
|
||||
} else {
|
||||
$do_commentmanage_js = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($do_commentmanage_js) {
|
||||
my $js = "<script>\n// don't crawl this. read http://www.livejournal.com/developer/exporting.bml\n";
|
||||
$js .= "var LJ_cmtinfo = {\n";
|
||||
my $canAdmin = LJ::can_manage($remote, $u) ? 1 : 0;
|
||||
$js .= "\tjournal: '$u->{user}',\n";
|
||||
$js .= "\tcanAdmin: $canAdmin,\n";
|
||||
$js .= "\tremote: '$remote->{user}',\n" if $remote;
|
||||
my $recurse = sub {
|
||||
my ($self, $array) = @_;
|
||||
foreach my $i (@$array) {
|
||||
my $has_threads = scalar @{$i->{'replies'}};
|
||||
my $poster = $i->{'poster'} ? $i->{'poster'}{'username'} : "";
|
||||
my $child_ids = join(',', map { $_->{'talkid'} } @{$i->{'replies'}});
|
||||
$js .= "\t$i->{'talkid'}: { rc: [$child_ids], u: '$poster' },\n";
|
||||
$self->($self, $i->{'replies'}) if $has_threads;
|
||||
}
|
||||
};
|
||||
$recurse->($recurse, $p->{'comments'});
|
||||
chop $js; chop $js; # remove final ",\n". stupid javascript.
|
||||
$js .= "\n};\n" .
|
||||
"var LJVAR;\n".
|
||||
"if (!LJVAR) LJVAR = new Object();\n".
|
||||
"LJVAR.imgprefix = \"$LJ::IMGPREFIX\";\n".
|
||||
"</script>\n";
|
||||
$p->{'head_content'} .= $js;
|
||||
$p->{'head_content'} .= "<script src='$LJ::SITEROOT/js/commentmanage.js'></script>\n";
|
||||
|
||||
}
|
||||
|
||||
|
||||
$p->{'viewing_thread'} = $get->{'thread'} ? 1 : 0;
|
||||
|
||||
# default values if there were no comments, because
|
||||
# LJ::Talk::load_comments() doesn't provide them.
|
||||
if ($copts->{'out_error'} eq 'noposts') {
|
||||
$copts->{'out_pages'} = $copts->{'out_page'} = 1;
|
||||
$copts->{'out_items'} = 0;
|
||||
$copts->{'out_itemfirst'} = $copts->{'out_itemlast'} = undef;
|
||||
}
|
||||
|
||||
$p->{'comment_pages'} = ItemRange({
|
||||
'all_subitems_displayed' => ($copts->{'out_pages'} == 1),
|
||||
'current' => $copts->{'out_page'},
|
||||
'from_subitem' => $copts->{'out_itemfirst'},
|
||||
'num_subitems_displayed' => scalar @comments,
|
||||
'to_subitem' => $copts->{'out_itemlast'},
|
||||
'total' => $copts->{'out_pages'},
|
||||
'total_subitems' => $copts->{'out_items'},
|
||||
'_url_of' => sub { return "$permalink?page=" . int($_[0]) .
|
||||
($stylemine ? "&$stylemine" : ''); },
|
||||
});
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
sub EntryPage_fast_check
|
||||
{
|
||||
my ($u, $view, $remote, $opts) = @_;
|
||||
|
||||
return unless ($view eq "entry" || $view eq "reply");
|
||||
|
||||
my $get = $opts->{'getargs'};
|
||||
|
||||
my $r = $opts->{'r'};
|
||||
my $uri = $r->uri;
|
||||
|
||||
my ($ditemid, $itemid, $anum);
|
||||
unless ($uri =~ /(\d+)\.html/) {
|
||||
$opts->{'handler_return'} = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
$ditemid = $1;
|
||||
$anum = $ditemid % 256;
|
||||
$itemid = $ditemid >> 8;
|
||||
|
||||
my $entry = LJ::Talk::get_journal_item($u, $itemid, "props_only");
|
||||
unless ($entry && $entry->{'anum'} == $anum) {
|
||||
$opts->{'handler_return'} = 404;
|
||||
return;
|
||||
}
|
||||
my $props = $entry->{'props'};
|
||||
|
||||
# do they have the viewall priv?
|
||||
my $viewall = 0;
|
||||
my $viewsome = 0;
|
||||
if ($get->{'viewall'} && LJ::check_priv($remote, "canview")) {
|
||||
LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
|
||||
"viewall", "entry: $u->{'user'}, itemid: $itemid, statusvis: $u->{'statusvis'}");
|
||||
$viewall = LJ::check_priv($remote, 'canview', '*');
|
||||
$viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended');
|
||||
}
|
||||
|
||||
# check using normal rules
|
||||
unless (LJ::can_view($remote, $entry) || $viewall) {
|
||||
$opts->{'handler_return'} = 403;
|
||||
return;
|
||||
}
|
||||
|
||||
my $pu = $u;
|
||||
if ($entry->{'posterid'} != $entry->{'ownerid'}) {
|
||||
$pu = LJ::load_userid($entry->{'posterid'});
|
||||
}
|
||||
if (($pu && $pu->{'statusvis'} eq 'S') && !$viewsome) {
|
||||
$opts->{'suspendeduser'} = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
# check If-Modified-Since
|
||||
my $lastmod = $props->{'commentalter'};
|
||||
my $revisiontime = $props->{' revtime'};
|
||||
$lastmod = $revisiontime if $revisiontime && $revisiontime > $lastmod;
|
||||
|
||||
my $ims = $r->header_in('If-Modified-Since');
|
||||
if ($ims) {
|
||||
my $theirtime = LJ::http_to_time($ims);
|
||||
if ($theirtime >= $lastmod && !$remote) {
|
||||
# only for anonymous: logged users will be checked by Etag for exact match
|
||||
# reply special: uniq challange string for regular users
|
||||
if ($view eq "entry" || ($view eq "reply" && $r->header_in('User-Agent') =~ /$LJ::ROBOTS_REGEXP/)) {
|
||||
|
||||
$opts->{'notmodified'} = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
$r->header_out("Last-Modified", LJ::time_to_http($lastmod));
|
||||
}
|
||||
|
||||
sub EntryPage_entry
|
||||
{
|
||||
my ($u, $remote, $opts) = @_;
|
||||
|
||||
### NB! EntryPage_fast_check was previously called, so all checks passed.
|
||||
|
||||
my $get = $opts->{'getargs'};
|
||||
|
||||
my $r = $opts->{'r'};
|
||||
my $uri = $r->uri;
|
||||
|
||||
my ($ditemid, $itemid, $anum);
|
||||
unless ($uri =~ /(\d+)\.html/) {
|
||||
$opts->{'handler_return'} = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
$ditemid = $1;
|
||||
$anum = $ditemid % 256;
|
||||
$itemid = $ditemid >> 8;
|
||||
|
||||
my $entry = LJ::Talk::get_journal_item($u, $itemid);
|
||||
unless ($entry && $entry->{'anum'} == $anum) {
|
||||
$opts->{'handler_return'} = 404;
|
||||
return;
|
||||
}
|
||||
### more checks skipped ###
|
||||
|
||||
my $props = $entry->{'props'};
|
||||
|
||||
my $userlite_journal = UserLite($u);
|
||||
my $userlite_poster = $userlite_journal;
|
||||
my $pu = $u;
|
||||
if ($entry->{'posterid'} != $entry->{'ownerid'}) {
|
||||
$pu = LJ::load_userid($entry->{'posterid'});
|
||||
$userlite_poster = UserLite($pu);
|
||||
}
|
||||
|
||||
my $replycount = $props->{'replycount'};
|
||||
my $nc = "";
|
||||
$nc .= "nc=$replycount" if $replycount; # && $remote && $remote->{'opt_nctalklinks'};
|
||||
|
||||
my $stylemine = $get->{'style'} eq "mine" ? "style=mine" : "";
|
||||
|
||||
my $userpic = Image_userpic($pu, 0, $props->{'picture_keyword'});
|
||||
|
||||
my $permalink = LJ::journal_base($u) . "/$ditemid.html";
|
||||
my $readurl = LJ::Talk::talkargs($permalink, $nc, $stylemine);
|
||||
my $posturl = LJ::Talk::talkargs($permalink, "mode=reply", $stylemine);
|
||||
|
||||
my $comments = CommentInfo({
|
||||
'read_url' => $readurl,
|
||||
'post_url' => $posturl,
|
||||
'count' => $replycount,
|
||||
'maxcomments' => ($replycount >= LJ::get_cap($u, 'maxcomments')) ? 1 : 0,
|
||||
'enabled' => ($u->{'opt_showtalklinks'} eq "Y" && !
|
||||
$props->{'opt_nocomments'}) ? 1 : 0,
|
||||
'screened' => ($props->{'hasscreened'} && $remote &&
|
||||
LJ::can_manage($remote, $u)) ? 1 : 0,
|
||||
});
|
||||
|
||||
# format it
|
||||
if ($opts->{'getargs'}->{'nohtml'}) {
|
||||
# quote all non-LJ tags
|
||||
$entry->{'subject'} =~ s{<(?!/?lj)(.*?)>} {<$1>}gi;
|
||||
$entry->{'event'} =~ s{<(?!/?lj)(.*?)>} {<$1>}gi;
|
||||
}
|
||||
my $raw_subj = $entry->{'subject'};
|
||||
LJ::CleanHTML::clean_subject(\$entry->{'subject'});
|
||||
LJ::CleanHTML::clean_event(\$entry->{'event'}, $props->{'opt_preformatted'});
|
||||
LJ::expand_embedded($u, $ditemid, $remote, \$entry->{'event'});
|
||||
|
||||
my $s2entry = Entry($u, {
|
||||
'_rawsubject' => $raw_subj,
|
||||
'subject' => $entry->{'subject'},
|
||||
'text' => $entry->{'event'},
|
||||
'dateparts' => $entry->{'alldatepart'},
|
||||
'security' => $entry->{'security'},
|
||||
'props' => $props,
|
||||
'itemid' => $ditemid,
|
||||
'comments' => $comments,
|
||||
'journal' => $userlite_journal,
|
||||
'poster' => $userlite_poster,
|
||||
'new_day' => 0,
|
||||
'end_day' => 0,
|
||||
'userpic' => $userpic,
|
||||
'permalink_url' => $permalink,
|
||||
'enable_tags_compatibility' => [$opts->{enable_tags_compatibility}, $opts->{ctx}],
|
||||
});
|
||||
|
||||
return ($entry, $s2entry);
|
||||
}
|
||||
|
||||
1;
|
||||
444
local/cgi-bin/LJ/S2/FriendsPage.pm
Executable file
444
local/cgi-bin/LJ/S2/FriendsPage.pm
Executable file
@@ -0,0 +1,444 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
use strict;
|
||||
package LJ::S2;
|
||||
|
||||
eval "use LJR::Distributed;";
|
||||
my $ljr = $@ ? 0 : 1;
|
||||
|
||||
if ($ljr) {
|
||||
use LJR::Distributed;
|
||||
}
|
||||
|
||||
sub FriendsPage
|
||||
{
|
||||
my ($u, $remote, $opts) = @_;
|
||||
|
||||
my $p = Page($u, $opts);
|
||||
$p->{'_type'} = "FriendsPage";
|
||||
$p->{'view'} = "friends";
|
||||
$p->{'entries'} = [];
|
||||
$p->{'friends'} = {};
|
||||
$p->{'friends_title'} = LJ::ehtml($u->{'friendspagetitle'});
|
||||
$p->{'filter_active'} = 0;
|
||||
$p->{'filter_name'} = "";
|
||||
|
||||
my $sth;
|
||||
my $user = $u->{'user'};
|
||||
|
||||
# see how often the remote user can reload this page.
|
||||
# "friendsviewupdate" time determines what granularity time
|
||||
# increments by for checking for new updates
|
||||
my $nowtime = time();
|
||||
|
||||
# update delay specified by "friendsviewupdate"
|
||||
my $newinterval = LJ::get_cap_min($remote, "friendsviewupdate") || 1;
|
||||
|
||||
# when are we going to say page was last modified? back up to the
|
||||
# most recent time in the past where $time % $interval == 0
|
||||
my $lastmod = $nowtime;
|
||||
$lastmod -= $lastmod % $newinterval;
|
||||
|
||||
# see if they have a previously cached copy of this page they
|
||||
# might be able to still use.
|
||||
my $ims = $opts->{'r'}->header_in('If-Modified-Since');
|
||||
if ($ims) {
|
||||
my $theirtime = LJ::http_to_time($ims);
|
||||
|
||||
# send back a 304 Not Modified if they say they've reloaded this
|
||||
# document in the last $newinterval seconds:
|
||||
unless ($theirtime < $lastmod) {
|
||||
$opts->{'handler_return'} = 304;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
$opts->{'r'}->header_out('Last-Modified', LJ::time_to_http($lastmod));
|
||||
|
||||
my $get = $opts->{'getargs'};
|
||||
|
||||
my $ret;
|
||||
|
||||
if ($get->{'mode'} eq "live") {
|
||||
$ret .= "<html><head><title>${user}'s friends: live!</title></head>\n";
|
||||
$ret .= "<frameset rows=\"100%,0%\" border=0>\n";
|
||||
$ret .= " <frame name=livetop src=\"friends?mode=framed\">\n";
|
||||
$ret .= " <frame name=livebottom src=\"friends?mode=livecond&lastitemid=0\">\n";
|
||||
$ret .= "</frameset></html>\n";
|
||||
return $ret;
|
||||
}
|
||||
|
||||
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
|
||||
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'}) . "/friends";
|
||||
return 1;
|
||||
}
|
||||
|
||||
LJ::load_user_props($remote, "opt_nctalklinks", "opt_stylemine", "opt_imagelinks", "opt_ljcut_disable_friends");
|
||||
|
||||
# load options for image links
|
||||
my ($maximgwidth, $maximgheight) = (undef, undef);
|
||||
($maximgwidth, $maximgheight) = ($1, $2)
|
||||
if ($remote && $remote->{'userid'} == $u->{'userid'} &&
|
||||
$remote->{'opt_imagelinks'} =~ m/^(\d+)\|(\d+)$/);
|
||||
|
||||
## never have spiders index friends pages (change too much, and some
|
||||
## people might not want to be indexed)
|
||||
$p->{'head_content'} .= LJ::robot_meta_tags();
|
||||
|
||||
my $itemshow = S2::get_property_value($opts->{'ctx'}, "page_friends_items")+0;
|
||||
if ($itemshow < 1) { $itemshow = 20; }
|
||||
elsif ($itemshow > 50) { $itemshow = 50; }
|
||||
|
||||
my $skip = $get->{'skip'}+0;
|
||||
my $maxskip = ($LJ::MAX_SCROLLBACK_FRIENDS || 1000) - $itemshow;
|
||||
if ($skip > $maxskip) { $skip = $maxskip; }
|
||||
if ($skip < 0) { $skip = 0; }
|
||||
my $itemload = $itemshow+$skip;
|
||||
|
||||
my $dayskip = $get->{'dayskip'}+0;
|
||||
|
||||
my $filter;
|
||||
my $group;
|
||||
my $common_filter = 1;
|
||||
|
||||
if (defined $get->{'filter'} && $remote && $remote->{'user'} eq $user) {
|
||||
$filter = $get->{'filter'};
|
||||
$common_filter = 0;
|
||||
$p->{'filter_active'} = 1;
|
||||
$p->{'filter_name'} = "";
|
||||
} else {
|
||||
if ($opts->{'pathextra'}) {
|
||||
$group = $opts->{'pathextra'};
|
||||
$group =~ s!^/!!;
|
||||
$group =~ s!/$!!;
|
||||
if ($group) { $group = LJ::durl($group); $common_filter = 0; }
|
||||
}
|
||||
if ($group) {
|
||||
$p->{'filter_active'} = 1;
|
||||
$p->{'filter_name'} = LJ::ehtml($group);
|
||||
}
|
||||
my $grp = LJ::get_friend_group($u, { 'name' => $group || "Default View" });
|
||||
my $bit = $grp->{'groupnum'};
|
||||
my $public = $grp->{'is_public'};
|
||||
if ($bit && ($public || ($remote && $remote->{'user'} eq $user))) {
|
||||
$filter = (1 << $bit);
|
||||
} elsif ($group) {
|
||||
$opts->{'badfriendgroup'} = 1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($opts->{'view'} eq "friendsfriends") {
|
||||
$p->{'friends_mode'} = "friendsfriends";
|
||||
}
|
||||
|
||||
if ($get->{'mode'} eq "livecond")
|
||||
{
|
||||
## load the itemids
|
||||
my @items = LJ::get_friend_items({
|
||||
'u' => $u,
|
||||
'remote' => $remote,
|
||||
'itemshow' => 1,
|
||||
'skip' => 0,
|
||||
'filter' => $filter,
|
||||
'common_filter' => $common_filter,
|
||||
});
|
||||
my $first = @items ? $items[0]->{'itemid'} : 0;
|
||||
|
||||
$ret .= "time = " . scalar(time()) . "<br />";
|
||||
$opts->{'headers'}->{'Refresh'} = "30;URL=$LJ::SITEROOT/users/$user/friends?mode=livecond&lastitemid=$first";
|
||||
if ($get->{'lastitemid'} == $first) {
|
||||
$ret .= "nothing new!";
|
||||
} else {
|
||||
if ($get->{'lastitemid'}) {
|
||||
$ret .= "<b>New stuff!</b>\n";
|
||||
$ret .= "<script language=\"JavaScript\">\n";
|
||||
$ret .= "window.parent.livetop.location.reload(true);\n";
|
||||
$ret .= "</script>\n";
|
||||
$opts->{'trusted_html'} = 1;
|
||||
} else {
|
||||
$ret .= "Friends Live! started.";
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
## load the itemids
|
||||
my %friends;
|
||||
my %friends_row;
|
||||
my @items = LJ::get_friend_items({
|
||||
'u' => $u,
|
||||
'remote' => $remote,
|
||||
'itemshow' => $itemshow,
|
||||
'skip' => $skip,
|
||||
'dayskip' => $dayskip,
|
||||
'filter' => $filter,
|
||||
'common_filter' => $common_filter,
|
||||
'friends_u' => \%friends,
|
||||
'friends' => \%friends_row,
|
||||
'showtypes' => $get->{'show'},
|
||||
'friendsoffriends' => $opts->{'view'} eq "friendsfriends",
|
||||
'dateformat' => 'S2',
|
||||
});
|
||||
|
||||
while ($_ = each %friends) {
|
||||
# we expect fgcolor/bgcolor to be in here later
|
||||
$friends{$_}->{'fgcolor'} = $friends_row{$_}->{'fgcolor'} || '#ffffff';
|
||||
$friends{$_}->{'bgcolor'} = $friends_row{$_}->{'bgcolor'} || '#000000';
|
||||
}
|
||||
|
||||
return $p unless %friends;
|
||||
|
||||
my %posters;
|
||||
{
|
||||
my @posterids;
|
||||
foreach my $item (@items) {
|
||||
next if $friends{$item->{'posterid'}};
|
||||
push @posterids, $item->{'posterid'};
|
||||
}
|
||||
LJ::load_userids_multiple([ map { $_ => \$posters{$_} } @posterids ])
|
||||
if @posterids;
|
||||
}
|
||||
|
||||
my %objs_of_picid;
|
||||
my @userpic_load;
|
||||
|
||||
my %lite; # posterid -> s2_UserLite
|
||||
my $get_lite = sub {
|
||||
my $id = shift;
|
||||
return $lite{$id} if $lite{$id};
|
||||
return $lite{$id} = UserLite($posters{$id} || $friends{$id});
|
||||
};
|
||||
|
||||
my $eventnum = 0;
|
||||
my $hiddenentries = 0;
|
||||
ENTRY:
|
||||
foreach my $item (@items)
|
||||
{
|
||||
my ($friendid, $posterid, $itemid, $security, $alldatepart) =
|
||||
map { $item->{$_} } qw(ownerid posterid itemid security alldatepart);
|
||||
|
||||
my $fru = $friends{$friendid};
|
||||
my ($friend, $poster);
|
||||
$friend = $poster = $fru->{'user'};
|
||||
$p->{'friends'}->{$fru->{'user'}} ||= Friend($fru);
|
||||
|
||||
my $clusterid = $item->{'clusterid'}+0;
|
||||
|
||||
my $props = $item->{'props'};
|
||||
|
||||
my $replycount = $props->{'replycount'};
|
||||
my $subject = $item->{'text'}->[0];
|
||||
my $text = $item->{'text'}->[1];
|
||||
if ($get->{'nohtml'}) {
|
||||
# quote all non-LJ tags
|
||||
$subject =~ s{<(?!/?lj)(.*?)>} {<$1>}gi;
|
||||
$text =~ s{<(?!/?lj)(.*?)>} {<$1>}gi;
|
||||
}
|
||||
|
||||
LJ::CleanHTML::clean_subject(\$subject) if $subject;
|
||||
|
||||
my $ditemid = $itemid * 256 + $item->{'anum'};
|
||||
|
||||
my $stylemine = "";
|
||||
$stylemine .= "style=mine" if $remote && $remote->{'opt_stylemine'} &&
|
||||
$remote->{'userid'} != $friendid;
|
||||
|
||||
LJ::CleanHTML::clean_event(\$text, { 'preformatted' => $props->{'opt_preformatted'},
|
||||
'cuturl' => LJ::item_link($fru, $itemid, $item->{'anum'}, $stylemine),
|
||||
'maximgwidth' => $maximgwidth,
|
||||
'maximgheight' => $maximgheight,
|
||||
'ljcut_disable' => $remote->{'opt_ljcut_disable_friends'}, });
|
||||
LJ::expand_embedded($fru, $ditemid, $remote, \$text);
|
||||
|
||||
my $userlite_poster = $get_lite->($posterid);
|
||||
my $userlite_journal = $get_lite->($friendid);
|
||||
|
||||
# get the poster user
|
||||
my $po = $posters{$posterid} || $friends{$posterid};
|
||||
|
||||
# don't allow posts from suspended users
|
||||
if ($po->{'statusvis'} eq 'S') {
|
||||
$hiddenentries++; # Remember how many we've skipped for later
|
||||
next ENTRY;
|
||||
}
|
||||
|
||||
# do the picture
|
||||
my $picid = 0;
|
||||
my $picu = undef;
|
||||
if ($friendid != $posterid && S2::get_property_value($opts->{ctx}, 'use_shared_pic')) {
|
||||
# using the community, the user wants to see shared pictures
|
||||
$picu = $fru;
|
||||
|
||||
# use shared pic for community
|
||||
$picid = $fru->{defaultpicid};
|
||||
} else {
|
||||
# we're using the poster for this picture
|
||||
$picu = $po;
|
||||
|
||||
# check if they specified one
|
||||
$picid = LJ::get_picid_from_keyword($po, $props->{picture_keyword})
|
||||
if $props->{picture_keyword};
|
||||
|
||||
# fall back on the poster's default
|
||||
$picid ||= $po->{defaultpicid};
|
||||
}
|
||||
|
||||
my $nc = "";
|
||||
$nc .= "nc=$replycount" if $replycount; # && $remote && $remote->{'opt_nctalklinks'};
|
||||
|
||||
my $journalbase = LJ::journal_base($fru);
|
||||
my $permalink = "$journalbase/$ditemid.html";
|
||||
my $readurl = LJ::Talk::talkargs($permalink, $nc, $stylemine);
|
||||
my $posturl = LJ::Talk::talkargs($permalink, "mode=reply", $stylemine);
|
||||
my $synurl = "";
|
||||
|
||||
if ($ljr && $props->{'syn_link'}) {
|
||||
my $rs = LJR::Distributed::match_remote_server($props->{'syn_link'});
|
||||
if ($rs->{"servertype"} eq "lj") {
|
||||
$readurl = $props->{'syn_link'};
|
||||
$posturl = $props->{'syn_link'} . "?mode=reply";
|
||||
$replycount = 'Read';
|
||||
}
|
||||
else {
|
||||
$posturl = $props->{'syn_link'};
|
||||
$replycount = undef;
|
||||
}
|
||||
$synurl = $props->{'syn_link'};
|
||||
}
|
||||
|
||||
my $comments = CommentInfo({
|
||||
'read_url' => $readurl,
|
||||
'post_url' => $posturl,
|
||||
'syn_url' => $synurl,
|
||||
'count' => $replycount,
|
||||
'maxcomments' => ($replycount >= LJ::get_cap($u, 'maxcomments')) ? 1 : 0,
|
||||
'enabled' => ($fru->{'opt_showtalklinks'} eq "Y" &&
|
||||
! $props->{'opt_nocomments'} ||
|
||||
$props->{'syn_link'}
|
||||
) ? 1 : 0,
|
||||
'screened' => ($remote && LJ::can_manage($remote, $fru) && $props->{'hasscreened'}) ? 1 : 0,
|
||||
});
|
||||
|
||||
my $moodthemeid = $u->{'opt_forcemoodtheme'} eq 'Y' ?
|
||||
$u->{'moodthemeid'} : $fru->{'moodthemeid'};
|
||||
|
||||
my $entry = Entry($u, {
|
||||
'subject' => $subject,
|
||||
'text' => $text,
|
||||
'dateparts' => $alldatepart,
|
||||
'security' => $security,
|
||||
'props' => $props,
|
||||
'itemid' => $ditemid,
|
||||
'journal' => $userlite_journal,
|
||||
'poster' => $userlite_poster,
|
||||
'comments' => $comments,
|
||||
'new_day' => 0, # setup below
|
||||
'end_day' => 0, # setup below
|
||||
'userpic' => undef,
|
||||
'permalink_url' => $permalink,
|
||||
'base_url' => $journalbase,
|
||||
'moodthemeid' => $moodthemeid,
|
||||
'enable_tags_compatibility' => [$opts->{enable_tags_compatibility}, $opts->{ctx}],
|
||||
});
|
||||
$entry->{'_ymd'} = join('-', map { $entry->{'time'}->{$_} } qw(year month day));
|
||||
|
||||
if ($picid && $picu) {
|
||||
push @userpic_load, [ $picu, $picid ];
|
||||
push @{$objs_of_picid{$picid}}, \$entry->{'userpic'};
|
||||
}
|
||||
|
||||
push @{$p->{'entries'}}, $entry;
|
||||
$eventnum++;
|
||||
|
||||
} # end while
|
||||
|
||||
# set the new_day and end_day members.
|
||||
if ($eventnum) {
|
||||
for (my $i = 0; $i < $eventnum; $i++) {
|
||||
my $entry = $p->{'entries'}->[$i];
|
||||
$entry->{'new_day'} = 1;
|
||||
my $last = $i;
|
||||
for (my $j = $i+1; $j < $eventnum; $j++) {
|
||||
my $ej = $p->{'entries'}->[$j];
|
||||
if ($ej->{'_ymd'} eq $entry->{'_ymd'}) {
|
||||
$last = $j;
|
||||
}
|
||||
}
|
||||
$p->{'entries'}->[$last]->{'end_day'} = 1;
|
||||
$i = $last;
|
||||
}
|
||||
}
|
||||
|
||||
# load the pictures that were referenced, then retroactively populate
|
||||
# the userpic fields of the Entries above
|
||||
my %userpics;
|
||||
LJ::load_userpics(\%userpics, \@userpic_load);
|
||||
|
||||
foreach my $picid (keys %userpics) {
|
||||
my $up = Image("$LJ::USERPIC_ROOT/$picid/$userpics{$picid}->{'userid'}",
|
||||
$userpics{$picid}->{'width'},
|
||||
$userpics{$picid}->{'height'});
|
||||
foreach (@{$objs_of_picid{$picid}}) { $$_ = $up; }
|
||||
}
|
||||
|
||||
# make the skip links
|
||||
my $nav = {
|
||||
'_type' => 'RecentNav',
|
||||
'version' => 1,
|
||||
'skip' => $skip,
|
||||
'count' => $eventnum,
|
||||
};
|
||||
|
||||
my $base = "$u->{'_journalbase'}/$opts->{'view'}";
|
||||
if ($group) {
|
||||
$base .= "/" . LJ::eurl($group);
|
||||
}
|
||||
|
||||
# $linkfilter is distinct from $filter: if user has a default view,
|
||||
# $filter is now set according to it but we don't want it to show in the links.
|
||||
# $incfilter may be true even if $filter is 0: user may use filter=0 to turn
|
||||
# off the default group
|
||||
my $linkfilter = $get->{'filter'} + 0;
|
||||
my $incfilter = defined $get->{'filter'};
|
||||
|
||||
# if we've skipped down, then we can skip back up
|
||||
if ($skip) {
|
||||
my %linkvars;
|
||||
$linkvars{'filter'} = $linkfilter if $incfilter;
|
||||
$linkvars{'show'} = $get->{'show'} if $get->{'show'} =~ /^\w+$/;
|
||||
my $newskip = $skip - $itemshow;
|
||||
if ($newskip > 0) { $linkvars{'skip'} = $newskip; }
|
||||
else { $newskip = 0; }
|
||||
$linkvars{'dayskip'} = $dayskip if $dayskip;
|
||||
$nav->{'forward_url'} = LJ::make_link($base, \%linkvars);
|
||||
$nav->{'forward_skip'} = $newskip;
|
||||
$nav->{'forward_count'} = $itemshow;
|
||||
}
|
||||
|
||||
## unless we didn't even load as many as we were expecting on this
|
||||
## page, then there are more (unless there are exactly the number shown
|
||||
## on the page, but who cares about that)
|
||||
# Must remember to count $hiddenentries or we'll have no skiplinks when > 1
|
||||
unless (($eventnum + $hiddenentries) != $itemshow || $skip == $maxskip) {
|
||||
my %linkvars;
|
||||
$linkvars{'filter'} = $linkfilter if $incfilter;
|
||||
$linkvars{'show'} = $get->{'show'} if $get->{'show'} =~ /^\w+$/;
|
||||
my $newskip = $skip + $itemshow;
|
||||
$linkvars{'skip'} = $newskip;
|
||||
$linkvars{'dayskip'} = $dayskip if $dayskip;
|
||||
$nav->{'backward_url'} = LJ::make_link($base, \%linkvars);
|
||||
$nav->{'backward_skip'} = $newskip;
|
||||
$nav->{'backward_count'} = $itemshow;
|
||||
}
|
||||
|
||||
$p->{'nav'} = $nav;
|
||||
|
||||
if ($get->{'mode'} eq "framed") {
|
||||
$p->{'head_content'} .= "<base target='_top' />";
|
||||
}
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
1;
|
||||
224
local/cgi-bin/LJ/S2/MonthPage.pm
Executable file
224
local/cgi-bin/LJ/S2/MonthPage.pm
Executable file
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
use strict;
|
||||
package LJ::S2;
|
||||
|
||||
sub MonthPage
|
||||
{
|
||||
my ($u, $remote, $opts) = @_;
|
||||
|
||||
my $get = $opts->{'getargs'};
|
||||
|
||||
my $p = Page($u, $opts);
|
||||
$p->{'_type'} = "MonthPage";
|
||||
$p->{'view'} = "month";
|
||||
$p->{'days'} = [];
|
||||
|
||||
my $ctx = $opts->{'ctx'};
|
||||
|
||||
my $dbcr = LJ::get_cluster_reader($u);
|
||||
|
||||
my $user = $u->{'user'};
|
||||
my $journalbase = LJ::journal_base($user, $opts->{'vhost'});
|
||||
|
||||
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
|
||||
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'}) .
|
||||
"/" . $opts->{'pathextra'};
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($u->{'opt_blockrobots'}) {
|
||||
$p->{'head_content'} .= LJ::robot_meta_tags();
|
||||
}
|
||||
|
||||
my ($year, $month);
|
||||
if ($opts->{'pathextra'} =~ m!^/(\d\d\d\d)/(\d\d)\b!) {
|
||||
($year, $month) = ($1, $2);
|
||||
}
|
||||
|
||||
$opts->{'errors'} = [];
|
||||
if ($month < 1 || $month > 12) { push @{$opts->{'errors'}}, "Invalid month: $month"; }
|
||||
if ($year < 1970 || $year > 2038) { push @{$opts->{'errors'}}, "Invalid year: $year"; }
|
||||
unless ($dbcr) { push @{$opts->{'errors'}}, "Database temporarily unavailable"; }
|
||||
return if @{$opts->{'errors'}};
|
||||
|
||||
$p->{'date'} = Date($year, $month, 0);
|
||||
|
||||
# load the log items
|
||||
my $dateformat = "%Y %m %d %H %i %s %w"; # yyyy mm dd hh mm ss day_of_week
|
||||
my $sth;
|
||||
|
||||
my $secwhere = "AND l.security='public'";
|
||||
my $viewall = 0;
|
||||
my $viewsome = 0;
|
||||
if ($remote) {
|
||||
|
||||
# do they have the viewall priv?
|
||||
if ($get->{'viewall'} && LJ::check_priv($remote, "canview")) {
|
||||
LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
|
||||
"viewall", "month: $user, statusvis: $u->{'statusvis'}");
|
||||
$viewall = LJ::check_priv($remote, 'canview', '*');
|
||||
$viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended');
|
||||
}
|
||||
|
||||
if ($remote->{'userid'} == $u->{'userid'} || $viewall) {
|
||||
$secwhere = ""; # see everything
|
||||
} elsif ($remote->{'journaltype'} eq 'P' || $remote->{'journaltype'} eq 'I') {
|
||||
my $gmask = LJ::get_groupmask($u, $remote);
|
||||
$secwhere = "AND (l.security='public' OR (l.security='usemask' AND l.allowmask & $gmask))"
|
||||
if $gmask;
|
||||
}
|
||||
}
|
||||
|
||||
$sth = $dbcr->prepare("SELECT l.jitemid AS 'itemid', l.posterid, l.anum, l.day, ".
|
||||
" DATE_FORMAT(l.eventtime, '$dateformat') AS 'alldatepart', ".
|
||||
" l.replycount, l.security ".
|
||||
"FROM log2 l ".
|
||||
"WHERE l.journalid=? AND l.year=? AND l.month=? ".
|
||||
"$secwhere LIMIT 2000");
|
||||
$sth->execute($u->{'userid'}, $year, $month);
|
||||
|
||||
my @items;
|
||||
push @items, $_ while $_ = $sth->fetchrow_hashref;
|
||||
@items = sort { $a->{'alldatepart'} cmp $b->{'alldatepart'} } @items;
|
||||
|
||||
LJ::fill_items_with_text_props(\@items, $u, {'only_subject' => 1});
|
||||
|
||||
my (%pu, %pu_lite); # poster users; UserLite objects
|
||||
foreach (@items) {
|
||||
$pu{$_->{'posterid'}} = undef;
|
||||
}
|
||||
LJ::load_userids_multiple([map { $_, \$pu{$_} } keys %pu], [$u]);
|
||||
$pu_lite{$_} = UserLite($pu{$_}) foreach keys %pu;
|
||||
|
||||
my %day_entries; # <day> -> [ Entry+ ]
|
||||
|
||||
my $opt_text_subjects = S2::get_property_value($ctx, "page_month_textsubjects");
|
||||
my $userlite_journal = UserLite($u);
|
||||
|
||||
ENTRY:
|
||||
foreach my $item (@items)
|
||||
{
|
||||
my ($posterid, $itemid, $security, $alldatepart, $replycount, $anum) =
|
||||
map { $item->{$_} } qw(posterid itemid security alldatepart replycount anum);
|
||||
my $day = $item->{'day'};
|
||||
|
||||
# don't show posts from suspended users
|
||||
next unless $pu{$posterid};
|
||||
next ENTRY if $pu{$posterid}->{'statusvis'} eq 'S' && !$viewsome;
|
||||
|
||||
my $subject = $item->{'text'}->[0];
|
||||
my $props = $item->{'props'};
|
||||
|
||||
if ($opt_text_subjects) {
|
||||
LJ::CleanHTML::clean_subject_all(\$subject);
|
||||
} else {
|
||||
LJ::CleanHTML::clean_subject(\$subject);
|
||||
}
|
||||
|
||||
my $ditemid = $itemid*256 + $anum;
|
||||
my $nc = "";
|
||||
$nc .= "nc=$replycount" if $replycount; # && $remote && $remote->{'opt_nctalklinks'};
|
||||
my $permalink = "$journalbase/$ditemid.html";
|
||||
my $readurl = $permalink;
|
||||
$readurl .= "?$nc" if $nc;
|
||||
my $posturl = $permalink . "?mode=reply";
|
||||
|
||||
my $comments = CommentInfo({
|
||||
'read_url' => $readurl,
|
||||
'post_url' => $posturl,
|
||||
'count' => $replycount,
|
||||
'maxcomments' => ($replycount >= LJ::get_cap($u, 'maxcomments')) ? 1 : 0,
|
||||
'enabled' => ($u->{'opt_showtalklinks'} eq "Y" && ! $props->{'opt_nocomments'}) ? 1 : 0,
|
||||
'screened' => ($props->{'hasscreened'} && $remote &&
|
||||
($remote->{'user'} eq $u->{'user'} || LJ::can_manage($remote, $u))) ? 1 : 0,
|
||||
});
|
||||
|
||||
my $userlite_poster = $userlite_journal;
|
||||
my $userpic = $p->{'journal'}->{'default_pic'};
|
||||
if ($u->{'userid'} != $posterid) {
|
||||
$userlite_poster = $pu_lite{$posterid};
|
||||
$userpic = Image_userpic($pu{$posterid}, 0, $props->{'picture_keyword'});
|
||||
}
|
||||
|
||||
my $entry = Entry($u, {
|
||||
'subject' => $subject,
|
||||
'text' => "",
|
||||
'dateparts' => $alldatepart,
|
||||
'security' => $security,
|
||||
'props' => $props,
|
||||
'itemid' => $ditemid,
|
||||
'journal' => $userlite_journal,
|
||||
'poster' => $userlite_poster,
|
||||
'comments' => $comments,
|
||||
'userpic' => $userpic,
|
||||
'permalink_url' => $permalink,
|
||||
});
|
||||
|
||||
push @{$day_entries{$day}}, $entry;
|
||||
}
|
||||
|
||||
my $days_month = LJ::days_in_month($month, $year);
|
||||
for my $day (1..$days_month) {
|
||||
my $entries = $day_entries{$day} || [];
|
||||
my $month_day = {
|
||||
'_type' => 'MonthDay',
|
||||
'date' => Date($year, $month, $day),
|
||||
'day' => $day,
|
||||
'has_entries' => scalar @$entries > 0,
|
||||
'num_entries' => scalar @$entries,
|
||||
'url' => $journalbase . sprintf("/%04d/%02d/%02d/", $year, $month, $day),
|
||||
'entries' => $entries,
|
||||
};
|
||||
push @{$p->{'days'}}, $month_day;
|
||||
}
|
||||
|
||||
# populate redirector
|
||||
my $vhost = $opts->{'vhost'};
|
||||
$vhost =~ s/:.*//;
|
||||
$p->{'redir'} = {
|
||||
'_type' => "Redirector",
|
||||
'user' => $u->{'user'},
|
||||
'vhost' => $vhost,
|
||||
'type' => 'monthview',
|
||||
'url' => "$LJ::SITEROOT/go.bml",
|
||||
};
|
||||
|
||||
# figure out what months have been posted into
|
||||
my $nowval = $year*12 + $month;
|
||||
|
||||
$p->{'months'} = [];
|
||||
|
||||
my $days = LJ::get_daycounts($u, $remote) || [];
|
||||
my $lastmo;
|
||||
foreach my $day (@$days) {
|
||||
my ($oy, $om) = ($day->[0], $day->[1]);
|
||||
my $mo = "$oy-$om";
|
||||
next if $mo eq $lastmo;
|
||||
$lastmo = $mo;
|
||||
|
||||
my $date = Date($oy, $om, 0);
|
||||
my $url = $journalbase . sprintf("/%04d/%02d/", $oy, $om);
|
||||
push @{$p->{'months'}}, {
|
||||
'_type' => "MonthEntryInfo",
|
||||
'date' => $date,
|
||||
'url' => $url,
|
||||
'redir_key' => sprintf("%04d%02d", $oy, $om),
|
||||
};
|
||||
|
||||
my $val = $oy*12+$om;
|
||||
if ($val < $nowval) {
|
||||
$p->{'prev_url'} = $url;
|
||||
$p->{'prev_date'} = $date;
|
||||
}
|
||||
if ($val > $nowval && ! $p->{'next_date'}) {
|
||||
$p->{'next_url'} = $url;
|
||||
$p->{'next_date'} = $date;
|
||||
}
|
||||
}
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
1;
|
||||
216
local/cgi-bin/LJ/S2/RecentPage.pm
Executable file
216
local/cgi-bin/LJ/S2/RecentPage.pm
Executable file
@@ -0,0 +1,216 @@
|
||||
use strict;
|
||||
package LJ::S2;
|
||||
|
||||
sub RecentPage
|
||||
{
|
||||
my ($u, $remote, $opts) = @_;
|
||||
|
||||
my $p = Page($u, $opts);
|
||||
$p->{'_type'} = "RecentPage";
|
||||
$p->{'view'} = "recent";
|
||||
$p->{'entries'} = [];
|
||||
|
||||
my $user = $u->{'user'};
|
||||
my $journalbase = LJ::journal_base($user, $opts->{'vhost'});
|
||||
|
||||
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
|
||||
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'});
|
||||
return;
|
||||
}
|
||||
|
||||
LJ::load_user_props($remote, "opt_nctalklinks", "opt_ljcut_disable_lastn");
|
||||
|
||||
my $get = $opts->{'getargs'};
|
||||
|
||||
if ($opts->{'pathextra'}) {
|
||||
$opts->{'badargs'} = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($u->{'opt_blockrobots'} || $get->{'skip'}) {
|
||||
$p->{'head_content'} .= LJ::robot_meta_tags();
|
||||
}
|
||||
|
||||
$p->{'head_content'} .= qq{<link rel="openid.server" href="$LJ::OPENID_SERVER" />\n}
|
||||
if LJ::OpenID::server_enabled();
|
||||
|
||||
my $itemshow = S2::get_property_value($opts->{'ctx'}, "page_recent_items")+0;
|
||||
if ($itemshow < 1) { $itemshow = 20; }
|
||||
elsif ($itemshow > 50) { $itemshow = 50; }
|
||||
|
||||
my $skip = $get->{'skip'}+0;
|
||||
my $maxskip = $LJ::MAX_HINTS_LASTN-$itemshow;
|
||||
if ($skip < 0) { $skip = 0; }
|
||||
if ($skip > $maxskip) { $skip = $maxskip; }
|
||||
|
||||
my $dayskip = $get->{'dayskip'}+0;
|
||||
|
||||
# do they want to view all entries, regardless of security?
|
||||
my $viewall = 0;
|
||||
my $viewsome = 0;
|
||||
if ($get->{'viewall'} && LJ::check_priv($remote, "canview")) {
|
||||
LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
|
||||
"viewall", "lastn: $user, statusvis: $u->{'statusvis'}");
|
||||
$viewall = LJ::check_priv($remote, 'canview', '*');
|
||||
$viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended');
|
||||
}
|
||||
|
||||
## load the items
|
||||
my $err;
|
||||
my @items = LJ::get_recent_items({
|
||||
'u' => $u,
|
||||
'clustersource' => 'slave',
|
||||
'viewall' => $viewall,
|
||||
'remote' => $remote,
|
||||
'itemshow' => $itemshow,
|
||||
'skip' => $skip,
|
||||
'dayskip' => $dayskip,
|
||||
'tags' => $opts->{tags},
|
||||
'dateformat' => 'S2',
|
||||
'order' => ($u->{'journaltype'} eq "C" || $u->{'journaltype'} eq "Y") # community or syndicated
|
||||
? "logtime" : "",
|
||||
'err' => \$err,
|
||||
});
|
||||
|
||||
die $err if $err;
|
||||
|
||||
my $lastdate = "";
|
||||
my $itemnum = 0;
|
||||
my $lastentry = undef;
|
||||
|
||||
my (%apu, %apu_lite); # alt poster users; UserLite objects
|
||||
foreach (@items) {
|
||||
next unless $_->{'posterid'} != $u->{'userid'};
|
||||
$apu{$_->{'posterid'}} = undef;
|
||||
}
|
||||
if (%apu) {
|
||||
LJ::load_userids_multiple([map { $_, \$apu{$_} } keys %apu], [$u]);
|
||||
$apu_lite{$_} = UserLite($apu{$_}) foreach keys %apu;
|
||||
}
|
||||
|
||||
my $userlite_journal = UserLite($u);
|
||||
|
||||
ENTRY:
|
||||
foreach my $item (@items)
|
||||
{
|
||||
my ($posterid, $itemid, $security, $alldatepart) =
|
||||
map { $item->{$_} } qw(posterid itemid security alldatepart);
|
||||
|
||||
my $props = $item->{'props'};
|
||||
|
||||
my $replycount = $props->{'replycount'};
|
||||
my $subject = $item->{'text'}->[0];
|
||||
my $text = $item->{'text'}->[1];
|
||||
if ($get->{'nohtml'}) {
|
||||
# quote all non-LJ tags
|
||||
$subject =~ s{<(?!/?lj)(.*?)>} {<$1>}gi;
|
||||
$text =~ s{<(?!/?lj)(.*?)>} {<$1>}gi;
|
||||
}
|
||||
|
||||
# don't show posts from suspended users unless the user doing the viewing says to (and is allowed)
|
||||
next ENTRY if $apu{$posterid} && $apu{$posterid}->{'statusvis'} eq 'S' && !$viewsome;
|
||||
|
||||
my $date = substr($alldatepart, 0, 10);
|
||||
my $new_day = 0;
|
||||
if ($date ne $lastdate) {
|
||||
$new_day = 1;
|
||||
$lastdate = $date;
|
||||
$lastentry->{'end_day'} = 1 if $lastentry;
|
||||
}
|
||||
|
||||
$itemnum++;
|
||||
LJ::CleanHTML::clean_subject(\$subject) if $subject;
|
||||
|
||||
my $ditemid = $itemid * 256 + $item->{'anum'};
|
||||
LJ::CleanHTML::clean_event(\$text, { 'preformatted' => $props->{'opt_preformatted'},
|
||||
'cuturl' => LJ::item_link($u, $itemid, $item->{'anum'}),
|
||||
'ljcut_disable' => $remote->{"opt_ljcut_disable_lastn"}, });
|
||||
LJ::expand_embedded($u, $ditemid, $remote, \$text);
|
||||
|
||||
my $nc = "";
|
||||
$nc .= "nc=$replycount" if $replycount; # && $remote && $remote->{'opt_nctalklinks'};
|
||||
|
||||
my $permalink = "$journalbase/$ditemid.html";
|
||||
my $readurl = $permalink;
|
||||
$readurl .= "?$nc" if $nc;
|
||||
my $posturl = $permalink . "?mode=reply";
|
||||
|
||||
my $comments = CommentInfo({
|
||||
'read_url' => $readurl,
|
||||
'post_url' => $posturl,
|
||||
'count' => $replycount,
|
||||
'maxcomments' => ($replycount >= LJ::get_cap($u, 'maxcomments')) ? 1 : 0,
|
||||
'enabled' => ($u->{'opt_showtalklinks'} eq "Y" && ! $props->{'opt_nocomments'}) ? 1 : 0,
|
||||
'screened' => ($props->{'hasscreened'} && ($remote->{'user'} eq $u->{'user'}|| LJ::can_manage($remote, $u))) ? 1 : 0,
|
||||
});
|
||||
|
||||
my $userlite_poster = $userlite_journal;
|
||||
my $pu = $u;
|
||||
if ($u->{'userid'} != $posterid) {
|
||||
$userlite_poster = $apu_lite{$posterid} or die "No apu_lite for posterid=$posterid";
|
||||
$pu = $apu{$posterid};
|
||||
}
|
||||
my $userpic = Image_userpic($pu, 0, $props->{'picture_keyword'});
|
||||
|
||||
my $entry = $lastentry = Entry($u, {
|
||||
'subject' => $subject,
|
||||
'text' => $text,
|
||||
'dateparts' => $alldatepart,
|
||||
'security' => $security,
|
||||
'props' => $props,
|
||||
'itemid' => $ditemid,
|
||||
'journal' => $userlite_journal,
|
||||
'poster' => $userlite_poster,
|
||||
'comments' => $comments,
|
||||
'new_day' => $new_day,
|
||||
'end_day' => 0, # if true, set later
|
||||
'userpic' => $userpic,
|
||||
'permalink_url' => $permalink,
|
||||
'enable_tags_compatibility' => [$opts->{enable_tags_compatibility}, $opts->{ctx}],
|
||||
});
|
||||
|
||||
push @{$p->{'entries'}}, $entry;
|
||||
|
||||
} # end huge while loop
|
||||
|
||||
# mark last entry as closing.
|
||||
$p->{'entries'}->[-1]->{'end_day'} = 1 if $itemnum;
|
||||
|
||||
#### make the skip links
|
||||
my $nav = {
|
||||
'_type' => 'RecentNav',
|
||||
'version' => 1,
|
||||
'skip' => $skip,
|
||||
'count' => $itemnum,
|
||||
};
|
||||
|
||||
# if we've skipped down, then we can skip back up
|
||||
if ($skip) {
|
||||
my $newskip = $skip - $itemshow;
|
||||
$newskip = 0 if $newskip <= 0;
|
||||
$nav->{'forward_skip'} = $newskip;
|
||||
$nav->{'forward_url'} = LJ::make_link("$p->{base_url}/", { skip => ($newskip || ""), tag => (LJ::eurl($get->{tag}) || ""), dayskip => ($dayskip || "") });
|
||||
$nav->{'forward_count'} = $itemshow;
|
||||
}
|
||||
|
||||
# unless we didn't even load as many as we were expecting on this
|
||||
# page, then there are more (unless there are exactly the number shown
|
||||
# on the page, but who cares about that)
|
||||
unless ($itemnum != $itemshow) {
|
||||
$nav->{'backward_count'} = $itemshow;
|
||||
if ($skip == $maxskip) {
|
||||
my $date_slashes = $lastdate; # "yyyy mm dd";
|
||||
$date_slashes =~ s! !/!g;
|
||||
$nav->{'backward_url'} = "$p->{'base_url'}/day/$date_slashes";
|
||||
} else {
|
||||
my $newskip = $skip + $itemshow;
|
||||
$nav->{'backward_url'} = LJ::make_link("$p->{'base_url'}/", { skip => ($newskip || ""), tag => (LJ::eurl($get->{tag}) || ""), dayskip => ($dayskip || "") });
|
||||
$nav->{'backward_skip'} = $newskip;
|
||||
}
|
||||
}
|
||||
|
||||
$p->{'nav'} = $nav;
|
||||
return $p;
|
||||
}
|
||||
|
||||
1;
|
||||
139
local/cgi-bin/LJ/S2/ReplyPage.pm
Executable file
139
local/cgi-bin/LJ/S2/ReplyPage.pm
Executable file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
use strict;
|
||||
package LJ::S2;
|
||||
|
||||
sub ReplyPage
|
||||
{
|
||||
my ($u, $remote, $opts) = @_;
|
||||
|
||||
my $p = Page($u, $opts);
|
||||
$p->{'_type'} = "ReplyPage";
|
||||
$p->{'view'} = "reply";
|
||||
|
||||
my $get = $opts->{'getargs'};
|
||||
|
||||
my ($entry, $s2entry) = EntryPage_entry($u, $remote, $opts);
|
||||
return if $opts->{'suspendeduser'};
|
||||
return if $opts->{'handler_return'};
|
||||
my $ditemid = $entry->{'itemid'}*256 + $entry->{'anum'};
|
||||
$p->{'head_content'} .= $LJ::COMMON_CODE{'chalresp_js'};
|
||||
|
||||
if ($u->{'opt_blockrobots'}) {
|
||||
$p->{'head_content'} .= LJ::robot_meta_tags();
|
||||
}
|
||||
|
||||
$p->{'entry'} = $s2entry;
|
||||
|
||||
# setup the replying item
|
||||
my $replyto = $s2entry;
|
||||
my $parpost;
|
||||
if ($get->{'replyto'}) {
|
||||
my $re_talkid = int($get->{'replyto'} >> 8);
|
||||
my $re_anum = $get->{'replyto'} % 256;
|
||||
unless ($re_anum == $entry->{'anum'}) {
|
||||
$opts->{'handler_return'} = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
my $sql = "SELECT jtalkid, posterid, state, datepost FROM talk2 ".
|
||||
"WHERE journalid=$u->{'userid'} AND jtalkid=$re_talkid ".
|
||||
"AND nodetype='L' AND nodeid=$entry->{'jitemid'}";
|
||||
foreach my $pass (1, 2) {
|
||||
my $db = $pass == 1 ? LJ::get_cluster_reader($u) : LJ::get_cluster_def_reader($u);
|
||||
$parpost = $db->selectrow_hashref($sql);
|
||||
last if $parpost;
|
||||
}
|
||||
unless ($parpost and $parpost->{'state'} ne 'D') {
|
||||
$opts->{'handler_return'} = 404;
|
||||
return;
|
||||
}
|
||||
if ($parpost->{'state'} eq 'S' && !LJ::Talk::can_unscreen($remote, $u, $s2entry->{'poster'}->{'username'}, undef)) {
|
||||
$opts->{'handler_return'} = 403;
|
||||
return;
|
||||
}
|
||||
if ($parpost->{'state'} eq 'F') {
|
||||
# frozen comment, no replies allowed
|
||||
|
||||
# FIXME: eventually have S2 ErrorPage to handle this and similar
|
||||
# For now, this hack will work; this error is pretty uncommon anyway.
|
||||
$opts->{status} = "403 Forbidden";
|
||||
return "<p>This thread has been frozen; no more replies are allowed.</p>";
|
||||
}
|
||||
|
||||
my $tt = LJ::get_talktext2($u, $re_talkid);
|
||||
$parpost->{'subject'} = $tt->{$re_talkid}->[0];
|
||||
$parpost->{'body'} = $tt->{$re_talkid}->[1];
|
||||
$parpost->{'props'} =
|
||||
LJ::load_talk_props2($u, [ $re_talkid ])->{$re_talkid} || {};
|
||||
|
||||
if($LJ::UNICODE && $parpost->{'props'}->{'unknown8bit'}) {
|
||||
LJ::item_toutf8($u, \$parpost->{'subject'}, \$parpost->{'body'}, {});
|
||||
}
|
||||
|
||||
LJ::CleanHTML::clean_comment(\$parpost->{'body'},
|
||||
{ 'preformatted' => $parpost->{'props'}->{'opt_preformatted'},
|
||||
'anon_comment' => !$parpost->{posterid} });
|
||||
|
||||
my $datetime = DateTime_unix(LJ::mysqldate_to_time($parpost->{'datepost'}));
|
||||
|
||||
my ($s2poster, $pu);
|
||||
my $comment_userpic;
|
||||
if ($parpost->{'posterid'}) {
|
||||
$pu = LJ::load_userid($parpost->{'posterid'});
|
||||
return $opts->{handler_return} = 403 if $pu->{statusvis} eq 'S'; # do not show comments by suspended users
|
||||
$s2poster = UserLite($pu);
|
||||
|
||||
# FIXME: this is a little heavy:
|
||||
$comment_userpic = Image_userpic($pu, 0, $parpost->{'props'}->{'picture_keyword'});
|
||||
}
|
||||
|
||||
my $dtalkid = $re_talkid * 256 + $entry->{'anum'};
|
||||
$replyto = {
|
||||
'_type' => 'EntryLite',
|
||||
'subject' => LJ::ehtml($parpost->{'subject'}),
|
||||
'text' => $parpost->{'body'},
|
||||
'userpic' => $comment_userpic,
|
||||
'poster' => $s2poster,
|
||||
'journal' => $s2entry->{'journal'},
|
||||
'metadata' => {},
|
||||
'permalink_url' => $u->{'_journalbase'} . "/$ditemid.html?view=$dtalkid#t$dtalkid",
|
||||
'depth' => 1,
|
||||
'time' => $datetime,
|
||||
};
|
||||
}
|
||||
|
||||
$p->{'replyto'} = $replyto;
|
||||
|
||||
$p->{'form'} = {
|
||||
'_type' => "ReplyForm",
|
||||
'_remote' => $remote,
|
||||
'_u' => $u,
|
||||
'_ditemid' => $ditemid,
|
||||
'_parpost' => $parpost,
|
||||
};
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
package S2::Builtin::LJ;
|
||||
|
||||
sub ReplyForm__print
|
||||
{
|
||||
my ($ctx, $form) = @_;
|
||||
my $remote = $form->{'_remote'};
|
||||
my $u = $form->{'_u'};
|
||||
my $parpost = $form->{'_parpost'};
|
||||
my $parent = $parpost ? $parpost->{'jtalkid'} : 0;
|
||||
|
||||
$S2::pout->(LJ::Talk::talkform({ 'remote' => $remote,
|
||||
'journalu' => $u,
|
||||
'parpost' => $parpost,
|
||||
'replyto' => $parent,
|
||||
'ditemid' => $form->{'_ditemid'},
|
||||
'form' => $form }));
|
||||
|
||||
}
|
||||
|
||||
1;
|
||||
181
local/cgi-bin/LJ/S2/YearPage.pm
Executable file
181
local/cgi-bin/LJ/S2/YearPage.pm
Executable file
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
use strict;
|
||||
package LJ::S2;
|
||||
|
||||
sub YearPage
|
||||
{
|
||||
my ($u, $remote, $opts) = @_;
|
||||
|
||||
my $p = Page($u, $opts);
|
||||
$p->{'_type'} = "YearPage";
|
||||
$p->{'view'} = "archive";
|
||||
|
||||
my $user = $u->{'user'};
|
||||
|
||||
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
|
||||
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'}) .
|
||||
"/calendar" . $opts->{'pathextra'};
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($u->{'opt_blockrobots'}) {
|
||||
$p->{'head_content'} .= LJ::robot_meta_tags();
|
||||
}
|
||||
if ($LJ::UNICODE) {
|
||||
$p->{'head_content'} .= '<meta http-equiv="Content-Type" content="text/html; charset='.$opts->{'saycharset'}."\" />\n";
|
||||
}
|
||||
|
||||
my $get = $opts->{'getargs'};
|
||||
|
||||
my $count = LJ::S2::get_journal_day_counts($p);
|
||||
my @years = sort { $a <=> $b } keys %$count;
|
||||
my $maxyear = @years ? $years[-1] : undef;
|
||||
my $year = $get->{'year'}; # old form was /users/<user>/calendar?year=1999
|
||||
|
||||
# but the new form is purtier: */calendar/2001
|
||||
if (! $year && $opts->{'pathextra'} =~ m!^/(\d\d\d\d)/?\b!) {
|
||||
$year = $1;
|
||||
}
|
||||
|
||||
# else... default to the year they last posted.
|
||||
$year ||= $maxyear;
|
||||
|
||||
$p->{'year'} = $year;
|
||||
$p->{'years'} = [];
|
||||
foreach (@years) {
|
||||
push @{$p->{'years'}}, YearYear($_, "$p->{'base_url'}/$_/", $_ == $p->{'year'});
|
||||
}
|
||||
|
||||
$p->{'months'} = [];
|
||||
|
||||
for my $month (1..12) {
|
||||
push @{$p->{'months'}}, YearMonth($p, {
|
||||
'month' => $month,
|
||||
'year' => $year,
|
||||
});
|
||||
}
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
sub YearMonth {
|
||||
my ($p, $calmon) = @_;
|
||||
|
||||
my ($month, $year) = ($calmon->{'month'}, $calmon->{'year'});
|
||||
$calmon->{'_type'} = 'YearMonth';
|
||||
$calmon->{'weeks'} = [];
|
||||
$calmon->{'url'} = sprintf("$p->{'_u'}->{'_journalbase'}/$year/%02d/", $month);
|
||||
|
||||
my $count = LJ::S2::get_journal_day_counts($p);
|
||||
my $has_entries = $count->{$year} && $count->{$year}->{$month} ? 1 : 0;
|
||||
$calmon->{'has_entries'} = $has_entries;
|
||||
|
||||
my $start_monday = 0; # FIXME: check some property to see if weeks start on monday
|
||||
my $week = undef;
|
||||
|
||||
my $flush_week = sub {
|
||||
my $end_month = shift;
|
||||
return unless $week;
|
||||
push @{$calmon->{'weeks'}}, $week;
|
||||
if ($end_month) {
|
||||
$week->{'post_empty'} =
|
||||
7 - $week->{'pre_empty'} - @{$week->{'days'}};
|
||||
}
|
||||
$week = undef;
|
||||
};
|
||||
|
||||
my $push_day = sub {
|
||||
my $d = shift;
|
||||
unless ($week) {
|
||||
my $leading = $d->{'date'}->{'_dayofweek'}-1;
|
||||
if ($start_monday) {
|
||||
$leading = 6 if --$leading < 0;
|
||||
}
|
||||
$week = {
|
||||
'_type' => 'YearWeek',
|
||||
'days' => [],
|
||||
'pre_empty' => $leading,
|
||||
'post_empty' => 0,
|
||||
};
|
||||
}
|
||||
push @{$week->{'days'}}, $d;
|
||||
if ($week->{'pre_empty'} + @{$week->{'days'}} == 7) {
|
||||
$flush_week->();
|
||||
my $size = scalar @{$calmon->{'weeks'}};
|
||||
}
|
||||
};
|
||||
|
||||
my $day_of_week = LJ::day_of_week($year, $month, 1);
|
||||
|
||||
my $daysinmonth = LJ::days_in_month($month, $year);
|
||||
|
||||
for my $day (1..$daysinmonth) {
|
||||
# so we don't auto-vivify years/months
|
||||
my $daycount = $has_entries ? $count->{$year}->{$month}->{$day} : 0;
|
||||
my $d = YearDay($p->{'_u'}, $year, $month, $day,
|
||||
$daycount, $day_of_week+1);
|
||||
$push_day->($d);
|
||||
$day_of_week = ($day_of_week + 1) % 7;
|
||||
}
|
||||
$flush_week->(1); # end of month flag
|
||||
|
||||
my $nowval = $year * 12 + $month;
|
||||
|
||||
# determine the most recent month with posts that is older than
|
||||
# the current time $month/$year. gives calendars the ability to
|
||||
# provide smart next/previous links.
|
||||
my $maxbefore;
|
||||
while (my ($iy, $h) = each %$count) {
|
||||
next if $iy > $year;
|
||||
while (my $im = each %$h) {
|
||||
next if $im >= $month;
|
||||
my $val = $iy * 12 + $im;
|
||||
if ($val < $nowval && $val > $maxbefore) {
|
||||
$maxbefore = $val;
|
||||
$calmon->{'prev_url'} = $p->{'_u'}->{'_journalbase'} . sprintf("/%04d/%02d/", $iy, $im);
|
||||
$calmon->{'prev_date'} = Date($iy, $im, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# same, except inverse: next month after current time with posts
|
||||
my $minafter;
|
||||
while (my ($iy, $h) = each %$count) {
|
||||
next if $iy < $year;
|
||||
while (my $im = each %$h) {
|
||||
next if $im <= $month;
|
||||
my $val = $iy * 12 + $im;
|
||||
if ($val > $nowval && (!$minafter || $val < $minafter)) {
|
||||
$minafter = $val;
|
||||
$calmon->{'next_url'} = $p->{'_u'}->{'_journalbase'} . sprintf("/%04d/%02d/", $iy, $im);
|
||||
$calmon->{'next_date'} = Date($iy, $im, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $calmon;
|
||||
}
|
||||
|
||||
sub YearYear {
|
||||
my ($year, $url, $displayed) = @_;
|
||||
return { '_type' => "YearYear",
|
||||
'year' => $year, 'url' => $url, 'displayed' => $displayed };
|
||||
}
|
||||
|
||||
sub YearDay {
|
||||
my ($u, $year, $month, $day, $count, $dow) = @_;
|
||||
my $d = {
|
||||
'_type' => 'YearDay',
|
||||
'day' => $day,
|
||||
'date' => Date($year, $month, $day, $dow),
|
||||
'num_entries' => $count
|
||||
};
|
||||
if ($count) {
|
||||
$d->{'url'} = sprintf("$u->{'_journalbase'}/$year/%02d/%02d/",
|
||||
$month, $day);
|
||||
}
|
||||
return $d;
|
||||
}
|
||||
|
||||
1;
|
||||
2164
local/cgi-bin/LJ/TextMessage.pm
Executable file
2164
local/cgi-bin/LJ/TextMessage.pm
Executable file
File diff suppressed because it is too large
Load Diff
3712
local/cgi-bin/LJ/User.pm
Executable file
3712
local/cgi-bin/LJ/User.pm
Executable file
File diff suppressed because it is too large
Load Diff
1458
local/cgi-bin/LJR/Distributed.pm
Executable file
1458
local/cgi-bin/LJR/Distributed.pm
Executable file
File diff suppressed because it is too large
Load Diff
51
local/cgi-bin/LJR/GD.pm
Normal file
51
local/cgi-bin/LJR/GD.pm
Normal file
@@ -0,0 +1,51 @@
|
||||
use strict;
|
||||
use GD::Simple;
|
||||
|
||||
package LJR::GD;
|
||||
|
||||
sub generate_number {
|
||||
my ($num, $fontname, $fontcolor, $stuff) = @_;
|
||||
|
||||
$num =~ s/^(\ +)//g;
|
||||
$num =~ s/(\ +)$//g;
|
||||
|
||||
my $font;
|
||||
if ($fontname eq "gdTinyFont") {
|
||||
$font = GD::Font->Tiny();
|
||||
}
|
||||
elsif ($fontname eq "gdSmallFont") {
|
||||
$font = GD::Font->Small();
|
||||
}
|
||||
elsif ($fontname eq "gdLargeFont") {
|
||||
$font = GD::Font->Large();
|
||||
}
|
||||
elsif ($fontname eq "gdMediumBoldFont") {
|
||||
$font = GD::Font->MediumBold();
|
||||
}
|
||||
elsif ($fontname eq "gdGiantFont") {
|
||||
$font = GD::Font->Giant();
|
||||
}
|
||||
else {
|
||||
$font = GD::Font->Small();
|
||||
}
|
||||
|
||||
my $cell_width = $font->width;
|
||||
my $cell_height = $font->height;
|
||||
my $cols = length($stuff) > length($num) ? length($stuff) : length($num);
|
||||
my $width = int($cols * $cell_width + $cell_width / 3);
|
||||
my $height = $cell_height + 1;
|
||||
my $img = GD::Simple->new($width,$height);
|
||||
$img->font($font);
|
||||
$img->moveTo(1,$font->height + 1);
|
||||
$img->transparent("white");
|
||||
$img->bgcolor("white");
|
||||
$img->fgcolor($fontcolor);
|
||||
|
||||
my $str = (length($num) < length($stuff) ?
|
||||
substr($stuff, 0, length($stuff) - length($num)) :
|
||||
"") . $num;
|
||||
$img->string($str);
|
||||
return $img;
|
||||
}
|
||||
|
||||
return 1;
|
||||
234
local/cgi-bin/LJR/Gate.pm
Normal file
234
local/cgi-bin/LJR/Gate.pm
Normal file
@@ -0,0 +1,234 @@
|
||||
use strict;
|
||||
|
||||
use XMLRPC::Lite;
|
||||
use Digest::MD5;
|
||||
use Time::Local;
|
||||
|
||||
use LJR::Distributed;
|
||||
use LJR::xmlrpc;
|
||||
use LJR::Viewuserstandalone;
|
||||
|
||||
require "$ENV{'LJHOME'}/cgi-bin/ljpoll.pl";
|
||||
|
||||
package LJR::Gate;
|
||||
|
||||
$LJR::Gate::clientver = 'LJR::Gate/0.02';
|
||||
|
||||
sub Authenticate {
|
||||
my ($server, $user, $pass) = @_;
|
||||
|
||||
my $xmlrpc = new XMLRPC::Lite;
|
||||
$xmlrpc->proxy("http://" . $server . "/interface/xmlrpc", timeout => 60);
|
||||
|
||||
my $xmlrpc_ret = LJR::xmlrpc::xmlrpc_call($xmlrpc, "LJ.XMLRPC.getchallenge");
|
||||
return $xmlrpc_ret if $xmlrpc_ret->{"err_text"};
|
||||
|
||||
my $challenge = $xmlrpc_ret->{'result'}->{'challenge'};
|
||||
my $response = Digest::MD5::md5_hex($challenge . Digest::MD5::md5_hex($pass));
|
||||
|
||||
my $xmlrpc_req = {
|
||||
'username' => $user,
|
||||
'auth_method' => 'challenge',
|
||||
'auth_challenge' => $challenge,
|
||||
'auth_response' => $response,
|
||||
'ver' => 1,
|
||||
'clientver' => $LJR::Gate::clientver,
|
||||
};
|
||||
|
||||
$xmlrpc_ret = LJR::xmlrpc::xmlrpc_call($xmlrpc, "LJ.XMLRPC.login", $xmlrpc_req);
|
||||
return $xmlrpc_ret if $xmlrpc_ret->{"err_text"};
|
||||
|
||||
return $xmlrpc;
|
||||
}
|
||||
|
||||
sub ExportEntry {
|
||||
my ($u, $req, $security, $jitemid, $anum) = @_;
|
||||
|
||||
return "User [" . $u->{'user'} . "] is not gated." unless LJR::Distributed::is_gated_local($u->{'user'});
|
||||
|
||||
my $dbr = LJ::get_db_reader();
|
||||
return "Can't get database reader!" unless $dbr;
|
||||
|
||||
my $r;
|
||||
$r = $dbr->selectrow_hashref (
|
||||
"SELECT * FROM ljr_export_settings WHERE user=?",
|
||||
undef, $u->{'user'});
|
||||
|
||||
my $ru;
|
||||
$ru = LJR::Distributed::get_cached_user({ 'ru_id' => $r->{'ru_id'}});
|
||||
$ru = LJR::Distributed::get_remote_server_byid($ru);
|
||||
|
||||
my $xmlrpc = new XMLRPC::Lite;
|
||||
$xmlrpc->proxy($ru->{'servername'} . "/interface/xmlrpc", timeout => 60);
|
||||
|
||||
my $xmlrpc_ret;
|
||||
my $xmlrpc_req;
|
||||
my $challenge;
|
||||
my $response;
|
||||
|
||||
my $real_event;
|
||||
my $real_subject;
|
||||
|
||||
my $last_status;
|
||||
|
||||
if ($req->{'event'} !~ /\S/) {
|
||||
$last_status = "removed entry.";
|
||||
|
||||
$real_event = $req->{'event'};
|
||||
$real_subject = $req->{'subject'};
|
||||
}
|
||||
else {
|
||||
my $item_url = LJ::item_link($u, $jitemid, $anum);
|
||||
$last_status = "exported <a href=$item_url>entry</a>";
|
||||
|
||||
$real_event = LJR::Viewuserstandalone::expand_ljuser_tags($req->{'event'});
|
||||
$real_subject = LJR::Viewuserstandalone::expand_ljuser_tags($req->{'subject'});
|
||||
|
||||
my $i=0;
|
||||
while ($real_event =~ /lj-cut/ig) { $i++ };
|
||||
while ($real_event =~ /\/lj-cut/ig) { $i-- };
|
||||
if ($i gt 0) {
|
||||
$real_event .= "</lj-cut>";
|
||||
}
|
||||
LJ::Poll::replace_polls_with_links(\$real_event);
|
||||
LJ::EmbedModule->expand_entry($u, \$real_event, ('content_only' => 1));
|
||||
|
||||
unless ($req->{'props'}->{'opt_nocomments'}) {
|
||||
LJR::Distributed::sign_exported_gate_entry($u, $jitemid, $anum, \$real_event);
|
||||
}
|
||||
}
|
||||
|
||||
$security = $req->{'sequrity'} if !$security && $req->{'security'};
|
||||
$security = "public" unless $security;
|
||||
|
||||
$xmlrpc_req = {
|
||||
'username' => $ru->{'username'},
|
||||
'auth_method' => 'challenge',
|
||||
'ver' => 1,
|
||||
'clientver' => $LJR::Gate::clientver,
|
||||
'subject' => $real_subject,
|
||||
'event' => $real_event,
|
||||
'year' => $req->{'year'},
|
||||
'mon' => $req->{'mon'},
|
||||
'day' => $req->{'day'},
|
||||
'hour' => $req->{'hour'},
|
||||
'min' => $req->{'min'},
|
||||
'security' => $security,
|
||||
'allowmask' => $req->{'allowmask'},
|
||||
'props' => {
|
||||
'current_moodid' => $req->{'props'}->{'current_moodid'},
|
||||
'current_mood' => $req->{'props'}->{'current_mood'},
|
||||
'current_music' => $req->{'props'}->{'current_music'},
|
||||
'picture_keyword' => $req->{'props'}->{'picture_keyword'},
|
||||
'taglist' => $req->{'props'}->{'taglist'},
|
||||
'opt_backdated' => $req->{'props'}->{'opt_backdated'},
|
||||
'opt_preformatted' => $req->{'props'}->{'opt_preformatted'},
|
||||
'opt_nocomments' => 1,
|
||||
},
|
||||
};
|
||||
|
||||
my $is_invalid_remote_journal = sub {
|
||||
my ($error_message) = @_;
|
||||
if (
|
||||
$error_message =~ /Invalid password/ ||
|
||||
$error_message =~ /Selected journal no longer exists/ ||
|
||||
$error_message =~ /account is suspended/ ||
|
||||
$error_message =~ /Invalid username/
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
my $is_invalid_remote_entry = sub {
|
||||
my ($error_message) = @_;
|
||||
if ($error_message =~ /Can\'t edit post from requested journal/) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
my $post_new_event = sub {
|
||||
$xmlrpc_ret = LJR::xmlrpc::xmlrpc_call($xmlrpc, "LJ.XMLRPC.getchallenge");
|
||||
return $xmlrpc_ret->{"err_text"} if $xmlrpc_ret->{"err_text"};
|
||||
$challenge = $xmlrpc_ret->{'result'}->{'challenge'};
|
||||
$response = Digest::MD5::md5_hex($challenge . Digest::MD5::md5_hex($r->{'remote_password'}));
|
||||
$xmlrpc_req->{'auth_challenge'} = $challenge;
|
||||
$xmlrpc_req->{'auth_response'} = $response;
|
||||
|
||||
my $item_time = Time::Local::timelocal(0, $req->{'min'}, $req->{'hour'},
|
||||
$req->{'day'}, $req->{'mon'} - 1, $req->{'year'});
|
||||
|
||||
if ((time - $item_time) > 60*60*24) {
|
||||
$xmlrpc_req->{'props'}->{'opt_backdated'} = 1;
|
||||
}
|
||||
|
||||
$xmlrpc_ret = LJR::xmlrpc::xmlrpc_call($xmlrpc, "LJ.XMLRPC.postevent", $xmlrpc_req);
|
||||
if ($xmlrpc_ret->{'err_text'}) {
|
||||
if ($is_invalid_remote_journal->($xmlrpc_ret->{'err_text'})) {
|
||||
$r = LJR::Distributed::update_export_status($u->{'user'}, 0, "ERROR: " . $xmlrpc_ret->{'err_text'});
|
||||
}
|
||||
else {
|
||||
$r = LJR::Distributed::update_export_status($u->{'user'}, 1, "ERROR: " . $xmlrpc_ret->{'err_text'});
|
||||
}
|
||||
return $xmlrpc_ret->{"err_text"} . " " . ($r->{'err'} ? $r->{'errtext'} : "");
|
||||
}
|
||||
|
||||
my $rhtml_id = $xmlrpc_ret->{'result'}->{'itemid'} * 256 +
|
||||
$xmlrpc_ret->{'result'}->{'anum'};
|
||||
|
||||
$r = LJR::Distributed::store_remote_itemid(
|
||||
$u,
|
||||
$jitemid,
|
||||
$ru->{'ru_id'},
|
||||
$xmlrpc_ret->{'result'}->{'itemid'},
|
||||
$rhtml_id,
|
||||
"E"
|
||||
);
|
||||
return
|
||||
"store_remote_itemid: " . $u->{'user'} . "," .
|
||||
$jitemid . "," . $ru->{'ru_id'} . "," .
|
||||
$xmlrpc_ret->{'result'}->{'itemid'} . "," . $rhtml_id . ": " .
|
||||
$r->{"errtext"} if $r->{"err"};
|
||||
};
|
||||
|
||||
my $ritem = LJR::Distributed::get_remote_itemid($u->{'userid'}, $jitemid, "E");
|
||||
if ($ritem && ($req->{'props'}->{'revnum'} || $req->{'event'} !~ /\S/)) {
|
||||
$xmlrpc_ret = LJR::xmlrpc::xmlrpc_call($xmlrpc, "LJ.XMLRPC.getchallenge");
|
||||
return $xmlrpc_ret->{"err_text"} if $xmlrpc_ret->{"err_text"};
|
||||
$challenge = $xmlrpc_ret->{'result'}->{'challenge'};
|
||||
$response = Digest::MD5::md5_hex($challenge . Digest::MD5::md5_hex($r->{'remote_password'}));
|
||||
$xmlrpc_req->{'auth_challenge'} = $challenge;
|
||||
$xmlrpc_req->{'auth_response'} = $response;
|
||||
|
||||
$xmlrpc_req->{'itemid'} = $ritem->{'ritemid'};
|
||||
|
||||
$xmlrpc_ret = LJR::xmlrpc::xmlrpc_call($xmlrpc, "LJ.XMLRPC.editevent", $xmlrpc_req);
|
||||
if ($xmlrpc_ret->{'err_text'}) {
|
||||
if ($is_invalid_remote_entry->($xmlrpc_ret->{'err_text'})) {
|
||||
LJR::Distributed::remove_remote_itemid($u, $jitemid, $ru->{'ru_id'}, $ritem->{'ritemid'}, "E");
|
||||
my $errmsg = $post_new_event->();
|
||||
return $errmsg if $errmsg;
|
||||
}
|
||||
elsif ($is_invalid_remote_journal->($xmlrpc_ret->{'err_text'})) {
|
||||
$r = LJR::Distributed::update_export_status($u->{'user'}, 0, "ERROR: " . $xmlrpc_ret->{'err_text'});
|
||||
return $xmlrpc_ret->{"err_text"} . " " . ($r->{'err'} ? $r->{'errtext'} : "");
|
||||
}
|
||||
$r = LJR::Distributed::update_export_status($u->{'user'}, 1, "ERROR: " . $xmlrpc_ret->{'err_text'});
|
||||
return $xmlrpc_ret->{"err_text"} . " " . ($r->{'err'} ? $r->{'errtext'} : "");
|
||||
}
|
||||
if ($req->{'event'} !~ /\S/) {
|
||||
LJR::Distributed::remove_remote_itemid($u, $jitemid, $ru->{'ru_id'}, $ritem->{'ritemid'}, "E");
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
my $errmsg = $post_new_event->();
|
||||
return $errmsg if $errmsg;
|
||||
}
|
||||
|
||||
$r = LJR::Distributed::update_export_status($u->{'user'}, 1, "OK: $last_status");
|
||||
return $r->{'errtext'} if $r->{'err'};
|
||||
|
||||
return;
|
||||
}
|
||||
241
local/cgi-bin/LJR/Viewuser.pm
Executable file
241
local/cgi-bin/LJR/Viewuser.pm
Executable file
@@ -0,0 +1,241 @@
|
||||
package LJR::Viewuser;
|
||||
|
||||
use strict;
|
||||
use Carp;
|
||||
use lib "$ENV{'LJHOME'}/cgi-bin";
|
||||
use DBI;
|
||||
use DBI::Role;
|
||||
use DBIx::StateKeeper;
|
||||
|
||||
# A function to canonicalize sitename: take one of the possible
|
||||
# abbreviations for a given known site, and returns the siteid
|
||||
# from the list. Otherwise, assume that abbreivation is actually the
|
||||
# full URL, and return it "as is", without the possible leading http://.
|
||||
#
|
||||
# We check the known servers database for "site=servername" or "site
|
||||
# contains serverurl without the leading www"; make additional
|
||||
# explicit matchings if necessary (presently none are necessary), et
|
||||
# voila.
|
||||
#
|
||||
|
||||
sub canonical_sitenum {
|
||||
my ($site)=@_;
|
||||
|
||||
# Cut away leading http://
|
||||
$site =~ s|http://(.*)|$1|;
|
||||
|
||||
my $dbh = LJ::get_db_reader();
|
||||
my $sth = $dbh->prepare(
|
||||
"SELECT serverid FROM alienservers WHERE servername=?"
|
||||
);
|
||||
|
||||
$sth->execute($site);
|
||||
return LJ::error($dbh) if $dbh->err;
|
||||
#
|
||||
# Match $site=servername (e.g. "LJ")
|
||||
#
|
||||
if ($sth->rows) {
|
||||
my ($guu) = $sth->fetchrow_array;
|
||||
return $guu;
|
||||
}
|
||||
$sth->finish;
|
||||
|
||||
$sth = $dbh->prepare(
|
||||
"SELECT serverid, REPLACE(serverurl, 'www.', '') FROM alienservers"
|
||||
);
|
||||
$sth->execute;
|
||||
return LJ::error($dbh) if $dbh->err;
|
||||
#
|
||||
# Scan all known servers and match "serverurl without www is
|
||||
# contained in $site"
|
||||
#
|
||||
while (my ($hale, $guu) = $sth->fetchrow_array) {
|
||||
if (index ($site, $guu) !=-1) {
|
||||
return $hale;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (lc($site) eq "ljr") || ($site =~ m/.*${LJ::DOMAIN}.*/) )
|
||||
#
|
||||
# 0 means ourselves
|
||||
#
|
||||
{return 0;}
|
||||
# elsif ( ($site eq "LJ") || ($site =~ m/.*livejournal\.com.*/) )
|
||||
# {return 1;}
|
||||
# elsif ( ($site eq "GJ") || ($site =~ m/.*greatestjournal\.com.*/) )
|
||||
# {return 2;}
|
||||
# elsif ( ($site eq "NPJ") || ($site =~ m/.*npj\.ru.*/) )
|
||||
# {return 3;}
|
||||
else {return $site};
|
||||
|
||||
}
|
||||
|
||||
#
|
||||
# Provides a representation of a user.
|
||||
#
|
||||
# Format: we receive a username and a site, where site is either a
|
||||
# number or a string. If a non-zero number, this is a known site; we take
|
||||
# information about it from the alianservers table in the db. If
|
||||
# zero, site is ourselves. If a string, we do not know anything about
|
||||
# the site and treat it as an OpenID guest; we assume site is the URL.
|
||||
#
|
||||
# We return the HTML code.
|
||||
#
|
||||
# <lj user="username" site="sitename"> should be expand to
|
||||
# ljuser( $username, {'site'=> canonical_sitenum($sitename),
|
||||
# 'type'=>'P'} )
|
||||
#
|
||||
# For lj comm, replace 'P' with 'C'
|
||||
#
|
||||
|
||||
sub ljuser {
|
||||
# we assume $opts->{'site'} to be a siteid of a known site or a full
|
||||
# URL of a site we do not have in db
|
||||
my $user = shift;
|
||||
my $opts = shift;
|
||||
my $u;
|
||||
my $native=0;
|
||||
my $known=0;
|
||||
|
||||
my $name="";
|
||||
my $url;
|
||||
my $uicon;
|
||||
my $cicon;
|
||||
my $commdir;
|
||||
my $udir;
|
||||
my $lj_type;
|
||||
|
||||
# If site is not given, assume native (siteid=0)
|
||||
unless ($opts->{'site'}) {$opts->{'site'}=0;}
|
||||
|
||||
# Check if site is a number
|
||||
if($opts->{'site'} =~ m/(\d+)/)
|
||||
{ $known=1; }
|
||||
|
||||
if($known) {
|
||||
|
||||
# Site a number (known site)
|
||||
|
||||
$opts->{'site'} = $opts->{'site'}+0;
|
||||
|
||||
# now we've got default - $LJ::DOMAIN
|
||||
|
||||
if ($opts->{'site'}==0){
|
||||
|
||||
# local
|
||||
$url=$LJ::DOMAIN;
|
||||
$cicon='community.gif'; # default local commicon
|
||||
$uicon='userinfo.gif'; # default local usericon
|
||||
$commdir='community/';
|
||||
$udir='users/';
|
||||
$lj_type='Y';
|
||||
|
||||
$native=1;
|
||||
} else {
|
||||
|
||||
# alien but known --
|
||||
# go to db to get $name
|
||||
|
||||
my $dbh = LJ::get_db_writer();
|
||||
my $sth = $dbh->prepare("SELECT serverurl, servername, udir, uicon, cdir, cicon, ljtype FROM alienservers WHERE serverid=?");
|
||||
$sth->execute($opts->{'site'});
|
||||
($url, $name, $udir, $uicon, $commdir, $cicon, $lj_type) = $sth->fetchrow_array;
|
||||
$native=0;
|
||||
}
|
||||
} else {
|
||||
|
||||
# site is not a number -- unknown alien site
|
||||
|
||||
$name=$opts->{'site'};
|
||||
$url=$opts->{'site'};
|
||||
$uicon='openid-profile.gif'; # default unknown alien usericon
|
||||
$cicon='openid-profile.gif'; # default unknown alien commicon
|
||||
$commdir='';
|
||||
$udir='';
|
||||
$lj_type='N';
|
||||
$native=0;
|
||||
|
||||
}
|
||||
|
||||
if ($native){
|
||||
|
||||
# If the user is local, we do some processing: check validity, check
|
||||
# whether user or community, etc.
|
||||
|
||||
# my $do_dynamic = $LJ::DYNAMIC_LJUSER || ($user =~ /^ext_/);
|
||||
# if ($do_dynamic && ! isu($user) && ! $opts->{'type'}) {
|
||||
# Try to automatically pick the user type, but still
|
||||
# make something if we can't (user doesn't exist?)
|
||||
$user = LJ::load_user($user) || $user;
|
||||
|
||||
my $hops = 0;
|
||||
|
||||
# Traverse the renames to the final journal
|
||||
while (ref $user and $user->{'journaltype'} eq 'R'
|
||||
and ! $opts->{'no_follow'} && $hops++ < 5) {
|
||||
|
||||
LJ::load_user_props($user, 'renamedto');
|
||||
last unless length $user->{'renamedto'};
|
||||
$user = LJ::load_user($user->{'renamedto'});
|
||||
|
||||
}
|
||||
# }
|
||||
|
||||
if (LJ::isu($user)) {
|
||||
$u = $user;
|
||||
$opts->{'type'} = $user->{'journaltype'};
|
||||
# Mark accounts as deleted that aren't visible, memorial, or locked
|
||||
$opts->{'del'} = $user->{'statusvis'} ne 'V' &&
|
||||
$user->{'statusvis'} ne 'M' &&
|
||||
$user->{'statusvis'} ne 'L';
|
||||
$user = $user->{'user'};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# End of local-specific part
|
||||
|
||||
my $andfull = $opts->{'full'} ? "&mode=full" : "";
|
||||
my $img = $opts->{'imgroot'} || $LJ::IMGPREFIX;
|
||||
my $strike = $opts->{'del'} ? ' text-decoration: line-through;' : '';
|
||||
my $make_tag = sub {
|
||||
my ($s, $n, $fil, $dir) = @_;
|
||||
$n = lc ($n);
|
||||
|
||||
if ($n eq ""){
|
||||
return "<span class='ljruser' style='white-space: nowrap;$strike'><a href='http://$s/userinfo.bml?user=$user$andfull'><img src='$img/$fil' alt='[info]' style='vertical-align: bottom; border: 0;' /></a><a href='http://$s/$dir$user/'><b>$user</b></a></span>";
|
||||
} else {
|
||||
if ($lj_type eq 'Y') {
|
||||
|
||||
# If the site is known and has an lj-type engine, then we now how to
|
||||
# refer to userinfo; make the info icon link to this
|
||||
|
||||
return "<span class='ljruser' style='white-space: nowrap;$strike'><a href='http://$s/userinfo.bml?user=$user$andfull'><img src='$img/$fil' alt='[info]' style='vertical-align: bottom; border: 0;' /></a><a href='http://$s/$dir$user/'><b>$user\@$n</b></a></span>";
|
||||
} elsif ($known) {
|
||||
|
||||
# If not lj-type, but known, let the info icon link to the user journal
|
||||
|
||||
return "<span class='ljruser' style='white-space: nowrap;$strike'><a href='http://$s/$dir$user/'><img src='$img/$fil' alt='[info]' style='vertical-align: bottom; border: 0;' /></a><a href='http://$s/$dir$user/'><b>$user\@$n</b></a></span>";
|
||||
} else {
|
||||
|
||||
# Unknown site. Treat as openid
|
||||
|
||||
return "<span class='ljruser' style='white-space: nowrap;$strike'><a href='http://$s/$dir$user/'><img src='$img/$fil' alt='[info]' style='vertical-align: bottom; border: 0;' /></a><a href='http://$s/$dir$user/'><b>$user</b> [$n]</a></span>";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ($opts->{'type'} eq 'C') {
|
||||
return $make_tag->( $url, $name, $cicon, $commdir);
|
||||
} elsif ($opts->{'type'} eq 'Y') {
|
||||
return $make_tag->( $url, $name, 'syndicated.gif', 'users/');
|
||||
} elsif ($opts->{'type'} eq 'N') {
|
||||
return $make_tag->( $url, $name, 'newsinfo.gif', 'users/');
|
||||
} elsif ($opts->{'type'} eq 'I') {
|
||||
return $u->ljuser_display($opts);
|
||||
} else {
|
||||
return $make_tag->( $url, $name, $uicon, $udir);
|
||||
}
|
||||
};
|
||||
|
||||
1;
|
||||
210
local/cgi-bin/LJR/Viewuserstandalone.pm
Executable file
210
local/cgi-bin/LJR/Viewuserstandalone.pm
Executable file
@@ -0,0 +1,210 @@
|
||||
package LJR::Viewuserstandalone;
|
||||
|
||||
use strict;
|
||||
|
||||
# A function to canonicalize sitename: take one of the possible
|
||||
# abbreviations for a given known site, and returns the siteid
|
||||
# from the list. Otherwise, assume that abbreivation is actually the
|
||||
# full URL, and return it "as is", without the possible leading http://.
|
||||
# Right now we work case-by-case, since the number of known
|
||||
# abbreviations is small.
|
||||
#
|
||||
#
|
||||
# Known sites:
|
||||
#
|
||||
# 0 -- local
|
||||
# 1 -- www.livejournal.com
|
||||
# 2 -- greatestjournal.com
|
||||
# 3 -- npj.ru
|
||||
# 4 -- dreamwidth.org
|
||||
# TODO: add third level domains
|
||||
|
||||
sub canonical_sitenum {
|
||||
my ($site)=@_;
|
||||
|
||||
if ( ($site eq "LJR") || ($site =~ m/.*lj\.rossia\.org.*/) )
|
||||
{return 0;}
|
||||
elsif ( ($site eq "LJ") || ($site =~ m/.*livejournal\.com.*/) )
|
||||
{return 1;}
|
||||
elsif ( ($site eq "GJ") || ($site =~ m/.*greatestjournal\.com.*/) )
|
||||
{return 2;}
|
||||
elsif ( ($site eq "NPJ") || ($site =~ m/.*npj\.ru.*/) )
|
||||
{return 3;}
|
||||
elsif ( ($site eq "DW") || ($site eq "dw") || ($site =~ m/.*dreamwidth\.org.*/) )
|
||||
{return 4;}
|
||||
else {return $site;}
|
||||
}
|
||||
|
||||
#
|
||||
# Provides a representation of a user.
|
||||
#
|
||||
# Format: we receive a username and a site, where site is either a
|
||||
# number or a string. If a non-zero number, this is a known site; we take
|
||||
# information about it from the alianservers table in the db. If
|
||||
# zero, site is ourselves. If a string, we do not know anything about
|
||||
# the site and treat it as an OpenID guest; we assume site is the URL.
|
||||
#
|
||||
# We return the HTML code.
|
||||
#
|
||||
# <lj user="username" site="sitename"> should be expand to
|
||||
# ljuser( $username, {'site'=> canonical_sitenum($sitename),
|
||||
# 'type'=>'P','imgroot'=>''} )
|
||||
#
|
||||
# For lj comm, replace 'P' with 'C'; 'imgroot' should be equal to the
|
||||
# current value of $LJ::IMGPREFIX -- right now it is differs
|
||||
# between test and production!!
|
||||
#
|
||||
|
||||
sub ljuser {
|
||||
# we assume $opts->{'site'} to be a siteid of a known site or a full
|
||||
# URL of a site we do not have in db
|
||||
my $user = shift;
|
||||
my $opts = shift;
|
||||
my $u;
|
||||
|
||||
my $name="";
|
||||
my $url;
|
||||
my $uicon;
|
||||
my $cicon;
|
||||
my $commdir;
|
||||
my $udir;
|
||||
my $lj_type;
|
||||
|
||||
# If site is not given, assume native (siteid=0)
|
||||
unless ($opts->{'site'}) {$opts->{'site'}=0;}
|
||||
|
||||
# Check if site is a number
|
||||
if($opts->{'site'} =~ m/(\d+)/) {
|
||||
|
||||
# Site a number (known site)
|
||||
|
||||
$opts->{'site'} = $opts->{'site'}+0;
|
||||
|
||||
# now we've got default - $LJ::DOMAIN
|
||||
|
||||
if ($opts->{'site'}==0){
|
||||
|
||||
# local
|
||||
$url='lj.rossia.org';
|
||||
$cicon='community.gif'; # default local commicon
|
||||
$uicon='userinfo.gif'; # default local usericon
|
||||
$commdir='community/';
|
||||
$udir='users/';
|
||||
$lj_type='Y';
|
||||
} elsif ($opts->{'site'}==1) {
|
||||
# LJ
|
||||
$name="LJ";
|
||||
$url='www.livejournal.com';
|
||||
$cicon='community-lj.gif';
|
||||
$uicon='userinfo-lj.gif';
|
||||
$commdir='community/';
|
||||
$udir='users/';
|
||||
$lj_type='Y';
|
||||
} elsif ($opts->{'site'}==2) {
|
||||
# GJ
|
||||
$name="GJ";
|
||||
$url='www.greatestjournal.com';
|
||||
$cicon='community-lj.gif';
|
||||
$uicon='userinfo-lj.gif';
|
||||
$commdir='community/';
|
||||
$udir='users/';
|
||||
$lj_type='Y';
|
||||
} elsif ($opts->{'site'}==3) {
|
||||
# LJ
|
||||
$name="NPJ";
|
||||
$url='www.npj.ru';
|
||||
$cicon='community-npj.gif';
|
||||
$uicon='userinfo-npj.gif';
|
||||
$commdir='';
|
||||
$udir='';
|
||||
$lj_type='N';
|
||||
} elsif ($opts->{'site'}==4) {
|
||||
# DW
|
||||
$name="DW";
|
||||
$url='www.dreamwidth.org';
|
||||
$cicon='community-dw.gif';
|
||||
$uicon='userinfo-dw.gif';
|
||||
$commdir='community/';
|
||||
$udir='users/';
|
||||
$lj_type='Y';
|
||||
} else { return "[Unknown LJ user tag]"; }
|
||||
|
||||
} else {
|
||||
|
||||
# site is not a number -- unknown alien site
|
||||
|
||||
$name=$opts->{'site'};
|
||||
$url=$opts->{'site'};
|
||||
$uicon=''; # default unknown alien usericon
|
||||
$cicon=''; # default unknown alien commicon
|
||||
$commdir='community';
|
||||
$udir='users';
|
||||
$lj_type='N';
|
||||
}
|
||||
|
||||
|
||||
my $andfull = $opts->{'full'} ? "&mode=full" : "";
|
||||
|
||||
my $img = $opts->{'imgroot'};
|
||||
my $make_tag = sub {
|
||||
my ($s, $n, $fil, $dir) = @_;
|
||||
if ($n eq ""){
|
||||
return "<span class='ljruser' style='white-space: nowrap;'><a href='http://$s/userinfo.bml?user=$user$andfull'><img src='$img/$fil' alt='[info]' style='vertical-align: bottom; border: 0;' /></a><a href='http://$s/$dir$user/'><b>$user</b></a></span>";
|
||||
} else {
|
||||
if ($lj_type eq 'Y') {
|
||||
|
||||
# If the site is known and has an lj-type engine, then we now how to
|
||||
# refer to userinfo; make the info icon link to this
|
||||
|
||||
return "<span class='ljruser' style='white-space: nowrap;'><a href='http://$s/userinfo.bml?user=$user$andfull'><img src='$img/$fil' alt='[info]' style='vertical-align: bottom; border: 0;' /></a><a href='http://$s/$dir$user/'><b>$user</b> [$n]</a></span>";
|
||||
} else {
|
||||
|
||||
# If not lj-type, let the info icon link to the user journal
|
||||
|
||||
return "<span class='ljruser' style='white-space: nowrap;'><a href='http://$s/$dir$user/'><img src='$img/$fil' alt='[info]' style='vertical-align: bottom; border: 0;' /></a><a href='http://$s/$dir$user/'><b>$user</b> [$n]</a></span>";
|
||||
}
|
||||
}
|
||||
};
|
||||
if ($opts->{'type'} eq 'C') {
|
||||
return $make_tag->( $url, $name, $cicon, $commdir);
|
||||
} else {
|
||||
return $make_tag->( $url, $name, $uicon, $udir);
|
||||
}
|
||||
}
|
||||
|
||||
sub expand_ljuser_tags {
|
||||
|
||||
my ($string)=@_;
|
||||
|
||||
return "" unless $string;
|
||||
|
||||
my $imgroot='http://lj.rossia.org/img';
|
||||
|
||||
$string=~ s/<lj\s+user=\"?(\w+)\"?\s+site=\"?([^"]+)\"?\s*\/?>/
|
||||
ljuser($1,{
|
||||
'site'=>canonical_sitenum($2),
|
||||
'type'=>'P','imgroot'=>$imgroot,
|
||||
})
|
||||
/egxi;
|
||||
$string=~ s/<lj\s+comm=\"?(\w+)\"?\s+site=\"?([^"]+)\"?\s*\/?>/
|
||||
ljuser($1,{
|
||||
'site'=>canonical_sitenum($2),
|
||||
'type'=>'C','imgroot'=>$imgroot,
|
||||
})
|
||||
/egxi;
|
||||
$string=~ s/<ljr\s+user=\"?(\w+)\"?\s*\/?>/
|
||||
ljuser($1,{
|
||||
'site'=>0,
|
||||
'type'=>'P','imgroot'=>$imgroot,
|
||||
})
|
||||
/egxi;
|
||||
$string=~ s/<ljr\s+comm=\"?(\w+)\"?\s*\/?>/
|
||||
ljuser($1,{
|
||||
'site'=>0,
|
||||
'type'=>'C','imgroot'=>$imgroot,
|
||||
})
|
||||
/egxi;
|
||||
return $string;
|
||||
}
|
||||
|
||||
1;
|
||||
25
local/cgi-bin/LJR/ljpoll-local.pl
Normal file
25
local/cgi-bin/LJR/ljpoll-local.pl
Normal file
@@ -0,0 +1,25 @@
|
||||
package LJ::Poll;
|
||||
|
||||
use strict;
|
||||
|
||||
sub replace_polls_with_links {
|
||||
my ($event) = @_;
|
||||
|
||||
my $dbr = LJ::get_db_reader();
|
||||
|
||||
while ($$event =~ /<lj-poll-(\d+)>/g) {
|
||||
my $pollid = $1;
|
||||
my $name = $dbr->selectrow_array("SELECT name FROM poll WHERE pollid=?",
|
||||
undef, $pollid);
|
||||
|
||||
if ($name) {
|
||||
LJ::Poll::clean_poll(\$name);
|
||||
} else {
|
||||
$name = "#$pollid";
|
||||
}
|
||||
|
||||
$$event =~ s!<lj-poll-$pollid>!<div><a href="$LJ::SITEROOT/poll/?id=$pollid">View Poll: $name</a></div>!g;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
53
local/cgi-bin/LJR/unicode.pm
Normal file
53
local/cgi-bin/LJR/unicode.pm
Normal file
@@ -0,0 +1,53 @@
|
||||
use strict;
|
||||
|
||||
package LJR::unicode;
|
||||
|
||||
use XML::Parser;
|
||||
use Unicode::MapUTF8 qw(to_utf8 from_utf8 utf8_supported_charset);
|
||||
|
||||
|
||||
sub utf8ize {
|
||||
my $text_in = shift;
|
||||
$$text_in = pack("C*", unpack("C*", $$text_in)) if $$text_in;
|
||||
}
|
||||
|
||||
|
||||
sub force_utf8 {
|
||||
my $xdata = shift;
|
||||
my %error_lines;
|
||||
my $finished = 0;
|
||||
my @xlines;
|
||||
my $orig_xdata = $$xdata;
|
||||
|
||||
my $p1 = new XML::Parser ();
|
||||
|
||||
while (!$finished) {
|
||||
eval { $p1->parse($$xdata); };
|
||||
|
||||
if ($@ && $@ =~ /not\ well\-formed\ \(invalid\ token\)\ at\ line\ (\d+)\,/) {
|
||||
my $error_line = $1;
|
||||
$error_lines{$error_line} ++;
|
||||
|
||||
if ($error_lines{$error_line} > 1) {
|
||||
$$xdata = $orig_xdata;
|
||||
$finished = 1;
|
||||
}
|
||||
else {
|
||||
@xlines = split(/\n/, $$xdata);
|
||||
my $output = to_utf8({ -string => $xlines[$error_line - 1], -charset => 'latin1' });
|
||||
$xlines[$error_line - 1] = $output;
|
||||
$$xdata = join("\n", @xlines);
|
||||
}
|
||||
}
|
||||
# unknown error or no error, doesn't matter
|
||||
elsif ($@) {
|
||||
$$xdata = $orig_xdata;
|
||||
$finished = 1;
|
||||
}
|
||||
else {
|
||||
$finished = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
28
local/cgi-bin/LJR/xmlrpc.pm
Normal file
28
local/cgi-bin/LJR/xmlrpc.pm
Normal file
@@ -0,0 +1,28 @@
|
||||
use strict;
|
||||
|
||||
package LJR::xmlrpc;
|
||||
|
||||
sub xmlrpc_call {
|
||||
my ($xmlrpc, $method, $request) = @_;
|
||||
my $res;
|
||||
|
||||
if ($xmlrpc) {
|
||||
$res = $xmlrpc->call ($method, $request);
|
||||
|
||||
if ($res && $res->fault) {
|
||||
$res->{"err_text"} = $method . ": " . "XML-RPC Error [" . $res->faultcode . "]: " . $res->faultstring;
|
||||
}
|
||||
elsif (!$res) {
|
||||
$res->{"err_text"} = $method . ": " . "Unknown XML-RPC Error.";
|
||||
}
|
||||
|
||||
$res->{"result"} = $res->result;
|
||||
}
|
||||
else {
|
||||
$res->{"err_text"} = "Invalid xmlrpc object";
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return 1;
|
||||
585
local/cgi-bin/LWPx/ParanoidAgent.pm
Executable file
585
local/cgi-bin/LWPx/ParanoidAgent.pm
Executable file
@@ -0,0 +1,585 @@
|
||||
package LWPx::ParanoidAgent;
|
||||
require LWP::UserAgent;
|
||||
|
||||
use vars qw(@ISA $VERSION);
|
||||
@ISA = qw(LWP::UserAgent);
|
||||
$VERSION = '1.02';
|
||||
|
||||
require HTTP::Request;
|
||||
require HTTP::Response;
|
||||
|
||||
use HTTP::Status ();
|
||||
use strict;
|
||||
use Net::DNS;
|
||||
use LWP::Debug;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my %opts = @_;
|
||||
|
||||
my $blocked_hosts = delete $opts{blocked_hosts} || [];
|
||||
my $whitelisted_hosts = delete $opts{whitelisted_hosts} || [];
|
||||
my $resolver = delete $opts{resolver};
|
||||
$opts{timeout} ||= 15;
|
||||
|
||||
my $self = LWP::UserAgent->new( %opts );
|
||||
|
||||
$self->{'blocked_hosts'} = $blocked_hosts;
|
||||
$self->{'whitelisted_hosts'} = $whitelisted_hosts;
|
||||
$self->{'resolver'} = $resolver;
|
||||
|
||||
$self = bless $self, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
# returns seconds remaining given a request
|
||||
sub _time_remain {
|
||||
my $self = shift;
|
||||
my $req = shift;
|
||||
|
||||
my $now = time();
|
||||
my $start_time = $req->{_time_begin} || $now;
|
||||
return $start_time + $self->{timeout} - $now;
|
||||
}
|
||||
|
||||
sub _resolve {
|
||||
my ($self, $host, $request, $timeout, $depth) = @_;
|
||||
my $res = $self->resolver;
|
||||
$depth ||= 0;
|
||||
|
||||
die "CNAME recursion depth limit exceeded.\n" if $depth > 10;
|
||||
die "Suspicious results from DNS lookup" if $self->_bad_host($host);
|
||||
|
||||
# return the IP address if it looks like one and wasn't marked bad
|
||||
return ($host) if $host =~ /^\d+\.\d+\.\d+\.\d+$/;
|
||||
|
||||
my $sock = $res->bgsend($host)
|
||||
or die "No sock from bgsend";
|
||||
|
||||
my $rin = '';
|
||||
vec($rin, fileno($sock), 1) = 1;
|
||||
my $nf = select($rin, undef, undef, $self->_time_remain($request));
|
||||
die "DNS lookup timeout" unless $nf;
|
||||
|
||||
my $packet = $res->bgread($sock)
|
||||
or die "DNS bgread failure";
|
||||
$sock = undef;
|
||||
|
||||
my @addr;
|
||||
my $cname;
|
||||
foreach my $rr ($packet->answer) {
|
||||
if ($rr->type eq "A") {
|
||||
die "Suspicious DNS results from A record\n" if $self->_bad_host($rr->address);
|
||||
push @addr, $rr->address;
|
||||
} elsif ($rr->type eq "CNAME") {
|
||||
# will be checked for validity in the recursion path
|
||||
$cname = $rr->cname;
|
||||
}
|
||||
}
|
||||
|
||||
return @addr if @addr;
|
||||
return () unless $cname;
|
||||
return $self->_resolve($cname, $request, $timeout, $depth + 1);
|
||||
}
|
||||
|
||||
sub _host_list_match {
|
||||
my $self = shift;
|
||||
my $list_name = shift;
|
||||
my $host = shift;
|
||||
|
||||
foreach my $rule (@{ $self->{$list_name} }) {
|
||||
if (ref $rule eq "CODE") {
|
||||
return 1 if $rule->($host);
|
||||
} elsif (ref $rule) {
|
||||
# assume regexp
|
||||
return 1 if $host =~ /$rule/;
|
||||
} else {
|
||||
return 1 if $host eq $rule;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub _bad_host {
|
||||
my $self = shift;
|
||||
my $host = lc(shift);
|
||||
|
||||
return 0 if $self->_host_list_match("whitelisted_hosts", $host);
|
||||
return 1 if $self->_host_list_match("blocked_hosts", $host);
|
||||
return 1 if
|
||||
$host =~ /^localhost$/i || # localhost is bad. even though it'd be stopped in
|
||||
# a later call to _bad_host with the IP address
|
||||
$host =~ /\s/i; # any whitespace is questionable
|
||||
|
||||
# Let's assume it's an IP address now, and get it into 32 bits.
|
||||
# Uf at any time something doesn't look like a number, then it's
|
||||
# probably a hostname and we've already either whitelisted or
|
||||
# blacklisted those, so we'll just say it's okay and it'll come
|
||||
# back here later when the resolver finds an IP address.
|
||||
my @parts = split(/\./, $host);
|
||||
return 0 if @parts > 4;
|
||||
|
||||
# un-octal/un-hex the parts, or return if there's a non-numeric part
|
||||
my $overflow_flag = 0;
|
||||
foreach (@parts) {
|
||||
return 0 unless /^\d+$/ || /^0x[a-f\d]+$/;
|
||||
local $SIG{__WARN__} = sub { $overflow_flag = 1; };
|
||||
$_ = oct($_) if /^0/;
|
||||
}
|
||||
|
||||
# a purely numeric address shouldn't overflow.
|
||||
return 1 if $overflow_flag;
|
||||
|
||||
my $addr; # network order packed IP address
|
||||
|
||||
if (@parts == 1) {
|
||||
# a - 32 bits
|
||||
return 1 if
|
||||
$parts[0] > 0xffffffff;
|
||||
$addr = pack("N", $parts[0]);
|
||||
} elsif (@parts == 2) {
|
||||
# a.b - 8.24 bits
|
||||
return 1 if
|
||||
$parts[0] > 0xff ||
|
||||
$parts[1] > 0xffffff;
|
||||
$addr = pack("N", $parts[0] << 24 | $parts[1]);
|
||||
} elsif (@parts == 3) {
|
||||
# a.b.c - 8.8.16 bits
|
||||
return 1 if
|
||||
$parts[0] > 0xff ||
|
||||
$parts[1] > 0xff ||
|
||||
$parts[2] > 0xffff;
|
||||
$addr = pack("N", $parts[0] << 24 | $parts[1] << 16 | $parts[2]);
|
||||
} else {
|
||||
# a.b.c.d - 8.8.8.8 bits
|
||||
return 1 if
|
||||
$parts[0] > 0xff ||
|
||||
$parts[1] > 0xff ||
|
||||
$parts[2] > 0xff ||
|
||||
$parts[3] > 0xff;
|
||||
$addr = pack("N", $parts[0] << 24 | $parts[1] << 16 | $parts[2] << 8 | $parts[3]);
|
||||
}
|
||||
|
||||
my $haddr = unpack("N", $addr); # host order IP address
|
||||
return 1 if
|
||||
($haddr & 0xFF000000) == 0x00000000 || # 0.0.0.0/8
|
||||
($haddr & 0xFF000000) == 0x0A000000 || # 10.0.0.0/8
|
||||
($haddr & 0xFF000000) == 0x7F000000 || # 127.0.0.0/8
|
||||
($haddr & 0xFFF00000) == 0xAC100000 || # 172.16.0.0/12
|
||||
($haddr & 0xFFFF0000) == 0xA9FE0000 || # 169.254.0.0/16
|
||||
($haddr & 0xFFFF0000) == 0xC0A80000 || # 192.168.0.0/16
|
||||
$haddr == 0xFFFFFFFF || # 255.255.255.255
|
||||
($haddr & 0xF0000000) == 0xE0000000; # multicast addresses
|
||||
|
||||
# as final IP address check, pass in the canonical a.b.c.d decimal form
|
||||
# to the blacklisted host check to see if matches as bad there.
|
||||
my $can_ip = join(".", map { ord } split //, $addr);
|
||||
return 1 if $self->_host_list_match("blocked_hosts", $can_ip);
|
||||
|
||||
# looks like an okay IP address
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub request {
|
||||
my ($self, $req, $arg, $size, $previous) = @_;
|
||||
|
||||
# walk back to the first request, and set our _time_begin to its _time_begin, or if
|
||||
# we're the first, then use current time. used by LWPx::Protocol::http_paranoid
|
||||
my $first_res = $previous; # previous is the previous response that invoked this request
|
||||
$first_res = $first_res->previous while $first_res && $first_res->previous;
|
||||
$req->{_time_begin} = $first_res ? $first_res->request->{_time_begin} : time();
|
||||
|
||||
my $host = $req->uri->host;
|
||||
if ($self->_bad_host($host)) {
|
||||
my $err_res = HTTP::Response->new(403, "Unauthorized access to blocked host");
|
||||
$err_res->request($req);
|
||||
$err_res->header("Client-Date" => HTTP::Date::time2str(time));
|
||||
$err_res->header("Client-Warning" => "Internal response");
|
||||
$err_res->header("Content-Type" => "text/plain");
|
||||
$err_res->content("403 Unauthorized access to blocked host\n");
|
||||
return $err_res;
|
||||
}
|
||||
|
||||
return $self->SUPER::request($req, $arg, $size, $previous);
|
||||
}
|
||||
|
||||
sub redirect_ok
|
||||
{
|
||||
# RFC 2616, section 10.3.2 and 10.3.3 say:
|
||||
# If the 30[12] status code is received in response to a request other
|
||||
# than GET or HEAD, the user agent MUST NOT automatically redirect the
|
||||
# request unless it can be confirmed by the user, since this might
|
||||
# change the conditions under which the request was issued.
|
||||
|
||||
# Note that this routine used to be just:
|
||||
# return 0 if $_[1]->method eq "POST"; return 1;
|
||||
|
||||
my($self, $new_request, $response) = @_;
|
||||
my $method = $response->request->method;
|
||||
return 0 unless grep $_ eq $method,
|
||||
@{ $self->requests_redirectable || [] };
|
||||
|
||||
if ($new_request->url->scheme eq 'file') {
|
||||
$response->header("Client-Warning" =>
|
||||
"Can't redirect to a file:// URL!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$self->{'final_url'} = $new_request->uri;
|
||||
|
||||
# Otherwise it's apparently okay...
|
||||
return 1;
|
||||
}
|
||||
|
||||
# taken from LWP::UserAgent and modified slightly. (proxy support removed,
|
||||
# and map http and https schemes to separate protocol handlers)
|
||||
sub send_request
|
||||
{
|
||||
my ($self, $request, $arg, $size) = @_;
|
||||
$self->_request_sanity_check($request);
|
||||
|
||||
my ($method, $url) = ($request->method, $request->uri);
|
||||
|
||||
local($SIG{__DIE__}); # protect against user defined die handlers
|
||||
|
||||
# Check that we have a METHOD and a URL first
|
||||
return _new_response($request, &HTTP::Status::RC_BAD_REQUEST, "Method missing")
|
||||
unless $method;
|
||||
return _new_response($request, &HTTP::Status::RC_BAD_REQUEST, "URL missing")
|
||||
unless $url;
|
||||
return _new_response($request, &HTTP::Status::RC_BAD_REQUEST, "URL must be absolute")
|
||||
unless $url->scheme;
|
||||
return _new_response($request, &HTTP::Status::RC_BAD_REQUEST,
|
||||
"ParanoidAgent doesn't support going through proxies. ".
|
||||
"In that case, do your paranoia at your proxy instead.")
|
||||
if $self->_need_proxy($url);
|
||||
|
||||
my $scheme = $url->scheme;
|
||||
return _new_response($request, &HTTP::Status::RC_BAD_REQUEST, "Only http and https are supported by ParanoidAgent")
|
||||
unless $scheme eq "http" || $scheme eq "https";
|
||||
|
||||
LWP::Debug::trace("$method $url");
|
||||
|
||||
my $protocol;
|
||||
|
||||
{
|
||||
# Honor object-specific restrictions by forcing protocol objects
|
||||
# into class LWP::Protocol::nogo.
|
||||
my $x;
|
||||
if($x = $self->protocols_allowed) {
|
||||
if(grep lc($_) eq $scheme, @$x) {
|
||||
LWP::Debug::trace("$scheme URLs are among $self\'s allowed protocols (@$x)");
|
||||
}
|
||||
else {
|
||||
LWP::Debug::trace("$scheme URLs aren't among $self\'s allowed protocols (@$x)");
|
||||
require LWP::Protocol::nogo;
|
||||
$protocol = LWP::Protocol::nogo->new;
|
||||
}
|
||||
}
|
||||
elsif ($x = $self->protocols_forbidden) {
|
||||
if(grep lc($_) eq $scheme, @$x) {
|
||||
LWP::Debug::trace("$scheme URLs are among $self\'s forbidden protocols (@$x)");
|
||||
require LWP::Protocol::nogo;
|
||||
$protocol = LWP::Protocol::nogo->new;
|
||||
}
|
||||
else {
|
||||
LWP::Debug::trace("$scheme URLs aren't among $self\'s forbidden protocols (@$x)");
|
||||
}
|
||||
}
|
||||
# else fall thru and create the protocol object normally
|
||||
}
|
||||
|
||||
unless ($protocol) {
|
||||
LWP::Protocol::implementor("${scheme}_paranoid", "LWPx::Protocol::${scheme}_paranoid");
|
||||
eval "require LWPx::Protocol::${scheme}_paranoid;";
|
||||
if ($@) {
|
||||
$@ =~ s/ at .* line \d+.*//s; # remove file/line number
|
||||
my $response = _new_response($request, &HTTP::Status::RC_NOT_IMPLEMENTED, $@);
|
||||
return $response;
|
||||
}
|
||||
|
||||
$protocol = eval { LWP::Protocol::create($scheme eq "http" ? "http_paranoid" : "https_paranoid", $self) };
|
||||
if ($@) {
|
||||
$@ =~ s/ at .* line \d+.*//s; # remove file/line number
|
||||
my $response = _new_response($request, &HTTP::Status::RC_NOT_IMPLEMENTED, $@);
|
||||
if ($scheme eq "https") {
|
||||
$response->message($response->message . " (Crypt::SSLeay not installed)");
|
||||
$response->content_type("text/plain");
|
||||
$response->content(<<EOT);
|
||||
LWP will support https URLs if the Crypt::SSLeay module is installed.
|
||||
More information at <http://www.linpro.no/lwp/libwww-perl/README.SSL>.
|
||||
EOT
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
# Extract fields that will be used below
|
||||
my ($timeout, $cookie_jar, $use_eval, $parse_head, $max_size) =
|
||||
@{$self}{qw(timeout cookie_jar use_eval parse_head max_size)};
|
||||
|
||||
my $response;
|
||||
my $proxy = undef;
|
||||
if ($use_eval) {
|
||||
# we eval, and turn dies into responses below
|
||||
eval {
|
||||
$response = $protocol->request($request, $proxy,
|
||||
$arg, $size, $timeout);
|
||||
};
|
||||
if ($@) {
|
||||
$@ =~ s/ at .* line \d+.*//s; # remove file/line number
|
||||
$response = _new_response($request,
|
||||
&HTTP::Status::RC_INTERNAL_SERVER_ERROR,
|
||||
$@);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$response = $protocol->request($request, $proxy,
|
||||
$arg, $size, $timeout);
|
||||
# XXX: Should we die unless $response->is_success ???
|
||||
}
|
||||
|
||||
$response->request($request); # record request for reference
|
||||
$cookie_jar->extract_cookies($response) if $cookie_jar;
|
||||
$response->header("Client-Date" => HTTP::Date::time2str(time));
|
||||
return $response;
|
||||
}
|
||||
|
||||
# blocked hostnames, compiled patterns, or subrefs
|
||||
sub blocked_hosts
|
||||
{
|
||||
my $self = shift;
|
||||
if (@_) {
|
||||
my @hosts = @_;
|
||||
$self->{'blocked_hosts'} = \@hosts;
|
||||
return;
|
||||
}
|
||||
return @{ $self->{'blocked_hosts'} };
|
||||
}
|
||||
|
||||
# whitelisted hostnames, compiled patterns, or subrefs
|
||||
sub whitelisted_hosts
|
||||
{
|
||||
my $self = shift;
|
||||
if (@_) {
|
||||
my @hosts = @_;
|
||||
$self->{'whitelisted_hosts'} = \@hosts;
|
||||
return;
|
||||
}
|
||||
return @{ $self->{'whitelisted_hosts'} };
|
||||
}
|
||||
|
||||
# get/set Net::DNS resolver object
|
||||
sub resolver
|
||||
{
|
||||
my $self = shift;
|
||||
if (@_) {
|
||||
$self->{'resolver'} = shift;
|
||||
require UNIVERSAL ;
|
||||
die "Not a Net::DNS::Resolver object" unless
|
||||
UNIVERSAL::isa($self->{'resolver'}, "Net::DNS::Resolver");
|
||||
}
|
||||
return $self->{'resolver'} ||= Net::DNS::Resolver->new;
|
||||
}
|
||||
|
||||
# Taken directly from LWP::UserAgent because it was private there, and we can't depend on it
|
||||
# staying there in future versions: needed by our modified version of send_request
|
||||
sub _need_proxy
|
||||
{
|
||||
my($self, $url) = @_;
|
||||
$url = $HTTP::URI_CLASS->new($url) unless ref $url;
|
||||
|
||||
my $scheme = $url->scheme || return;
|
||||
if (my $proxy = $self->{'proxy'}{$scheme}) {
|
||||
if (@{ $self->{'no_proxy'} }) {
|
||||
if (my $host = eval { $url->host }) {
|
||||
for my $domain (@{ $self->{'no_proxy'} }) {
|
||||
if ($host =~ /\Q$domain\E$/) {
|
||||
LWP::Debug::trace("no_proxy configured");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LWP::Debug::debug("Proxied to $proxy");
|
||||
return $HTTP::URI_CLASS->new($proxy);
|
||||
}
|
||||
LWP::Debug::debug('Not proxied');
|
||||
undef;
|
||||
}
|
||||
|
||||
# Taken directly from LWP::UserAgent because it was private there, and we can't depend on it
|
||||
# staying there in future versions: needed by our modified version of send_request
|
||||
sub _request_sanity_check {
|
||||
my($self, $request) = @_;
|
||||
# some sanity checking
|
||||
if (defined $request) {
|
||||
if (ref $request) {
|
||||
Carp::croak("You need a request object, not a " . ref($request) . " object")
|
||||
if ref($request) eq 'ARRAY' or ref($request) eq 'HASH' or
|
||||
!$request->can('method') or !$request->can('uri');
|
||||
}
|
||||
else {
|
||||
Carp::croak("You need a request object, not '$request'");
|
||||
}
|
||||
}
|
||||
else {
|
||||
Carp::croak("No request object passed in");
|
||||
}
|
||||
}
|
||||
|
||||
# Taken directly from LWP::UserAgent because it was private there, and we can't depend on it
|
||||
# staying there in future versions: needed by our modified version of send_request
|
||||
sub _new_response {
|
||||
my($request, $code, $message) = @_;
|
||||
my $response = HTTP::Response->new($code, $message);
|
||||
$response->request($request);
|
||||
$response->header("Client-Date" => HTTP::Date::time2str(time));
|
||||
$response->header("Client-Warning" => "Internal response");
|
||||
$response->header("Content-Type" => "text/plain");
|
||||
$response->content("$code $message\n");
|
||||
return $response;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
LWPx::ParanoidAgent - subclass of LWP::UserAgent that protects you from harm
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
require LWPx::ParanoidAgent;
|
||||
|
||||
my $ua = LWPx::ParanoidAgent->new;
|
||||
|
||||
# this is 10 seconds overall, from start to finish. not just between
|
||||
# socket reads. and it includes all redirects. so attackers telling
|
||||
# you to download from a malicious tarpit webserver can only stall
|
||||
# you for $n seconds
|
||||
|
||||
$ua->timeout(10);
|
||||
|
||||
# setup extra block lists, in addition to the always-enforced blocking
|
||||
# of private IP addresses, loopbacks, and multicast addresses
|
||||
|
||||
$ua->blocked_hosts(
|
||||
"foo.com",
|
||||
qr/\.internal\.company\.com$/i,
|
||||
sub { my $host = shift; return 1 if is_bad($host); },
|
||||
);
|
||||
|
||||
$ua->whitelisted_hosts(
|
||||
"brad.lj",
|
||||
qr/^192\.168\.64\.3?/,
|
||||
sub { ... },
|
||||
);
|
||||
|
||||
# get/set the DNS resolver object that's used
|
||||
my $resolver = $ua->resolver;
|
||||
$ua->resolver(Net::DNS::Resolver->new(...));
|
||||
|
||||
# and then just like a normal LWP::UserAgent, because it is one.
|
||||
my $response = $ua->get('http://search.cpan.org/');
|
||||
...
|
||||
if ($response->is_success) {
|
||||
print $response->content; # or whatever
|
||||
}
|
||||
else {
|
||||
die $response->status_line;
|
||||
}
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
The C<LWPx::ParanoidAgent> is a class subclassing C<LWP::UserAgent>,
|
||||
but paranoid against attackers. It's to be used when you're fetching
|
||||
a remote resource on behalf of a possibly malicious user.
|
||||
|
||||
This class can do whatever C<LWP::UserAgent> can (callbacks, uploads from
|
||||
files, etc), except proxy support is explicitly removed, because in
|
||||
that case you should do your paranoia at your proxy.
|
||||
|
||||
Also, the schemes are limited to http and https, which are mapped to
|
||||
C<LWPx::Protocol::http_paranoid> and
|
||||
C<LWPx::Protocol::https_paranoid>, respectively, which are forked
|
||||
versions of the same ones without the "_paranoid". Subclassing them
|
||||
didn't look possible, as they were essentially just one huge function.
|
||||
|
||||
This class protects you from connecting to internal IP ranges (unless you
|
||||
whitelist them), hostnames/IPs that you blacklist, remote webserver
|
||||
tarpitting your process (the timeout parameter is changed to be a global
|
||||
timeout over the entire process), and all combinations of redirects and
|
||||
DNS tricks to otherwise tarpit and/or connect to internal resources.
|
||||
|
||||
=head1 CONSTRUCTOR
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<new>
|
||||
|
||||
my $ua = LWPx::ParanoidAgent->new([ %opts ]);
|
||||
|
||||
In addition to any constructor options from L<LWP::UserAgent>, you may
|
||||
also set C<blocked_hosts> (to an arrayref), C<whitelisted_hosts> (also
|
||||
an arrayref), and C<resolver>, a Net::DNS::Resolver object.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over 4
|
||||
|
||||
=item $csr->B<resolver>($net_dns_resolver)
|
||||
|
||||
=item $csr->B<resolver>
|
||||
|
||||
Get/set the L<Net::DNS::Resolver> object used to lookup hostnames.
|
||||
|
||||
=item $csr->B<blocked_hosts>(@host_list)
|
||||
|
||||
=item $csr->B<blocked_hosts>
|
||||
|
||||
Get/set the the list of blocked hosts. The items in @host_list may be
|
||||
compiled regular expressions (with qr//), code blocks, or scalar
|
||||
literals. In any case, the thing that is match, passed in, or
|
||||
compared (respectively), is all of the given hostname, given IP
|
||||
address, and IP address in canonical a.b.c.d decimal notation. So if
|
||||
you want to block "1.2.3.4" and the user entered it in a mix of
|
||||
network/host form in a mix of decimal/octal/hex, you need only block
|
||||
"1.2.3.4" and not worry about the details.
|
||||
|
||||
=item $csr->B<whitelisted_hosts>(@host_list)
|
||||
|
||||
=item $csr->B<whitelisted_hosts>
|
||||
|
||||
Like blocked hosts, but matching the hosts/IPs that bypass blocking
|
||||
checks. The only difference is the IP address isn't canonicalized
|
||||
before being whitelisted-matched, mostly because it doesn't make sense
|
||||
for somebody to enter in a good address in a subversive way.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
See L<LWP::UserAgent> to see how to use this class.
|
||||
|
||||
=head1 WARRANTY
|
||||
|
||||
This module is supplied "as-is" and comes with no warranty, expressed
|
||||
or implied. It tries to protect you from harm, but maybe it will.
|
||||
Maybe it will destroy your data and your servers. You'd better audit
|
||||
it and send me bug reports.
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
Maybe. See the warranty above.
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2005 Brad Fitzpatrick
|
||||
|
||||
Lot of code from the the base class, copyright 1995-2004 Gisle Aas.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself.
|
||||
231
local/cgi-bin/Net/OpenID/Association.pm
Executable file
231
local/cgi-bin/Net/OpenID/Association.pm
Executable file
@@ -0,0 +1,231 @@
|
||||
use strict;
|
||||
use Carp ();
|
||||
|
||||
############################################################################
|
||||
package Net::OpenID::Association;
|
||||
use fields (
|
||||
'server', # author-identity identity server endpoint
|
||||
'secret', # the secret for this association
|
||||
'handle', # the 255-character-max ASCII printable handle (33-126)
|
||||
'expiry', # unixtime, adjusted, of when this association expires
|
||||
'type', # association type
|
||||
);
|
||||
|
||||
use Storable ();
|
||||
use Digest::SHA1 qw(sha1);
|
||||
|
||||
sub new {
|
||||
my Net::OpenID::Association $self = shift;
|
||||
$self = fields::new( $self ) unless ref $self;
|
||||
my %opts = @_;
|
||||
for my $f (qw( server secret handle expiry type )) {
|
||||
$self->{$f} = delete $opts{$f};
|
||||
}
|
||||
Carp::croak("unknown options: " . join(", ", keys %opts)) if %opts;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub handle {
|
||||
my $self = shift;
|
||||
die if @_;
|
||||
$self->{'handle'};
|
||||
}
|
||||
|
||||
sub secret {
|
||||
my $self = shift;
|
||||
die if @_;
|
||||
$self->{'secret'};
|
||||
}
|
||||
|
||||
sub server {
|
||||
my Net::OpenID::Association $self = shift;
|
||||
Carp::croak("Too many parameters") if @_;
|
||||
return $self->{server};
|
||||
}
|
||||
|
||||
sub expired {
|
||||
my Net::OpenID::Association $self = shift;
|
||||
return time() > $self->{'expiry'};
|
||||
}
|
||||
|
||||
sub usable {
|
||||
my Net::OpenID::Association $self = shift;
|
||||
return 0 unless $self->{'handle'} =~ /^[\x21-\x7e]{1,255}$/;
|
||||
return 0 unless $self->{'expiry'} =~ /^\d+$/;
|
||||
return 0 unless $self->{'secret'};
|
||||
return 0 if $self->expired;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
# return a handle for an identity server, or undef if
|
||||
# no local storage/cache is available, in which case the caller
|
||||
# goes into dumb consumer mode. will do a POST and allocate
|
||||
# a new assoc_handle if none is found, or has expired
|
||||
sub server_assoc {
|
||||
my ($csr, $server) = @_;
|
||||
|
||||
# closure to return undef (dumb consumer mode) and log why
|
||||
my $dumb = sub {
|
||||
$csr->_debug("server_assoc: dumb mode: $_[0]");
|
||||
return undef;
|
||||
};
|
||||
|
||||
my $cache = $csr->cache;
|
||||
return $dumb->("no_cache") unless $cache;
|
||||
|
||||
# try first from cached association handle
|
||||
if (my $handle = $cache->get("shandle:$server")) {
|
||||
my $assoc = handle_assoc($csr, $server, $handle);
|
||||
|
||||
if ($assoc && $assoc->usable) {
|
||||
$csr->_debug("Found association from cache (handle=$handle)");
|
||||
return $assoc;
|
||||
}
|
||||
}
|
||||
|
||||
# make a new association
|
||||
my $dh = _default_dh();
|
||||
|
||||
my %post = (
|
||||
"openid.mode" => "associate",
|
||||
"openid.assoc_type" => "HMAC-SHA1",
|
||||
"openid.session_type" => "DH-SHA1",
|
||||
"openid.dh_consumer_public" => OpenID::util::bi2arg($dh->pub_key),
|
||||
);
|
||||
|
||||
my $req = HTTP::Request->new(POST => $server);
|
||||
$req->header("Content-Type" => "application/x-www-form-urlencoded");
|
||||
$req->content(join("&", map { "$_=" . OpenID::util::eurl($post{$_}) } keys %post));
|
||||
|
||||
$csr->_debug("Associate mode request: " . $req->content);
|
||||
|
||||
my $ua = $csr->ua;
|
||||
my $res = $ua->request($req);
|
||||
|
||||
# uh, some failure, let's go into dumb mode?
|
||||
return $dumb->("http_failure_no_associate") unless $res && $res->is_success;
|
||||
|
||||
my $recv_time = time();
|
||||
my $content = $res->content;
|
||||
my %args = OpenID::util::parse_keyvalue($content);
|
||||
$csr->_debug("Response to associate mode: [$content] parsed = " . join(",", %args));
|
||||
|
||||
return $dumb->("unknown_assoc_type") unless $args{'assoc_type'} eq "HMAC-SHA1";
|
||||
|
||||
my $stype = $args{'session_type'};
|
||||
return $dumb->("unknown_session_type") if $stype && $stype ne "DH-SHA1";
|
||||
|
||||
# protocol version 1.1
|
||||
my $expires_in = $args{'expires_in'};
|
||||
|
||||
# protocol version 1.0 (DEPRECATED)
|
||||
if (! $expires_in) {
|
||||
if (my $issued = OpenID::util::w3c_to_time($args{'issued'})) {
|
||||
my $expiry = OpenID::util::w3c_to_time($args{'expiry'});
|
||||
my $replace_after = OpenID::util::w3c_to_time($args{'replace_after'});
|
||||
|
||||
# seconds ahead (positive) or behind (negative) the server is
|
||||
$expires_in = ($replace_after || $expiry) - $issued;
|
||||
}
|
||||
}
|
||||
|
||||
# between 1 second and 2 years
|
||||
return $dumb->("bogus_expires_in") unless $expires_in > 0 && $expires_in < 63072000;
|
||||
|
||||
my $ahandle = $args{'assoc_handle'};
|
||||
|
||||
my $secret;
|
||||
if ($stype ne "DH-SHA1") {
|
||||
$secret = OpenID::util::d64($args{'mac_key'});
|
||||
} else {
|
||||
my $server_pub = OpenID::util::arg2bi($args{'dh_server_public'});
|
||||
my $dh_sec = $dh->compute_secret($server_pub);
|
||||
$secret = OpenID::util::d64($args{'enc_mac_key'}) ^ sha1(OpenID::util::bi2bytes($dh_sec));
|
||||
}
|
||||
return $dumb->("secret_not_20_bytes") unless length($secret) == 20;
|
||||
|
||||
my %assoc = (
|
||||
handle => $ahandle,
|
||||
server => $server,
|
||||
secret => $secret,
|
||||
type => $args{'assoc_type'},
|
||||
expiry => $recv_time + $expires_in,
|
||||
);
|
||||
|
||||
my $assoc = Net::OpenID::Association->new( %assoc );
|
||||
return $dumb->("assoc_undef") unless $assoc;
|
||||
|
||||
$cache->set("hassoc:$server:$ahandle", Storable::nfreeze(\%assoc));
|
||||
$cache->set("shandle:$server", $ahandle);
|
||||
|
||||
return $assoc;
|
||||
}
|
||||
|
||||
# returns association, or undef if it can't be found
|
||||
sub handle_assoc {
|
||||
my ($csr, $server, $handle) = @_;
|
||||
|
||||
# closure to return undef (dumb consumer mode) and log why
|
||||
my $dumb = sub {
|
||||
$csr->_debug("handle_assoc: dumb mode: $_[0]");
|
||||
return undef;
|
||||
};
|
||||
|
||||
return $dumb->("no_handle") unless $handle;
|
||||
|
||||
my $cache = $csr->cache;
|
||||
return $dumb->("no_cache") unless $cache;
|
||||
|
||||
my $frozen = $cache->get("hassoc:$server:$handle");
|
||||
return $dumb->("not_in_cache") unless $frozen;
|
||||
|
||||
my $param = eval { Storable::thaw($frozen) };
|
||||
return $dumb->("not_a_hashref") unless ref $param eq "HASH";
|
||||
|
||||
return Net::OpenID::Association->new( %$param );
|
||||
}
|
||||
|
||||
sub invalidate_handle {
|
||||
my ($csr, $server, $handle) = @_;
|
||||
my $cache = $csr->cache
|
||||
or return;
|
||||
$cache->set("hassoc:$server:$handle", "");
|
||||
}
|
||||
|
||||
sub _default_dh {
|
||||
my $dh = Crypt::DH->new;
|
||||
$dh->p("155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443");
|
||||
$dh->g("2");
|
||||
$dh->generate_keys;
|
||||
return $dh;
|
||||
}
|
||||
|
||||
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Net::OpenID::Association - a relationship with an identity server
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Internal class.
|
||||
|
||||
=head1 COPYRIGHT, WARRANTY, AUTHOR
|
||||
|
||||
See L<Net::OpenID::Consumer> for author, copyrignt and licensing information.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Net::OpenID::Consumer>
|
||||
|
||||
L<Net::OpenID::VerifiedIdentity>
|
||||
|
||||
L<Net::OpenID::Server>
|
||||
|
||||
Website: L<http://www.danga.com/openid/>
|
||||
|
||||
952
local/cgi-bin/Net/OpenID/Consumer.pm
Executable file
952
local/cgi-bin/Net/OpenID/Consumer.pm
Executable file
@@ -0,0 +1,952 @@
|
||||
# LICENSE: You're free to distribute this under the same terms as Perl itself.
|
||||
|
||||
use strict;
|
||||
use Carp ();
|
||||
use LWP::UserAgent;
|
||||
use URI::Fetch 0.02;
|
||||
|
||||
############################################################################
|
||||
package Net::OpenID::Consumer;
|
||||
|
||||
use vars qw($VERSION);
|
||||
$VERSION = "0.12";
|
||||
|
||||
use fields (
|
||||
'cache', # the Cache object sent to URI::Fetch
|
||||
'ua', # LWP::UserAgent instance to use
|
||||
'args', # how to get at your args
|
||||
'consumer_secret', # scalar/subref
|
||||
'required_root', # the default required_root value, or undef
|
||||
'last_errcode', # last error code we got
|
||||
'last_errtext', # last error code we got
|
||||
'debug', # debug flag or codeblock
|
||||
);
|
||||
|
||||
use Net::OpenID::ClaimedIdentity;
|
||||
use Net::OpenID::VerifiedIdentity;
|
||||
use Net::OpenID::Association;
|
||||
|
||||
use MIME::Base64 ();
|
||||
use Digest::SHA1 ();
|
||||
use Crypt::DH 0.05;
|
||||
use Time::Local;
|
||||
use HTTP::Request;
|
||||
|
||||
sub new {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
$self = fields::new( $self ) unless ref $self;
|
||||
my %opts = @_;
|
||||
|
||||
$self->{ua} = delete $opts{ua};
|
||||
$self->args ( delete $opts{args} );
|
||||
$self->cache ( delete $opts{cache} );
|
||||
$self->consumer_secret ( delete $opts{consumer_secret} );
|
||||
$self->required_root ( delete $opts{required_root} );
|
||||
|
||||
$self->{debug} = delete $opts{debug};
|
||||
|
||||
Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub cache { &_getset; }
|
||||
sub consumer_secret { &_getset; }
|
||||
sub required_root { &_getset; }
|
||||
|
||||
sub _getset {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
my $param = (caller(1))[3];
|
||||
$param =~ s/.+:://;
|
||||
|
||||
if (@_) {
|
||||
my $val = shift;
|
||||
Carp::croak("Too many parameters") if @_;
|
||||
$self->{$param} = $val;
|
||||
}
|
||||
return $self->{$param};
|
||||
}
|
||||
|
||||
sub _debug {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
return unless $self->{debug};
|
||||
|
||||
if (ref $self->{debug} eq "CODE") {
|
||||
$self->{debug}->($_[0]);
|
||||
} else {
|
||||
print STDERR "[DEBUG Net::OpenID::Consumer] $_[0]\n";
|
||||
}
|
||||
}
|
||||
|
||||
# given something that can have GET arguments, returns a subref to get them:
|
||||
# Apache
|
||||
# Apache::Request
|
||||
# CGI
|
||||
# HASH of get args
|
||||
# CODE returning get arg, given key
|
||||
|
||||
# ...
|
||||
|
||||
sub args {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
|
||||
if (my $what = shift) {
|
||||
Carp::croak("Too many parameters") if @_;
|
||||
my $getter;
|
||||
if (! ref $what){
|
||||
Carp::croak("No args defined") unless $self->{args};
|
||||
return $self->{args}->($what);
|
||||
} elsif (ref $what eq "HASH") {
|
||||
$getter = sub { $what->{$_[0]}; };
|
||||
} elsif (ref $what eq "CGI") {
|
||||
$getter = sub { scalar $what->param($_[0]); };
|
||||
} elsif (ref $what eq "Apache") {
|
||||
my %get = $what->args;
|
||||
$getter = sub { $get{$_[0]}; };
|
||||
} elsif (ref $what eq "Apache::Request") {
|
||||
$getter = sub { scalar $what->param($_[0]); };
|
||||
} elsif (ref $what eq "CODE") {
|
||||
$getter = $what;
|
||||
} else {
|
||||
Carp::croak("Unknown parameter type ($what)");
|
||||
}
|
||||
if ($getter) {
|
||||
$self->{args} = $getter;
|
||||
}
|
||||
}
|
||||
$self->{args};
|
||||
}
|
||||
|
||||
sub ua {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
$self->{ua} = shift if @_;
|
||||
Carp::croak("Too many parameters") if @_;
|
||||
|
||||
# make default one on first access
|
||||
unless ($self->{ua}) {
|
||||
my $ua = $self->{ua} = LWP::UserAgent->new;
|
||||
$ua->timeout(10);
|
||||
}
|
||||
|
||||
$self->{ua};
|
||||
}
|
||||
|
||||
sub _fail {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
my ($code, $text) = @_;
|
||||
|
||||
$text ||= {
|
||||
'no_identity_server' => "The provided URL doesn't declare its OpenID identity server.",
|
||||
'empty_url' => "No URL entered.",
|
||||
'bogus_url' => "Invalid URL.",
|
||||
'no_head_tag' => "URL provided doesn't seem to have a head tag.",
|
||||
'url_fetch_err' => "Error fetching the provided URL.",
|
||||
}->{$code};
|
||||
|
||||
$self->{last_errcode} = $code;
|
||||
$self->{last_errtext} = $text;
|
||||
|
||||
$self->_debug("fail($code) $text");
|
||||
wantarray ? () : undef;
|
||||
}
|
||||
|
||||
sub json_err {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
return OpenID::util::js_dumper({
|
||||
err_code => $self->{last_errcode},
|
||||
err_text => $self->{last_errtext},
|
||||
});
|
||||
}
|
||||
|
||||
sub err {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
$self->{last_errcode} . ": " . $self->{last_errtext};
|
||||
}
|
||||
|
||||
sub errcode {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
$self->{last_errcode};
|
||||
}
|
||||
|
||||
sub errtext {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
$self->{last_errtext};
|
||||
}
|
||||
|
||||
|
||||
sub _get_url_contents {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
my ($url, $final_url_ref, $hook) = @_;
|
||||
$final_url_ref ||= do { my $dummy; \$dummy; };
|
||||
|
||||
my $ures = URI::Fetch->fetch($url,
|
||||
UserAgent => $self->ua,
|
||||
Cache => $self->cache,
|
||||
ContentAlterHook => $hook,
|
||||
)
|
||||
or return $self->_fail("url_fetch_error", "Error fetching URL: " . URI::Fetch->errstr);
|
||||
|
||||
# who actually uses HTTP gone response status? uh, nobody.
|
||||
if ($ures->status == URI::Fetch::URI_GONE()) {
|
||||
return $self->_fail("url_gone", "URL is no longer available");
|
||||
}
|
||||
|
||||
my $res = $ures->http_response;
|
||||
$$final_url_ref = $res->request->uri->as_string;
|
||||
|
||||
return $ures->content;
|
||||
}
|
||||
|
||||
sub _find_semantic_info {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
my $url = shift;
|
||||
my $final_url_ref = shift;
|
||||
|
||||
my $trim_hook = sub {
|
||||
my $htmlref = shift;
|
||||
# trim everything past the body. this is in case the user doesn't
|
||||
# have a head document and somebody was able to inject their own
|
||||
# head. -- brad choate
|
||||
$$htmlref =~ s/<body\b.*//is;
|
||||
};
|
||||
|
||||
my $doc = $self->_get_url_contents($url, $final_url_ref, $trim_hook) or
|
||||
return;
|
||||
|
||||
# find <head> content of document (notably: the first head, if an attacker
|
||||
# has added others somehow)
|
||||
return $self->_fail("no_head_tag", "Couldn't find OpenID servers due to no head tag")
|
||||
unless $doc =~ m!<head[^>]*>(.*?)</head>!is;
|
||||
my $head = $1;
|
||||
|
||||
my $ret = {
|
||||
'openid.server' => undef,
|
||||
'openid.delegate' => undef,
|
||||
'foaf' => undef,
|
||||
'foaf.maker' => undef,
|
||||
'rss' => undef,
|
||||
'atom' => undef,
|
||||
};
|
||||
|
||||
# analyze link/meta tags
|
||||
while ($head =~ m!<(link|meta)\b([^>]+)>!g) {
|
||||
my ($type, $val) = ($1, $2);
|
||||
my $temp;
|
||||
|
||||
# OpenID servers / delegated identities
|
||||
# <link rel="openid.server" href="http://www.livejournal.com/misc/openid.bml" />
|
||||
if ($type eq "link" &&
|
||||
$val =~ /\brel=.openid\.(server|delegate)./i && ($temp = $1) &&
|
||||
$val =~ m!\bhref=[\"\']([^\"\']+)[\"\']!i) {
|
||||
$ret->{"openid.$temp"} = $1;
|
||||
next;
|
||||
}
|
||||
|
||||
# FOAF documents
|
||||
#<link rel="meta" type="application/rdf+xml" title="FOAF" href="http://brad.livejournal.com/data/foaf" />
|
||||
if ($type eq "link" &&
|
||||
$val =~ m!title=.foaf.!i &&
|
||||
$val =~ m!rel=.meta.!i &&
|
||||
$val =~ m!type=.application/rdf\+xml.!i &&
|
||||
$val =~ m!href=[\"\']([^\"\']+)[\"\']!i) {
|
||||
$ret->{"foaf"} = $1;
|
||||
next;
|
||||
}
|
||||
|
||||
# FOAF maker info
|
||||
# <meta name="foaf:maker" content="foaf:mbox_sha1sum '4caa1d6f6203d21705a00a7aca86203e82a9cf7a'" />
|
||||
if ($type eq "meta" &&
|
||||
$val =~ m!name=.foaf:maker.!i &&
|
||||
$val =~ m!content=([\'\"])(.*?)\1!i) {
|
||||
$ret->{"foaf.maker"} = $2;
|
||||
next;
|
||||
}
|
||||
|
||||
if ($type eq "meta" &&
|
||||
$val =~ m!name=.foaf:maker.!i &&
|
||||
$val =~ m!content=([\'\"])(.*?)\1!i) {
|
||||
$ret->{"foaf.maker"} = $2;
|
||||
next;
|
||||
}
|
||||
|
||||
# RSS
|
||||
# <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.livejournal.com/~brad/data/rss" />
|
||||
if ($type eq "link" &&
|
||||
$val =~ m!rel=.alternate.!i &&
|
||||
$val =~ m!type=.application/rss\+xml.!i &&
|
||||
$val =~ m!href=[\"\']([^\"\']+)[\"\']!i) {
|
||||
$ret->{"rss"} = $1;
|
||||
next;
|
||||
}
|
||||
|
||||
# Atom
|
||||
# <link rel="alternate" type="application/atom+xml" title="Atom" href="http://www.livejournal.com/~brad/data/rss" />
|
||||
if ($type eq "link" &&
|
||||
$val =~ m!rel=.alternate.!i &&
|
||||
$val =~ m!type=.application/atom\+xml.!i &&
|
||||
$val =~ m!href=[\"\']([^\"\']+)[\"\']!i) {
|
||||
$ret->{"atom"} = $1;
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# map the 4 entities that the spec asks for
|
||||
my $emap = {
|
||||
'lt' => '<',
|
||||
'gt' => '>',
|
||||
'quot' => '"',
|
||||
'amp' => '&',
|
||||
};
|
||||
foreach my $k (keys %$ret) {
|
||||
next unless $ret->{$k};
|
||||
$ret->{$k} =~ s/&(\w+);/$emap->{$1} || ""/eg;
|
||||
}
|
||||
|
||||
$self->_debug("semantic info ($url) = " . join(", ", %$ret));
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
sub _find_openid_server {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
my $url = shift;
|
||||
my $final_url_ref = shift;
|
||||
|
||||
my $sem_info = $self->_find_semantic_info($url, $final_url_ref) or
|
||||
return;
|
||||
|
||||
return $self->_fail("no_identity_server") unless $sem_info->{"openid.server"};
|
||||
$sem_info->{"openid.server"};
|
||||
}
|
||||
|
||||
# returns Net::OpenID::ClaimedIdentity
|
||||
sub claimed_identity {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
my $url = shift;
|
||||
Carp::croak("Too many parameters") if @_;
|
||||
|
||||
# trim whitespace
|
||||
$url =~ s/^\s+//;
|
||||
$url =~ s/\s+$//;
|
||||
return $self->_fail("empty_url", "Empty URL") unless $url;
|
||||
|
||||
# do basic canonicalization
|
||||
$url = "http://$url" if $url && $url !~ m!^\w+://!;
|
||||
return $self->_fail("bogus_url", "Invalid URL") unless $url =~ m!^https?://!i;
|
||||
# add a slash, if none exists
|
||||
$url .= "/" unless $url =~ m!^http://.+/!i;
|
||||
|
||||
my $final_url;
|
||||
|
||||
my $sem_info = $self->_find_semantic_info($url, \$final_url) or
|
||||
return;
|
||||
|
||||
my $id_server = $sem_info->{"openid.server"} or
|
||||
return $self->_fail("no_identity_server");
|
||||
|
||||
return Net::OpenID::ClaimedIdentity->new(
|
||||
identity => $final_url,
|
||||
server => $id_server,
|
||||
consumer => $self,
|
||||
delegate => $sem_info->{'openid.delegate'},
|
||||
);
|
||||
}
|
||||
|
||||
sub user_cancel {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
return $self->args("openid.mode") eq "cancel";
|
||||
}
|
||||
|
||||
sub user_setup_url {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
my %opts = @_;
|
||||
my $post_grant = delete $opts{'post_grant'};
|
||||
Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts;
|
||||
return $self->_fail("bad_mode") unless $self->args("openid.mode") eq "id_res";
|
||||
|
||||
my $setup_url = $self->args("openid.user_setup_url");
|
||||
|
||||
OpenID::util::push_url_arg(\$setup_url, "openid.post_grant", $post_grant)
|
||||
if $setup_url && $post_grant;
|
||||
|
||||
return $setup_url;
|
||||
}
|
||||
|
||||
sub verified_identity {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
my %opts = @_;
|
||||
|
||||
my $rr = delete $opts{'required_root'} || $self->{required_root};
|
||||
Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts;
|
||||
|
||||
return $self->_fail("bad_mode") unless $self->args("openid.mode") eq "id_res";
|
||||
|
||||
# the asserted identity (the delegated one, if there is one, since the protocol
|
||||
# knows nothing of the original URL)
|
||||
my $a_ident = $self->args("openid.identity") or return $self->_fail("no_identity");
|
||||
|
||||
my $sig64 = $self->args("openid.sig") or return $self->_fail("no_sig");
|
||||
my $returnto = $self->args("openid.return_to") or return $self->_fail("no_return_to");
|
||||
my $signed = $self->args("openid.signed");
|
||||
|
||||
my $real_ident = $self->args("oic.identity") || $a_ident;
|
||||
|
||||
# check that returnto is for the right host
|
||||
return $self->_fail("bogus_return_to") if $rr && $returnto !~ /^\Q$rr\E/;
|
||||
|
||||
# check age/signature of return_to
|
||||
my $now = time();
|
||||
{
|
||||
my ($sig_time, $sig) = split(/\-/, $self->args("oic.time") || "");
|
||||
# complain if more than an hour since we sent them off
|
||||
return $self->_fail("time_expired") if $sig_time < $now - 3600;
|
||||
# also complain if the signature is from the future by more than 30 seconds,
|
||||
# which compensates for potential clock drift between nodes in a web farm.
|
||||
return $self->_fail("time_in_future") if $sig_time - 30 > $now;
|
||||
# and check that the time isn't faked
|
||||
my $c_secret = $self->_get_consumer_secret($sig_time);
|
||||
my $good_sig = substr(OpenID::util::hmac_sha1_hex($sig_time, $c_secret), 0, 20);
|
||||
return $self->_fail("time_bad_sig") unless $sig eq $good_sig;
|
||||
}
|
||||
|
||||
my $final_url;
|
||||
my $sem_info = $self->_find_semantic_info($real_ident, \$final_url);
|
||||
return $self->_fail("unexpected_url_redirect") unless $final_url eq $real_ident;
|
||||
|
||||
my $server = $sem_info->{"openid.server"} or
|
||||
return $self->_fail("no_identity_server");
|
||||
|
||||
# if openid.delegate was used, check that it was done correctly
|
||||
if ($a_ident ne $real_ident) {
|
||||
return $self->_fail("bogus_delegation") unless $sem_info->{"openid.delegate"} eq $a_ident;
|
||||
}
|
||||
|
||||
my $assoc_handle = $self->args("openid.assoc_handle");
|
||||
|
||||
$self->_debug("verified_identity: assoc_handle: $assoc_handle");
|
||||
my $assoc = Net::OpenID::Association::handle_assoc($self, $server, $assoc_handle);
|
||||
|
||||
if ($assoc) {
|
||||
$self->_debug("verified_identity: verifying with found association");
|
||||
|
||||
return $self->_fail("expired_association")
|
||||
if $assoc->expired;
|
||||
|
||||
# verify the token
|
||||
my $token = "";
|
||||
foreach my $p (split(/,/, $signed)) {
|
||||
$token .= "$p:" . $self->args("openid.$p") . "\n";
|
||||
}
|
||||
|
||||
my $good_sig = OpenID::util::b64(OpenID::util::hmac_sha1($token, $assoc->secret));
|
||||
return $self->_fail("signature_mismatch") unless $sig64 eq $good_sig;
|
||||
|
||||
} else {
|
||||
$self->_debug("verified_identity: verifying using HTTP (dumb mode)");
|
||||
# didn't find an association. have to do dumb consumer mode
|
||||
# and check it with a POST
|
||||
my %post = (
|
||||
"openid.mode" => "check_authentication",
|
||||
"openid.assoc_handle" => $assoc_handle,
|
||||
"openid.signed" => $signed,
|
||||
"openid.sig" => $sig64,
|
||||
);
|
||||
|
||||
# and copy in all signed parameters that we don't already have into %post
|
||||
foreach my $param (split(/,/, $signed)) {
|
||||
next unless $param =~ /^\w+$/;
|
||||
next if $post{"openid.$param"};
|
||||
$post{"openid.$param"} = $self->args("openid.$param");
|
||||
}
|
||||
|
||||
# if the server told us our handle as bogus, let's ask in our
|
||||
# check_authentication mode whether that's true
|
||||
if (my $ih = $self->args("openid.invalidate_handle")) {
|
||||
$post{"openid.invalidate_handle"} = $ih;
|
||||
}
|
||||
|
||||
my $req = HTTP::Request->new(POST => $server);
|
||||
$req->header("Content-Type" => "application/x-www-form-urlencoded");
|
||||
$req->content(join("&", map { "$_=" . OpenID::util::eurl($post{$_}) } keys %post));
|
||||
|
||||
my $ua = $self->ua;
|
||||
my $res = $ua->request($req);
|
||||
|
||||
# uh, some failure, let's go into dumb mode?
|
||||
return $self->_fail("naive_verify_failed_network") unless $res && $res->is_success;
|
||||
|
||||
my $content = $res->content;
|
||||
my %args = OpenID::util::parse_keyvalue($content);
|
||||
|
||||
# delete the handle from our cache
|
||||
if (my $ih = $args{'invalidate_handle'}) {
|
||||
Net::OpenID::Association::invalidate_handle($self, $server, $ih);
|
||||
}
|
||||
|
||||
# bad but works. check out
|
||||
# http://www.livejournal.com/community/lj_everywhere/194480.html?thread=724400#t724400
|
||||
#
|
||||
if ($content !~ /error:bad_handle/) {
|
||||
return $self->_fail("naive_verify_failed_return: $content") unless
|
||||
$args{'is_valid'} eq "true" || # protocol 1.1
|
||||
$args{'lifetime'} > 0; # DEPRECATED protocol 1.0
|
||||
}
|
||||
}
|
||||
|
||||
$self->_debug("verified identity! = $real_ident");
|
||||
|
||||
# verified!
|
||||
return Net::OpenID::VerifiedIdentity->new(
|
||||
identity => $real_ident,
|
||||
foaf => $sem_info->{"foaf"},
|
||||
foafmaker => $sem_info->{"foaf.maker"},
|
||||
rss => $sem_info->{"rss"},
|
||||
atom => $sem_info->{"atom"},
|
||||
consumer => $self,
|
||||
);
|
||||
}
|
||||
|
||||
sub supports_consumer_secret { 1; }
|
||||
|
||||
sub _get_consumer_secret {
|
||||
my Net::OpenID::Consumer $self = shift;
|
||||
my $time = shift;
|
||||
|
||||
my $ss;
|
||||
if (ref $self->{consumer_secret} eq "CODE") {
|
||||
$ss = $self->{consumer_secret};
|
||||
} elsif ($self->{consumer_secret}) {
|
||||
$ss = sub { return $self->{consumer_secret}; };
|
||||
} else {
|
||||
Carp::croak("You haven't defined a consumer_secret value or subref.\n");
|
||||
}
|
||||
|
||||
my $sec = $ss->($time);
|
||||
Carp::croak("Consumer secret too long") if length($sec) > 255;
|
||||
return $sec;
|
||||
}
|
||||
|
||||
package OpenID::util;
|
||||
|
||||
# From Digest::HMAC
|
||||
sub hmac_sha1_hex {
|
||||
unpack("H*", &hmac_sha1);
|
||||
}
|
||||
sub hmac_sha1 {
|
||||
hmac($_[0], $_[1], \&Digest::SHA1::sha1, 64);
|
||||
}
|
||||
sub hmac {
|
||||
my($data, $key, $hash_func, $block_size) = @_;
|
||||
$block_size ||= 64;
|
||||
$key = &$hash_func($key) if length($key) > $block_size;
|
||||
|
||||
my $k_ipad = $key ^ (chr(0x36) x $block_size);
|
||||
my $k_opad = $key ^ (chr(0x5c) x $block_size);
|
||||
|
||||
&$hash_func($k_opad, &$hash_func($k_ipad, $data));
|
||||
}
|
||||
|
||||
sub parse_keyvalue {
|
||||
my $reply = shift;
|
||||
my %ret;
|
||||
$reply =~ s/\r//g;
|
||||
foreach (split /\n/, $reply) {
|
||||
next unless /^(\S+?):(.*)/;
|
||||
$ret{$1} = $2;
|
||||
}
|
||||
return %ret;
|
||||
}
|
||||
|
||||
sub ejs
|
||||
{
|
||||
my $a = $_[0];
|
||||
$a =~ s/[\"\'\\]/\\$&/g;
|
||||
$a =~ s/\r?\n/\\n/gs;
|
||||
$a =~ s/\r//;
|
||||
return $a;
|
||||
}
|
||||
|
||||
# Data::Dumper for JavaScript
|
||||
sub js_dumper {
|
||||
my $obj = shift;
|
||||
if (ref $obj eq "HASH") {
|
||||
my $ret = "{";
|
||||
foreach my $k (keys %$obj) {
|
||||
$ret .= "$k: " . js_dumper($obj->{$k}) . ",";
|
||||
}
|
||||
chop $ret;
|
||||
$ret .= "}";
|
||||
return $ret;
|
||||
} elsif (ref $obj eq "ARRAY") {
|
||||
my $ret = "[" . join(", ", map { js_dumper($_) } @$obj) . "]";
|
||||
return $ret;
|
||||
} else {
|
||||
return $obj if $obj =~ /^\d+$/;
|
||||
return "\"" . ejs($obj) . "\"";
|
||||
}
|
||||
}
|
||||
|
||||
sub eurl
|
||||
{
|
||||
my $a = $_[0];
|
||||
$a =~ s/([^a-zA-Z0-9_\,\-.\/\\\: ])/uc sprintf("%%%02x",ord($1))/eg;
|
||||
$a =~ tr/ /+/;
|
||||
return $a;
|
||||
}
|
||||
|
||||
sub push_url_arg {
|
||||
my $uref = shift;
|
||||
$$uref =~ s/[&?]$//;
|
||||
my $got_qmark = ($$uref =~ /\?/);
|
||||
|
||||
while (@_) {
|
||||
my $key = shift;
|
||||
my $value = shift;
|
||||
$$uref .= $got_qmark ? "&" : ($got_qmark = 1, "?");
|
||||
$$uref .= eurl($key) . "=" . eurl($value);
|
||||
}
|
||||
}
|
||||
|
||||
sub time_to_w3c {
|
||||
my $time = shift || time();
|
||||
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime($time);
|
||||
$mon++;
|
||||
$year += 1900;
|
||||
|
||||
return sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ",
|
||||
$year, $mon, $mday,
|
||||
$hour, $min, $sec);
|
||||
}
|
||||
|
||||
sub w3c_to_time {
|
||||
my $hms = shift;
|
||||
return 0 unless
|
||||
$hms =~ /^(\d{4,4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/;
|
||||
|
||||
my $time;
|
||||
eval {
|
||||
$time = Time::Local::timegm($6, $5, $4, $3, $2 - 1, $1);
|
||||
};
|
||||
return 0 if $@;
|
||||
return $time;
|
||||
}
|
||||
|
||||
sub bi2bytes {
|
||||
my $bigint = shift;
|
||||
die "Can't deal with negative numbers" if $bigint->is_negative;
|
||||
|
||||
my $bits = $bigint->as_bin;
|
||||
die unless $bits =~ s/^0b//;
|
||||
|
||||
# prepend zeros to round to byte boundary, or to unset high bit
|
||||
my $prepend = (8 - length($bits) % 8) || ($bits =~ /^1/ ? 8 : 0);
|
||||
$bits = ("0" x $prepend) . $bits if $prepend;
|
||||
|
||||
return pack("B*", $bits);
|
||||
}
|
||||
|
||||
sub bi2arg {
|
||||
return b64(bi2bytes($_[0]));
|
||||
}
|
||||
|
||||
sub b64 {
|
||||
my $val = MIME::Base64::encode_base64($_[0]);
|
||||
$val =~ s/\s+//g;
|
||||
return $val;
|
||||
}
|
||||
|
||||
sub d64 {
|
||||
return MIME::Base64::decode_base64($_[0]);
|
||||
}
|
||||
|
||||
sub bytes2bi {
|
||||
return Math::BigInt->new("0b" . unpack("B*", $_[0]));
|
||||
}
|
||||
|
||||
sub arg2bi {
|
||||
return undef unless defined $_[0] and $_[0] ne "";
|
||||
# don't acccept base-64 encoded numbers over 700 bytes. which means
|
||||
# those over 4200 bits.
|
||||
return Math::BigInt->new("0") if length($_[0]) > 700;
|
||||
return bytes2bi(MIME::Base64::decode_base64($_[0]));
|
||||
}
|
||||
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Net::OpenID::Consumer - library for consumers of OpenID identities
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Net::OpenID::Consumer;
|
||||
|
||||
my $csr = Net::OpenID::Consumer->new(
|
||||
ua => LWPx::ParanoidAgent->new,
|
||||
cache => Some::Cache->new,
|
||||
args => $cgi,
|
||||
consumer_secret => ...,
|
||||
required_root => "http://site.example.com/",
|
||||
);
|
||||
|
||||
# a user entered, say, "bradfitz.com" as their identity. The first
|
||||
# step is to fetch that page, parse it, and get a
|
||||
# Net::OpenID::ClaimedIdentity object:
|
||||
|
||||
my $claimed_identity = $csr->claimed_identity("bradfitz.com");
|
||||
|
||||
# now your app has to send them at their identity server's endpoint
|
||||
# to get redirected to either a positive assertion that they own
|
||||
# that identity, or where they need to go to login/setup trust/etc.
|
||||
|
||||
my $check_url = $claimed_identity->check_url(
|
||||
return_to => "http://example.com/openid-check.app?yourarg=val",
|
||||
trust_root => "http://example.com/",
|
||||
);
|
||||
|
||||
# so you send the user off there, and then they come back to
|
||||
# openid-check.app, then you see what the identity server said;
|
||||
|
||||
if (my $setup_url = $csr->user_setup_url) {
|
||||
# redirect/link/popup user to $setup_url
|
||||
} elsif ($csr->user_cancel) {
|
||||
# restore web app state to prior to check_url
|
||||
} elsif (my $vident = $csr->verified_identity) {
|
||||
my $verified_url = $vident->url;
|
||||
print "You are $verified_url !";
|
||||
} else {
|
||||
die "Error validating identity: " . $csr->err;
|
||||
}
|
||||
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is the Perl API for (the consumer half of) OpenID, a distributed
|
||||
identity system based on proving you own a URL, which is then your
|
||||
identity. More information is available at:
|
||||
|
||||
http://www.danga.com/openid/
|
||||
|
||||
=head1 CONSTRUCTOR
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<new>
|
||||
|
||||
my $csr = Net::OpenID::Consumer->new([ %opts ]);
|
||||
|
||||
You can set the C<ua>, C<cache>, C<consumer_secret>, C<required_root>,
|
||||
and C<args> in the constructor. See the corresponding method
|
||||
descriptions below.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over 4
|
||||
|
||||
=item $csr->B<ua>($user_agent)
|
||||
|
||||
=item $csr->B<ua>
|
||||
|
||||
Getter/setter for the LWP::UserAgent (or subclass) instance which will
|
||||
be used when web donwloads are needed. It's highly recommended that
|
||||
you use LWPx::ParanoidAgent, or at least read its documentation so
|
||||
you're aware of why you should care.
|
||||
|
||||
=item $csr->B<cache>($cache)
|
||||
|
||||
=item $csr->B<cache>
|
||||
|
||||
Getter/setter for the optional (but recommended!) cache instance you
|
||||
want to use for storing fetched parts of pages. (identity server
|
||||
public keys, and the E<lt>headE<gt> section of user's HTML pages)
|
||||
|
||||
The $cache object can be anything that has a -E<gt>get($key) and
|
||||
-E<gt>set($key,$value) methods. See L<URI::Fetch> for more
|
||||
information. This cache object is just passed to L<URI::Fetch>
|
||||
directly.
|
||||
|
||||
=item $nos->B<consumer_secret>($scalar)
|
||||
|
||||
=item $nos->B<consumer_secret>($code)
|
||||
|
||||
=item $code = $nos->B<consumer_secret>; ($secret) = $code->($time);
|
||||
|
||||
The consumer secret is used to generate self-signed nonces for the
|
||||
return_to URL, to prevent spoofing.
|
||||
|
||||
In the simplest (and least secure) form, you configure a static secret
|
||||
value with a scalar. If you use this method and change the scalar
|
||||
value, any outstanding requests from the last 30 seconds or so will fail.
|
||||
|
||||
The more robust (but more complicated) form is to supply a subref that
|
||||
returns a secret based on the provided I<$time>, a unix timestamp.
|
||||
And if one doesn't exist for that time, create, store and return it
|
||||
(with appropriate locking so you never return different secrets for
|
||||
the same time.)
|
||||
|
||||
Your secret may not exceed 255 characters.
|
||||
|
||||
=item $csr->B<args>($ref)
|
||||
|
||||
=item $csr->B<args>($param)
|
||||
|
||||
=item $csr->B<args>
|
||||
|
||||
Can be used in 1 of 3 ways:
|
||||
|
||||
1. Setting the way which the Consumer instances obtains GET parameters:
|
||||
|
||||
$csr->args( $reference )
|
||||
|
||||
Where $reference is either a HASH ref, CODE ref, Apache $r,
|
||||
Apache::Request $apreq, or CGI.pm $cgi. If a CODE ref, the subref
|
||||
must return the value given one argument (the parameter to retrieve)
|
||||
|
||||
2. Get a paramater:
|
||||
|
||||
my $foo = $csr->args("foo");
|
||||
|
||||
When given an unblessed scalar, it retrieves the value. It croaks if
|
||||
you haven't defined a way to get at the parameters.
|
||||
|
||||
3. Get the getter:
|
||||
|
||||
my $code = $csr->args;
|
||||
|
||||
Without arguments, returns a subref that returns the value given a
|
||||
parameter name.
|
||||
|
||||
=item $nos->B<required_root>($url_prefix)
|
||||
|
||||
=item $url_prefix = $nos->B<required_root>
|
||||
|
||||
If provided, this is the required string that all return_to URLs must
|
||||
start with. If it doesn't match, it'll be considered invalid (spoofed
|
||||
from another site)
|
||||
|
||||
=item $csr->B<claimed_identity>($url)
|
||||
|
||||
Given a user-entered $url (which could be missing http://, or have
|
||||
extra whitespace, etc), returns either a Net::OpenID::ClaimedIdentity
|
||||
object, or undef on failure.
|
||||
|
||||
Note that this identity is NOT verified yet. It's only who the user
|
||||
claims they are, but they could be lying.
|
||||
|
||||
If this method returns undef, you can rely on the following errors
|
||||
codes (from $csr->B<errcode>) to decide what to present to the user:
|
||||
|
||||
=over 8
|
||||
|
||||
=item no_identity_server
|
||||
|
||||
=item empty_url
|
||||
|
||||
=item bogus_url
|
||||
|
||||
=item no_head_tag
|
||||
|
||||
=item url_fetch_err
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=item $csr->B<user_setup_url>( [ %opts ] )
|
||||
|
||||
Returns the URL the user must return to in order to login, setup trust,
|
||||
or do whatever the identity server needs them to do in order to make
|
||||
the identity assertion which they previously initiated by entering
|
||||
their claimed identity URL. Returns undef if this setup URL isn't
|
||||
required, in which case you should ask for the verified_identity.
|
||||
|
||||
The base URL this this function returns can be modified by using the
|
||||
following options in %opts:
|
||||
|
||||
=over
|
||||
|
||||
=item C<post_grant>
|
||||
|
||||
What you're asking the identity server to do with the user after they
|
||||
setup trust. Can be either C<return> or C<close> to return the user
|
||||
back to the return_to URL, or close the browser window with
|
||||
JavaScript. If you don't specify, the behavior is undefined (probably
|
||||
the user gets a dead-end page with a link back to the return_to URL).
|
||||
In any case, the identity server can do whatever it wants, so don't
|
||||
depend on this.
|
||||
|
||||
=back
|
||||
|
||||
=item $csr->B<user_cancel>
|
||||
|
||||
Returns true if the user declined to share their identity, false
|
||||
otherwise. (This function is literally one line: returns true if
|
||||
"openid.mode" eq "cancel")
|
||||
|
||||
It's then your job to restore your app to where it was prior to
|
||||
redirecting them off to the user_setup_url, using the other query
|
||||
parameters that you'd sent along in your return_to URL.
|
||||
|
||||
=item $csr->B<verified_identity>( [ %opts ] )
|
||||
|
||||
Returns a Net::OpenID::VerifiedIdentity object, or undef.
|
||||
Verification includes double-checking the reported identity URL
|
||||
declares the identity server, verifying the signature, etc.
|
||||
|
||||
The options in %opts may contain:
|
||||
|
||||
=over
|
||||
|
||||
=item C<required_root>
|
||||
|
||||
Sets the required_root just for this request. Values returns to its
|
||||
previous value afterwards.
|
||||
|
||||
=back
|
||||
|
||||
=item $csr->B<err>
|
||||
|
||||
Returns the last error, in form "errcode: errtext"
|
||||
|
||||
=item $csr->B<errcode>
|
||||
|
||||
Returns the last error code.
|
||||
|
||||
=item $csr->B<errtext>
|
||||
|
||||
Returns the last error text.
|
||||
|
||||
=item $csr->B<json_err>
|
||||
|
||||
Returns the last error code/text in JSON format.
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
This module is Copyright (c) 2005 Brad Fitzpatrick.
|
||||
All rights reserved.
|
||||
|
||||
You may distribute under the terms of either the GNU General Public
|
||||
License or the Artistic License, as specified in the Perl README file.
|
||||
If you need more liberal licensing terms, please contact the
|
||||
maintainer.
|
||||
|
||||
=head1 WARRANTY
|
||||
|
||||
This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
OpenID website: http://www.danga.com/openid/
|
||||
|
||||
L<Net::OpenID::ClaimedIdentity> -- part of this module
|
||||
|
||||
L<Net::OpenID::VerifiedIdentity> -- part of this module
|
||||
|
||||
L<Net::OpenID::Server> -- another module, for acting like an OpenID server
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
Brad Fitzpatrick <brad@danga.com>
|
||||
32
local/cgi-bin/bml/scheme/added.variables
Executable file
32
local/cgi-bin/bml/scheme/added.variables
Executable file
@@ -0,0 +1,32 @@
|
||||
ljrlook.nav.create
|
||||
ljrlook.nav.update
|
||||
ljrlook.nav.fullupdate
|
||||
ljrlook.nav.site
|
||||
ljrlook.nav.news
|
||||
ljrlook.nav.paidaccounts
|
||||
ljrlook.nav.edit
|
||||
ljrlook.nav.modify
|
||||
ljrlook.nav.editinfo
|
||||
ljrlook.nav.editfriends
|
||||
ljrlook.nav.editjournal
|
||||
ljrlook.nav.editpics
|
||||
ljrlook.nav.changepassword
|
||||
ljrlook.nav.communities.manage
|
||||
ljrlook.nav.frills
|
||||
ljrlook.nav.customize
|
||||
ljrlook.nav.createstyle
|
||||
ljrlook.nav.editstyle
|
||||
ljrlook.nav.needhelp
|
||||
ljrlook.nav.lostinfo
|
||||
ljrlook.nav.support.faq
|
||||
ljrlook.nav.support
|
||||
|
||||
ljrlook.nav.hello
|
||||
ljrlook.nav.yourjournal
|
||||
ljrlook.nav.recent
|
||||
ljrlook.nav.calendar
|
||||
ljrlook.nav.friends
|
||||
ljrlook.nav.userinfo
|
||||
ljrlook.nav.memories
|
||||
ljrlook.nav.logout
|
||||
ljrlook.nav.login
|
||||
267
local/cgi-bin/bml/scheme/bluewhite.look
Executable file
267
local/cgi-bin/bml/scheme/bluewhite.look
Executable file
@@ -0,0 +1,267 @@
|
||||
#
|
||||
# Welcome to GENERIC.LOOK for the WhiteBlue scheme
|
||||
#
|
||||
# by....
|
||||
# Brad Fitzpatrick
|
||||
# brad@danga.com
|
||||
#
|
||||
|
||||
######################### little stuff
|
||||
|
||||
_parent=>global.look
|
||||
|
||||
AL=>{P}<I><A HREF="%%DATA1%%">%%DATA2%%</A></I> <IMG SRC="/img/external_link.gif" WIDTH=16 HEIGHT=11 ALIGN=ABSMIDDLE>
|
||||
AWAYLINK=>{P}<I><A HREF="%%DATA1%%">%%DATA2%%</A></I> <IMG SRC="/img/external_link.gif" WIDTH=16 HEIGHT=11 ALIGN=ABSMIDDLE>
|
||||
|
||||
H1=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000"><B>%%DATA%%</B></FONT>
|
||||
H1/FOLLOW_CHOICES=>{D}<FONT FACE="Arial,Helvetica" COLOR="#CC0000"><B>%%DATA%%</B></FONT>
|
||||
HEAD1=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000"><B>%%DATA%%</B></FONT>
|
||||
|
||||
H2=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000" SIZE=-1><B>%%DATA%%</B></FONT>
|
||||
HEAD2=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000" SIZE=-1><B>%%DATA%%</B></FONT>
|
||||
|
||||
# Banner Header: search results banner, content desriptor, etc...
|
||||
BH=>{D}<P ALIGN=CENTER><FONT FACE="Arial,Helvetica" COLOR="#CC0000" SIZE=-1><B>%%DATA%%</B></FONT>
|
||||
|
||||
GRIN=><grin>
|
||||
HR=><P ALIGN="CENTER"><FONT COLOR=BLUE>*</FONT></P>
|
||||
|
||||
NEWLINE=>{D}<BR>
|
||||
P=>{D}<BR>%%DATA%%
|
||||
P/FOLLOW_P=>{D}<BR><IMG SRC="/img/dot.gif" WIDTH=1 VSPACE=6 HEIGHT=1><BR>%%DATA%%
|
||||
|
||||
STANDOUTO<=
|
||||
{D}<CENTER><FONT SIZE=1><BR></FONT><TABLE ALIGN=CENTER CELLPADDING=8 BORDER=1 BGCOLOR=#CCCCFF BORDERCOLORLIGHT=#DDDDFF
|
||||
BORDERCOLORDARK=#BBBBFF><TR><TD VALIGN=CENTER>
|
||||
%%DATA%%
|
||||
|
||||
</TD></TR></TABLE></CENTER>
|
||||
<=STANDOUTO
|
||||
|
||||
STANDOUT<=
|
||||
{D}<CENTER><FONT SIZE=1><BR></FONT>
|
||||
<table cellspacing=0 cellpadding=0 border=0 bgcolor="#ccccff">
|
||||
<tr>
|
||||
<td width=7 align=left valign=top>
|
||||
<img width=7 height=7 src="/img/corn_nw.gif" alt=""></td>
|
||||
<td height=7>
|
||||
<img height=7 src="/img/dot.gif" alt=""></td>
|
||||
<td width=7 valign=top align=right>
|
||||
<img height=7 src="/img/corn_ne.gif" alt=""></td>
|
||||
</tr><tr>
|
||||
<td width=7>
|
||||
<img width=7 height=1 src="/img/dot.gif" alt=""></td>
|
||||
<td valign=top>
|
||||
%%DATA%%
|
||||
|
||||
</td>
|
||||
<td width=7>
|
||||
<img width=7 height=1 src="/img/dot.gif" alt=""></td>
|
||||
</tr><tr>
|
||||
<td width=7 align=left valign=top>
|
||||
<img width=7 height=7 src="/img/corn_sw.gif" alt=""></td>
|
||||
<td height=7>
|
||||
<img height=7 src="/img/dot.gif" alt=""></td>
|
||||
<td width=7 valign=top align=right>
|
||||
<img height=7 src="/img/corn_se.gif" alt=""></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</CENTER>
|
||||
<=STANDOUT
|
||||
SOERROR=><div style='background-color:#f3f4fe; color:red; font-weight:bold; text-align:center'>%%data%%</div>
|
||||
EMAILEX=><div style='width: 50%; font-family: courier; background-color: #efefef; border: dotted #cdcdcd 2px; padding: 5px;'>%%data%%</div>
|
||||
|
||||
######################### choices stuff
|
||||
|
||||
CHOICE=>{P}<DT><A HREF="%%DATA2%%"><FONT FACE="Arial,Helvetica"><B>%%DATA1%%</B></FONT></A><DD><FONT SIZE="2">%%DATA3%%</FONT>
|
||||
|
||||
CHOICES<=
|
||||
{F}<P><DIV CLASS="choice"><TABLE WIDTH="100%" CELLPADDING="2" CELLSPACING="5">
|
||||
<TR>
|
||||
<TD VALIGN=TOP WIDTH="50%">
|
||||
<DL>
|
||||
%%ITEMS%%
|
||||
</DL>
|
||||
</TD>
|
||||
<TD VALIGN=TOP WIDTH="50%">
|
||||
<DL>
|
||||
%%ITEMSB%%
|
||||
</DL>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE></DIV>
|
||||
<=CHOICES
|
||||
|
||||
##################################################################################
|
||||
################################### MAIN PAGE ####################################
|
||||
##################################################################################
|
||||
|
||||
PAGE<=
|
||||
{Fps}<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML><?load_page_info?>
|
||||
<HEAD>
|
||||
<title><?_code {
|
||||
my $elhash = $_[2];
|
||||
return $elhash->{'WINDOWTITLE'} || $elhash->{'TITLE'};
|
||||
} _code?></title>
|
||||
%%head%%
|
||||
<?_code
|
||||
use strict;
|
||||
my $crumb_up;
|
||||
if(LJ::get_active_crumb() ne '')
|
||||
{
|
||||
my $parentcrumb = LJ::get_parent_crumb();
|
||||
$crumb_up = "<link rel='up' title='$parentcrumb->[0]' href='$parentcrumb->[1]' />";
|
||||
}
|
||||
return $crumb_up;
|
||||
_code?>
|
||||
</HEAD>
|
||||
|
||||
<BODY BGCOLOR=#FFFFFF TOPMARGIN="0" LEFTMARGIN="0" MARGINHEIGHT="0" MARGINWIDTH="0" LINK=#0000C0 VLINK=#600060 %%bodyopts%%>
|
||||
|
||||
<TABLE WIDTH=100% BORDER=0 CELLPADDING=0 CELLSPACING=0 BACKGROUND="/img/bluewhite/bluefade.jpg">
|
||||
<TR WIDTH=100%>
|
||||
<TD VALIGN=BOTTOM ALIGN=LEFT HEIGHT=100>
|
||||
|
||||
<TABLE BACKGROUND="" HEIGHT=95 WIDTH=100% BORDER=0>
|
||||
<TR>
|
||||
<TD WIDTH=3> </TD>
|
||||
<TD HEIGHT=53 WIDTH=406 VALIGN=BOTTOM>
|
||||
<?_code
|
||||
$is_home = (BML::get_uri() =~ m!^/(index\.bml)?!);
|
||||
if (0 && $is_home)
|
||||
{
|
||||
return '<IMG SRC="/img/bluewhite/title.gif" WIDTH=600 HEIGHT=53><!-- ';
|
||||
}
|
||||
return "";
|
||||
_code?>
|
||||
|
||||
<FONT SIZE=6 COLOR="#000a3f" FACE="Arial, Helvetica"><B>%%TITLE%%</B></FONT>
|
||||
|
||||
<?_code
|
||||
if (0 && $is_home)
|
||||
{
|
||||
return ' -->';
|
||||
}
|
||||
return "";
|
||||
_code?>
|
||||
|
||||
</TD>
|
||||
<TD VALIGN=TOP ALIGN=RIGHT>
|
||||
<?_code
|
||||
unless ($is_home) {
|
||||
return "<A HREF=\"/\"><IMG SRC=\"/img/bluewhite/home.gif\" WIDTH=35 HEIGHT=36 BORDER=0></A> ";
|
||||
}
|
||||
return "";
|
||||
_code?>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
||||
</TD></TR>
|
||||
<TR><TD bgcolor="#FFFFFF"><?breadcrumbs?></TD></TR>
|
||||
</TABLE>
|
||||
|
||||
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>
|
||||
<TR VALIGN=TOP>
|
||||
<TD WIDTH=155 BGCOLOR=#d7d9e8 NOWRAP><IMG SRC="/img/bluewhite/hline.gif" WIDTH=155 HEIGHT=25 ALT="">
|
||||
|
||||
<TABLE WIDTH=153 BORDER=0 CELLSPACING=0 CELLPADDING=0>
|
||||
<TR><TD>
|
||||
|
||||
<FONT FACE="Arial,Helvetica" SIZE=-1>
|
||||
<?_code
|
||||
|
||||
$ret = "";
|
||||
|
||||
sub dump_entry
|
||||
{
|
||||
my ($ret, $listref, $depth) = @_;
|
||||
|
||||
foreach my $mi (@$listref)
|
||||
{
|
||||
if ($depth==0) {
|
||||
$$ret .= "<P><IMG SRC=\"/img/bluewhite/bullet.gif\" WIDTH=10 HEIGHT=10 HSPACE=2 ALIGN=ABSMIDDLE>";
|
||||
} else {
|
||||
$$ret .= " " x ($depth*3+1);
|
||||
$$ret .= $mi->{'cont'} ? " " : "- ";
|
||||
}
|
||||
|
||||
my $name = $mi->{'name'};
|
||||
$name =~ s/ / /g;
|
||||
if (! defined $mi->{'uri'}) {
|
||||
if ($depth == 0) {
|
||||
$$ret .= "<B>$name</B><BR>";
|
||||
} else {
|
||||
$$ret .= "$name<BR>";
|
||||
}
|
||||
} elsif ($mi->{'match'} ?
|
||||
(BML::get_uri() =~ /$mi->{'match'}/) :
|
||||
(BML::get_uri() eq $mi->{'uri'})
|
||||
){
|
||||
$$ret .= "<B><SPAN style=\"background-color: #FFFFFF\"><FONT COLOR=#0000D0>$name</FONT></SPAN></B><BR>";
|
||||
} else {
|
||||
$$ret .= "<A HREF=\"$mi->{'uri'}\">$name</A><BR>";
|
||||
}
|
||||
|
||||
if ($mi->{'children'} &&
|
||||
($mi->{'recursematch'} ? BML::get_uri() =~ /$mi->{'recursematch'}/ : 1)) {
|
||||
&dump_entry($ret, $mi->{'children'}, $depth+1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&dump_entry(\$ret, \@sidebar, 0);
|
||||
|
||||
return $ret;
|
||||
|
||||
_code?>
|
||||
</FONT>
|
||||
|
||||
</TD></TR></TABLE>
|
||||
</TD>
|
||||
<TD ALIGN=LEFT BACKGROUND="/img/bluewhite/vline.gif" WIDTH=25 NOWRAP>
|
||||
<IMG SRC="/img/bluewhite/linetop.gif" WIDTH=25 HEIGHT=25 ALT=""><BR>
|
||||
<IMG SRC="/img/bluewhite/vline.gif" WIDTH=25 HEIGHT=800 ALT="">
|
||||
</TD>
|
||||
<TD>
|
||||
|
||||
<IMG SRC="/img/dot.gif" WIDTH=1 HEIGHT=3><BR>
|
||||
%%BODY%%
|
||||
|
||||
</TD>
|
||||
<TD WIDTH=20> </TD>
|
||||
</TR>
|
||||
|
||||
<!-- table closure row -->
|
||||
<TR>
|
||||
<TD WIDTH=155 NOWRAP><IMG SRC="/img/bluewhite/sidebarfade.gif" WIDTH=155 HEIGHT=25 ALT=""></TD>
|
||||
<TD WIDTH=25 NOWRAP><IMG SRC="/img/bluewhite/sidebarfade_line.gif" WIDTH=25 HEIGHT=25 ALT=""></TD></TD>
|
||||
<TD>
|
||||
|
||||
</TD>
|
||||
<TD WIDTH=20> </TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
<!-- /table closure row -->
|
||||
|
||||
<!--<TABLE WIDTH=100%>
|
||||
<TR>
|
||||
<TD ALIGN=RIGHT>
|
||||
<FONT FACE="Arial, Helvetica" SIZE="-2">
|
||||
<A HREF="/privacy.bml">Privacy Policy</A> -
|
||||
<A HREF="/coppa.bml">COPPA</A><BR>
|
||||
<A HREF="/disclaimer.bml">Legal Disclaimer</A> -
|
||||
<A HREF="/sitemap.bml">Site Map</A><BR>
|
||||
</FONT>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
-->
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
<=PAGE
|
||||
|
||||
622
local/cgi-bin/bml/scheme/dystopia.look
Executable file
622
local/cgi-bin/bml/scheme/dystopia.look
Executable file
@@ -0,0 +1,622 @@
|
||||
# LiveJournal.com-specific library
|
||||
#
|
||||
# This file is NOT licensed under the GPL. As with everything in the
|
||||
# "ljcom" CVS repository, this file is the property of Danga
|
||||
# Interactive and is made available to the public only as a reference
|
||||
# as to the best way to modify/extend the base LiveJournal server code
|
||||
# (which is licensed under the GPL).
|
||||
#
|
||||
# Feel free to read and learn from things in "ljcom", but don't use
|
||||
# our schemes because we don't want your site looking like
|
||||
# LiveJournal.com (our logo and site scheme are our identity and we
|
||||
# don't want to confuse users)
|
||||
#
|
||||
# Instead, use/modify one of the schemes in the "livejournal" repository.
|
||||
# (Ideally you'd make your own entirely)
|
||||
#
|
||||
|
||||
_parent=>global.look
|
||||
|
||||
help=>{Ds}<a href="%%data%%"><img src="<?imgprefix?>/help.gif" alt="(<?_ml Help _ml?>)" title="(<?_ml Help _ml?>)" width='14' height='14' hspace='2' align='absmiddle' border='0'></a>
|
||||
|
||||
h1=>{D}<p><span class="heading">%%data%%</span>
|
||||
h1/follow_choices=>{D}<span class="heading">%%data%%</span>
|
||||
|
||||
h2=>{D}<p><span class="heading2">%%data%%</span>
|
||||
|
||||
# Banner Header: search results banner, content desriptor, etc...
|
||||
bh=>{D}<p align="center"><font face="Arial,Helvetica" color="#cc0000" size="-1"><b>%%data%%</b></font>
|
||||
|
||||
grin=>{S}<grin>
|
||||
hr=>{S}<p align="center"><font color=#660066>*</font></p>
|
||||
|
||||
newline=>{S}<br />
|
||||
p=>{DRp}<br />%%data%%
|
||||
p/follow_p=>{DRps}<br /><img src="<?imgprefix?>/dot.gif" width="1" vspace="6" height="1"><br />%%data%%
|
||||
|
||||
emcolor=>{S}#a7c7e8
|
||||
emcolorlite=>{S}#d9e9f9
|
||||
altcolor1=>{S}#d9e9f9
|
||||
altcolor2=>{S}#a7c7e8
|
||||
|
||||
de=>{DRp}<span style="color:#909090;">%%data%%</span>
|
||||
|
||||
standout<=
|
||||
{DRps}<center><font size="1"><br /></font>
|
||||
<table cellspacing="0" cellpadding="0" border="0" bgcolor="<?emcolor?>">
|
||||
<tr align="left">
|
||||
<td width="7" align="left" valign="top">
|
||||
<img width="7" height="7" src="<?imgprefix?>/dys/corn_nw.gif" alt="/"></td>
|
||||
<td height="7">
|
||||
<img height="7" src="<?imgprefix?>/dot.gif" alt=""></td>
|
||||
<td width="7" valign="top" align="right">
|
||||
<img height="7" src="<?imgprefix?>/dys/corn_ne.gif" alt="\"></td>
|
||||
</tr><tr align="left">
|
||||
<td width="7">
|
||||
<img width="7" height="1" src="<?imgprefix?>/dot.gif" alt=""></td>
|
||||
<td valign="top">
|
||||
%%data%%
|
||||
|
||||
</td>
|
||||
<td width="7">
|
||||
<img width="7" height="1" src="<?imgprefix?>/dot.gif" alt=""></td>
|
||||
</tr><tr>
|
||||
<td width="7" align=left valign=top>
|
||||
<img width="7" height="7" src="<?imgprefix?>/dys/corn_sw.gif" alt="\"></td>
|
||||
<td height="7">
|
||||
<img height="7" src="<?imgprefix?>/dot.gif" alt=""></td>
|
||||
<td width="7" valign=top align=right>
|
||||
<img height="7" src="<?imgprefix?>/dys/corn_se.gif" alt="/"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</center>
|
||||
<=standout
|
||||
|
||||
warningbar<=
|
||||
{DRps}<div class="warningbar" style="background-image: URL('<?imgprefix?>/message-warning.gif');">
|
||||
%%data%%
|
||||
</div>
|
||||
<=warningbar
|
||||
|
||||
errorbar<=
|
||||
{DRps}<div class="errorbar" style="background-image: URL('<?imgprefix?>/message-error.gif');">
|
||||
%%data%%
|
||||
</div>
|
||||
<=errorbar
|
||||
|
||||
soerror=><div style='background-color:#d0eef9; color:red; font-weight:bold; text-align:center'>%%data%%</div>
|
||||
emailex=><div style='width: 50%; font-family: courier; background-color: #efefef; border: dotted #cdcdcd 2px; padding: 5px;'>%%data%%</div>
|
||||
|
||||
######################### choices stuff
|
||||
|
||||
choice=>{PRps}<dt><img src="<?imgprefix?>/dys/b_purp.gif" align="absmiddle" width="8" height="8"> <a href="%%data2%%"><font face="Arial,Helvetica"><b>%%data1%%</b></font></a><dd><font size="2">%%data3%%</font>
|
||||
|
||||
choices<=
|
||||
{FRp}<p><div class="choice"><table width="100%" cellpadding="2" cellspacing="5">
|
||||
<tr>
|
||||
<td valign="top" width="50%">
|
||||
<dl>
|
||||
%%items%%
|
||||
</dl>
|
||||
</td>
|
||||
<td valign="top" width="50%">
|
||||
<dl>
|
||||
%%itemsb%%
|
||||
</dl>
|
||||
</td>
|
||||
</tr>
|
||||
</table></div>
|
||||
<=choices
|
||||
|
||||
ENTRYFORMCSS<=
|
||||
{Ss}
|
||||
<style type="text/css">
|
||||
#EntryForm #MetaInfo {
|
||||
width: 100%;
|
||||
}
|
||||
#EntryForm th {
|
||||
font-size: .85em;
|
||||
}
|
||||
#EntryForm #SubmitBar {
|
||||
background-color: #dfdfdf;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
border: 1px outset #000;
|
||||
margin-left: auto; margin-right: auto;
|
||||
}
|
||||
#MetaInfo tr {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
#metainfo th {
|
||||
text-align: left;
|
||||
}
|
||||
#mood_preview {
|
||||
display: none;
|
||||
}
|
||||
#datetime_box input, #datetime_box select {
|
||||
margin-right: 2px;
|
||||
}
|
||||
#EntryForm legend {
|
||||
font-weight: bold;
|
||||
}
|
||||
#EntryForm #Options {
|
||||
margin-left: 0; margin-right: 0; padding: 0;
|
||||
background-color: #dfdfdf;
|
||||
border: 1px outset #000;
|
||||
}
|
||||
#EntryForm #Options th {
|
||||
text-align: left;
|
||||
}
|
||||
#EntryForm #infobox {
|
||||
text-align: center;
|
||||
}
|
||||
#EntryForm #infobox table {
|
||||
background-color: #dfdfdf;
|
||||
border: 2px solid <?emcolor?>;
|
||||
}
|
||||
#EntryForm textarea {
|
||||
border: 1px inset #000;
|
||||
padding: 2px;
|
||||
}
|
||||
#EntryForm #Security option {
|
||||
padding-left: 18px;
|
||||
}
|
||||
#EntryForm #security_public {
|
||||
background-image: url("<?imgprefix?>/userinfo.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #security_private {
|
||||
background-image: url("<?imgprefix?>/icon_private.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #security_friends, #EntryForm #security_custom {
|
||||
background-image: url("<?imgprefix?>/icon_protected.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #UserpicPreviewImage {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
#EntryForm {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<=ENTRYFORMCSS
|
||||
|
||||
##################################################################################
|
||||
################################### MAIN PAGE ####################################
|
||||
##################################################################################
|
||||
|
||||
PAGE<=
|
||||
{Fps}<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<html>
|
||||
<?_code
|
||||
{
|
||||
my $remote = LJ::get_remote(); # will be requested later and returned from cache
|
||||
return LJ::LJcom::expresslane_html_comment($remote, $_[0]->{r});
|
||||
}
|
||||
_code?>
|
||||
<head>
|
||||
<link rel="SHORTCUT ICON" href="<?siteroot?>/favicon.ico">
|
||||
<link rel="home" title="Home" href="/" />
|
||||
<link rel="contents" title="Site Map" href="/site/" />
|
||||
<link rel="help" title="Technical Support" href="/support/" />
|
||||
<title><?_code {
|
||||
my $elhash = $_[2];
|
||||
return $elhash->{'WINDOWTITLE'} || $elhash->{'TITLE'};
|
||||
} _code?></title>
|
||||
<?metactype?>
|
||||
<style type="text/css">
|
||||
<!--
|
||||
p, td { font-size: 12px; font-family: Verdana, Arial, Helvetica, sans-serif; }
|
||||
li { font-size: 12px; font-family: Verdana, Arial, Helvetica, sans-serif; }
|
||||
body { font-size: 12px; font-family: Verdana, Arial, Helvetica, sans-serif; margin: 0px; }
|
||||
.navtext { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #FF9900; font-weight: bold}
|
||||
.navlinks { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #FFFFFF; text-decoration: underline}
|
||||
a:link { font-family: Verdana, Arial, Helvetica, sans-serif; color: #000066; }
|
||||
a:visited { font-family: Verdana, Arial, Helvetica, sans-serif; color: #000066; }
|
||||
a:active { font-family: Verdana, Arial, Helvetica, sans-serif; color: #006699; }
|
||||
.wtext { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; font-weight: bold; color: #FFFFFF}
|
||||
.login { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px}
|
||||
.wtextunbld { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #FFFFFF }
|
||||
.copy { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #000000}
|
||||
.heading { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 13px; color: #660066; font-weight: bold}
|
||||
.heading2 { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 13px; color: #660066; font-style: italic }
|
||||
.talk-comment { margin-top: 1em; }
|
||||
.lesstop { margin-top: 2px; }
|
||||
.formitem { color: #343434; font-size: 1em; }
|
||||
.formnumber { font-weight: bold; margin-top: 1.8em; font-size: .9em; }
|
||||
.formitemName { font-weight: bold; font-size: .9em; margin-top: 1.8em; }
|
||||
.formitemDesc { margin-top: .4em; margin-bottom: .4em; color: #505050; }
|
||||
.formitemNote { color: #da6320; font-size: .9em; margin-top: .4em; margin-bottom: .4em; }
|
||||
.formitemFlag { color: #CE0000; font-size: .9em; margin-top: .4em; margin-bottom: .4em; }
|
||||
.borderedtable { border: solid 1px black; }
|
||||
.borderedtable th { background-color: #dddddd; border-bottom: solid 1px black; padding-left: 10px; padding-right: 10px; white-space: nowrap; font-size: 0.8em; }
|
||||
|
||||
#Comments q { padding-left: 2.5em; font-style: italic; }
|
||||
|
||||
.errorbar {
|
||||
color: #000;
|
||||
font: 12px Verdana, Arial, Sans-Serif;
|
||||
background-color: #FFEEEE;
|
||||
background-repeat: repeat-x;
|
||||
border: 1px solid #FF9999;
|
||||
padding: 8px;
|
||||
margin-top: auto; margin-bottom: auto;
|
||||
margin-left: auto; margin-right: auto;
|
||||
width: auto;
|
||||
text-align: left;
|
||||
}
|
||||
.warningbar {
|
||||
color: #000;
|
||||
font: 12px Verdana, Arial, Sans-Serif;
|
||||
background-color: #FFFFDD;
|
||||
background-repeat: repeat-x;
|
||||
border: 1px solid #FFCC33;
|
||||
padding: 8px;
|
||||
margin-top: auto; margin-bottom: auto;
|
||||
margin-left: auto; margin-right: auto;
|
||||
width: auto;
|
||||
text-align: left;
|
||||
}
|
||||
-->
|
||||
</style>
|
||||
|
||||
<script language="JavaScript">
|
||||
window.onerror = null; // damn javascript.
|
||||
</script>
|
||||
<?_code return (! LJ::get_remote() &&
|
||||
! $LJ::IS_SSL &&
|
||||
! $LJ::REQ_HEAD_HAS{'chalresp_js'}++) ?
|
||||
$LJ::COMMON_CODE{'chalresp_js'} : "";
|
||||
_code?>
|
||||
<?_code
|
||||
use strict;
|
||||
my $crumb_up;
|
||||
if(LJ::get_active_crumb() ne '')
|
||||
{
|
||||
my $parentcrumb = LJ::get_parent_crumb();
|
||||
$crumb_up = "<link rel='up' title='$parentcrumb->[0]' href='$parentcrumb->[1]' />";
|
||||
}
|
||||
return $crumb_up;
|
||||
_code?>
|
||||
%%head%%
|
||||
</head>
|
||||
<body bgcolor="#FFFFFF" background="<?imgprefix?>/dys/bg.gif" leftmargin="0" topmargin="0" marginwidth="0" marginheight="0" text="#000000" link="#660066" vlink="#000066" alink="#CC6600" %%bodyopts%%>
|
||||
<basefont face="Verdana,Arial,Helvetica">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr align="left" valign="top">
|
||||
<td colspan='2'>
|
||||
<table width='100%' border="0" cellspacing="0" cellpadding="0" background="<?imgprefix?>/dys/bg_top.gif">
|
||||
<tr>
|
||||
<td><a href="<?siteroot?>/"><img src="<?imgprefix?>/dys/logo1.gif" width="122" height="51" border="0"></a></td>
|
||||
<td width="163" align="left" valign="top"><a href="<?siteroot?>/"><img src="<?imgprefix?>/dys/logo2.gif" width="170" height="51" border="0"></a></td>
|
||||
<td background="<?imgprefix?>/dys/bg_top.gif" align="left" valign="top" width="244"> </td>
|
||||
<td background="<?imgprefix?>/dys/bg_top.gif" align="left" valign="top" width="100%"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- logo, then search & logged in links bar stack on top of each other -->
|
||||
|
||||
<tr align="left" valign="top">
|
||||
<td width="<?_ml dystopia.nav.width _ml?>" height="49"
|
||||
><?_code
|
||||
unless ($BML::COOKIE{'langpref'}) {
|
||||
return '<img src="<?imgprefix?>/dys/logo3-lang.gif" width="122" height="52" border="0" ismap="ismap" usemap="#setlang"><map name="setlang"><area href="/manage/siteopts.bml" shape="rect" coords="50,25,122,50"></map>';
|
||||
} else {
|
||||
return '<img src="<?imgprefix?>/dys/logo3.gif" width="122" height="52" border="0">';
|
||||
}
|
||||
_code?></td>
|
||||
<td height="49">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<colgroup span="3">
|
||||
<col width="19%" />
|
||||
<col width="34%" />
|
||||
<col width="47%" />
|
||||
</colgroup>
|
||||
|
||||
<!-- search bar -->
|
||||
<tr valign="top">
|
||||
<td height="24" width="19%" align="left"> </td>
|
||||
<form action="/multisearch.bml">
|
||||
<td height="24" align="right" valign="middle" colspan="2" nowrap="nowrap">
|
||||
<font face="verdana, arial, sans-serif" color=#333333 size=-2>
|
||||
<span class="wtextunbld"><label for='searchlj'><?_ml dystopia.searchlj _ml?></label> </span>
|
||||
<?_code
|
||||
#BML:cache
|
||||
my $ret;
|
||||
my ($cur, $val) = ("user", "");
|
||||
my ($uri, $args) = (BML::get_uri(), BML::get_query_string());
|
||||
if ($uri eq '/interests.bml' && $args =~ /int=(.+?)(&|$)/) {
|
||||
$cur = "int";
|
||||
$val = LJ::durl($1);
|
||||
}
|
||||
if ($FORM{'s_loc'}) {
|
||||
$cur = "region";
|
||||
}
|
||||
|
||||
my $hval = LJ::ehtml($val);
|
||||
$ret .= "<input id='searchlj' type='text' name='q' size='15' class='login' value='$hval'> ";
|
||||
$ret .= '<select style="FONT-SIZE: 10px; FONT-FAMILY: verdana, arial, helvetica" name=type>';
|
||||
foreach my $it (
|
||||
["user", BML::ml("Username")],
|
||||
["email", BML::ml("Email")],
|
||||
["region", BML::ml("dystopia.search.region")],
|
||||
["int", BML::ml("dystopia.search.int")],
|
||||
["aolim", BML::ml("dystopia.search.aolim")],
|
||||
["icq", BML::ml("dystopia.search.icq")],
|
||||
["yahoo", BML::ml("dystopia.search.yahoo")],
|
||||
["msn", BML::ml("dystopia.search.msn")],
|
||||
["jabber", BML::ml("dystopia.search.jabber")],
|
||||
) {
|
||||
next if ($it->[0] eq "region" && $LJ::DISABLED{'directory'});
|
||||
my $sel = $cur eq $it->[0] ? " SELECTED" : "";
|
||||
$ret .= "<option value=$it->[0]$sel>$it->[1]";
|
||||
}
|
||||
return BML::noparse($ret);
|
||||
_code?>
|
||||
</select>
|
||||
<img src="<?imgprefix?>/dot.gif" width="1" height="5">
|
||||
<input type=submit value="<?_ml btn.search _ml?>" class="login">
|
||||
</font>
|
||||
</td></form>
|
||||
</tr>
|
||||
<!-- /search livejournal bar -->
|
||||
|
||||
|
||||
<!-- logged in bar -->
|
||||
<tr>
|
||||
<td height="27" class="wtext" width="53%" colspan="2" nowrap="nowrap" valign="middle">
|
||||
<?_code
|
||||
#BML:cache
|
||||
if (LJ::get_remote()) {
|
||||
return BML::noparse(BML::ml("dystopia.hello_loggedin", { 'username' => LJ::get_remote()->{'user'} }));
|
||||
} else {
|
||||
return BML::noparse(BML::ml("dystopia.hello_anonymous"))
|
||||
}
|
||||
_code?></td>
|
||||
<td height="27" width="47%" nowrap="nowrap" align="right" valign="middle">
|
||||
<a href="/"><span class="navlinks"><?_ml dystopia.nav.home _ml?></span></a> <span class="navtext">|</span>
|
||||
<a href="/site/"><span class="navlinks"><?_ml dystopia.nav.sitemap _ml?></span></a> <span class="navtext">|</span>
|
||||
<a href="/news.bml"><span class="navlinks"><?_ml dystopia.nav.news _ml?></span></a> <span class="navtext">|</span>
|
||||
<a href="/manage/siteopts.bml"><span class="navlinks"><?_ml dystopia.nav.siteopts _ml?></span></a> <span class="navtext">|</span>
|
||||
<a href="/support/"><span class="navlinks"><?_ml Help _ml?></span></a>
|
||||
<?_code
|
||||
#BML:cache
|
||||
my $r = LJ::get_remote();
|
||||
if ($r) {
|
||||
return BML::noparse(' <span class="navtext">|</span> <a href="/logout.bml?user=' . "$r->{'user'}&sessid=$r->{'_session'}->{'sessid'}" . '"><span class="navlinks">' . BML::ml("dystopia.nav.logout") . '</span></a>');
|
||||
}
|
||||
return;
|
||||
_code?>
|
||||
<img src="<?imgprefix?>/dys/5x5.gif" width="10" height="5"></td>
|
||||
</tr>
|
||||
<!-- /logged in bar -->
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- /logo, search, logged in bar -->
|
||||
|
||||
<!-- left sidebar and body -->
|
||||
|
||||
<tr align="left" valign="top">
|
||||
<td bgcolor="#336699" width="<?_ml dystopia.nav.width _ml?>" height="813">
|
||||
<table width="<?_ml dystopia.nav.width _ml?>" border="0" cellspacing="0" cellpadding="10">
|
||||
<?_code
|
||||
#BML:cache
|
||||
my @nav;
|
||||
my $remote = LJ::get_remote();
|
||||
if ($remote) {
|
||||
push @nav, { 'name' => BML::ml('dystopia.navhead.journal'),
|
||||
'links' => [ { 'url' => '/update.bml',
|
||||
'text' => BML::ml('dystopia.nav.updatejournal'), },
|
||||
{ 'url' => "/users/$remote->{'user'}/",
|
||||
'text' => BML::ml('dystopia.nav.journalrecent'), },
|
||||
{ 'url' => "/users/$remote->{'user'}/calendar",
|
||||
'text' => BML::ml('dystopia.nav.journalcalendar'), },
|
||||
{ 'url' => "/users/$remote->{'user'}/friends",
|
||||
'text' => BML::ml('dystopia.nav.journalfriends'),
|
||||
'extra' => '/friends/filter.bml', },
|
||||
{ 'url' => "/userinfo.bml?user=$remote->{'user'}",
|
||||
'text' => BML::ml('dystopia.nav.journalinfo'),
|
||||
'extra' => "/userinfo.bml?user=$remote->{'user'}&mode=full",
|
||||
},
|
||||
{ 'url' => "/tools/memories.bml?user=$remote->{'user'}",
|
||||
'text' => BML::ml('dystopia.nav.memories'), },
|
||||
{ 'url' => "/editjournal.bml",
|
||||
'text' => BML::ml('dystopia.nav.editentries'), },
|
||||
],
|
||||
};
|
||||
push @nav, { 'name' => BML::ml('dystopia.navhead.settings'),
|
||||
'links' => [ { 'url' => '/manage/',
|
||||
'text' => BML::ml('dystopia.nav.manage') },
|
||||
{ 'url' => '/editinfo.bml',
|
||||
'text' => BML::ml('dystopia.nav.personalinfo') },
|
||||
{ 'url' => "/friends/edit.bml",
|
||||
'text' => BML::ml('dystopia.nav.editfriends'), },
|
||||
{ 'url' => "/editpics.bml",
|
||||
'text' => BML::ml('dystopia.nav.editpics'), },
|
||||
{ 'url' => "/changepassword.bml",
|
||||
'text' => BML::ml('dystopia.nav.editpassword'), },
|
||||
{ 'url' => "/modify.bml",
|
||||
'text' => BML::ml('dystopia.nav.modifyjournal'), },
|
||||
{ 'url' => "/styles/edit.bml",
|
||||
'text' => BML::ml('dystopia.nav.editstyle'), },
|
||||
],
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
push @nav, { 'name' => BML::ml('dystopia.navhead.welcome'),
|
||||
'links' => [
|
||||
{ 'url' => '/login.bml',
|
||||
'text' => BML::ml('dystopia.nav.login'), },
|
||||
{ 'url' => '/create.bml',
|
||||
'text' => BML::ml('dystopia.nav.createjournal'), },
|
||||
{ 'url' => "/update.bml",
|
||||
'text' => BML::ml('dystopia.nav.updatejournal'), },
|
||||
],
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
push @nav, { 'name' => BML::ml('dystopia.navhead.findusers'),
|
||||
'links' => [
|
||||
{ 'url' => '/random.bml',
|
||||
'text' => BML::ml('dystopia.nav.findrandom'), },
|
||||
$LJ::DISABLED{'directory'} ? () :
|
||||
(
|
||||
{ 'url' => '/directory.bml',
|
||||
'text' => BML::ml('dystopia.nav.findregion'), }
|
||||
),
|
||||
{ 'url' => '/community/',
|
||||
'text' => BML::ml('dystopia.nav.findcomm'), },
|
||||
{ 'url' => '/interests.bml',
|
||||
'text' => BML::ml('dystopia.nav.findint'), },
|
||||
$LJ::DISABLED{'directory'} ? () :
|
||||
(
|
||||
{ 'url' => '/directorysearch.bml',
|
||||
'text' => BML::ml('dystopia.nav.finddir'), }
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
push @nav, { 'name' => 'LiveJournal',
|
||||
'links' => [
|
||||
{ 'url' => '/download/',
|
||||
'text' => BML::ml('dystopia.nav.download'), },
|
||||
{ 'url' => '/paidaccounts/',
|
||||
'text' => BML::ml('dystopia.nav.paidaccts'), },
|
||||
{ 'url' => '/pay/',
|
||||
'text' => BML::ml('dystopia.nav.paymentarea'), },
|
||||
],
|
||||
};
|
||||
|
||||
push @nav, { 'name' => BML::ml('dystopia.navhead.help'),
|
||||
'links' => [ { 'url' => '/support/faq.bml',
|
||||
'text' => BML::ml('dystopia.nav.faq'), },
|
||||
{ 'url' => '/support/',
|
||||
'text' => BML::ml('dystopia.nav.support'), },
|
||||
{ 'url' => '/lostinfo.bml',
|
||||
'text' => BML::ml('dystopia.nav.lostinfo'), },
|
||||
{ 'url' => '/developer/',
|
||||
'text' => BML::ml('dystopia.nav.developer'), },
|
||||
{ 'url' => '/press/staff.bml',
|
||||
'text' => BML::ml('dystopia.nav.contact'), },
|
||||
],
|
||||
};
|
||||
|
||||
push @nav, { 'name' => BML::ml('dystopia.navhead.legal'),
|
||||
'links' => [ { 'url' => '/tos.html',
|
||||
'text' => BML::ml('dystopia.nav.legaltos'), },
|
||||
{ 'url' => '/privacy.bml',
|
||||
'text' => BML::ml('dystopia.nav.legalprivacy'), },
|
||||
# { 'url' => '/legal/dmca.bml',
|
||||
# 'text' => BML::ml('dystopia.nav.legaldmca'), },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
my $ret = $LJ::DYS_LEFT_TOP;
|
||||
foreach my $sec (@nav) {
|
||||
$ret .= "<tr align=left valign=top><td><p><span class=navtext>$sec->{'name'}</span><br />";
|
||||
foreach my $l (@{$sec->{'links'}}) {
|
||||
$ret .= "<a href=\"$l->{'url'}\"><span class=navlinks>$l->{'text'}</span></a>";
|
||||
if ($l->{'extra'}) {
|
||||
$ret .= " <a href=\"$l->{'extra'}\"><span class=navlinks>...</span></a>";
|
||||
}
|
||||
$ret .= "<br />";
|
||||
}
|
||||
$ret .= "</td></tr>";
|
||||
}
|
||||
return BML::noparse($ret);
|
||||
|
||||
_code?>
|
||||
<tr align="left" valign="top">
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr align="left" valign="top">
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr align="left" valign="top">
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
</td>
|
||||
<td height="813" bgcolor="#FFFFFF">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
|
||||
<!-- login bar -->
|
||||
<?_code
|
||||
#BML:cache
|
||||
#WITHPORTAL: unless ($remote || BML::get_uri() eq "/") {
|
||||
my $remote = LJ::get_remote();
|
||||
unless ($remote || BML::get_uri eq '/login.bml') {
|
||||
my $button;
|
||||
my $logincaption = BML::ml('dystopia.btn.login');
|
||||
if ($logincaption eq 'LOGIN') {
|
||||
if (! $LJ::IS_SSL) {
|
||||
$button = "<input type=image onclick='return sendForm()' src='$LJ::IMGPREFIX/dys/login_but.gif' width='48' height='15' border='0'>";
|
||||
} else {
|
||||
$button = "<input type=image src='$LJ::IMGPREFIX/dys/login_but.gif' width='48' height='15' border='0'>";
|
||||
}
|
||||
} else {
|
||||
if (! $LJ::IS_SSL) {
|
||||
$button = "<input type='submit' onclick='return sendForm()' value='$ML{'dystopia.btn.login'}' style='margin-top: 0px; margin-bottom: 1px; font-weight: bold; height: 19px; border: 1px solid #ffffff; background: #336699 none; color: #ffffff; padding-left: 0px; padding-right: 0px'></td>";
|
||||
} else {
|
||||
$button = "<input type='submit' value='$ML{'dystopia.btn.login'}' style='margin-top: 0px; margin-bottom: 1px; font-weight: bold; height: 19px; border: 1px solid #ffffff; background: #336699 none; color: #ffffff; padding-left: 0px; padding-right: 0px'></td>";
|
||||
}
|
||||
}
|
||||
|
||||
my $chal = LJ::challenge_generate(300);
|
||||
return <<"END_LOGIN_BAR";
|
||||
<form action="/login.bml" method="post" id='login'>
|
||||
<input type="hidden" name="mode" value="login" />
|
||||
<input type='hidden' name='chal' id='login_chal' value='$chal' />
|
||||
<input type='hidden' name='response' id='login_response' value='' />
|
||||
<tr>
|
||||
<td align="right" valign="top" bgcolor="#FFFFFF">
|
||||
<table border='0' cellspacing='0' cellpadding='0' width='200' align='right'>
|
||||
<tr>
|
||||
<td align="left" valign="bottom" bgcolor="#660066"><img src="<?imgprefix?>/dys/lg_crnrgif.gif" width="14" height="23"></td>
|
||||
<td align="right" valign="middle" bgcolor="#660066" class="wtextunbld" nowrap="nowrap"> $ML{'Username'}: </td>
|
||||
<td align="center" valign="top" bgcolor="#660066" class="wtext" nowrap="nowrap"><input type="text" name="user" size="15" maxlength="15" class="login" style="<?loginboxstyle?>"></td>
|
||||
<td align="right" valign="middle" bgcolor="#660066" class="wtextunbld" nowrap="nowrap"> $ML{'Password'}: </td>
|
||||
<td align="center" valign="top" bgcolor="#660066" class="wtext" nowrap="nowrap"><input type="password" name="password" size="10" id='xc_password' class="login"></td>
|
||||
<td align="center" valign="middle" bgcolor="#660066" nowrap="nowrap"> $button</tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
</form>
|
||||
END_LOGIN_BAR
|
||||
}
|
||||
return;
|
||||
|
||||
_code?>
|
||||
<!-- /login bar -->
|
||||
|
||||
<tr align="left" valign="top" bgcolor="#ffffff">
|
||||
<td height="585" colspan="7">
|
||||
|
||||
<!-- body area -->
|
||||
<table border="0" cellspacing="0" cellpadding="10" width="100%"><tr><td>
|
||||
|
||||
<?breadcrumbs?>
|
||||
|
||||
%%pretitle%%
|
||||
|
||||
<font size="+2" face="Verdana, Arial, Helvetica" color=#000066>%%title%%</font><p>
|
||||
|
||||
%%body%%
|
||||
|
||||
</td></tr></table>
|
||||
<!-- /body area -->
|
||||
</td></tr></table>
|
||||
</td></tr></table>
|
||||
</body></html>
|
||||
<=PAGE
|
||||
|
||||
379
local/cgi-bin/bml/scheme/global.look
Executable file
379
local/cgi-bin/bml/scheme/global.look
Executable file
@@ -0,0 +1,379 @@
|
||||
_parent=>../../lj-bml-blocks.pl
|
||||
|
||||
loginboxstyle=>{Ss}background: url(<?imgprefix?>/userinfo.gif) no-repeat; background-color: #fff; background-position: 0px 1px; padding-left: 18px; color: #00C; font-weight: bold;
|
||||
commloginboxstyle=>{Ss}background: url(<?imgprefix?>/community.gif) no-repeat; background-color: #fff; background-position: 0px 2px; padding-left: 19px; color: #00C; font-weight: bold;
|
||||
|
||||
SECURITYPRIVATE=>{Ss}<img src="<?imgprefix?>/icon_private.gif" width=16 height=16 align=absmiddle>
|
||||
SECURITYPROTECTED=>{Ss}<img src="<?imgprefix?>/icon_protected.gif" width=14 height=15 align=absmiddle>
|
||||
LJUSER=>{DRs}<span class='ljuser' style='white-space:nowrap;'><a href='/userinfo.bml?user=%%data%%'><img src='<?imgprefix?>/userinfo.gif' alt='userinfo' width='17' height='17' style='vertical-align:bottom;border:0;' /></a><a href='/users/%%data%%/'><b>%%data%%</b></a></span>
|
||||
LJCOMM=>{DRs}<span class='ljuser' style='white-space:nowrap;'><a href='/userinfo.bml?user=%%data%%'><img src='<?imgprefix?>/community.gif' alt='userinfo' width='16' height='16' style='vertical-align:bottom;border:0;' /></a><a href='/community/%%data%%/'><b>%%data%%</b></a></span>
|
||||
LJUSERF=>{DRs}<span class='ljuser' style='white-space:nowrap;'><a href='/userinfo.bml?user=%%data%%&mode=full'><img src='<?imgprefix?>/userinfo.gif' alt='userinfo' width='17' height='17' style='vertical-align:bottom;border:0;' /></a><a href='/users/%%data%%/'><b>%%data%%</b></a></span>
|
||||
HELP=>{DR}(<a href="%%data%%"><i>help</i></a>)
|
||||
INERR=>{DR}<font color="#ff0000"><b>%%data%%</b></font>
|
||||
SOERROR=>{DR}<div><b>%%data%%</b></div>
|
||||
EMAILEX=><div style='font-family: courier; border: solid black 1px; padding: 5px;'>%%data%%</div>
|
||||
|
||||
ENTRYFORMCSS<=
|
||||
{Ss}
|
||||
<style type="text/css">
|
||||
#EntryForm #MetaInfo {
|
||||
width: 100%;
|
||||
}
|
||||
#EntryForm th {
|
||||
font-size: 1em;
|
||||
}
|
||||
#EntryForm #SubmitBar {
|
||||
background-color: #dfdfdf;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
border: 1px outset #000;
|
||||
margin-left: auto; margin-right: auto;
|
||||
}
|
||||
#MetaInfo tr {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
#metainfo th {
|
||||
text-align: left;
|
||||
}
|
||||
#mood_preview {
|
||||
display: none;
|
||||
}
|
||||
#datetime_box input, #datetime_box select {
|
||||
margin-right: 2px;
|
||||
}
|
||||
#EntryForm legend {
|
||||
font-weight: bold;
|
||||
}
|
||||
#EntryForm #Options {
|
||||
margin-left: 0; margin-right: 0; padding: 0;
|
||||
background-color: #dfdfdf;
|
||||
border: 1px outset #000;
|
||||
}
|
||||
#EntryForm #Options th {
|
||||
text-align: left;
|
||||
}
|
||||
#EntryForm #infobox {
|
||||
text-align: center;
|
||||
}
|
||||
#EntryForm #infobox table {
|
||||
background-color: #dfdfdf;
|
||||
border: 2px solid <?emcolor?>;
|
||||
}
|
||||
#EntryForm textarea {
|
||||
border: 1px inset #000;
|
||||
padding: 2px;
|
||||
}
|
||||
#EntryForm #Security option {
|
||||
padding-left: 18px;
|
||||
}
|
||||
#EntryForm #security_public {
|
||||
background-image: url("<?imgprefix?>/userinfo.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #security_private {
|
||||
background-image: url("<?imgprefix?>/icon_private.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #security_friends, #EntryForm #security_custom {
|
||||
background-image: url("<?imgprefix?>/icon_protected.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #UserpicPreviewImage {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
#EntryForm {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<=ENTRYFORMCSS
|
||||
|
||||
NEEDLOGIN<=
|
||||
<?h1 <?_ml bml.needlogin.head _ml?> h1?>
|
||||
<?p <?_ml bml.needlogin.body2 _ml?> p?>
|
||||
<=NEEDLOGIN
|
||||
|
||||
BADINPUT<=
|
||||
<?h1 <?_ml bml.badinput.head _ml?> h1?>
|
||||
<?p <?_ml bml.badinput.body _ml?> p?>
|
||||
<=BADINPUT
|
||||
|
||||
REQUIREPOST=><?_ml bml.requirepost _ml?>
|
||||
|
||||
LOAD_PAGE_INFO<=
|
||||
<?_code
|
||||
#line 3
|
||||
@sidebar = ({ 'name' => 'Home',
|
||||
'uri' => '/',
|
||||
'match' => "^/(index\\.bml)?(\\?.*)?\$",
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.create'),
|
||||
'uri' => '/create.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.update'),
|
||||
'uri' => '/update.bml',
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.fullupdate'),
|
||||
'uri' => '/update.bml?mode=full', }
|
||||
],
|
||||
},
|
||||
# { 'name' => 'Download',
|
||||
# 'uri' => '/download/', },
|
||||
],
|
||||
},
|
||||
{ 'name' => BML::ml('ljrlook.nav.site'),
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.news'),
|
||||
'match' => '^/news\\.bml\$',
|
||||
'uri' => '/community/ljr_news/', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.siteopts'),
|
||||
'uri' => '/manage/siteopts.bml', },
|
||||
{ 'name' => 'Sitemap',
|
||||
'uri' => '/site/', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.paidaccounts'),
|
||||
'uri' => '/paidaccounts/',
|
||||
# 'recursematch' => '^/paidaccounts/',
|
||||
# 'children' => [
|
||||
# { 'name' => 'Is this safe?',
|
||||
# 'uri' => '/paidaccounts/whysafe.bml', },
|
||||
# { 'name' => 'Progress',
|
||||
# 'uri' => '/paidaccounts/progress.bml', },
|
||||
# ],
|
||||
},
|
||||
{ 'name' => BML::ml('ljrlook.nav.ljfif'),
|
||||
'uri' => '/users/ljr_fif/friends', },
|
||||
# { 'name' => 'To-Do list',
|
||||
# 'uri' => '/todo.bml', },
|
||||
# { 'name' => 'Contributors',
|
||||
# 'uri' => '/contributors.bml', },
|
||||
],
|
||||
},
|
||||
# { 'name' => 'Find Users',
|
||||
# 'children' => [
|
||||
# { 'name' => 'Random!',
|
||||
# 'uri' => '/random.bml', },
|
||||
# { 'name' => 'By Region',
|
||||
# 'uri' => '/directory.bml', },
|
||||
# { 'name' => 'By Interest',
|
||||
# 'uri' => '/interests.bml', },
|
||||
# { 'name' => 'Search',
|
||||
# 'uri' => '/directorysearch.bml', }
|
||||
# ], },
|
||||
{ 'name' => BML::ml('ljrlook.nav.edit'),
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.editinfo'),
|
||||
'uri' => '/editinfo.bml', },
|
||||
# { 'name' => 'Settings', cont => 1,
|
||||
# 'uri' => '/editinfo.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.editfriends'),
|
||||
'uri' => '/friends/edit.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.editjournal'),
|
||||
'uri' => '/editjournal.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.editpics'),
|
||||
'uri' => '/editpics.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.changepassword'),
|
||||
'uri' => '/changepassword.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.modify'),
|
||||
'uri' => '/modify.bml', },
|
||||
# { 'name' => 'Import',
|
||||
# 'uri' => '/import.bml' },
|
||||
],
|
||||
},
|
||||
{ 'name' => BML::ml('ljrlook.nav.communities.manage'),
|
||||
'uri' => '/community/manage.bml'
|
||||
},
|
||||
# { 'name' => 'Developer Area',
|
||||
# 'uri' => '/developer/',
|
||||
# 'match' => "^/developer/\$",
|
||||
# 'recursematch' => "^/developer/",
|
||||
# 'children' => [
|
||||
# { 'name' => 'Style System',
|
||||
# 'uri' => '/developer/styles.bml',
|
||||
# 'children' => [
|
||||
# { 'name' => 'View Types',
|
||||
# 'uri' => '/developer/views.bml', },
|
||||
# { 'name' => 'Variable List',
|
||||
# 'uri' => '/developer/varlist.bml', },
|
||||
# ],
|
||||
# },
|
||||
# { 'name' => 'Embedding',
|
||||
# 'uri' => '/developer/embedding.bml', },
|
||||
# { 'name' => 'Protocol',
|
||||
# 'uri' => '/developer/protocol.bml',
|
||||
# 'children' => [
|
||||
# { 'name' => 'Mode List',
|
||||
# 'uri' => '/developer/modelist.bml', }
|
||||
# ],
|
||||
# },
|
||||
# ],
|
||||
# },
|
||||
# { 'name' => BML::ml('ljrlook.nav.frills'),#Styles,customization
|
||||
# 'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.customize'),
|
||||
'uri' => '/customize/', },
|
||||
# { 'name' => BML::ml('ljrlook.nav.createstyle'),
|
||||
# 'uri' => '/createstyle.bml', },
|
||||
# { 'name' => BML::ml('ljrlook.nav.editstyle'),
|
||||
# 'uri' => '/editstyle.bml', },
|
||||
# ],
|
||||
# },
|
||||
{ 'name' => BML::ml('ljrlook.nav.needhelp'),
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.lostinfo'),
|
||||
'uri' => '/lostinfo.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.support.faq'),
|
||||
'uri' => '/support/faq.bml', },
|
||||
# { 'name' => 'Questions',
|
||||
# 'uri' => '/support/faq.bml', cont => 1, },
|
||||
{ 'name' => BML::ml('ljrlook.nav.support'),
|
||||
'uri' => '/support/', },
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
my $remote = LJ::get_remote();
|
||||
my $remuser = $remote ? $remote->{'user'} : "";
|
||||
my $hello_name = $remote ? LJ::User::display_name($remote) : "";
|
||||
my $uri = BML::get_uri();
|
||||
if ($remuser ne "" && $uri ne "/logout.bml")
|
||||
{
|
||||
my $subdomain = $remuser;
|
||||
$subdomain =~ s/_/-/g;
|
||||
unshift @sidebar, { 'name' => BML::ml('ljrlook.nav.hello').", ".$hello_name."!",
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.yourjournal'),
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.recent'),
|
||||
'uri' => "/users/$remuser/", },
|
||||
{ 'name' => BML::ml('ljrlook.nav.calendar'),
|
||||
'uri' => "/users/$remuser/calendar", },
|
||||
{ 'name' => BML::ml('ljrlook.nav.friends'),
|
||||
'uri' => "/users/$remuser/friends",
|
||||
'extra' => "/friendsfilter.bml",
|
||||
},
|
||||
],
|
||||
},
|
||||
{ 'name' => BML::ml('ljrlook.nav.userinfo'),
|
||||
'uri' => "/userinfo.bml?user=$remuser", },
|
||||
{ 'name' => BML::ml('ljrlook.nav.memories'),
|
||||
'uri' => "/memories.bml?user=$remuser", },
|
||||
{ 'name' => BML::ml('ljrlook.nav.logout'),
|
||||
'uri' => '/logout.bml', },
|
||||
]
|
||||
};
|
||||
} elsif ($uri ne "/login.bml") {
|
||||
unshift @sidebar, { 'name' => BML::ml('ljrlook.nav.login'),,
|
||||
'uri' => '/login.bml', }
|
||||
}
|
||||
|
||||
return "";
|
||||
_code?>
|
||||
<=LOAD_PAGE_INFO
|
||||
|
||||
AL=>{P}<i><a href="%%data1%%">%%data2%%</a></i> <img src="/img/external_link.gif" width='16' height='11' align='absmiddle' />
|
||||
AWAYLINK=>{P}<i><a href="%%data1%%">%%data2%%</a></i> <img src="/img/external_link.gif" width='16' height='11' align='absmiddle' />
|
||||
|
||||
H1=>{D}<h1>%%data%%</h1>
|
||||
H2=>{D}<h2>%%data%%</h2>
|
||||
|
||||
# Banner Header: search results banner, content desriptor, etc...
|
||||
BH=>{D}<p align='center'><font face="Arial,Helvetica" color="#CC0000" size='-1'><b>%%data%%</b></font>
|
||||
|
||||
GRIN=>{S}<grin>
|
||||
HR=>{S}<hr />
|
||||
|
||||
NEWLINE=>{S}<BR>
|
||||
P=>{D}<P>%%data%%</P>
|
||||
|
||||
STANDOUT<=
|
||||
{D}<blockquote>
|
||||
<hr />
|
||||
%%data%%
|
||||
<hr />
|
||||
</blockquote>
|
||||
<=STANDOUT
|
||||
|
||||
ERRORBAR<=
|
||||
{D}<blockquote>
|
||||
<hr />
|
||||
%%data%%
|
||||
<hr />
|
||||
</blockquote>
|
||||
<=ERRORBAR
|
||||
|
||||
WARNINGBAR<=
|
||||
{D}<blockquote>
|
||||
<hr />
|
||||
%%data%%
|
||||
<hr />
|
||||
</blockquote>
|
||||
<=WARNINGBAR
|
||||
|
||||
BADCONTENT<=
|
||||
<?h1 <?_ml Error _ml?> h1?>
|
||||
<?p <?_ml bml.badcontent.body _ml?> p?>
|
||||
<=BADCONTENT
|
||||
|
||||
DE<=
|
||||
%%data%%
|
||||
<=DE
|
||||
|
||||
EMCOLOR=>{S}#c0c0c0
|
||||
HOTCOLOR=>{S}#ff0000
|
||||
EMCOLORLITE=>{S}#e2e2e2
|
||||
ALTCOLOR1=>{S}#eeeeee
|
||||
ALTCOLOR2=>{S}#dddddd
|
||||
screenedbarcolor=>{S}#d0d0d0
|
||||
|
||||
CHOICE=>{P}<dt><a href="%%data2%%"><font size="+1"><tt><b>%%data1%%</b></tt></font></a><dd><font size="2">%%data3%%</font>
|
||||
|
||||
CHOICES<=
|
||||
{F}<table width="100%" cellpadding="2" cellspacing="5">
|
||||
<tr>
|
||||
<td valign='top' width="50%">
|
||||
<dl>
|
||||
%%items%%
|
||||
</dl>
|
||||
</td>
|
||||
<td valign='top' width="50%">
|
||||
<dl>
|
||||
%%itemsb%%
|
||||
</dl>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<=CHOICES
|
||||
|
||||
PAGE<=
|
||||
{Fp}<html>
|
||||
<head><title>%%title%%</title>%%head%%</head>
|
||||
<body %%bodyopts%%>
|
||||
%%body%%
|
||||
</body>
|
||||
</html>
|
||||
<=PAGE
|
||||
|
||||
BREADCRUMBS<=
|
||||
{Fp}<?_code
|
||||
# where are we
|
||||
my @crumbs = LJ::get_crumb_path();
|
||||
return unless @crumbs;
|
||||
my @ret;
|
||||
my $count = 0;
|
||||
foreach my $crumb (@crumbs) {
|
||||
# put crumbs together
|
||||
next unless $crumb->[3]; # no blank crumbs
|
||||
if ($crumb->[3] eq 'dynamic') {
|
||||
# dynamic
|
||||
unshift @ret, "<b>$crumb->[0]</b>";
|
||||
$count++;
|
||||
} else {
|
||||
# non-dynamic
|
||||
unshift @ret, $count++ == 0 ?
|
||||
"<b>$ML{'crumb.'.$crumb->[3]}</b>" :
|
||||
$crumb->[1] ne '' ?
|
||||
"<a href=\"$crumb->[1]\">$ML{'crumb.'.$crumb->[3]}</a>" :
|
||||
"$ML{'crumb.'.$crumb->[3]}";
|
||||
}
|
||||
}
|
||||
return "<div id='ljbreadcrumbs'>" . join(" : ", @ret) . "</div>";
|
||||
_code?>
|
||||
<=BREADCRUMBS
|
||||
370
local/cgi-bin/bml/scheme/global.look.original
Executable file
370
local/cgi-bin/bml/scheme/global.look.original
Executable file
@@ -0,0 +1,370 @@
|
||||
_parent=>../../lj-bml-blocks.pl
|
||||
|
||||
loginboxstyle=>{Ss}background: url(<?imgprefix?>/userinfo.gif) no-repeat; background-color: #fff; background-position: 0px 1px; padding-left: 18px; color: #00C; font-weight: bold;
|
||||
commloginboxstyle=>{Ss}background: url(<?imgprefix?>/community.gif) no-repeat; background-color: #fff; background-position: 0px 2px; padding-left: 19px; color: #00C; font-weight: bold;
|
||||
|
||||
SECURITYPRIVATE=>{Ss}<img src="<?imgprefix?>/icon_private.gif" width=16 height=16 align=absmiddle>
|
||||
SECURITYPROTECTED=>{Ss}<img src="<?imgprefix?>/icon_protected.gif" width=14 height=15 align=absmiddle>
|
||||
LJUSER=>{DRs}<span class='ljuser' style='white-space:nowrap;'><a href='/userinfo.bml?user=%%data%%'><img src='<?imgprefix?>/userinfo.gif' alt='userinfo' width='17' height='17' style='vertical-align:bottom;border:0;' /></a><a href='/users/%%data%%/'><b>%%data%%</b></a></span>
|
||||
LJCOMM=>{DRs}<span class='ljuser' style='white-space:nowrap;'><a href='/userinfo.bml?user=%%data%%'><img src='<?imgprefix?>/community.gif' alt='userinfo' width='16' height='16' style='vertical-align:bottom;border:0;' /></a><a href='/community/%%data%%/'><b>%%data%%</b></a></span>
|
||||
LJUSERF=>{DRs}<span class='ljuser' style='white-space:nowrap;'><a href='/userinfo.bml?user=%%data%%&mode=full'><img src='<?imgprefix?>/userinfo.gif' alt='userinfo' width='17' height='17' style='vertical-align:bottom;border:0;' /></a><a href='/users/%%data%%/'><b>%%data%%</b></a></span>
|
||||
HELP=>{DR}(<a href="%%data%%"><i>help</i></a>)
|
||||
INERR=>{DR}<font color="#ff0000"><b>%%data%%</b></font>
|
||||
SOERROR=>{DR}<div><b>%%data%%</b></div>
|
||||
EMAILEX=><div style='font-family: courier; border: solid black 1px; padding: 5px;'>%%data%%</div>
|
||||
|
||||
ENTRYFORMCSS<=
|
||||
{Ss}
|
||||
<style type="text/css">
|
||||
#EntryForm #MetaInfo {
|
||||
width: 100%;
|
||||
}
|
||||
#EntryForm th {
|
||||
font-size: 1em;
|
||||
}
|
||||
#EntryForm #SubmitBar {
|
||||
background-color: #dfdfdf;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
border: 1px outset #000;
|
||||
margin-left: auto; margin-right: auto;
|
||||
}
|
||||
#MetaInfo tr {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
#metainfo th {
|
||||
text-align: left;
|
||||
}
|
||||
#mood_preview {
|
||||
display: none;
|
||||
}
|
||||
#datetime_box input, #datetime_box select {
|
||||
margin-right: 2px;
|
||||
}
|
||||
#EntryForm legend {
|
||||
font-weight: bold;
|
||||
}
|
||||
#EntryForm #Options {
|
||||
margin-left: 0; margin-right: 0; padding: 0;
|
||||
background-color: #dfdfdf;
|
||||
border: 1px outset #000;
|
||||
}
|
||||
#EntryForm #Options th {
|
||||
text-align: left;
|
||||
}
|
||||
#EntryForm #infobox {
|
||||
text-align: center;
|
||||
}
|
||||
#EntryForm #infobox table {
|
||||
background-color: #dfdfdf;
|
||||
border: 2px solid <?emcolor?>;
|
||||
}
|
||||
#EntryForm textarea {
|
||||
border: 1px inset #000;
|
||||
padding: 2px;
|
||||
}
|
||||
#EntryForm #Security option {
|
||||
padding-left: 18px;
|
||||
}
|
||||
#EntryForm #security_public {
|
||||
background-image: url("<?imgprefix?>/userinfo.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #security_private {
|
||||
background-image: url("<?imgprefix?>/icon_private.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #security_friends, #EntryForm #security_custom {
|
||||
background-image: url("<?imgprefix?>/icon_protected.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #UserpicPreviewImage {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
#EntryForm {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<=ENTRYFORMCSS
|
||||
|
||||
NEEDLOGIN<=
|
||||
<?h1 <?_ml bml.needlogin.head _ml?> h1?>
|
||||
<?p <?_ml bml.needlogin.body2 _ml?> p?>
|
||||
<=NEEDLOGIN
|
||||
|
||||
BADINPUT<=
|
||||
<?h1 <?_ml bml.badinput.head _ml?> h1?>
|
||||
<?p <?_ml bml.badinput.body _ml?> p?>
|
||||
<=BADINPUT
|
||||
|
||||
REQUIREPOST=><?_ml bml.requirepost _ml?>
|
||||
|
||||
LOAD_PAGE_INFO<=
|
||||
<?_code
|
||||
#line 3
|
||||
@sidebar = ({ 'name' => 'Home',
|
||||
'uri' => '/',
|
||||
'match' => "^/(index\\.bml)?(\\?.*)?\$",
|
||||
'children' => [
|
||||
{ 'name' => 'Create Journal',
|
||||
'uri' => '/create.bml', },
|
||||
{ 'name' => 'Update',
|
||||
'uri' => '/update.bml',
|
||||
'children' => [
|
||||
{ 'name' => 'Full Update',
|
||||
'uri' => '/update.bml?mode=full', }
|
||||
],
|
||||
},
|
||||
# { 'name' => 'Download',
|
||||
# 'uri' => '/download/', },
|
||||
],
|
||||
},
|
||||
{ 'name' => 'LiveJournal',
|
||||
'children' => [
|
||||
{ 'name' => 'News',
|
||||
'match' => '^/news\\.bml\$',
|
||||
'uri' => '/news.bml', },
|
||||
# { 'name' => 'Paid Accounts',
|
||||
# 'uri' => '/paidaccounts/',
|
||||
# 'recursematch' => '^/paidaccounts/',
|
||||
# 'children' => [
|
||||
# { 'name' => 'Is this safe?',
|
||||
# 'uri' => '/paidaccounts/whysafe.bml', },
|
||||
# { 'name' => 'Progress',
|
||||
# 'uri' => '/paidaccounts/progress.bml', },
|
||||
# ],
|
||||
# },
|
||||
{ 'name' => 'To-Do list',
|
||||
'uri' => '/todo.bml', },
|
||||
# { 'name' => 'Contributors',
|
||||
# 'uri' => '/contributors.bml', },
|
||||
],
|
||||
},
|
||||
{ 'name' => 'Customize',
|
||||
'children' => [
|
||||
{ 'name' => 'Modify Journal',
|
||||
'uri' => '/modify.bml',
|
||||
'children' => [
|
||||
{ 'name' => 'Customize S2',
|
||||
'uri' => '/customize/', },
|
||||
],
|
||||
},
|
||||
{ 'name' => 'Create Style',
|
||||
'uri' => '/createstyle.bml', },
|
||||
{ 'name' => 'Edit Style',
|
||||
'uri' => '/editstyle.bml', },
|
||||
],
|
||||
},
|
||||
{ 'name' => 'Find Users',
|
||||
'children' => [
|
||||
{ 'name' => 'Random!',
|
||||
'uri' => '/random.bml', },
|
||||
{ 'name' => 'By Region',
|
||||
'uri' => '/directory.bml', },
|
||||
{ 'name' => 'By Interest',
|
||||
'uri' => '/interests.bml', },
|
||||
{ 'name' => 'Search',
|
||||
'uri' => '/directorysearch.bml', }
|
||||
], },
|
||||
{ 'name' => 'Edit ...',
|
||||
'children' => [
|
||||
{ 'name' => 'Personal Info &',
|
||||
'uri' => '/editinfo.bml', },
|
||||
{ 'name' => 'Settings', cont => 1,
|
||||
'uri' => '/editinfo.bml', },
|
||||
{ 'name' => 'Your Friends',
|
||||
'uri' => '/editfriends.bml', },
|
||||
{ 'name' => 'Old Entries',
|
||||
'uri' => '/editjournal.bml', },
|
||||
{ 'name' => 'Your Pictures',
|
||||
'uri' => '/editpics.bml', },
|
||||
{ 'name' => 'Your Password',
|
||||
'uri' => '/changepassword.bml', },
|
||||
],
|
||||
},
|
||||
# { 'name' => 'Developer Area',
|
||||
# 'uri' => '/developer/',
|
||||
# 'match' => "^/developer/\$",
|
||||
# 'recursematch' => "^/developer/",
|
||||
# 'children' => [
|
||||
# { 'name' => 'Style System',
|
||||
# 'uri' => '/developer/styles.bml',
|
||||
# 'children' => [
|
||||
# { 'name' => 'View Types',
|
||||
# 'uri' => '/developer/views.bml', },
|
||||
# { 'name' => 'Variable List',
|
||||
# 'uri' => '/developer/varlist.bml', },
|
||||
# ],
|
||||
# },
|
||||
# { 'name' => 'Embedding',
|
||||
# 'uri' => '/developer/embedding.bml', },
|
||||
# { 'name' => 'Protocol',
|
||||
# 'uri' => '/developer/protocol.bml',
|
||||
# 'children' => [
|
||||
# { 'name' => 'Mode List',
|
||||
# 'uri' => '/developer/modelist.bml', }
|
||||
# ],
|
||||
# },
|
||||
# ],
|
||||
# },
|
||||
{ 'name' => 'Need Help?',
|
||||
'children' => [
|
||||
{ 'name' => 'Lost Password?',
|
||||
'uri' => '/lostinfo.bml', },
|
||||
{ 'name' => 'Freq. Asked',
|
||||
'uri' => '/support/faq.bml', },
|
||||
{ 'name' => 'Questions',
|
||||
'uri' => '/support/faq.bml', cont => 1, },
|
||||
{ 'name' => 'Support Area',
|
||||
'uri' => '/support/', },
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
my $remote = LJ::get_remote();
|
||||
my $remuser = $remote ? $remote->{'user'} : "";
|
||||
my $uri = BML::get_uri();
|
||||
if ($remuser ne "" && $uri ne "/logout.bml")
|
||||
{
|
||||
my $subdomain = $remuser;
|
||||
$subdomain =~ s/_/-/g;
|
||||
unshift @sidebar, { 'name' => "Hello, $remuser!",
|
||||
'children' => [
|
||||
{ 'name' => 'Your Journal',
|
||||
'children' => [
|
||||
{ 'name' => 'Recent',
|
||||
'uri' => "/users/$remuser/", },
|
||||
{ 'name' => 'Calendar',
|
||||
'uri' => "/users/$remuser/calendar", },
|
||||
{ 'name' => 'Friends',
|
||||
'uri' => "/users/$remuser/friends",
|
||||
'extra' => "/friendsfilter.bml",
|
||||
},
|
||||
],
|
||||
},
|
||||
{ 'name' => 'User Info',
|
||||
'uri' => "/userinfo.bml?user=$remuser", },
|
||||
{ 'name' => 'Memories',
|
||||
'uri' => "/memories.bml?user=$remuser", },
|
||||
{ 'name' => 'Logout',
|
||||
'uri' => '/logout.bml', },
|
||||
]
|
||||
};
|
||||
} elsif ($uri ne "/login.bml") {
|
||||
unshift @sidebar, { 'name' => "Log In",
|
||||
'uri' => '/login.bml', }
|
||||
}
|
||||
|
||||
return "";
|
||||
_code?>
|
||||
<=LOAD_PAGE_INFO
|
||||
|
||||
AL=>{P}<i><a href="%%data1%%">%%data2%%</a></i> <img src="/img/external_link.gif" width='16' height='11' align='absmiddle' />
|
||||
AWAYLINK=>{P}<i><a href="%%data1%%">%%data2%%</a></i> <img src="/img/external_link.gif" width='16' height='11' align='absmiddle' />
|
||||
|
||||
H1=>{D}<h1>%%data%%</h1>
|
||||
H2=>{D}<h2>%%data%%</h2>
|
||||
|
||||
# Banner Header: search results banner, content desriptor, etc...
|
||||
BH=>{D}<p align='center'><font face="Arial,Helvetica" color="#CC0000" size='-1'><b>%%data%%</b></font>
|
||||
|
||||
GRIN=>{S}<grin>
|
||||
HR=>{S}<hr />
|
||||
|
||||
NEWLINE=>{S}<BR>
|
||||
P=>{D}<P>%%data%%</P>
|
||||
|
||||
STANDOUT<=
|
||||
{D}<blockquote>
|
||||
<hr />
|
||||
%%data%%
|
||||
<hr />
|
||||
</blockquote>
|
||||
<=STANDOUT
|
||||
|
||||
ERRORBAR<=
|
||||
{D}<blockquote>
|
||||
<hr />
|
||||
%%data%%
|
||||
<hr />
|
||||
</blockquote>
|
||||
<=ERRORBAR
|
||||
|
||||
WARNINGBAR<=
|
||||
{D}<blockquote>
|
||||
<hr />
|
||||
%%data%%
|
||||
<hr />
|
||||
</blockquote>
|
||||
<=WARNINGBAR
|
||||
|
||||
BADCONTENT<=
|
||||
<?h1 <?_ml Error _ml?> h1?>
|
||||
<?p <?_ml bml.badcontent.body _ml?> p?>
|
||||
<=BADCONTENT
|
||||
|
||||
DE<=
|
||||
%%data%%
|
||||
<=DE
|
||||
|
||||
EMCOLOR=>{S}#c0c0c0
|
||||
HOTCOLOR=>{S}#ff0000
|
||||
EMCOLORLITE=>{S}#e2e2e2
|
||||
ALTCOLOR1=>{S}#eeeeee
|
||||
ALTCOLOR2=>{S}#dddddd
|
||||
screenedbarcolor=>{S}#d0d0d0
|
||||
|
||||
CHOICE=>{P}<dt><a href="%%data2%%"><font size="+1"><tt><b>%%data1%%</b></tt></font></a><dd><font size="2">%%data3%%</font>
|
||||
|
||||
CHOICES<=
|
||||
{F}<table width="100%" cellpadding="2" cellspacing="5">
|
||||
<tr>
|
||||
<td valign='top' width="50%">
|
||||
<dl>
|
||||
%%items%%
|
||||
</dl>
|
||||
</td>
|
||||
<td valign='top' width="50%">
|
||||
<dl>
|
||||
%%itemsb%%
|
||||
</dl>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<=CHOICES
|
||||
|
||||
PAGE<=
|
||||
{Fp}<html>
|
||||
<head><title>%%title%%</title>%%head%%</head>
|
||||
<body %%bodyopts%%>
|
||||
%%body%%
|
||||
</body>
|
||||
</html>
|
||||
<=PAGE
|
||||
|
||||
BREADCRUMBS<=
|
||||
{Fp}<?_code
|
||||
# where are we
|
||||
my @crumbs = LJ::get_crumb_path();
|
||||
return unless @crumbs;
|
||||
my @ret;
|
||||
my $count = 0;
|
||||
foreach my $crumb (@crumbs) {
|
||||
# put crumbs together
|
||||
next unless $crumb->[3]; # no blank crumbs
|
||||
if ($crumb->[3] eq 'dynamic') {
|
||||
# dynamic
|
||||
unshift @ret, "<b>$crumb->[0]</b>";
|
||||
$count++;
|
||||
} else {
|
||||
# non-dynamic
|
||||
unshift @ret, $count++ == 0 ?
|
||||
"<b>$ML{'crumb.'.$crumb->[3]}</b>" :
|
||||
$crumb->[1] ne '' ?
|
||||
"<a href=\"$crumb->[1]\">$ML{'crumb.'.$crumb->[3]}</a>" :
|
||||
"$ML{'crumb.'.$crumb->[3]}";
|
||||
}
|
||||
}
|
||||
return "<div id='ljbreadcrumbs'>" . join(" : ", @ret) . "</div>";
|
||||
_code?>
|
||||
<=BREADCRUMBS
|
||||
374
local/cgi-bin/bml/scheme/ljr.look
Executable file
374
local/cgi-bin/bml/scheme/ljr.look
Executable file
@@ -0,0 +1,374 @@
|
||||
_parent=>../../lj-bml-blocks.pl
|
||||
|
||||
loginboxstyle=>{Ss}background: url(<?imgprefix?>/userinfo.gif) no-repeat; background-color: #fff; background-position: 0px 1px; padding-left: 18px; color: #00C; font-weight: bold;
|
||||
commloginboxstyle=>{Ss}background: url(<?imgprefix?>/community.gif) no-repeat; background-color: #fff; background-position: 0px 2px; padding-left: 19px; color: #00C; font-weight: bold;
|
||||
|
||||
SECURITYPRIVATE=>{Ss}<img src="<?imgprefix?>/icon_private.gif" width=16 height=16 align=absmiddle>
|
||||
SECURITYPROTECTED=>{Ss}<img src="<?imgprefix?>/icon_protected.gif" width=14 height=15 align=absmiddle>
|
||||
LJUSER=>{DRs}<span class='ljuser' style='white-space:nowrap;'><a href='/userinfo.bml?user=%%data%%'><img src='<?imgprefix?>/userinfo.gif' alt='userinfo' width='17' height='17' style='vertical-align:bottom;border:0;' /></a><a href='/users/%%data%%/'><b>%%data%%</b></a></span>
|
||||
LJCOMM=>{DRs}<span class='ljuser' style='white-space:nowrap;'><a href='/userinfo.bml?user=%%data%%'><img src='<?imgprefix?>/community.gif' alt='userinfo' width='16' height='16' style='vertical-align:bottom;border:0;' /></a><a href='/community/%%data%%/'><b>%%data%%</b></a></span>
|
||||
LJUSERF=>{DRs}<span class='ljuser' style='white-space:nowrap;'><a href='/userinfo.bml?user=%%data%%&mode=full'><img src='<?imgprefix?>/userinfo.gif' alt='userinfo' width='17' height='17' style='vertical-align:bottom;border:0;' /></a><a href='/users/%%data%%/'><b>%%data%%</b></a></span>
|
||||
HELP=>{DR}(<a href="%%data%%"><i>help</i></a>)
|
||||
INERR=>{DR}<font color="#ff0000"><b>%%data%%</b></font>
|
||||
SOERROR=>{DR}<div><b>%%data%%</b></div>
|
||||
EMAILEX=><div style='font-family: courier; border: solid black 1px; padding: 5px;'>%%data%%</div>
|
||||
|
||||
ENTRYFORMCSS<=
|
||||
{Ss}
|
||||
<style type="text/css">
|
||||
#EntryForm #MetaInfo {
|
||||
width: 100%;
|
||||
}
|
||||
#EntryForm th {
|
||||
font-size: 1em;
|
||||
}
|
||||
#EntryForm #SubmitBar {
|
||||
background-color: #dfdfdf;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
border: 1px outset #000;
|
||||
margin-left: auto; margin-right: auto;
|
||||
}
|
||||
#MetaInfo tr {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
#metainfo th {
|
||||
text-align: left;
|
||||
}
|
||||
#mood_preview {
|
||||
display: none;
|
||||
}
|
||||
#datetime_box input, #datetime_box select {
|
||||
margin-right: 2px;
|
||||
}
|
||||
#EntryForm legend {
|
||||
font-weight: bold;
|
||||
}
|
||||
#EntryForm #Options {
|
||||
margin-left: 0; margin-right: 0; padding: 0;
|
||||
background-color: #dfdfdf;
|
||||
border: 1px outset #000;
|
||||
}
|
||||
#EntryForm #Options th {
|
||||
text-align: left;
|
||||
}
|
||||
#EntryForm #infobox {
|
||||
text-align: center;
|
||||
}
|
||||
#EntryForm #infobox table {
|
||||
background-color: #dfdfdf;
|
||||
border: 2px solid <?emcolor?>;
|
||||
}
|
||||
#EntryForm textarea {
|
||||
border: 1px inset #000;
|
||||
padding: 2px;
|
||||
}
|
||||
#EntryForm #Security option {
|
||||
padding-left: 18px;
|
||||
}
|
||||
#EntryForm #security_public {
|
||||
background-image: url("<?imgprefix?>/userinfo.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #security_private {
|
||||
background-image: url("<?imgprefix?>/icon_private.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #security_friends, #EntryForm #security_custom {
|
||||
background-image: url("<?imgprefix?>/icon_protected.gif");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#EntryForm #UserpicPreviewImage {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
#EntryForm {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<=ENTRYFORMCSS
|
||||
|
||||
NEEDLOGIN<=
|
||||
<?h1 <?_ml bml.needlogin.head _ml?> h1?>
|
||||
<?p <?_ml bml.needlogin.body2 _ml?> p?>
|
||||
<=NEEDLOGIN
|
||||
|
||||
BADINPUT<=
|
||||
<?h1 <?_ml bml.badinput.head _ml?> h1?>
|
||||
<?p <?_ml bml.badinput.body _ml?> p?>
|
||||
<=BADINPUT
|
||||
|
||||
REQUIREPOST=><?_ml bml.requirepost _ml?>
|
||||
|
||||
LOAD_PAGE_INFO<=
|
||||
<?_code
|
||||
#line 3
|
||||
@sidebar = ({ 'name' => 'Home',
|
||||
'uri' => '/',
|
||||
'match' => "^/(index\\.bml)?(\\?.*)?\$",
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.create'),
|
||||
'uri' => '/create.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.update'),
|
||||
'uri' => '/update.bml',
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.fullupdate'),
|
||||
'uri' => '/update.bml?mode=full', }
|
||||
],
|
||||
},
|
||||
# { 'name' => 'Download',
|
||||
# 'uri' => '/download/', },
|
||||
],
|
||||
},
|
||||
{ 'name' => BML::ml('ljrlook.nav.site'),
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.news'),
|
||||
'match' => '^/news\\.bml\$',
|
||||
'uri' => '/community/ljr_news/', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.siteopts'),
|
||||
'uri' => '/manage/siteopts.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.paidaccounts'),
|
||||
'uri' => '/paidaccounts/',
|
||||
# 'recursematch' => '^/paidaccounts/',
|
||||
# 'children' => [
|
||||
# { 'name' => 'Is this safe?',
|
||||
# 'uri' => '/paidaccounts/whysafe.bml', },
|
||||
# { 'name' => 'Progress',
|
||||
# 'uri' => '/paidaccounts/progress.bml', },
|
||||
# ],
|
||||
},
|
||||
{ 'name' => BML::ml('ljrlook.nav.ljfif'),
|
||||
'uri' => '/users/ljr_fif/friends', },
|
||||
# { 'name' => 'To-Do list',
|
||||
# 'uri' => '/todo.bml', },
|
||||
# { 'name' => 'Contributors',
|
||||
# 'uri' => '/contributors.bml', },
|
||||
],
|
||||
},
|
||||
# { 'name' => 'Find Users',
|
||||
# 'children' => [
|
||||
# { 'name' => 'Random!',
|
||||
# 'uri' => '/random.bml', },
|
||||
# { 'name' => 'By Region',
|
||||
# 'uri' => '/directory.bml', },
|
||||
# { 'name' => 'By Interest',
|
||||
# 'uri' => '/interests.bml', },
|
||||
# { 'name' => 'Search',
|
||||
# 'uri' => '/directorysearch.bml', }
|
||||
# ], },
|
||||
{ 'name' => BML::ml('ljrlook.nav.edit'),
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.editinfo'),
|
||||
'uri' => '/editinfo.bml', },
|
||||
# { 'name' => 'Settings', cont => 1,
|
||||
# 'uri' => '/editinfo.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.editfriends'),
|
||||
'uri' => '/editfriends.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.editjournal'),
|
||||
'uri' => '/editjournal.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.editpics'),
|
||||
'uri' => '/editpics.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.changepassword'),
|
||||
'uri' => '/changepassword.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.modify'),
|
||||
'uri' => '/modify.bml', },
|
||||
],
|
||||
},
|
||||
{ 'name' => BML::ml('ljrlook.nav.communities.manage'),
|
||||
'uri' => '/community/manage.bml'
|
||||
},
|
||||
# { 'name' => 'Developer Area',
|
||||
# 'uri' => '/developer/',
|
||||
# 'match' => "^/developer/\$",
|
||||
# 'recursematch' => "^/developer/",
|
||||
# 'children' => [
|
||||
# { 'name' => 'Style System',
|
||||
# 'uri' => '/developer/styles.bml',
|
||||
# 'children' => [
|
||||
# { 'name' => 'View Types',
|
||||
# 'uri' => '/developer/views.bml', },
|
||||
# { 'name' => 'Variable List',
|
||||
# 'uri' => '/developer/varlist.bml', },
|
||||
# ],
|
||||
# },
|
||||
# { 'name' => 'Embedding',
|
||||
# 'uri' => '/developer/embedding.bml', },
|
||||
# { 'name' => 'Protocol',
|
||||
# 'uri' => '/developer/protocol.bml',
|
||||
# 'children' => [
|
||||
# { 'name' => 'Mode List',
|
||||
# 'uri' => '/developer/modelist.bml', }
|
||||
# ],
|
||||
# },
|
||||
# ],
|
||||
# },
|
||||
{ 'name' => BML::ml('ljrlook.nav.frills'),#Styles,customization
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.customize'),
|
||||
'uri' => '/customize/', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.createstyle'),
|
||||
'uri' => '/createstyle.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.editstyle'),
|
||||
'uri' => '/editstyle.bml', },
|
||||
],
|
||||
},
|
||||
{ 'name' => BML::ml('ljrlook.nav.needhelp'),
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.lostinfo'),
|
||||
'uri' => '/lostinfo.bml', },
|
||||
{ 'name' => BML::ml('ljrlook.nav.support.faq'),
|
||||
'uri' => '/support/faq.bml', },
|
||||
# { 'name' => 'Questions',
|
||||
# 'uri' => '/support/faq.bml', cont => 1, },
|
||||
{ 'name' => BML::ml('ljrlook.nav.support'),
|
||||
'uri' => '/support/', },
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
my $remote = LJ::get_remote();
|
||||
my $remuser = $remote ? $remote->{'user'} : "";
|
||||
my $uri = BML::get_uri();
|
||||
if ($remuser ne "" && $uri ne "/logout.bml")
|
||||
{
|
||||
my $subdomain = $remuser;
|
||||
$subdomain =~ s/_/-/g;
|
||||
unshift @sidebar, { 'name' => BML::ml('ljrlook.nav.hello').", ".$remuser."!",
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.yourjournal'),
|
||||
'children' => [
|
||||
{ 'name' => BML::ml('ljrlook.nav.recent'),
|
||||
'uri' => "/users/$remuser/", },
|
||||
{ 'name' => BML::ml('ljrlook.nav.calendar'),
|
||||
'uri' => "/users/$remuser/calendar", },
|
||||
{ 'name' => BML::ml('ljrlook.nav.friends'),
|
||||
'uri' => "/users/$remuser/friends",
|
||||
'extra' => "/friendsfilter.bml",
|
||||
},
|
||||
],
|
||||
},
|
||||
{ 'name' => BML::ml('ljrlook.nav.userinfo'),
|
||||
'uri' => "/userinfo.bml?user=$remuser", },
|
||||
{ 'name' => BML::ml('ljrlook.nav.memories'),
|
||||
'uri' => "/memories.bml?user=$remuser", },
|
||||
{ 'name' => BML::ml('ljrlook.nav.logout'),
|
||||
'uri' => '/logout.bml', },
|
||||
]
|
||||
};
|
||||
} elsif ($uri ne "/login.bml") {
|
||||
unshift @sidebar, { 'name' => BML::ml('ljrlook.nav.login'),,
|
||||
'uri' => '/login.bml', }
|
||||
}
|
||||
|
||||
return "";
|
||||
_code?>
|
||||
<=LOAD_PAGE_INFO
|
||||
|
||||
AL=>{P}<i><a href="%%data1%%">%%data2%%</a></i> <img src="/img/external_link.gif" width='16' height='11' align='absmiddle' />
|
||||
AWAYLINK=>{P}<i><a href="%%data1%%">%%data2%%</a></i> <img src="/img/external_link.gif" width='16' height='11' align='absmiddle' />
|
||||
|
||||
H1=>{D}<h1>%%data%%</h1>
|
||||
H2=>{D}<h2>%%data%%</h2>
|
||||
|
||||
# Banner Header: search results banner, content desriptor, etc...
|
||||
BH=>{D}<p align='center'><font face="Arial,Helvetica" color="#CC0000" size='-1'><b>%%data%%</b></font>
|
||||
|
||||
GRIN=>{S}<grin>
|
||||
HR=>{S}<hr />
|
||||
|
||||
NEWLINE=>{S}<BR>
|
||||
P=>{D}<P>%%data%%</P>
|
||||
|
||||
STANDOUT<=
|
||||
{D}<blockquote>
|
||||
<hr />
|
||||
%%data%%
|
||||
<hr />
|
||||
</blockquote>
|
||||
<=STANDOUT
|
||||
|
||||
ERRORBAR<=
|
||||
{D}<blockquote>
|
||||
<hr />
|
||||
%%data%%
|
||||
<hr />
|
||||
</blockquote>
|
||||
<=ERRORBAR
|
||||
|
||||
WARNINGBAR<=
|
||||
{D}<blockquote>
|
||||
<hr />
|
||||
%%data%%
|
||||
<hr />
|
||||
</blockquote>
|
||||
<=WARNINGBAR
|
||||
|
||||
BADCONTENT<=
|
||||
<?h1 <?_ml Error _ml?> h1?>
|
||||
<?p <?_ml bml.badcontent.body _ml?> p?>
|
||||
<=BADCONTENT
|
||||
|
||||
DE<=
|
||||
%%data%%
|
||||
<=DE
|
||||
|
||||
EMCOLOR=>{S}#c0c0c0
|
||||
HOTCOLOR=>{S}#ff0000
|
||||
EMCOLORLITE=>{S}#e2e2e2
|
||||
ALTCOLOR1=>{S}#eeeeee
|
||||
ALTCOLOR2=>{S}#dddddd
|
||||
screenedbarcolor=>{S}#d0d0d0
|
||||
|
||||
CHOICE=>{P}<dt><a href="%%data2%%"><font size="+1"><tt><b>%%data1%%</b></tt></font></a><dd><font size="2">%%data3%%</font>
|
||||
|
||||
CHOICES<=
|
||||
{F}<table width="100%" cellpadding="2" cellspacing="5">
|
||||
<tr>
|
||||
<td valign='top' width="50%">
|
||||
<dl>
|
||||
%%items%%
|
||||
</dl>
|
||||
</td>
|
||||
<td valign='top' width="50%">
|
||||
<dl>
|
||||
%%itemsb%%
|
||||
</dl>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<=CHOICES
|
||||
|
||||
PAGE<=
|
||||
{Fp}<html>
|
||||
<head><title>%%title%%</title>%%head%%</head>
|
||||
<body %%bodyopts%%>
|
||||
%%body%%
|
||||
</body>
|
||||
</html>
|
||||
<=PAGE
|
||||
|
||||
BREADCRUMBS<=
|
||||
{Fp}<?_code
|
||||
# where are we
|
||||
my @crumbs = LJ::get_crumb_path();
|
||||
return unless @crumbs;
|
||||
my @ret;
|
||||
my $count = 0;
|
||||
foreach my $crumb (@crumbs) {
|
||||
# put crumbs together
|
||||
next unless $crumb->[3]; # no blank crumbs
|
||||
if ($crumb->[3] eq 'dynamic') {
|
||||
# dynamic
|
||||
unshift @ret, "<b>$crumb->[0]</b>";
|
||||
$count++;
|
||||
} else {
|
||||
# non-dynamic
|
||||
unshift @ret, $count++ == 0 ?
|
||||
"<b>$ML{'crumb.'.$crumb->[3]}</b>" :
|
||||
$crumb->[1] ne '' ?
|
||||
"<a href=\"$crumb->[1]\">$ML{'crumb.'.$crumb->[3]}</a>" :
|
||||
"$ML{'crumb.'.$crumb->[3]}";
|
||||
}
|
||||
}
|
||||
return "<div id='ljbreadcrumbs'>" . join(" : ", @ret) . "</div>";
|
||||
_code?>
|
||||
<=BREADCRUMBS
|
||||
225
local/cgi-bin/bml/scheme/opalcat.look
Executable file
225
local/cgi-bin/bml/scheme/opalcat.look
Executable file
@@ -0,0 +1,225 @@
|
||||
_parent=>global.look
|
||||
|
||||
AL=>{P}<I><A HREF="%%DATA1%%">%%DATA2%%</A></I> <IMG SRC="<?imgprefix?>/external_link.gif" WIDTH=16 HEIGHT=11 ALIGN=ABSMIDDLE>
|
||||
AWAYLINK=>{P}<I><A HREF="%%DATA1%%">%%DATA2%%</A></I> <IMG SRC="<?imgprefix?>/external_link.gif" WIDTH=16 HEIGHT=11 ALIGN=ABSMIDDLE>
|
||||
|
||||
H1=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000"><B>%%DATA%%</B></FONT>
|
||||
H1/FOLLOW_CHOICES=>{D}<FONT FACE="Arial,Helvetica" COLOR="#CC0000"><B>%%DATA%%</B></FONT>
|
||||
HEAD1=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000"><B>%%DATA%%</B></FONT>
|
||||
|
||||
H2=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000" SIZE=-1><B>%%DATA%%</B></FONT>
|
||||
HEAD2=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000" SIZE=-1><B>%%DATA%%</B></FONT>
|
||||
|
||||
# Banner Header: search results banner, content desriptor, etc...
|
||||
BH=>{D}<P ALIGN=CENTER><FONT FACE="Arial,Helvetica" COLOR="#CC0000" SIZE=-1><B>%%DATA%%</B></FONT>
|
||||
|
||||
GRIN=><grin>
|
||||
HR=><P ALIGN="CENTER"><FONT COLOR=BLUE>*</FONT></P>
|
||||
|
||||
DE<=
|
||||
<font size=-1>%%DATA%%</font>
|
||||
<=DE
|
||||
|
||||
NEWLINE=>{D}<BR>
|
||||
P=>{D}<BR>%%DATA%%
|
||||
P/FOLLOW_P=>{D}<BR><IMG SRC="<?imgprefix?>/dot.gif" WIDTH=1 VSPACE=6 HEIGHT=1><BR>%%DATA%%
|
||||
|
||||
STANDOUTO<=
|
||||
{D}<CENTER><FONT SIZE=1><BR></FONT><TABLE ALIGN=CENTER CELLPADDING=8 BORDER=1 BGCOLOR=#CCCCFF BORDERCOLORLIGHT=#DDDDFF
|
||||
BORDERCOLORDARK=#BBBBFF><TR><TD VALIGN=CENTER>
|
||||
%%DATA%%
|
||||
|
||||
</TD></TR></TABLE></CENTER>
|
||||
<=STANDOUTO
|
||||
|
||||
STANDOUT<=
|
||||
{D}<CENTER><FONT SIZE=1><BR></FONT>
|
||||
<table cellspacing=0 cellpadding=0 border=0 bgcolor="#ccccff">
|
||||
<tr>
|
||||
<td width=7 align=left valign=top>
|
||||
<img width=7 height=7 src="<?imgprefix?>/corn_nw.gif" alt=""></td>
|
||||
<td height=7>
|
||||
<img height=7 src="<?imgprefix?>/dot.gif" alt=""></td>
|
||||
<td width=7 valign=top align=right>
|
||||
<img height=7 src="<?imgprefix?>/corn_ne.gif" alt=""></td>
|
||||
</tr><tr>
|
||||
<td width=7>
|
||||
<img width=7 height=1 src="<?imgprefix?>/dot.gif" alt=""></td>
|
||||
<td valign=top>
|
||||
%%DATA%%
|
||||
|
||||
</td>
|
||||
<td width=7>
|
||||
<img width=7 height=1 src="<?imgprefix?>/dot.gif" alt=""></td>
|
||||
</tr><tr>
|
||||
<td width=7 align=left valign=top>
|
||||
<img width=7 height=7 src="<?imgprefix?>/corn_sw.gif" alt=""></td>
|
||||
<td height=7>
|
||||
<img height=7 src="<?imgprefix?>/dot.gif" alt=""></td>
|
||||
<td width=7 valign=top align=right>
|
||||
<img height=7 src="<?imgprefix?>/corn_se.gif" alt=""></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</CENTER>
|
||||
<=STANDOUT
|
||||
|
||||
######################### choices stuff
|
||||
|
||||
CHOICE=>{P}<DT><A HREF="%%DATA2%%"><FONT FACE="Arial,Helvetica"><B>%%DATA1%%</B></FONT></A><DD><FONT SIZE="2">%%DATA3%%</FONT>
|
||||
|
||||
CHOICES<=
|
||||
{F}<P><DIV CLASS="choice"><TABLE WIDTH="100%" CELLPADDING="2" CELLSPACING="5">
|
||||
<TR>
|
||||
<TD VALIGN=TOP WIDTH="50%">
|
||||
<DL>
|
||||
%%ITEMS%%
|
||||
</DL>
|
||||
</TD>
|
||||
<TD VALIGN=TOP WIDTH="50%">
|
||||
<DL>
|
||||
%%ITEMSB%%
|
||||
</DL>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE></DIV>
|
||||
<=CHOICES
|
||||
|
||||
##################################################################################
|
||||
################################### MAIN PAGE ####################################
|
||||
##################################################################################
|
||||
|
||||
PAGE<=
|
||||
{F}<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML><?load_page_info?>
|
||||
<HEAD>
|
||||
<LINK REL="SHORTCUT ICON" HREF="http://www.livejournal.com/favicon.ico">
|
||||
<TITLE>%%TITLE%%</TITLE>
|
||||
%%HEAD%%
|
||||
<SCRIPT LANGUAGE="JavaScript">
|
||||
window.onerror = null; // damn javascript.
|
||||
</SCRIPT>
|
||||
</HEAD>
|
||||
<BODY BGCOLOR=#FFFFFF TOPMARGIN="0" LEFTMARGIN="0" RIGHTMARGIN="0" BOTTOMMARGIN="0"
|
||||
MARGINHEIGHT="0" MARGINWIDTH="0" LINK=#0000C0 VLINK=#600060
|
||||
BACKGROUND="<?imgprefix?>/opal/spiral2.jpg" %%bodyopts%%>
|
||||
|
||||
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 WIDTH=100% HEIGHT=100%>
|
||||
<TR VALIGN=TOP>
|
||||
<TD WIDTH=128 NOWRAP>
|
||||
|
||||
<IMG SRC="<?imgprefix?>/dot.gif" WIDTH=1 HEIGHT=15><BR>
|
||||
|
||||
<FONT FACE="Arial,Helvetica" SIZE=-1>
|
||||
<?_code
|
||||
|
||||
$ret = "";
|
||||
|
||||
sub dump_entry
|
||||
{
|
||||
my ($ret, $listref, $depth) = @_;
|
||||
|
||||
foreach my $mi (@$listref)
|
||||
{
|
||||
if ($depth==0) {
|
||||
$$ret .= "<P><IMG SRC=\"<?imgprefix?>/opal/bullet.gif\" WIDTH=10 HEIGHT=10 HSPACE=2 ALIGN=ABSMIDDLE>";
|
||||
} else {
|
||||
$$ret .= " " x ($depth*3+1);
|
||||
$$ret .= $mi->{'cont'} ? " " : "- ";
|
||||
}
|
||||
|
||||
my $extra = "";
|
||||
if ($mi->{'extra'}) {
|
||||
$extra = " <A HREF=\"$mi->{'extra'}\">...</A>";
|
||||
}
|
||||
|
||||
my $name = $mi->{'name'};
|
||||
$name =~ s/ / /g;
|
||||
if (! defined $mi->{'uri'}) {
|
||||
if ($depth == 0) {
|
||||
$$ret .= "<B>$name</B>$extra<BR>";
|
||||
} else {
|
||||
$$ret .= "$name$extra<BR>";
|
||||
}
|
||||
} elsif ($mi->{'match'} ?
|
||||
(BML::get_uri() =~ /$mi->{'match'}/) :
|
||||
(BML::get_uri() eq $mi->{'uri'})
|
||||
){
|
||||
$$ret .= "<B><SPAN style=\"background-color: #D0D0FF\"><FONT COLOR=#0000D0>$name</FONT></SPAN></B>$extra<BR>";
|
||||
} else {
|
||||
$$ret .= "<A HREF=\"$mi->{'uri'}\">$name</A>$extra<BR>";
|
||||
}
|
||||
|
||||
if ($mi->{'children'} &&
|
||||
($mi->{'recursematch'} ? BML::get_uri() =~ /$mi->{'recursematch'}/ : 1)) {
|
||||
&dump_entry($ret, $mi->{'children'}, $depth+1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&dump_entry(\$ret, \@sidebar, 0);
|
||||
|
||||
return $ret;
|
||||
|
||||
_code?>
|
||||
</FONT>
|
||||
|
||||
</TD>
|
||||
<TD ALIGN=LEFT WIDTH=39 NOWRAP><BR></TD>
|
||||
|
||||
</TD>
|
||||
<TD WIDTH=100%>
|
||||
|
||||
<TABLE HEIGHT=95 WIDTH=100% BORDER=0 cellpadding=0 cellspacing=0>
|
||||
<TR>
|
||||
<TD VALIGN=TOP ALIGN=RIGHT>
|
||||
<?_code
|
||||
$is_home = (BML::get_uri() =~ m!^/(index\.bml)?!);
|
||||
if (! $is_home)
|
||||
{
|
||||
return '<P><A HREF="/"><IMG SRC="<?imgprefix?>/opal/home.gif" WIDTH=87 HEIGHT=51 BORDER=0 HSPACE=0></A></P>';
|
||||
} else {
|
||||
return "<P> </P>";
|
||||
}
|
||||
return "";
|
||||
_code?>
|
||||
|
||||
<P align=left><FONT SIZE=6 COLOR="#000a3f" FACE="Arial, Helvetica"><B>%%TITLE%%</B></FONT>
|
||||
<BR><IMG SRC="<?imgprefix?>/opal/pencil-line.gif" WIDTH=345 HEIGHT=23></P>
|
||||
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
||||
|
||||
<IMG SRC="<?imgprefix?>/dot.gif" WIDTH=1 HEIGHT=5><BR>
|
||||
|
||||
<TABLE WIDTH=100%>
|
||||
<TR><TD ALIGN=LEFT>
|
||||
%%BODY%%
|
||||
</TD>
|
||||
<TD WIDTH=20> </TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR ALIGN=RIGHT>
|
||||
<TD> </TD><TD> </TD>
|
||||
<TD>
|
||||
<P> <P>
|
||||
<FONT FACE="Arial, Helvetica" SIZE="-2">
|
||||
<A HREF="/tos.html">Terms of Service</A><BR>
|
||||
<A HREF="/privacy.bml">Privacy Policy</A> -
|
||||
<A HREF="/legal/coppa.bml">COPPA</A>
|
||||
</FONT>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
<=PAGE
|
||||
|
||||
267
local/cgi-bin/bml/scheme/redwhite.look
Executable file
267
local/cgi-bin/bml/scheme/redwhite.look
Executable file
@@ -0,0 +1,267 @@
|
||||
#
|
||||
# Welcome to GENERIC.LOOK for the WhiteBlue scheme
|
||||
#
|
||||
# by....
|
||||
# Brad Fitzpatrick
|
||||
# brad@danga.com
|
||||
#
|
||||
|
||||
######################### little stuff
|
||||
|
||||
_parent=>global.look
|
||||
|
||||
AL=>{P}<I><A HREF="%%DATA1%%">%%DATA2%%</A></I> <IMG SRC="/img/external_link.gif" WIDTH=16 HEIGHT=11 ALIGN=ABSMIDDLE>
|
||||
AWAYLINK=>{P}<I><A HREF="%%DATA1%%">%%DATA2%%</A></I> <IMG SRC="/img/external_link.gif" WIDTH=16 HEIGHT=11 ALIGN=ABSMIDDLE>
|
||||
|
||||
H1=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000"><B>%%DATA%%</B></FONT>
|
||||
H1/FOLLOW_CHOICES=>{D}<FONT FACE="Arial,Helvetica" COLOR="#CC0000"><B>%%DATA%%</B></FONT>
|
||||
HEAD1=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000"><B>%%DATA%%</B></FONT>
|
||||
|
||||
H2=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000" SIZE=-1><B>%%DATA%%</B></FONT>
|
||||
HEAD2=>{D}<P><FONT FACE="Arial,Helvetica" COLOR="#CC0000" SIZE=-1><B>%%DATA%%</B></FONT>
|
||||
|
||||
# Banner Header: search results banner, content desriptor, etc...
|
||||
BH=>{D}<P ALIGN=CENTER><FONT FACE="Arial,Helvetica" COLOR="#CC0000" SIZE=-1><B>%%DATA%%</B></FONT>
|
||||
|
||||
GRIN=><grin>
|
||||
HR=><P ALIGN="CENTER"><FONT COLOR=BLUE>*</FONT></P>
|
||||
|
||||
NEWLINE=>{D}<BR>
|
||||
P=>{D}<BR>%%DATA%%
|
||||
P/FOLLOW_P=>{D}<BR><IMG SRC="/img/dot.gif" WIDTH=1 VSPACE=6 HEIGHT=1><BR>%%DATA%%
|
||||
|
||||
STANDOUTO<=
|
||||
{D}<CENTER><FONT SIZE=1><BR></FONT><TABLE ALIGN=CENTER CELLPADDING=8 BORDER=1 BGCOLOR=#CCCCFF BORDERCOLORLIGHT=#DDDDFF
|
||||
BORDERCOLORDARK=#BBBBFF><TR><TD VALIGN=CENTER>
|
||||
%%DATA%%
|
||||
|
||||
</TD></TR></TABLE></CENTER>
|
||||
<=STANDOUTO
|
||||
|
||||
STANDOUT<=
|
||||
{D}<CENTER><FONT SIZE=1><BR></FONT>
|
||||
<table cellspacing=0 cellpadding=0 border=0 bgcolor="#ccccff">
|
||||
<tr>
|
||||
<td width=7 align=left valign=top>
|
||||
<img width=7 height=7 src="/img/corn_nw.gif" alt=""></td>
|
||||
<td height=7>
|
||||
<img height=7 src="/img/dot.gif" alt=""></td>
|
||||
<td width=7 valign=top align=right>
|
||||
<img height=7 src="/img/corn_ne.gif" alt=""></td>
|
||||
</tr><tr>
|
||||
<td width=7>
|
||||
<img width=7 height=1 src="/img/dot.gif" alt=""></td>
|
||||
<td valign=top>
|
||||
%%DATA%%
|
||||
|
||||
</td>
|
||||
<td width=7>
|
||||
<img width=7 height=1 src="/img/dot.gif" alt=""></td>
|
||||
</tr><tr>
|
||||
<td width=7 align=left valign=top>
|
||||
<img width=7 height=7 src="/img/corn_sw.gif" alt=""></td>
|
||||
<td height=7>
|
||||
<img height=7 src="/img/dot.gif" alt=""></td>
|
||||
<td width=7 valign=top align=right>
|
||||
<img height=7 src="/img/corn_se.gif" alt=""></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</CENTER>
|
||||
<=STANDOUT
|
||||
SOERROR=><div style='background-color:#f3f4fe; color:red; font-weight:bold; text-align:center'>%%data%%</div>
|
||||
EMAILEX=><div style='width: 50%; font-family: courier; background-color: #efefef; border: dotted #cdcdcd 2px; padding: 5px;'>%%data%%</div>
|
||||
|
||||
######################### choices stuff
|
||||
|
||||
CHOICE=>{P}<DT><A HREF="%%DATA2%%"><FONT FACE="Arial,Helvetica"><B>%%DATA1%%</B></FONT></A><DD><FONT SIZE="2">%%DATA3%%</FONT>
|
||||
|
||||
CHOICES<=
|
||||
{F}<P><DIV CLASS="choice"><TABLE WIDTH="100%" CELLPADDING="2" CELLSPACING="5">
|
||||
<TR>
|
||||
<TD VALIGN=TOP WIDTH="50%">
|
||||
<DL>
|
||||
%%ITEMS%%
|
||||
</DL>
|
||||
</TD>
|
||||
<TD VALIGN=TOP WIDTH="50%">
|
||||
<DL>
|
||||
%%ITEMSB%%
|
||||
</DL>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE></DIV>
|
||||
<=CHOICES
|
||||
|
||||
##################################################################################
|
||||
################################### MAIN PAGE ####################################
|
||||
##################################################################################
|
||||
|
||||
PAGE<=
|
||||
{Fps}<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML><?load_page_info?>
|
||||
<HEAD>
|
||||
<title><?_code {
|
||||
my $elhash = $_[2];
|
||||
return $elhash->{'WINDOWTITLE'} || $elhash->{'TITLE'};
|
||||
} _code?></title>
|
||||
%%head%%
|
||||
<?_code
|
||||
use strict;
|
||||
my $crumb_up;
|
||||
if(LJ::get_active_crumb() ne '')
|
||||
{
|
||||
my $parentcrumb = LJ::get_parent_crumb();
|
||||
$crumb_up = "<link rel='up' title='$parentcrumb->[0]' href='$parentcrumb->[1]' />";
|
||||
}
|
||||
return $crumb_up;
|
||||
_code?>
|
||||
</HEAD>
|
||||
|
||||
<BODY BGCOLOR=#FFFFFF TOPMARGIN="0" LEFTMARGIN="0" MARGINHEIGHT="0" MARGINWIDTH="0" LINK=#0000C0 VLINK=#600060 %%bodyopts%%>
|
||||
|
||||
<TABLE WIDTH=100% BORDER=0 CELLPADDING=0 CELLSPACING=0 BACKGROUND="/img/bluewhite/bluefade.jpg">
|
||||
<TR WIDTH=100%>
|
||||
<TD VALIGN=BOTTOM ALIGN=LEFT HEIGHT=100>
|
||||
|
||||
<TABLE BACKGROUND="" HEIGHT=95 WIDTH=100% BORDER=0>
|
||||
<TR>
|
||||
<TD WIDTH=3> </TD>
|
||||
<TD HEIGHT=53 WIDTH=406 VALIGN=BOTTOM>
|
||||
<?_code
|
||||
$is_home = (BML::get_uri() =~ m!^/(index\.bml)?!);
|
||||
if (0 && $is_home)
|
||||
{
|
||||
return '<IMG SRC="/img/bluewhite/title.gif" WIDTH=600 HEIGHT=53><!-- ';
|
||||
}
|
||||
return "";
|
||||
_code?>
|
||||
|
||||
<FONT SIZE=6 COLOR="#000a3f" FACE="Arial, Helvetica"><B>%%TITLE%%</B></FONT>
|
||||
|
||||
<?_code
|
||||
if (0 && $is_home)
|
||||
{
|
||||
return ' -->';
|
||||
}
|
||||
return "";
|
||||
_code?>
|
||||
|
||||
</TD>
|
||||
<TD VALIGN=TOP ALIGN=RIGHT>
|
||||
<?_code
|
||||
unless ($is_home) {
|
||||
return "<A HREF=\"/\"><IMG SRC=\"/img/bluewhite/home.gif\" WIDTH=35 HEIGHT=36 BORDER=0></A> ";
|
||||
}
|
||||
return "";
|
||||
_code?>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
||||
</TD></TR>
|
||||
<TR><TD bgcolor="#FFFFFF"><?breadcrumbs?></TD></TR>
|
||||
</TABLE>
|
||||
|
||||
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>
|
||||
<TR VALIGN=TOP>
|
||||
<TD WIDTH=155 BGCOLOR=#d7d9e8 NOWRAP><IMG SRC="/img/bluewhite/hline.gif" WIDTH=155 HEIGHT=25 ALT="">
|
||||
|
||||
<TABLE WIDTH=153 BORDER=0 CELLSPACING=0 CELLPADDING=0>
|
||||
<TR><TD>
|
||||
|
||||
<FONT FACE="Arial,Helvetica" SIZE=-1>
|
||||
<?_code
|
||||
|
||||
$ret = "";
|
||||
|
||||
sub dump_entry
|
||||
{
|
||||
my ($ret, $listref, $depth) = @_;
|
||||
|
||||
foreach my $mi (@$listref)
|
||||
{
|
||||
if ($depth==0) {
|
||||
$$ret .= "<P><IMG SRC=\"/img/bluewhite/bullet.gif\" WIDTH=10 HEIGHT=10 HSPACE=2 ALIGN=ABSMIDDLE>";
|
||||
} else {
|
||||
$$ret .= " " x ($depth*3+1);
|
||||
$$ret .= $mi->{'cont'} ? " " : "- ";
|
||||
}
|
||||
|
||||
my $name = $mi->{'name'};
|
||||
$name =~ s/ / /g;
|
||||
if (! defined $mi->{'uri'}) {
|
||||
if ($depth == 0) {
|
||||
$$ret .= "<B>$name</B><BR>";
|
||||
} else {
|
||||
$$ret .= "$name<BR>";
|
||||
}
|
||||
} elsif ($mi->{'match'} ?
|
||||
(BML::get_uri() =~ /$mi->{'match'}/) :
|
||||
(BML::get_uri() eq $mi->{'uri'})
|
||||
){
|
||||
$$ret .= "<B><SPAN style=\"background-color: #FFFFFF\"><FONT COLOR=#0000D0>$name</FONT></SPAN></B><BR>";
|
||||
} else {
|
||||
$$ret .= "<A HREF=\"$mi->{'uri'}\">$name</A><BR>";
|
||||
}
|
||||
|
||||
if ($mi->{'children'} &&
|
||||
($mi->{'recursematch'} ? BML::get_uri() =~ /$mi->{'recursematch'}/ : 1)) {
|
||||
&dump_entry($ret, $mi->{'children'}, $depth+1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&dump_entry(\$ret, \@sidebar, 0);
|
||||
|
||||
return $ret;
|
||||
|
||||
_code?>
|
||||
</FONT>
|
||||
|
||||
</TD></TR></TABLE>
|
||||
</TD>
|
||||
<TD ALIGN=LEFT BACKGROUND="/img/bluewhite/vline.gif" WIDTH=25 NOWRAP>
|
||||
<IMG SRC="/img/bluewhite/linetop.gif" WIDTH=25 HEIGHT=25 ALT=""><BR>
|
||||
<IMG SRC="/img/bluewhite/vline.gif" WIDTH=25 HEIGHT=800 ALT="">
|
||||
</TD>
|
||||
<TD>
|
||||
|
||||
<IMG SRC="/img/dot.gif" WIDTH=1 HEIGHT=3><BR>
|
||||
%%BODY%%
|
||||
|
||||
</TD>
|
||||
<TD WIDTH=20> </TD>
|
||||
</TR>
|
||||
|
||||
<!-- table closure row -->
|
||||
<TR>
|
||||
<TD WIDTH=155 NOWRAP><IMG SRC="/img/bluewhite/sidebarfade.gif" WIDTH=155 HEIGHT=25 ALT=""></TD>
|
||||
<TD WIDTH=25 NOWRAP><IMG SRC="/img/bluewhite/sidebarfade_line.gif" WIDTH=25 HEIGHT=25 ALT=""></TD></TD>
|
||||
<TD>
|
||||
|
||||
</TD>
|
||||
<TD WIDTH=20> </TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
<!-- /table closure row -->
|
||||
|
||||
<!--<TABLE WIDTH=100%>
|
||||
<TR>
|
||||
<TD ALIGN=RIGHT>
|
||||
<FONT FACE="Arial, Helvetica" SIZE="-2">
|
||||
<A HREF="/privacy.bml">Privacy Policy</A> -
|
||||
<A HREF="/coppa.bml">COPPA</A><BR>
|
||||
<A HREF="/disclaimer.bml">Legal Disclaimer</A> -
|
||||
<A HREF="/sitemap.bml">Site Map</A><BR>
|
||||
</FONT>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
-->
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
<=PAGE
|
||||
|
||||
32
local/cgi-bin/bml/scheme/vari
Executable file
32
local/cgi-bin/bml/scheme/vari
Executable file
@@ -0,0 +1,32 @@
|
||||
ljrlook.nav.create
|
||||
ljrlook.nav.update
|
||||
ljrlook.nav.fullupdate
|
||||
ljrlook.nav.site
|
||||
ljrlook.nav.news
|
||||
ljrlook.nav.paidaccounts
|
||||
ljrlook.nav.edit
|
||||
ljrlook.nav.modify
|
||||
ljrlook.nav.editinfo
|
||||
ljrlook.nav.editfriends
|
||||
ljrlook.nav.editjournal
|
||||
ljrlook.nav.editpics
|
||||
ljrlook.nav.changepassword
|
||||
ljrlook.nav.communities.manage
|
||||
ljrlook.nav.frills
|
||||
ljrlook.nav.customize
|
||||
ljrlook.nav.createstyle
|
||||
ljrlook.nav.editstyle
|
||||
ljrlook.nav.needhelp
|
||||
ljrlook.nav.lostinfo
|
||||
ljrlook.nav.support.faq
|
||||
ljrlook.nav.support
|
||||
|
||||
ljrlook.nav.hello
|
||||
ljrlook.nav.yourjournal
|
||||
ljrlook.nav.recent
|
||||
ljrlook.nav.calendar
|
||||
ljrlook.nav.friends
|
||||
ljrlook.nav.userinfo
|
||||
ljrlook.nav.memories
|
||||
ljrlook.nav.logout
|
||||
ljrlook.nav.login
|
||||
1618
local/cgi-bin/cleanhtml.pl
Executable file
1618
local/cgi-bin/cleanhtml.pl
Executable file
File diff suppressed because it is too large
Load Diff
75
local/cgi-bin/communitylib-local.pl
Executable file
75
local/cgi-bin/communitylib-local.pl
Executable file
@@ -0,0 +1,75 @@
|
||||
# <LJFUNC>
|
||||
# name: LJ::comm_member_request
|
||||
# des: Registers an authaction to add a user to a
|
||||
# community and sends an approval email
|
||||
#
|
||||
# taken from lilib.pl, version 1.681:
|
||||
# http://cvs.livejournal.org/browse.cgi/livejournal/cgi-bin/ljlib.pl?rev=1.681&content-type=text/x-cvsweb-markup
|
||||
#
|
||||
# returns: Hashref; output of LJ::register_authaction()
|
||||
# includes datecreate of old row if no new row was created
|
||||
# args: comm, u, attr?
|
||||
# des-comm: Community user object
|
||||
# des-u: User object to add to community
|
||||
# des-attr: array of attributes new user will have
|
||||
# </LJFUNC>
|
||||
sub comm_member_request {
|
||||
my ($comm, $u, $attr) = @_;
|
||||
return undef unless ref $comm && ref $u;
|
||||
|
||||
my $arg = join("&", "targetid=$u->{'userid'}", map { "$_=1" } sort @$attr);
|
||||
|
||||
my $dbh = LJ::get_db_writer();
|
||||
|
||||
# check for duplicates within the same hour (to prevent spamming)
|
||||
my $oldaa = $dbh->selectrow_hashref("SELECT aaid, authcode, datecreate FROM authactions " .
|
||||
"WHERE userid=? AND arg1=? " .
|
||||
"AND action='comm_invite' AND used='N' " .
|
||||
"AND NOW() < datecreate + INTERVAL 1 HOUR " .
|
||||
"ORDER BY 1 DESC LIMIT 1",
|
||||
undef, $comm->{'userid'}, $arg);
|
||||
return $oldaa if $oldaa;
|
||||
|
||||
# insert authactions row
|
||||
my $aa = LJ::register_authaction($comm->{'userid'}, 'comm_invite', $arg);
|
||||
return undef unless $aa;
|
||||
|
||||
# if there are older duplicates, invalidate any existing unused authactions of this type
|
||||
$dbh->do("UPDATE authactions SET used='Y' WHERE userid=? AND aaid<>? AND arg1=? " .
|
||||
"AND action='comm_invite' AND used='N'",
|
||||
undef, $comm->{'userid'}, $aa->{'aaid'}, $arg);
|
||||
|
||||
# email recipient user for confirmation
|
||||
$attr ||= [];
|
||||
my %attr_map = ('member' => "ÕÞÁÓÔÎÉË",
|
||||
'admin' => "ÓÍÏÔÒÉÔÅÌØ",
|
||||
'post' => "ÍÏÖÅÔ ÐÉÓÁÔØ",
|
||||
'moderate' => "ÍÏÄÅÒÁÔÏÒ",
|
||||
'preapprove' => "ÎÅÍÏÄÅÒÉÒÕÅÍÏÅ",
|
||||
);
|
||||
|
||||
my $cuser = $comm->{'user'};
|
||||
my $body = "õ×ÁÖÁÅÍÙÊ $u->{'user'},\n\n";
|
||||
$body .= "ÓÍÏÔÒÉÔÅÌØ ÓÏÏÂÝÅÓÔ×Á $cuser ($LJ::SITEROOT/community/$cuser/)\n" .
|
||||
"ÐÒÉÇÌÁÛÁÅÔ ×ÁÓ ÓÔÁÔØ ÞÌÅÎÏÍ ÓÏÏÂÝÅÓÔ×Á ÓÏ ÓÌÅÄÕÀÝÉÍÉ ÐÒÉ×ÉÌÅÇÉÑÍÉ:\n".
|
||||
join(", ", map { $attr_map{$_} } @$attr) . ".\n\n" .
|
||||
"åÓÌÉ ×Ù ÎÅ ÈÏÔÉÔÅ ÕÞÁÓÔ×Ï×ÁÔØ × $cuser, ÔÏ ÐÒÏÓÔÏ ÐÒÏÉÇÎÏÒÉÒÕÊÔÅ ÜÔÏ ÐÉÓØÍÏ.\n" .
|
||||
"ïÄÎÁËÏ, ÅÓÌÉ ×Ù ÈÏÔÉÔÅ ÐÒÉÓÏÅÄÉÎÉÔØÓÑ Ë ÓÏÏÂÝÅÓÔ×Õ, ÔÏ, ÐÏÖÁÌÕÊÓÔÁ,\n" .
|
||||
"ÐÅÒÅÊÄÉÔÅ ÐÏ ÓÓÙÌËÅ (ÎÉÖÅ × ÐÉÓØÍÅ), ÞÔÏÂÙ ÐÏÄÔ×ÅÒÄÉÔØ Ó×ÏÅ ÓÏÇÌÁÓÉÅ.\n\n" .
|
||||
" $LJ::SITEROOT/approve/$aa->{'aaid'}.$aa->{'authcode'}\n\n" .
|
||||
"\nëÏÍÁÎÄÁ $LJ::SITENAME\n";
|
||||
|
||||
LJ::send_mail({
|
||||
'to' => $u->{'email'},
|
||||
'from' => $LJ::COMMUNITY_EMAIL,
|
||||
'fromname' => $LJ::SITENAME,
|
||||
'charset' => 'koi8-r',
|
||||
'subject' => "þÌÅÎÓÔ×Ï × ÓÏÏÂÝÅÓÔ×Å: $cuser",
|
||||
'body' => $body
|
||||
});
|
||||
|
||||
return $aa;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
529
local/cgi-bin/communitylib.pl
Executable file
529
local/cgi-bin/communitylib.pl
Executable file
@@ -0,0 +1,529 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
package LJ;
|
||||
|
||||
use strict;
|
||||
|
||||
require "$ENV{'LJHOME'}/cgi-bin/communitylib-local.pl"
|
||||
if -e "$ENV{'LJHOME'}/cgi-bin/communitylib-local.pl";
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::get_sent_invites
|
||||
# des: Get a list of sent invitations from the past 30 days.
|
||||
# args: cuserid
|
||||
# des-cuserid: a userid or u object of the community to get sent invitations for
|
||||
# returns: hashref of arrayrefs with keys userid, maintid, recvtime, status, args (itself
|
||||
# a hashref if what abilities the user would be given)
|
||||
# </LJFUNC>
|
||||
sub get_sent_invites {
|
||||
my $cu = shift;
|
||||
$cu = LJ::want_user($cu);
|
||||
return undef unless $cu;
|
||||
|
||||
# now hit the database for their recent invites
|
||||
my $dbcr = LJ::get_cluster_def_reader($cu);
|
||||
return LJ::error('db') unless $dbcr;
|
||||
my $data = $dbcr->selectall_arrayref('SELECT userid, maintid, recvtime, status, args FROM invitesent ' .
|
||||
'WHERE commid = ? AND recvtime > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
|
||||
undef, $cu->{userid});
|
||||
|
||||
# now break data down into usable format for caller
|
||||
my @res;
|
||||
foreach my $row (@{$data || []}) {
|
||||
my $temp = {};
|
||||
LJ::decode_url_string($row->[4], $temp);
|
||||
push @res, {
|
||||
userid => $row->[0]+0,
|
||||
maintid => $row->[1]+0,
|
||||
recvtime => $row->[2],
|
||||
status => $row->[3],
|
||||
args => $temp,
|
||||
};
|
||||
}
|
||||
|
||||
# all done
|
||||
return \@res;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::send_comm_invite
|
||||
# des: Sends an invitation to a user to join a community with the passed abilities.
|
||||
# args: uuserid, cuserid, muserid, attrs
|
||||
# des-uuserid: a userid or u object of the user to invite
|
||||
# des-cuserid: a userid or u object of the community to invite the user to
|
||||
# des-muserid: a userid or u object of the maintainer doing the inviting
|
||||
# des-attrs: a hashref of abilities this user should have (e.g. member, post, unmoderated, ...)
|
||||
# returns: 1 for success, undef if failure
|
||||
# </LJFUNC>
|
||||
sub send_comm_invite {
|
||||
my ($u, $cu, $mu, $attrs) = @_;
|
||||
$u = LJ::want_user($u);
|
||||
$cu = LJ::want_user($cu);
|
||||
$mu = LJ::want_user($mu);
|
||||
return undef unless $u && $cu && $mu;
|
||||
|
||||
# step 1: if the user has banned the community, don't accept the invite
|
||||
return LJ::error('comm_user_has_banned') if LJ::is_banned($cu, $u);
|
||||
|
||||
# step 2: outstanding invite?
|
||||
my $dbcr = LJ::get_cluster_def_reader($u);
|
||||
return LJ::error('db') unless $dbcr;
|
||||
my $argstr = $dbcr->selectrow_array('SELECT args FROM inviterecv WHERE userid = ? AND commid = ? ' .
|
||||
'AND recvtime > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
|
||||
undef, $u->{userid}, $cu->{userid});
|
||||
|
||||
# step 3: exceeded outstanding invitation limit? only if no outstanding invite
|
||||
unless ($argstr) {
|
||||
my $cdbcr = LJ::get_cluster_def_reader($cu);
|
||||
return LJ::error('db') unless $cdbcr;
|
||||
my $count = $cdbcr->selectrow_array("SELECT COUNT(*) FROM invitesent WHERE commid = ? AND userid <> ? AND status = 'outstanding'",
|
||||
undef, $cu->{userid}, $u->{userid});
|
||||
my $fr = LJ::get_friends($cu) || {};
|
||||
my $max = int(scalar(keys %$fr) / 10); # can invite up to 1/10th of the community
|
||||
$max = 50 if $max < 50; # or 50, whichever is greater
|
||||
return LJ::error('comm_invite_limit') if $count > $max;
|
||||
}
|
||||
|
||||
# step 4: setup arg string as url-encoded string
|
||||
my $newargstr = join('=1&', map { LJ::eurl($_) } @$attrs) . '=1';
|
||||
|
||||
# step 5: delete old stuff (lazy cleaning of invite tables)
|
||||
return LJ::error('db') unless $u->writer;
|
||||
$u->do('DELETE FROM inviterecv WHERE userid = ? AND ' .
|
||||
'recvtime < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
|
||||
undef, $u->{userid});
|
||||
|
||||
return LJ::error('db') unless $cu->writer;
|
||||
$cu->do('DELETE FROM invitesent WHERE commid = ? AND ' .
|
||||
'recvtime < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
|
||||
undef, $cu->{userid});
|
||||
|
||||
# step 6: branch here to update or insert
|
||||
if ($argstr) {
|
||||
# merely an update, so just do it quietly
|
||||
$u->do("UPDATE inviterecv SET args = ? WHERE userid = ? AND commid = ?",
|
||||
undef, $newargstr, $u->{userid}, $cu->{userid});
|
||||
|
||||
$cu->do("UPDATE invitesent SET args = ?, status = 'outstanding' WHERE userid = ? AND commid = ?",
|
||||
undef, $newargstr, $cu->{userid}, $u->{userid});
|
||||
} else {
|
||||
# insert new data, as this is a new invite
|
||||
$u->do("INSERT INTO inviterecv VALUES (?, ?, ?, UNIX_TIMESTAMP(), ?)",
|
||||
undef, $u->{userid}, $cu->{userid}, $mu->{userid}, $newargstr);
|
||||
|
||||
$cu->do("REPLACE INTO invitesent VALUES (?, ?, ?, UNIX_TIMESTAMP(), 'outstanding', ?)",
|
||||
undef, $cu->{userid}, $u->{userid}, $mu->{userid}, $newargstr);
|
||||
}
|
||||
|
||||
# step 7: error check database work
|
||||
return LJ::error('db') if $u->err || $cu->err;
|
||||
|
||||
# create authaction and send email to user
|
||||
LJ::comm_member_request($cu, $u, $attrs) if $LJ::LJR_EMAIL_COMM_INVITES;
|
||||
|
||||
# success
|
||||
return 1;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::accept_comm_invite
|
||||
# des: Accepts an invitation a user has received. This does all the work to make the
|
||||
# user join the community as well as sets up privileges.
|
||||
# args: uuserid, cuserid
|
||||
# des-uuserid: a userid or u object of the user to get pending invites for
|
||||
# des-cuserid: a userid or u object of the community to reject the invitation from
|
||||
# returns: 1 for success, undef if failure
|
||||
# </LJFUNC>
|
||||
sub accept_comm_invite {
|
||||
my ($u, $cu) = @_;
|
||||
$u = LJ::want_user($u);
|
||||
$cu = LJ::want_user($cu);
|
||||
return undef unless $u && $cu;
|
||||
|
||||
# get their invite to make sure they have one
|
||||
my $dbcr = LJ::get_cluster_def_reader($u);
|
||||
return LJ::error('db') unless $dbcr;
|
||||
my $argstr = $dbcr->selectrow_array('SELECT args FROM inviterecv WHERE userid = ? AND commid = ? ' .
|
||||
'AND recvtime > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
|
||||
undef, $u->{userid}, $cu->{userid});
|
||||
return undef unless $argstr;
|
||||
|
||||
# decode to find out what they get
|
||||
my $args = {};
|
||||
LJ::decode_url_string($argstr, $args);
|
||||
|
||||
# valid invite. let's accept it as far as the community listing us goes.
|
||||
# 0, 0 means don't add comm to user's friends list, and don't auto-add P edge.
|
||||
LJ::join_community($u, $cu, 0, 0) if $args->{member};
|
||||
|
||||
# now grant necessary abilities
|
||||
my %edgelist = (
|
||||
post => 'P',
|
||||
preapprove => 'N',
|
||||
moderate => 'M',
|
||||
admin => 'A',
|
||||
);
|
||||
foreach (keys %edgelist) {
|
||||
LJ::set_rel($cu->{userid}, $u->{userid}, $edgelist{$_}) if $args->{$_};
|
||||
}
|
||||
|
||||
# now we can delete the invite and update the status on the other side
|
||||
return LJ::error('db') unless $u->writer;
|
||||
$u->do("DELETE FROM inviterecv WHERE userid = ? AND commid = ?",
|
||||
undef, $u->{userid}, $cu->{userid});
|
||||
|
||||
return LJ::error('db') unless $cu->writer;
|
||||
$cu->do("UPDATE invitesent SET status = 'accepted' WHERE commid = ? AND userid = ?",
|
||||
undef, $cu->{userid}, $u->{userid});
|
||||
|
||||
# done
|
||||
return 1;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::reject_comm_invite
|
||||
# des: Rejects an invitation a user has received.
|
||||
# args: uuserid, cuserid
|
||||
# des-uuserid: a userid or u object of the user to get pending invites for
|
||||
# des-cuserid: a userid or u object of the community to reject the invitation from
|
||||
# returns: 1 for success, undef if failure
|
||||
# </LJFUNC>
|
||||
sub reject_comm_invite {
|
||||
my ($u, $cu) = @_;
|
||||
$u = LJ::want_user($u);
|
||||
$cu = LJ::want_user($cu);
|
||||
return undef unless $u && $cu;
|
||||
|
||||
# get their invite to make sure they have one
|
||||
my $dbcr = LJ::get_cluster_def_reader($u);
|
||||
return LJ::error('db') unless $dbcr;
|
||||
my $test = $dbcr->selectrow_array('SELECT userid FROM inviterecv WHERE userid = ? AND commid = ? ' .
|
||||
'AND recvtime > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
|
||||
undef, $u->{userid}, $cu->{userid});
|
||||
return undef unless $test;
|
||||
|
||||
# now just reject it
|
||||
return LJ::error('db') unless $u->writer;
|
||||
$u->do("DELETE FROM inviterecv WHERE userid = ? AND commid = ?",
|
||||
undef, $u->{userid}, $cu->{userid});
|
||||
|
||||
return LJ::error('db') unless $cu->writer;
|
||||
$cu->do("UPDATE invitesent SET status = 'rejected' WHERE commid = ? AND userid = ?",
|
||||
undef, $cu->{userid}, $u->{userid});
|
||||
|
||||
# done
|
||||
return 1;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::get_pending_invites
|
||||
# des: Gets a list of pending invitations for a user to join a community.
|
||||
# args: uuserid
|
||||
# des-uuserid: a userid or u object of the user to get pending invites for
|
||||
# returns: [ [ commid, maintainerid, time, args(url encoded) ], [ ... ], ... ] or
|
||||
# undef if failure
|
||||
# </LJFUNC>
|
||||
sub get_pending_invites {
|
||||
my $u = shift;
|
||||
$u = LJ::want_user($u);
|
||||
return undef unless $u;
|
||||
|
||||
# hit up database for invites and return them
|
||||
my $dbcr = LJ::get_cluster_def_reader($u);
|
||||
return LJ::error('db') unless $dbcr;
|
||||
my $pending = $dbcr->selectall_arrayref('SELECT commid, maintid, recvtime, args FROM inviterecv WHERE userid = ? ' .
|
||||
'AND recvtime > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY))',
|
||||
undef, $u->{userid});
|
||||
return undef if $dbcr->err;
|
||||
return $pending;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::leave_community
|
||||
# des: Makes a user leave a community. Takes care of all reluser and friend stuff.
|
||||
# args: uuserid, ucommid, defriend
|
||||
# des-uuserid: a userid or u object of the user doing the leaving
|
||||
# des-ucommid: a userid or u object of the community being left
|
||||
# des-defriend: remove comm from user's friends list
|
||||
# returns: 1 if success, undef if error of some sort (ucommid not a comm, uuserid not in
|
||||
# comm, db error, etc)
|
||||
# </LJFUNC>
|
||||
sub leave_community {
|
||||
my ($uuid, $ucid, $defriend) = @_;
|
||||
my $u = LJ::want_user($uuid);
|
||||
my $cu = LJ::want_user($ucid);
|
||||
$defriend = $defriend ? 1 : 0;
|
||||
return LJ::error('comm_not_found') unless $u && $cu;
|
||||
|
||||
# defriend comm -> user
|
||||
return LJ::error('comm_not_comm') unless $cu->{journaltype} =~ /[CS]/;
|
||||
my $ret = LJ::remove_friend($cu->{userid}, $u->{userid});
|
||||
return LJ::error('comm_not_member') unless $ret; # $ret = number of rows deleted, should be 1 if the user was in the comm
|
||||
|
||||
# clear edges that effect this relationship
|
||||
foreach my $edge (qw(P N A M)) {
|
||||
LJ::clear_rel($cu->{userid}, $u->{userid}, $edge);
|
||||
}
|
||||
|
||||
# defriend user -> comm?
|
||||
return 1 unless $defriend;
|
||||
LJ::remove_friend($u, $cu);
|
||||
|
||||
# don't care if we failed the removal of comm from user's friends list...
|
||||
return 1;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::join_community
|
||||
# des: Makes a user join a community. Takes care of all reluser and friend stuff.
|
||||
# args: uuserid, ucommid, friend?, noauto?
|
||||
# des-uuserid: a userid or u object of the user doing the joining
|
||||
# des-ucommid: a userid or u object of the community being joined
|
||||
# des-friend: 1 to add this comm to user's friends list, else not
|
||||
# des-noauto: if defined, 1 adds P edge, 0 does not; else, base on community postlevel
|
||||
# returns: 1 if success, undef if error of some sort (ucommid not a comm, uuserid already in
|
||||
# comm, db error, etc)
|
||||
# </LJFUNC>
|
||||
sub join_community {
|
||||
my ($uuid, $ucid, $friend, $canpost) = @_;
|
||||
my $u = LJ::want_user($uuid);
|
||||
my $cu = LJ::want_user($ucid);
|
||||
$friend = $friend ? 1 : 0;
|
||||
return LJ::error('comm_not_found') unless $u && $cu;
|
||||
return LJ::error('comm_not_comm') unless $cu->{journaltype} eq 'C';
|
||||
|
||||
# friend comm -> user
|
||||
LJ::add_friend($cu->{userid}, $u->{userid});
|
||||
|
||||
# add edges that effect this relationship... if the user sent a fourth
|
||||
# argument, use that as a bool. else, load commrow and use the postlevel.
|
||||
my $addpostacc = 0;
|
||||
if (defined $canpost) {
|
||||
$addpostacc = $canpost ? 1 : 0;
|
||||
} else {
|
||||
my $crow = LJ::get_community_row($cu);
|
||||
$addpostacc = $crow->{postlevel} eq 'members' ? 1 : 0;
|
||||
}
|
||||
LJ::set_rel($cu->{userid}, $u->{userid}, 'P') if $addpostacc;
|
||||
|
||||
# friend user -> comm?
|
||||
return 1 unless $friend;
|
||||
LJ::add_friend($u->{userid}, $cu->{userid}, { defaultview => 1 });
|
||||
|
||||
# done
|
||||
return 1;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::get_community_row
|
||||
# des: Gets data relevant to a community such as their membership level and posting access.
|
||||
# args: ucommid
|
||||
# des-ucommid: a userid or u object of the community
|
||||
# returns: a hashref with user, userid, name, membership, and postlevel data from the
|
||||
# user and community tables; undef if error
|
||||
# </LJFUNC>
|
||||
sub get_community_row {
|
||||
my $ucid = shift;
|
||||
my $cu = LJ::want_user($ucid);
|
||||
return unless $cu;
|
||||
|
||||
# hit up database
|
||||
my $dbr = LJ::get_db_reader();
|
||||
my ($membership, $postlevel) =
|
||||
$dbr->selectrow_array('SELECT membership, postlevel FROM community WHERE userid=?',
|
||||
undef, $cu->{userid});
|
||||
return if $dbr->err;
|
||||
return unless $membership && $postlevel;
|
||||
|
||||
# return result hashref
|
||||
my $row = {
|
||||
user => $cu->{user},
|
||||
userid => $cu->{userid},
|
||||
name => $cu->{name},
|
||||
membership => $membership,
|
||||
postlevel => $postlevel,
|
||||
};
|
||||
return $row;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::get_pending_members
|
||||
# des: Gets a list of userids for people that have requested to be added to a community
|
||||
# but haven't yet actually been approved or rejected.
|
||||
# args: comm
|
||||
# des-comm: a userid or u object of the community to get pending members of
|
||||
# returns: an arrayref of userids of people with pending membership requests
|
||||
# </LJFUNC>
|
||||
sub get_pending_members {
|
||||
my $comm = shift;
|
||||
my $cu = LJ::want_user($comm);
|
||||
|
||||
# database request
|
||||
my $dbr = LJ::get_db_reader();
|
||||
my $args = $dbr->selectcol_arrayref('SELECT arg1 FROM authactions WHERE userid = ? ' .
|
||||
"AND action = 'comm_join_request' AND used = 'N'",
|
||||
undef, $cu->{userid}) || [];
|
||||
|
||||
# parse out the args
|
||||
my @list;
|
||||
foreach (@$args) {
|
||||
push @list, $1+0 if $_ =~ /^targetid=(\d+)$/;
|
||||
}
|
||||
return \@list;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::approve_pending_member
|
||||
# des: Approves someone's request to join a community. This updates the authactions table
|
||||
# as appropriate as well as does the regular join logic. This also generates an email to
|
||||
# be sent to the user notifying them of the acceptance.
|
||||
# args: commid, userid
|
||||
# des-commid: userid of the community
|
||||
# des-userid: userid of the user doing the join
|
||||
# returns: 1 on success, 0/undef on error
|
||||
# </LJFUNC>
|
||||
sub approve_pending_member {
|
||||
my ($commid, $userid) = @_;
|
||||
my $cu = LJ::want_user($commid);
|
||||
my $u = LJ::want_user($userid);
|
||||
return unless $cu && $u;
|
||||
|
||||
# step 1, update authactions table
|
||||
my $dbh = LJ::get_db_writer();
|
||||
my $count = $dbh->do("UPDATE authactions SET used = 'Y' WHERE userid = ? AND arg1 = ?",
|
||||
undef, $cu->{userid}, "targetid=$u->{userid}");
|
||||
return unless $count;
|
||||
|
||||
# step 2, make user join the community
|
||||
return unless LJ::join_community($u->{userid}, $cu->{userid});
|
||||
|
||||
# step 3, email the user
|
||||
my $email = "Dear $u->{name},\n\n" .
|
||||
"Your request to join the \"$cu->{user}\" community has been approved. If you " .
|
||||
"wish to add this community to your friends page reading list, click the link below.\n\n" .
|
||||
"\t$LJ::SITEROOT/friends/add.bml?user=$cu->{user}\n\n" .
|
||||
"Regards,\n$LJ::SITENAME Team";
|
||||
LJ::send_mail({
|
||||
to => $u->{email},
|
||||
from => $LJ::COMMUNITY_EMAIL,
|
||||
fromname => $LJ::SITENAME,
|
||||
charset => 'utf-8',
|
||||
subject => "Your Request to Join $cu->{user}",
|
||||
body => $email,
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::reject_pending_member
|
||||
# des: Rejects someone's request to join a community. Updates authactions and generates
|
||||
# an email to the user.
|
||||
# args: commid, userid
|
||||
# des-commid: userid of the community
|
||||
# des-userid: userid of the user doing the join
|
||||
# returns: 1 on success, 0/undef on error
|
||||
# </LJFUNC>
|
||||
sub reject_pending_member {
|
||||
my ($commid, $userid) = @_;
|
||||
my $cu = LJ::want_user($commid);
|
||||
my $u = LJ::want_user($userid);
|
||||
return unless $cu && $u;
|
||||
|
||||
# step 1, update authactions table
|
||||
my $dbh = LJ::get_db_writer();
|
||||
my $count = $dbh->do("UPDATE authactions SET used = 'Y' WHERE userid = ? AND arg1 = ?",
|
||||
undef, $cu->{userid}, "targetid=$u->{userid}");
|
||||
return unless $count;
|
||||
|
||||
# step 2, email the user
|
||||
my $email = "Dear $u->{name},\n\n" .
|
||||
"Your request to join the \"$cu->{user}\" community has been declined. You " .
|
||||
"may wish to contact the maintainer(s) of this community if you are still " .
|
||||
"interested in joining.\n\n" .
|
||||
"Regards,\n$LJ::SITENAME Team";
|
||||
LJ::send_mail({
|
||||
to => $u->{email},
|
||||
from => $LJ::COMMUNITY_EMAIL,
|
||||
fromname => $LJ::SITENAME,
|
||||
charset => 'utf-8',
|
||||
subject => "Your Request to Join $cu->{user}",
|
||||
body => $email,
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::comm_join_request
|
||||
# des: Registers an authaction to add a user to a
|
||||
# community and sends an approval email to the maintainers
|
||||
# returns: Hashref; output of LJ::register_authaction()
|
||||
# includes datecreate of old row if no new row was created
|
||||
# args: comm, u
|
||||
# des-comm: Community user object
|
||||
# des-u: User object to add to community
|
||||
# </LJFUNC>
|
||||
sub comm_join_request {
|
||||
my ($comm, $u) = @_;
|
||||
return undef unless ref $comm && ref $u;
|
||||
|
||||
my $arg = "targetid=$u->{userid}";
|
||||
my $dbh = LJ::get_db_writer();
|
||||
|
||||
# check for duplicates within the same hour (to prevent spamming)
|
||||
my $oldaa = $dbh->selectrow_hashref("SELECT aaid, authcode, datecreate FROM authactions " .
|
||||
"WHERE userid=? AND arg1=? " .
|
||||
"AND action='comm_join_request' AND used='N' " .
|
||||
"AND NOW() < datecreate + INTERVAL 1 HOUR " .
|
||||
"ORDER BY 1 DESC LIMIT 1",
|
||||
undef, $comm->{'userid'}, $arg);
|
||||
return $oldaa if $oldaa;
|
||||
|
||||
# insert authactions row
|
||||
my $aa = LJ::register_authaction($comm->{'userid'}, 'comm_join_request', $arg);
|
||||
return undef unless $aa;
|
||||
|
||||
# if there are older duplicates, invalidate any existing unused authactions of this type
|
||||
$dbh->do("UPDATE authactions SET used='Y' WHERE userid=? AND aaid<>? AND arg1=? " .
|
||||
"AND action='comm_invite' AND used='N'",
|
||||
undef, $comm->{'userid'}, $aa->{'aaid'}, $arg);
|
||||
|
||||
# get maintainers of community
|
||||
my $adminids = LJ::load_rel_user($comm->{userid}, 'A') || [];
|
||||
my $admins = LJ::load_userids(@$adminids);
|
||||
|
||||
# now prepare the emails
|
||||
my %dests;
|
||||
my $cuser = $comm->{user};
|
||||
foreach my $au (values %$admins) {
|
||||
next if $dests{$au->{email}}++;
|
||||
LJ::load_user_props($au, 'opt_communityjoinemail');
|
||||
next if $au->{opt_communityjoinemail} =~ /[DN]/; # Daily, None
|
||||
|
||||
my $body = "Dear $au->{name},\n\n" .
|
||||
"The user \"$u->{user}\" has requested to join the \"$cuser\" community. If you wish " .
|
||||
"to add this user to your community, please click this link:\n\n" .
|
||||
"\t$LJ::SITEROOT/approve/$aa->{aaid}.$aa->{authcode}\n\n" .
|
||||
"Alternately, to approve or reject all outstanding membership requests at the same time, " .
|
||||
"visit the community member management page:\n\n" .
|
||||
"\t$LJ::SITEROOT/community/pending.bml?comm=$cuser\n\n" .
|
||||
"You may also ignore this e-mail. The request to join will expire after a period of 30 days.\n\n" .
|
||||
"If you wish to no longer receive these e-mails, visit the community management page and " .
|
||||
"set the relevant options:\n\n\t$LJ::SITEROOT/community/manage.bml\n\n" .
|
||||
"Regards,\n$LJ::SITENAME Team\n";
|
||||
|
||||
LJ::send_mail({
|
||||
to => $au->{email},
|
||||
from => $LJ::COMMUNITY_EMAIL,
|
||||
fromname => $LJ::SITENAME,
|
||||
charset => 'utf-8',
|
||||
subject => "$cuser Membership Request by $u->{user}",
|
||||
body => $body,
|
||||
wrap => 76,
|
||||
});
|
||||
}
|
||||
|
||||
return $aa;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
738
local/cgi-bin/console-local.pl
Executable file
738
local/cgi-bin/console-local.pl
Executable file
@@ -0,0 +1,738 @@
|
||||
use LJ::MemCache;
|
||||
use Golem;
|
||||
|
||||
$cmd{'syn_delete'} = {
|
||||
'handler' => \&syn_delete,
|
||||
'privs' => [qw(syn_edit)],
|
||||
'des' => "Deletes syndication. Totally.",
|
||||
'argsummary' => '<username>',
|
||||
'args' => [
|
||||
'username' => "The username of the syndicated journal.",
|
||||
],
|
||||
};
|
||||
|
||||
$cmd{'accounts_by_ip'} = {
|
||||
'handler' => \&accounts_by_ip,
|
||||
'privs' => [qw(finduser)],
|
||||
'des' => "Find accounts registered from given ip",
|
||||
'argsummary' => '<ip>',
|
||||
'args' => [
|
||||
'ip' => "IP address or beginning of IP address",
|
||||
],
|
||||
};
|
||||
|
||||
$cmd{'expunge_user'} = {
|
||||
'handler' => \&expunge_user,
|
||||
'privs' => [qw(suspend)],
|
||||
'des' => "Expunge malicious user products. For accounts with a lot of comments you might need to run this command several times.",
|
||||
'argsummary' => '<username> <userid>',
|
||||
'args' => [
|
||||
'username' => "Malicious username",
|
||||
'userid' => "Malicious userid (must match username)",
|
||||
],
|
||||
};
|
||||
|
||||
$cmd{'expunge_anonymous_comments'} = {
|
||||
'handler' => \&expunge_anonymous_comments,
|
||||
'privs' => [qw(suspend)],
|
||||
'des' => "Expunge all anonymous comments for a given post.",
|
||||
'argsummary' => '<username> <itemid> <talkid>',
|
||||
'args' => [
|
||||
'username' => "The username of the journal comment is in",
|
||||
'itemid' => "The itemid of the post to have a comment deleted from it",
|
||||
'talkid' => "delete comments starting this thread, 0 for all",
|
||||
|
||||
# note: the ditemid, actually, but that's too internals-ish?
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
$cmd{'ljr_fif'} = {
|
||||
'handler' => \&ljr_fif,
|
||||
'privs' => [qw(siteadmin)],
|
||||
'des' => "ljr_fif manipulation.",
|
||||
'argsummary' => 'add|delete|list_excluded [<username>]',
|
||||
'args' => [
|
||||
'username' => "The username.",
|
||||
],
|
||||
};
|
||||
|
||||
$cmd{'net'} = {
|
||||
'handler' => \&net,
|
||||
'privs' => [qw(siteadmin)],
|
||||
'des' => "ip blocks manipulation.",
|
||||
'argsummary' => 'add [CIDR] name|delete <CIDR>|ban_new_accounts <CIDR>|ban_comments <CIDR>|list',
|
||||
'args' => [
|
||||
'username' => "The username.",
|
||||
],
|
||||
};
|
||||
|
||||
sub net {
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
my $err = sub { push @$out, [ "error", $_[0] ]; 0; };
|
||||
|
||||
return $err->("This command needs at least 1 argument.")
|
||||
if @$args < 1;
|
||||
|
||||
return $err->("Golem is not plugged-into this site.")
|
||||
unless $Golem::on;
|
||||
|
||||
my $action = $args->[1];
|
||||
my $cidr = $args->[2];
|
||||
|
||||
my $net_list = sub {
|
||||
my $dbr = LJ::get_db_reader();
|
||||
return $err->("Can't get database reader!") unless $dbr;
|
||||
|
||||
my $sth = $dbr->prepare("select net_v4.* from net_v4");
|
||||
$sth->execute();
|
||||
die $dbr->errstr if $dbr->err;
|
||||
|
||||
push @$out, [ '', "Known ip_v4 networks" ];
|
||||
while (my $row = $sth->fetchrow_hashref) {
|
||||
my $tnet = Golem::get_net_by_id($row->{'id'}, {"with_props" => 1});
|
||||
|
||||
my $str = $tnet->{'net_with_mask'} . " [" . $row->{'name'} . "]";
|
||||
|
||||
if ($tnet->{'props'}->{'data'}->{'ban_new_accounts'}) {
|
||||
$str .= ", ban_new_accounts";
|
||||
}
|
||||
if ($tnet->{'props'}->{'data'}->{'ban_comments'}) {
|
||||
$str .= ", ban_comments in ";
|
||||
foreach my $userid (keys %{$tnet->{'props'}->{'data'}->{'ban_comments'}}) {
|
||||
next unless $userid;
|
||||
|
||||
my $u = LJ::load_userid($userid);
|
||||
if ($u) {
|
||||
$str .= $u->{'user'} . ",";
|
||||
}
|
||||
else {
|
||||
$str .= $userid . " (user does not exist),";
|
||||
}
|
||||
}
|
||||
chop($str);
|
||||
}
|
||||
|
||||
push @$out, [ '', $str ];
|
||||
}
|
||||
};
|
||||
|
||||
if ($action eq "list") {
|
||||
$net_list->();
|
||||
}
|
||||
else {
|
||||
my $start_ip = $cidr;
|
||||
$start_ip =~ s/\/.+//o;
|
||||
|
||||
my $net_mask;
|
||||
if ($cidr =~ /\//o) {
|
||||
$net_mask = $cidr;
|
||||
$net_mask =~ s/.+\///o;
|
||||
}
|
||||
|
||||
if (!$net_mask) {
|
||||
$net_mask = "32";
|
||||
push @$out, ['', "Netmask not specified, assuming /32"];
|
||||
}
|
||||
|
||||
return $err->("Invalid CIDR format. Required: 1.2.3.4/32")
|
||||
unless $start_ip && $net_mask;
|
||||
|
||||
return $err->("Invalid IP address.")
|
||||
unless Golem::is_ipv4($start_ip);
|
||||
|
||||
if ($action eq "add") {
|
||||
my $name;
|
||||
my $j = 3;
|
||||
while ($args->[$j]) {
|
||||
$name .= $args->[$j] . " ";
|
||||
$j++;
|
||||
}
|
||||
chop($name) if $name;
|
||||
|
||||
return $err->("Please specify some net details")
|
||||
unless $name;
|
||||
|
||||
my $sip = Golem::ipv4_str2int($start_ip);
|
||||
my $eip = $sip + Golem::ipv4_mask2offset($net_mask);
|
||||
|
||||
if ($eip - $sip > 10000) {
|
||||
return $err->("IP block should be less than 10000 addresses.");
|
||||
}
|
||||
|
||||
for (my $i = $sip; $i <= $eip; $i++) {
|
||||
my $tnet = Golem::get_containing_net($i);
|
||||
if (!$tnet) {
|
||||
$tnet = Golem::get_net($i, "32");
|
||||
}
|
||||
|
||||
if ($tnet) {
|
||||
return $err->("IP [" .
|
||||
Golem::ipv4_int2str($i) .
|
||||
"] is already contained in net [" . $tnet->{'ip_str'} . "/" . $tnet->{'mask'} . "]\n");
|
||||
}
|
||||
}
|
||||
|
||||
my $tnet = Golem::insert_net({
|
||||
"ip_str" => $start_ip,
|
||||
"mask" => $net_mask,
|
||||
"name" => $name
|
||||
});
|
||||
|
||||
if ($tnet && !$tnet->{'err'}) {
|
||||
push @$out, ['', "Created net [" . $tnet->{'ip_str'} . "/" . $tnet->{'mask'} . "]\n"];
|
||||
|
||||
$net_list->();
|
||||
}
|
||||
else {
|
||||
return $err->("Error creating net: " . $tnet->{'errstr'});
|
||||
}
|
||||
}
|
||||
elsif ($action eq "delete") {
|
||||
my $tnet = Golem::get_net($start_ip, $net_mask);
|
||||
if ($tnet) {
|
||||
my $r = Golem::delete_net($tnet);
|
||||
if ($r && !$r->{'err'}) {
|
||||
push @$out, ['', "Deleted net [$start_ip/$net_mask]\n"];
|
||||
|
||||
$net_list->();
|
||||
}
|
||||
else {
|
||||
return $err->("Error deleting net: " . $r->{'errstr'});
|
||||
}
|
||||
}
|
||||
else {
|
||||
return $err->("Net [$start_ip/$net_mask] does not exist.");
|
||||
}
|
||||
}
|
||||
elsif ($action eq "ban_new_accounts") {
|
||||
my $tnet = Golem::get_net($start_ip, $net_mask, {"with_props" => 1});
|
||||
if ($tnet) {
|
||||
$tnet->{'props'}->{'data'}->{'ban_new_accounts'} = 1;
|
||||
$tnet = Golem::save_net($tnet);
|
||||
|
||||
if ($tnet && $tnet->{'err'}) {
|
||||
return $err->("Error saving net [$start_ip/$net_mask]: " . $tnet->{'errstr'});
|
||||
}
|
||||
else {
|
||||
$net_list->();
|
||||
}
|
||||
}
|
||||
else {
|
||||
return $err->("Net [$start_ip/$net_mask] does not exist.");
|
||||
}
|
||||
}
|
||||
elsif ($action eq "ban_comments") {
|
||||
my $u = LJ::load_user($args->[3]);
|
||||
return $err->("ban_comments needs username as last parameter")
|
||||
unless $u;
|
||||
|
||||
my $tnet = Golem::get_net($start_ip, $net_mask, {"with_props" => 1});
|
||||
if ($tnet) {
|
||||
$tnet->{'props'}->{'data'}->{'ban_comments'} = {}
|
||||
unless defined($tnet->{'props'}->{'data'}->{'ban_comments'});
|
||||
|
||||
$tnet->{'props'}->{'data'}->{'ban_comments'}->{$u->{'userid'}} = time();
|
||||
$tnet = Golem::save_net($tnet);
|
||||
|
||||
if ($tnet && $tnet->{'err'}) {
|
||||
return $err->("Error saving net [$start_ip/$net_mask]: " . $tnet->{'errstr'});
|
||||
}
|
||||
else {
|
||||
$net_list->();
|
||||
}
|
||||
}
|
||||
else {
|
||||
return $err->("Net [$start_ip/$net_mask] does not exist.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sub expunge_user {
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
my $err = sub { push @$out, [ "error", $_[0] ]; 0; };
|
||||
my $do_out = sub { push @$out, [ "", $_[0] ]; 1; };
|
||||
|
||||
return $err->("This command takes 2 arguments") unless @$args eq 3;
|
||||
return $err->("You are not authorized to use this command.")
|
||||
unless ($remote && $remote->{'priv'}->{'suspend'});
|
||||
|
||||
my $in_user = $args->[1];
|
||||
my $in_userid = $args->[2];
|
||||
|
||||
my $u = LJ::load_user($in_user);
|
||||
return $err->("Supplied userid doesn't match user name.")
|
||||
unless $u->{'userid'} eq $in_userid;
|
||||
|
||||
# copied from delete_all_comments (from talklib.pl)
|
||||
my $dbcm = LJ::get_cluster_master($u);
|
||||
return 0 unless $dbcm && $u->writer;
|
||||
|
||||
my ($t, $loop) = (undef, 1);
|
||||
my $chunk_size = 200;
|
||||
|
||||
my %affected_journals;
|
||||
my $n = 0;
|
||||
|
||||
while ($loop &&
|
||||
($t = $dbcm->selectrow_arrayref("SELECT journalid, jtalkid, nodetype, nodeid, state FROM talk2 WHERE ".
|
||||
"posterid=? LIMIT $chunk_size", undef, $u->{'userid'}))
|
||||
&& $t && @$t)
|
||||
{
|
||||
my @processed = @$t;
|
||||
while(@processed) {
|
||||
my $state = pop @processed;
|
||||
my $nodeid = pop @processed;
|
||||
my $nodetype = pop @processed;
|
||||
my $jtalkid = pop @processed;
|
||||
my $journalid = pop @processed;
|
||||
|
||||
$affected_journals{$journalid} = 1;
|
||||
$n++;
|
||||
|
||||
foreach my $table (qw(talkprop2 talktext2 talk2)) {
|
||||
$u->do("DELETE FROM $table WHERE journalid=? AND jtalkid=?",
|
||||
undef, $journalid, $jtalkid);
|
||||
}
|
||||
# (NB!) slow and suboptimal
|
||||
# (NB!) replycount will be updated on demand
|
||||
# (Sic!) may break threads consistency!!!
|
||||
|
||||
my $memkey = [$journalid, "talk2:$journalid:$nodetype:$nodeid"];
|
||||
LJ::MemCache::delete($memkey);
|
||||
|
||||
my $tu = LJ::load_userid($journalid);
|
||||
LJ::Talk::update_commentalter($tu, $nodeid);
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $j (keys %affected_journals) {
|
||||
my $tu = LJ::load_userid($j);
|
||||
LJ::wipe_major_memcache($tu); # is it ever needed?
|
||||
}
|
||||
my $m = scalar keys %affected_journals;
|
||||
|
||||
$do_out->("$in_user: $n comments expunged from $m journals.");
|
||||
}
|
||||
|
||||
sub expunge_anonymous_comments {
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
my $err = sub { push @$out, [ "error", $_[0] ]; 0; };
|
||||
my $do_out = sub { push @$out, [ "", $_[0] ]; 1; };
|
||||
|
||||
return $err->("This command takes 3 arguments") unless @$args eq 4;
|
||||
return $err->("You are not authorized to use this command.")
|
||||
unless ($remote && $remote->{'priv'}->{'suspend'});
|
||||
|
||||
my $in_user = $args->[1];
|
||||
|
||||
my $dtalkid = $args->[3]+0;
|
||||
my $jtalkid_min = $dtalkid >> 8;
|
||||
|
||||
my $ditemid = $args->[2]+0;
|
||||
my $jitemid = $ditemid >> 8;
|
||||
my $nodetype = 'L';
|
||||
|
||||
my $u = LJ::load_user($in_user);
|
||||
return $err->("Supplied userid doesn't exists.")
|
||||
unless $u;
|
||||
|
||||
my $dbcm = LJ::get_cluster_master($u);
|
||||
return 0 unless $dbcm && $u->writer;
|
||||
|
||||
my $journalid = $u->{'userid'};
|
||||
my $nodeid = $jitemid;
|
||||
$jtalkid_min--; #start from this
|
||||
|
||||
# see also delete_all_comments (from talklib.pl) for right sql request
|
||||
my $t = $dbcm->selectcol_arrayref("SELECT jtalkid FROM talk2 WHERE ".
|
||||
"journalid=? AND nodeid=? ".
|
||||
"AND posterid=0 AND jtalkid>?",
|
||||
undef,
|
||||
$journalid, $nodeid, $jtalkid_min);
|
||||
return 0 unless $t;
|
||||
|
||||
my $num = LJ::Talk::delete_comments($u, $nodetype, $jitemid, $t);
|
||||
|
||||
LJ::MemCache::delete([$journalid, "talk2:$journalid:$nodetype:$nodeid"]);
|
||||
LJ::MemCache::delete([$journalid, "talk2ct:$journalid"]);
|
||||
|
||||
$do_out->("$num anonymous comments deleted.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub accounts_by_ip {
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
my $err = sub { push @$out, [ "error", $_[0] ]; 0; };
|
||||
my $do_out = sub { push @$out, [ "", $_[0] ]; 1; };
|
||||
|
||||
return $err->("This command takes 1 argument") unless @$args eq 2;
|
||||
return $err->("You are not authorized to use this command.")
|
||||
unless ($remote && $remote->{'priv'}->{'finduser'});
|
||||
|
||||
my $ip = $args->[1];
|
||||
|
||||
my $dbr = LJ::get_db_reader();
|
||||
return $err->("Can't get database reader!") unless $dbr;
|
||||
|
||||
my $sth = $dbh->prepare("SELECT userid, ip, FROM_UNIXTIME(logtime) FROM userlog where action = 'account_create' and ip like ?");
|
||||
$sth->execute($ip . "%");
|
||||
die $dbh->errstr if $dbh->err;
|
||||
|
||||
while (my @row = $sth->fetchrow_array) {
|
||||
my $userid = $row[0];
|
||||
my $tip = $row[1];
|
||||
my $date = $row[2];
|
||||
my $u = LJ::load_userid($userid);
|
||||
my $user = $u->{'user'};
|
||||
my $status = $u->{'statusvis'};
|
||||
|
||||
$do_out->("$user $tip $date $status");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub syn_delete
|
||||
{
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
my $err = sub { push @$out, [ "error", $_[0] ]; 0; };
|
||||
|
||||
return $err->("This command has 1 argument") unless @$args == 2;
|
||||
|
||||
return $err->("You are not authorized to use this command.")
|
||||
unless ($remote && $remote->{'priv'}->{'syn_edit'});
|
||||
|
||||
my $user = $args->[1];
|
||||
my $u = LJ::load_user($user);
|
||||
my $du = $u;
|
||||
my $uid = $u->{'userid'};
|
||||
|
||||
return $err->("Invalid user $user") unless $u;
|
||||
return $err->("Not a syndicated account") unless $u->{'journaltype'} eq 'Y';
|
||||
|
||||
# copied from bin/deleteusers.pl
|
||||
my $runsql = sub {
|
||||
my $db = $dbh;
|
||||
if (ref $_[0]) { $db = shift; }
|
||||
my $user = shift;
|
||||
my $sql = shift;
|
||||
$db->do($sql);
|
||||
return ! $db->err;
|
||||
};
|
||||
|
||||
my $dbcm = LJ::get_cluster_master($du);
|
||||
|
||||
# delete userpics
|
||||
{
|
||||
if ($du->{'dversion'} > 6) {
|
||||
$ids = $dbcm->selectcol_arrayref("SELECT picid FROM userpic2 WHERE userid=$uid");
|
||||
} else {
|
||||
$ids = $dbh->selectcol_arrayref("SELECT picid FROM userpic WHERE userid=$uid");
|
||||
}
|
||||
my $in = join(",",@$ids);
|
||||
if ($in) {
|
||||
$runsql->($dbcm, $user, "DELETE FROM userpicblob2 WHERE userid=$uid AND picid IN ($in)");
|
||||
if ($du->{'dversion'} > 6) {
|
||||
|
||||
return $err->("error deleting from userpic2: " . $dbh->errstr)
|
||||
unless $runsql->($dbcm, $user, "DELETE FROM userpic2 WHERE userid=$uid");
|
||||
|
||||
return $err->("error deleting from userpicmap2: " . $dbh->errstr)
|
||||
unless $runsql->($dbcm, $user, "DELETE FROM userpicmap2 WHERE userid=$uid");
|
||||
|
||||
return $err->("error deleting from userkeywords: " . $dbh->errstr)
|
||||
unless $runsql->($dbcm, $user, "DELETE FROM userkeywords WHERE userid=$uid");
|
||||
} else {
|
||||
return $err->("error deleting from userpic: " . $dbh->errstr)
|
||||
unless $runsql->($dbh, $user, "DELETE FROM userpic WHERE userid=$uid");
|
||||
|
||||
return $err->("error deleting from userpicmap: " . $dbh->errstr)
|
||||
unless $runsql->($dbh, $user, "DELETE FROM userpicmap WHERE userid=$uid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# delete posts
|
||||
while (($ids = $dbcm->selectall_arrayref("SELECT jitemid, anum FROM log2 WHERE journalid=$uid LIMIT 100")) && @{$ids})
|
||||
{
|
||||
foreach my $idanum (@$ids) {
|
||||
my ($id, $anum) = ($idanum->[0], $idanum->[1]);
|
||||
LJ::delete_entry($du, $id, 0, $anum);
|
||||
}
|
||||
}
|
||||
|
||||
# misc:
|
||||
$runsql->($user, "DELETE FROM userusage WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM friends WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM friends WHERE friendid=$uid");
|
||||
$runsql->($user, "DELETE FROM friendgroup WHERE userid=$uid");
|
||||
$runsql->($dbcm, $user, "DELETE FROM friendgroup2 WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM memorable WHERE userid=$uid");
|
||||
$runsql->($dbcm, $user, "DELETE FROM memorable2 WHERE userid=$uid");
|
||||
$runsql->($dbcm, $user, "DELETE FROM userkeywords WHERE userid=$uid");
|
||||
$runsql->($dbcm, $user, "DELETE FROM memkeyword2 WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM userbio WHERE userid=$uid");
|
||||
$runsql->($dbcm, $user, "DELETE FROM userbio WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM userinterests WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM userprop WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM userproplite WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM txtmsg WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM overrides WHERE user='$du->{'user'}'");
|
||||
$runsql->($user, "DELETE FROM priv_map WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM infohistory WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM reluser WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM reluser WHERE targetid=$uid");
|
||||
$runsql->($user, "DELETE FROM userlog WHERE userid=$uid");
|
||||
$runsql->($user, "DELETE FROM syndicated WHERE userid=$uid");
|
||||
|
||||
return $err->("error updating user uid $uid: " . $dbh->errstr)
|
||||
unless $runsql->($user, "UPDATE user SET statusvis='X', statusvisdate=NOW(), password='' WHERE userid=$uid");
|
||||
|
||||
push @$out, [ '', "Deleted syndication accout $user." ];
|
||||
# log to statushistory
|
||||
LJ::statushistory_add($u->{userid}, $remote->{userid}, 'synd_delete',
|
||||
"Syndication deleted.");
|
||||
|
||||
LJ::MemCache::set("uidof:$user", "");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sub ljr_fif
|
||||
{
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
my $err = sub { push @$out, [ "error", $_[0] ]; 0; };
|
||||
|
||||
return $err->("This command needs at least 1 argument.")
|
||||
if @$args != 2 && @$args != 3;
|
||||
|
||||
return $err->("LJR_FIF is not configured for this site.")
|
||||
unless $LJ::LJR_FIF;
|
||||
|
||||
my $action = $args->[1];
|
||||
my $user = $args->[2];
|
||||
|
||||
my $fifid = LJ::get_userid($LJ::LJR_FIF);
|
||||
return $err->("Invalid fif $LJ::LJR_FIF") unless $fifid;
|
||||
|
||||
if ($action eq "list_excluded") {
|
||||
my $dbr = LJ::get_db_reader();
|
||||
return $err->("Can't get database reader!") unless $dbr;
|
||||
|
||||
my $sth = $dbr->prepare("select user.*, friends.userid
|
||||
from user left outer join friends on
|
||||
friends.userid = ? and
|
||||
friends.friendid = user.userid
|
||||
where
|
||||
user.userid < ? and
|
||||
user.journaltype <> 'I' and
|
||||
user.journaltype <> 'Y' and
|
||||
friends.userid IS NULL;
|
||||
");
|
||||
$sth->execute($fifid, $LJ::LJR_IMPORTED_USERIDS);
|
||||
die $dbr->errstr if $dbr->err;
|
||||
|
||||
push @$out, [ '', "Excluded from ljr_fif friends" ];
|
||||
while (my $row = $sth->fetchrow_hashref) {
|
||||
push @$out, [ '', $row->{'user'} ];
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
return $err->("You are not authorized to use this command.")
|
||||
unless ($remote && $remote->{'priv'}->{'siteadmin'});
|
||||
|
||||
my $userid = LJ::get_userid($user);
|
||||
return $err->("Invalid user $user") unless $userid;
|
||||
|
||||
my $action_text;
|
||||
if ($action eq "add") {
|
||||
LJ::add_friend($fifid, $userid);
|
||||
$action_text = "Added $user to";
|
||||
}
|
||||
elsif ($action eq "delete") {
|
||||
LJ::remove_friend($fifid, $userid);
|
||||
$action_text = "Deleted $user from";
|
||||
}
|
||||
|
||||
push @$out, [ '', "$action_text ljr_fif friends." ];
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
$cmd{'twit_set'} = {
|
||||
'handler' => \&twit_set,
|
||||
'des' => 'If you twit somebody you won\'t see his/her entries in ljr-fif.',
|
||||
'argsummary' => '<user>',
|
||||
'args' => [
|
||||
'user' => "This is the user you don't want to see in ljr-fif.",
|
||||
],
|
||||
};
|
||||
|
||||
$cmd{'twit_unset'} = {
|
||||
'handler' => \&twit_set,
|
||||
'des' => 'Remove twit on a user.',
|
||||
'argsummary' => '<user>',
|
||||
'args' => [
|
||||
'user' => "The user's entries will be seen in ljr-fif.",
|
||||
],
|
||||
};
|
||||
|
||||
$cmd{'twit_list'} = {
|
||||
'handler' => \&twit_list,
|
||||
'des' => 'List your twits (the users you don\'t see in ljr_fif).',
|
||||
'argsummary' => '[ <user> ]',
|
||||
'args' => [
|
||||
'user' => "Optional; list twits for any user if you have the 'finduser' priv. (this admin-only feature is broken right now)",
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
sub twit_list
|
||||
{
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
|
||||
unless ($remote) {
|
||||
push @$out, [ "error", "You must be logged in to use this command." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
# journal to list from
|
||||
my $j = $remote;
|
||||
|
||||
unless ($remote->{'journaltype'} eq "P") {
|
||||
push @$out, [ "error", "You're not logged in as a person account." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $twitedids = load_twit($j->{userid}) || [];
|
||||
my $us = LJ::load_userids(@$twitedids);
|
||||
my @userlist = map { $us->{$_}{user} } keys %$us;
|
||||
|
||||
foreach my $username (@userlist) {
|
||||
push @$out, [ 'info', $username ];
|
||||
}
|
||||
push @$out, [ "info", "$j->{user} has not twitted any other users." ] unless @userlist;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub twit_set
|
||||
{
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
my $error = 0;
|
||||
|
||||
unless ($remote) {
|
||||
push @$out, [ "error", "You must be logged in to use this command" ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
# journal to ban from:
|
||||
my $j;
|
||||
|
||||
unless ($remote->{'journaltype'} eq "P") {
|
||||
push @$out, [ "error", "You're not logged in as a person account." ],
|
||||
return 0;
|
||||
}
|
||||
|
||||
$j = $remote;
|
||||
|
||||
my $user = $args->[1];
|
||||
my $twitid = LJ::get_userid($dbh, $user);
|
||||
|
||||
unless ($twitid) {
|
||||
$error = 1;
|
||||
push @$out, [ "error", "Invalid user \"$user\"" ];
|
||||
}
|
||||
|
||||
return 0 if ($error);
|
||||
|
||||
my $qtwitid = $twitid+0;
|
||||
my $quserid = $j->{'userid'}+0;
|
||||
|
||||
# exceeded twit limit?
|
||||
if ($args->[0] eq 'twit_set') {
|
||||
my $twitlist = load_twit($j->{userid}) || [];
|
||||
if (scalar(@$twitlist) >= ($LJ::MAX_BANS || 5000)) {
|
||||
push @$out, [ "error", "You have reached the maximum number of twits. Sorry." ];
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($args->[0] eq "twit_set") {
|
||||
twit_rel_set($j->{userid}, $twitid);
|
||||
$j->log_event('twit_set', { actiontarget => $twitid, remote => $remote });
|
||||
if (!LJ::check_twit($j->{userid},$twitid)) {
|
||||
push @$out, [ "error",
|
||||
"An error occured!\n" .
|
||||
"User $user ($twitid) is not twitted by $j->{'user'}." ];
|
||||
return 0;
|
||||
}
|
||||
push @$out, [ "info", "User $user ($twitid) twitted by $j->{'user'}." ];
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($args->[0] eq "twit_unset") {
|
||||
twit_rel_unset($j->{userid}, $twitid);
|
||||
$j->log_event('twit_unset', { actiontarget => $twitid, remote => $remote });
|
||||
if (LJ::check_twit($j->{userid},$twitid)) {
|
||||
push @$out, [ "error",
|
||||
"An error occured!\n" .
|
||||
"User $user ($twitid) is still twitted by $j->{'user'}." ];
|
||||
return 0;
|
||||
}
|
||||
push @$out, [ "info", "User $user ($twitid) is not twitted by $j->{'user'} in ljr-fif." ];
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
# des: Load user twits table
|
||||
# UNCLUSTERED! Should be rewritten if we are going clusered
|
||||
# args: userid
|
||||
sub load_twit
|
||||
{
|
||||
my $userid = $_[0];
|
||||
return undef unless $userid;
|
||||
my $u = LJ::want_user($userid);
|
||||
$userid = LJ::want_userid($userid);
|
||||
$db = LJ::get_db_reader();
|
||||
return $err->("Can't get database reader!") unless $db;
|
||||
return $db->selectcol_arrayref("SELECT twitid FROM
|
||||
twits WHERE userid=?", undef, $userid);
|
||||
}
|
||||
|
||||
# des: Add the second user to the twit list of the first
|
||||
# UNCLUSTERED! Should be rewritten if we are going clusered
|
||||
# args: userid, twitid
|
||||
sub twit_rel_set
|
||||
{
|
||||
my ($userid,$twitid) = @_;
|
||||
return undef unless $userid; return undef unless $twitid;
|
||||
$db = LJ::get_db_writer();
|
||||
return $err->("Can't get database reader!") unless $db;
|
||||
$db->do("INSERT INTO twits VALUES ($userid, $twitid)");
|
||||
}
|
||||
|
||||
# des: Remove the second user from the twit list of the first
|
||||
# UNCLUSTERED! Should be rewritten if we are going clusered
|
||||
# args: userid, twitid
|
||||
sub twit_rel_unset
|
||||
{
|
||||
my ($userid,$twitid) = @_;
|
||||
return undef unless $userid; return undef unless $twitid;
|
||||
$db = LJ::get_db_writer();
|
||||
return $err->("Can't get database reader!") unless $db;
|
||||
$db->do("DELETE FROM twits WHERE userid=$userid AND twitid=$twitid");
|
||||
}
|
||||
|
||||
return 1;
|
||||
1647
local/cgi-bin/console.pl
Executable file
1647
local/cgi-bin/console.pl
Executable file
File diff suppressed because it is too large
Load Diff
457
local/cgi-bin/consuspend.pl
Executable file
457
local/cgi-bin/consuspend.pl
Executable file
@@ -0,0 +1,457 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
package LJ::Con;
|
||||
|
||||
use strict;
|
||||
use vars qw(%cmd);
|
||||
|
||||
$cmd{'expunge_userpic'}->{'handler'} = \&expunge_userpic;
|
||||
$cmd{'suspend'}->{'handler'} = \&suspend;
|
||||
$cmd{'unsuspend'}->{'handler'} = \&suspend;
|
||||
$cmd{'getemail'}->{'handler'} = \&getemail;
|
||||
$cmd{'get_maintainer'}->{'handler'} = \&get_maintainer;
|
||||
$cmd{'get_moderator'}->{'handler'} = \&get_moderator;
|
||||
$cmd{'finduser'}->{'handler'} = \&finduser;
|
||||
$cmd{'infohistory'}->{'handler'} = \&infohistory;
|
||||
$cmd{'change_journal_status'}->{'handler'} = \&change_journal_status;
|
||||
$cmd{'set_underage'}->{'handler'} = \&set_underage;
|
||||
|
||||
sub set_underage {
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
|
||||
my $err = sub { push @$out, [ "error", shift ]; 0; };
|
||||
my $info = sub { push @$out, [ "info", shift ]; 1; };
|
||||
|
||||
return $err->("This command takes three arguments. Consult the reference for details.")
|
||||
unless scalar(@$args) == 4;
|
||||
return $err->("You don't have the necessary privilege (siteadmin:underage) to change an account's underage flag.")
|
||||
unless LJ::check_priv($remote, 'siteadmin', 'underage') || LJ::check_priv($remote, 'siteadmin', '*');
|
||||
|
||||
my $u = LJ::load_user($args->[1]);
|
||||
return $err->("Invalid user.")
|
||||
unless $u;
|
||||
return $err->("Account is not a person type account.")
|
||||
unless $u->{journaltype} eq 'P';
|
||||
|
||||
return $err->("Second argument must be 'on' or 'off'.")
|
||||
unless $args->[2] =~ /^(?:on|off)$/;
|
||||
my $on = $args->[2] eq 'on' ? 1 : 0;
|
||||
|
||||
my $note = $args->[3];
|
||||
return $err->("You must provide a reason for this change as the third argument.")
|
||||
unless $note;
|
||||
|
||||
# can't set state to what it is already
|
||||
return $err->("User is already of the requested underage state.")
|
||||
unless $on ^ $u->underage;
|
||||
|
||||
my ($res, $sh, $status);
|
||||
if ($on) {
|
||||
$status = 'M'; # "M"anually turned on
|
||||
$res = "User marked as underage.";
|
||||
$sh = "marked; $note";
|
||||
} else {
|
||||
$status = undef; # no status change
|
||||
$res = "User no longer marked as underaged.";
|
||||
$sh = "unmarked; $note";
|
||||
}
|
||||
|
||||
# now record this change (yes we log it twice)
|
||||
LJ::statushistory_add($u->{userid}, $remote->{userid}, "set_underage", $sh);
|
||||
$u->underage($on, $status, "manual");
|
||||
return $info->($res);
|
||||
}
|
||||
|
||||
sub change_journal_status {
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
|
||||
my $err = sub { push @$out, [ "error", shift ]; 0; };
|
||||
my $info = sub { push @$out, [ "info", shift ]; 1; };
|
||||
|
||||
return $err->("This command takes two arguments. Consult the reference for details.")
|
||||
unless scalar(@$args) == 3;
|
||||
return $err->("You don't have the necessary privilege (siteadmin:users) to change account status.")
|
||||
unless LJ::check_priv($remote, 'siteadmin', 'users') || LJ::check_priv($remote, 'siteadmin', '*');
|
||||
|
||||
my $u = LJ::load_user($args->[1]);
|
||||
return $err->("Invalid user.")
|
||||
unless $u;
|
||||
|
||||
# figure out the new status
|
||||
my $status = $args->[2];
|
||||
my $opts = {
|
||||
#name => [ 'status-to', 'valid-statuses-from', 'error-message-if-from-fails', 'success-message' ]
|
||||
normal => [ 'V', 'ML', 'The user must be in memorial or locked status first.', 'User status set back to normal.' ],
|
||||
memorial => [ 'M', 'V', 'The user must be in normal status first.', 'User account set as memorial.' ],
|
||||
locked => [ 'L', 'V', 'The user must be in normal status first.', 'User account has been locked.' ],
|
||||
}->{$status};
|
||||
|
||||
# make sure we got a valid $opts arrayref
|
||||
return $err->("Invalid status. Consult the reference for more information.")
|
||||
unless defined $opts && ref $opts eq 'ARRAY';
|
||||
|
||||
# verify user's from-statusvis is okay (it's contained in $opts->[1])
|
||||
return $err->($opts->[2]) unless $opts->[1] =~ /$u->{statusvis}/;
|
||||
|
||||
# okay, so we need to update the user now and update statushistory
|
||||
LJ::statushistory_add($u->{userid}, $remote->{userid}, "journal_status", "Changed status to $status from $u->{statusvis}.");
|
||||
LJ::update_user($u->{'userid'}, { statusvis => $opts->[0], raw => 'statusvisdate=NOW()' });
|
||||
return $info->($opts->[3]);
|
||||
}
|
||||
|
||||
sub expunge_userpic {
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
|
||||
unless (scalar(@$args) == 3) {
|
||||
push @$out, [ "error", "This command takes exactly two arguments, username and picid. Consult the reference." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $user = $args->[1];
|
||||
my $picid = $args->[2]+0;
|
||||
|
||||
unless (LJ::check_priv($remote, 'siteadmin', 'userpics') || LJ::check_priv($remote, 'siteadmin', '*')) {
|
||||
push @$out, [ "error", "You don't have access to expunge user picture icons." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $u = LJ::load_user($user);
|
||||
|
||||
# the actual expunging happens in ljlib
|
||||
my ($rval, $hookval) = LJ::expunge_userpic($u, $picid);
|
||||
push @$out, $hookval if @{$hookval || []};
|
||||
|
||||
# now load up from the return value we got
|
||||
unless ($rval && $u) {
|
||||
push @$out, [ "error", "Error expunging user picture icon." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
# but make sure to log it
|
||||
LJ::statushistory_add($u->{userid}, $remote->{userid}, 'expunge_userpic', "expunged userpic; id=$picid");
|
||||
push @$out, [ "info", "User picture icon $picid for $u->{user} expunged from $LJ::SITENAMESHORT." ];
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub suspend
|
||||
{
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
|
||||
my $confirmed = 0;
|
||||
if (scalar(@$args) == 4 && $args->[3] eq 'confirm') {
|
||||
pop @$args;
|
||||
$confirmed = 1;
|
||||
}
|
||||
|
||||
unless (scalar(@$args) == 3) {
|
||||
push @$out, [ "error", "This command takes exactly 2 arguments. Consult the reference." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $cmd = $args->[0];
|
||||
my ($user, $reason) = ($args->[1], $args->[2]);
|
||||
|
||||
if ($cmd eq "suspend" && $reason eq "off") {
|
||||
push @$out, [ "error", "The second argument to the 'suspend' command is no longer 'off' to unsuspend. Use the 'unsuspend' command instead." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
unless ($remote->{'priv'}->{'suspend'}) {
|
||||
push @$out, [ "error", "You don't have access to $cmd users." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
# if the user argument is an email address...
|
||||
my @users;
|
||||
if ($user =~ /@/) {
|
||||
push @$out, [ "info", "Acting on users matching email $user..." ];
|
||||
|
||||
my $dbr = LJ::get_db_reader();
|
||||
my $names = $dbr->selectcol_arrayref('SELECT user FROM user WHERE email = ?', undef, $user);
|
||||
if ($dbr->err) {
|
||||
push @$out, [ "error", "Database error: " . $dbr->errstr ];
|
||||
return 0;
|
||||
}
|
||||
unless ($names && @$names) {
|
||||
push @$out, [ "error", "No users found matching the email address $user." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
# bail unless they've confirmed this mass action
|
||||
unless ($confirmed) {
|
||||
push @$out, [ "info", " $_" ] foreach @$names;
|
||||
push @$out, [ "info", "To actually confirm this action, please do this again:" ];
|
||||
push @$out, [ "info", " $cmd $user \"$reason\" confirm" ];
|
||||
return 1;
|
||||
}
|
||||
|
||||
push @users, $_ foreach @$names;
|
||||
} else {
|
||||
push @users, $user;
|
||||
}
|
||||
|
||||
foreach my $username (@users) {
|
||||
my $u = LJ::load_user($username);
|
||||
unless ($u) {
|
||||
push @$out, [ "error", "$username invalid/unable to load." ];
|
||||
next;
|
||||
}
|
||||
|
||||
my $status = ($cmd eq "unsuspend") ? "V" : "S";
|
||||
if ($u->{'statusvis'} eq $status) {
|
||||
push @$out, [ "error", "$username was already in that state ($status)" ];
|
||||
next;
|
||||
}
|
||||
|
||||
LJ::update_user($u->{'userid'}, { statusvis => $status, raw => 'statusvisdate=NOW()' });
|
||||
$u->{statusvis} = $status;
|
||||
|
||||
LJ::statushistory_add($u->{'userid'}, $remote->{'userid'}, $cmd, $reason);
|
||||
|
||||
LJ::Con::fb_push( $u );
|
||||
|
||||
push @$out, [ "info", "User '$username' ${cmd}ed." ];
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub getemail
|
||||
{
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
|
||||
unless (scalar(@$args) == 2) {
|
||||
push @$out, [ "error", "This command takes exactly 1 argument. Consult the reference." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
my ($user) = ($args->[1]);
|
||||
my $userid = &LJ::get_userid($user);
|
||||
|
||||
unless ($remote->{'priv'}->{'suspend'}) {
|
||||
push @$out, [ "error", "You don't have access to see email addresses." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
unless ($userid) {
|
||||
push @$out, [ "error", "Invalid user \"$user\"" ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $sth = $dbh->prepare("SELECT email, status FROM user WHERE userid=$userid");
|
||||
$sth->execute;
|
||||
my ($email, $status) = $sth->fetchrow_array;
|
||||
|
||||
push @$out, [ "info", "User: $user" ];
|
||||
push @$out, [ "info", "Email: $email" ];
|
||||
push @$out, [ "info", "Status: $status (A=approved, N=new, T=transferring)" ];
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub finduser
|
||||
{
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
|
||||
unless ($remote->{'priv'}->{'finduser'}) {
|
||||
push @$out, [ "error", "$remote->{'user'}, you are not authorized to use this command." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
my ($crit, $data);
|
||||
if (scalar(@$args) == 2) {
|
||||
# new form; we can auto-detect emails easy enough
|
||||
$data = $args->[1];
|
||||
if ($data =~ /@/) {
|
||||
$crit = 'email';
|
||||
} else {
|
||||
# TODO: autodetect ip and userid...
|
||||
$crit = 'user';
|
||||
}
|
||||
} else {
|
||||
# old format...but new variation
|
||||
$crit = $args->[1];
|
||||
$data = $args->[2];
|
||||
|
||||
# if they gave us a username and want to search by email, instead find
|
||||
# all users with that email address
|
||||
if ($crit eq 'email' && $data !~ /@/) {
|
||||
my $u = LJ::load_user($data);
|
||||
unless ($u) {
|
||||
push @$out, [ "error", "User doesn't exist." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
$data = $u->{email};
|
||||
}
|
||||
}
|
||||
|
||||
my $qd = $dbh->quote($data);
|
||||
|
||||
my $userids;
|
||||
my $ip;
|
||||
|
||||
if ($crit eq "email") {
|
||||
$userids = $dbh->selectcol_arrayref("SELECT userid FROM user WHERE email=$qd");
|
||||
} elsif ($crit eq "userid") {
|
||||
$userids = $dbh->selectcol_arrayref("SELECT userid FROM user WHERE userid=$qd");
|
||||
} elsif ($crit eq "user") {
|
||||
$userids = $dbh->selectcol_arrayref("SELECT userid FROM user WHERE user=$qd");
|
||||
} elsif ($crit eq "ip") {
|
||||
$userids = $dbh->selectcol_arrayref("SELECT userid FROM userlog WHERE action='account_create' AND ip=$qd");
|
||||
$ip = $data;
|
||||
} elsif ($crit eq "sameip") {
|
||||
$userids = $dbh->selectcol_arrayref("SELECT userid FROM user WHERE user=$qd");
|
||||
#like 'user'+'ip' #continue...
|
||||
}
|
||||
|
||||
|
||||
if ($dbh->err) {
|
||||
push @$out, [ "error", "Error in database query: " . $dbh->errstr ];
|
||||
return 0;
|
||||
}
|
||||
unless ($userids && @$userids) {
|
||||
push @$out, [ "error", "No matches." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if ($crit eq "sameip") { #continue...
|
||||
my $res = $dbh->selectcol_arrayref("SELECT ip FROM userlog WHERE action='account_create' AND userid=$userids->[0]");
|
||||
$ip = $res->[0];
|
||||
$qd = $dbh->quote($ip);
|
||||
$userids = $dbh->selectcol_arrayref("SELECT userid FROM userlog WHERE action='account_create' AND ip=$qd");
|
||||
}
|
||||
|
||||
|
||||
my $us = LJ::load_userids(@$userids);
|
||||
foreach my $u (sort { $a->{userid} <=> $b->{userid} } values %$us) {
|
||||
|
||||
unless ($ip) {
|
||||
my $res = $dbh->selectcol_arrayref("SELECT ip FROM userlog WHERE action='account_create' AND userid=$u->{'userid'}");
|
||||
$ip = $res->[0];
|
||||
}
|
||||
|
||||
push @$out, [ "info", "User: $u->{'user'} ".
|
||||
"($u->{'userid'}), journaltype: $u->{'journaltype'}, statusvis: $u->{'statusvis'}, $ip, email: ($u->{'status'}) $u->{'email'}" ];
|
||||
|
||||
if ($u->underage) {
|
||||
my $reason;
|
||||
if ($u->underage_status eq 'M') {
|
||||
$reason = "manual set (see statushistory type set_underage)";
|
||||
} elsif ($u->underage_status eq 'Y') {
|
||||
$reason = "provided birthdate";
|
||||
} elsif ($u->underage_status eq 'O') {
|
||||
$reason = "unique cookie";
|
||||
}
|
||||
push @$out, [ "info", " User is marked underage due to $reason." ];
|
||||
}
|
||||
|
||||
# no Paid accouns in LJR!
|
||||
# foreach (LJ::run_hooks("finduser_extrainfo", { 'dbh' => $dbh, 'u' => $u })) {
|
||||
# next unless $_->[0];
|
||||
# foreach (split(/\n/, $_->[0])) {
|
||||
# push @$out, [ "info", $_ ];
|
||||
# }
|
||||
# }
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_maintainer
|
||||
{
|
||||
my ($dbh, $remote, $args, $out, $edge) = @_;
|
||||
$edge ||= 'A';
|
||||
|
||||
unless (scalar(@$args) == 2) {
|
||||
push @$out, [ "error", "This command takes exactly 1 argument. Consult the reference." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
unless ($remote->{'priv'}->{'finduser'}) {
|
||||
push @$out, [ "error", "$remote->{'user'}, you are not authorized to use this command." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $user = $args->[1];
|
||||
my $u = LJ::load_user($user);
|
||||
|
||||
unless ($u) {
|
||||
push @$out, [ "error", "Invalid user \"$user\"" ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
# journaltype eq 'P' means we're calling get_maintainer on a
|
||||
# plain user and we should get a list of what they maintain instead of
|
||||
# getting a list of what maintains them
|
||||
my $ids = $u->{journaltype} eq 'P' ?
|
||||
LJ::load_rel_target($u->{userid}, $edge) :
|
||||
LJ::load_rel_user($u->{userid}, $edge);
|
||||
$ids ||= [];
|
||||
|
||||
# finduser loop
|
||||
finduser($dbh, $remote, ['finduser', 'userid', $_], $out) foreach @$ids;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_moderator
|
||||
{
|
||||
# simple pass through, but specify to use the 'M' edge
|
||||
return get_maintainer(@_, 'M');
|
||||
}
|
||||
|
||||
sub infohistory
|
||||
{
|
||||
my ($dbh, $remote, $args, $out) = @_;
|
||||
|
||||
unless ($remote->{'privarg'}->{'finduser'}->{'infohistory'}) {
|
||||
push @$out, [ "error", "$remote->{'user'}, you are not authorized to use this command." ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $user = $args->[1];
|
||||
my $userid = LJ::get_userid($user);
|
||||
|
||||
unless ($userid) {
|
||||
push @$out, [ "error", "Invalid user $user" ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $sth = $dbh->prepare("SELECT * FROM infohistory WHERE userid='$userid'");
|
||||
$sth->execute;
|
||||
if (! $sth->rows) {
|
||||
push @$out, [ "error", "Not much info found" ];
|
||||
my $sth1 = $dbh->prepare("select FROM_UNIXTIME(logtime) as logtime, ip from userlog where userid=? and action='account_create'");
|
||||
$sth1->execute($userid);
|
||||
my $acc = $sth1->fetchrow_hashref;
|
||||
push @$out, [ "info", "Account created at $acc->{'logtime'} from $acc->{'ip'} "];
|
||||
} else {
|
||||
push @$out, ["info", "Infohistory of user: $user"];
|
||||
my $sth1 = $dbh->prepare("select FROM_UNIXTIME(logtime) as logtime, ip from userlog where userid=? and action='account_create'");
|
||||
$sth1->execute($userid);
|
||||
if ($sth1->rows) {
|
||||
my $acc = $sth1->fetchrow_hashref;
|
||||
push @$out, [ "info", "Account created at $acc->{'logtime'} from $acc->{'ip'} "];
|
||||
}
|
||||
else {
|
||||
push @$out, [ "error", "No account creation info found!" ];
|
||||
}
|
||||
|
||||
while (my $info = $sth->fetchrow_hashref) {
|
||||
$info->{'oldvalue'} ||= '(none)';
|
||||
push @$out, [ "info",
|
||||
"Changed $info->{'what'} at $info->{'timechange'}.\n".
|
||||
"Old value of $info->{'what'} was $info->{'oldvalue'}.".
|
||||
($info->{'other'} ?
|
||||
"\nOther information recorded: $info->{'other'}" : "") ];
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
650
local/cgi-bin/directorylib.pl
Executable file
650
local/cgi-bin/directorylib.pl
Executable file
@@ -0,0 +1,650 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
# Directory search code.
|
||||
#
|
||||
############################################################
|
||||
#
|
||||
# Misc Notes...
|
||||
#
|
||||
# directory handle can only touch:
|
||||
# community
|
||||
# friends
|
||||
# payments
|
||||
# userinterests
|
||||
# userprop
|
||||
# userusage
|
||||
#
|
||||
|
||||
use strict;
|
||||
package LJ::Dir;
|
||||
use Digest::MD5 qw(md5_hex);
|
||||
|
||||
my $MAX_RETURN_RESULT = 1000;
|
||||
|
||||
my %filters = (
|
||||
'int' => { 'searcher' => \&search_int,
|
||||
'validate' => \&validate_int, },
|
||||
'fr' => { 'searcher' => \&search_fr,
|
||||
'validate' => \&validate_fr, },
|
||||
'fro' => { 'searcher' => \&search_fro,
|
||||
'validate' => \&validate_fro, },
|
||||
'loc' => { 'validate' => \&validate_loc,
|
||||
'searcher' => \&search_loc, },
|
||||
#'gen' => { 'validate' => \&validate_gen,
|
||||
# 'searcher' => \&search_gen, },
|
||||
'age' => { 'validate' => \&validate_age,
|
||||
'searcher' => \&search_age, },
|
||||
'ut' => { 'validate' => \&validate_ut,
|
||||
'searcher' => \&search_ut, },
|
||||
'com' => { 'searcher' => \&search_com,
|
||||
'validate' => \&validate_com, },
|
||||
);
|
||||
|
||||
# validate all filter options
|
||||
#
|
||||
sub validate
|
||||
{
|
||||
my ($req, $errors) = @_;
|
||||
my @filters;
|
||||
foreach my $f (sort keys %filters) {
|
||||
next unless $filters{$f}->{'validate'};
|
||||
if ($filters{$f}->{'validate'}->($req, $errors)) {
|
||||
push @filters, $f;
|
||||
}
|
||||
}
|
||||
return sort @filters;
|
||||
}
|
||||
|
||||
# entry point to do a search: give it
|
||||
# a db-read handle
|
||||
# directory master (must be able to write to dirsearchres2)
|
||||
# hashref of the request,
|
||||
# a listref of where to put the user hashrefs returned,
|
||||
# hashref of where to return results of the query
|
||||
sub do_search
|
||||
{
|
||||
my ($dbr, $dbdir, $req, $users, $info) = @_;
|
||||
my $sth;
|
||||
|
||||
# clear return buffers
|
||||
@{$users} = ();
|
||||
%{$info} = ();
|
||||
|
||||
my @crits;
|
||||
foreach my $f (sort keys %filters)
|
||||
{
|
||||
next unless $filters{$f}->{'validate'}->($req, []);
|
||||
|
||||
my @criteria = $filters{$f}->{'searcher'}->($dbr, $req, $info);
|
||||
if (@criteria) {
|
||||
push @crits, @criteria;
|
||||
} else {
|
||||
# filters return nothing to signal an error, and should have set $info->{'errmsg'}
|
||||
$info->{'errmsg'} = "[Filter $f failed] $info->{'errmsg'}";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
unless (scalar(@crits)) {
|
||||
$info->{'errmsg'} = "You did not enter any search criteria.";
|
||||
return 0;
|
||||
}
|
||||
|
||||
########## time to build us some huge SQL statement. yee haw.
|
||||
|
||||
my $orderby;
|
||||
my %only_one_copy = qw(community c user u userusage uu);
|
||||
|
||||
## keep track of what table aliases we've used
|
||||
my %alias_used;
|
||||
$alias_used{'u'} = "?"; # only used by dbr, not dbdir
|
||||
$alias_used{'c'} = "?"; # might be used later, if opt_format eq "com"
|
||||
$alias_used{'uu'} = "?"; # might be used later, if opt_sort is by time
|
||||
|
||||
my %conds; # where condition -> 1
|
||||
my %useridcol; # all keys here equal each other (up.userid == uu.userid == ..)
|
||||
|
||||
## foreach each critera, build up the query
|
||||
foreach my $crit (@crits)
|
||||
{
|
||||
### each search criteria has its own table aliases. make those unique.
|
||||
my %map_alias = (); # keep track of local -> global table alias mapping
|
||||
|
||||
foreach my $localalias (keys %{$crit->{'tables'}})
|
||||
{
|
||||
my $table = $crit->{'tables'}->{$localalias};
|
||||
my $newalias;
|
||||
|
||||
# some tables might be used multiple times but they're
|
||||
# setup such that opening them multiple times is useless.
|
||||
if ($only_one_copy{$table}) {
|
||||
$newalias = $only_one_copy{$table};
|
||||
$alias_used{$newalias} = $table;
|
||||
} else {
|
||||
my $ct = 1;
|
||||
$newalias = $localalias;
|
||||
while ($alias_used{$newalias}) {
|
||||
$ct++;
|
||||
$newalias = "$localalias$ct";
|
||||
}
|
||||
$alias_used{$newalias} = $table;
|
||||
}
|
||||
|
||||
$map_alias{$localalias} = $newalias;
|
||||
}
|
||||
|
||||
## add each condition to the where clause, after fixing up aliases
|
||||
foreach my $cond (@{$crit->{'conds'}}) {
|
||||
$cond =~ s/\{(\w+?)\}/$map_alias{$1}/g;
|
||||
$conds{$cond} = 1;
|
||||
}
|
||||
|
||||
## add join to u.userid table
|
||||
my $cond = $crit->{'userid'};
|
||||
if ($cond) {
|
||||
$cond =~ s/\{(\w+?)\}/$map_alias{$1}/g;
|
||||
$useridcol{$cond} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
my $pagesize = $req->{'opt_pagesize'}+0 || 100;
|
||||
if ($pagesize > 200) { $pagesize = 200; }
|
||||
if ($pagesize < 5) { $pagesize = 5; }
|
||||
|
||||
$req->{'opt_format'} ||= "pics";
|
||||
if ($req->{'opt_format'} eq "com") {
|
||||
$alias_used{'c'} = "community";
|
||||
$useridcol{"c.userid"} = 1;
|
||||
}
|
||||
|
||||
$req->{'opt_sort'} ||= "ut";
|
||||
if ($req->{'opt_sort'} eq "ut") {
|
||||
$alias_used{'uu'} = 'userusage';
|
||||
$useridcol{"uu.userid"} = 1;
|
||||
$orderby = "ORDER BY uu.timeupdate DESC";
|
||||
} elsif ($req->{'opt_sort'} eq "user") {
|
||||
$alias_used{'u'} = 'user';
|
||||
$useridcol{"u.userid"} = 1;
|
||||
$orderby = "ORDER BY u.user";
|
||||
} elsif ($req->{'opt_sort'} eq "name") {
|
||||
$alias_used{'u'} = 'user';
|
||||
$useridcol{"u.userid"} = 1;
|
||||
$orderby = "ORDER BY u.name";
|
||||
}
|
||||
|
||||
# delete reserved table aliases the didn't end up being used
|
||||
foreach (keys %alias_used) {
|
||||
delete $alias_used{$_} if $alias_used{$_} eq "?";
|
||||
}
|
||||
|
||||
# add clauses to make all userid cols equal each other
|
||||
my $useridcol; # any one
|
||||
foreach my $ca (keys %useridcol) {
|
||||
foreach my $cb (keys %useridcol) {
|
||||
next if $ca eq $cb;
|
||||
$conds{"$ca=$cb"} = 1;
|
||||
}
|
||||
$useridcol = $ca;
|
||||
}
|
||||
|
||||
my $fromwhat = join(", ", map { "$alias_used{$_} $_" } keys %alias_used);
|
||||
my $conds = join(" AND ", keys %conds);
|
||||
|
||||
my $sql = "SELECT $useridcol FROM $fromwhat WHERE $conds $orderby LIMIT $MAX_RETURN_RESULT";
|
||||
|
||||
if ($req->{'sql'}) {
|
||||
$info->{'errmsg'} = "SQL: $sql";
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $qdig = $dbr->quote(md5_hex($sql));
|
||||
my $hit_cache = 0;
|
||||
my $count = 0;
|
||||
my @ids;
|
||||
|
||||
# delete any stale results
|
||||
$dbdir->do("DELETE FROM dirsearchres2 WHERE qdigest=$qdig AND ".
|
||||
"dateins < DATE_SUB(NOW(), INTERVAL 15 MINUTE)");
|
||||
# mark query as in progress.
|
||||
$dbdir->do("INSERT INTO dirsearchres2 (qdigest, dateins, userids) ".
|
||||
"VALUES ($qdig, NOW(), '[searching]')");
|
||||
if ($dbdir->err)
|
||||
{
|
||||
# if there's an error inserting that, we know something's there.
|
||||
# let's see what!
|
||||
my $ids = $dbdir->selectrow_array("SELECT userids FROM dirsearchres2 ".
|
||||
"WHERE qdigest=$qdig");
|
||||
if (defined $ids) {
|
||||
if ($ids eq "[searching]") {
|
||||
# somebody else (or same user before) is still searching
|
||||
$info->{'searching'} = 1;
|
||||
return 1;
|
||||
}
|
||||
@ids = split(/,/, $ids);
|
||||
$count = scalar(@ids);
|
||||
$hit_cache = 1;
|
||||
}
|
||||
}
|
||||
|
||||
## guess we'll have to query it.
|
||||
if (! $hit_cache)
|
||||
{
|
||||
BML::do_later(sub {
|
||||
$sth = $dbdir->prepare($sql);
|
||||
$sth->execute;
|
||||
while (my ($id) = $sth->fetchrow_array) {
|
||||
push @ids, $id;
|
||||
}
|
||||
my $ids = $dbdir->quote(join(",", @ids));
|
||||
$dbdir->do("REPLACE INTO dirsearchres2 (qdigest, dateins, userids) ".
|
||||
"VALUES ($qdig, NOW(), $ids)");
|
||||
});
|
||||
$info->{'searching'} = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $page = $req->{'page'} || 1;
|
||||
my $skip = ($page-1)*$pagesize;
|
||||
my $pages = int($count / $pagesize) + (($count % $pagesize) ? 1 : 0);
|
||||
$pages ||= 1;
|
||||
if ($page > $pages) { $page = $pages; }
|
||||
$info->{'pages'} = $pages;
|
||||
$info->{'page'} = $page;
|
||||
$info->{'first'} = ($page-1)*$pagesize+1;
|
||||
$info->{'last'} = $page * $pagesize;
|
||||
$info->{'count'} = $count;
|
||||
if ($count == $MAX_RETURN_RESULT) {
|
||||
$info->{'overflow'} = 1;
|
||||
}
|
||||
if ($page == $pages) { $info->{'last'} = $count; }
|
||||
|
||||
## now, get info on the ones we want.
|
||||
@ids = grep{ $_+0 } @ids[($info->{'first'}-1)..($info->{'last'}-1)];
|
||||
return 1 unless @ids;
|
||||
|
||||
my %u;
|
||||
LJ::load_userids_multiple([ map { $_ => \$u{$_} } @ids ]);
|
||||
my $tu = LJ::get_timeupdate_multi_fast(undef, \%u); # %u = hint
|
||||
my $now = time();
|
||||
|
||||
# need to get community info
|
||||
if ($req->{'opt_format'} eq "com") {
|
||||
my $in = join(',', @ids);
|
||||
my $sth = $dbr->prepare("SELECT userid, membership, postlevel ".
|
||||
"FROM community ".
|
||||
"WHERE userid IN ($in)");
|
||||
$sth->execute;
|
||||
while (my ($uid, $mem, $postlev) = $sth->fetchrow_array) {
|
||||
next unless $u{$uid};
|
||||
next unless $u{$uid}->{'statusvis'} eq "V";
|
||||
$u{$uid}->{'membership'} = $mem;
|
||||
$u{$uid}->{'postlevel'} = $postlev;
|
||||
}
|
||||
foreach (@ids) {
|
||||
delete $u{$_} unless $u{$_}->{'membership'};
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $id (@ids) {
|
||||
next unless $u{$id};
|
||||
$u{$id}->{'secondsold'} = $tu->{$id} ? $now - $tu->{$id} : undef;
|
||||
push @$users, $u{$id} if $u{$id};
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub ago_text
|
||||
{
|
||||
my $secondsold = shift;
|
||||
return "Never." unless ($secondsold);
|
||||
my $num;
|
||||
my $unit;
|
||||
if ($secondsold > 60*60*24*7) {
|
||||
$num = int($secondsold / (60*60*24*7));
|
||||
$unit = "week";
|
||||
} elsif ($secondsold > 60*60*24) {
|
||||
$num = int($secondsold / (60*60*24));
|
||||
$unit = "day";
|
||||
} elsif ($secondsold > 60*60) {
|
||||
$num = int($secondsold / (60*60));
|
||||
$unit = "hour";
|
||||
} elsif ($secondsold > 60) {
|
||||
$num = int($secondsold / (60));
|
||||
$unit = "minute";
|
||||
} else {
|
||||
$num = $secondsold;
|
||||
$unit = "second";
|
||||
}
|
||||
return "$num $unit" . ($num==1?"":"s") . " ago";
|
||||
}
|
||||
|
||||
########## INTEREST ############
|
||||
|
||||
sub validate_int
|
||||
{
|
||||
my ($req, $errors) = @_;
|
||||
|
||||
my $int = lc($req->{'int_like'});
|
||||
$int =~ s/^\s+//;
|
||||
$int =~ s/\s+$//;
|
||||
return 0 unless $int;
|
||||
|
||||
$req->{'int_like'} = $int;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub search_int
|
||||
{
|
||||
my ($dbr, $req, $info) = @_;
|
||||
my $arg = $req->{'int_like'};
|
||||
push @{$info->{'english'}}, "are interested in \"$arg\"";
|
||||
|
||||
## find interest id, if one doth exist.
|
||||
my $qint = $dbr->quote($req->{'int_like'});
|
||||
my $intid = $dbr->selectrow_array("SELECT intid FROM interests ".
|
||||
"WHERE interest=$qint");
|
||||
unless ($intid) {
|
||||
$info->{'errmsg'} = "The interest you have entered is not valid.";
|
||||
return;
|
||||
}
|
||||
|
||||
my $UI_TABLE = $req->{'com_do'} ? "comminterests" : "userinterests";
|
||||
|
||||
return {
|
||||
'tables' => {
|
||||
'ui' => $UI_TABLE,
|
||||
},
|
||||
'conds' => [ "{ui}.intid=$intid" ],
|
||||
'userid' => "{ui}.userid",
|
||||
};
|
||||
}
|
||||
|
||||
######## HAS FRIEND ##############
|
||||
|
||||
sub validate_fr
|
||||
{
|
||||
my ($req, $errors) = @_;
|
||||
return 0 unless $req->{'fr_user'} =~ /\S/;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub search_fr
|
||||
{
|
||||
my ($dbr, $req, $info) = @_;
|
||||
|
||||
my $user = lc($req->{'fr_user'});
|
||||
my $arg = $user;
|
||||
|
||||
push @{$info->{'english'}}, "consider \"$arg\" a friend";
|
||||
|
||||
my $friendid = LJ::get_userid($user);
|
||||
|
||||
return {
|
||||
'tables' => {
|
||||
'f' => 'friends',
|
||||
},
|
||||
'conds' => [ "{f}.friendid=$friendid" ],
|
||||
'userid' => "{f}.userid",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
######## FRIEND OF ##############
|
||||
|
||||
sub validate_fro
|
||||
{
|
||||
my ($req, $errors) = @_;
|
||||
return 0 unless $req->{'fro_user'} =~ /\S/;
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub search_fro
|
||||
{
|
||||
my ($dbr, $req, $info) = @_;
|
||||
|
||||
my $user = lc($req->{'fro_user'});
|
||||
my $arg = $user;
|
||||
|
||||
push @{$info->{'english'}}, "are considered a friend by \"$arg\"";
|
||||
|
||||
my $userid = LJ::get_userid($user);
|
||||
|
||||
return {
|
||||
'tables' => {
|
||||
'f' => 'friends',
|
||||
},
|
||||
'conds' => [ "{f}.userid=$userid" ],
|
||||
'userid' => "{f}.friendid",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
########### LOCATION ###############
|
||||
|
||||
sub validate_loc
|
||||
{
|
||||
my ($req, $errors) = @_;
|
||||
return 0 unless $req->{'loc_cn'};
|
||||
|
||||
unless ($req->{'loc_cn'} =~ /^[A-Z0-9]{2}$/ || # ISO code
|
||||
$req->{'loc_cn'} =~ /^LJ/) # site-local country/region code
|
||||
{
|
||||
push @$errors, "Invalid country for location search.";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub search_loc
|
||||
{
|
||||
my ($dbr, $req, $info) = @_;
|
||||
my ($sth);
|
||||
|
||||
my ($longcountry, $longstate, $longcity);
|
||||
my $qcode = $dbr->quote(uc($req->{'loc_cn'}));
|
||||
$sth = $dbr->prepare("SELECT item FROM codes WHERE type='country' AND code=$qcode");
|
||||
$sth->execute;
|
||||
($longcountry) = $sth->fetchrow_array;
|
||||
|
||||
$longstate = lc($req->{'loc_st'});
|
||||
$longstate =~ s/(\w+)/\u$1/g;
|
||||
$longcity = lc($req->{'loc_ci'});
|
||||
$longcity =~ s/(\w+)/\u$1/g;
|
||||
|
||||
$req->{'loc_st'} = lc($req->{'loc_st'});
|
||||
$req->{'loc_ci'} = lc($req->{'loc_ci'});
|
||||
|
||||
if ($req->{'loc_cn'} eq "US") {
|
||||
my $qstate = $dbr->quote($req->{'loc_st'});
|
||||
if (length($req->{'loc_st'}) > 2) {
|
||||
## convert long state name into state code
|
||||
$sth = $dbr->prepare("SELECT code FROM codes WHERE type='state' AND item=$qstate");
|
||||
$sth->execute;
|
||||
my ($code) = $sth->fetchrow_array;
|
||||
if ($code) {
|
||||
$req->{'loc_st'} = lc($code);
|
||||
}
|
||||
} else {
|
||||
$sth = $dbr->prepare("SELECT item FROM codes WHERE type='state' AND code=$qstate");
|
||||
$sth->execute;
|
||||
($longstate) = $sth->fetchrow_array;
|
||||
}
|
||||
}
|
||||
|
||||
push @{$info->{'english'}}, "live in " . join(", ", grep { $_; } ($longcity, $longstate, $longcountry));
|
||||
|
||||
my $p = LJ::get_prop("user", "sidx_loc");
|
||||
unless ($p) {
|
||||
$info->{'errmsg'} = "Userprop sidx_loc doesn't exist. Run update-db.pl?";
|
||||
return;
|
||||
}
|
||||
|
||||
my $prefix = join("-", $req->{'loc_cn'}, $req->{'loc_st'}, $req->{'loc_ci'});
|
||||
$prefix =~ s/\-+$//; # remove trailing hyphens
|
||||
$prefix =~ s![\_\%\"\']!\\$&!g;
|
||||
|
||||
#### do the sub requests.
|
||||
|
||||
return {
|
||||
'tables' => {
|
||||
'up' => 'userprop',
|
||||
},
|
||||
'conds' => [ "{up}.upropid=$p->{'id'}",
|
||||
"{up}.value LIKE '$prefix%'",
|
||||
],
|
||||
'userid' => "{up}.userid",
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
########### GENDER ###################
|
||||
|
||||
sub validate_gen
|
||||
{
|
||||
my ($req, $errors) = @_;
|
||||
return 0 unless $req->{'gen_sel'};
|
||||
unless ($req->{'gen_sel'} eq "M" ||
|
||||
$req->{'gen_sel'} eq "F")
|
||||
{
|
||||
push @$errors, "You must select either Male or Female when searching by gender.\n";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub search_gen
|
||||
{
|
||||
my ($dbr, $req, $info) = @_;
|
||||
my $args = $req->{'gen_sel'};
|
||||
|
||||
push @{$info->{'english'}}, "are " . ($args eq "M" ? "male" : "female");
|
||||
my $qgen = $dbr->quote($args);
|
||||
|
||||
my $p = LJ::get_prop("user", "gender");
|
||||
unless ($p) {
|
||||
$info->{'errmsg'} = "Userprop gender doesn't exist. Run update-db.pl?";
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
'tables' => {
|
||||
'up' => 'userprop',
|
||||
},
|
||||
'conds' => [ "{up}.upropid=$p->{'id'}",
|
||||
"{up}.value=$qgen",
|
||||
],
|
||||
'userid' => "{up}.userid",
|
||||
};
|
||||
}
|
||||
|
||||
########### AGE ###################
|
||||
|
||||
sub validate_age
|
||||
{
|
||||
my ($req, $errors) = @_;
|
||||
return 0 if $req->{'age_min'} eq "" && $req->{'age_max'} eq "";
|
||||
|
||||
for (qw(age_min age_max)) {
|
||||
unless ($req->{$_} =~ /^\d+$/) {
|
||||
push @$errors, "Both min and max age must be specified for an age query.";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ($req->{'age_min'} > $req->{'age_max'}) {
|
||||
push @$errors, "Minimum age must be less than maximum age.";
|
||||
return 0;
|
||||
}
|
||||
if ($req->{'age_min'} < 14) {
|
||||
push @$errors, "You cannot search for users under 14 years of age.";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub search_age
|
||||
{
|
||||
my ($dbr, $req, $info) = @_;
|
||||
my $qagemin = $dbr->quote($req->{'age_min'});
|
||||
my $qagemax = $dbr->quote($req->{'age_max'});
|
||||
my $args = "$req->{'age_min'}-$req->{'age_max'}";
|
||||
|
||||
if ($req->{'age_min'} == $req->{'age_max'}) {
|
||||
push @{$info->{'english'}}, "are $req->{'age_min'} years old";
|
||||
} else {
|
||||
push @{$info->{'english'}}, "are between $req->{'age_min'} and $req->{'age_max'} years old";
|
||||
}
|
||||
|
||||
my $p = LJ::get_prop("user", "sidx_bdate");
|
||||
unless ($p) {
|
||||
$info->{'errmsg'} = "Userprop sidx_bdate doesn't exist. Run update-db.pl?";
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
'tables' => {
|
||||
'up' => 'userprop',
|
||||
},
|
||||
'conds' => [ "{up}.upropid=$p->{'id'}",
|
||||
"{up}.value BETWEEN DATE_SUB(NOW(), INTERVAL $qagemax YEAR) AND DATE_SUB(NOW(), INTERVAL $qagemin YEAR)",
|
||||
],
|
||||
'userid' => "{up}.userid",
|
||||
};
|
||||
}
|
||||
|
||||
########### UPDATE TIME ###################
|
||||
|
||||
sub validate_ut
|
||||
{
|
||||
my ($req, $errors) = @_;
|
||||
return 0 unless $req->{'ut_days'};
|
||||
unless ($req->{'ut_days'} =~ /^\d+$/) {
|
||||
push @$errors, "Days since last updated must be a postive, whole number.";
|
||||
return;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub search_ut
|
||||
{
|
||||
my ($dbr, $req, $info) = @_;
|
||||
my $qdays = $req->{'ut_days'}+0;
|
||||
|
||||
if ($qdays == 1) {
|
||||
push @{$info->{'english'}}, "have updated their journal in the past day";
|
||||
} else {
|
||||
push @{$info->{'english'}}, "have updated their journal in the past $qdays days";
|
||||
}
|
||||
|
||||
return {
|
||||
'tables' => {
|
||||
'uu' => 'userusage',
|
||||
},
|
||||
'conds' => [ "{uu}.timeupdate > DATE_SUB(NOW(), INTERVAL $qdays DAY)", ],
|
||||
'userid' => "{uu}.userid",
|
||||
};
|
||||
}
|
||||
|
||||
######### community
|
||||
|
||||
sub validate_com
|
||||
{
|
||||
my ($req, $errors) = @_;
|
||||
return 0 unless $req->{'com_do'};
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub search_com
|
||||
{
|
||||
my ($dbr, $req, $info) = @_;
|
||||
$info->{'allwhat'} = "communities";
|
||||
|
||||
return {
|
||||
'tables' => {
|
||||
'c' => 'community',
|
||||
},
|
||||
'userid' => "{c}.userid",
|
||||
};
|
||||
}
|
||||
|
||||
1;
|
||||
90
local/cgi-bin/emailcheck.pl
Executable file
90
local/cgi-bin/emailcheck.pl
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
# Function to reject bogus email addresses
|
||||
#
|
||||
|
||||
package LJ;
|
||||
|
||||
sub check_email
|
||||
{
|
||||
my ($email, $errors) = @_;
|
||||
|
||||
# Trim off whitespace and force to lowercase.
|
||||
$email =~ s/^\s+//;
|
||||
$email =~ s/\s+$//;
|
||||
$email = lc $email;
|
||||
|
||||
my $reject = sub {
|
||||
my $errcode = shift;
|
||||
my $errmsg = shift;
|
||||
# TODO: add $opts to end of check_email and make option
|
||||
# to either return error codes, or let caller supply
|
||||
# a subref to resolve error codes into native language
|
||||
# error messages (probably via BML::ML hash, or something)
|
||||
push @$errors, $errmsg;
|
||||
return;
|
||||
};
|
||||
|
||||
# Empty email addresses are not good.
|
||||
unless ($email) {
|
||||
return $reject->("empty",
|
||||
"Your email address cannot be blank.");
|
||||
}
|
||||
|
||||
# Check that the address is of the form username@some.domain.
|
||||
my ($username, $domain);
|
||||
if ($email =~ /^([^@]+)@([^@]+)/) {
|
||||
$username = $1;
|
||||
$domain = $2;
|
||||
} else {
|
||||
return $reject->("bad_form",
|
||||
"You did not give a valid email address. An email address looks like username\@some.domain");
|
||||
}
|
||||
|
||||
# Check the username for invalid characters.
|
||||
unless ($username =~ /^[^\s\",;\(\)\[\]\{\}\<\>]+$/) {
|
||||
return $reject->("bad_username",
|
||||
"You have invalid characters in your email address username.");
|
||||
}
|
||||
|
||||
# Check the domain name.
|
||||
unless ($domain =~ /^[\w-]+(\.[\w-]+)*\.(ac|ad|ae|aero|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bi|biz|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|com|coop|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|info|int|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mil|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|museum|mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)$/)
|
||||
{
|
||||
return $reject->("bad_domain",
|
||||
"Your email address domain is invalid.");
|
||||
}
|
||||
|
||||
if ($domain =~ /spam/) # to avoid spam.su and suchlike
|
||||
{
|
||||
return $reject->("bad_domain",
|
||||
"Your email address domain is invalid.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Catch misspellings of hotmail.com
|
||||
if ($domain =~ /^(otmail|hotmial|hotmil|hotamail|hotmaul|hoatmail|hatmail|htomail)\.(cm|co|com|cmo|om)$/ or
|
||||
$domain =~ /^hotmail\.(cm|co|om|cmo)$/)
|
||||
{
|
||||
return $reject->("bad_hotmail_spelling",
|
||||
"You gave $email as your email address. Are you sure you didn't mean hotmail.com?");
|
||||
}
|
||||
|
||||
# Catch misspellings of aol.com
|
||||
elsif ($domain =~ /^(ol|aoll)\.(cm|co|com|cmo|om)$/ or
|
||||
$domain =~ /^aol\.(cm|co|om|cmo)$/)
|
||||
{
|
||||
return $reject->("bad_aol_spelling",
|
||||
"You gave $email as your email address. Are you sure you didn't mean aol.com?");
|
||||
}
|
||||
|
||||
# Catch web addresses (two or more w's followed by a dot)
|
||||
# elsif ($username =~ /^www*\./)
|
||||
# {
|
||||
# return $reject->("web_address",
|
||||
# "You gave $email as your email address, but it looks more like a web address to me.");
|
||||
# }
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
1559
local/cgi-bin/ljcom.pl
Executable file
1559
local/cgi-bin/ljcom.pl
Executable file
File diff suppressed because it is too large
Load Diff
241
local/cgi-bin/ljdefaults.pl
Executable file
241
local/cgi-bin/ljdefaults.pl
Executable file
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
# Do not edit this file. You should edit ljconfig.pl, which you should have at
|
||||
# cgi-bin/ljconfig.pl. If you don't, copy it from doc/ljconfig.pl.txt to cgi-bin
|
||||
# and edit it there. This file only provides backup default values for upgrading.
|
||||
#
|
||||
|
||||
{
|
||||
package LJ;
|
||||
use Sys::Hostname ();
|
||||
|
||||
$DEFAULT_STYLE ||= {
|
||||
'core' => 'core1',
|
||||
'layout' => 'generator/layout',
|
||||
'i18n' => 'generator/en',
|
||||
};
|
||||
|
||||
# cluster 0 is no longer supported
|
||||
$DEFAULT_CLUSTER ||= 1;
|
||||
@CLUSTERS = (1) unless @CLUSTERS;
|
||||
|
||||
$HOME = $ENV{'LJHOME'};
|
||||
$HTDOCS = "$HOME/htdocs";
|
||||
$BIN = "$HOME/bin";
|
||||
|
||||
$SERVER_NAME ||= Sys::Hostname::hostname();
|
||||
|
||||
$UNICODE = 1 unless defined $UNICODE;
|
||||
|
||||
@LANGS = ("en") unless @LANGS;
|
||||
$DEFAULT_LANG ||= $LANGS[0];
|
||||
|
||||
$SITENAME ||= "NameNotConfigured.com";
|
||||
unless ($SITENAMESHORT) {
|
||||
$SITENAMESHORT = $SITENAME;
|
||||
$SITENAMESHORT =~ s/\..*//; # remove .net/.com/etc
|
||||
}
|
||||
$SITENAMEABBREV ||= "[??]";
|
||||
|
||||
$NODB_MSG ||= "Database temporarily unavailable. Try again shortly.";
|
||||
$MSG_READONLY_USER ||= "Database temporarily in read-only mode during maintenance.";
|
||||
|
||||
$SITEROOT ||= "http://www.$DOMAIN:8011";
|
||||
$IMGPREFIX ||= "$SITEROOT/img";
|
||||
$STATPREFIX ||= "$SITEROOT/stc";
|
||||
$JSPREFIX ||= "$SITEROOT/js";
|
||||
$USERPIC_ROOT ||= "$LJ::SITEROOT/userpic";
|
||||
$PALIMGROOT ||= "$LJ::SITEROOT/palimg";
|
||||
|
||||
if ($LJ::DB_USERIDMAP ||= "") {
|
||||
$LJ::DB_USERIDMAP .= "." unless $LJ::DB_USERIDMAP =~ /\.$/;
|
||||
}
|
||||
|
||||
# path to sendmail and any necessary options
|
||||
$SENDMAIL ||= "/usr/sbin/sendmail -t -oi";
|
||||
|
||||
# protocol, mailserver hostname, and preferential weight.
|
||||
# qmtp, smtp, dmtp, and sendmail are the currently supported protocols.
|
||||
@MAIL_TRANSPORTS = ( [ 'sendmail', $SENDMAIL, 1 ] ) unless @MAIL_TRANSPORTS;
|
||||
|
||||
# where we set the cookies (note the period before the domain)
|
||||
$COOKIE_DOMAIN ||= ".$DOMAIN";
|
||||
$COOKIE_PATH ||= "/";
|
||||
@COOKIE_DOMAIN_RESET = ("", "$DOMAIN", ".$DOMAIN") unless @COOKIE_DOMAIN_RESET;
|
||||
|
||||
## default portal options
|
||||
@PORTAL_COLS = qw(main right moz) unless (@PORTAL_COLS);
|
||||
|
||||
$PORTAL_URI ||= "/portal/"; # either "/" or "/portal/"
|
||||
|
||||
$PORTAL_LOGGED_IN ||= {'main' => [
|
||||
[ 'update', 'mode=full'],
|
||||
],
|
||||
'right' => [
|
||||
[ 'stats', '', ],
|
||||
[ 'bdays', '', ],
|
||||
[ 'popfaq', '', ],
|
||||
] };
|
||||
$PORTAL_LOGGED_OUT ||= {'main' => [
|
||||
[ 'update', 'mode='],
|
||||
],
|
||||
'right' => [
|
||||
[ 'login', '', ],
|
||||
[ 'stats', '', ],
|
||||
[ 'randuser', '', ],
|
||||
[ 'popfaq', '', ],
|
||||
],
|
||||
'moz' => [
|
||||
[ 'login', '', ],
|
||||
],
|
||||
};
|
||||
|
||||
# this option can be a boolean or a URL, but internally we want a URL
|
||||
# (which can also be a boolean)
|
||||
if ($LJ::OPENID_SERVER && $LJ::OPENID_SERVER == 1) {
|
||||
$LJ::OPENID_SERVER = "$LJ::SITEROOT/openid/server.bml";
|
||||
}
|
||||
|
||||
# set default capability limits if the site maintainer hasn't.
|
||||
{
|
||||
my %defcap = (
|
||||
'checkfriends' => 1,
|
||||
'checkfriends_interval' => 60,
|
||||
'friendsviewupdate' => 30,
|
||||
'makepoll' => 1,
|
||||
'maxfriends' => 500,
|
||||
'moodthemecreate' => 1,
|
||||
'styles' => 1,
|
||||
's2styles' => 1,
|
||||
's2viewentry' => 1,
|
||||
's2viewreply' => 1,
|
||||
's2stylesmax' => 10,
|
||||
's2layersmax' => 50,
|
||||
'textmessage' => 1,
|
||||
'todomax' => 100,
|
||||
'todosec' => 1,
|
||||
'userdomain' => 0,
|
||||
'useremail' => 0,
|
||||
'userpics' => 5,
|
||||
'findsim' => 1,
|
||||
'full_rss' => 1,
|
||||
'can_post' => 1,
|
||||
'get_comments' => 1,
|
||||
'leave_comments' => 1,
|
||||
'mod_queue' => 50,
|
||||
'mod_queue_per_poster' => 1,
|
||||
'weblogscom' => 0,
|
||||
'hide_email_after' => 0,
|
||||
'userlinks' => 5,
|
||||
'maxcomments' => $MAXCOMMENTS || 5000,
|
||||
'rateperiod-lostinfo' => 24*60, # 24 hours
|
||||
'rateallowed-lostinfo' => 5,
|
||||
'tools_recent_comments_display' => 110,
|
||||
);
|
||||
foreach my $k (keys %defcap) {
|
||||
next if (defined $LJ::CAP_DEF{$k});
|
||||
$LJ::CAP_DEF{$k} = $defcap{$k};
|
||||
}
|
||||
}
|
||||
|
||||
# FIXME: should forcibly limit userlinks to 255 (tinyint)
|
||||
|
||||
# set default userprop limits if site maintainer hasn't
|
||||
{
|
||||
my %defuser = (
|
||||
's1_lastn_style' => 29,
|
||||
's1_friends_style' => 20,
|
||||
's1_calendar_style' => 2,
|
||||
's1_day_style' => 11,
|
||||
);
|
||||
foreach my $k (keys %defuser) {
|
||||
next if (defined $LJ::USERPROP_DEF{$k});
|
||||
$LJ::USERPROP_DEF{$k} = $defuser{$k};
|
||||
}
|
||||
}
|
||||
|
||||
# Send community invites from the admin address unless otherwise specified
|
||||
$COMMUNITY_EMAIL ||= $ADMIN_EMAIL;
|
||||
|
||||
# By default, auto-detect account types for
|
||||
# <lj user> tags only if using memcache
|
||||
unless (defined $LJ::DYNAMIC_LJUSER) {
|
||||
$LJ::DYNAMIC_LJUSER = scalar(@LJ::MEMCACHE_SERVERS) ? 1 : 0;
|
||||
}
|
||||
|
||||
# The list of content types that we consider valid for gzip compression.
|
||||
%GZIP_OKAY = (
|
||||
'text/html' => 1, # regular web pages; XHTML 1.0 "may" be this
|
||||
'text/xml' => 1, # regular XML files
|
||||
'application/xml' => 1, # XHTML 1.1 "may" be this
|
||||
'application/xhtml+xml' => 1, # XHTML 1.1 "should" be this
|
||||
'application/rdf+xml' => 1, # FOAF should be this
|
||||
) unless %GZIP_OKAY;
|
||||
|
||||
# maximum FOAF friends to return (so the server doesn't get overloaded)
|
||||
$MAX_FOAF_FRIENDS ||= 1000;
|
||||
|
||||
# maximum number of friendofs to load/memcache (affects userinfo.bml display)
|
||||
$MAX_FRIENDOF_LOAD ||= 5000;
|
||||
|
||||
# whether to proactively delete any comments associated with an entry when we assign
|
||||
# a new jitemid (see the big comment above LJ::Protocol::new_entry_cleanup_hack)
|
||||
$NEW_ENTRY_CLEANUP_HACK ||= 0;
|
||||
|
||||
# block size is used in stats generation code that gets n rows from the db at a time
|
||||
$STATS_BLOCK_SIZE ||= 10_000;
|
||||
|
||||
# Maximum number of comments to display on Recent Comments page
|
||||
$TOOLS_RECENT_COMMENTS_MAX ||= 110;
|
||||
|
||||
# setup the mogilefs defaults so we can create the necessary domains
|
||||
# and such. it is not recommended that you change the name of the
|
||||
# classes. you can feel free to add your own or alter the mindevcount
|
||||
# from within ljconfig.pl, but the LiveJournal code uses these class
|
||||
# names elsewhere and depends on them existing if you're using MogileFS
|
||||
# for storage.
|
||||
#
|
||||
# also note that this won't actually do anything unless you have
|
||||
# defined a MOGILEFS_CONFIG hash in ljconfig.pl and you explicitly set
|
||||
# at least the hosts key to be an arrayref of ip:port combinations
|
||||
# indicating where to reach your local MogileFS server.
|
||||
%MOGILEFS_CONFIG = () unless scalar(%MOGILEFS_CONFIG);
|
||||
$MOGILEFS_CONFIG{domain} ||= 'livejournal';
|
||||
$MOGILEFS_CONFIG{classes} ||= {};
|
||||
$MOGILEFS_CONFIG{classes}->{userpics} ||= 3;
|
||||
$MOGILEFS_CONFIG{classes}->{captcha} ||= 2;
|
||||
|
||||
# Default to allow all reproxying.
|
||||
%REPROXY_DISABLE = () unless %REPROXY_DISABLE;
|
||||
|
||||
# Default error message for age verification needed
|
||||
$UNDERAGE_ERROR ||= "Sorry, your account needs to be <a href='$SITEROOT/agecheck/'>age verified</a> before you can leave any comments.";
|
||||
|
||||
# Terms of Service revision requirements
|
||||
foreach (
|
||||
[ rev => '0.0' ],
|
||||
[ title => 'Terms of Service agreement required' ],
|
||||
[ html => '' ],
|
||||
[ text => '' ]
|
||||
)
|
||||
{
|
||||
$LJ::REQUIRED_TOS{$_->[0]} = $_->[1]
|
||||
unless defined $LJ::REQUIRED_TOS{$_->[0]};
|
||||
}
|
||||
|
||||
# setup default minimal style information
|
||||
$MINIMAL_USERAGENT{$_} ||= 1 foreach qw(Links Lynx w BlackBerry); # w is for w3m
|
||||
$MINIMAL_BML_SCHEME ||= 'lynx';
|
||||
$MINIMAL_STYLE{'core'} ||= 'core1';
|
||||
|
||||
# maximum size to cache s2compiled data
|
||||
$MAX_S2COMPILED_CACHE_SIZE ||= 7500; # bytes
|
||||
$S2COMPILED_MIGRATION_DONE ||= 0; # turn on after s2compiled2 migration
|
||||
|
||||
}
|
||||
|
||||
# no dependencies.
|
||||
# <LJDEP>
|
||||
# </LJDEP>
|
||||
|
||||
return 1;
|
||||
567
local/cgi-bin/ljfeed.pl
Executable file
567
local/cgi-bin/ljfeed.pl
Executable file
@@ -0,0 +1,567 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
|
||||
package LJ::Feed;
|
||||
|
||||
eval "use LJR::Distributed;";
|
||||
my $ljr = $@ ? 0 : 1;
|
||||
|
||||
if ($ljr) {
|
||||
use LJR::Distributed;
|
||||
require "$ENV{'LJHOME'}/cgi-bin/ljpoll.pl";
|
||||
}
|
||||
|
||||
|
||||
my %feedtypes = (
|
||||
rss => \&create_view_rss,
|
||||
atom => \&create_view_atom,
|
||||
foaf => \&create_view_foaf,
|
||||
);
|
||||
|
||||
sub make_feed
|
||||
{
|
||||
my ($r, $u, $remote, $opts) = @_;
|
||||
|
||||
$opts->{pathextra} =~ s!^/(\w+)!!;
|
||||
my $feedtype = $1;
|
||||
my $viewfunc = $feedtypes{$feedtype};
|
||||
|
||||
unless ($viewfunc) {
|
||||
$opts->{'handler_return'} = 404;
|
||||
return undef;
|
||||
}
|
||||
|
||||
$r->notes('codepath' => "feed.$feedtype") if $r;
|
||||
|
||||
if ($u->{'journaltype'} eq "R" && $u->{'renamedto'} ne "") {
|
||||
$opts->{'redir'} = LJ::journal_base($u->{'renamedto'}, $opts->{'vhost'}) . "/data/$feedtype";
|
||||
return undef;
|
||||
}
|
||||
|
||||
my $userid = $u->{'userid'};
|
||||
|
||||
# try cached copy
|
||||
# (ljprotocol.pl: memcache "rss:$userid" deleted if "rsslastmod:$userid" changed)
|
||||
my $lastmod = LJ::MemCache::get([$userid, "rsslastmod:$userid"]); # more strict than timeupdate: care of edit!
|
||||
|
||||
my $ims = $r->header_in('If-Modified-Since');
|
||||
my $theirtime = LJ::http_to_time($ims) if $ims;
|
||||
|
||||
# If-Modified-Since, try #1
|
||||
if (defined $lastmod && $ims && $feedtype ne 'foaf' && $theirtime >= $lastmod) {
|
||||
#$opts->{'handler_return'} = 304; #Not Modified
|
||||
$opts->{'notmodified'} = 1;
|
||||
return undef;
|
||||
}
|
||||
|
||||
# text already in memcache -
|
||||
if ($feedtype eq 'rss' && !$remote) {
|
||||
my $data = LJ::MemCache::get([$userid, "rss:$userid"]); #{lastmod => $lastmod, text => $ret}
|
||||
if ($data) {
|
||||
if (!defined $lastmod) {
|
||||
LJ::MemCache::set([$userid, "rsslastmod:$userid"], $data->{lastmod});
|
||||
}
|
||||
$opts->{'cachecontrol'} = 'max-age=600, private, proxy-revalidate';
|
||||
$opts->{'contenttype'} = 'text/xml; charset='.$opts->{'saycharset'};
|
||||
#$r->header_out("Last-Modified", LJ::time_to_http($data->{lastmod}));
|
||||
$r->set_last_modified($data->{lastmod});
|
||||
return $data->{text};
|
||||
}
|
||||
}
|
||||
|
||||
LJ::load_user_props($u, qw/ journaltitle journalsubtitle opt_synlevel /);
|
||||
|
||||
LJ::text_out(\$u->{$_})
|
||||
foreach ("name", "url", "urlname");
|
||||
|
||||
# opt_synlevel will default to 'full'
|
||||
$u->{'opt_synlevel'} = 'full'
|
||||
unless $u->{'opt_synlevel'} =~ /^(?:full|summary|title)$/;
|
||||
|
||||
# some data used throughout the channel
|
||||
my $journalinfo = {
|
||||
u => $u,
|
||||
link => LJ::journal_base($u) . "/",
|
||||
title => $u->{journaltitle} || $u->{name} || $u->{user},
|
||||
subtitle => $u->{journalsubtitle} || $u->{name},
|
||||
builddate => LJ::time_to_http(time()),
|
||||
};
|
||||
|
||||
# if we do not want items for this view, just call out
|
||||
$opts->{noitems} = 1 if $feedtype eq 'foaf';
|
||||
|
||||
return $viewfunc->($journalinfo, $u, $opts)
|
||||
if ($opts->{'noitems'});
|
||||
|
||||
# for syndicated accounts, redirect to the syndication URL
|
||||
# However, we only want to do this if the data we're returning
|
||||
# is similar. (Not FOAF, for example)
|
||||
|
||||
if ($u->{'journaltype'} eq 'Y') {
|
||||
my $dbr = LJ::get_db_reader();
|
||||
my $synurl = $dbr->selectrow_array("SELECT synurl FROM syndicated WHERE userid=$userid");
|
||||
unless ($synurl) {
|
||||
return 'No syndication URL available.';
|
||||
}
|
||||
$opts->{'redir'} = $synurl;
|
||||
return undef;
|
||||
}
|
||||
|
||||
## load the items
|
||||
my @items = LJ::get_recent_items({
|
||||
'u' => $u,
|
||||
'clustersource' => 'slave',
|
||||
'remote' => $remote,
|
||||
'itemshow' => 25,
|
||||
'order' => "logtime",
|
||||
'friendsview' => 1, # this returns rlogtimes
|
||||
'dateformat' => "S2", # S2 format time format is easier
|
||||
});
|
||||
|
||||
if (!defined $lastmod) {
|
||||
$lastmod = @items ? $LJ::EndOfTime - $items[0]->{'rlogtime'} : 0;
|
||||
LJ::MemCache::set([$userid, "rsslastmod:$userid"], $lastmod);
|
||||
|
||||
# If-Modified-Since, try #2
|
||||
if ($ims && $theirtime >= $lastmod) {
|
||||
#$opts->{'handler_return'} = 304; #Not Modified
|
||||
$opts->{'notmodified'} = 1;
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
# set last-modified header:
|
||||
#$r->header_out("Last-Modified", LJ::time_to_http($lastmod));
|
||||
$r->set_last_modified($lastmod); # including $lastmod = 0
|
||||
$journalinfo->{'modtime'} = $lastmod;
|
||||
|
||||
#$opts->{'cachecontrol'} = 'max-age=600, proxy-revalidate'; # unless $remote; #hmm
|
||||
$opts->{'cachecontrol'} = 'max-age=600, private, proxy-revalidate'; # if $remote;
|
||||
$opts->{'contenttype'} = 'text/xml; charset='.$opts->{'saycharset'};
|
||||
|
||||
|
||||
# email address of journal owner, but respect their privacy settings
|
||||
if ($u->{'allow_contactshow'} eq "Y" && $u->{'opt_whatemailshow'} ne "N" && $u->{'opt_mangleemail'} ne "Y") {
|
||||
my $cemail;
|
||||
|
||||
# default to their actual email
|
||||
$cemail = $u->{'email'};
|
||||
|
||||
# use their livejournal email if they have one
|
||||
if ($LJ::USER_EMAIL && $u->{'opt_whatemailshow'} eq "L" &&
|
||||
LJ::get_cap($u, "useremail") && ! $u->{'no_mail_alias'}) {
|
||||
|
||||
$cemail = "$u->{'user'}\@$LJ::USER_DOMAIN";
|
||||
}
|
||||
|
||||
# clean it up since we know we have one now
|
||||
$journalinfo->{email} = $cemail;
|
||||
} else { $journalinfo->{email} = "$u->{'user'} at lj.rossia.org"; }
|
||||
|
||||
my %posteru = (); # map posterids to u objects
|
||||
LJ::load_userids_multiple([map { $_->{'posterid'}, \$posteru{$_->{'posterid'}} } @items], [$u]);
|
||||
|
||||
my @cleanitems;
|
||||
ENTRY:
|
||||
foreach my $it (@items)
|
||||
{
|
||||
# load required data
|
||||
my $itemid = $it->{'itemid'};
|
||||
my $ditemid = $itemid*256 + $it->{'anum'};
|
||||
|
||||
next ENTRY if $posteru{$it->{'posterid'}} && $posteru{$it->{'posterid'}}->{'statusvis'} eq 'S';
|
||||
|
||||
my $props = $it->{'props'};
|
||||
|
||||
# see if we have a subject and clean it
|
||||
my $subject = $it->{'text'}->[0];
|
||||
if ($subject) {
|
||||
$subject =~ s/[\r\n]/ /g;
|
||||
LJ::CleanHTML::clean_subject_all(\$subject);
|
||||
}
|
||||
|
||||
# an HTML link to the entry. used if we truncate or summarize
|
||||
my $readmore = "<b>(<a href=\"$journalinfo->{link}$ditemid.html\">Read more ...</a>)</b>";
|
||||
|
||||
# empty string so we don't waste time cleaning an entry that won't be used
|
||||
my $event = $u->{'opt_synlevel'} eq 'title' ? '' : $it->{'text'}->[1];
|
||||
|
||||
# clean the event, if non-empty
|
||||
my $ppid = 0;
|
||||
if ($event) {
|
||||
|
||||
# users without 'full_rss' get their logtext bodies truncated
|
||||
# do this now so that the html cleaner will hopefully fix html we break
|
||||
unless (LJ::get_cap($u, 'full_rss')) {
|
||||
my $trunc = LJ::text_trim($event, 0, 80);
|
||||
$event = "$trunc $readmore" if $trunc ne $event;
|
||||
}
|
||||
|
||||
LJ::CleanHTML::clean_event(\$event, {'ljcut_disable'=>1},
|
||||
{ 'preformatted' => $props->{'opt_preformatted'} });
|
||||
|
||||
# do this after clean so we don't have to about know whether or not
|
||||
# the event is preformatted
|
||||
if ($u->{'opt_synlevel'} eq 'summary') {
|
||||
|
||||
# assume the first paragraph is terminated by two <br> or a </p>
|
||||
# valid XML tags should be handled, even though it makes an uglier regex
|
||||
if ($event =~ m!((<br\s*/?\>(</br\s*>)?\s*){2})|(</p\s*>)!i) {
|
||||
# everything before the matched tag + the tag itself
|
||||
# + a link to read more
|
||||
$event = $` . $& . $readmore;
|
||||
}
|
||||
}
|
||||
|
||||
LJ::Poll::replace_polls_with_links(\$event);
|
||||
LJ::EmbedModule->expand_entry($u, \$event, ('content_only' => 1));
|
||||
|
||||
$ppid = $1
|
||||
if $event =~ m!<lj-phonepost journalid=['"]\d+['"] dpid=['"](\d+)['"] />!; #'
|
||||
}
|
||||
|
||||
my $mood;
|
||||
if ($props->{'current_mood'}) {
|
||||
$mood = $props->{'current_mood'};
|
||||
} elsif ($props->{'current_moodid'}) {
|
||||
$mood = LJ::mood_name($props->{'current_moodid'}+0);
|
||||
}
|
||||
|
||||
if ($ljr) {
|
||||
LJR::Distributed::sign_exported_rss_entry($u, $it->{'itemid'}, $it->{'anum'}, \$event);
|
||||
}
|
||||
|
||||
my $createtime = $LJ::EndOfTime - $it->{rlogtime};
|
||||
|
||||
my $cleanitem = {
|
||||
itemid => $itemid,
|
||||
ditemid => $ditemid,
|
||||
subject => $subject,
|
||||
event => $event,
|
||||
createtime => $createtime,
|
||||
eventtime => $it->{alldatepart}, # ugly: this is of a different format than the other two times.
|
||||
modtime => $props->{revtime} || $createtime,
|
||||
comments => ($props->{'opt_nocomments'} == 0),
|
||||
music => $props->{'current_music'},
|
||||
mood => $mood,
|
||||
ppid => $ppid,
|
||||
tags => $props->{'tags'},
|
||||
};
|
||||
push @cleanitems, $cleanitem;
|
||||
}
|
||||
|
||||
# fix up the build date to use entry-time
|
||||
# (empty journals show 01 Jan 1970 00:00:00 GMT as build date and Last-Modified time).
|
||||
$journalinfo->{'builddate'} = LJ::time_to_http(@items ? $LJ::EndOfTime - $items[0]->{'rlogtime'} : 0),
|
||||
|
||||
return $viewfunc->($journalinfo, $u, $opts, \@cleanitems, $remote, $lastmod);
|
||||
}
|
||||
|
||||
|
||||
# the creator for the RSS XML syndication view
|
||||
sub create_view_rss
|
||||
{
|
||||
my ($journalinfo, $u, $opts, $cleanitems, $remote, $lastmod) = @_;
|
||||
|
||||
my $ret;
|
||||
|
||||
# header
|
||||
$ret .= "<?xml version='1.0' encoding='$opts->{'saycharset'}' ?>\n";
|
||||
$ret .= LJ::run_hook("bot_director", "<!-- ", " -->") . "\n";
|
||||
$ret .= "<rss version='2.0' xmlns:lj='http://www.livejournal.org/rss/lj/1.0/'>\n";
|
||||
|
||||
# channel attributes
|
||||
$ret .= "<channel>\n";
|
||||
$ret .= " <title>" . LJ::exml($journalinfo->{title}) . "</title>\n";
|
||||
$ret .= " <link>$journalinfo->{link}</link>\n";
|
||||
$ret .= " <description>" . LJ::exml("$journalinfo->{title} - $LJ::SITENAME") . "</description>\n";
|
||||
|
||||
if ($u->{'opt_blockrobots'}) {
|
||||
$ret .= " <copyright>noindex</copyright>\n";
|
||||
}
|
||||
|
||||
$ret .= " <managingEditor>" . $journalinfo->{title} . "</managingEditor>\n" if $journalinfo->{email};
|
||||
$ret .= " <lastBuildDate>$journalinfo->{builddate}</lastBuildDate>\n";
|
||||
$ret .= " <generator>LiveJournal / $LJ::SITENAME</generator>\n";
|
||||
# TODO: add 'language' field when user.lang has more useful information
|
||||
|
||||
### image block, returns info for their current userpic
|
||||
if ($u->{'defaultpicid'}) {
|
||||
my $pic = {};
|
||||
LJ::load_userpics($pic, [ $u, $u->{'defaultpicid'} ]);
|
||||
$pic = $pic->{$u->{'defaultpicid'}}; # flatten
|
||||
|
||||
$ret .= " <image>\n";
|
||||
$ret .= " <url>$LJ::USERPIC_ROOT/$u->{'defaultpicid'}/$u->{'userid'}</url>\n";
|
||||
$ret .= " <title>" . LJ::exml($journalinfo->{title}) . "</title>\n";
|
||||
$ret .= " <link>$journalinfo->{link}</link>\n";
|
||||
$ret .= " <width>$pic->{'width'}</width>\n";
|
||||
$ret .= " <height>$pic->{'height'}</height>\n";
|
||||
$ret .= " </image>\n\n";
|
||||
}
|
||||
|
||||
# output individual item blocks
|
||||
|
||||
foreach my $it (@$cleanitems)
|
||||
{
|
||||
my $itemid = $it->{itemid};
|
||||
my $ditemid = $it->{ditemid};
|
||||
$ret .= "<item>\n";
|
||||
$ret .= " <guid isPermaLink='true'>$journalinfo->{link}$ditemid.html</guid>\n";
|
||||
$ret .= " <pubDate>" . LJ::time_to_http($it->{createtime}) . "</pubDate>\n";
|
||||
$ret .= " <title>" . LJ::exml($it->{subject}) . "</title>\n" if $it->{subject};
|
||||
## hide e-mail for security concern
|
||||
## $ret .= " <author>" . LJ::exml($journalinfo->{email}) . "</author>" if $journalinfo->{email};
|
||||
$ret .= " <link>$journalinfo->{link}$ditemid.html</link>\n";
|
||||
# omit the description tag if we're only syndicating titles
|
||||
# note: the $event was also emptied earlier, in make_feed
|
||||
unless ($u->{'opt_synlevel'} eq 'title') {
|
||||
$ret .= " <description>" . LJ::exml($it->{event}) . "</description>\n";
|
||||
}
|
||||
if ($it->{comments}) {
|
||||
$ret .= " <comments>$journalinfo->{link}$ditemid.html</comments>\n";
|
||||
}
|
||||
$ret .= " <category>$_</category>\n" foreach map { LJ::exml($_) } @{$it->{tags} || []};
|
||||
# support 'podcasting' enclosures
|
||||
$ret .= LJ::run_hook( "pp_rss_enclosure",
|
||||
{ userid => $u->{userid}, ppid => $it->{ppid} }) if $it->{ppid};
|
||||
# TODO: add author field with posterid's email address, respect communities
|
||||
$ret .= " <lj:music>" . LJ::exml($it->{music}) . "</lj:music>\n" if $it->{music};
|
||||
$ret .= " <lj:mood>" . LJ::exml($it->{mood}) . "</lj:mood>\n" if $it->{mood};
|
||||
$ret .= "</item>\n";
|
||||
}
|
||||
|
||||
$ret .= "</channel>\n";
|
||||
$ret .= "</rss>\n";
|
||||
|
||||
#store to memcached, anonymous
|
||||
LJ::MemCache::set([$u->{userid}, "rss:$u->{userid}"], {lastmod => $lastmod, text => $ret}) unless $remote;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
# the creator for the Atom view
|
||||
# keys of $opts:
|
||||
# saycharset - required: the charset of the feed
|
||||
# noheader - only output an <entry>..</entry> block. off by default
|
||||
# apilinks - output AtomAPI links for posting a new entry or
|
||||
# getting/editing/deleting an existing one. off by default
|
||||
# TODO: define and use an 'lj:' namespace
|
||||
|
||||
sub create_view_atom
|
||||
{
|
||||
my ($journalinfo, $u, $opts, $cleanitems) = @_;
|
||||
|
||||
my $ret;
|
||||
|
||||
# prolog line
|
||||
$ret .= "<?xml version='1.0' encoding='$opts->{'saycharset'}' ?>\n";
|
||||
$ret .= LJ::run_hook("bot_director", "<!-- ", " -->");
|
||||
|
||||
# AtomAPI interface
|
||||
my $api = $opts->{'apilinks'} ? "$LJ::SITEROOT/interface/atom" :
|
||||
"$LJ::SITEROOT/users/$u->{user}/data/atom";
|
||||
|
||||
# header
|
||||
unless ($opts->{'noheader'}) {
|
||||
$ret .= "<feed version='0.3' xmlns='http://purl.org/atom/ns#'>\n";
|
||||
|
||||
# attributes
|
||||
$ret .= "<title mode='escaped'>" . LJ::exml($journalinfo->{title}) . "</title>\n";
|
||||
$ret .= "<tagline mode='escaped'>" . LJ::exml($journalinfo->{subtitle}) . "</tagline>\n"
|
||||
if $journalinfo->{subtitle};
|
||||
$ret .= "<link rel='alternate' type='text/html' href='$journalinfo->{link}' />\n";
|
||||
|
||||
# last update
|
||||
$ret .= "<modified>" . LJ::time_to_w3c($journalinfo->{'modtime'}, 'Z')
|
||||
. "</modified>";
|
||||
|
||||
# link to the AtomAPI version of this feed
|
||||
$ret .= "<link rel='service.feed' type='application/x.atom+xml' title='";
|
||||
$ret .= LJ::ehtml($journalinfo->{title});
|
||||
$ret .= $opts->{'apilinks'} ? "' href='$api/feed' />" : "' href='$api' />";
|
||||
|
||||
if ($opts->{'apilinks'}) {
|
||||
$ret .= "<link rel='service.post' type='application/x.atom+xml' title='Create a new post' href='$api/post' />";
|
||||
}
|
||||
}
|
||||
|
||||
# output individual item blocks
|
||||
|
||||
foreach my $it (@$cleanitems)
|
||||
{
|
||||
my $itemid = $it->{itemid};
|
||||
my $ditemid = $it->{ditemid};
|
||||
|
||||
$ret .= " <entry xmlns=\"http://purl.org/atom/ns#\">\n";
|
||||
# include empty tag if we don't have a subject.
|
||||
$ret .= " <title mode='escaped'>" . LJ::exml($it->{subject}) . "</title>\n";
|
||||
$ret .= " <id>urn:lj:$LJ::DOMAIN:atom1:$journalinfo->{u}{user}:$ditemid</id>\n";
|
||||
$ret .= " <link rel='alternate' type='text/html' href='$journalinfo->{link}$ditemid.html' />\n";
|
||||
if ($opts->{'apilinks'}) {
|
||||
$ret .= "<link rel='service.edit' type='application/x.atom+xml' title='Edit this post' href='$api/edit/$itemid' />";
|
||||
}
|
||||
$ret .= " <created>" . LJ::time_to_w3c($it->{createtime}, 'Z') . "</created>\n"
|
||||
if $it->{createtime} != $it->{modtime};
|
||||
|
||||
my ($year, $mon, $mday, $hour, $min, $sec) = split(/ /, $it->{eventtime});
|
||||
$ret .= " <issued>" . sprintf("%04d-%02d-%02dT%02d:%02d:%02d",
|
||||
$year, $mon, $mday,
|
||||
$hour, $min, $sec) . "</issued>\n";
|
||||
$ret .= " <modified>" . LJ::time_to_w3c($it->{modtime}, 'Z') . "</modified>\n";
|
||||
$ret .= " <author>\n";
|
||||
$ret .= " <name>" . LJ::exml($journalinfo->{u}{name}) . "</name>\n";
|
||||
## hide e-mail for security concern
|
||||
## $ret .= " <email>" . LJ::exml($journalinfo->{email}) . "</email>\n" if $journalinfo->{email};
|
||||
$ret .= " </author>\n";
|
||||
$ret .= " <category term='$_' />\n" foreach map { LJ::exml($_) } @{$it->{tags} || []};
|
||||
# if syndicating the complete entry
|
||||
# -print a content tag
|
||||
# elsif syndicating summaries
|
||||
# -print a summary tag
|
||||
# else (code omitted), we're syndicating title only
|
||||
# -print neither (the title has already been printed)
|
||||
# note: the $event was also emptied earlier, in make_feed
|
||||
if ($u->{'opt_synlevel'} eq 'full') {
|
||||
$ret .= " <content type='text/html' mode='escaped'>" . LJ::exml($it->{event}) . "</content>\n";
|
||||
} elsif ($u->{'opt_synlevel'} eq 'summary') {
|
||||
$ret .= " <summary type='text/html' mode='escaped'>" . LJ::exml($it->{event}) . "</summary>\n";
|
||||
}
|
||||
|
||||
$ret .= " </entry>\n";
|
||||
}
|
||||
|
||||
unless ($opts->{'noheader'}) {
|
||||
$ret .= "</feed>\n";
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
# create a FOAF page for a user
|
||||
sub create_view_foaf {
|
||||
my ($journalinfo, $u, $opts) = @_;
|
||||
my $comm = ($u->{journaltype} eq 'C');
|
||||
|
||||
my $ret;
|
||||
|
||||
# return nothing if we're not a user
|
||||
unless ($u->{journaltype} eq 'P' || $comm) {
|
||||
$opts->{handler_return} = 404;
|
||||
return undef;
|
||||
}
|
||||
|
||||
# set our content type
|
||||
$opts->{contenttype} = 'application/rdf+xml; charset=' . $opts->{saycharset};
|
||||
|
||||
# setup userprops we will need
|
||||
LJ::load_user_props($u, qw{
|
||||
aolim icq yahoo jabber msn url urlname external_foaf_url
|
||||
});
|
||||
|
||||
# create bare foaf document, for now
|
||||
$ret = "<?xml version='1.0'?>\n";
|
||||
$ret .= LJ::run_hook("bot_director", "<!-- ", " -->");
|
||||
$ret .= "<rdf:RDF\n";
|
||||
$ret .= " xml:lang=\"en\"\n";
|
||||
$ret .= " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n";
|
||||
$ret .= " xmlns:rdfs=\"http://www.w3.org/2000/01/rdf-schema#\"\n";
|
||||
$ret .= " xmlns:foaf=\"http://xmlns.com/foaf/0.1/\"\n";
|
||||
$ret .= " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n";
|
||||
|
||||
# precompute some values
|
||||
my $digest = Digest::SHA1::sha1_hex('mailto:' . $u->{email});
|
||||
|
||||
# channel attributes
|
||||
$ret .= ($comm ? " <foaf:Group>\n" : " <foaf:Person>\n");
|
||||
$ret .= " <foaf:nick>$u->{user}</foaf:nick>\n";
|
||||
if ($u->{bdate} && $u->{bdate} ne "0000-00-00" && !$comm && $u->{allow_infoshow} eq 'Y') {
|
||||
my $bdate = $u->{bdate};
|
||||
$bdate =~ s/^0000-//;
|
||||
$ret .= " <foaf:dateOfBirth>$bdate</foaf:dateOfBirth>\n";
|
||||
}
|
||||
$ret .= " <foaf:mbox_sha1sum>$digest</foaf:mbox_sha1sum>\n";
|
||||
$ret .= " <foaf:page>\n";
|
||||
$ret .= " <foaf:Document rdf:about=\"$LJ::SITEROOT/userinfo.bml?user=$u->{user}\">\n";
|
||||
$ret .= " <dc:title>$LJ::SITENAME Profile</dc:title>\n";
|
||||
$ret .= " <dc:description>Full $LJ::SITENAME profile, including information such as interests and bio.</dc:description>\n";
|
||||
$ret .= " </foaf:Document>\n";
|
||||
$ret .= " </foaf:page>\n";
|
||||
|
||||
# we want to bail out if they have an external foaf file, because
|
||||
# we want them to be able to provide their own information.
|
||||
if ($u->{external_foaf_url}) {
|
||||
$ret .= " <rdfs:seeAlso rdf:resource=\"" . LJ::eurl($u->{external_foaf_url}) . "\" />\n";
|
||||
$ret .= ($comm ? " </foaf:Group>\n" : " </foaf:Person>\n");
|
||||
$ret .= "</rdf:RDF>\n";
|
||||
return $ret;
|
||||
}
|
||||
|
||||
# contact type information
|
||||
my %types = (
|
||||
aolim => 'aimChatID',
|
||||
icq => 'icqChatID',
|
||||
yahoo => 'yahooChatID',
|
||||
msn => 'msnChatID',
|
||||
jabber => 'jabberID',
|
||||
);
|
||||
if ($u->{allow_contactshow} eq 'Y') {
|
||||
foreach my $type (keys %types) {
|
||||
next unless $u->{$type};
|
||||
$ret .= " <foaf:$types{$type}>" . LJ::exml($u->{$type}) . "</foaf:$types{$type}>\n";
|
||||
}
|
||||
}
|
||||
|
||||
# include a user's journal page and web site info
|
||||
$ret .= " <foaf:weblog rdf:resource=\"" . LJ::journal_base($u) . "/\"/>\n";
|
||||
if ($u->{url}) {
|
||||
$ret .= " <foaf:homepage rdf:resource=\"" . LJ::eurl($u->{url});
|
||||
$ret .= "\" dc:title=\"" . LJ::exml($u->{urlname}) . "\" />\n";
|
||||
}
|
||||
|
||||
# interests, please!
|
||||
# arrayref of interests rows: [ intid, intname, intcount ]
|
||||
my $intu = LJ::get_interests($u);
|
||||
foreach my $int (@$intu) {
|
||||
LJ::text_out(\$int->[1]); # 1==interest
|
||||
$ret .= " <foaf:interest dc:title=\"". LJ::exml($int->[1]) . "\" " .
|
||||
"rdf:resource=\"$LJ::SITEROOT/interests.bml?int=" . LJ::eurl($int->[1]) . "\" />\n";
|
||||
}
|
||||
|
||||
# check if the user has a "FOAF-knows" group
|
||||
my $groups = LJ::get_friend_group($u->{userid}, { name => 'FOAF-knows' });
|
||||
my $mask = $groups ? 1 << $groups->{groupnum} : 0;
|
||||
|
||||
# now information on who you know, limited to a certain maximum number of users
|
||||
my $friends = LJ::get_friends($u->{userid}, $mask);
|
||||
my @ids = keys %$friends;
|
||||
@ids = splice(@ids, 0, $LJ::MAX_FOAF_FRIENDS) if @ids > $LJ::MAX_FOAF_FRIENDS;
|
||||
|
||||
# now load
|
||||
my %users;
|
||||
LJ::load_userids_multiple([ map { $_, \$users{$_} } @ids ], [$u]);
|
||||
|
||||
# iterate to create data structure
|
||||
foreach my $friendid (@ids) {
|
||||
next if $friendid == $u->{userid};
|
||||
my $fu = $users{$friendid};
|
||||
next if $fu->{statusvis} =~ /[DXS]/ || $fu->{journaltype} ne 'P';
|
||||
$ret .= $comm ? " <foaf:member>\n" : " <foaf:knows>\n";
|
||||
$ret .= " <foaf:Person>\n";
|
||||
$ret .= " <foaf:nick>$fu->{'user'}</foaf:nick>\n";
|
||||
$ret .= " <rdfs:seeAlso rdf:resource=\"" . LJ::journal_base($fu) ."/data/foaf\" />\n";
|
||||
$ret .= " <foaf:weblog rdf:resource=\"" . LJ::journal_base($fu) . "/\"/>\n";
|
||||
$ret .= " </foaf:Person>\n";
|
||||
$ret .= $comm ? " </foaf:member>\n" : " </foaf:knows>\n";
|
||||
}
|
||||
|
||||
# finish off the document
|
||||
$ret .= $comm ? " </foaf:Group>\n" : " </foaf:Person>\n";
|
||||
$ret .= "</rdf:RDF>\n";
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
1;
|
||||
418
local/cgi-bin/ljlang.pl
Executable file
418
local/cgi-bin/ljlang.pl
Executable file
@@ -0,0 +1,418 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
use strict;
|
||||
use lib "$ENV{'LJHOME'}/cgi-bin";
|
||||
use LJ::Cache;
|
||||
|
||||
package LJ::Lang;
|
||||
|
||||
my @day_short = (qw[Sun Mon Tue Wed Thu Fri Sat]);
|
||||
my @day_long = (qw[Sunday Monday Tuesday Wednesday Thursday Friday Saturday]);
|
||||
my @month_short = (qw[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec]);
|
||||
my @month_long = (qw[January February March April May June July August September October November December]);
|
||||
|
||||
# get entire array of days and months
|
||||
sub day_list_short { return @LJ::Lang::day_short; }
|
||||
sub day_list_long { return @LJ::Lang::day_long; }
|
||||
sub month_list_short { return @LJ::Lang::month_short; }
|
||||
sub month_list_long { return @LJ::Lang::month_long; }
|
||||
|
||||
# access individual day or month given integer
|
||||
sub day_short { return $day_short[$_[0] - 1]; }
|
||||
sub day_long { return $day_long[$_[0] - 1]; }
|
||||
sub month_short { return $month_short[$_[0] - 1]; }
|
||||
sub month_long { return $month_long[$_[0] - 1]; }
|
||||
|
||||
# lang codes for individual day or month given integer
|
||||
sub day_short_langcode { return "date.day." . lc(LJ::Lang::day_long(@_)) . ".short"; }
|
||||
sub day_long_langcode { return "date.day." . lc(LJ::Lang::day_long(@_)) . ".long"; }
|
||||
sub month_short_langcode { return "date.month." . lc(LJ::Lang::month_long(@_)) . ".short"; }
|
||||
sub month_long_langcode { return "date.month." . lc(LJ::Lang::month_long(@_)) . ".long"; }
|
||||
|
||||
## ordinal suffix
|
||||
sub day_ord {
|
||||
my $day = shift;
|
||||
|
||||
# teens all end in 'th'
|
||||
if ($day =~ /1\d$/) { return "th"; }
|
||||
|
||||
# otherwise endings in 1, 2, 3 are special
|
||||
if ($day % 10 == 1) { return "st"; }
|
||||
if ($day % 10 == 2) { return "nd"; }
|
||||
if ($day % 10 == 3) { return "rd"; }
|
||||
|
||||
# everything else (0,4-9) end in "th"
|
||||
return "th";
|
||||
}
|
||||
|
||||
sub time_format
|
||||
{
|
||||
my ($hours, $h, $m, $formatstring) = @_;
|
||||
|
||||
if ($formatstring eq "short") {
|
||||
if ($hours == 12) {
|
||||
my $ret;
|
||||
my $ap = "a";
|
||||
if ($h == 0) { $ret .= "12"; }
|
||||
elsif ($h < 12) { $ret .= ($h+0); }
|
||||
elsif ($h == 12) { $ret .= ($h+0); $ap = "p"; }
|
||||
else { $ret .= ($h-12); $ap = "p"; }
|
||||
$ret .= sprintf(":%02d$ap", $m);
|
||||
return $ret;
|
||||
} elsif ($hours == 24) {
|
||||
return sprintf("%02d:%02d", $h, $m);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
#### ml_ stuff:
|
||||
my $LS_CACHED = 0;
|
||||
my %DM_ID = (); # id -> { type, args, dmid, langs => { => 1, => 0, => 1 } }
|
||||
my %DM_UNIQ = (); # "$type/$args" => ^^^
|
||||
my %LN_ID = (); # id -> { ..., ..., 'children' => [ $ids, .. ] }
|
||||
my %LN_CODE = (); # $code -> ^^^^
|
||||
my $LAST_ERROR;
|
||||
my $TXT_CACHE; # LJ::Cache for text
|
||||
|
||||
sub get_cache_object { return $TXT_CACHE; }
|
||||
|
||||
sub last_error
|
||||
{
|
||||
return $LAST_ERROR;
|
||||
}
|
||||
|
||||
sub set_error
|
||||
{
|
||||
$LAST_ERROR = $_[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub get_lang
|
||||
{
|
||||
my $code = shift;
|
||||
load_lang_struct() unless $LS_CACHED;
|
||||
return $LN_CODE{$code};
|
||||
}
|
||||
|
||||
sub get_lang_id
|
||||
{
|
||||
my $id = shift;
|
||||
load_lang_struct() unless $LS_CACHED;
|
||||
return $LN_ID{$id};
|
||||
}
|
||||
|
||||
sub get_dom
|
||||
{
|
||||
my $dmcode = shift;
|
||||
load_lang_struct() unless $LS_CACHED;
|
||||
return $DM_UNIQ{$dmcode};
|
||||
}
|
||||
|
||||
sub get_dom_id
|
||||
{
|
||||
my $dmid = shift;
|
||||
load_lang_struct() unless $LS_CACHED;
|
||||
return $DM_ID{$dmid};
|
||||
}
|
||||
|
||||
sub get_domains
|
||||
{
|
||||
load_lang_struct() unless $LS_CACHED;
|
||||
return values %DM_ID;
|
||||
}
|
||||
|
||||
sub get_root_lang
|
||||
{
|
||||
my $dom = shift; # from, say, get_dom
|
||||
return undef unless ref $dom eq "HASH";
|
||||
foreach (keys %{$dom->{'langs'}}) {
|
||||
if ($dom->{'langs'}->{$_}) {
|
||||
return get_lang_id($_);
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub load_lang_struct
|
||||
{
|
||||
return 1 if $LS_CACHED;
|
||||
my $dbr = LJ::get_db_reader();
|
||||
return set_error("No database available") unless $dbr;
|
||||
my $sth;
|
||||
|
||||
$TXT_CACHE = new LJ::Cache { 'maxbytes' => $LJ::LANG_CACHE_BYTES || 50_000 };
|
||||
|
||||
$sth = $dbr->prepare("SELECT dmid, type, args FROM ml_domains");
|
||||
$sth->execute;
|
||||
while (my ($dmid, $type, $args) = $sth->fetchrow_array) {
|
||||
my $uniq = $args ? "$type/$args" : $type;
|
||||
$DM_UNIQ{$uniq} = $DM_ID{$dmid} = {
|
||||
'type' => $type, 'args' => $args, 'dmid' => $dmid,
|
||||
'uniq' => $uniq,
|
||||
};
|
||||
}
|
||||
|
||||
$sth = $dbr->prepare("SELECT lnid, lncode, lnname, parenttype, parentlnid FROM ml_langs");
|
||||
$sth->execute;
|
||||
while (my ($id, $code, $name, $ptype, $pid) = $sth->fetchrow_array) {
|
||||
$LN_ID{$id} = $LN_CODE{$code} = {
|
||||
'lnid' => $id,
|
||||
'lncode' => $code,
|
||||
'lnname' => $name,
|
||||
'parenttype' => $ptype,
|
||||
'parentlnid' => $pid,
|
||||
};
|
||||
}
|
||||
foreach (values %LN_CODE) {
|
||||
next unless $_->{'parentlnid'};
|
||||
push @{$LN_ID{$_->{'parentlnid'}}->{'children'}}, $_->{'lnid'};
|
||||
}
|
||||
|
||||
$sth = $dbr->prepare("SELECT lnid, dmid, dmmaster FROM ml_langdomains");
|
||||
$sth->execute;
|
||||
while (my ($lnid, $dmid, $dmmaster) = $sth->fetchrow_array) {
|
||||
$DM_ID{$dmid}->{'langs'}->{$lnid} = $dmmaster;
|
||||
}
|
||||
|
||||
$LS_CACHED = 1;
|
||||
}
|
||||
|
||||
sub get_itemid
|
||||
{
|
||||
&LJ::nodb;
|
||||
my ($dmid, $itcode, $opts) = @_;
|
||||
load_lang_struct() unless $LS_CACHED;
|
||||
|
||||
my $dbr = LJ::get_db_reader();
|
||||
$dmid += 0;
|
||||
my $itid = $dbr->selectrow_array("SELECT itid FROM ml_items WHERE dmid=$dmid AND itcode=?", undef, $itcode);
|
||||
return $itid if defined $itid;
|
||||
|
||||
my $dbh = LJ::get_db_writer();
|
||||
return 0 unless $dbh;
|
||||
|
||||
# allocate a new id
|
||||
LJ::get_lock($dbh, 'global', 'mlitem_dmid') || return 0;
|
||||
$itid = $dbh->selectrow_array("SELECT MAX(itid)+1 FROM ml_items WHERE dmid=?", undef, $dmid);
|
||||
$itid ||= 1; # if the table is empty, NULL+1 == NULL
|
||||
$dbh->do("INSERT INTO ml_items (dmid, itid, itcode, notes) ".
|
||||
"VALUES (?, ?, ?, ?)", undef, $dmid, $itid, $itcode, $opts->{'notes'});
|
||||
LJ::release_lock($dbh, 'global', 'mlitem_dmid');
|
||||
|
||||
if ($dbh->err) {
|
||||
return $dbh->selectrow_array("SELECT itid FROM ml_items WHERE dmid=$dmid AND itcode=?",
|
||||
undef, $itcode);
|
||||
}
|
||||
return $itid;
|
||||
}
|
||||
|
||||
sub set_text
|
||||
{
|
||||
&LJ::nodb;
|
||||
my ($dmid, $lncode, $itcode, $text, $opts) = @_;
|
||||
load_lang_struct() unless $LS_CACHED;
|
||||
|
||||
my $l = $LN_CODE{$lncode} or return set_error("Language not defined.");
|
||||
my $lnid = $l->{'lnid'};
|
||||
$dmid += 0;
|
||||
|
||||
# is this domain/language request even possible?
|
||||
return set_error("Bogus domain")
|
||||
unless exists $DM_ID{$dmid};
|
||||
return set_error("Bogus lang for that domain")
|
||||
unless exists $DM_ID{$dmid}->{'langs'}->{$lnid};
|
||||
|
||||
my $itid = get_itemid($dmid, $itcode, { 'notes' => $opts->{'notes'}});
|
||||
return set_error("Couldn't allocate itid.") unless $itid;
|
||||
|
||||
my $dbh = LJ::get_db_writer();
|
||||
my $txtid = 0;
|
||||
if (defined $text) {
|
||||
my $userid = $opts->{'userid'} + 0;
|
||||
# Strip bad characters
|
||||
$text =~ s/\r//;
|
||||
my $qtext = $dbh->quote($text);
|
||||
LJ::get_lock( $dbh, 'global', 'ml_text_txtid' ) || return 0;
|
||||
$txtid = $dbh->selectrow_array("SELECT MAX(txtid)+1 FROM ml_text WHERE dmid=?", undef, $dmid);
|
||||
$txtid ||= 1;
|
||||
$dbh->do("INSERT INTO ml_text (dmid, txtid, lnid, itid, text, userid) ".
|
||||
"VALUES ($dmid, $txtid, $lnid, $itid, $qtext, $userid)");
|
||||
LJ::release_lock( $dbh, 'global', 'ml_text_txtid' );
|
||||
return set_error("Error inserting ml_text: ".$dbh->errstr) if $dbh->err;
|
||||
}
|
||||
if ($opts->{'txtid'}) {
|
||||
$txtid = $opts->{'txtid'}+0;
|
||||
}
|
||||
|
||||
my $staleness = $opts->{'staleness'}+0;
|
||||
$dbh->do("REPLACE INTO ml_latest (lnid, dmid, itid, txtid, chgtime, staleness) ".
|
||||
"VALUES ($lnid, $dmid, $itid, $txtid, NOW(), $staleness)");
|
||||
return set_error("Error inserting ml_latest: ".$dbh->errstr) if $dbh->err;
|
||||
LJ::MemCache::set("ml.${lncode}.${dmid}.${itcode}", $text) if defined $text;
|
||||
|
||||
{
|
||||
my $vals;
|
||||
my $langids;
|
||||
my $rec = sub {
|
||||
my $l = shift;
|
||||
my $rec = shift;
|
||||
foreach my $cid (@{$l->{'children'}}) {
|
||||
my $clid = $LN_ID{$cid};
|
||||
if ($opts->{'childrenlatest'}) {
|
||||
my $stale = $clid->{'parenttype'} eq "diff" ? 3 : 0;
|
||||
$vals .= "," if $vals;
|
||||
$vals .= "($cid, $dmid, $itid, $txtid, NOW(), $stale)";
|
||||
}
|
||||
$langids .= "," if $langids;
|
||||
$langids .= $cid+0;
|
||||
LJ::MemCache::delete("ml.$clid->{'lncode'}.${dmid}.${itcode}");
|
||||
$rec->($clid, $rec);
|
||||
}
|
||||
};
|
||||
$rec->($l, $rec);
|
||||
|
||||
# set descendants to use this mapping
|
||||
$dbh->do("INSERT IGNORE INTO ml_latest (lnid, dmid, itid, txtid, chgtime, staleness) ".
|
||||
"VALUES $vals") if $vals;
|
||||
|
||||
# update languages that have no translation yet
|
||||
$dbh->do("UPDATE ml_latest SET txtid=$txtid WHERE dmid=$dmid ".
|
||||
"AND lnid IN ($langids) AND itid=$itid AND staleness >= 3") if $langids;
|
||||
}
|
||||
|
||||
if ($opts->{'changeseverity'} && $l->{'children'} && @{$l->{'children'}}) {
|
||||
my $in = join(",", @{$l->{'children'}});
|
||||
my $newstale = $opts->{'changeseverity'} == 2 ? 2 : 1;
|
||||
$dbh->do("UPDATE ml_latest SET staleness=$newstale WHERE lnid IN ($in) AND ".
|
||||
"dmid=$dmid AND itid=$itid AND txtid<>$txtid AND staleness < $newstale");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_text
|
||||
{
|
||||
my ($lang, $code, $dmid, $vars) = @_;
|
||||
$dmid = int($dmid || 1);
|
||||
$lang ||= $LJ::DEFAULT_LANG;
|
||||
load_lang_struct() unless $LS_CACHED;
|
||||
my $cache_key = "ml.${lang}.${dmid}.${code}";
|
||||
|
||||
my $text = $TXT_CACHE->get($cache_key);
|
||||
|
||||
unless (defined $text) {
|
||||
my $mem_good = 1;
|
||||
$text = LJ::MemCache::get($cache_key);
|
||||
unless (defined $text) {
|
||||
$mem_good = 0;
|
||||
my $l = $LN_CODE{$lang} or return "?lang?";
|
||||
my $dbr = LJ::get_db_reader();
|
||||
$text = $dbr->selectrow_array("SELECT t.text".
|
||||
" FROM ml_text t, ml_latest l, ml_items i".
|
||||
" WHERE t.dmid=$dmid AND t.txtid=l.txtid".
|
||||
" AND l.dmid=$dmid AND l.lnid=$l->{lnid} AND l.itid=i.itid".
|
||||
" AND i.dmid=$dmid AND i.itcode=?", undef,
|
||||
$code);
|
||||
}
|
||||
if (defined $text) {
|
||||
$TXT_CACHE->set($cache_key, $text) unless $LJ::NO_ML_CACHE;
|
||||
LJ::MemCache::set($cache_key, $text) unless $mem_good;
|
||||
}
|
||||
}
|
||||
|
||||
if ($vars) {
|
||||
$text =~ s/\[\[\?([\w\-]+)\|(.+?)\]\]/resolve_plural($lang, $vars, $1, $2)/eg;
|
||||
$text =~ s/\[\[([^\[]+?)\]\]/$vars->{$1}/g;
|
||||
}
|
||||
|
||||
$text =~ s/\[\[sitename\]\]/$LJ::SITENAME/g;
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
# The translation system now supports the ability to add multiple plural forms of the word
|
||||
# given different rules in a languge. This functionality is much like the plural support
|
||||
# in the S2 styles code. To use this code you must use the BML::ml function and pass
|
||||
# the number of items as one of the variables. To make sure that you are allowing the
|
||||
# utmost compatibility for each language you should not hardcode the placement of the
|
||||
# number of items in relation to the noun. Let the translation string do this for you.
|
||||
# A translation string is in the format of, with num being the variable storing the
|
||||
# number of items.
|
||||
# =[[num]] [[?num|singular|plural1|plural2|pluralx]]
|
||||
|
||||
sub resolve_plural {
|
||||
my ($lang, $vars, $varname, $wordlist) = @_;
|
||||
my $count = $vars->{$varname};
|
||||
my @wlist = split(/\|/, $wordlist);
|
||||
my $plural_form = plural_form($lang, $count);
|
||||
return $wlist[$plural_form];
|
||||
}
|
||||
|
||||
# TODO: make this faster, using AUTOLOAD and symbol tables pointing to dynamically
|
||||
# generated subs which only use $_[0] for $count.
|
||||
sub plural_form {
|
||||
my ($lang, $count) = @_;
|
||||
return plural_form_en($count) if $lang =~ /^en/;
|
||||
return plural_form_ru($count) if $lang =~ /^ru/ || $lang =~ /^uk/;
|
||||
return plural_form_fr($count) if $lang =~ /^fr/ || $lang =~ /^pt_BR/;
|
||||
return plural_form_lt($count) if $lang =~ /^lt/;
|
||||
return plural_form_pl($count) if $lang =~ /^pl/;
|
||||
return plural_form_singular() if $lang =~ /^hu/ || $lang =~ /^ja/ || $lang =~ /^tr/;
|
||||
return plural_form_lv($count) if $lang =~ /^lv/;
|
||||
return plural_form_en($count); # default
|
||||
}
|
||||
|
||||
# English, Danish, German, Norwegian, Swedish, Estonian, Finnish, Greek, Hebrew, Italian, Portugese, Spanish, Esperanto
|
||||
sub plural_form_en {
|
||||
my ($count) = shift;
|
||||
return 0 if $count == 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
# French, Brazilian Portuguese
|
||||
sub plural_form_fr {
|
||||
my ($count) = shift;
|
||||
return 1 if $count > 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
# Croatian, Czech, Russian, Slovak, Ukrainian
|
||||
sub plural_form_ru {
|
||||
my ($count) = shift;
|
||||
return 0 if ($count%10 == 1 and $count%100 != 11);
|
||||
return 1 if ($count%10 >= 2 and $count%10 <= 4 and ($count%100 < 10 or $count%100>=20));
|
||||
return 2;
|
||||
}
|
||||
|
||||
# Polish
|
||||
sub plural_form_pl {
|
||||
my ($count) = shift;
|
||||
return 0 if($count == 1);
|
||||
return 1 if($count%10 >= 2 && $count%10 <= 4 && ($count%100 < 10 || $count%100 >= 20));
|
||||
return 2;
|
||||
}
|
||||
|
||||
# Lithuanian
|
||||
sub plural_form_lt {
|
||||
my ($count) = shift;
|
||||
return 0 if($count%10 == 1 && $count%100 != 11);
|
||||
return 1 if ($count%10 >= 2 && ($count%100 < 10 || $count%100 >= 20));
|
||||
return 2;
|
||||
}
|
||||
|
||||
# Hungarian, Japanese, Korean (not supported), Turkish
|
||||
sub plural_form_singular {
|
||||
return 0;
|
||||
}
|
||||
|
||||
# Latvian
|
||||
sub plural_form_lv {
|
||||
my ($count) = shift;
|
||||
return 0 if($count%10 == 1 && $count%100 != 11);
|
||||
return 1 if($count != 0);
|
||||
return 2;
|
||||
}
|
||||
|
||||
1;
|
||||
114
local/cgi-bin/ljlib-local.pl
Executable file
114
local/cgi-bin/ljlib-local.pl
Executable file
@@ -0,0 +1,114 @@
|
||||
require "$ENV{'LJHOME'}/cgi-bin/ljcom.pl";
|
||||
|
||||
LJ::register_hook('emailconfirmed',
|
||||
sub {
|
||||
my $u = shift;
|
||||
BML::set_cookie("LJR_confirmedemailthisyear", '1',
|
||||
time() + 3600*24*365, $LJ::COOKIE_PATH, $LJ::COOKIE_DOMAIN);
|
||||
return $text;
|
||||
}
|
||||
);
|
||||
|
||||
sub get_new_userid {
|
||||
my $ruserid = 0; # scope
|
||||
|
||||
my $dbr = LJ::get_db_reader();
|
||||
|
||||
if (! $LJ::LJR_IMPORTED_USERIDS) {
|
||||
$ruserid = $dbr->selectrow_array(
|
||||
"select max(userid) from user"
|
||||
);
|
||||
}
|
||||
else {
|
||||
$ruserid = $dbr->selectrow_array(
|
||||
"select max(userid) from user where userid < $LJ::LJR_IMPORTED_USERIDS"
|
||||
);
|
||||
}
|
||||
|
||||
if ($ruserid) {
|
||||
$ruserid++;
|
||||
}
|
||||
|
||||
return $ruserid;
|
||||
}
|
||||
|
||||
sub get_new_importedid {
|
||||
my $ruserid = 0; # scope
|
||||
my $dbr = LJ::get_db_reader();
|
||||
$ruserid = $dbr->selectrow_array(
|
||||
"select max(userid) from user where userid >= $LJ::LJR_IMPORTED_USERIDS",
|
||||
);
|
||||
|
||||
if ($ruserid) {
|
||||
$ruserid++;
|
||||
}
|
||||
else {
|
||||
$ruserid = $LJ::LJR_IMPORTED_USERIDS;
|
||||
}
|
||||
|
||||
return $ruserid;
|
||||
}
|
||||
|
||||
sub get_callstack {
|
||||
my $cstack;
|
||||
my $i = 0;
|
||||
while ( 1 ) {
|
||||
my $tfunc = (caller($i))[3];
|
||||
if ($tfunc && $tfunc ne "") {
|
||||
if ($tfunc !~ /\_\_ANON\_\_/) {
|
||||
$cstack .= " " . $tfunc;
|
||||
}
|
||||
$i = $i + 1;
|
||||
}
|
||||
else {
|
||||
last;
|
||||
}
|
||||
}
|
||||
return $cstack;
|
||||
}
|
||||
|
||||
# should be used when you need to concatenate string
|
||||
# which might be undefined and you want empty string ("")
|
||||
# instead of perl warnings about uninitialized values
|
||||
#
|
||||
sub safe_string {
|
||||
my ($str) = @_;
|
||||
|
||||
if ($str) {
|
||||
return $str;
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
# check_unfriend
|
||||
# takes 2 userids, returns 1 if one unfriended another, 0 otherwise
|
||||
# args userid1, userid2
|
||||
|
||||
sub check_twit
|
||||
{
|
||||
my ($userid, $twitid) = @_;
|
||||
return undef unless $userid; return undef unless $twitid;
|
||||
$dbh = LJ::get_db_reader();
|
||||
return $err->("Can't get database reader!") unless $dbh;
|
||||
my $sth = $dbh->prepare("SELECT * FROM twits WHERE".
|
||||
" userid=$userid AND twitid=$twitid");
|
||||
$sth->execute() || print STDERR "Couldn't execute" . $sth->errstr;
|
||||
# print STDERR "rows $sth->rows";
|
||||
if ($sth->rows) { return 1;} else {return 0;}
|
||||
}
|
||||
|
||||
# takes userid, returns arrayref of its twitid's
|
||||
#
|
||||
sub get_twit_list
|
||||
{
|
||||
my ($userid) = @_;
|
||||
return undef unless $userid;
|
||||
|
||||
$db = LJ::get_db_reader();
|
||||
return $err->("Can't get database reader!") unless $db;
|
||||
return $db->selectcol_arrayref("SELECT twitid FROM
|
||||
twits WHERE userid=?", undef, $userid);
|
||||
}
|
||||
|
||||
5088
local/cgi-bin/ljlib.pl
Executable file
5088
local/cgi-bin/ljlib.pl
Executable file
File diff suppressed because it is too large
Load Diff
356
local/cgi-bin/ljmail.pl
Executable file
356
local/cgi-bin/ljmail.pl
Executable file
@@ -0,0 +1,356 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
# Send mail outbound using a weighted random selection.
|
||||
# Supports a variety of mail protocols.
|
||||
#
|
||||
|
||||
package LJ;
|
||||
|
||||
use strict;
|
||||
use Text::Wrap ();
|
||||
use MIME::Lite ();
|
||||
use Time::HiRes qw/ gettimeofday tv_interval /;
|
||||
|
||||
use IO::Socket::INET (); # temp, for use with DMTP
|
||||
|
||||
require "$ENV{LJHOME}/cgi-bin/ljlib.pl";
|
||||
|
||||
sub maildebug
|
||||
{
|
||||
return unless $LJ::EMAIL_OUTGOING_DEBUG;
|
||||
print STDERR "ljmail: " . shift() . "\n";
|
||||
}
|
||||
sub store_message
|
||||
{
|
||||
my ( $data, $type ) = @_;
|
||||
$type ||= 'none';
|
||||
|
||||
maildebug "Storing message for retry.";
|
||||
my $time = [ gettimeofday() ];
|
||||
|
||||
# try this on each cluster
|
||||
my $frozen = Storable::nfreeze($data);
|
||||
my $rval = LJ::do_to_cluster(
|
||||
sub {
|
||||
# first parameter is cluster id
|
||||
return LJ::cmd_buffer_add( shift(@_), 0, 'send_mail', $frozen );
|
||||
}
|
||||
);
|
||||
return undef unless $rval;
|
||||
|
||||
my $notes = sprintf(
|
||||
"Queued mail send to %s %s: %s",
|
||||
$data->get('to'), $rval ? "succeeded" : "failed",
|
||||
$data->get('subject')
|
||||
);
|
||||
maildebug $notes;
|
||||
|
||||
LJ::blocking_report(
|
||||
$type, 'send_mail',
|
||||
tv_interval($time), $notes
|
||||
);
|
||||
|
||||
# we only attempt to store the message
|
||||
# on delivery failure. if we're here, something
|
||||
# failed, so always return false.
|
||||
return 0;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::send_mail
|
||||
# des: Sends email. Character set will only be used if message is not ascii.
|
||||
# args: opt[, async_caller]
|
||||
# des-opt: Hashref of arguments. <b>Required:</b> to, from, subject, body.
|
||||
# <b>Optional:</b> toname, fromname, cc, bcc, charset, wrap
|
||||
# </LJFUNC>
|
||||
sub send_mail
|
||||
{
|
||||
my $opts = shift;
|
||||
my $async_caller = shift;
|
||||
my $time = [gettimeofday()];
|
||||
|
||||
my (
|
||||
$proto, # what protocol we decided to use
|
||||
$msg, # email message (ascii)
|
||||
$data, # email message (MIME::Lite)
|
||||
$server, # remote server object
|
||||
$hostname # hostname of mailserver selected
|
||||
);
|
||||
|
||||
# support being given a direct MIME::Lite object,
|
||||
# for queued cmdbuffer 'frozen' retries
|
||||
$data = ( ref $opts eq 'MIME::Lite' ) ? $opts : build_message($opts);
|
||||
return 0 unless $data;
|
||||
$msg = $data->as_string();
|
||||
|
||||
# ok, we're sending via the network.
|
||||
# get a preferred server/protocol, or failover to cmdbuffer.
|
||||
( $server, $proto, $hostname ) = find_server();
|
||||
unless ( $server && $proto ) {
|
||||
maildebug "Suitable mail transport not found.";
|
||||
return store_message $data, undef;
|
||||
}
|
||||
my $info = "$hostname-$proto";
|
||||
|
||||
# Now we have an active server connection,
|
||||
# and we know what protocol to use.
|
||||
|
||||
# clean addresses.
|
||||
my ( @recips, %headers );
|
||||
$headers{$_} = $data->get( $_ ) foreach qw/ from to cc bcc /;
|
||||
|
||||
$opts->{'from'} =
|
||||
( Mail::Address->parse( $data->get('from') ) )[0]->address()
|
||||
if $headers{'from'};
|
||||
|
||||
push @recips, map { $_->address() } Mail::Address->parse( $headers{'to'} ) if $headers{'to'};
|
||||
push @recips, map { $_->address() } Mail::Address->parse( $headers{'cc'} ) if $headers{'cc'};
|
||||
push @recips, map { $_->address() } Mail::Address->parse( $headers{'bcc'} ) if $headers{'bcc'};
|
||||
|
||||
unless (scalar @recips) {
|
||||
maildebug "No recipients to send to!";
|
||||
return 0;
|
||||
}
|
||||
|
||||
# QMTP
|
||||
if ( $proto eq 'qmtp' ) {
|
||||
$server->recipient($_) foreach @recips;
|
||||
$server->sender( $opts->{'from'} );
|
||||
$server->message($msg);
|
||||
|
||||
# send!
|
||||
my $response = $server->send() or return store_message $data, $info;
|
||||
foreach ( keys %$response ) {
|
||||
return store_message $data, $info
|
||||
if $response->{$_} !~ /success/;
|
||||
}
|
||||
$server->disconnect();
|
||||
}
|
||||
|
||||
# SMTP
|
||||
if ( $proto eq 'smtp' ) {
|
||||
|
||||
$server->mail( $opts->{'from'} );
|
||||
|
||||
# this would only fail on denied relay access
|
||||
# or somesuch.
|
||||
return store_message $data, $info unless
|
||||
$server->to( join ', ', @recips );
|
||||
|
||||
$server->data();
|
||||
$server->datasend($msg);
|
||||
$server->dataend();
|
||||
|
||||
$server->quit;
|
||||
}
|
||||
|
||||
# DMTP (Danga Mail Transfer Protocol)
|
||||
# (slated for removal if our QMTP stuff is worry-free.)
|
||||
if ( $proto eq 'dmtp' ) {
|
||||
|
||||
my $len = length $msg;
|
||||
my $env = $opts->{'from'};
|
||||
|
||||
$server->print("Content-Length: $len\r\n");
|
||||
$server->print("Envelope-Sender: $env\r\n\r\n$msg");
|
||||
|
||||
return store_message $data, $info
|
||||
unless $server->getline() =~ /^OK/;
|
||||
}
|
||||
|
||||
# system mailer
|
||||
if ( $proto eq 'sendmail' ) {
|
||||
MIME::Lite->send( 'sendmail', $hostname );
|
||||
unless ( $data->send() ) {
|
||||
maildebug "Unable to send via system mailer!";
|
||||
return store_message $data, 'sendmail';
|
||||
}
|
||||
}
|
||||
|
||||
report( $data, $time, $info, $async_caller );
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub report
|
||||
{
|
||||
my ( $data, $time, $info, $async_caller ) = @_;
|
||||
|
||||
# report deliveries
|
||||
my $notes = sprintf(
|
||||
"Direct mail send to %s succeeded: %s",
|
||||
$data->get('to') ||
|
||||
$data->get('cc') ||
|
||||
$data->get('bcc'), $data->get('subject')
|
||||
);
|
||||
maildebug $notes;
|
||||
|
||||
LJ::blocking_report(
|
||||
$info, 'send_mail',
|
||||
tv_interval( $time ), $notes
|
||||
)
|
||||
unless $async_caller;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# locate a network server,
|
||||
# return (serverobj, protocol, hostname)
|
||||
sub find_server
|
||||
{
|
||||
# operate on a copy of the server list.
|
||||
my @objects = @LJ::MAIL_TRANSPORTS;
|
||||
|
||||
# backwards compatibility with earlier ljconfig.
|
||||
unless (@objects) {
|
||||
push @objects, [ 'sendmail', $LJ::SENDMAIL, 0 ] if $LJ::SENDMAIL;
|
||||
push @objects, [ 'smtp', $LJ::SMTP_SERVER, 0 ] if $LJ::SMTP_SERVER;
|
||||
push @objects, [ 'dmtp', $LJ::DMTP_SERVER, 1 ] if $LJ::DMTP_SERVER;
|
||||
}
|
||||
|
||||
my ( $server, $proto, $hostname );
|
||||
|
||||
while ( @objects && !$proto ) {
|
||||
my $item = get_slice(@objects);
|
||||
my $select = $objects[$item];
|
||||
|
||||
maildebug "Trying server $select->[1] ($select->[0])...";
|
||||
|
||||
# check service connectivity
|
||||
|
||||
# QMTP
|
||||
if ( $select->[0] eq 'qmtp' ) {
|
||||
eval 'use Net::QMTP';
|
||||
if ($@) {
|
||||
maildebug "Net::QMTP not installed?";
|
||||
splice @objects, $item, 1;
|
||||
next;
|
||||
}
|
||||
|
||||
eval {
|
||||
$server = Net::QMTP->new( $select->[1], ConnectTimeout => 10 );
|
||||
};
|
||||
}
|
||||
|
||||
# SMTP
|
||||
elsif ( $select->[0] eq 'smtp' ) {
|
||||
eval 'use Net::SMTP';
|
||||
if ($@) {
|
||||
maildebug "Net::SMTP not installed?";
|
||||
splice @objects, $item, 1;
|
||||
next;
|
||||
}
|
||||
|
||||
eval { $server = Net::SMTP->new( $select->[1], Timeout => 10 ); };
|
||||
}
|
||||
|
||||
# DMTP
|
||||
elsif ( $select->[0] eq 'dmtp' ) {
|
||||
my $host = $select->[1];
|
||||
my $port = $host =~ s/:(\d+)$// ? $1 : 7005;
|
||||
|
||||
$server = IO::Socket::INET->new(
|
||||
PeerAddr => $host,
|
||||
PeerPort => $port,
|
||||
Proto => 'tcp'
|
||||
);
|
||||
}
|
||||
|
||||
# system sendmail binary
|
||||
elsif ( $select->[0] eq 'sendmail' ) {
|
||||
my $sendmail = $1 if $select->[1] =~ /(\S+)/;
|
||||
$server = $sendmail if -e $sendmail && -x _;
|
||||
}
|
||||
|
||||
else {
|
||||
maildebug "Unknown mail protocol";
|
||||
splice @objects, $item, 1;
|
||||
next;
|
||||
}
|
||||
|
||||
# do we have a server connection?
|
||||
# if not, remove from our selection pool and try again.
|
||||
if ( ! $server ) {
|
||||
maildebug "Could not connect";
|
||||
splice @objects, $item, 1;
|
||||
}
|
||||
else {
|
||||
maildebug "Connected";
|
||||
( $proto, $hostname ) = ( $select->[0], $select->[1] );
|
||||
}
|
||||
}
|
||||
|
||||
return ( $server, $proto, $hostname );
|
||||
}
|
||||
|
||||
# return a ready to stringify MIME::Lite object.
|
||||
sub build_message
|
||||
{
|
||||
my $opts = shift;
|
||||
|
||||
local($Text::Tabs::columns) = 20000; ##temp hack; default 76
|
||||
|
||||
my $body = $opts->{'wrap'} ?
|
||||
Text::Wrap::wrap( '', '', $opts->{'body'} ) :
|
||||
$opts->{'body'};
|
||||
|
||||
my $to = Mail::Address->new( $opts->{'toname'}, $opts->{'to'} );
|
||||
my $from = Mail::Address->new( $opts->{'fromname'}, $opts->{'from'} );
|
||||
|
||||
my $msg = MIME::Lite->new
|
||||
(
|
||||
To => $to->format(),
|
||||
From => $from->format(),
|
||||
Cc => $opts->{'cc'} || '',
|
||||
Bcc => $opts->{'bcc'} || '',
|
||||
Data => "$body\n",
|
||||
Subject => $opts->{'subject'},
|
||||
);
|
||||
return unless $msg;
|
||||
|
||||
$msg->add(%{ $opts->{'headers'} }) if ref $opts->{'headers'};
|
||||
|
||||
$msg->attr("content-type.charset" => $opts->{'charset'})
|
||||
if $opts->{'charset'} &&
|
||||
! (LJ::is_ascii($opts->{'body'}) &&
|
||||
LJ::is_ascii($opts->{'subject'}));
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
# return a weighted random slice from an array.
|
||||
sub get_slice
|
||||
{
|
||||
my @objects = @_;
|
||||
|
||||
# Find cumulative values between weights, and in total.
|
||||
my (@csums, $cumulative_sum);
|
||||
@csums = map { $cumulative_sum += abs $_->[2] } @objects;
|
||||
|
||||
# *nothing* has weight? (all zeros?) just choose one.
|
||||
# same thing as equal weights.
|
||||
return int rand scalar @objects unless $cumulative_sum;
|
||||
|
||||
# Get a random number that will be compared to
|
||||
# the 'window' of probability for quotes.
|
||||
my $rand = rand $cumulative_sum;
|
||||
|
||||
# Create number ranges between each cumulative value,
|
||||
# and check the random number to see if it falls within
|
||||
# the weighted 'window size'.
|
||||
# Remember the array slice for matching the original object to.
|
||||
my $lastval = 0;
|
||||
my $slice = 0;
|
||||
foreach (@csums) {
|
||||
last if $rand >= $lastval && $rand <= $_;
|
||||
$slice++;
|
||||
$lastval = $_;
|
||||
}
|
||||
|
||||
return $slice;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
1;
|
||||
|
||||
953
local/cgi-bin/ljpoll.pl
Executable file
953
local/cgi-bin/ljpoll.pl
Executable file
@@ -0,0 +1,953 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
package LJ::Poll;
|
||||
|
||||
use strict;
|
||||
use HTML::TokeParser ();
|
||||
|
||||
require "$ENV{'LJHOME'}/cgi-bin/cleanhtml.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/LJR/ljpoll-local.pl";
|
||||
|
||||
sub clean_poll
|
||||
{
|
||||
my $ref = shift;
|
||||
if ($$ref !~ /[<>]/) {
|
||||
LJ::text_out($ref);
|
||||
return;
|
||||
}
|
||||
|
||||
my $poll_eat = [qw[head title style layer iframe applet object]];
|
||||
my $poll_allow = [qw[a b i u strong em img]];
|
||||
my $poll_remove = [qw[bgsound embed object caption link font]];
|
||||
|
||||
LJ::CleanHTML::clean($ref, {
|
||||
'wordlength' => 40,
|
||||
'addbreaks' => 0,
|
||||
'eat' => $poll_eat,
|
||||
'mode' => 'deny',
|
||||
'allow' => $poll_allow,
|
||||
'remove' => $poll_remove,
|
||||
});
|
||||
LJ::text_out($ref);
|
||||
}
|
||||
|
||||
|
||||
sub contains_new_poll
|
||||
{
|
||||
my $postref = shift;
|
||||
return ($$postref =~ /<lj-poll\b/i);
|
||||
}
|
||||
|
||||
sub parse
|
||||
{
|
||||
&LJ::nodb;
|
||||
my ($postref, $error, $iteminfo) = @_;
|
||||
|
||||
$iteminfo->{'posterid'} += 0;
|
||||
$iteminfo->{'journalid'} += 0;
|
||||
|
||||
my $newdata;
|
||||
|
||||
my $popen = 0;
|
||||
my %popts;
|
||||
|
||||
my $qopen = 0;
|
||||
my %qopts;
|
||||
|
||||
my $iopen = 0;
|
||||
my %iopts;
|
||||
|
||||
my @polls; # completed parsed polls
|
||||
|
||||
my $p = HTML::TokeParser->new($postref);
|
||||
|
||||
# if we're being called from mailgated, then we're not in web context and therefore
|
||||
# do not have any BML::ml functionality. detect this now and report errors in a
|
||||
# plaintext, non-translated form to be bounced via email.
|
||||
my $have_bml = eval { BML::ml() } || ! $@;
|
||||
|
||||
my $err = sub {
|
||||
# more than one element, either make a call to BML::ml
|
||||
# or build up a semi-useful error string from it
|
||||
if (@_ > 1) {
|
||||
if ($have_bml) {
|
||||
$$error = BML::ml(@_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$$error = shift() . ": ";
|
||||
while (my ($k, $v) = each %{$_[0]}) {
|
||||
$$error .= "$k=$v,";
|
||||
}
|
||||
chop $$error;
|
||||
return 0;
|
||||
}
|
||||
|
||||
# single element, either look up in %BML::ML or return verbatim
|
||||
$$error = $have_bml ? $BML::ML{$_[0]} : $_[0];
|
||||
return 0;
|
||||
};
|
||||
|
||||
while (my $token = $p->get_token)
|
||||
{
|
||||
my $type = $token->[0];
|
||||
my $append;
|
||||
|
||||
if ($type eq "S") # start tag
|
||||
{
|
||||
my $tag = $token->[1];
|
||||
my $opts = $token->[2];
|
||||
|
||||
######## Begin poll tag
|
||||
|
||||
if ($tag eq "lj-poll") {
|
||||
return $err->('poll.error.nested', { 'tag' => 'lj-poll' })
|
||||
if $popen;
|
||||
|
||||
$popen = 1;
|
||||
%popts = ();
|
||||
$popts{'questions'} = [];
|
||||
|
||||
$popts{'name'} = $opts->{'name'};
|
||||
$popts{'whovote'} = lc($opts->{'whovote'}) || "all";
|
||||
$popts{'whoview'} = lc($opts->{'whoview'}) || "all";
|
||||
|
||||
if ($popts{'whovote'} ne "all" &&
|
||||
$popts{'whovote'} ne "friends")
|
||||
{
|
||||
return $err->('poll.error.whovote');
|
||||
}
|
||||
if ($popts{'whoview'} ne "all" &&
|
||||
$popts{'whoview'} ne "friends" &&
|
||||
$popts{'whoview'} ne "none")
|
||||
{
|
||||
return $err->('poll.error.whoview');
|
||||
}
|
||||
}
|
||||
|
||||
######## Begin poll question tag
|
||||
|
||||
elsif ($tag eq "lj-pq")
|
||||
{
|
||||
return $err->('poll.error.nested', { 'tag' => 'lj-pq' })
|
||||
if $qopen;
|
||||
|
||||
return $err->('poll.error.missingljpoll')
|
||||
unless $popen;
|
||||
|
||||
$qopen = 1;
|
||||
%qopts = ();
|
||||
$qopts{'items'} = [];
|
||||
|
||||
$qopts{'type'} = $opts->{'type'};
|
||||
if ($qopts{'type'} eq "text") {
|
||||
my $size = 35;
|
||||
my $max = 255;
|
||||
if (defined $opts->{'size'}) {
|
||||
if ($opts->{'size'} > 0 &&
|
||||
$opts->{'size'} <= 100)
|
||||
{
|
||||
$size = $opts->{'size'}+0;
|
||||
} else {
|
||||
return $err->('poll.error.badsize');
|
||||
}
|
||||
}
|
||||
if (defined $opts->{'maxlength'}) {
|
||||
if ($opts->{'maxlength'} > 0 &&
|
||||
$opts->{'maxlength'} <= 255)
|
||||
{
|
||||
$max = $opts->{'maxlength'}+0;
|
||||
} else {
|
||||
return $err->('poll.error.badmaxlength');
|
||||
}
|
||||
}
|
||||
|
||||
$qopts{'opts'} = "$size/$max";
|
||||
}
|
||||
if ($qopts{'type'} eq "scale")
|
||||
{
|
||||
my $from = 1;
|
||||
my $to = 10;
|
||||
my $by = 1;
|
||||
|
||||
if (defined $opts->{'from'}) {
|
||||
$from = int($opts->{'from'});
|
||||
}
|
||||
if (defined $opts->{'to'}) {
|
||||
$to = int($opts->{'to'});
|
||||
}
|
||||
if (defined $opts->{'by'}) {
|
||||
$by = int($opts->{'by'});
|
||||
}
|
||||
if ($by < 1) {
|
||||
return $err->('poll.error.scaleincrement');
|
||||
}
|
||||
if ($from >= $to) {
|
||||
return $err->('poll.error.scalelessto');
|
||||
}
|
||||
if ((($to-$from)/$by) > 20) {
|
||||
return $err->('poll.error.scaletoobig');
|
||||
}
|
||||
$qopts{'opts'} = "$from/$to/$by";
|
||||
}
|
||||
|
||||
$qopts{'type'} = lc($opts->{'type'}) || "text";
|
||||
|
||||
if ($qopts{'type'} ne "radio" &&
|
||||
$qopts{'type'} ne "check" &&
|
||||
$qopts{'type'} ne "drop" &&
|
||||
$qopts{'type'} ne "scale" &&
|
||||
$qopts{'type'} ne "text")
|
||||
{
|
||||
return $err->('poll.error.unknownpqtype');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
######## Begin poll item tag
|
||||
|
||||
elsif ($tag eq "lj-pi")
|
||||
{
|
||||
if ($iopen) {
|
||||
return $err->('poll.error.nested', { 'tag' => 'lj-pi' });
|
||||
}
|
||||
if (! $qopen) {
|
||||
return $err->('poll.error.missingljpq');
|
||||
}
|
||||
if ($qopts{'type'} eq "text")
|
||||
{
|
||||
return $err->('poll.error.noitemstext');
|
||||
}
|
||||
|
||||
$iopen = 1;
|
||||
%iopts = ();
|
||||
}
|
||||
|
||||
#### not a special tag. dump it right back out.
|
||||
|
||||
else
|
||||
{
|
||||
$append .= "<$tag";
|
||||
foreach (keys %$opts) {
|
||||
$append .= " $_=\"$opts->{$_}\"";
|
||||
}
|
||||
$append .= ">";
|
||||
}
|
||||
}
|
||||
elsif ($type eq "E")
|
||||
{
|
||||
my $tag = $token->[1];
|
||||
|
||||
##### end POLL
|
||||
|
||||
if ($tag eq "lj-poll") {
|
||||
return $err->('poll.error.tagnotopen', { 'tag' => 'lj-poll' })
|
||||
unless $popen;
|
||||
|
||||
$popen = 0;
|
||||
|
||||
return $err->('poll.error.noquestions')
|
||||
unless @{$popts{'questions'}};
|
||||
|
||||
$popts{'journalid'} = $iteminfo->{'journalid'};
|
||||
$popts{'posterid'} = $iteminfo->{'posterid'};
|
||||
|
||||
push @polls, { %popts };
|
||||
|
||||
$append .= "<lj-poll-placeholder>";
|
||||
}
|
||||
|
||||
##### end QUESTION
|
||||
|
||||
elsif ($tag eq "lj-pq") {
|
||||
return $err->('poll.error.tagnotopen', { 'tag' => 'lj-pq' })
|
||||
unless $qopen;
|
||||
|
||||
unless ($qopts{'type'} eq "scale" ||
|
||||
$qopts{'type'} eq "text" ||
|
||||
@{$qopts{'items'}})
|
||||
{
|
||||
return $err->('poll.error.noitems');
|
||||
}
|
||||
|
||||
$qopts{'qtext'} =~ s/^\s+//;
|
||||
$qopts{'qtext'} =~ s/\s+$//;
|
||||
my $len = length($qopts{'qtext'})
|
||||
or return $err->('poll.error.notext');
|
||||
|
||||
push @{$popts{'questions'}}, { %qopts };
|
||||
$qopen = 0;
|
||||
|
||||
}
|
||||
|
||||
##### end ITEM
|
||||
|
||||
elsif ($tag eq "lj-pi") {
|
||||
return $err->('poll.error.tagnotopen', { 'tag' => 'lj-pi' })
|
||||
unless $iopen;
|
||||
|
||||
$iopts{'item'} =~ s/^\s+//;
|
||||
$iopts{'item'} =~ s/\s+$//;
|
||||
|
||||
my $len = length($iopts{'item'});
|
||||
return $err->('poll.error.pitoolong', { 'len' => $len, })
|
||||
if $len > 255 || $len < 1;
|
||||
|
||||
push @{$qopts{'items'}}, { %iopts };
|
||||
$iopen = 0;
|
||||
}
|
||||
|
||||
###### not a special tag.
|
||||
|
||||
else
|
||||
{
|
||||
$append .= "</$tag>";
|
||||
}
|
||||
}
|
||||
elsif ($type eq "T" || $type eq "D")
|
||||
{
|
||||
$append = $token->[1];
|
||||
}
|
||||
elsif ($type eq "C") {
|
||||
# ignore comments
|
||||
}
|
||||
elsif ($type eq "PI") {
|
||||
$newdata .= "<?$token->[1]>";
|
||||
}
|
||||
else {
|
||||
$newdata .= "<!-- OTHER: " . $type . "-->\n";
|
||||
}
|
||||
|
||||
##### append stuff to the right place
|
||||
if (length($append))
|
||||
{
|
||||
if ($iopen) {
|
||||
$iopts{'item'} .= $append;
|
||||
}
|
||||
elsif ($qopen) {
|
||||
$qopts{'qtext'} .= $append;
|
||||
}
|
||||
elsif ($popen) {
|
||||
0; # do nothing.
|
||||
} else {
|
||||
$newdata .= $append;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($popen) { return $err->('poll.error.unlockedtag', { 'tag' => 'lj-poll' }); }
|
||||
if ($qopen) { return $err->('poll.error.unlockedtag', { 'tag' => 'lj-pq' }); }
|
||||
if ($iopen) { return $err->('poll.error.unlockedtag', { 'tag' => 'lj-pi' }); }
|
||||
|
||||
$$postref = $newdata;
|
||||
return @polls;
|
||||
}
|
||||
|
||||
# preview poll
|
||||
# -- accepts $poll hashref as found in the array returned by LJ::Poll::parse()
|
||||
sub preview {
|
||||
my $poll = shift;
|
||||
return unless ref $poll eq 'HASH';
|
||||
|
||||
my $ret = '';
|
||||
|
||||
$ret .= "<form action='#'>\n";
|
||||
$ret .= "<b>" . BML::ml('poll.pollnum', { 'num' => 'xxxx' }) . "</b>";
|
||||
if ($poll->{'name'}) {
|
||||
LJ::Poll::clean_poll(\$poll->{'name'});
|
||||
$ret .= " <i>$poll->{'name'}</i>";
|
||||
}
|
||||
$ret .= "<br />\n";
|
||||
$ret .= BML::ml('poll.security', { 'whovote' => $BML::ML{'poll.security.'.$poll->{whovote}}, 'whoview' => $BML::ML{'poll.security.'.$poll->{whoview}}, });
|
||||
|
||||
# iterate through all questions
|
||||
foreach my $q (@{$poll->{'questions'}}) {
|
||||
if ($q->{'qtext'}) {
|
||||
LJ::Poll::clean_poll(\$q->{'qtext'});
|
||||
$ret .= "<p>$q->{'qtext'}</p>\n";
|
||||
}
|
||||
$ret .= "<div style='margin: 10px 0 10px 40px'>";
|
||||
|
||||
# text questions
|
||||
if ($q->{'type'} eq 'text') {
|
||||
my ($size, $max) = split(m!/!, $q->{'opts'});
|
||||
$ret .= LJ::html_text({ 'size' => $size, 'maxlength' => $max });
|
||||
|
||||
# scale questions
|
||||
} elsif ($q->{'type'} eq 'scale') {
|
||||
my ($from, $to, $by) = split(m!/!, $q->{'opts'});
|
||||
$by ||= 1;
|
||||
my $count = int(($to-$from)/$by) + 1;
|
||||
my $do_radios = ($count <= 11);
|
||||
|
||||
# few opts, display radios
|
||||
if ($do_radios) {
|
||||
$ret .= "<table><tr valign='top' align='center'>\n";
|
||||
for (my $at = $from; $at <= $to; $at += $by) {
|
||||
$ret .= "<td>" . LJ::html_check({ 'type' => 'radio' }) . "<br />$at</td>\n";
|
||||
}
|
||||
$ret .= "</tr></table>\n";
|
||||
|
||||
# many opts, display select
|
||||
} else {
|
||||
my @optlist = ();
|
||||
for (my $at = $from; $at <= $to; $at += $by) {
|
||||
push @optlist, ('', $at);
|
||||
}
|
||||
$ret .= LJ::html_select({}, @optlist);
|
||||
}
|
||||
|
||||
# questions with items
|
||||
} else {
|
||||
|
||||
# drop-down list
|
||||
if ($q->{'type'} eq 'drop') {
|
||||
my @optlist = ('', '');
|
||||
foreach my $it (@{$q->{'items'}}) {
|
||||
LJ::Poll::clean_poll(\$it->{'item'});
|
||||
push @optlist, ('', $it->{'item'});
|
||||
}
|
||||
$ret .= LJ::html_select({}, @optlist);
|
||||
|
||||
|
||||
# radio or checkbox
|
||||
} else {
|
||||
foreach my $it (@{$q->{'items'}}) {
|
||||
LJ::Poll::clean_poll(\$it->{'item'});
|
||||
$ret .= LJ::html_check({ 'type' => $q->{'type'} }) . "$it->{'item'}<br />\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ret .= "</div>\n";
|
||||
|
||||
}
|
||||
|
||||
$ret .= LJ::html_submit('', $BML::ML{'poll.submit'}, { 'disabled' => 1 }) . "\n";
|
||||
$ret .= "</form>";
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
# note: $itemid is a $ditemid (display itemid, *256 + anum)
|
||||
sub register
|
||||
{
|
||||
&LJ::nodb;
|
||||
my $dbh = LJ::get_db_writer();
|
||||
my $post = shift;
|
||||
my $error = shift;
|
||||
my $itemid = shift;
|
||||
my @polls = @_;
|
||||
|
||||
foreach my $po (@polls)
|
||||
{
|
||||
my %popts = %$po;
|
||||
$popts{'itemid'} = $itemid+0;
|
||||
|
||||
#### CREATE THE POLL!
|
||||
|
||||
my $sth = $dbh->prepare("INSERT INTO poll (itemid, journalid, posterid, whovote, whoview, name) " .
|
||||
"VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$sth->execute($itemid, $popts{'journalid'}, $popts{'posterid'},
|
||||
$popts{'whovote'}, $popts{'whoview'}, $popts{'name'});
|
||||
if ($dbh->err) {
|
||||
$$error = BML::ml('poll.dberror', { errmsg => $dbh->errstr });
|
||||
return 0;
|
||||
}
|
||||
my $pollid = $dbh->{'mysql_insertid'};
|
||||
|
||||
$$post =~ s/<lj-poll-placeholder>/<lj-poll-$pollid>/; # NOT global replace!
|
||||
|
||||
## start inserting poll questions
|
||||
my $qnum = 0;
|
||||
foreach my $q (@{$popts{'questions'}})
|
||||
{
|
||||
$qnum++;
|
||||
$sth = $dbh->prepare("INSERT INTO pollquestion (pollid, pollqid, sortorder, type, opts, qtext) " .
|
||||
"VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$sth->execute($pollid, $qnum, $qnum, $q->{'type'}, $q->{'opts'}, $q->{'qtext'});
|
||||
if ($dbh->err) {
|
||||
$$error = BML::ml('poll.dberror.questions', { errmsg => $dbh->errstr });
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $pollqid = $dbh->{'mysql_insertid'};
|
||||
|
||||
## start inserting poll items
|
||||
my $inum = 0;
|
||||
foreach my $it (@{$q->{'items'}}) {
|
||||
$inum++;
|
||||
$dbh->do("INSERT INTO pollitem (pollid, pollqid, pollitid, sortorder, item) " .
|
||||
"VALUES (?, ?, ?, ?, ?)", undef, $pollid, $qnum, $inum, $inum, $it->{'item'});
|
||||
if ($dbh->err) {
|
||||
$$error = BML::ml('poll.dberror.items', { errmsg => $dbh->errstr });
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
## end inserting poll items
|
||||
|
||||
}
|
||||
## end inserting poll questions
|
||||
|
||||
} ### end while over all poles
|
||||
|
||||
}
|
||||
|
||||
sub show_polls
|
||||
{
|
||||
&LJ::nodb;
|
||||
my $itemid = shift;
|
||||
my $remote = shift;
|
||||
my $postref = shift;
|
||||
|
||||
$$postref =~ s/<lj-poll-(\d+)>/&show_poll($itemid, $remote, $1)/eg;
|
||||
}
|
||||
|
||||
sub show_poll
|
||||
{
|
||||
&LJ::nodb;
|
||||
my $dbr = LJ::get_db_reader();
|
||||
my $itemid = shift;
|
||||
my $remote = shift;
|
||||
my $pollid = shift;
|
||||
my $opts = shift; # hashref. {"mode" => results/enter/ans}
|
||||
my $sth;
|
||||
|
||||
my $mode = $opts->{'mode'};
|
||||
$pollid += 0;
|
||||
|
||||
my $po = $dbr->selectrow_hashref("SELECT * FROM poll WHERE pollid=?", undef, $pollid);
|
||||
return "<b>[" . BML::ml('poll.error.pollnotfound', { 'num' => $pollid }) . "]</b>" unless $po;
|
||||
return "<b>[$BML::ML{'poll.error.noentry'}]</b>"
|
||||
if $itemid && $po->{'itemid'} != $itemid;
|
||||
|
||||
my ($can_vote, $can_view) = find_security($po, $remote);
|
||||
|
||||
# update the mode if we need to
|
||||
$mode = 'results' unless $remote;
|
||||
if (!$mode && $remote) {
|
||||
my $time = $dbr->selectrow_array('SELECT datesubmit FROM pollsubmission '.
|
||||
'WHERE pollid=? AND userid=?', undef, $pollid, $remote->{userid});
|
||||
$mode = $time ? 'results' : $can_vote ? 'enter' : 'results';
|
||||
}
|
||||
|
||||
### load all the questions
|
||||
my @qs;
|
||||
$sth = $dbr->prepare('SELECT * FROM pollquestion WHERE pollid=?');
|
||||
$sth->execute($pollid);
|
||||
push @qs, $_ while $_ = $sth->fetchrow_hashref;
|
||||
@qs = sort { $a->{sortorder} <=> $b->{sortorder} } @qs;
|
||||
|
||||
### load all the items
|
||||
my %its;
|
||||
$sth = $dbr->prepare("SELECT pollqid, pollitid, item FROM pollitem WHERE pollid=? ORDER BY sortorder");
|
||||
$sth->execute($pollid);
|
||||
while (my ($qid, $itid, $item) = $sth->fetchrow_array) {
|
||||
push @{$its{$qid}}, [ $itid, $item ];
|
||||
}
|
||||
|
||||
# see if we have a hook for alternate poll contents
|
||||
my $ret = LJ::run_hook('alternate_show_poll_html', $po, $mode, \@qs);
|
||||
return $ret if $ret;
|
||||
|
||||
### view answers to a particular question in a poll
|
||||
if ($mode eq "ans")
|
||||
{
|
||||
return "<b>[$BML::ML{'poll.error.cantview'}]</b>"
|
||||
unless $can_view;
|
||||
|
||||
# get the question from @qs, which we loaded earlier
|
||||
my $q;
|
||||
foreach (@qs) {
|
||||
$q = $_ if $_->{pollqid} == $opts->{qid};
|
||||
}
|
||||
return "<b>[$BML::ML{'poll.error.questionnotfound'}]</b>"
|
||||
unless $q;
|
||||
|
||||
# get the item information from %its, also loaded earlier
|
||||
my %it;
|
||||
$it{$_->[0]} = $_->[1] foreach (@{$its{$opts->{qid}}});
|
||||
|
||||
LJ::Poll::clean_poll(\$q->{'qtext'});
|
||||
$ret .= $q->{'qtext'};
|
||||
$ret .= "<p>";
|
||||
|
||||
my $LIMIT = 2000;
|
||||
$sth = $dbr->prepare("SELECT u.user, pr.value, ps.datesubmit ".
|
||||
"FROM useridmap u, pollresult pr, pollsubmission ps " .
|
||||
"WHERE u.userid=pr.userid AND pr.pollid=? AND pollqid=? " .
|
||||
"AND ps.pollid=pr.pollid AND ps.userid=pr.userid LIMIT $LIMIT");
|
||||
$sth->execute($pollid, $opts->{'qid'});
|
||||
|
||||
my @res;
|
||||
push @res, $_ while $_ = $sth->fetchrow_hashref;
|
||||
@res = sort { $a->{datesubmit} cmp $b->{datesubmit} } @res;
|
||||
|
||||
foreach my $res (@res) {
|
||||
my ($user, $value) = ($res->{user}, $res->{value});
|
||||
|
||||
## some question types need translation; type 'text' doesn't.
|
||||
if ($q->{'type'} eq "radio" || $q->{'type'} eq "drop") {
|
||||
$value = $it{$value};
|
||||
}
|
||||
elsif ($q->{'type'} eq "check") {
|
||||
$value = join(", ", map { $it{$_} } split(/,/, $value));
|
||||
}
|
||||
|
||||
LJ::Poll::clean_poll(\$value);
|
||||
$ret .= "<p>" . LJ::ljuser($user) . " -- $value</p>\n";
|
||||
}
|
||||
|
||||
# temporary
|
||||
if (@res == $LIMIT) {
|
||||
$ret .= "<p>[$BML::ML{'poll.error.truncated'}]</p>";
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
# Users cannot vote unless they are logged in
|
||||
return "<?needlogin?>"
|
||||
if $mode eq 'enter' && !$remote;
|
||||
|
||||
my $do_form = $mode eq 'enter' && $can_vote;
|
||||
my %preval;
|
||||
|
||||
if ($do_form) {
|
||||
$sth = $dbr->prepare("SELECT pollqid, value FROM pollresult WHERE pollid=? AND userid=?");
|
||||
$sth->execute($pollid, $remote->{'userid'});
|
||||
while (my ($qid, $value) = $sth->fetchrow_array) {
|
||||
$preval{$qid} = $value;
|
||||
}
|
||||
|
||||
$ret .= "<form action='$LJ::SITEROOT/poll/?id=$pollid' method='post'>";
|
||||
$ret .= LJ::form_auth();
|
||||
$ret .= LJ::html_hidden('pollid', $pollid);
|
||||
}
|
||||
|
||||
$ret .= "<b><a href='$LJ::SITEROOT/poll/?id=$pollid'>" . BML::ml('poll.pollnum', { 'num' => $pollid }) . "</a></b> ";
|
||||
if ($po->{'name'}) {
|
||||
LJ::Poll::clean_poll(\$po->{'name'});
|
||||
$ret .= "<i>$po->{'name'}</i>";
|
||||
}
|
||||
$ret .= "<br />\n";
|
||||
$ret .= BML::ml('poll.security', { 'whovote' => $BML::ML{'poll.security.'.$po->{whovote}},
|
||||
'whoview' => $BML::ML{'poll.security.'.$po->{whoview}} });
|
||||
my $text = LJ::run_hook('extra_poll_description', $po, \@qs);
|
||||
$ret .= "<br />$text" if $text;
|
||||
|
||||
## go through all questions, adding to buffer to return
|
||||
foreach my $q (@qs)
|
||||
{
|
||||
my $qid = $q->{'pollqid'};
|
||||
LJ::Poll::clean_poll(\$q->{'qtext'});
|
||||
$ret .= "<p>$q->{'qtext'}</p><div style='margin: 10px 0 10px 40px'>";
|
||||
|
||||
### get statistics, for scale questions
|
||||
my ($valcount, $valmean, $valstddev, $valmedian);
|
||||
if ($q->{'type'} eq "scale")
|
||||
{
|
||||
## manually add all the possible values, since they aren't in the database
|
||||
## (which was the whole point of making a "scale" type):
|
||||
my ($from, $to, $by) = split(m!/!, $q->{'opts'});
|
||||
$by = 1 unless ($by > 0 and int($by) == $by);
|
||||
for (my $at=$from; $at<=$to; $at+=$by) {
|
||||
push @{$its{$qid}}, [ $at, $at ]; # note: fake itemid, doesn't matter, but needed to be unique
|
||||
}
|
||||
|
||||
$sth = $dbr->prepare("SELECT COUNT(*), AVG(value), STDDEV(value) FROM pollresult WHERE pollid=? AND pollqid=?");
|
||||
$sth->execute($pollid, $qid);
|
||||
($valcount, $valmean, $valstddev) = $sth->fetchrow_array;
|
||||
|
||||
# find median:
|
||||
$valmedian = 0;
|
||||
if ($valcount == 1) {
|
||||
$valmedian = $valmean;
|
||||
} elsif ($valcount > 1) {
|
||||
my ($mid, $fetch);
|
||||
# fetch two mids and average if even count, else grab absolute middle
|
||||
$fetch = ($valcount % 2) ? 1 : 2;
|
||||
$mid = int(($valcount+1)/2);
|
||||
my $skip = $mid-1;
|
||||
|
||||
$sth = $dbr->prepare("SELECT value FROM pollresult WHERE pollid=? AND pollqid=? " .
|
||||
"ORDER BY value+0 LIMIT $skip,$fetch");
|
||||
$sth->execute($pollid, $qid);
|
||||
while (my ($v) = $sth->fetchrow_array) {
|
||||
$valmedian += $v;
|
||||
}
|
||||
$valmedian /= $fetch;
|
||||
}
|
||||
}
|
||||
|
||||
my $usersvoted = 0;
|
||||
my %itvotes;
|
||||
my $maxitvotes = 1;
|
||||
|
||||
if ($mode eq "results")
|
||||
{
|
||||
### to see individual's answers
|
||||
$ret .= "<a href='$LJ::SITEROOT/poll/?id=$pollid&qid=$qid&mode=ans'>$BML::ML{'poll.viewanswers'}</a><br />";
|
||||
|
||||
### but, if this is a non-text item, and we're showing results, need to load the answers:
|
||||
if ($q->{'type'} ne "text") {
|
||||
$sth = $dbr->prepare("SELECT value FROM pollresult WHERE pollid=? AND pollqid=?");
|
||||
$sth->execute($pollid, $qid);
|
||||
while (my ($val) = $sth->fetchrow_array) {
|
||||
$usersvoted++;
|
||||
if ($q->{'type'} eq "check") {
|
||||
foreach (split(/,/,$val)) {
|
||||
$itvotes{$_}++;
|
||||
}
|
||||
} else {
|
||||
$itvotes{$val}++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (values %itvotes) {
|
||||
$maxitvotes = $_ if ($_ > $maxitvotes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#### text questions are the easy case
|
||||
|
||||
if ($q->{'type'} eq "text" && $do_form) {
|
||||
my ($size, $max) = split(m!/!, $q->{'opts'});
|
||||
|
||||
$ret .= LJ::html_text({ 'size' => $size, 'maxlength' => $max,
|
||||
'name' => "pollq-$qid", 'value' => $preval{$qid} });
|
||||
}
|
||||
|
||||
#### drop-down list
|
||||
elsif ($q->{'type'} eq 'drop' && $do_form) {
|
||||
my @optlist = ('', '');
|
||||
foreach my $it (@{$its{$qid}}) {
|
||||
my ($itid, $item) = @$it;
|
||||
LJ::Poll::clean_poll(\$item);
|
||||
push @optlist, ($itid, $item);
|
||||
}
|
||||
$ret .= LJ::html_select({ 'name' => "pollq-$qid",
|
||||
'selected' => $preval{$qid} }, @optlist);
|
||||
}
|
||||
|
||||
#### scales (from 1-10) questions
|
||||
|
||||
elsif ($q->{'type'} eq "scale" && $do_form) {
|
||||
my ($from, $to, $by) = split(m!/!, $q->{'opts'});
|
||||
$by ||= 1;
|
||||
my $count = int(($to-$from)/$by) + 1;
|
||||
my $do_radios = ($count <= 11);
|
||||
|
||||
# few opts, display radios
|
||||
if ($do_radios) {
|
||||
|
||||
$ret .= "<table><tr valign='top' align='center'>";
|
||||
|
||||
for (my $at=$from; $at<=$to; $at+=$by) {
|
||||
$ret .= "<td style='text-align: center;'>";
|
||||
$ret .= LJ::html_check({ 'type' => 'radio', 'name' => "pollq-$qid",
|
||||
'value' => $at, 'id' => "pollq-$pollid-$qid-$at",
|
||||
'selected' => (defined $preval{$qid} && $at == $preval{$qid}) });
|
||||
$ret .= "<br /><label for='pollq-$pollid-$qid-$at'>$at</label></td>";
|
||||
}
|
||||
|
||||
$ret .= "</tr></table>\n";
|
||||
|
||||
# many opts, display select
|
||||
# but only if displaying form
|
||||
} else {
|
||||
|
||||
my @optlist = ('', '');
|
||||
for (my $at=$from; $at<=$to; $at+=$by) {
|
||||
push @optlist, ($at, $at);
|
||||
}
|
||||
$ret .= LJ::html_select({ 'name' => "pollq-$qid", 'selected' => $preval{$qid} }, @optlist);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#### now, questions with items
|
||||
|
||||
else
|
||||
{
|
||||
my $do_table = 0;
|
||||
|
||||
if ($q->{'type'} eq "scale") { # implies ! do_form
|
||||
my $stddev = sprintf("%.2f", $valstddev);
|
||||
my $mean = sprintf("%.2f", $valmean);
|
||||
$ret .= BML::ml('poll.scaleanswers', { 'mean' => $mean, 'median' => $valmedian, 'stddev' => $stddev });
|
||||
$ret .= "<br />\n";
|
||||
$do_table = 1;
|
||||
$ret .= "<table>";
|
||||
}
|
||||
|
||||
foreach my $it (@{$its{$qid}})
|
||||
{
|
||||
my ($itid, $item) = @$it;
|
||||
LJ::Poll::clean_poll(\$item);
|
||||
|
||||
# displaying a radio or checkbox
|
||||
if ($do_form) {
|
||||
$ret .= LJ::html_check({ 'type' => $q->{'type'}, 'name' => "pollq-$qid",
|
||||
'value' => $itid, 'id' => "pollq-$pollid-$qid-$itid",
|
||||
'selected' => ($preval{$qid} =~ /\b$itid\b/) });
|
||||
$ret .= " <label for='pollq-$pollid-$qid-$itid'>$item</label><br />";
|
||||
next;
|
||||
}
|
||||
|
||||
# displaying results
|
||||
my $count = $itvotes{$itid}+0;
|
||||
my $percent = sprintf("%.1f", (100 * $count / ($usersvoted||1)));
|
||||
my $width = 20+int(($count/$maxitvotes)*380);
|
||||
|
||||
if ($do_table) {
|
||||
$ret .= "<tr valign='middle'><td align='right'>$item</td>";
|
||||
$ret .= "<td><img src='$LJ::IMGPREFIX/poll/leftbar.gif' align='absmiddle' height='14' width='7' />";
|
||||
$ret .= "<img src='$LJ::IMGPREFIX/poll/mainbar.gif' align='absmiddle' height='14' width='$width' alt='$count ($percent%)' />";
|
||||
$ret .= "<img src='$LJ::IMGPREFIX/poll/rightbar.gif' align='absmiddle' height='14' width='7' /> ";
|
||||
$ret .= "<b>$count</b> ($percent%)</td></tr>";
|
||||
} else {
|
||||
$ret .= "<p>$item<br />";
|
||||
$ret .= "<span style='white-space: nowrap'><img src='$LJ::IMGPREFIX/poll/leftbar.gif' align='absmiddle' height='14' width='7' />";
|
||||
$ret .= "<img src='$LJ::IMGPREFIX/poll/mainbar.gif' align='absmiddle' height='14' width='$width' alt='$count ($percent%)' />";
|
||||
$ret .= "<img src='$LJ::IMGPREFIX/poll/rightbar.gif' align='absmiddle' height='14' width='7' /> ";
|
||||
$ret .= "<b>$count</b> ($percent%)</span></p>";
|
||||
}
|
||||
}
|
||||
|
||||
if ($do_table) {
|
||||
$ret .= "</table>";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$ret .= "</div>";
|
||||
}
|
||||
|
||||
if ($do_form) {
|
||||
$ret .= LJ::html_submit('poll-submit', $BML::ML{'poll.submit'}) . "</form>\n";;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
sub find_security
|
||||
{
|
||||
&LJ::nodb;
|
||||
|
||||
my $po = shift;
|
||||
my $remote = shift;
|
||||
my $sth;
|
||||
|
||||
## if remote is poll owner, can do anything.
|
||||
if ($remote && $remote->{'userid'} == $po->{'posterid'}) {
|
||||
return (1, 1);
|
||||
}
|
||||
|
||||
## need to be both a person and with a visible journal to vote
|
||||
if ($remote &&
|
||||
($remote->{'journaltype'} ne "P" || $remote->{'statusvis'} ne "V")) {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
my $is_friend = 0;
|
||||
if (($po->{'whoview'} eq "friends" ||
|
||||
$po->{'whovote'} eq "friends") && $remote)
|
||||
{
|
||||
$is_friend = LJ::is_friend($po->{'journalid'}, $remote->{'userid'});
|
||||
}
|
||||
|
||||
my %sec;
|
||||
if ($po->{'whoview'} eq "all" ||
|
||||
($po->{'whoview'} eq "friends" && $is_friend) ||
|
||||
($po->{'whoview'} eq "none" && $remote && $remote->{'userid'} == $po->{'posterid'}))
|
||||
{
|
||||
$sec{'view'} = 1;
|
||||
}
|
||||
|
||||
if ($po->{'whovote'} eq "all" ||
|
||||
($po->{'whovote'} eq "friends" && $is_friend))
|
||||
{
|
||||
$sec{'vote'} = 1;
|
||||
}
|
||||
|
||||
if ($sec{'vote'} && (LJ::is_banned($remote, $po->{'journalid'}) ||
|
||||
LJ::is_banned($remote, $po->{'posterid'}))) {
|
||||
$sec{'vote'} = 0;
|
||||
}
|
||||
|
||||
return ($sec{'vote'}, $sec{'view'});
|
||||
}
|
||||
|
||||
sub submit
|
||||
{
|
||||
&LJ::nodb;
|
||||
|
||||
my $remote = shift;
|
||||
my $form = shift;
|
||||
my $error = shift;
|
||||
my $sth;
|
||||
|
||||
my $dbh = LJ::get_db_writer();
|
||||
|
||||
unless ($remote) {
|
||||
$$error = $BML::ML{'error.noremote'}; # instead of <?needremote?>, because errors are displayed in LJ::bad_input()
|
||||
return 0;
|
||||
}
|
||||
|
||||
my $pollid = $form->{'pollid'}+0;
|
||||
my $po = $dbh->selectrow_hashref("SELECT itemid, whovote, journalid, posterid, whoview, whovote, name ".
|
||||
"FROM poll WHERE pollid=?", undef, $pollid);
|
||||
unless ($po) {
|
||||
$$error = $BML::ML{'poll.error.nopollid'};
|
||||
return 0;
|
||||
}
|
||||
|
||||
my ($can_vote, undef) = find_security($po, $remote);
|
||||
|
||||
unless ($can_vote) {
|
||||
$$error = $BML::ML{'poll.error.cantvote'};
|
||||
return 0;
|
||||
}
|
||||
|
||||
### load all the questions
|
||||
my @qs;
|
||||
$sth = $dbh->prepare("SELECT pollqid, type, opts, qtext FROM pollquestion WHERE pollid=?");
|
||||
$sth->execute($pollid);
|
||||
push @qs, $_ while $_ = $sth->fetchrow_hashref;
|
||||
|
||||
foreach my $q (@qs) {
|
||||
my $qid = $q->{'pollqid'}+0;
|
||||
my $val = $form->{"pollq-$qid"};
|
||||
if ($q->{'type'} eq "check") {
|
||||
## multi-selected items are comma separated from htdocs/poll/index.bml
|
||||
$val = join(",", sort { $a <=> $b } split(/,/, $val));
|
||||
}
|
||||
if ($q->{'type'} eq "scale") {
|
||||
my ($from, $to, $by) = split(m!/!, $q->{'opts'});
|
||||
if ($val < $from || $val > $to) {
|
||||
# bogus! cheating?
|
||||
$val = "";
|
||||
}
|
||||
}
|
||||
if ($val ne "") {
|
||||
$dbh->do("REPLACE INTO pollresult (pollid, pollqid, userid, value) VALUES (?, ?, ?, ?)",
|
||||
undef, $pollid, $qid, $remote->{'userid'}, $val);
|
||||
} else {
|
||||
$dbh->do("DELETE FROM pollresult WHERE pollid=? AND pollqid=? AND userid=?",
|
||||
undef, $pollid, $qid, $remote->{'userid'});
|
||||
}
|
||||
}
|
||||
|
||||
## finally, register the vote happened
|
||||
$dbh->do("REPLACE INTO pollsubmission (pollid, userid, datesubmit) VALUES (?, ?, NOW())",
|
||||
undef, $pollid, $remote->{'userid'});
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
3478
local/cgi-bin/ljprotocol.pl
Executable file
3478
local/cgi-bin/ljprotocol.pl
Executable file
File diff suppressed because it is too large
Load Diff
66
local/cgi-bin/ljr_readconf.pl
Executable file
66
local/cgi-bin/ljr_readconf.pl
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
{
|
||||
package LJ;
|
||||
|
||||
%LJR_REVERSE_PROXIES = (
|
||||
"127.0.0.1" => 1,
|
||||
"127.0.0.2" => 1,
|
||||
);
|
||||
eval {
|
||||
open (t1, "cat $ENV{'LJHOME'}/etc/reverse-proxy.conf 2> /dev/null | ");
|
||||
while(<t1>) {
|
||||
my $cline = $_;
|
||||
$cline =~ s/(#.*)//;
|
||||
if ($cline =~ /([\d]+\.[\d]+\.[\d]+\.[\d]+)/) {
|
||||
$LJR_REVERSE_PROXIES{$1} = 1;
|
||||
}
|
||||
}
|
||||
close t1;
|
||||
};
|
||||
|
||||
sub get_real_remote_ip {
|
||||
my @ips_in = @_;
|
||||
|
||||
my @ips;
|
||||
foreach my $i (@ips_in) {
|
||||
while ($i =~ /([\d]+\.[\d]+\.[\d]+\.[\d]+)/g) {
|
||||
push @ips, $1;
|
||||
}
|
||||
}
|
||||
my $ip;
|
||||
foreach $ip (@ips) {
|
||||
if (scalar(%LJ::LJR_REVERSE_PROXIES)) {
|
||||
if ($LJ::LJR_REVERSE_PROXIES{$ip}) {
|
||||
next;
|
||||
}
|
||||
return $ip;
|
||||
}
|
||||
else {
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
return $ip; # in case we access server right from the proxy
|
||||
}
|
||||
|
||||
sub filter_out_ip {
|
||||
my ($real_ip, @ips_in) = @_;
|
||||
my @ips;
|
||||
foreach my $i (@ips_in) {
|
||||
while ($i =~ /([\d]+\.[\d]+\.[\d]+\.[\d]+)/g) {
|
||||
my $t = $1;
|
||||
if ($t ne $real_ip) {
|
||||
push @ips, $t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return @ips;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 1;
|
||||
616
local/cgi-bin/ljtextutil.pl
Normal file
616
local/cgi-bin/ljtextutil.pl
Normal file
@@ -0,0 +1,616 @@
|
||||
package LJ;
|
||||
use strict;
|
||||
no warnings 'uninitialized';
|
||||
|
||||
use Class::Autouse qw(
|
||||
LJ::ConvUTF8
|
||||
HTML::TokeParser
|
||||
);
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::trim
|
||||
# class: text
|
||||
# des: Removes whitespace from left and right side of a string.
|
||||
# args: string
|
||||
# des-string: string to be trimmed
|
||||
# returns: trimmed string
|
||||
# </LJFUNC>
|
||||
sub trim
|
||||
{
|
||||
my $a = $_[0];
|
||||
$a =~ s/^\s+//;
|
||||
$a =~ s/\s+$//;
|
||||
return $a;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::decode_url_string
|
||||
# class: web
|
||||
# des: Parse URL-style arg/value pairs into a hash.
|
||||
# args: buffer, hashref
|
||||
# des-buffer: Scalar or scalarref of buffer to parse.
|
||||
# des-hashref: Hashref to populate.
|
||||
# returns: boolean; true.
|
||||
# </LJFUNC>
|
||||
sub decode_url_string
|
||||
{
|
||||
my $a = shift;
|
||||
my $buffer = ref $a ? $a : \$a;
|
||||
my $hashref = shift; # output hash
|
||||
my $keyref = shift; # array of keys as they were found
|
||||
|
||||
my $pair;
|
||||
my @pairs = split(/&/, $$buffer);
|
||||
@$keyref = @pairs;
|
||||
my ($name, $value);
|
||||
foreach $pair (@pairs)
|
||||
{
|
||||
($name, $value) = split(/=/, $pair);
|
||||
$value =~ tr/+/ /;
|
||||
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
|
||||
$name =~ tr/+/ /;
|
||||
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
|
||||
$hashref->{$name} .= $hashref->{$name} ? "\0$value" : $value;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# args: hashref of key/values
|
||||
# arrayref of keys in order (optional)
|
||||
# returns: urlencoded string
|
||||
sub encode_url_string {
|
||||
my ($hashref, $keyref) = @_;
|
||||
|
||||
return join('&', map { LJ::eurl($_) . '=' . LJ::eurl($hashref->{$_}) }
|
||||
(ref $keyref ? @$keyref : keys %$hashref));
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::eurl
|
||||
# class: text
|
||||
# des: Escapes a value before it can be put in a URL. See also [func[LJ::durl]].
|
||||
# args: string
|
||||
# des-string: string to be escaped
|
||||
# returns: string escaped
|
||||
# </LJFUNC>
|
||||
sub eurl
|
||||
{
|
||||
my $a = $_[0];
|
||||
return '' unless $a;
|
||||
$a =~ s/([^a-zA-Z0-9_\,\-.\/\\\: ])/uc sprintf("%%%02x",ord($1))/eg;
|
||||
$a =~ tr/ /+/;
|
||||
return $a;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::durl
|
||||
# class: text
|
||||
# des: Decodes a value that's URL-escaped. See also [func[LJ::eurl]].
|
||||
# args: string
|
||||
# des-string: string to be decoded
|
||||
# returns: string decoded
|
||||
# </LJFUNC>
|
||||
sub durl
|
||||
{
|
||||
my ($a) = @_;
|
||||
$a =~ tr/+/ /;
|
||||
$a =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
|
||||
return $a;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::exml
|
||||
# class: text
|
||||
# des: Escapes a value before it can be put in XML.
|
||||
# args: string
|
||||
# des-string: string to be escaped
|
||||
# returns: string escaped.
|
||||
# </LJFUNC>
|
||||
sub exml
|
||||
{
|
||||
# fast path for the commmon case:
|
||||
return $_[0] unless $_[0] =~ /[&\"\'<>\x00-\x08\x0B\x0C\x0E-\x1F]/;
|
||||
# what are those character ranges? XML 1.0 allows:
|
||||
# #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
|
||||
|
||||
my $a = shift;
|
||||
$a =~ s/\&/&/g;
|
||||
$a =~ s/\"/"/g;
|
||||
$a =~ s/\'/'/g;
|
||||
$a =~ s/</</g;
|
||||
$a =~ s/>/>/g;
|
||||
$a =~ s/[\x00-\x08\x0B\x0C\x0E-\x1F]//g;
|
||||
return $a;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::ehtml
|
||||
# class: text
|
||||
# des: Escapes a value before it can be put in HTML.
|
||||
# args: string
|
||||
# des-string: string to be escaped
|
||||
# returns: string escaped.
|
||||
# </LJFUNC>
|
||||
sub ehtml
|
||||
{
|
||||
# fast path for the commmon case:
|
||||
return $_[0] unless $_[0] =~ /[&\"\'<>]/;
|
||||
|
||||
# this is faster than doing one substitution with a map:
|
||||
my $a = $_[0];
|
||||
$a =~ s/\&/&/g;
|
||||
$a =~ s/\"/"/g;
|
||||
$a =~ s/\'/&\#39;/g;
|
||||
$a =~ s/</</g;
|
||||
$a =~ s/>/>/g;
|
||||
return $a;
|
||||
}
|
||||
*eall = \&ehtml; # old BML syntax required eall to also escape BML. not anymore.
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::etags
|
||||
# class: text
|
||||
# des: Escapes < and > from a string
|
||||
# args: string
|
||||
# des-string: string to be escaped
|
||||
# returns: string escaped.
|
||||
# </LJFUNC>
|
||||
sub etags
|
||||
{
|
||||
# fast path for the commmon case:
|
||||
return $_[0] unless $_[0] =~ /[<>]/;
|
||||
|
||||
my $a = $_[0];
|
||||
$a =~ s/</</g;
|
||||
$a =~ s/>/>/g;
|
||||
return $a;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::ejs
|
||||
# class: text
|
||||
# des: Escapes a string value before it can be put in JavaScript.
|
||||
# args: string
|
||||
# des-string: string to be escaped
|
||||
# returns: string escaped.
|
||||
# </LJFUNC>
|
||||
sub ejs
|
||||
{
|
||||
my $a = $_[0];
|
||||
$a =~ s/[\"\'\\]/\\$&/g;
|
||||
$a =~ s/"/\\"/g;
|
||||
$a =~ s/\r?\n/\\n/gs;
|
||||
$a =~ s/\r//gs;
|
||||
return $a;
|
||||
}
|
||||
|
||||
# given a string, makes it into a string you can put into javascript,
|
||||
# including protecting against closing </script> tags in the entry.
|
||||
# does the double quotes for ya.
|
||||
sub ejs_string {
|
||||
my $str = ejs($_[0]);
|
||||
$str =~ s!</script!</scri\" + \"pt!g;
|
||||
return "\"" . $str . "\"";
|
||||
}
|
||||
|
||||
# changes every char in a string to %XX where XX is the hex value
|
||||
# this is useful for passing strings to javascript through HTML, because
|
||||
# javascript's "unescape" function expects strings in this format
|
||||
sub ejs_all
|
||||
{
|
||||
my $a = $_[0];
|
||||
$a =~ s/(.)/uc sprintf("%%%02x",ord($1))/eg;
|
||||
return $a;
|
||||
}
|
||||
|
||||
|
||||
# strip all HTML tags from a string
|
||||
sub strip_html {
|
||||
my $str = shift;
|
||||
$str =~ s/\<lj user\=['"]?([\w-]+)['"]?\>/$1/g; # "
|
||||
$str =~ s/\<([^\<])+\>//g;
|
||||
return $str;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::is_ascii
|
||||
# des: checks if text is pure ASCII.
|
||||
# args: text
|
||||
# des-text: text to check for being pure 7-bit ASCII text.
|
||||
# returns: 1 if text is indeed pure 7-bit, 0 otherwise.
|
||||
# </LJFUNC>
|
||||
sub is_ascii {
|
||||
my $text = shift;
|
||||
return ($text !~ m/[^\x01-\x7f]/);
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::is_utf8
|
||||
# des: check text for UTF-8 validity.
|
||||
# args: text
|
||||
# des-text: text to check for UTF-8 validity
|
||||
# returns: 1 if text is a valid UTF-8 stream, 0 otherwise.
|
||||
# </LJFUNC>
|
||||
sub is_utf8 {
|
||||
my $text = shift;
|
||||
|
||||
# it seems there is a dumbass who calls this function
|
||||
# with $text being reference to scalar; now we can tolerate this
|
||||
if (ref ($text) eq "HASH") {
|
||||
return ! (grep { !LJ::is_utf8($_) } values %{$text});
|
||||
}
|
||||
elsif (ref ($text) eq "ARRAY") {
|
||||
return ! (grep { !LJ::is_utf8($_) } @{$text});
|
||||
}
|
||||
elsif (ref ($text)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (LJ::are_hooks("is_utf8")) {
|
||||
return LJ::run_hook("is_utf8", $text);
|
||||
}
|
||||
|
||||
# for a discussion of the different utf8 validity checking methods,
|
||||
# see: http://zilla.livejournal.org/657
|
||||
# in summary, this isn't the fastest, but it's pretty fast, it doesn't make
|
||||
# perl segfault, and it doesn't add new crazy dependencies. if you want
|
||||
# speed, check out ljcom's is_utf8 version in C, using Inline.pm
|
||||
|
||||
return 1 unless defined($text);
|
||||
|
||||
my $u = Unicode::String::utf8($text);
|
||||
my $text2 = $u->utf8;
|
||||
|
||||
# return $text eq $text2;
|
||||
|
||||
return LJ::is_ascii($text) || utf8::is_utf8($text) || $text eq $text2;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::text_out
|
||||
# des: force outgoing text into valid UTF-8.
|
||||
# args: text
|
||||
# des-text: reference to text to pass to output. Text if modified in-place.
|
||||
# returns: nothing.
|
||||
# </LJFUNC>
|
||||
sub text_out
|
||||
{
|
||||
my $rtext = shift;
|
||||
|
||||
# if we're not Unicode, do nothing
|
||||
return unless $LJ::UNICODE;
|
||||
|
||||
# is this valid UTF-8 already?
|
||||
return if LJ::is_utf8($$rtext);
|
||||
|
||||
# no. Blot out all non-ASCII chars
|
||||
$$rtext =~ s/[\x00\x80-\xff]/\?/g;
|
||||
return;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::text_in
|
||||
# des: do appropriate checks on input text. Should be called on all
|
||||
# user-generated text.
|
||||
# args: text
|
||||
# des-text: text to check
|
||||
# returns: 1 if the text is valid, 0 if not.
|
||||
# </LJFUNC>
|
||||
sub text_in
|
||||
{
|
||||
my $text = shift;
|
||||
return 1 unless $LJ::UNICODE;
|
||||
if (ref ($text) eq "HASH") {
|
||||
return ! (grep { !LJ::is_utf8($_) } values %{$text});
|
||||
}
|
||||
if (ref ($text) eq "ARRAY") {
|
||||
return ! (grep { !LJ::is_utf8($_) } @{$text});
|
||||
}
|
||||
return LJ::is_utf8($text);
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::text_convert
|
||||
# des: convert old entries/comments to UTF-8 using user's default encoding.
|
||||
# args: dbs?, text, u, error
|
||||
# des-dbs: optional. Deprecated; a master/slave set of database handles.
|
||||
# des-text: old possibly non-ASCII text to convert
|
||||
# des-u: user hashref of the journal's owner
|
||||
# des-error: ref to a scalar variable which is set to 1 on error
|
||||
# (when user has no default encoding defined, but
|
||||
# text needs to be translated).
|
||||
# returns: converted text or undef on error
|
||||
# </LJFUNC>
|
||||
sub text_convert
|
||||
{
|
||||
&nodb;
|
||||
my ($text, $u, $error) = @_;
|
||||
|
||||
# maybe it's pure ASCII?
|
||||
return $text if LJ::is_ascii($text);
|
||||
|
||||
# load encoding id->name mapping if it's not loaded yet
|
||||
LJ::load_codes({ "encoding" => \%LJ::CACHE_ENCODINGS } )
|
||||
unless %LJ::CACHE_ENCODINGS;
|
||||
|
||||
if ($u->{'oldenc'} == 0 ||
|
||||
not defined $LJ::CACHE_ENCODINGS{$u->{'oldenc'}}) {
|
||||
$$error = 1;
|
||||
return undef;
|
||||
};
|
||||
|
||||
# convert!
|
||||
my $name = $LJ::CACHE_ENCODINGS{$u->{'oldenc'}};
|
||||
unless (LJ::ConvUTF8->supported_charset($name)) {
|
||||
$$error = 1;
|
||||
return undef;
|
||||
}
|
||||
|
||||
return LJ::ConvUTF8->to_utf8($name, $text);
|
||||
}
|
||||
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::text_length
|
||||
# des: returns both byte length and character length of a string. In a non-Unicode
|
||||
# environment, this means byte length twice. In a Unicode environment,
|
||||
# the function assumes that its argument is a valid UTF-8 string.
|
||||
# args: text
|
||||
# des-text: the string to measure
|
||||
# returns: a list of two values, (byte_length, char_length).
|
||||
# </LJFUNC>
|
||||
|
||||
sub text_length
|
||||
{
|
||||
my $text = shift;
|
||||
my $bl = length($text);
|
||||
unless ($LJ::UNICODE) {
|
||||
return ($bl, $bl);
|
||||
}
|
||||
my $cl = 0;
|
||||
my $utf_char = "([\x00-\x7f]|[\xc0-\xdf].|[\xe0-\xef]..|[\xf0-\xf7]...)";
|
||||
|
||||
while ($text =~ m/$utf_char/go) { $cl++; }
|
||||
return ($bl, $cl);
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::text_trim
|
||||
# des: truncate string according to requirements on byte length, char
|
||||
# length, or both. "char length" means number of UTF-8 characters if
|
||||
# [ljconfig[unicode]] is set, or the same thing as byte length otherwise.
|
||||
# args: text, byte_max, char_max
|
||||
# des-text: the string to trim
|
||||
# des-byte_max: maximum allowed length in bytes; if 0, there's no restriction
|
||||
# des-char_max: maximum allowed length in chars; if 0, there's no restriction
|
||||
# returns: the truncated string.
|
||||
# </LJFUNC>
|
||||
sub text_trim
|
||||
{
|
||||
my ($text, $byte_max, $char_max) = @_;
|
||||
return $text unless $byte_max or $char_max;
|
||||
if (!$LJ::UNICODE) {
|
||||
$byte_max = $char_max if $char_max and $char_max < $byte_max;
|
||||
$byte_max = $char_max unless $byte_max;
|
||||
return substr($text, 0, $byte_max);
|
||||
}
|
||||
|
||||
# after upgrade to perl 5.12 we can use perl subroutines to handle unicode
|
||||
# (need to convert all the input to unicode as soon as it enters the system)
|
||||
return substr($text,0,$char_max);
|
||||
|
||||
# my $cur = 0;
|
||||
# my $utf_char = "([\x00-\x7f]|[\xc0-\xdf].|[\xe0-\xef]..|[\xf0-\xf7]...)";
|
||||
#
|
||||
# # if we don't have a character limit, assume it's the same as the byte limit.
|
||||
# # we will never have more characters than bytes, but we might have more bytes
|
||||
# # than characters, so we can't inherit the other way.
|
||||
# $char_max ||= $byte_max;
|
||||
#
|
||||
# while ($text =~ m/$utf_char/gco) {
|
||||
# last unless $char_max;
|
||||
# last if $cur + length($1) > $byte_max and $byte_max;
|
||||
# $cur += length($1);
|
||||
# $char_max--;
|
||||
# }
|
||||
# return substr($text,0,$cur);
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::text_compress
|
||||
# des: Compresses a chunk of text, to gzip, if configured for site. Can compress
|
||||
# a scalarref in place, or return a compressed copy. Won't compress if
|
||||
# value is too small, already compressed, or size would grow by compressing.
|
||||
# args: text
|
||||
# des-text: either a scalar or scalarref
|
||||
# returns: nothing if given a scalarref (to compress in-place), or original/compressed value,
|
||||
# depending on site config.
|
||||
# </LJFUNC>
|
||||
sub text_compress
|
||||
{
|
||||
my $text = shift;
|
||||
my $ref = ref $text;
|
||||
return $ref ? undef : $text unless $LJ::COMPRESS_TEXT;
|
||||
die "Invalid reference" if $ref && $ref ne "SCALAR";
|
||||
|
||||
my $tref = $ref ? $text : \$text;
|
||||
my $pre_len = length($$tref);
|
||||
unless (substr($$tref,0,2) eq "\037\213" || $pre_len < 100) {
|
||||
my $gz = Compress::Zlib::memGzip($$tref);
|
||||
if (length($gz) < $pre_len) {
|
||||
$$tref = $gz;
|
||||
}
|
||||
}
|
||||
|
||||
return $ref ? undef : $$tref;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::text_uncompress
|
||||
# des: Uncompresses a chunk of text, from gzip, if configured for site. Can uncompress
|
||||
# a scalarref in place, or return a compressed copy. Won't uncompress unless
|
||||
# it finds the gzip magic number at the beginning of the text.
|
||||
# args: text
|
||||
# des-text: either a scalar or scalarref.
|
||||
# returns: nothing if given a scalarref (to uncompress in-place), or original/uncompressed value,
|
||||
# depending on if test was compressed or not
|
||||
# </LJFUNC>
|
||||
sub text_uncompress
|
||||
{
|
||||
my $text = shift;
|
||||
my $ref = ref $text;
|
||||
die "Invalid reference" if $ref && $ref ne "SCALAR";
|
||||
my $tref = $ref ? $text : \$text;
|
||||
|
||||
# check for gzip's magic number
|
||||
if (substr($$tref,0,2) eq "\037\213") {
|
||||
$$tref = Compress::Zlib::memGunzip($$tref);
|
||||
}
|
||||
|
||||
return $ref ? undef : $$tref;
|
||||
}
|
||||
|
||||
# function to trim a string containing HTML. this will auto-close any
|
||||
# html tags that were still open when the string was truncated
|
||||
sub html_trim {
|
||||
my ($text, $char_max) = @_;
|
||||
|
||||
return $text unless $char_max;
|
||||
|
||||
my $p = HTML::TokeParser->new(\$text);
|
||||
my @open_tags; # keep track of what tags are open
|
||||
my $out = '';
|
||||
my $content_len = 0;
|
||||
|
||||
TOKEN:
|
||||
while (my $token = $p->get_token) {
|
||||
my $type = $token->[0];
|
||||
my $tag = $token->[1];
|
||||
my $attr = $token->[2]; # hashref
|
||||
|
||||
if ($type eq "S") {
|
||||
my $selfclose;
|
||||
|
||||
# start tag
|
||||
$out .= "<$tag";
|
||||
|
||||
# assume tags are properly self-closed
|
||||
$selfclose = 1 if lc $tag eq 'input' || lc $tag eq 'br' || lc $tag eq 'img';
|
||||
|
||||
# preserve order of attributes. the original order is
|
||||
# in element 4 of $token
|
||||
foreach my $attrname (@{$token->[3]}) {
|
||||
if ($attrname eq '/') {
|
||||
$selfclose = 1;
|
||||
next;
|
||||
}
|
||||
|
||||
# FIXME: ultra ghetto.
|
||||
$attr->{$attrname} = LJ::no_utf8_flag($attr->{$attrname});
|
||||
$out .= " $attrname=\"" . LJ::ehtml($attr->{$attrname}) . "\"";
|
||||
}
|
||||
|
||||
$out .= $selfclose ? " />" : ">";
|
||||
|
||||
push @open_tags, $tag unless $selfclose;
|
||||
|
||||
} elsif ($type eq 'T' || $type eq 'D') {
|
||||
my $content = $token->[1];
|
||||
|
||||
if (length($content) + $content_len > $char_max) {
|
||||
|
||||
# truncate and stop parsing
|
||||
$content = LJ::text_trim($content, undef, ($char_max - $content_len));
|
||||
$out .= $content;
|
||||
last;
|
||||
}
|
||||
|
||||
$content_len += length $content;
|
||||
|
||||
$out .= $content;
|
||||
|
||||
} elsif ($type eq 'C') {
|
||||
# comment, don't care
|
||||
$out .= $token->[1];
|
||||
|
||||
} elsif ($type eq 'E') {
|
||||
# end tag
|
||||
pop @open_tags;
|
||||
$out .= "</$tag>";
|
||||
}
|
||||
}
|
||||
|
||||
$out .= join("\n", map { "</$_>" } reverse @open_tags);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
# takes a number, inserts commas where needed
|
||||
sub commafy {
|
||||
my $number = shift;
|
||||
return $number unless $number =~ /^\d+$/;
|
||||
|
||||
my $punc = LJ::Lang::ml('number.punctuation') || ",";
|
||||
$number =~ s/(?<=\d)(?=(\d\d\d)+(?!\d))/$punc/g;
|
||||
return $number;
|
||||
}
|
||||
|
||||
# <LJFUNC>
|
||||
# name: LJ::html_newlines
|
||||
# des: Replace newlines with HTML break tags.
|
||||
# args: text
|
||||
# returns: text, possibly including HTML break tags.
|
||||
# </LJFUNC>
|
||||
sub html_newlines
|
||||
{
|
||||
my $text = shift;
|
||||
$text =~ s/\n/<br \/>/gm;
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
# given HTML, returns an arrayref of URLs to images that are in the HTML
|
||||
sub html_get_img_urls {
|
||||
my $htmlref = shift;
|
||||
my %opts = @_;
|
||||
|
||||
my $exclude_site_imgs = $opts{exclude_site_imgs} || 0;
|
||||
|
||||
my @image_urls;
|
||||
my $p = HTML::TokeParser->new($htmlref);
|
||||
|
||||
while (my $token = $p->get_token) {
|
||||
if ($token->[1] eq "img") {
|
||||
my $attrs = $token->[2];
|
||||
foreach my $attr (keys %$attrs) {
|
||||
push @image_urls, $attrs->{$attr} if
|
||||
$attr eq "src" &&
|
||||
($exclude_site_imgs ? $attrs->{$attr} !~ /^$LJ::IMGPREFIX/ : 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \@image_urls;
|
||||
}
|
||||
|
||||
# given HTML, returns an arrayref of link URLs that are in the HTML
|
||||
sub html_get_link_urls {
|
||||
my $htmlref = shift;
|
||||
my %opts = @_;
|
||||
|
||||
my @link_urls;
|
||||
my $p = HTML::TokeParser->new($htmlref);
|
||||
|
||||
while (my $token = $p->get_token) {
|
||||
if ($token->[0] eq "S" && $token->[1] eq "a") {
|
||||
my $attrs = $token->[2];
|
||||
foreach my $attr (keys %$attrs) {
|
||||
push @link_urls, $attrs->{$attr} if $attr eq "href";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \@link_urls;
|
||||
}
|
||||
|
||||
1;
|
||||
2389
local/cgi-bin/ljviews.pl
Executable file
2389
local/cgi-bin/ljviews.pl
Executable file
File diff suppressed because it is too large
Load Diff
144
local/cgi-bin/modperl_subs.pl
Executable file
144
local/cgi-bin/modperl_subs.pl
Executable file
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
# to be require'd by modperl.pl
|
||||
|
||||
use strict;
|
||||
|
||||
package LJ;
|
||||
|
||||
use Apache;
|
||||
use Apache::LiveJournal;
|
||||
use Apache::CompressClientFixup;
|
||||
use Apache::BML;
|
||||
use Apache::SendStats;
|
||||
use Apache::DebateSuicide;
|
||||
|
||||
use Digest::MD5;
|
||||
use MIME::Words;
|
||||
use Text::Wrap ();
|
||||
use LWP::UserAgent ();
|
||||
use Storable;
|
||||
use Time::HiRes ();
|
||||
use Image::Size ();
|
||||
use POSIX ();
|
||||
|
||||
use GD::Simple;
|
||||
|
||||
use LJ::SpellCheck;
|
||||
use LJ::TextMessage;
|
||||
use LJ::Blob;
|
||||
use LJ::Captcha;
|
||||
use LJ::OpenID;
|
||||
use MogileFS;
|
||||
#use MogileFS qw(+preload);
|
||||
use DDLockClient ();
|
||||
|
||||
# Try to load GTop library
|
||||
BEGIN { $LJ::HAVE_GTOP = eval "use GTop (); 1;" }
|
||||
|
||||
# Try to load DBI::Profile
|
||||
BEGIN { $LJ::HAVE_DBI_PROFILE = eval "use DBI::Profile (); 1;" }
|
||||
|
||||
require "$ENV{'LJHOME'}/cgi-bin/ljlang.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/ljpoll.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/htmlcontrols.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/weblib.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/imageconf.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/propparse.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/supportlib.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/cleanhtml.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/portal.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/talklib.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/ljtodo.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/ljfeed.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/ljlinks.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/directorylib.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/emailcheck.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/ljmemories.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/ljmail.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/sysban.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/synlib.pl";
|
||||
require "$ENV{'LJHOME'}/cgi-bin/communitylib.pl";
|
||||
|
||||
require "taglib.pl";
|
||||
|
||||
# preload site-local libraries, if present:
|
||||
require "$ENV{'LJHOME'}/cgi-bin/modperl_subs-local.pl"
|
||||
if -e "$ENV{'LJHOME'}/cgi-bin/modperl_subs-local.pl";
|
||||
|
||||
$LJ::IMGPREFIX_BAK = $LJ::IMGPREFIX;
|
||||
$LJ::STATPREFIX_BAK = $LJ::STATPREFIX;
|
||||
|
||||
package LJ::ModPerl;
|
||||
|
||||
# pull in a lot of useful stuff before we fork children
|
||||
|
||||
sub setup_start {
|
||||
|
||||
# auto-load some stuff before fork:
|
||||
Storable::thaw(Storable::nfreeze({}));
|
||||
foreach my $minifile ("GIF89a", "\x89PNG\x0d\x0a\x1a\x0a", "\xFF\xD8") {
|
||||
Image::Size::imgsize(\$minifile);
|
||||
}
|
||||
DBI->install_driver("mysql");
|
||||
LJ::CleanHTML::helper_preload();
|
||||
|
||||
# set this before we fork
|
||||
$LJ::CACHE_CONFIG_MODTIME = (stat("$ENV{'LJHOME'}/cgi-bin/ljconfig.pl"))[9];
|
||||
|
||||
my $img = GD::Simple->new(0,0);
|
||||
$img->read_color_table();
|
||||
|
||||
eval { setup_start_local(); };
|
||||
}
|
||||
|
||||
sub setup_restart {
|
||||
|
||||
# setup httpd.conf things for the user:
|
||||
Apache->httpd_conf("DocumentRoot $LJ::HTDOCS")
|
||||
if $LJ::HTDOCS;
|
||||
Apache->httpd_conf("ServerAdmin $LJ::ADMIN_EMAIL")
|
||||
if $LJ::ADMIN_EMAIL;
|
||||
|
||||
Apache->httpd_conf(qq{
|
||||
|
||||
# This interferes with LJ's /~user URI, depending on the module order
|
||||
<IfModule mod_userdir.c>
|
||||
UserDir disabled
|
||||
</IfModule>
|
||||
|
||||
PerlInitHandler Apache::LiveJournal
|
||||
PerlInitHandler Apache::SendStats
|
||||
PerlFixupHandler Apache::CompressClientFixup
|
||||
PerlCleanupHandler Apache::SendStats
|
||||
PerlCleanupHandler Apache::DebateSuicide
|
||||
PerlChildInitHandler Apache::SendStats
|
||||
DirectoryIndex index.html index.bml
|
||||
});
|
||||
|
||||
if ($LJ::BML_DENY_CONFIG) {
|
||||
Apache->httpd_conf("PerlSetVar BML_denyconfig \"$LJ::BML_DENY_CONFIG\"\n");
|
||||
}
|
||||
|
||||
unless ($LJ::SERVER_TOTALLY_DOWN)
|
||||
{
|
||||
Apache->httpd_conf(qq{
|
||||
# BML support:
|
||||
<Files ~ "\\.bml\$">
|
||||
SetHandler perl-script
|
||||
PerlHandler Apache::BML
|
||||
</Files>
|
||||
|
||||
# User-friendly error messages
|
||||
ErrorDocument 404 /404-error.html
|
||||
ErrorDocument 500 /500-error.html
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setup_start();
|
||||
|
||||
1;
|
||||
495
local/cgi-bin/parsefeed.pl
Executable file
495
local/cgi-bin/parsefeed.pl
Executable file
@@ -0,0 +1,495 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
|
||||
package LJ::ParseFeed;
|
||||
|
||||
use XML::RSS;
|
||||
use XML::Parser;
|
||||
|
||||
|
||||
# parse_feed parses an RSS/Atom feed
|
||||
# arguments: content and, optionally, type, specifying "atom" or
|
||||
# "rss". If type isn't supplied, the function will try to guess it
|
||||
# based on contents.
|
||||
# It returns $feed, which is a hash
|
||||
# with the following keys:
|
||||
# type - 'atom' or 'rss'
|
||||
# version - version of the feed in its standard
|
||||
# link - URL of the feed
|
||||
# title - title of the feed
|
||||
# description - description of the feed
|
||||
# # TODO: more kinds of info?
|
||||
#
|
||||
# items - arrayref of item hashes, in the same order they were in the feed
|
||||
# each item contains:
|
||||
# link - URL of the item
|
||||
# id - unique identifier (optional)
|
||||
# text - text of the item
|
||||
# subject - subject
|
||||
# time - in format 'yyyy-mm-dd hh:mm' (optional)
|
||||
# the second argument returned is $error, which, if defined, is a human-readable
|
||||
# error string. the third argument is arrayref of items, same as
|
||||
# $feed->{'items'}.
|
||||
|
||||
sub parse_feed
|
||||
{
|
||||
my ($content, $type) = @_;
|
||||
my ($feed, $items, $error);
|
||||
my $parser;
|
||||
|
||||
# is it RSS or Atom?
|
||||
# Atom feeds are rare for now, so prefer to err in favor of RSS
|
||||
# simple heuristic: Atom feeds will have '<feed' somewhere at the beginning
|
||||
# TODO: maybe store the feed's type on creation in a userprop and not guess here
|
||||
|
||||
my $cut = substr($content, 0, 255);
|
||||
if ($type eq 'atom' || $cut =~ m!\<feed!) {
|
||||
# try treating it as an atom feed
|
||||
$parser = new XML::Parser(Style=>'Stream', Pkg=>'LJ::ParseFeed::Atom');
|
||||
return ("", "failed to create XML parser") unless $parser;
|
||||
eval {
|
||||
$parser->parse($content);
|
||||
};
|
||||
if ($@) {
|
||||
$error = "XML parser error: $@";
|
||||
} else {
|
||||
($feed, $items, $error) = LJ::ParseFeed::Atom::results();
|
||||
};
|
||||
|
||||
if ($feed || $type eq 'atom') {
|
||||
# there was a top-level <feed> there, or we're forced to treat
|
||||
# as an Atom feed, so even if $error is set,
|
||||
# don't try RSS
|
||||
$feed->{'type'} = 'atom';
|
||||
return ($feed, $error, $items);
|
||||
}
|
||||
}
|
||||
|
||||
# try parsing it as RSS
|
||||
$parser = new XML::RSS;
|
||||
return ("", "failed to create RSS parser") unless $parser;
|
||||
eval {
|
||||
$parser->parse($content);
|
||||
};
|
||||
if ($@) {
|
||||
$error = "RSS parser error: $@";
|
||||
return ("", $error);
|
||||
}
|
||||
|
||||
$feed = {};
|
||||
$feed->{'type'} = 'rss';
|
||||
$feed->{'version'} = $parser->{'version'};
|
||||
|
||||
foreach (qw (link title description lastBuildDate)) {
|
||||
$feed->{$_} = $parser->{'channel'}->{$_}
|
||||
if $parser->{'channel'}->{$_};
|
||||
}
|
||||
|
||||
if ($parser->{'image'}->{'url'}) {
|
||||
$feed->{'image'} = $parser->{'image'}->{'url'};
|
||||
}
|
||||
|
||||
|
||||
$feed->{'lastmod'} = undef;
|
||||
$feed->{'items'} = [];
|
||||
|
||||
foreach(@{$parser->{'items'}}) {
|
||||
my $item = {};
|
||||
$item->{'subject'} = $_->{'title'};
|
||||
$item->{'text'} = $_->{'description'};
|
||||
$item->{'link'} = $_->{'link'} if $_->{'link'};
|
||||
$item->{'id'} = $_->{'guid'} if $_->{'guid'};
|
||||
|
||||
my $nsdc = 'http://purl.org/dc/elements/1.1/';
|
||||
my $nsenc = 'http://purl.org/rss/1.0/modules/content/';
|
||||
if ($_->{$nsenc} && ref($_->{$nsenc}) eq "HASH") {
|
||||
# prefer content:encoded if present
|
||||
$item->{'text'} = $_->{$nsenc}->{'encoded'}
|
||||
if defined $_->{$nsenc}->{'encoded'};
|
||||
}
|
||||
|
||||
if ($_->{'pubDate'}) {
|
||||
my $time = time822_to_time($_->{'pubDate'});
|
||||
$item->{'time'} = $time if $time;
|
||||
}
|
||||
if ($_->{$nsdc} && ref($_->{$nsdc}) eq "HASH") {
|
||||
if ($_->{$nsdc}->{date}) {
|
||||
my $time = w3cdtf_to_time($_->{$nsdc}->{date});
|
||||
$item->{'time'} = $time if $time;
|
||||
}
|
||||
}
|
||||
if ($_->{'pubDate'} && (! $feed->{'lastmod'} ||
|
||||
(LJ::http_to_time($feed->{'lastmod'}) < LJ::http_to_time($_->{'pubDate'})))) {
|
||||
$feed->{'lastmod'} = $_->{'pubDate'};
|
||||
}
|
||||
|
||||
push @{$feed->{'items'}}, $item;
|
||||
}
|
||||
|
||||
return ($feed, undef, $feed->{'items'});
|
||||
}
|
||||
|
||||
# convert rfc822-time in RSS's <pubDate> to our time
|
||||
# see http://www.faqs.org/rfcs/rfc822.html
|
||||
# RFC822 specifies 2 digits for year, and RSS2.0 refers to RFC822,
|
||||
# but real RSS2.0 feeds apparently use 4 digits.
|
||||
sub time822_to_time {
|
||||
my $t822 = shift;
|
||||
# remove day name if present
|
||||
$t822 =~ s/^\s*\w+\s*,//;
|
||||
# remove whitespace
|
||||
$t822 =~ s/^\s*//;
|
||||
# break it up
|
||||
if ($t822 =~ m!(\d?\d)\s+(\w+)\s+(\d\d\d\d)\s+(\d?\d):(\d\d)!) {
|
||||
my ($day, $mon, $year, $hour, $min) = ($1,$2,$3,$4,$5);
|
||||
$day = "0" . $day if length($day) == 1;
|
||||
$hour = "0" . $hour if length($hour) == 1;
|
||||
$mon = {'Jan'=>'01', 'Feb'=>'02', 'Mar'=>'03', 'Apr'=>'04',
|
||||
'May'=>'05', 'Jun'=>'06', 'Jul'=>'07', 'Aug'=>'08',
|
||||
'Sep'=>'09', 'Oct'=>'10', 'Nov'=>'11', 'Dec'=>'12'}->{$mon};
|
||||
return undef unless $mon;
|
||||
return "$year-$mon-$day $hour:$min";
|
||||
} else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
# convert W3C-DTF to our internal format
|
||||
# see http://www.w3.org/TR/NOTE-datetime
|
||||
# Based very loosely on code from DateTime::Format::W3CDTF,
|
||||
# which isn't stable yet so we can't use it directly.
|
||||
sub w3cdtf_to_time {
|
||||
my $tw3 = shift;
|
||||
|
||||
# TODO: Should somehow return the timezone offset
|
||||
# so that it can stored... but we don't do timezones
|
||||
# yet anyway. For now, just strip the timezone
|
||||
# portion if it is present, along with the decimal
|
||||
# fractions of a second.
|
||||
|
||||
$tw3 =~ s/(?:\.\d+)?(?:[+-]\d{1,2}:\d{1,2}|Z)$//;
|
||||
$tw3 =~ s/^\s*//; $tw3 =~ s/\s*$//; # Eat any superflous whitespace
|
||||
|
||||
# We can only use complete times, so anything which
|
||||
# doesn't feature the time part is considered invalid.
|
||||
|
||||
# This is working around clients that don't implement W3C-DTF
|
||||
# correctly, and only send single digit values in the dates.
|
||||
# 2004-4-8T16:9:4Z vs 2004-04-08T16:09:44Z
|
||||
# If it's more messed up than that, reject it outright.
|
||||
$tw3 =~ /^(\d{4})-(\d{1,2})-(\d{1,2})T(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/
|
||||
or return undef;
|
||||
|
||||
my %pd; # parsed date
|
||||
$pd{Y} = $1; $pd{M} = $2; $pd{D} = $3;
|
||||
$pd{h} = $4; $pd{m} = $5; $pd{s} = $6;
|
||||
|
||||
# force double digits
|
||||
foreach (qw/ M D h m s /) {
|
||||
next unless defined $pd{$_};
|
||||
$pd{$_} = sprintf "%02d", $pd{$_};
|
||||
}
|
||||
|
||||
return $pd{s} ? "$pd{Y}-$pd{M}-$pd{D} $pd{h}:$pd{m}:$pd{s}" :
|
||||
"$pd{Y}-$pd{M}-$pd{D} $pd{h}:$pd{m}";
|
||||
}
|
||||
|
||||
package LJ::ParseFeed::Atom;
|
||||
|
||||
our ($feed, $item, $data);
|
||||
our ($ddepth, $dholder); # for accumulating;
|
||||
our @items;
|
||||
our $error;
|
||||
|
||||
sub err {
|
||||
$error = shift unless $error;
|
||||
}
|
||||
|
||||
sub results {
|
||||
return ($feed, \@items, $error);
|
||||
}
|
||||
|
||||
# $name under which we'll store accumulated data may be different
|
||||
# from $tag which causes us to store it
|
||||
# $name may be a scalarref pointing to where we should store
|
||||
# swallowing is achieved by calling startaccum('');
|
||||
|
||||
sub startaccum {
|
||||
my $name = shift;
|
||||
|
||||
return err("Tag found under neither <feed> nor <entry>")
|
||||
unless $feed || $item;
|
||||
$data = ""; # defining $data triggers accumulation
|
||||
$ddepth = 1;
|
||||
|
||||
$dholder = undef
|
||||
unless $name;
|
||||
# if $name is a scalarref, it's actually our $dholder
|
||||
if (ref($name) eq 'SCALAR') {
|
||||
$dholder = $name;
|
||||
} else {
|
||||
$dholder = ($item ? \$item->{$name} : \$feed->{$name})
|
||||
if $name;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub swallow {
|
||||
return startaccum('');
|
||||
}
|
||||
|
||||
sub StartDocument {
|
||||
($feed, $item, $data) = (undef, undef, undef);
|
||||
@items = ();
|
||||
undef $error;
|
||||
}
|
||||
|
||||
sub StartTag {
|
||||
# $_ carries the unparsed tag
|
||||
my ($p, $tag) = @_;
|
||||
my $holder;
|
||||
|
||||
# do nothing if there has been an error
|
||||
return if $error;
|
||||
|
||||
# are we just accumulating data?
|
||||
if (defined $data) {
|
||||
$data .= $_;
|
||||
$ddepth++;
|
||||
return;
|
||||
}
|
||||
|
||||
# where we'll usually store info
|
||||
$holder = $item ? $item : $feed;
|
||||
|
||||
TAGS: {
|
||||
if ($tag eq 'feed') {
|
||||
return err("Nested <feed> tags")
|
||||
if $feed;
|
||||
$feed = {};
|
||||
$feed->{'standard'} = 'atom';
|
||||
$feed->{'version'} = $_{'version'};
|
||||
# return err("No version specified in <feed>")
|
||||
# unless $feed->{'version'};
|
||||
# commented out as it's done in
|
||||
# http://code.livejournal.org/trac/livejournal/browser/trunk/cgi-bin/parsefeed.pl
|
||||
# (too many sites return this error now)
|
||||
return err("Incompatible version specified in <feed>")
|
||||
if $feed->{'version'} && $feed->{'version'} < 0.3;
|
||||
last TAGS;
|
||||
}
|
||||
if ($tag eq 'entry') {
|
||||
return err("Nested <entry> tags")
|
||||
if $item;
|
||||
$item = {};
|
||||
last TAGS;
|
||||
}
|
||||
|
||||
# at this point, we must have a top-level <feed> or <entry>
|
||||
# to write into
|
||||
return err("Tag found under neither <feed> nor <entry>")
|
||||
unless $holder;
|
||||
|
||||
if ($tag eq 'link') {
|
||||
# ignore links with rel= anything but alternate
|
||||
unless ($_{'rel'} eq 'alternate') {
|
||||
swallow();
|
||||
last TAGS;
|
||||
}
|
||||
$holder->{'link'} = $_{'href'};
|
||||
return err("No href attribute in <link>")
|
||||
unless $holder->{'link'};
|
||||
last TAGS;
|
||||
}
|
||||
|
||||
if ($tag eq 'content') {
|
||||
return err("<content> outside <entry>")
|
||||
unless $item;
|
||||
# if type is multipart/alternative, we continue recursing
|
||||
# otherwise we accumulate
|
||||
my $type = $_{'type'} || "text/plain";
|
||||
unless ($type eq "multipart/alternative") {
|
||||
push @{$item->{'contents'}}, [$type, ""];
|
||||
startaccum(\$item->{'contents'}->[-1]->[1]);
|
||||
last TAGS;
|
||||
}
|
||||
# it's multipart/alternative, so recurse, but don't swallow
|
||||
last TAGS;
|
||||
}
|
||||
|
||||
# store tags which should require no further
|
||||
# processing as they are, and others under _atom_*, to be processed
|
||||
# in EndTag under </entry>
|
||||
if ($tag eq 'title') {
|
||||
if ($item) { # entry's subject
|
||||
startaccum("subject");
|
||||
} else { # feed's title
|
||||
startaccum($tag);
|
||||
}
|
||||
last TAGS;
|
||||
}
|
||||
if ($tag eq 'id') {
|
||||
unless ($item) {
|
||||
swallow(); # we don't need feed-level <id>
|
||||
} else {
|
||||
startaccum($tag);
|
||||
}
|
||||
last TAGS;
|
||||
}
|
||||
|
||||
if ($tag eq 'tagline' && !$item) { # feed's tagline, our "description"
|
||||
startaccum("description");
|
||||
last TAGS;
|
||||
}
|
||||
|
||||
# accumulate and store
|
||||
startaccum("_atom_" . $tag);
|
||||
last TAGS;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub EndTag {
|
||||
# $_ carries the unparsed tag
|
||||
my ($p, $tag) = @_;
|
||||
|
||||
# do nothing if there has been an error
|
||||
return if $error;
|
||||
|
||||
# are we accumulating data?
|
||||
if (defined $data) {
|
||||
$ddepth--;
|
||||
if ($ddepth == 0) { # stop accumulating
|
||||
$$dholder = $data
|
||||
if $dholder;
|
||||
undef $data;
|
||||
return;
|
||||
}
|
||||
$data .= $_;
|
||||
return;
|
||||
}
|
||||
|
||||
TAGS: {
|
||||
if ($tag eq 'entry') {
|
||||
# finalize item...
|
||||
# generate suitable text from $item->{'contents'}
|
||||
my $content;
|
||||
$item->{'contents'} ||= [];
|
||||
unless (scalar(@{$item->{'contents'}}) >= 1) {
|
||||
# this item had no <content>
|
||||
# maybe it has <summary>? if so, use <summary>
|
||||
# TODO: type= or encoding issues here? perhaps unite
|
||||
# handling of <summary> with that of <content>?
|
||||
if ($item->{'_atom_summary'}) {
|
||||
$item->{'text'} = $item->{'_atom_summary'};
|
||||
delete $item->{'contents'};
|
||||
} else {
|
||||
# nothing to display, so ignore this entry
|
||||
undef $item;
|
||||
last TAGS;
|
||||
}
|
||||
}
|
||||
|
||||
unless ($item->{'text'}) { # unless we already have text
|
||||
if (scalar(@{$item->{'contents'}}) == 1) {
|
||||
# only one <content> section
|
||||
$content = $item->{'contents'}->[0];
|
||||
} else {
|
||||
# several <content> section, must choose the best one
|
||||
foreach (@{$item->{'contents'}}) {
|
||||
if ($_->[0] eq "application/xhtml+xml") { # best match
|
||||
$content = $_;
|
||||
last; # don't bother to look at others
|
||||
}
|
||||
if ($_->[0] =~ m!html!) { # some kind of html/xhtml/html+xml, etc.
|
||||
# choose this unless we've already chosen some html
|
||||
$content = $_
|
||||
unless $content->[0] =~ m!html!;
|
||||
next;
|
||||
}
|
||||
if ($_->[0] eq "text/plain") {
|
||||
# choose this unless we have some html already
|
||||
$content = $_
|
||||
unless $content->[0] =~ m!html!;
|
||||
next;
|
||||
}
|
||||
}
|
||||
# if we didn't choose anything, pick the first one
|
||||
$content = $item->{'contents'}->[0]
|
||||
unless $content;
|
||||
}
|
||||
|
||||
# we ignore the 'mode' attribute of <content>. If it's "xml", we've
|
||||
# stringified it by accumulation; if it's "escaped", our parser
|
||||
# unescaped it
|
||||
# TODO: handle mode=base64?
|
||||
|
||||
$item->{'text'} = $content->[1];
|
||||
delete $item->{'contents'};
|
||||
}
|
||||
|
||||
# generate time
|
||||
my $w3time = $item->{'_atom_modified'} || $item->{'_atom_created'};
|
||||
my $time;
|
||||
if ($w3time) {
|
||||
# see http://www.w3.org/TR/NOTE-datetime for format
|
||||
# we insist on having granularity up to a minute,
|
||||
# and ignore finer data as well as the timezone, for now
|
||||
if ($w3time =~ m!^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d)!) {
|
||||
$time = "$1-$2-$3 $4:$5";
|
||||
}
|
||||
}
|
||||
if ($time) {
|
||||
$item->{'time'} = $time;
|
||||
}
|
||||
|
||||
# get rid of all other tags we don't need anymore
|
||||
foreach (keys %$item) {
|
||||
delete $item->{$_} if substr($_, 0, 6) eq '_atom_';
|
||||
}
|
||||
|
||||
push @items, $item;
|
||||
undef $item;
|
||||
last TAGS;
|
||||
}
|
||||
if ($tag eq 'feed') {
|
||||
# finalize feed
|
||||
# get rid of all other tags we don't need anymore
|
||||
foreach (keys %$feed) {
|
||||
delete $feed->{$_} if substr($_, 0, 6) eq '_atom_';
|
||||
}
|
||||
|
||||
# link the feed with its itms
|
||||
$feed->{'items'} = \@items
|
||||
if $feed;
|
||||
last TAGS;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub Text {
|
||||
my $p = shift;
|
||||
|
||||
# do nothing if there has been an error
|
||||
return if $error;
|
||||
|
||||
$data .= $_ if defined $data;
|
||||
}
|
||||
|
||||
sub PI {
|
||||
# ignore processing instructions
|
||||
return;
|
||||
}
|
||||
|
||||
sub EndDocument {
|
||||
# if we parsed a feed, link items to it
|
||||
$feed->{'items'} = \@items
|
||||
if $feed;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
321
local/cgi-bin/phonepost.pl
Executable file
321
local/cgi-bin/phonepost.pl
Executable file
@@ -0,0 +1,321 @@
|
||||
#!/usr/bin/perl
|
||||
# vim: set ts=4 sw=4 et :
|
||||
|
||||
# some variable names:
|
||||
# - phonepostid, bid, blobid: all refer to a blob id.
|
||||
# - dppid: display phonepost id; comparable to a ditemid, but for a phone post.
|
||||
|
||||
use strict;
|
||||
|
||||
use lib "$ENV{'LJHOME'}/cgi-bin";
|
||||
use LJ::Blob;
|
||||
|
||||
package LJ::PhonePost;
|
||||
|
||||
my $datatypes = {
|
||||
0 => { ext => 'mp3', mime => 'audio/mp3' },
|
||||
1 => { ext => 'ogg', mime => 'application/ogg' },
|
||||
2 => { ext => 'wav', mime => 'audio/wav' },
|
||||
};
|
||||
|
||||
sub may_transcribe {
|
||||
my ($u, $remote) = @_;
|
||||
return 0 if $remote && $remote->{journaltype} ne 'P';
|
||||
return 1 if $remote && $remote->{userid} == $u->{userid};
|
||||
LJ::load_user_props($u, 'pp_transallow');
|
||||
return 0 if $u->{pp_transallow} == -1;
|
||||
my $groupmask = LJ::get_groupmask($u, $remote);
|
||||
return 1 if ! $u->{pp_transallow} && $groupmask;
|
||||
return ($groupmask & (1 << $u->{pp_transallow})) ? 1 : 0;
|
||||
}
|
||||
|
||||
sub get_phonepost_entry {
|
||||
my ($u, $bid) = @_;
|
||||
my ($ppe, $memkey);
|
||||
|
||||
$memkey = [$u->{userid}, "ppe:$u->{userid}:$bid"];
|
||||
$ppe = LJ::MemCache::get($memkey);
|
||||
|
||||
return $ppe if $ppe;
|
||||
|
||||
my $dbcr = LJ::get_cluster_def_reader($u);
|
||||
$ppe = $dbcr->selectrow_hashref(qq{
|
||||
SELECT ppe.jitemid, ppe.posttime, ppe.anum,
|
||||
ppe.filetype, ub.length, ppe.lengthsecs, ppe.location
|
||||
FROM phonepostentry ppe, userblob ub
|
||||
WHERE ub.journalid=? AND ppe.userid=ub.journalid
|
||||
AND ub.domain=? AND ub.blobid=? AND ppe.blobid=ub.blobid
|
||||
}, undef, $u->{'userid'}, LJ::get_blob_domainid("phonepost"), $bid);
|
||||
LJ::MemCache::set($memkey, $ppe || 0);
|
||||
return $ppe;
|
||||
}
|
||||
|
||||
sub apache_content {
|
||||
my ($r, $u, $dppid) = @_;
|
||||
my $bid = $dppid >> 8;
|
||||
my $ppe = get_phonepost_entry($u, $bid);
|
||||
return 404 unless $ppe && $ppe->{jitemid} && $ppe->{anum} == $dppid % 256;
|
||||
|
||||
# check security of item
|
||||
my $logrow = LJ::get_log2_row($u, $ppe->{jitemid});
|
||||
return 404 unless $logrow;
|
||||
|
||||
if ($u->{statusvis} eq 'S' || $logrow->{security} ne "public") {
|
||||
# get the remote, ignoring IP, since the request is coming
|
||||
# from Akamai/Speedera/etc and the IP won't match
|
||||
my $remote = LJ::get_remote({ ignore_ip => 1 });
|
||||
|
||||
my %GET = $r->args;
|
||||
|
||||
my $viewall = 0;
|
||||
my $viewsome = 0;
|
||||
if ($remote && $GET{viewall} && LJ::check_priv($remote, "canview")) {
|
||||
LJ::statushistory_add($u->{'userid'}, $remote->{'userid'},
|
||||
"viewall", "phonepost: $u->{user}, itemid: $ppe->{jitemid}, statusvis: $u->{'statusvis'}");
|
||||
|
||||
$viewall = LJ::check_priv($remote, 'canview', '*');
|
||||
$viewsome = $viewall || LJ::check_priv($remote, 'canview', 'suspended');
|
||||
}
|
||||
|
||||
unless ($viewall || $viewsome && $logrow->{security} eq 'public') {
|
||||
return 403 unless LJ::can_view($remote, $logrow);
|
||||
}
|
||||
}
|
||||
|
||||
# future: if length is NULL, then it's an external reference and we redirect
|
||||
$r->header_out("Cache-Control", "must-revalidate, private");
|
||||
$r->header_out("Content-Length", $ppe->{length});
|
||||
$r->content_type( $datatypes->{ $ppe->{filetype} }->{mime} );
|
||||
|
||||
# handle IMS requests
|
||||
my $last_mod = LJ::time_to_http($ppe->{posttime});
|
||||
if (my $ims = $r->header_in("If-Modified-Since")) {
|
||||
return 304 if $ims eq $last_mod;
|
||||
}
|
||||
|
||||
$r->header_out("Last-Modified", $last_mod);
|
||||
if ($r->header_only) {
|
||||
$r->send_http_header();
|
||||
return 200;
|
||||
}
|
||||
|
||||
my $buffer;
|
||||
if ($ppe->{location} eq 'mogile') {
|
||||
# Mogile
|
||||
if ( !$LJ::REPROXY_DISABLE{phoneposts} &&
|
||||
$r->header_in('X-Proxy-Capabilities') &&
|
||||
$r->header_in('X-Proxy-Capabilities') =~ /\breproxy-file\b/i )
|
||||
{
|
||||
my @paths = LJ::mogclient()->get_paths( "pp:$u->{userid}:$bid", 1 );
|
||||
|
||||
# reproxy url
|
||||
if ($paths[0] =~ m/^http:/) {
|
||||
$r->header_out('X-REPROXY-URL', join(' ', @paths));
|
||||
}
|
||||
# reproxy file
|
||||
else {
|
||||
$r->header_out('X-REPROXY-FILE', $paths[0]);
|
||||
}
|
||||
$r->send_http_header();
|
||||
}
|
||||
else {
|
||||
$buffer = LJ::mogclient()->get_file_data("pp:$u->{userid}:$bid");
|
||||
$r->send_http_header();
|
||||
return 500 unless $buffer && ref $buffer;
|
||||
$r->print($$buffer);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
# BlobServer
|
||||
$r->send_http_header();
|
||||
my $ret = LJ::Blob::get_stream($u, 'phonepost',
|
||||
$datatypes->{ $ppe->{filetype} }->{ext}, $bid, sub {
|
||||
$buffer .= $_[0];
|
||||
if (length($buffer) > 50_000) {
|
||||
$r->print($buffer);
|
||||
undef $buffer;
|
||||
}
|
||||
});
|
||||
$r->print($buffer) if length($buffer);
|
||||
return 500 unless $ret;
|
||||
}
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
# if $u_embed and $ditemid are given, they represent an entry
|
||||
# in which a phonepost tag has been embedded.
|
||||
|
||||
sub make_link {
|
||||
my ($remote, $uuid, $phonepostid, $mode, $u_embed, $ditemid) = @_;
|
||||
$phonepostid += 0;
|
||||
|
||||
# mode can either be 'notrans', 'bare', or 'rss'
|
||||
$mode = "notrans" if $mode && $mode !~ /bare|rss/;
|
||||
|
||||
my $u = ref $uuid ? $uuid : LJ::load_userid($uuid);
|
||||
return $mode eq 'rss' ? "" : "<b>[Invalid user]</b>" unless $u;
|
||||
my $userid = $u->{'userid'};
|
||||
|
||||
my $ppe = get_phonepost_entry($u, $phonepostid);
|
||||
return $mode eq 'rss' ? "" : "<b>[Invalid audio link]</b>" unless $ppe;
|
||||
|
||||
if ($u_embed && $ditemid) {
|
||||
# have to check whether the link is embeddable in this entry
|
||||
if ($u_embed->{'userid'} == $userid &&
|
||||
$ditemid>>8 == $ppe->{'jitemid'}) {
|
||||
|
||||
# it's the original entry, we're ok
|
||||
} else {
|
||||
my $accdenied = "<b>[Access to audio link denied]</b>";
|
||||
|
||||
# log2 row in which the tag is embedded
|
||||
my $row = LJ::get_log2_row($u_embed, $ditemid >> 8);
|
||||
|
||||
# the original log2 row of this tag
|
||||
my $row_orig = LJ::get_log2_row($u, $ppe->{'jitemid'});
|
||||
return $mode eq 'rss' ? "" : $accdenied unless $row && $row_orig;
|
||||
|
||||
if ($row_orig->{'security'} eq "public" &&
|
||||
$row->{'posterid'} == $userid &&
|
||||
$u_embed->{'userid'} != $userid) {
|
||||
|
||||
# it's public and moved by the same
|
||||
# user to a different journal, we're okay
|
||||
} else {
|
||||
return $mode eq 'rss' ? "" : $accdenied;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $link;
|
||||
my $dppid = ($phonepostid << 8) + $ppe->{anum};
|
||||
my $ext = $datatypes->{ $ppe->{filetype} }->{ext};
|
||||
my $path = LJ::run_hook("url_phonepost", $u, $dppid, $ext) ||
|
||||
LJ::journal_base($u) . "/data/phonepost/$dppid.$ext";
|
||||
|
||||
# make link and just return that if in bare mode
|
||||
$link = $ppe->{location} eq 'none' ?
|
||||
"<img src='$LJ::IMGPREFIX/phonepost2.gif' alt='' width='35' height='33' />" :
|
||||
"<a href='$path'><img src='$LJ::IMGPREFIX/phonepost2.gif' alt='' " .
|
||||
"width='35' height='33' border='0' /></a>";
|
||||
return $link if $mode eq 'bare';
|
||||
|
||||
my $K = $ppe->{length} ? int($ppe->{length} / 1024) . "K" : "";
|
||||
my $secs = $ppe->{lengthsecs};
|
||||
my $duration = $secs ? sprintf("%d:%02d", int($secs/60), $secs%60) : "";
|
||||
|
||||
# support rss 'enclosures' - podcasting.
|
||||
if ($mode eq 'rss') {
|
||||
$link = "<enclosure url=\"$path\" length=\"$ppe->{length}\" " .
|
||||
"type=\"$datatypes->{ $ppe->{filetype} }->{mime}\" />";
|
||||
return $link;
|
||||
}
|
||||
|
||||
# return full table
|
||||
my $ret = "<table cellspacing='5' cellpadding='0' border='0' class='ljphonepost'><tr>";
|
||||
$ret .= "<td valign='top'>$link</td>";
|
||||
$ret .= "<td valign='top'><strong>";
|
||||
$ret .= $ppe->{location} eq 'none' ? "PhonePost" : "<a href='$path'>PhonePost</a>";
|
||||
$ret .= "</strong><br /><em>$K</em> $duration</td>";
|
||||
|
||||
unless ($mode eq 'notrans') {
|
||||
my $trans_url = "$LJ::SITEROOT/phonepost/transcribe.bml?user=$u->{user}&ppid=$dppid";
|
||||
$ret .= "<td valign='top'><a href='$LJ::SITEROOT/phonepost/about.bml'><img src='$LJ::IMGPREFIX/help.gif' width='14' height='14' border='0' alt='(Help)' /></a></td>";
|
||||
my $trans = LJ::PhonePost::get_latest_trans($u, $phonepostid);
|
||||
if ($trans) {
|
||||
my $by;
|
||||
if ($trans->{revid} == 1) {
|
||||
$by = LJ::ljuser(LJ::get_username($trans->{posterid}));
|
||||
} else {
|
||||
# multiple users transcribing, or just multiple transcriptions of one user?
|
||||
my $memkey = [$u->{userid},"ppetu:$u->{userid}:$phonepostid"];
|
||||
my $tu = LJ::MemCache::get($memkey);
|
||||
unless (defined $tu) {
|
||||
my $dbr = LJ::get_cluster_reader($u);
|
||||
$tu = $dbr->selectrow_array("SELECT COUNT(DISTINCT(posterid)) " .
|
||||
"FROM phoneposttrans " .
|
||||
"WHERE journalid=? AND blobid=?",
|
||||
undef, $u->{'userid'}, $phonepostid + 0);
|
||||
LJ::MemCache::set($memkey, $tu);
|
||||
}
|
||||
$by = ($tu == 1) ? LJ::ljuser(LJ::get_username($trans->{posterid})) : "multiple users";
|
||||
}
|
||||
my $text = LJ::ehtml($trans->{body});
|
||||
$text =~ s/\n/<br \/>/g;
|
||||
|
||||
$ret .= "<td valign='top'><blockquote cite='$path'>“$text”</blockquote><br />".
|
||||
"<a href='$trans_url'>Transcribed</a> by: $by</td>";
|
||||
} elsif (LJ::PhonePost::may_transcribe($u, $remote)) {
|
||||
$ret .= "<td valign='top'>(<a href='$trans_url'>transcribe</a>)</td>";
|
||||
} else {
|
||||
$ret .= "<td valign='top'>(no transcription available)</td>";
|
||||
}
|
||||
}
|
||||
|
||||
$ret .= "</tr></table>";
|
||||
return $ret;
|
||||
|
||||
}
|
||||
|
||||
sub get_latest_trans {
|
||||
my ($u, $id) = @_;
|
||||
$id += 0;
|
||||
|
||||
my $memkey = [$u->{userid},"ppelt:$u->{userid}:$id"];
|
||||
my $lt = LJ::MemCache::get($memkey);
|
||||
unless (defined $lt) {
|
||||
my $dbcr = LJ::get_cluster_def_reader($u);
|
||||
return undef unless $dbcr;
|
||||
|
||||
$lt = "";
|
||||
my $latest = $dbcr->selectrow_array("SELECT MAX(revid) FROM phoneposttrans ".
|
||||
"WHERE journalid=? AND blobid=?", undef,
|
||||
$u->{userid}, $id);
|
||||
if ($latest) {
|
||||
$lt = $dbcr->selectrow_hashref("SELECT revid,posterid,posttime,subject,body ".
|
||||
"FROM phoneposttrans WHERE journalid=? ".
|
||||
"AND blobid=? AND revid=?", undef,
|
||||
$u->{userid}, $id, $latest);
|
||||
}
|
||||
LJ::MemCache::set($memkey, $lt);
|
||||
}
|
||||
return $lt;
|
||||
}
|
||||
|
||||
sub show_phoneposts {
|
||||
my ($u_embed, $ditemid, $remote, $eventref) = @_;
|
||||
|
||||
my $replace = sub {
|
||||
my $tag = shift;
|
||||
my ($user, $userid, $uobj, $phid, $blobid, $dpid);
|
||||
|
||||
# old tag <lj-phonepost user='foo' phonepostid='1' />
|
||||
# new tag <lj-phonepost journalid='1234567' dpid='10000422' />
|
||||
if ($tag =~ m!^journalid=['"](\d+)['"]\s*dpid=['"](\d+)['"]\s*/?$!) {
|
||||
($userid, $dpid)=($1,$2);
|
||||
|
||||
# prefer phonepostid and userid
|
||||
$phid = $dpid >> 8 if $dpid;
|
||||
|
||||
} elsif ($tag =~ m!^(user=['"](\S+)['"])?\s*(phonepostid=['"](\d+)['"])?\s*(userid=['"](\d+)['"])?\s*(blobid=['"](\d+)['"])?\s*/?$!) {
|
||||
($user,$phid,$userid,$blobid)=($2,$4,$6,$8);
|
||||
|
||||
# prefer phonepostid and userid
|
||||
$phid = $blobid >> 8
|
||||
unless $phid or not $blobid;
|
||||
do {
|
||||
$uobj = LJ::load_user($user);
|
||||
$userid = $uobj->{'userid'};
|
||||
} unless $userid or $user eq "";
|
||||
}
|
||||
|
||||
return "<b>[Invalid audio link]</b>" unless $phid and $userid;
|
||||
|
||||
return make_link($remote, $userid, $phid, 0, $u_embed, $ditemid);
|
||||
};
|
||||
$$eventref =~ s!<lj-phonepost\s*([^>]*)>!$replace->($1)!eg;
|
||||
}
|
||||
|
||||
1;
|
||||
1193
local/cgi-bin/taglib.pl
Executable file
1193
local/cgi-bin/taglib.pl
Executable file
File diff suppressed because it is too large
Load Diff
3306
local/cgi-bin/talklib.pl
Executable file
3306
local/cgi-bin/talklib.pl
Executable file
File diff suppressed because it is too large
Load Diff
1870
local/cgi-bin/weblib.pl
Executable file
1870
local/cgi-bin/weblib.pl
Executable file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user