4460 lines
144 KiB
Perl
4460 lines
144 KiB
Perl
|
|
#!/usr/bin/perl
|
||
|
|
#
|
||
|
|
# This code was forked from the LiveJournal project owned and operated
|
||
|
|
# by Live Journal, Inc. The code has been modified and expanded by
|
||
|
|
# Dreamwidth Studios, LLC. These files were originally licensed under
|
||
|
|
# the terms of the license supplied by Live Journal, Inc, which can
|
||
|
|
# currently be found at:
|
||
|
|
#
|
||
|
|
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
|
||
|
|
#
|
||
|
|
# In accordance with the original license, this code and all its
|
||
|
|
# modifications are provided under the GNU General Public License.
|
||
|
|
# A copy of that license can be found in the LICENSE file included as
|
||
|
|
# part of this distribution.
|
||
|
|
|
||
|
|
use strict;
|
||
|
|
no warnings 'uninitialized';
|
||
|
|
|
||
|
|
use Digest::MD5;
|
||
|
|
use Encode ();
|
||
|
|
use SOAP::Lite ();
|
||
|
|
|
||
|
|
use LJ::Global::Constants;
|
||
|
|
use LJ::Console;
|
||
|
|
use LJ::Event::JournalNewEntry;
|
||
|
|
use LJ::Event::AddedToCircle;
|
||
|
|
use LJ::Entry;
|
||
|
|
use LJ::Poll;
|
||
|
|
use LJ::Config;
|
||
|
|
use LJ::Comment;
|
||
|
|
use DW::Task::SphinxCopier;
|
||
|
|
use DW::Task::XPost;
|
||
|
|
|
||
|
|
LJ::Config->load;
|
||
|
|
|
||
|
|
use DW::API::Key;
|
||
|
|
use DW::Auth::Challenge;
|
||
|
|
use LJ::Tags;
|
||
|
|
use LJ::Feed;
|
||
|
|
use LJ::EmbedModule;
|
||
|
|
|
||
|
|
#### New interface (meta handler) ... other handlers should call into this.
|
||
|
|
package LJ::Protocol;
|
||
|
|
|
||
|
|
# global declaration of this text since we use it in two places
|
||
|
|
our $CannotBeShown = '(cannot be shown)';
|
||
|
|
|
||
|
|
# error classes
|
||
|
|
use constant E_TEMP => 0;
|
||
|
|
use constant E_PERM => 1;
|
||
|
|
|
||
|
|
# maximum items for get_friends_page function
|
||
|
|
use constant FRIEND_ITEMS_LIMIT => 50;
|
||
|
|
|
||
|
|
my %e = (
|
||
|
|
|
||
|
|
# User Errors
|
||
|
|
"100" => [ E_PERM, "Invalid username" ],
|
||
|
|
"101" => [ E_PERM, "Invalid password" ],
|
||
|
|
"102" => [ E_PERM, "Can't use custom security on community journals." ],
|
||
|
|
"103" => [ E_PERM, "Poll error" ],
|
||
|
|
"104" => [ E_TEMP, "Error adding one or more friends" ],
|
||
|
|
"105" => [ E_PERM, "Challenge expired" ],
|
||
|
|
"106" =>
|
||
|
|
[ E_PERM, "Can only use administrator-locked security on community journals you manage." ],
|
||
|
|
"150" => [ E_PERM, "Can't post as non-user" ],
|
||
|
|
"151" => [ E_TEMP, "Banned from journal" ],
|
||
|
|
"152" => [ E_PERM, "Can't make back-dated entries in non-personal journal." ],
|
||
|
|
"153" => [ E_PERM, "Incorrect time value" ],
|
||
|
|
"154" => [ E_PERM, "Can't add a redirected account as a friend" ],
|
||
|
|
"155" => [ E_TEMP, "Non-authenticated email address" ],
|
||
|
|
"157" => [ E_TEMP, "Tags error" ],
|
||
|
|
"158" => [ E_PERM, "Comment error" ],
|
||
|
|
|
||
|
|
# Client Errors
|
||
|
|
"200" => [ E_PERM, "Missing required argument(s)" ],
|
||
|
|
"201" => [ E_PERM, "Unknown method" ],
|
||
|
|
"202" => [ E_PERM, "Too many arguments" ],
|
||
|
|
"203" => [ E_PERM, "Invalid argument(s)" ],
|
||
|
|
"204" => [ E_PERM, "Invalid metadata datatype" ],
|
||
|
|
"205" => [ E_PERM, "Unknown metadata" ],
|
||
|
|
"206" => [ E_PERM, "Invalid destination journal username." ],
|
||
|
|
"207" => [ E_PERM, "Protocol version mismatch" ],
|
||
|
|
"208" => [ E_PERM, "Invalid text encoding" ],
|
||
|
|
"209" => [ E_PERM, "Parameter out of range" ],
|
||
|
|
"210" => [ E_PERM, "Client tried to edit with corrupt data. Preventing." ],
|
||
|
|
"211" => [ E_PERM, "Invalid or malformed tag list" ],
|
||
|
|
"212" => [ E_PERM, "Message body is too long" ],
|
||
|
|
"213" => [ E_PERM, "Message body is empty" ],
|
||
|
|
"214" => [ E_PERM, "Message looks like spam" ],
|
||
|
|
|
||
|
|
# Access Errors
|
||
|
|
"300" => [ E_TEMP, "Don't have access to requested journal" ],
|
||
|
|
"301" => [ E_TEMP, "Access of restricted feature" ],
|
||
|
|
"302" => [ E_TEMP, "Can't edit post from requested journal" ],
|
||
|
|
"303" => [ E_TEMP, "Can't edit post in community journal" ],
|
||
|
|
"304" => [ E_TEMP, "Can't delete post in this community journal" ],
|
||
|
|
"305" => [ E_TEMP, "Action forbidden; account is suspended." ],
|
||
|
|
"306" => [
|
||
|
|
E_TEMP, "This journal is temporarily in read-only mode. Try again in a couple minutes."
|
||
|
|
],
|
||
|
|
"307" => [ E_PERM, "Selected journal no longer exists." ],
|
||
|
|
"308" => [ E_TEMP, "Account is locked and cannot be used." ],
|
||
|
|
"309" => [ E_PERM, "Account is marked as a memorial." ],
|
||
|
|
"310" => [ E_TEMP, "Account needs to be age verified before use." ],
|
||
|
|
"311" => [ E_TEMP, "Access temporarily disabled." ],
|
||
|
|
"312" => [ E_TEMP, "Not allowed to add tags to entries in this journal" ],
|
||
|
|
"313" =>
|
||
|
|
[ E_TEMP, "Must use existing tags for entries in this journal (can't create new ones)" ],
|
||
|
|
"314" => [ E_PERM, "Only paid users allowed to use this request" ],
|
||
|
|
"315" => [ E_PERM, "User messaging is currently disabled" ],
|
||
|
|
"316" => [ E_TEMP, "Poster is read-only and cannot post entries." ],
|
||
|
|
"317" => [ E_TEMP, "Journal is read-only and entries cannot be posted to it." ],
|
||
|
|
"318" => [ E_TEMP, "Poster is read-only and cannot edit entries." ],
|
||
|
|
"319" => [ E_TEMP, "Journal is read-only and its entries cannot be edited." ],
|
||
|
|
|
||
|
|
# Limit errors
|
||
|
|
"402" =>
|
||
|
|
[ E_TEMP, "Your IP address is temporarily banned for exceeding the login failure rate." ],
|
||
|
|
"404" => [ E_TEMP, "Cannot post" ],
|
||
|
|
"405" => [ E_TEMP, "Post frequency limit." ],
|
||
|
|
"406" => [ E_TEMP, "Client is making repeated requests. Perhaps it's broken?" ],
|
||
|
|
"407" => [ E_TEMP, "Moderation queue full" ],
|
||
|
|
"408" => [ E_TEMP, "Maximum queued posts for this community+poster combination reached." ],
|
||
|
|
"409" => [ E_PERM, "Post too large." ],
|
||
|
|
"411" => [ E_PERM, "Subject too long." ],
|
||
|
|
"412" => [ E_PERM, "Maximum number of comments reached" ],
|
||
|
|
|
||
|
|
# Server Errors
|
||
|
|
"500" => [ E_TEMP, "Internal server error" ],
|
||
|
|
"501" => [ E_TEMP, "Database error" ],
|
||
|
|
"502" => [ E_TEMP, "Database temporarily unavailable" ],
|
||
|
|
"503" => [ E_TEMP, "Error obtaining necessary database lock" ],
|
||
|
|
"504" => [ E_PERM, "Protocol mode no longer supported." ],
|
||
|
|
"505" => [ E_TEMP, "Account data format on server is old and needs to be upgraded." ]
|
||
|
|
, # cluster0
|
||
|
|
"506" => [ E_TEMP, "Journal sync temporarily unavailable." ],
|
||
|
|
"507" => [ E_TEMP, "Method temporarily disabled; try again later." ],
|
||
|
|
);
|
||
|
|
|
||
|
|
sub translate {
|
||
|
|
my ( $u, $msg, $vars ) = @_;
|
||
|
|
|
||
|
|
# we no longer support preferred language selection
|
||
|
|
return LJ::Lang::get_default_text( "protocol.$msg", $vars );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub error_class {
|
||
|
|
my $code = shift;
|
||
|
|
$code = $1 if $code =~ /^(\d\d\d):(.+)/;
|
||
|
|
return $e{$code} && ref $e{$code} ? $e{$code}->[0] : undef;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub error_message {
|
||
|
|
my $code = shift;
|
||
|
|
my $des;
|
||
|
|
( $code, $des ) = ( $1, $2 ) if $code =~ /^(\d\d\d):(.+)/;
|
||
|
|
|
||
|
|
my $prefix = "";
|
||
|
|
my $error =
|
||
|
|
$e{$code} && ref $e{$code}
|
||
|
|
? ( ref $e{$code}->[1] eq 'CODE' ? $e{$code}->[1]->() : $e{$code}->[1] )
|
||
|
|
: "BUG: Unknown error code!";
|
||
|
|
$prefix = "Client error: " if $code >= 200;
|
||
|
|
$prefix = "Server error: " if $code >= 500;
|
||
|
|
my $totalerror = "$prefix$error";
|
||
|
|
$totalerror .= ": $des" if $des;
|
||
|
|
return $totalerror;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub do_request {
|
||
|
|
|
||
|
|
# get the request and response hash refs
|
||
|
|
my ( $method, $req, $err, $flags ) = @_;
|
||
|
|
|
||
|
|
# if version isn't specified explicitly, it's version 0
|
||
|
|
if ( ref $req eq "HASH" ) {
|
||
|
|
$req->{'ver'} ||= $req->{'version'};
|
||
|
|
$req->{'ver'} = 0 unless defined $req->{'ver'};
|
||
|
|
}
|
||
|
|
|
||
|
|
$flags ||= {};
|
||
|
|
my @args = ( $req, $err, $flags );
|
||
|
|
|
||
|
|
DW::Stats::increment( 'dw.protocol_request', 1, ["method:$method"] );
|
||
|
|
|
||
|
|
if ( $method eq "login" ) { return login(@args); }
|
||
|
|
if ( $method eq "getfriendgroups" ) { return getfriendgroups(@args); }
|
||
|
|
if ( $method eq "gettrustgroups" ) { return gettrustgroups(@args); }
|
||
|
|
if ( $method eq "getfriends" ) { return getfriends(@args); }
|
||
|
|
if ( $method eq "getcircle" ) { return getcircle(@args); }
|
||
|
|
if ( $method eq "editcircle" ) { return editcircle(@args); }
|
||
|
|
if ( $method eq "friendof" ) { return friendof(@args); }
|
||
|
|
if ( $method eq "checkfriends" ) { return checkfriends(@args); }
|
||
|
|
if ( $method eq "checkforupdates" ) { return checkforupdates(@args); }
|
||
|
|
if ( $method eq "getdaycounts" ) { return getdaycounts(@args); }
|
||
|
|
if ( $method eq "postevent" ) { return postevent(@args); }
|
||
|
|
if ( $method eq "editevent" ) { return editevent(@args); }
|
||
|
|
if ( $method eq "syncitems" ) { return syncitems(@args); }
|
||
|
|
if ( $method eq "getevents" ) { return getevents(@args); }
|
||
|
|
if ( $method eq "editfriends" ) { return editfriends(@args); }
|
||
|
|
if ( $method eq "editfriendgroups" ) { return editfriendgroups(@args); }
|
||
|
|
if ( $method eq "consolecommand" ) { return consolecommand(@args); }
|
||
|
|
if ( $method eq "getchallenge" ) { return getchallenge(@args); }
|
||
|
|
if ( $method eq "sessiongenerate" ) { return sessiongenerate(@args); }
|
||
|
|
if ( $method eq "sessionexpire" ) { return sessionexpire(@args); }
|
||
|
|
if ( $method eq "getusertags" ) { return getusertags(@args); }
|
||
|
|
if ( $method eq "getfriendspage" ) { return getfriendspage(@args); }
|
||
|
|
if ( $method eq "getreadpage" ) { return getreadpage(@args); }
|
||
|
|
if ( $method eq "getinbox" ) { return getinbox(@args); }
|
||
|
|
if ( $method eq "sendmessage" ) { return sendmessage(@args); }
|
||
|
|
if ( $method eq "setmessageread" ) { return setmessageread(@args); }
|
||
|
|
if ( $method eq "addcomment" ) { return addcomment(@args); }
|
||
|
|
|
||
|
|
return fail( $err, 201 );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub addcomment {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
|
||
|
|
# some additional checks
|
||
|
|
return fail( $err, 314 ) unless $u->is_paid || $flags->{nocheckcap};
|
||
|
|
|
||
|
|
my $journal;
|
||
|
|
if ( $req->{journal} ) {
|
||
|
|
$journal = LJ::load_user( $req->{journal} ) or return fail( $err, 100 );
|
||
|
|
my $entry = LJ::Entry->new( $journal, ditemid => $req->{ditemid} );
|
||
|
|
return fail( $err, 214 )
|
||
|
|
if LJ::Talk::Post::require_captcha_test( $u, $journal, $req->{body}, $entry );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$journal = $u;
|
||
|
|
}
|
||
|
|
|
||
|
|
# create
|
||
|
|
my $comment_err;
|
||
|
|
my $comment = LJ::Comment->create(
|
||
|
|
journal => $journal,
|
||
|
|
ditemid => $req->{ditemid},
|
||
|
|
parenttalkid => ( $req->{parenttalkid} || ( $req->{parent} >> 8 ) ),
|
||
|
|
|
||
|
|
poster => $u,
|
||
|
|
|
||
|
|
body => $req->{body},
|
||
|
|
subject => $req->{subject},
|
||
|
|
|
||
|
|
props => {
|
||
|
|
picture_keyword => $req->{prop_picture_keyword},
|
||
|
|
editor => $req->{prop_editor},
|
||
|
|
},
|
||
|
|
|
||
|
|
err_ref => \$comment_err,
|
||
|
|
);
|
||
|
|
|
||
|
|
my $err_code_mapping = {
|
||
|
|
bad_journal => 206, # authenticate() takes care of this
|
||
|
|
bad_poster => 100, # authenticate() takes care of this
|
||
|
|
bad_args => 202,
|
||
|
|
no_entry => 200,
|
||
|
|
|
||
|
|
too_many_comments => 412,
|
||
|
|
|
||
|
|
init_comment => 158,
|
||
|
|
post_comment => 158,
|
||
|
|
}->{ $comment_err->{code} }
|
||
|
|
if $comment_err;
|
||
|
|
|
||
|
|
return fail( $err, $err_code_mapping, $comment_err->{msg} ) if $comment_err;
|
||
|
|
|
||
|
|
my %props = ();
|
||
|
|
$props{useragent} = $req->{useragent} if $req->{useragent};
|
||
|
|
$comment->set_props(%props);
|
||
|
|
|
||
|
|
# OK
|
||
|
|
return {
|
||
|
|
status => "OK",
|
||
|
|
commentlink => $comment->url,
|
||
|
|
dtalkid => $comment->dtalkid,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
sub getfriendspage {
|
||
|
|
return fail( $_[1], 504, "Use 'getreadpage' instead." );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub getreadpage {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
|
||
|
|
my $itemshow = ( defined $req->{itemshow} ) ? $req->{itemshow} : 100;
|
||
|
|
return fail( $err, 209, "Bad itemshow value" )
|
||
|
|
if $itemshow ne int($itemshow)
|
||
|
|
or $itemshow <= 0
|
||
|
|
or $itemshow > 100;
|
||
|
|
my $skip = ( defined $req->{skip} ) ? $req->{skip} : 0;
|
||
|
|
return fail( $err, 209, "Bad skip value" ) if $skip ne int($skip) or $skip < 0 or $skip > 100;
|
||
|
|
|
||
|
|
my @entries = $u->watch_items(
|
||
|
|
remote => $u,
|
||
|
|
|
||
|
|
itemshow => $itemshow,
|
||
|
|
skip => $skip,
|
||
|
|
|
||
|
|
dateformat => 'S2',
|
||
|
|
);
|
||
|
|
|
||
|
|
my @attrs = qw/subject_raw event_raw journalid posterid ditemid security/;
|
||
|
|
|
||
|
|
my @uids;
|
||
|
|
|
||
|
|
my @res = ();
|
||
|
|
my $lastsync = int $req->{lastsync};
|
||
|
|
foreach my $ei (@entries) {
|
||
|
|
|
||
|
|
next unless $ei;
|
||
|
|
|
||
|
|
# exit cycle if maximum friend items limit reached
|
||
|
|
last
|
||
|
|
if scalar @res >= FRIEND_ITEMS_LIMIT;
|
||
|
|
|
||
|
|
# if passed lastsync argument - skip items with logtime less than lastsync
|
||
|
|
if ($lastsync) {
|
||
|
|
next
|
||
|
|
if $LJ::EndOfTime - $ei->{rlogtime} <= $lastsync;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $entry = LJ::Entry->new_from_item_hash($ei);
|
||
|
|
next unless $entry;
|
||
|
|
|
||
|
|
# event result data structure
|
||
|
|
my %h = ();
|
||
|
|
|
||
|
|
# Add more data for public posts
|
||
|
|
foreach my $method (@attrs) {
|
||
|
|
$h{$method} = $entry->$method;
|
||
|
|
}
|
||
|
|
|
||
|
|
# log time value
|
||
|
|
$h{logtime} = $LJ::EndOfTime - $ei->{rlogtime};
|
||
|
|
|
||
|
|
push @res, \%h;
|
||
|
|
|
||
|
|
push @uids, $h{posterid}, $h{journalid};
|
||
|
|
}
|
||
|
|
|
||
|
|
my $users = LJ::load_userids(@uids);
|
||
|
|
|
||
|
|
foreach (@res) {
|
||
|
|
$_->{journalname} = $users->{ $_->{journalid} }->{'user'};
|
||
|
|
$_->{journaltype} = $users->{ $_->{journalid} }->{'journaltype'};
|
||
|
|
delete $_->{journalid};
|
||
|
|
$_->{postername} = $users->{ $_->{posterid} }->{'user'};
|
||
|
|
$_->{postertype} = $users->{ $_->{posterid} }->{'journaltype'};
|
||
|
|
delete $_->{posterid};
|
||
|
|
}
|
||
|
|
|
||
|
|
LJ::Hooks::run_hooks( "getfriendspage", { 'userid' => $u->userid, } );
|
||
|
|
|
||
|
|
return { 'entries' => [@res] };
|
||
|
|
}
|
||
|
|
|
||
|
|
sub getinbox {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
|
||
|
|
my $itemshow = ( defined $req->{itemshow} ) ? $req->{itemshow} : 100;
|
||
|
|
return fail( $err, 209, "Bad itemshow value" )
|
||
|
|
if $itemshow ne int($itemshow)
|
||
|
|
or $itemshow <= 0
|
||
|
|
or $itemshow > 100;
|
||
|
|
my $skip = ( defined $req->{skip} ) ? $req->{skip} : 0;
|
||
|
|
return fail( $err, 209, "Bad skip value" ) if $skip ne int($skip) or $skip < 0 or $skip > 100;
|
||
|
|
|
||
|
|
# get the user's inbox
|
||
|
|
my $inbox = $u->notification_inbox or return fail( $err, 500, "Cannot get user inbox" );
|
||
|
|
|
||
|
|
my %type_number = (
|
||
|
|
AddedToCircle => 1,
|
||
|
|
Birthday => 2,
|
||
|
|
CommunityInvite => 3,
|
||
|
|
CommunityJoinApprove => 4,
|
||
|
|
CommunityJoinReject => 5,
|
||
|
|
CommunityJoinRequest => 6,
|
||
|
|
RemovedFromCircle => 7,
|
||
|
|
InvitedFriendJoins => 8,
|
||
|
|
JournalNewComment => 9,
|
||
|
|
JournalNewEntry => 10,
|
||
|
|
NewUserpic => 11,
|
||
|
|
NewVGift => 12,
|
||
|
|
OfficialPost => 13,
|
||
|
|
PermSale => 14,
|
||
|
|
PollVote => 15,
|
||
|
|
SupOfficialPost => 16,
|
||
|
|
UserExpunged => 17,
|
||
|
|
UserMessageRecvd => 18,
|
||
|
|
UserMessageSent => 19,
|
||
|
|
);
|
||
|
|
my %number_type = reverse %type_number;
|
||
|
|
|
||
|
|
my @notifications;
|
||
|
|
|
||
|
|
my $sync_date;
|
||
|
|
|
||
|
|
# check lastsync for valid date
|
||
|
|
if ( $req->{'lastsync'} ) {
|
||
|
|
$sync_date = int $req->{'lastsync'};
|
||
|
|
if ( $sync_date <= 0 ) {
|
||
|
|
return fail( $err, 203, "Invalid syncitems date format (must be unixtime)" );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $req->{gettype} ) {
|
||
|
|
@notifications =
|
||
|
|
grep { $_->event->class eq "LJ::Event::" . $number_type{ $req->{gettype} } }
|
||
|
|
$inbox->items;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
@notifications = $inbox->all_items;
|
||
|
|
}
|
||
|
|
|
||
|
|
# By default, notifications are sorted as "oldest are the first"
|
||
|
|
# Reverse it by "newest are the first"
|
||
|
|
@notifications = reverse @notifications;
|
||
|
|
|
||
|
|
$itemshow = scalar @notifications - $skip if scalar @notifications < $skip + $itemshow;
|
||
|
|
|
||
|
|
my @res;
|
||
|
|
foreach my $item ( @notifications[ $skip .. $itemshow + $skip - 1 ] ) {
|
||
|
|
next if $sync_date && $item->when_unixtime < $sync_date;
|
||
|
|
|
||
|
|
my $raw = $item->event->raw_info( $u, { extended => $req->{extended} } );
|
||
|
|
|
||
|
|
my $type_index = $type_number{ $raw->{type} };
|
||
|
|
if ( defined $type_index ) {
|
||
|
|
$raw->{type} = $type_index;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$raw->{typename} = $raw->{type};
|
||
|
|
$raw->{type} = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$raw->{state} = $item->{state};
|
||
|
|
|
||
|
|
push @res,
|
||
|
|
{
|
||
|
|
%$raw,
|
||
|
|
when => $item->when_unixtime,
|
||
|
|
qid => $item->qid,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
'items' => \@res,
|
||
|
|
'login' => $u->user,
|
||
|
|
'journaltype' => $u->journaltype
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
sub setmessageread {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
|
||
|
|
# get the user's inbox
|
||
|
|
my $inbox = $u->notification_inbox or return fail( $err, 500, "Cannot get user inbox" );
|
||
|
|
my @result;
|
||
|
|
|
||
|
|
# passing requested ids for loading
|
||
|
|
my @notifications = $inbox->all_items;
|
||
|
|
|
||
|
|
# Try to select messages by qid if specified
|
||
|
|
my @qids = @{ $req->{qid} };
|
||
|
|
if ( scalar @qids ) {
|
||
|
|
foreach my $qid (@qids) {
|
||
|
|
my $item = eval { LJ::NotificationItem->new( $u, $qid ) };
|
||
|
|
$item->mark_read if $item;
|
||
|
|
push @result, { qid => $qid, result => 'set read' };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else { # Else select it by msgid for back compatibility
|
||
|
|
# make hash of requested message ids
|
||
|
|
my %requested_items = map { $_ => 1 } @{ $req->{messageid} };
|
||
|
|
|
||
|
|
# proccessing only requested ids
|
||
|
|
foreach my $item (@notifications) {
|
||
|
|
my $msgid = $item->event->raw_info($u)->{msgid};
|
||
|
|
next unless $requested_items{$msgid};
|
||
|
|
|
||
|
|
# if message already read -
|
||
|
|
if ( $item->{state} eq 'R' ) {
|
||
|
|
push @result, { msgid => $msgid, result => 'already red' };
|
||
|
|
next;
|
||
|
|
}
|
||
|
|
|
||
|
|
# in state no 'R' - marking as red
|
||
|
|
$item->mark_read;
|
||
|
|
push @result, { msgid => $msgid, result => 'set read' };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { result => \@result };
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
# Sends a private message from one account to another
|
||
|
|
sub sendmessage {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
|
||
|
|
return fail( $err, 315 ) unless LJ::is_enabled('user_messaging');
|
||
|
|
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
|
||
|
|
return fail( $err, 305 ) if $u->is_suspended; # suspended cannot send private messages
|
||
|
|
|
||
|
|
my $msg_limit = $u->count_usermessage_length;
|
||
|
|
|
||
|
|
my @errors;
|
||
|
|
|
||
|
|
# test encoding and length
|
||
|
|
my $subject_text = $req->{'subject'};
|
||
|
|
return fail( $err, 208, 'subject' )
|
||
|
|
unless LJ::text_in($subject_text);
|
||
|
|
|
||
|
|
# test encoding and length
|
||
|
|
my $body_text = $req->{'body'};
|
||
|
|
return fail( $err, 208, 'body' )
|
||
|
|
unless LJ::text_in($body_text);
|
||
|
|
|
||
|
|
my ( $msg_len_b, $msg_len_c ) = LJ::text_length($body_text);
|
||
|
|
return fail( $err, 212,
|
||
|
|
'found: '
|
||
|
|
. LJ::commafy($msg_len_c)
|
||
|
|
. ' characters, it should not exceed '
|
||
|
|
. LJ::commafy($msg_limit) )
|
||
|
|
unless ( $msg_len_c <= $msg_limit );
|
||
|
|
|
||
|
|
return fail( $err, 213,
|
||
|
|
'found: ' . LJ::commafy($msg_len_c) . ' characters, it should exceed zero' )
|
||
|
|
if ( $msg_len_c <= 0 );
|
||
|
|
|
||
|
|
#test if to argument is present
|
||
|
|
return fail( $err, 200, "to" ) unless exists $req->{'to'};
|
||
|
|
|
||
|
|
my @to = ( ref $req->{'to'} ) ? @{ $req->{'to'} } : ( $req->{'to'} );
|
||
|
|
return fail( $err, 200 ) unless scalar @to;
|
||
|
|
|
||
|
|
# remove duplicates
|
||
|
|
my %to = map { lc($_), 1 } @to;
|
||
|
|
@to = keys %to;
|
||
|
|
|
||
|
|
my @msg;
|
||
|
|
BML::set_language('en'); # FIXME
|
||
|
|
|
||
|
|
foreach my $to (@to) {
|
||
|
|
my $tou = LJ::load_user($to);
|
||
|
|
return fail( $err, 100, $to )
|
||
|
|
unless $tou;
|
||
|
|
|
||
|
|
my $msguserpic;
|
||
|
|
$msguserpic = $req->{'userpic'} if defined $req->{'userpic'};
|
||
|
|
|
||
|
|
my $msg = LJ::Message->new(
|
||
|
|
{
|
||
|
|
journalid => $u->userid,
|
||
|
|
otherid => $tou->userid,
|
||
|
|
subject => $subject_text,
|
||
|
|
body => $body_text,
|
||
|
|
parent_msgid => defined $req->{'parent'} ? $req->{'parent'} + 0 : undef,
|
||
|
|
userpic => $msguserpic,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
push @msg, $msg
|
||
|
|
if $msg->can_send( \@errors );
|
||
|
|
}
|
||
|
|
return fail( $err, 203, join( '; ', @errors ) )
|
||
|
|
if scalar @errors;
|
||
|
|
|
||
|
|
foreach my $msg (@msg) {
|
||
|
|
$msg->send( \@errors );
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
'sent_count' => scalar @msg,
|
||
|
|
'msgid' => [ grep { $_ } map { $_->msgid } @msg ],
|
||
|
|
( @errors ? ( 'last_errors' => \@errors ) : () ),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
sub login {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
my $res = {};
|
||
|
|
my $ver = $req->{'ver'};
|
||
|
|
|
||
|
|
# do not let locked people log in
|
||
|
|
return fail( $err, 308 ) if $u->is_locked;
|
||
|
|
|
||
|
|
## return a message to the client to be displayed (optional)
|
||
|
|
login_message( $req, $res, $flags );
|
||
|
|
LJ::text_out( \$res->{'message'} ) if $ver >= 1 and defined $res->{'message'};
|
||
|
|
|
||
|
|
## report what shared journals this user may post in
|
||
|
|
$res->{'usejournals'} = list_usejournals($u);
|
||
|
|
|
||
|
|
## return their friend groups
|
||
|
|
$res->{'friendgroups'} = list_friendgroups($u);
|
||
|
|
return fail( $err, 502, "Error loading friend groups" ) unless $res->{'friendgroups'};
|
||
|
|
if ( $ver >= 1 ) {
|
||
|
|
foreach ( @{ $res->{'friendgroups'} } ) {
|
||
|
|
LJ::text_out( \$_->{'name'} );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
## if they gave us a number of moods to get higher than, then return them
|
||
|
|
if ( defined $req->{'getmoods'} ) {
|
||
|
|
$res->{'moods'} = list_moods( $req->{'getmoods'} );
|
||
|
|
if ( $ver >= 1 ) {
|
||
|
|
|
||
|
|
# currently all moods are in English, but this might change
|
||
|
|
foreach ( @{ $res->{'moods'} } ) { LJ::text_out( \$_->{'name'} ) }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
### picture keywords, if they asked for them.
|
||
|
|
if ( $req->{'getpickws'} ) {
|
||
|
|
my $pickws = list_pickws($u);
|
||
|
|
@$pickws = sort { lc( $a->[0] ) cmp lc( $b->[0] ) } @$pickws;
|
||
|
|
$res->{'pickws'} = [ map { $_->[0] } @$pickws ];
|
||
|
|
if ( $req->{'getpickwurls'} ) {
|
||
|
|
if ( $u->{'defaultpicid'} ) {
|
||
|
|
$res->{'defaultpicurl'} = "$LJ::USERPIC_ROOT/$u->{'defaultpicid'}/$u->{'userid'}";
|
||
|
|
}
|
||
|
|
$res->{'pickwurls'} = [ map { "$LJ::USERPIC_ROOT/$_->[1]/$u->{'userid'}" } @$pickws ];
|
||
|
|
}
|
||
|
|
if ( $ver >= 1 ) {
|
||
|
|
|
||
|
|
# validate all text
|
||
|
|
foreach ( @{ $res->{'pickws'} } ) { LJ::text_out( \$_ ); }
|
||
|
|
foreach ( @{ $res->{'pickwurls'} } ) { LJ::text_out( \$_ ); }
|
||
|
|
LJ::text_out( \$res->{'defaultpicurl'} );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
## return caps, if they asked for them
|
||
|
|
if ( $req->{'getcaps'} ) {
|
||
|
|
$res->{'caps'} = $u->caps;
|
||
|
|
}
|
||
|
|
|
||
|
|
## return client menu tree, if requested
|
||
|
|
if ( $req->{'getmenus'} ) {
|
||
|
|
$res->{'menus'} = hash_menus($u);
|
||
|
|
if ( $ver >= 1 ) {
|
||
|
|
|
||
|
|
# validate all text, just in case, even though currently
|
||
|
|
# it's all English
|
||
|
|
foreach ( @{ $res->{'menus'} } ) {
|
||
|
|
LJ::text_out( \$_->{'text'} );
|
||
|
|
LJ::text_out( \$_->{'url'} ); # should be redundant
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
## tell some users they can hit the fast servers later.
|
||
|
|
$res->{'fastserver'} = 1 if $u->can_use_fastlane;
|
||
|
|
|
||
|
|
## user info
|
||
|
|
$res->{'userid'} = $u->{'userid'};
|
||
|
|
$res->{'fullname'} = $u->{'name'};
|
||
|
|
LJ::text_out( \$res->{'fullname'} ) if $ver >= 1;
|
||
|
|
|
||
|
|
if ( $req->{'clientversion'} =~ /^\S+\/\S+$/ ) {
|
||
|
|
eval {
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
$apache_r->notes->{clientver} = $req->{'clientversion'};
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
## update or add to clientusage table
|
||
|
|
if ( $req->{'clientversion'} =~ /^\S+\/\S+$/
|
||
|
|
&& LJ::is_enabled('clientversionlog') )
|
||
|
|
{
|
||
|
|
my $client = $req->{'clientversion'};
|
||
|
|
|
||
|
|
return fail( $err, 208, "Bad clientversion string" )
|
||
|
|
if $ver >= 1 and not LJ::text_in($client);
|
||
|
|
|
||
|
|
my $dbh = LJ::get_db_writer();
|
||
|
|
my $qclient = $dbh->quote($client);
|
||
|
|
my $cu_sql = "REPLACE INTO clientusage (userid, clientid, lastlogin) "
|
||
|
|
. "SELECT $u->{'userid'}, clientid, NOW() FROM clients WHERE client=$qclient";
|
||
|
|
my $sth = $dbh->prepare($cu_sql);
|
||
|
|
$sth->execute;
|
||
|
|
unless ( $sth->rows ) {
|
||
|
|
|
||
|
|
# only way this can be 0 is if client doesn't exist in clients table, so
|
||
|
|
# we need to add a new row there, to get a new clientid for this new client:
|
||
|
|
$dbh->do("INSERT INTO clients (client) VALUES ($qclient)");
|
||
|
|
|
||
|
|
# and now we can do the query from before and it should work:
|
||
|
|
$sth = $dbh->prepare($cu_sql);
|
||
|
|
$sth->execute;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
#deprecated
|
||
|
|
sub getfriendgroups {
|
||
|
|
return fail( $_[1], 504 );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub gettrustgroups {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
my $u = $flags->{u};
|
||
|
|
my $res = {};
|
||
|
|
$res->{trustgroups} = list_trustgroups($u);
|
||
|
|
return fail( $err, 502, "Error loading trust groups" ) unless $res->{trustgroups};
|
||
|
|
if ( $req->{ver} >= 1 ) {
|
||
|
|
foreach ( @{ $res->{trustgroups} || [] } ) {
|
||
|
|
LJ::text_out( \$_->{name} );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub getusertags {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
return undef unless check_altusage( $req, $err, $flags );
|
||
|
|
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
my $uowner = $flags->{'u_owner'} || $u;
|
||
|
|
return fail( $req, 502 ) unless $u && $uowner;
|
||
|
|
|
||
|
|
my $tags = LJ::Tags::get_usertags( $uowner, { remote => $u } );
|
||
|
|
return { tags => [ values %$tags ] };
|
||
|
|
}
|
||
|
|
|
||
|
|
sub getfriends {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
return fail( $req, 502 ) unless LJ::get_db_reader();
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
my $res = {};
|
||
|
|
if ( $req->{'includegroups'} ) {
|
||
|
|
$res->{'friendgroups'} = list_friendgroups($u);
|
||
|
|
return fail( $err, 502, "Error loading friend groups" ) unless $res->{'friendgroups'};
|
||
|
|
if ( $req->{'ver'} >= 1 ) {
|
||
|
|
foreach ( @{ $res->{'friendgroups'} || [] } ) {
|
||
|
|
LJ::text_out( \$_->{'name'} );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# TAG:FR:protocol:getfriends_of
|
||
|
|
if ( $req->{'includefriendof'} ) {
|
||
|
|
$res->{'friendofs'} = list_friends(
|
||
|
|
$u,
|
||
|
|
{
|
||
|
|
'limit' => $req->{'friendoflimit'},
|
||
|
|
'friendof' => 1,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
if ( $req->{'ver'} >= 1 ) {
|
||
|
|
foreach ( @{ $res->{'friendofs'} } ) { LJ::text_out( \$_->{'fullname'} ) }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# TAG:FR:protocol:getfriends
|
||
|
|
$res->{'friends'} = list_friends(
|
||
|
|
$u,
|
||
|
|
{
|
||
|
|
'limit' => $req->{'friendlimit'},
|
||
|
|
'includebdays' => $req->{'includebdays'},
|
||
|
|
}
|
||
|
|
);
|
||
|
|
if ( $req->{'ver'} >= 1 ) {
|
||
|
|
foreach ( @{ $res->{'friends'} } ) { LJ::text_out( \$_->{'fullname'} ) }
|
||
|
|
}
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub getcircle {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
my $u = $flags->{u};
|
||
|
|
my $res = {};
|
||
|
|
my $limit = $LJ::MAX_WT_EDGES_LOAD;
|
||
|
|
$limit = $req->{limit}
|
||
|
|
if defined $req->{limit} && $req->{limit} < $limit;
|
||
|
|
|
||
|
|
if ( $req->{includetrustgroups} ) {
|
||
|
|
$res->{trustgroups} = list_trustgroups($u);
|
||
|
|
return fail( $err, 502, "Error loading trust groups" ) unless $res->{trustgroups};
|
||
|
|
if ( $req->{ver} >= 1 ) {
|
||
|
|
LJ::text_out( \$_->{name} ) foreach ( @{ $res->{trustgroups} || [] } );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ( $req->{includecontentfilters} ) {
|
||
|
|
$res->{contentfilters} = list_contentfilters($u);
|
||
|
|
return fail( $err, 502, "Error loading content filters" ) unless $res->{contentfilters};
|
||
|
|
if ( $req->{ver} >= 1 ) {
|
||
|
|
LJ::text_out( \$_->{name} ) foreach ( @{ $res->{contentfilters} || [] } );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ( $req->{includewatchedusers} ) {
|
||
|
|
$res->{watchedusers} = list_users(
|
||
|
|
$u,
|
||
|
|
limit => $limit,
|
||
|
|
watched => 1,
|
||
|
|
includebdays => $req->{includebdays},
|
||
|
|
);
|
||
|
|
if ( $req->{ver} >= 1 ) {
|
||
|
|
LJ::text_out( \$_->{fullname} ) foreach ( @{ $res->{watchedusers} || [] } );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ( $req->{includewatchedby} ) {
|
||
|
|
$res->{watchedbys} = list_users(
|
||
|
|
$u,
|
||
|
|
limit => $limit,
|
||
|
|
watchedby => 1,
|
||
|
|
);
|
||
|
|
if ( $req->{ver} >= 1 ) {
|
||
|
|
LJ::text_out( \$_->{fullname} ) foreach ( @{ $res->{watchedbys} || [] } );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ( $req->{includetrustedusers} ) {
|
||
|
|
$res->{trustedusers} = list_users(
|
||
|
|
$u,
|
||
|
|
limit => $limit,
|
||
|
|
trusted => 1,
|
||
|
|
includebdays => $req->{includebdays},
|
||
|
|
);
|
||
|
|
if ( $req->{ver} >= 1 ) {
|
||
|
|
LJ::text_out( \$_->{fullname} ) foreach ( @{ $res->{trustedusers} || [] } );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ( $req->{includetrustedby} ) {
|
||
|
|
$res->{trustedbys} = list_users(
|
||
|
|
$u,
|
||
|
|
limit => $limit,
|
||
|
|
trustedby => 1,
|
||
|
|
);
|
||
|
|
if ( $req->{ver} >= 1 ) {
|
||
|
|
LJ::text_out( \$_->{fullname} ) foreach ( @{ $res->{trustedbys} || [] } );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub friendof {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
return fail( $req, 502 ) unless LJ::get_db_reader();
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
my $res = {};
|
||
|
|
|
||
|
|
# TAG:FR:protocol:getfriends_of2 (same as TAG:FR:protocol:getfriends_of)
|
||
|
|
$res->{'friendofs'} = list_friends(
|
||
|
|
$u,
|
||
|
|
{
|
||
|
|
'friendof' => 1,
|
||
|
|
'limit' => $req->{'friendoflimit'},
|
||
|
|
}
|
||
|
|
);
|
||
|
|
if ( $req->{'ver'} >= 1 ) {
|
||
|
|
foreach ( @{ $res->{'friendofs'} } ) { LJ::text_out( \$_->{'fullname'} ) }
|
||
|
|
}
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub checkfriends {
|
||
|
|
return fail( $_[1], 504, "Use 'checkforupdates' instead." );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub checkforupdates {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
my $res = {};
|
||
|
|
|
||
|
|
# return immediately if they can't use this mode
|
||
|
|
unless ( $u->can_use_checkforupdates ) {
|
||
|
|
$res->{'new'} = 0;
|
||
|
|
$res->{'interval'} = 36000;
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
## have a valid date?
|
||
|
|
my $lastupdate = $req->{'lastupdate'};
|
||
|
|
if ($lastupdate) {
|
||
|
|
return fail( $err, 203 )
|
||
|
|
unless ( $lastupdate =~ /^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/ );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$lastupdate = "0000-00-00 00:00:00";
|
||
|
|
}
|
||
|
|
|
||
|
|
my $interval = LJ::Capabilities::get_cap_min( $u, "checkfriends_interval" );
|
||
|
|
$res->{'interval'} = $interval;
|
||
|
|
|
||
|
|
my $filter;
|
||
|
|
if ( $req->{filter} ) {
|
||
|
|
$filter = $u->content_filters( name => $req->{filter} );
|
||
|
|
return fail( $err, 203,
|
||
|
|
"Invalid filter name. Trying to check updates for a filter that does not exist." )
|
||
|
|
unless $filter;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $memkey = [ $u->id, "checkforupdates:$u->{userid}:" . ( $filter ? $filter->id : "" ) ];
|
||
|
|
my $update = LJ::MemCache::get($memkey);
|
||
|
|
unless ($update) {
|
||
|
|
my @fr = $u->watched_userids;
|
||
|
|
|
||
|
|
# FIXME: see whether we can just get the list of users who are in the filter
|
||
|
|
if ($filter) {
|
||
|
|
my @filter_users;
|
||
|
|
|
||
|
|
foreach my $fid (@fr) {
|
||
|
|
push @filter_users, $fid
|
||
|
|
if $filter->contains_userid($fid);
|
||
|
|
}
|
||
|
|
@fr = @filter_users;
|
||
|
|
}
|
||
|
|
|
||
|
|
unless (@fr) {
|
||
|
|
$res->{'new'} = 0;
|
||
|
|
$res->{'lastupdate'} = $lastupdate;
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
if (@LJ::MEMCACHE_SERVERS) {
|
||
|
|
my $tu = LJ::get_timeupdate_multi( { memcache_only => 1 }, @fr );
|
||
|
|
my $max = 0;
|
||
|
|
foreach ( values %$tu ) {
|
||
|
|
$max = $_ if $_ > $max;
|
||
|
|
}
|
||
|
|
$update = LJ::mysql_time($max) if $max;
|
||
|
|
}
|
||
|
|
unless ($update) {
|
||
|
|
my $dbr = LJ::get_db_reader();
|
||
|
|
unless ($dbr) {
|
||
|
|
|
||
|
|
# rather than return a 502 no-db error, just say no updates,
|
||
|
|
# because problem'll be fixed soon enough by db admins
|
||
|
|
$res->{'new'} = 0;
|
||
|
|
$res->{'lastupdate'} = $lastupdate;
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
my $list = join( ", ", map { int($_) } @fr );
|
||
|
|
if ($list) {
|
||
|
|
my $sql = "SELECT MAX(timeupdate) FROM userusage " . "WHERE userid IN ($list)";
|
||
|
|
$update = $dbr->selectrow_array($sql);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
LJ::MemCache::set( $memkey, $update, time() + $interval ) if $update;
|
||
|
|
}
|
||
|
|
$update ||= "0000-00-00 00:00:00";
|
||
|
|
|
||
|
|
if ( $req->{'lastupdate'} && $update gt $lastupdate ) {
|
||
|
|
$res->{'new'} = 1;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$res->{'new'} = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'lastupdate'} = $update;
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub getdaycounts {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
return undef unless check_altusage( $req, $err, $flags );
|
||
|
|
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
my $uowner = $flags->{'u_owner'} || $u;
|
||
|
|
my $ownerid = $flags->{'ownerid'};
|
||
|
|
return fail( $err, 502 ) unless LJ::isu($uowner);
|
||
|
|
|
||
|
|
my $res = {};
|
||
|
|
my $daycts = $uowner->get_daycounts($u);
|
||
|
|
return fail( $err, 502 ) unless $daycts;
|
||
|
|
|
||
|
|
foreach my $day (@$daycts) {
|
||
|
|
my $date = sprintf( "%04d-%02d-%02d", $day->[0], $day->[1], $day->[2] );
|
||
|
|
push @{ $res->{'daycounts'} }, { 'date' => $date, 'count' => $day->[3] };
|
||
|
|
}
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub common_event_validation {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
|
||
|
|
# clean up event whitespace
|
||
|
|
# remove surrounding whitespace
|
||
|
|
$req->{event} =~ s/^\s+//;
|
||
|
|
$req->{event} =~ s/\s+$//;
|
||
|
|
|
||
|
|
# convert line endings to unix format
|
||
|
|
if ( $req->{'lineendings'} eq "mac" ) {
|
||
|
|
$req->{event} =~ s/\r/\n/g;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$req->{event} =~ s/\r//g;
|
||
|
|
}
|
||
|
|
|
||
|
|
# date validation
|
||
|
|
if (
|
||
|
|
$req->{'year'} !~ /^\d\d\d\d$/
|
||
|
|
|| $req->{'year'} < 1970
|
||
|
|
|| # before unix time started = bad
|
||
|
|
$req->{'year'} > 2037
|
||
|
|
) # after unix time ends = worse! :)
|
||
|
|
{
|
||
|
|
return fail( $err, 203, "Invalid year value (must be in the range 1970-2037)." );
|
||
|
|
}
|
||
|
|
if ( $req->{'mon'} !~ /^\d{1,2}$/
|
||
|
|
|| $req->{'mon'} < 1
|
||
|
|
|| $req->{'mon'} > 12 )
|
||
|
|
{
|
||
|
|
return fail( $err, 203, "Invalid month value." );
|
||
|
|
}
|
||
|
|
if ( $req->{'day'} !~ /^\d{1,2}$/
|
||
|
|
|| $req->{'day'} < 1
|
||
|
|
|| $req->{'day'} > LJ::days_in_month( $req->{'mon'}, $req->{'year'} ) )
|
||
|
|
{
|
||
|
|
return fail( $err, 203, "Invalid day of month value." );
|
||
|
|
}
|
||
|
|
if ( $req->{'hour'} !~ /^\d{1,2}$/
|
||
|
|
|| $req->{'hour'} < 0
|
||
|
|
|| $req->{'hour'} > 23 )
|
||
|
|
{
|
||
|
|
return fail( $err, 203, "Invalid hour value." );
|
||
|
|
}
|
||
|
|
if ( $req->{'min'} !~ /^\d{1,2}$/
|
||
|
|
|| $req->{'min'} < 0
|
||
|
|
|| $req->{'min'} > 59 )
|
||
|
|
{
|
||
|
|
return fail( $err, 203, "Invalid minute value." );
|
||
|
|
}
|
||
|
|
|
||
|
|
# setup non-user meta-data. it's important we define this here to
|
||
|
|
# 0. if it's not defined at all, then an editevent where a user
|
||
|
|
# removes random 8bit data won't remove the metadata. not that
|
||
|
|
# that matters much. but having this here won't hurt. false
|
||
|
|
# meta-data isn't saved anyway. so the only point of this next
|
||
|
|
# line is making the metadata be deleted on edit.
|
||
|
|
$req->{'props'}->{'unknown8bit'} = 0;
|
||
|
|
|
||
|
|
# we don't want attackers sending something that looks like gzipped data
|
||
|
|
# in protocol version 0 (unknown8bit allowed), otherwise they might
|
||
|
|
# inject a 100MB string of single letters in a few bytes.
|
||
|
|
return fail( $err, 208, "Cannot send gzipped data" )
|
||
|
|
if substr( $req->{'event'}, 0, 2 ) eq "\037\213";
|
||
|
|
|
||
|
|
# non-ASCII?
|
||
|
|
unless (
|
||
|
|
$flags->{'use_old_content'}
|
||
|
|
|| ( LJ::is_ascii( $req->{'event'} )
|
||
|
|
&& LJ::is_ascii( $req->{'subject'} )
|
||
|
|
&& LJ::is_ascii( join( ' ', values %{ $req->{'props'} } ) ) )
|
||
|
|
)
|
||
|
|
{
|
||
|
|
|
||
|
|
if ( $req->{ver} < 1 ) { # client doesn't support Unicode
|
||
|
|
# only people should have unknown8bit entries.
|
||
|
|
my $uowner = $flags->{u_owner} || $flags->{u};
|
||
|
|
return fail( $err, 207,
|
||
|
|
'Posting in a community with international or special characters require a Unicode-capable LiveJournal client. Download one at http://www.livejournal.com/download/.'
|
||
|
|
) if !$uowner->is_person;
|
||
|
|
}
|
||
|
|
|
||
|
|
# validate that the text is valid UTF-8
|
||
|
|
if ( !LJ::text_in( $req->{subject} )
|
||
|
|
|| !LJ::text_in( $req->{event} )
|
||
|
|
|| grep { !LJ::text_in($_) } values %{ $req->{props} } )
|
||
|
|
{
|
||
|
|
return fail( $err, 208, "The text entered is not a valid UTF-8 stream" );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# trim to column width
|
||
|
|
|
||
|
|
# we did a quick check for number of bytes earlier
|
||
|
|
# this one also handles the case of too many characters,
|
||
|
|
# even if we'd be within the byte limit
|
||
|
|
my $did_trim = 0;
|
||
|
|
$req->{'event'} = LJ::text_trim( $req->{'event'}, LJ::BMAX_EVENT, LJ::CMAX_EVENT, \$did_trim );
|
||
|
|
return fail( $err, 409 ) if $did_trim;
|
||
|
|
|
||
|
|
$did_trim = 0;
|
||
|
|
$req->{'subject'} =
|
||
|
|
LJ::text_trim( $req->{'subject'}, LJ::BMAX_SUBJECT, LJ::CMAX_SUBJECT, \$did_trim );
|
||
|
|
return fail( $err, 411 ) if $did_trim && !$flags->{allow_truncated_subject};
|
||
|
|
|
||
|
|
foreach ( keys %{ $req->{'props'} } ) {
|
||
|
|
|
||
|
|
# do not trim this property, as it's magical and handled later
|
||
|
|
next if $_ eq 'taglist';
|
||
|
|
|
||
|
|
# Allow syn_links and syn_ids the full width of the prop, to avoid truncating long URLS
|
||
|
|
if ( $_ eq 'syn_link' || $_ eq 'syn_id' ) {
|
||
|
|
$req->{'props'}->{$_} = LJ::text_trim( $req->{'props'}->{$_}, LJ::BMAX_PROP );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$req->{'props'}->{$_} =
|
||
|
|
LJ::text_trim( $req->{'props'}->{$_}, LJ::BMAX_PROP, LJ::CMAX_PROP );
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
## handle meta-data (properties)
|
||
|
|
LJ::load_props("log");
|
||
|
|
|
||
|
|
my $allow_system = $flags->{allow_system} || {};
|
||
|
|
foreach my $pname ( keys %{ $req->{'props'} } ) {
|
||
|
|
my $p = LJ::get_prop( "log", $pname );
|
||
|
|
|
||
|
|
# does the property even exist?
|
||
|
|
unless ($p) {
|
||
|
|
$pname =~ s/[^\w]//g;
|
||
|
|
return fail( $err, 205, $pname );
|
||
|
|
}
|
||
|
|
|
||
|
|
# This is a system logprop
|
||
|
|
# fail with unknown metadata here?
|
||
|
|
if ( $p->{ownership} eq 'system' && !( $allow_system == 1 || $allow_system->{$pname} ) ) {
|
||
|
|
$pname =~ s/[^\w]//g;
|
||
|
|
return fail( $err, 205, $pname );
|
||
|
|
}
|
||
|
|
|
||
|
|
# don't validate its type if it's 0 or undef (deleting)
|
||
|
|
next unless ( $req->{'props'}->{$pname} );
|
||
|
|
|
||
|
|
my $ptype = $p->{'datatype'};
|
||
|
|
my $val = $req->{'props'}->{$pname};
|
||
|
|
|
||
|
|
if ( $ptype eq "bool" && $val !~ /^[01]$/ ) {
|
||
|
|
return fail( $err, 204, "Property \"$pname\" should be 0 or 1" );
|
||
|
|
}
|
||
|
|
if ( $ptype eq "num" && $val =~ /[^\d]/ ) {
|
||
|
|
return fail( $err, 204, "Property \"$pname\" should be numeric" );
|
||
|
|
}
|
||
|
|
if ( $pname eq "current_coords" && !eval { LJ::Location->new( coords => $val ) } ) {
|
||
|
|
return fail( $err, 204, "Property \"current_coords\" has invalid value" );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# check props for inactive userpic
|
||
|
|
if ( ( my $pickwd = $req->{'props'}->{'picture_keyword'} ) and !$flags->{allow_inactive} ) {
|
||
|
|
my $pic = LJ::Userpic->new_from_keyword( $flags->{u}, $pickwd );
|
||
|
|
|
||
|
|
# need to make sure they aren't trying to post with an inactive keyword,
|
||
|
|
# but also we don't want to allow them to post with a keyword that has
|
||
|
|
# no pic at all to prevent them from deleting the keyword, posting, then
|
||
|
|
# adding it back with the editicons page
|
||
|
|
delete $req->{props}->{picture_keyword}
|
||
|
|
unless $pic && $pic->state ne 'I';
|
||
|
|
}
|
||
|
|
|
||
|
|
# validate incoming list of tags
|
||
|
|
return fail( $err, 211 )
|
||
|
|
if $req->{props}->{taglist}
|
||
|
|
&& !LJ::Tags::is_valid_tagstring( $req->{props}->{taglist} );
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub schedule_xposts {
|
||
|
|
my ( $u, $ditemid, $deletep, $fn ) = @_;
|
||
|
|
return unless LJ::isu($u) && $ditemid > 0;
|
||
|
|
return unless $fn && ref $fn eq 'CODE';
|
||
|
|
|
||
|
|
my ( @successes, @failures );
|
||
|
|
my @accounts = DW::External::Account->get_external_accounts($u);
|
||
|
|
|
||
|
|
foreach my $acct (@accounts) {
|
||
|
|
my ( $xpostp, $info ) = $fn->($acct);
|
||
|
|
next unless $xpostp;
|
||
|
|
|
||
|
|
my $jobargs = {
|
||
|
|
uid => $u->userid,
|
||
|
|
accountid => $acct->acctid,
|
||
|
|
ditemid => $ditemid + 0,
|
||
|
|
delete => $deletep ? 1 : 0,
|
||
|
|
%{$info}
|
||
|
|
};
|
||
|
|
|
||
|
|
DW::TaskQueue->dispatch( DW::Task::XPost->new($jobargs) );
|
||
|
|
push @successes, $acct;
|
||
|
|
}
|
||
|
|
|
||
|
|
return ( \@successes, \@failures );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub postevent {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
un_utf8_request($req);
|
||
|
|
|
||
|
|
# if the importer is calling us, we want to allow it to post in all but the most extreme
|
||
|
|
# of cases. and even then, we try our hardest to allow content to be posted. setting this
|
||
|
|
# flag will bypass a lot of the safety restrictions about who can post where and when, so
|
||
|
|
# we trust the importer to be intelligent about this. (And if you aren't the importer, don't
|
||
|
|
# use this option!!!)
|
||
|
|
my $importer_bypass = $flags->{importer_bypass} ? 1 : 0;
|
||
|
|
if ($importer_bypass) {
|
||
|
|
$flags->{nomod} = 1;
|
||
|
|
$flags->{ignore_tags_max} = 1;
|
||
|
|
$flags->{nonotify} = 1;
|
||
|
|
$flags->{noauth} = 1;
|
||
|
|
$flags->{usejournal_okay} = 1;
|
||
|
|
$flags->{no_xpost} = 1;
|
||
|
|
$flags->{create_unknown_picture_mapid} = 1;
|
||
|
|
$flags->{allow_inactive} = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return undef
|
||
|
|
unless LJ::Hooks::run_hook( 'post_noauth', $req ) || authenticate( $req, $err, $flags );
|
||
|
|
|
||
|
|
# if going through mod queue, then we know they're permitted to post at least this entry
|
||
|
|
return undef unless check_altusage( $req, $err, $flags ) || $flags->{nomod};
|
||
|
|
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
my $ownerid = $flags->{'ownerid'} + 0;
|
||
|
|
my $uowner = $flags->{'u_owner'} || $u;
|
||
|
|
|
||
|
|
# Make sure we have a real user object here
|
||
|
|
$uowner = LJ::want_user($uowner) unless LJ::isu($uowner);
|
||
|
|
my $clusterid = $uowner->{'clusterid'};
|
||
|
|
|
||
|
|
my $dbh = LJ::get_db_writer();
|
||
|
|
my $dbcm = LJ::get_cluster_master($uowner);
|
||
|
|
|
||
|
|
return fail( $err, 306 ) unless $dbh && $dbcm && $uowner->writer;
|
||
|
|
return fail( $err, 200 ) unless $req->{'event'} =~ /\S/;
|
||
|
|
|
||
|
|
### make sure community journals don't post
|
||
|
|
return fail( $err, 150 ) if $u->is_community;
|
||
|
|
|
||
|
|
# suspended users can't post
|
||
|
|
return fail( $err, 305 ) if !$importer_bypass && $u->is_suspended;
|
||
|
|
|
||
|
|
# memorials can't post
|
||
|
|
return fail( $err, 309 ) if !$importer_bypass && $u->is_memorial;
|
||
|
|
|
||
|
|
# locked accounts can't post
|
||
|
|
return fail( $err, 308 ) if !$importer_bypass && $u->is_locked;
|
||
|
|
|
||
|
|
# check the journal's read-only bit
|
||
|
|
return fail( $err, 306 ) if $uowner->is_readonly;
|
||
|
|
|
||
|
|
# is the user allowed to post?
|
||
|
|
return fail( $err, 404, $LJ::MSG_NO_POST ) unless $importer_bypass || $u->can_post;
|
||
|
|
|
||
|
|
# read-only accounts can't post
|
||
|
|
return fail( $err, 316 ) if $u->is_readonly;
|
||
|
|
|
||
|
|
# read-only accounts can't be posted to
|
||
|
|
return fail( $err, 317 ) if $uowner->is_readonly;
|
||
|
|
|
||
|
|
# can't post to deleted/suspended community
|
||
|
|
return fail( $err, 307 ) unless $importer_bypass || $uowner->is_visible;
|
||
|
|
|
||
|
|
# must have a validated email address to post to a community
|
||
|
|
# unless this is approved from the mod queue (we'll error out initially, but in case they change later)
|
||
|
|
return fail( $err, 155,
|
||
|
|
"You must have an authenticated email address in order to post to another account" )
|
||
|
|
unless $u->equals($uowner) || $u->{'status'} eq 'A' || $flags->{'nomod'};
|
||
|
|
|
||
|
|
return fail( $err, 155, "You must confirm your email address before posting." )
|
||
|
|
if $u->{'status'} eq 'N' && !$importer_bypass && !$u->is_syndicated && !$LJ::_T_CONFIG;
|
||
|
|
|
||
|
|
# post content too large
|
||
|
|
# NOTE: requires $req->{event} be binary data, but we've already
|
||
|
|
# removed the utf-8 flag in the XML-RPC path, and it never gets
|
||
|
|
# set in the "flat" protocol path.
|
||
|
|
return fail( $err, 409 ) if length( $req->{'event'} ) >= LJ::BMAX_EVENT;
|
||
|
|
|
||
|
|
my $time_was_faked = 0;
|
||
|
|
my $offset = 0; # assume gmt at first.
|
||
|
|
|
||
|
|
if ( defined $req->{'tz'} ) {
|
||
|
|
if ( $req->{tz} eq 'guess' ) {
|
||
|
|
LJ::get_timezone( $u, \$offset, \$time_was_faked );
|
||
|
|
}
|
||
|
|
elsif ( $req->{'tz'} =~ /^[+\-]\d\d\d\d$/ ) {
|
||
|
|
|
||
|
|
# FIXME we ought to store this timezone and make use of it somehow.
|
||
|
|
$offset = $req->{'tz'} / 100.0;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return fail( $err, 203, "Invalid tz" );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( defined $req->{'tz'} and not grep { defined $req->{$_} } qw(year mon day hour min) ) {
|
||
|
|
my @ltime = gmtime( time() + ( $offset * 3600 ) );
|
||
|
|
$req->{'year'} = $ltime[5] + 1900;
|
||
|
|
$req->{'mon'} = $ltime[4] + 1;
|
||
|
|
$req->{'day'} = $ltime[3];
|
||
|
|
$req->{'hour'} = $ltime[2];
|
||
|
|
$req->{'min'} = $ltime[1];
|
||
|
|
$time_was_faked = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return undef
|
||
|
|
unless common_event_validation( $req, $err, $flags );
|
||
|
|
|
||
|
|
# now we can move over to picture_mapid instead of picture_keyword if appropriate
|
||
|
|
if ( $req->{props} && defined $req->{props}->{picture_keyword} && $u->userpic_have_mapid ) {
|
||
|
|
$req->{props}->{picture_mapid} =
|
||
|
|
$u->get_mapid_from_keyword( $req->{props}->{picture_keyword},
|
||
|
|
create => $flags->{create_unknown_picture_mapid} || 0 );
|
||
|
|
delete $req->{props}->{picture_keyword};
|
||
|
|
}
|
||
|
|
|
||
|
|
# confirm we can add tags, at least
|
||
|
|
return fail( $err, 312 )
|
||
|
|
if $req->{props}
|
||
|
|
&& $req->{props}->{taglist}
|
||
|
|
&& !( $importer_bypass || LJ::Tags::can_add_tags( $uowner, $u ) );
|
||
|
|
|
||
|
|
my $event = $req->{'event'};
|
||
|
|
|
||
|
|
### allow for posting to journals that aren't yours (if you have permission)
|
||
|
|
my $posterid = $u->{'userid'} + 0;
|
||
|
|
|
||
|
|
# make the proper date format
|
||
|
|
my $eventtime = sprintf( "%04d-%02d-%02d %02d:%02d",
|
||
|
|
$req->{'year'}, $req->{'mon'}, $req->{'day'}, $req->{'hour'}, $req->{'min'} );
|
||
|
|
my $qeventtime = $dbh->quote($eventtime);
|
||
|
|
|
||
|
|
# load userprops all at once
|
||
|
|
my @poster_props = qw(newesteventtime dupsig_post);
|
||
|
|
my @owner_props = qw(newpost_minsecurity moderated);
|
||
|
|
|
||
|
|
$u->preload_props( @poster_props, @owner_props );
|
||
|
|
if ( $u->equals($uowner) ) {
|
||
|
|
$uowner->{$_} = $u->{$_} foreach @owner_props;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$uowner->preload_props(@owner_props);
|
||
|
|
}
|
||
|
|
|
||
|
|
my $qallowmask = $req->{'allowmask'} + 0;
|
||
|
|
my $security = "public";
|
||
|
|
my $uselogsec = 0;
|
||
|
|
if ( $req->{'security'} eq "usemask" || $req->{'security'} eq "private" ) {
|
||
|
|
$security = $req->{'security'};
|
||
|
|
}
|
||
|
|
if ( $req->{'security'} eq "usemask" ) {
|
||
|
|
$uselogsec = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# can't specify both a custom security and 'friends-only'
|
||
|
|
return fail( $err, 203, "Invalid friends group security set" )
|
||
|
|
if $qallowmask > 1 && $qallowmask % 2;
|
||
|
|
|
||
|
|
## if newpost_minsecurity is set, new entries have to be
|
||
|
|
## a minimum security level
|
||
|
|
$security = "private"
|
||
|
|
if $uowner->{'newpost_minsecurity'} eq "private";
|
||
|
|
( $security, $qallowmask ) = ( "usemask", 1 )
|
||
|
|
if $uowner->{'newpost_minsecurity'} eq "friends"
|
||
|
|
and $security eq "public";
|
||
|
|
|
||
|
|
my $qsecurity = $dbh->quote($security);
|
||
|
|
|
||
|
|
### make sure user can't post with "custom security" on communities
|
||
|
|
return fail( $err, 102 )
|
||
|
|
if $ownerid != $posterid && # community post
|
||
|
|
$req->{'security'} eq "usemask" && $qallowmask != 1;
|
||
|
|
|
||
|
|
## make sure user can't post with "private security" on communities they don't manage
|
||
|
|
return fail( $err, 106 )
|
||
|
|
if $ownerid != $posterid && # community post
|
||
|
|
$req->{'security'} eq "private"
|
||
|
|
&& !$u->can_manage($uowner);
|
||
|
|
|
||
|
|
# make sure this user isn't banned from posting here (if
|
||
|
|
# this is a community journal)
|
||
|
|
return fail( $err, 151 ) if $uowner->has_banned($u);
|
||
|
|
|
||
|
|
# don't allow backdated posts in communities
|
||
|
|
return fail( $err, 152 )
|
||
|
|
if ( $req->{props}->{opt_backdated}
|
||
|
|
&& !$importer_bypass
|
||
|
|
&& $uowner->is_community );
|
||
|
|
|
||
|
|
# do processing of embedded polls (doesn't add to database, just
|
||
|
|
# does validity checking)
|
||
|
|
my @polls = ();
|
||
|
|
if ( LJ::Poll->contains_new_poll( \$event ) ) {
|
||
|
|
return fail( $err, 301, "Your account type doesn't permit creating polls." )
|
||
|
|
unless ( $u->can_create_polls
|
||
|
|
|| ( $uowner->is_community && $uowner->can_create_polls ) );
|
||
|
|
|
||
|
|
my $error = "";
|
||
|
|
@polls = LJ::Poll->new_from_html(
|
||
|
|
\$event,
|
||
|
|
\$error,
|
||
|
|
{
|
||
|
|
'journalid' => $ownerid,
|
||
|
|
'posterid' => $posterid,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
return fail( $err, 103, $error ) if $error;
|
||
|
|
}
|
||
|
|
|
||
|
|
# convert RTE lj-embeds to normal lj-embeds
|
||
|
|
$event = LJ::EmbedModule->transform_rte_post($event);
|
||
|
|
|
||
|
|
# process module embedding
|
||
|
|
LJ::EmbedModule->parse_module_embed( $uowner, \$event );
|
||
|
|
|
||
|
|
my $now = $dbcm->selectrow_array("SELECT UNIX_TIMESTAMP()");
|
||
|
|
my $anum = int( rand(256) );
|
||
|
|
|
||
|
|
# by default we record the true reverse time that the item was entered.
|
||
|
|
# however, if backdate is on, we put the reverse time at the end of time
|
||
|
|
# (which makes it equivalent to 1969, but get_recent_items will never load
|
||
|
|
# it... where clause there is: < $LJ::EndOfTime). but this way we can
|
||
|
|
# have entries that don't show up on friends view, now that we don't have
|
||
|
|
# the hints table to not insert into.
|
||
|
|
my $rlogtime = $LJ::EndOfTime;
|
||
|
|
unless ( $req->{'props'}->{"opt_backdated"} ) {
|
||
|
|
$rlogtime -= $now;
|
||
|
|
}
|
||
|
|
my $logtime = "FROM_UNIXTIME($now)";
|
||
|
|
|
||
|
|
# this is when the entry was posted. for most cases this is accurate but in case
|
||
|
|
# we're using the importer in the community case, it will mess life up.
|
||
|
|
if ( $importer_bypass && $posterid != $ownerid ) {
|
||
|
|
$logtime = $qeventtime;
|
||
|
|
$rlogtime = "$LJ::EndOfTime - UNIX_TIMESTAMP($qeventtime)";
|
||
|
|
}
|
||
|
|
|
||
|
|
my $dupsig = Digest::MD5::md5_hex(
|
||
|
|
join( '', map { $req->{$_} } qw(subject event usejournal security allowmask) ) );
|
||
|
|
my $lock_key = "post-$ownerid";
|
||
|
|
|
||
|
|
# release our duplicate lock
|
||
|
|
my $release = sub { $dbcm->do( "SELECT RELEASE_LOCK(?)", undef, $lock_key ); };
|
||
|
|
|
||
|
|
# our own local version of fail that releases our lock first
|
||
|
|
my $fail = sub { $release->(); return fail(@_); };
|
||
|
|
|
||
|
|
my $res = {};
|
||
|
|
my $res_done = 0; # set true by getlock when post was duplicate, or error getting lock
|
||
|
|
|
||
|
|
my $getlock = sub {
|
||
|
|
my $r = $dbcm->selectrow_array( "SELECT GET_LOCK(?, 2)", undef, $lock_key );
|
||
|
|
unless ($r) {
|
||
|
|
$res = undef; # a failure case has an undef result
|
||
|
|
fail( $err, 503 ); # set error flag to "can't get lock";
|
||
|
|
$res_done = 1; # tell caller to bail out
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
# If we're the importer, don't do duplicate detection here; the importer already
|
||
|
|
# has tooling to do that to compare remote vs local
|
||
|
|
return if $importer_bypass;
|
||
|
|
|
||
|
|
my @parts = split( /:/, $u->{'dupsig_post'} );
|
||
|
|
if ( $parts[0] eq $dupsig ) {
|
||
|
|
|
||
|
|
# duplicate! let's make the client think this was just the
|
||
|
|
# normal first response.
|
||
|
|
$res->{'itemid'} = $parts[1];
|
||
|
|
$res->{'anum'} = $parts[2];
|
||
|
|
|
||
|
|
my $dup_entry =
|
||
|
|
LJ::Entry->new( $uowner, jitemid => $res->{'itemid'}, anum => $res->{'anum'} );
|
||
|
|
$res->{'url'} = $dup_entry->url;
|
||
|
|
|
||
|
|
$res_done = 1;
|
||
|
|
$release->();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
# if posting to a moderated community, store and bail out here
|
||
|
|
if ( $uowner->is_community && $uowner->{'moderated'} && !$flags->{'nomod'} ) {
|
||
|
|
|
||
|
|
# Don't moderate pre-approved users
|
||
|
|
my $dbh = LJ::get_db_writer();
|
||
|
|
my $relcount =
|
||
|
|
$dbh->selectrow_array( "SELECT COUNT(*) FROM reluser "
|
||
|
|
. "WHERE userid=$ownerid AND targetid=$posterid "
|
||
|
|
. "AND type IN ('N')" );
|
||
|
|
unless ($relcount) {
|
||
|
|
|
||
|
|
# moderation queue full?
|
||
|
|
my $modcount =
|
||
|
|
$dbcm->selectrow_array("SELECT COUNT(*) FROM modlog WHERE journalid=$ownerid");
|
||
|
|
return fail( $err, 407 ) if $modcount >= $uowner->count_max_mod_queue;
|
||
|
|
|
||
|
|
$modcount = $dbcm->selectrow_array( "SELECT COUNT(*) FROM modlog "
|
||
|
|
. "WHERE journalid=$ownerid AND posterid=$posterid" );
|
||
|
|
return fail( $err, 408 ) if $modcount >= $uowner->count_max_mod_queue_per_poster;
|
||
|
|
|
||
|
|
$req->{'_moderate'}->{'authcode'} = LJ::make_auth_code(15);
|
||
|
|
|
||
|
|
# create tag <lj-embed> from HTML-tag <embed>
|
||
|
|
LJ::EmbedModule->parse_module_embed( $uowner, \$req->{event} );
|
||
|
|
|
||
|
|
my $fr = $dbcm->quote( Storable::freeze($req) );
|
||
|
|
return fail( $err, 409 ) if length($fr) > 600_000;
|
||
|
|
|
||
|
|
# store
|
||
|
|
my $modid = LJ::alloc_user_counter( $uowner, "M" );
|
||
|
|
return fail( $err, 501 ) unless $modid;
|
||
|
|
|
||
|
|
$uowner->do(
|
||
|
|
"INSERT INTO modlog (journalid, modid, posterid, subject, logtime) "
|
||
|
|
. "VALUES ($ownerid, $modid, $posterid, ?, NOW())",
|
||
|
|
undef,
|
||
|
|
LJ::text_trim( $req->{'subject'}, 30, 0 )
|
||
|
|
);
|
||
|
|
return fail( $err, 501 ) if $uowner->err;
|
||
|
|
|
||
|
|
$uowner->do( "INSERT INTO modblob (journalid, modid, request_stor) "
|
||
|
|
. "VALUES ($ownerid, $modid, $fr)" );
|
||
|
|
if ( $uowner->err ) {
|
||
|
|
$uowner->do("DELETE FROM modlog WHERE journalid=$ownerid AND modid=$modid");
|
||
|
|
return fail( $err, 501 );
|
||
|
|
}
|
||
|
|
|
||
|
|
# expire mod_queue_count memcache
|
||
|
|
$uowner->memc_delete('mqcount');
|
||
|
|
|
||
|
|
# alert moderator(s)
|
||
|
|
my $mods = LJ::load_rel_user( $dbh, $ownerid, 'M' ) || [];
|
||
|
|
|
||
|
|
if (@$mods) {
|
||
|
|
my $modlist = LJ::load_userids(@$mods);
|
||
|
|
|
||
|
|
my @emails;
|
||
|
|
foreach my $mod ( values %$modlist ) {
|
||
|
|
next unless $mod->is_visible;
|
||
|
|
|
||
|
|
LJ::Event::CommunityModeratedEntryNew->new( $mod, $uowner, $modid )->fire;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $msg = translate( $u, "modpost", undef );
|
||
|
|
return { 'message' => $msg };
|
||
|
|
}
|
||
|
|
} # /moderated comms
|
||
|
|
|
||
|
|
# posting:
|
||
|
|
|
||
|
|
$getlock->();
|
||
|
|
return $res if $res_done;
|
||
|
|
|
||
|
|
# do rate-checking
|
||
|
|
if ( !$u->is_syndicated && !$u->rate_log( "post", 1 ) && !$importer_bypass ) {
|
||
|
|
return $fail->( $err, 405 );
|
||
|
|
}
|
||
|
|
|
||
|
|
my $jitemid = LJ::alloc_user_counter( $uowner, "L" );
|
||
|
|
return $fail->( $err, 501, "No itemid could be generated." ) unless $jitemid;
|
||
|
|
|
||
|
|
LJ::Entry->can("dostuff");
|
||
|
|
LJ::replycount_do( $uowner, $jitemid, "init" );
|
||
|
|
|
||
|
|
my $dberr;
|
||
|
|
$uowner->log2_do( \$dberr,
|
||
|
|
"INSERT INTO log2 (journalid, jitemid, posterid, eventtime, logtime, security, "
|
||
|
|
. "allowmask, replycount, year, month, day, revttime, rlogtime, anum) "
|
||
|
|
. "VALUES ($ownerid, $jitemid, $posterid, $qeventtime, $logtime, $qsecurity, $qallowmask, "
|
||
|
|
. "0, $req->{'year'}, $req->{'mon'}, $req->{'day'}, $LJ::EndOfTime-"
|
||
|
|
. "UNIX_TIMESTAMP($qeventtime), $rlogtime, $anum)" );
|
||
|
|
return $fail->( $err, 501, $dberr ) if $dberr;
|
||
|
|
|
||
|
|
LJ::MemCache::incr( [ $ownerid, "log2ct:$ownerid" ] );
|
||
|
|
$uowner->clear_daycounts( $qallowmask || $security );
|
||
|
|
|
||
|
|
# set userprops.
|
||
|
|
{
|
||
|
|
my %set_userprop;
|
||
|
|
|
||
|
|
# keep track of itemid/anum for later potential duplicates
|
||
|
|
$set_userprop{"dupsig_post"} = "$dupsig:$jitemid:$anum";
|
||
|
|
|
||
|
|
# record the eventtime of the last update (for own journals only)
|
||
|
|
$set_userprop{"newesteventtime"} = $eventtime
|
||
|
|
if $posterid == $ownerid
|
||
|
|
and not $req->{'props'}->{'opt_backdated'}
|
||
|
|
and not $time_was_faked;
|
||
|
|
|
||
|
|
$u->set_prop( \%set_userprop );
|
||
|
|
}
|
||
|
|
|
||
|
|
# end duplicate locking section
|
||
|
|
$release->();
|
||
|
|
|
||
|
|
my $ditemid = $jitemid * 256 + $anum;
|
||
|
|
|
||
|
|
### finish embedding stuff now that we have the itemid
|
||
|
|
{
|
||
|
|
### this should NOT return an error, and we're mildly fucked by now
|
||
|
|
### if it does (would have to delete the log row up there), so we're
|
||
|
|
### not going to check it for now.
|
||
|
|
|
||
|
|
my $error = "";
|
||
|
|
foreach my $poll (@polls) {
|
||
|
|
$poll->save_to_db(
|
||
|
|
journalid => $ownerid,
|
||
|
|
posterid => $posterid,
|
||
|
|
ditemid => $ditemid,
|
||
|
|
error => \$error,
|
||
|
|
);
|
||
|
|
|
||
|
|
my $pollid = $poll->pollid;
|
||
|
|
|
||
|
|
$event =~ s/<poll-placeholder>/<poll-$pollid>/;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#### /embedding
|
||
|
|
|
||
|
|
# record journal's disk usage
|
||
|
|
my $bytes = length($event) + length( $req->{'subject'} );
|
||
|
|
$uowner->dudata_set( 'L', $jitemid, $bytes );
|
||
|
|
|
||
|
|
$uowner->do(
|
||
|
|
"INSERT INTO logtext2 (journalid, jitemid, subject, event) "
|
||
|
|
. "VALUES ($ownerid, $jitemid, ?, ?)",
|
||
|
|
undef, $req->{'subject'}, LJ::text_compress($event)
|
||
|
|
);
|
||
|
|
if ( $uowner->err ) {
|
||
|
|
my $msg = $uowner->errstr;
|
||
|
|
LJ::delete_entry( $uowner, $jitemid ); # roll-back
|
||
|
|
return fail( $err, 501, "logtext:$msg" );
|
||
|
|
}
|
||
|
|
LJ::MemCache::set( [ $ownerid, "logtext:$clusterid:$ownerid:$jitemid" ],
|
||
|
|
[ $req->{'subject'}, $event ] );
|
||
|
|
|
||
|
|
# warn the user of any bad markup errors
|
||
|
|
my $clean_event = $event;
|
||
|
|
my $errref;
|
||
|
|
|
||
|
|
# TODO: accept editor prop and thread it through to the cleaner.
|
||
|
|
my $editor = undef;
|
||
|
|
LJ::CleanHTML::clean_event( \$clean_event, { errref => \$errref, editor => $editor } );
|
||
|
|
$res->{message} = translate(
|
||
|
|
$u, $errref,
|
||
|
|
{
|
||
|
|
aopts => "href='$LJ::SITEROOT/editjournal?journal="
|
||
|
|
. $uowner->user
|
||
|
|
. "&itemid=$ditemid'"
|
||
|
|
}
|
||
|
|
) if $errref;
|
||
|
|
|
||
|
|
# keep track of custom security stuff in other table.
|
||
|
|
if ($uselogsec) {
|
||
|
|
$uowner->do( "INSERT INTO logsec2 (journalid, jitemid, allowmask) "
|
||
|
|
. "VALUES ($ownerid, $jitemid, $qallowmask)" );
|
||
|
|
if ( $uowner->err ) {
|
||
|
|
my $msg = $uowner->errstr;
|
||
|
|
LJ::delete_entry( $uowner, $jitemid ); # roll-back
|
||
|
|
return fail( $err, 501, "logsec2:$msg" );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Entry tags
|
||
|
|
if ( $req->{props} && defined $req->{props}->{taglist} && $req->{props}->{taglist} ne '' ) {
|
||
|
|
|
||
|
|
# slightly misnamed, the taglist is/was normally a string, but now can also be an arrayref.
|
||
|
|
my $taginput = $req->{props}->{taglist};
|
||
|
|
|
||
|
|
my $tagerr = "";
|
||
|
|
my $logtag_opts = {
|
||
|
|
remote => $u,
|
||
|
|
ignore_max => $flags->{ignore_tags_max} ? 1 : 0,
|
||
|
|
force => $importer_bypass,
|
||
|
|
err_ref => \$tagerr,
|
||
|
|
};
|
||
|
|
|
||
|
|
if ( ref $taginput eq 'ARRAY' ) {
|
||
|
|
$logtag_opts->{set} = [@$taginput];
|
||
|
|
$req->{props}->{taglist} = join( ", ", @$taginput );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$logtag_opts->{set_string} = $taginput;
|
||
|
|
}
|
||
|
|
|
||
|
|
# Do not fail here; worst case we lose tags, but if we fail here we don't perform
|
||
|
|
# half of the processing below
|
||
|
|
LJ::Tags::update_logtags( $uowner, $jitemid, $logtag_opts );
|
||
|
|
|
||
|
|
# Propagate any "skippable" errors
|
||
|
|
$res->{message} = $tagerr if $tagerr;
|
||
|
|
}
|
||
|
|
|
||
|
|
# meta-data
|
||
|
|
if ( %{ $req->{'props'} } ) {
|
||
|
|
my $propset = {};
|
||
|
|
foreach my $pname ( keys %{ $req->{'props'} } ) {
|
||
|
|
next unless $req->{'props'}->{$pname};
|
||
|
|
next if $pname eq "revnum" || $pname eq "revtime";
|
||
|
|
my $p = LJ::get_prop( "log", $pname );
|
||
|
|
next unless $p;
|
||
|
|
next unless $req->{'props'}->{$pname};
|
||
|
|
$propset->{$pname} = $req->{'props'}->{$pname};
|
||
|
|
}
|
||
|
|
my %logprops;
|
||
|
|
LJ::set_logprop( $uowner, $jitemid, $propset, \%logprops ) if %$propset;
|
||
|
|
|
||
|
|
# if set_logprop modified props above, we can set the memcache key
|
||
|
|
# to be the hashref of modified props, since this is a new post
|
||
|
|
LJ::MemCache::set( [ $uowner->{'userid'}, "logprop:$uowner->{'userid'}:$jitemid" ],
|
||
|
|
\%logprops )
|
||
|
|
if %logprops;
|
||
|
|
}
|
||
|
|
|
||
|
|
$dbh->do("UPDATE userusage SET timeupdate=NOW(), lastitemid=$jitemid WHERE userid=$ownerid")
|
||
|
|
unless $flags->{'notimeupdate'};
|
||
|
|
LJ::MemCache::set( [ $ownerid, "tu:$ownerid" ], pack( "N", time() ), 30 * 60 );
|
||
|
|
|
||
|
|
# update timeupdate_public for stats page
|
||
|
|
if ( $security eq 'public' ) {
|
||
|
|
$dbh->do("UPDATE userusage SET timeupdate_public=NOW() WHERE userid=$ownerid")
|
||
|
|
unless $flags->{'notimeupdate'};
|
||
|
|
}
|
||
|
|
|
||
|
|
# argh, this is all too ugly. need to unify more postpost stuff into async
|
||
|
|
$u->invalidate_directory_record;
|
||
|
|
|
||
|
|
# Insert the slug (try to, this will fail if this slug is already used)
|
||
|
|
my $slug = LJ::canonicalize_slug( $req->{slug} );
|
||
|
|
if ( defined $slug && length $slug > 0 ) {
|
||
|
|
$u->do( 'INSERT INTO logslugs (journalid, jitemid, slug) VALUES (?, ?, ?)',
|
||
|
|
undef, $ownerid, $jitemid, $slug );
|
||
|
|
if ( $u->err ) {
|
||
|
|
$res->{message} ||= 'Sorry, it looks like that slug has already been used. '
|
||
|
|
. 'Your entry has been posted without a slug, but you can still edit it to add a unique slug.';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# if the post was public, and the user has not opted out, try to insert into the random table;
|
||
|
|
# We're doing a REPLACE INTO because chances are the user will already
|
||
|
|
# be in there (having posted less than 7 days ago).
|
||
|
|
if ( $security eq 'public' && !$u->prop('latest_optout') ) {
|
||
|
|
$u->do(
|
||
|
|
"REPLACE INTO random_user_set (posttime, userid, journaltype) VALUES (UNIX_TIMESTAMP(), ?, ?)",
|
||
|
|
undef, $uowner->{userid}, $uowner->{journaltype}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
my @jobs; # jobs to add into TaskQueue
|
||
|
|
|
||
|
|
my $entry = LJ::Entry->new( $uowner, jitemid => $jitemid, anum => $anum );
|
||
|
|
|
||
|
|
if ( $u->equals($uowner) && $req->{xpost} ne '0' && !$flags->{no_xpost} ) {
|
||
|
|
schedule_xposts( $u, $ditemid, 0, sub { ( (shift)->xpostbydefault, {} ) } );
|
||
|
|
}
|
||
|
|
|
||
|
|
# run local site-specific actions
|
||
|
|
LJ::Hooks::run_hooks(
|
||
|
|
"postpost",
|
||
|
|
{
|
||
|
|
'itemid' => $jitemid,
|
||
|
|
'anum' => $anum,
|
||
|
|
'journal' => $uowner,
|
||
|
|
'poster' => $u,
|
||
|
|
'event' => $event,
|
||
|
|
'eventtime' => $eventtime,
|
||
|
|
'subject' => $req->{'subject'},
|
||
|
|
'security' => $security,
|
||
|
|
'allowmask' => $qallowmask,
|
||
|
|
'props' => $req->{'props'},
|
||
|
|
'entry' => $entry,
|
||
|
|
'jobs' => \@jobs, # for hooks to push jobs onto
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
# cluster tracking
|
||
|
|
LJ::mark_user_active( $u, 'post' );
|
||
|
|
LJ::mark_user_active( $uowner, 'post' ) unless $u->equals($uowner);
|
||
|
|
|
||
|
|
DW::Stats::increment( 'dw.action.entry.post', 1,
|
||
|
|
[ 'journal_type:' . $uowner->journaltype_readable ] );
|
||
|
|
|
||
|
|
$res->{'itemid'} = $jitemid; # by request of mart
|
||
|
|
$res->{'anum'} = $anum;
|
||
|
|
$res->{'url'} = $entry->url;
|
||
|
|
|
||
|
|
# if the caller told us not to fire events (importer?) then skip the user events,
|
||
|
|
# but still fire the logging events
|
||
|
|
unless ( $flags->{nonotify} ) {
|
||
|
|
push @jobs, LJ::Event::JournalNewEntry->new($entry);
|
||
|
|
push @jobs, LJ::Event::OfficialPost->new($entry) if $uowner->is_official;
|
||
|
|
|
||
|
|
# latest posts feed update
|
||
|
|
DW::LatestFeed->new_item($entry);
|
||
|
|
}
|
||
|
|
|
||
|
|
# update the sphinx search engine
|
||
|
|
if ( @LJ::SPHINX_SEARCHD && !$importer_bypass ) {
|
||
|
|
push @jobs,
|
||
|
|
DW::Task::SphinxCopier->new(
|
||
|
|
{ userid => $uowner->id, jitemid => $jitemid, source => "entrynew" } );
|
||
|
|
}
|
||
|
|
|
||
|
|
DW::TaskQueue->dispatch(@jobs) if @jobs;
|
||
|
|
|
||
|
|
# To minimize impact on legacy code, let's make sure the entry object in
|
||
|
|
# memory has been populated with data. Easiest way to do that is to call
|
||
|
|
# one of the methods that loads the relevant row from the database.
|
||
|
|
$entry->valid;
|
||
|
|
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub editevent {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
my $res = {};
|
||
|
|
my $deleted = 0;
|
||
|
|
un_utf8_request($req);
|
||
|
|
|
||
|
|
my $add_message = sub {
|
||
|
|
my $new_message = shift;
|
||
|
|
if ( $res->{message} ) {
|
||
|
|
$res->{message} .= "\n\n" . $new_message;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$res->{message} = $new_message;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
|
||
|
|
# we check later that user owns entry they're modifying, so all
|
||
|
|
# we care about for check_altusage is that the target journal
|
||
|
|
# exists, and we want it to setup some data in $flags.
|
||
|
|
$flags->{'ignorecanuse'} = 1;
|
||
|
|
return undef unless check_altusage( $req, $err, $flags );
|
||
|
|
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
my $ownerid = $flags->{'ownerid'};
|
||
|
|
my $uowner = $flags->{'u_owner'} || $u;
|
||
|
|
|
||
|
|
# Make sure we have a user object here
|
||
|
|
$uowner = LJ::want_user($uowner) unless LJ::isu($uowner);
|
||
|
|
my $clusterid = $uowner->{'clusterid'};
|
||
|
|
my $posterid = $u->{'userid'};
|
||
|
|
my $qallowmask = $req->{'allowmask'} + 0;
|
||
|
|
my $sth;
|
||
|
|
|
||
|
|
my $itemid = $req->{'itemid'} + 0;
|
||
|
|
|
||
|
|
# check the journal's read-only bit
|
||
|
|
return fail( $err, 306 ) if $uowner->is_readonly;
|
||
|
|
|
||
|
|
# can't edit in deleted/suspended community
|
||
|
|
return fail( $err, 307 ) unless $uowner->is_visible || $uowner->is_readonly;
|
||
|
|
|
||
|
|
my $dbcm = LJ::get_cluster_master($uowner);
|
||
|
|
return fail( $err, 306 ) unless $dbcm;
|
||
|
|
|
||
|
|
# can't specify both a custom security and 'friends-only'
|
||
|
|
return fail( $err, 203, "Invalid friends group security set." )
|
||
|
|
if $qallowmask > 1 && $qallowmask % 2;
|
||
|
|
|
||
|
|
### make sure user can't post with "custom security" on communities
|
||
|
|
return fail( $err, 102 )
|
||
|
|
if $ownerid != $posterid && # community post
|
||
|
|
$req->{'security'} eq "usemask" && $qallowmask != 1;
|
||
|
|
|
||
|
|
## make sure user can't post with "private security" on communities they don't manage
|
||
|
|
return fail( $err, 106 )
|
||
|
|
if $ownerid != $posterid && # community post
|
||
|
|
$req->{'security'} eq "private"
|
||
|
|
&& !$u->can_manage($uowner);
|
||
|
|
|
||
|
|
# make sure the new entry's under the char limit
|
||
|
|
# NOTE: as in postevent, this requires $req->{event} to be binary data
|
||
|
|
# but we've already removed the utf-8 flag in the XML-RPC path, and it
|
||
|
|
# never gets set in the "flat" protocol path
|
||
|
|
return fail( $err, 409 ) if length( $req->{event} ) >= LJ::BMAX_EVENT;
|
||
|
|
|
||
|
|
# fetch the old entry from master database so we know what we
|
||
|
|
# really have to update later. usually people just edit one part,
|
||
|
|
# not every field in every table. reads are quicker than writes,
|
||
|
|
# so this is worth it.
|
||
|
|
my $oldevent =
|
||
|
|
$dbcm->selectrow_hashref( "SELECT journalid AS 'ownerid', posterid, eventtime, logtime, "
|
||
|
|
. "compressed, security, allowmask, year, month, day, "
|
||
|
|
. "rlogtime, anum FROM log2 WHERE journalid=$ownerid AND jitemid=$itemid" );
|
||
|
|
|
||
|
|
my $ditemid = $itemid * 256 + $oldevent->{anum};
|
||
|
|
|
||
|
|
( $oldevent->{subject}, $oldevent->{event} ) = $dbcm->selectrow_array(
|
||
|
|
"SELECT subject, event FROM logtext2 " . "WHERE journalid=$ownerid AND jitemid=$itemid" );
|
||
|
|
|
||
|
|
LJ::text_uncompress( \$oldevent->{'event'} );
|
||
|
|
|
||
|
|
# use_old_content indicates the subject and entry are not changing
|
||
|
|
if ( $flags->{'use_old_content'} ) {
|
||
|
|
$req->{'event'} = $oldevent->{event};
|
||
|
|
$req->{'subject'} = $oldevent->{subject};
|
||
|
|
}
|
||
|
|
|
||
|
|
# kill seconds in eventtime, since we don't use it, then we can use 'eq' and such
|
||
|
|
$oldevent->{'eventtime'} =~ s/:00$//;
|
||
|
|
|
||
|
|
### make sure this user is allowed to edit this entry
|
||
|
|
return fail( $err, 302 )
|
||
|
|
unless ( $ownerid == $oldevent->{'ownerid'} );
|
||
|
|
|
||
|
|
### load existing meta-data
|
||
|
|
my %curprops;
|
||
|
|
LJ::load_log_props2( $dbcm, $ownerid, [$itemid], \%curprops );
|
||
|
|
|
||
|
|
# xpost helper for later
|
||
|
|
my $schedule_xposts = sub {
|
||
|
|
my $xpost_string = $curprops{$itemid}->{xpost};
|
||
|
|
if ( $xpost_string && $u->equals($uowner) && $req->{xpost} ne '0' ) {
|
||
|
|
my $xpost_info = DW::External::Account->xpost_string_to_hash($xpost_string);
|
||
|
|
schedule_xposts( $u, $ditemid, $deleted,
|
||
|
|
sub { ( $xpost_info->{ (shift)->acctid }, {} ) } );
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
### what can they do to somebody elses entry? (in shared journal)
|
||
|
|
### can edit it if they own or maintain the journal, but not if the journal is read-only
|
||
|
|
if ( $posterid != $oldevent->{'posterid'} || $u->is_readonly || $uowner->is_readonly ) {
|
||
|
|
## deleting.
|
||
|
|
return fail( $err, 304 )
|
||
|
|
if $req->{'event'} !~ /\S/ && !$u->can_manage($uowner);
|
||
|
|
|
||
|
|
## editing:
|
||
|
|
if ( $req->{'event'} =~ /\S/ ) {
|
||
|
|
return fail( $err, 303 ) if $posterid != $oldevent->{'posterid'};
|
||
|
|
return fail( $err, 318 ) if $u->is_readonly;
|
||
|
|
return fail( $err, 319 ) if $uowner->is_readonly;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# simple logic for deleting an entry
|
||
|
|
if ( !$flags->{'use_old_content'} && $req->{'event'} !~ /\S/ ) {
|
||
|
|
$deleted = 1;
|
||
|
|
|
||
|
|
# if their newesteventtime prop equals the time of the one they're deleting
|
||
|
|
# then delete their newesteventtime.
|
||
|
|
if ( $u->equals($uowner) ) {
|
||
|
|
$u->preload_props( { use_master => 1 }, "newesteventtime" );
|
||
|
|
if ( $u->{'newesteventtime'} eq $oldevent->{'eventtime'} ) {
|
||
|
|
$u->set_prop( "newesteventtime", undef );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# log this event, unless noauth is on, which means it is being done internally and we should
|
||
|
|
# rely on them to log why they're deleting the entry if they need to. that way we don't have
|
||
|
|
# double entries, and we have as much information available as possible at the location the
|
||
|
|
# delete is initiated.
|
||
|
|
$uowner->log_event(
|
||
|
|
'delete_entry',
|
||
|
|
{
|
||
|
|
remote => $u,
|
||
|
|
actiontarget => $ditemid,
|
||
|
|
method => 'protocol',
|
||
|
|
}
|
||
|
|
) unless $flags->{noauth};
|
||
|
|
|
||
|
|
LJ::delete_entry( $uowner, $req->{'itemid'}, 'quick', $oldevent->{'anum'} );
|
||
|
|
|
||
|
|
# clear their duplicate protection, so they can later repost
|
||
|
|
# what they just deleted. (or something... probably rare.)
|
||
|
|
$u->set_prop( "dupsig_post", undef );
|
||
|
|
$uowner->clear_daycounts( $qallowmask || $req->{security} );
|
||
|
|
|
||
|
|
# pass the delete
|
||
|
|
$schedule_xposts->();
|
||
|
|
|
||
|
|
$res = { itemid => $itemid, anum => $oldevent->{anum} };
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
# now make sure the new entry text isn't $CannotBeShown
|
||
|
|
return fail( $err, 210 )
|
||
|
|
if $req->{event} eq $CannotBeShown;
|
||
|
|
|
||
|
|
# don't allow backdated posts in communities... unless this is an import
|
||
|
|
if ( $req->{props}->{opt_backdated} && $uowner->is_community ) {
|
||
|
|
return fail( $err, 152 )
|
||
|
|
unless $curprops{$itemid}->{import_source};
|
||
|
|
}
|
||
|
|
|
||
|
|
# make year/mon/day/hour/min optional in an edit event,
|
||
|
|
# and just inherit their old values
|
||
|
|
{
|
||
|
|
$oldevent->{'eventtime'} =~ /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d)/;
|
||
|
|
$req->{'year'} = $1 unless defined $req->{'year'};
|
||
|
|
$req->{'mon'} = $2 + 0 unless defined $req->{'mon'};
|
||
|
|
$req->{'day'} = $3 + 0 unless defined $req->{'day'};
|
||
|
|
$req->{'hour'} = $4 + 0 unless defined $req->{'hour'};
|
||
|
|
$req->{'min'} = $5 + 0 unless defined $req->{'min'};
|
||
|
|
}
|
||
|
|
|
||
|
|
# updating an entry:
|
||
|
|
return undef
|
||
|
|
unless common_event_validation( $req, $err, $flags );
|
||
|
|
|
||
|
|
# now we can move over to picture_mapid instead of picture_keyword if appropriate
|
||
|
|
if ( $req->{props} && defined $req->{props}->{picture_keyword} && $u->userpic_have_mapid ) {
|
||
|
|
$req->{props}->{picture_mapid} = '';
|
||
|
|
$req->{props}->{picture_mapid} =
|
||
|
|
$u->get_mapid_from_keyword( $req->{props}->{picture_keyword},
|
||
|
|
create => $flags->{create_unknown_picture_mapid} || 0 )
|
||
|
|
if defined $req->{props}->{picture_keyword};
|
||
|
|
delete $req->{props}->{picture_keyword};
|
||
|
|
}
|
||
|
|
|
||
|
|
## handle meta-data (properties)
|
||
|
|
# FIXME: Hey... I think this just throws the changed props away???? -NF
|
||
|
|
my %props_byname = ();
|
||
|
|
foreach my $key ( keys %{ $req->{'props'} } ) {
|
||
|
|
## changing to something else?
|
||
|
|
if ( $curprops{$itemid}->{$key} ne $req->{'props'}->{$key} ) {
|
||
|
|
$props_byname{$key} = $req->{'props'}->{$key};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# additionally, if the 'opt_nocomments_maintainer' prop was set before and the poster now sets
|
||
|
|
# 'opt_nocomments' to 0 again, 'opt_nocomments_maintainer' should be set to 0 again, as well
|
||
|
|
# so comments are enabled again
|
||
|
|
$req->{props}->{opt_nocomments_maintainer} = 0
|
||
|
|
if defined $req->{props}->{opt_nocomments} && !$req->{props}->{opt_nocomments};
|
||
|
|
|
||
|
|
my $event = $req->{'event'};
|
||
|
|
my $owneru = LJ::load_userid($ownerid);
|
||
|
|
$event = LJ::EmbedModule->transform_rte_post($event);
|
||
|
|
LJ::EmbedModule->parse_module_embed( $owneru, \$event );
|
||
|
|
|
||
|
|
my $bytes = length($event) + length( $req->{'subject'} );
|
||
|
|
|
||
|
|
my $eventtime =
|
||
|
|
sprintf( "%04d-%02d-%02d %02d:%02d", map { $req->{$_} } qw(year mon day hour min) );
|
||
|
|
my $qeventtime = $dbcm->quote($eventtime);
|
||
|
|
|
||
|
|
# preserve old security by default, use user supplied if it's understood
|
||
|
|
my $security = $oldevent->{security};
|
||
|
|
$security = $req->{security}
|
||
|
|
if $req->{security}
|
||
|
|
&& $req->{security} =~ /^(?:public|private|usemask)$/;
|
||
|
|
|
||
|
|
my $do_tags = $req->{props} && defined $req->{props}->{taglist};
|
||
|
|
my $do_tags_security;
|
||
|
|
my $entry_tags;
|
||
|
|
|
||
|
|
if ( $oldevent->{security} ne $security || $qallowmask != $oldevent->{allowmask} ) {
|
||
|
|
|
||
|
|
# FIXME: this is a hopefully temporary hack which deletes tags from the entry
|
||
|
|
# when the security has changed. the real fix is to make update_logtags aware
|
||
|
|
# of security changes so it can update logkwsum appropriately.
|
||
|
|
|
||
|
|
# we need to fix security on this entry's tags; if the user didn't give us a
|
||
|
|
# tag list to work with, we use the existing tags on this entry
|
||
|
|
unless ($do_tags) {
|
||
|
|
$entry_tags = LJ::Tags::get_logtags( $uowner, $itemid );
|
||
|
|
$entry_tags = $entry_tags->{$itemid};
|
||
|
|
$entry_tags = join( ',', sort values %{ $entry_tags || {} } );
|
||
|
|
$req->{props}->{taglist} = $entry_tags;
|
||
|
|
}
|
||
|
|
|
||
|
|
# FIXME: temporary hack until we can make update_logtags recognize entry security edits
|
||
|
|
if ( LJ::Tags::can_control_tags( $uowner, $u ) || LJ::Tags::can_add_tags( $uowner, $u ) ) {
|
||
|
|
my $delete = LJ::Tags::delete_logtags( $uowner, $itemid );
|
||
|
|
$do_tags_security = 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $qyear = $req->{'year'} + 0;
|
||
|
|
my $qmonth = $req->{'mon'} + 0;
|
||
|
|
my $qday = $req->{'day'} + 0;
|
||
|
|
|
||
|
|
if ( $eventtime ne $oldevent->{'eventtime'}
|
||
|
|
|| $security ne $oldevent->{'security'}
|
||
|
|
|| ( !$curprops{$itemid}->{opt_backdated} && $req->{props}{opt_backdated} )
|
||
|
|
|| $qallowmask != $oldevent->{'allowmask'} )
|
||
|
|
{
|
||
|
|
# are they changing their most recent post?
|
||
|
|
if ( $u->equals($uowner)
|
||
|
|
&& $u->prop("newesteventtime") eq $oldevent->{eventtime} )
|
||
|
|
{
|
||
|
|
|
||
|
|
if ( !$curprops{$itemid}->{opt_backdated} && $req->{props}{opt_backdated} ) {
|
||
|
|
|
||
|
|
# if they set the backdated flag, then we no longer know
|
||
|
|
# the newesteventtime.
|
||
|
|
$u->set_prop( "newesteventtime", undef );
|
||
|
|
}
|
||
|
|
elsif ( $eventtime ne $oldevent->{eventtime} ) {
|
||
|
|
|
||
|
|
# otherwise, if they changed time on this event,
|
||
|
|
# the newesteventtime is this event's new time.
|
||
|
|
$u->set_prop( "newesteventtime", $eventtime );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $qsecurity = $uowner->quote($security);
|
||
|
|
my $dberr;
|
||
|
|
$uowner->log2_do( \$dberr,
|
||
|
|
"UPDATE log2 SET eventtime=$qeventtime, revttime=$LJ::EndOfTime-"
|
||
|
|
. "UNIX_TIMESTAMP($qeventtime), year=$qyear, month=$qmonth, day=$qday, "
|
||
|
|
. "security=$qsecurity, allowmask=$qallowmask WHERE journalid=$ownerid "
|
||
|
|
. "AND jitemid=$itemid" );
|
||
|
|
return fail( $err, 501, $dberr ) if $dberr;
|
||
|
|
|
||
|
|
# update memcached
|
||
|
|
my $sec = $qallowmask;
|
||
|
|
$sec = 0 if $security eq 'private';
|
||
|
|
$sec = $LJ::PUBLICBIT if $security eq 'public';
|
||
|
|
|
||
|
|
my $row = pack( $LJ::LOGMEMCFMT,
|
||
|
|
$oldevent->{'posterid'},
|
||
|
|
LJ::mysqldate_to_time( $eventtime, 1 ),
|
||
|
|
LJ::mysqldate_to_time( $oldevent->{'logtime'}, 1 ),
|
||
|
|
$sec, $ditemid );
|
||
|
|
|
||
|
|
LJ::MemCache::set( [ $ownerid, "log2:$ownerid:$itemid" ], $row );
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $security ne $oldevent->{'security'}
|
||
|
|
|| $qallowmask != $oldevent->{'allowmask'} )
|
||
|
|
{
|
||
|
|
if ( $security eq "public" || $security eq "private" ) {
|
||
|
|
$uowner->do("DELETE FROM logsec2 WHERE journalid=$ownerid AND jitemid=$itemid");
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$uowner->do( "REPLACE INTO logsec2 (journalid, jitemid, allowmask) "
|
||
|
|
. "VALUES ($ownerid, $itemid, $qallowmask)" );
|
||
|
|
}
|
||
|
|
return fail( $err, 501, $dbcm->errstr ) if $uowner->err;
|
||
|
|
}
|
||
|
|
|
||
|
|
LJ::MemCache::set( [ $ownerid, "logtext:$clusterid:$ownerid:$itemid" ],
|
||
|
|
[ $req->{'subject'}, $event ] );
|
||
|
|
|
||
|
|
if (
|
||
|
|
!$flags->{'use_old_content'}
|
||
|
|
&& ( $event ne $oldevent->{'event'}
|
||
|
|
|| $req->{'subject'} ne $oldevent->{'subject'} )
|
||
|
|
)
|
||
|
|
{
|
||
|
|
$uowner->do(
|
||
|
|
"UPDATE logtext2 SET subject=?, event=? "
|
||
|
|
. "WHERE journalid=$ownerid AND jitemid=$itemid",
|
||
|
|
undef, $req->{'subject'}, LJ::text_compress($event)
|
||
|
|
);
|
||
|
|
return fail( $err, 501, $uowner->errstr ) if $uowner->err;
|
||
|
|
|
||
|
|
# update disk usage
|
||
|
|
$uowner->dudata_set( 'L', $itemid, $bytes );
|
||
|
|
}
|
||
|
|
|
||
|
|
my $clean_event = $event;
|
||
|
|
my $errref;
|
||
|
|
|
||
|
|
# TODO: get editor prop from the new props (or current, if unchanged) and
|
||
|
|
# thread it through to the cleaner.
|
||
|
|
my $editor = undef;
|
||
|
|
LJ::CleanHTML::clean_event( \$clean_event, { errref => \$errref, editor => $editor } );
|
||
|
|
$add_message->(
|
||
|
|
translate(
|
||
|
|
$u, $errref,
|
||
|
|
{
|
||
|
|
aopts => "href='$LJ::SITEROOT/editjournal?journal="
|
||
|
|
. $uowner->user
|
||
|
|
. "&itemid=$ditemid'"
|
||
|
|
}
|
||
|
|
)
|
||
|
|
) if $errref;
|
||
|
|
|
||
|
|
# up the revision number
|
||
|
|
$req->{'props'}->{'revnum'} = ( $curprops{$itemid}->{'revnum'} || 0 ) + 1;
|
||
|
|
$req->{'props'}->{'revtime'} = time();
|
||
|
|
|
||
|
|
if ($do_tags) {
|
||
|
|
|
||
|
|
# we only want to update the tags if they've been modified
|
||
|
|
# so load the original entry tags
|
||
|
|
unless ($entry_tags) {
|
||
|
|
$entry_tags = LJ::Tags::get_logtags( $uowner, $itemid );
|
||
|
|
$entry_tags = $entry_tags->{$itemid};
|
||
|
|
$entry_tags = join( ',', sort values %{ $entry_tags || {} } );
|
||
|
|
}
|
||
|
|
|
||
|
|
my $request_tags = [];
|
||
|
|
LJ::Tags::is_valid_tagstring( $req->{props}->{taglist}, $request_tags );
|
||
|
|
$request_tags = join( ",", sort @{ $request_tags || [] } );
|
||
|
|
$do_tags = ( $request_tags ne $entry_tags );
|
||
|
|
}
|
||
|
|
|
||
|
|
# handle tags if they're defined
|
||
|
|
if ( $do_tags || $do_tags_security ) {
|
||
|
|
my $tagerr = "";
|
||
|
|
my $rv = LJ::Tags::update_logtags(
|
||
|
|
$uowner, $itemid,
|
||
|
|
{
|
||
|
|
set_string => $req->{props}->{taglist},
|
||
|
|
remote => $u,
|
||
|
|
err_ref => \$tagerr,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
# we only want to warn if we tried to edit the tags, not if we just tried to edit the security
|
||
|
|
$add_message->($tagerr) if $tagerr && $do_tags;
|
||
|
|
}
|
||
|
|
|
||
|
|
# handle the props
|
||
|
|
{
|
||
|
|
my $propset = {};
|
||
|
|
foreach my $pname ( keys %{ $req->{'props'} } ) {
|
||
|
|
my $p = LJ::get_prop( "log", $pname );
|
||
|
|
next unless $p;
|
||
|
|
$propset->{$pname} = $req->{'props'}->{$pname};
|
||
|
|
}
|
||
|
|
LJ::set_logprop( $uowner, $itemid, $propset );
|
||
|
|
}
|
||
|
|
|
||
|
|
# deal with backdated changes. if the entry's rlogtime is
|
||
|
|
# $EndOfTime, then it's backdated. if they want that off, need to
|
||
|
|
# reset rlogtime to real reverse log time. also need to set
|
||
|
|
# rlogtime to $EndOfTime if they're turning backdate on.
|
||
|
|
if ( $req->{'props'}->{'opt_backdated'} eq "1"
|
||
|
|
&& $oldevent->{'rlogtime'} != $LJ::EndOfTime )
|
||
|
|
{
|
||
|
|
my $dberr;
|
||
|
|
$uowner->log2_do( undef,
|
||
|
|
"UPDATE log2 SET rlogtime=$LJ::EndOfTime WHERE "
|
||
|
|
. "journalid=$ownerid AND jitemid=$itemid" );
|
||
|
|
return fail( $err, 501, $dberr ) if $dberr;
|
||
|
|
}
|
||
|
|
if ( $req->{'props'}->{'opt_backdated'} eq "0"
|
||
|
|
&& $oldevent->{'rlogtime'} == $LJ::EndOfTime )
|
||
|
|
{
|
||
|
|
my $dberr;
|
||
|
|
$uowner->log2_do( \$dberr,
|
||
|
|
"UPDATE log2 SET rlogtime=$LJ::EndOfTime-UNIX_TIMESTAMP(logtime) "
|
||
|
|
. "WHERE journalid=$ownerid AND jitemid=$itemid" );
|
||
|
|
return fail( $err, 501, $dberr ) if $dberr;
|
||
|
|
}
|
||
|
|
return fail( $err, 501, $dbcm->errstr ) if $dbcm->err;
|
||
|
|
|
||
|
|
$uowner->clear_daycounts( $oldevent->{allowmask} + 0 || $oldevent->{security},
|
||
|
|
$qallowmask || $security );
|
||
|
|
|
||
|
|
# Update the slug (try to, this will fail if this slug is already used). To
|
||
|
|
# delete or change the slug, you must pass this parameter in. If it is not
|
||
|
|
# present, we leave the slug alone.
|
||
|
|
if ( exists $req->{slug} ) {
|
||
|
|
LJ::MemCache::delete( [ $ownerid, "logslug:$ownerid:$itemid" ] );
|
||
|
|
$u->do( 'DELETE FROM logslugs WHERE journalid = ? AND jitemid = ?',
|
||
|
|
undef, $ownerid, $itemid );
|
||
|
|
|
||
|
|
my $slug = LJ::canonicalize_slug( $req->{slug} );
|
||
|
|
if ( defined $slug && length $slug > 0 ) {
|
||
|
|
$u->do( 'INSERT INTO logslugs (journalid, jitemid, slug) VALUES (?, ?, ?)',
|
||
|
|
undef, $ownerid, $itemid, $slug );
|
||
|
|
if ( $u->err ) {
|
||
|
|
$add_message->( 'Sorry, it looks like that slug has already been used. '
|
||
|
|
. 'Your entry has been updated, but you can still edit it again to add a unique slug.'
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $entry = LJ::Entry->new( $ownerid, jitemid => $itemid );
|
||
|
|
|
||
|
|
$res->{itemid} = $itemid;
|
||
|
|
if ( defined $oldevent->{'anum'} ) {
|
||
|
|
$res->{'anum'} = $oldevent->{'anum'};
|
||
|
|
$res->{'url'} = $entry->url;
|
||
|
|
}
|
||
|
|
|
||
|
|
DW::Stats::increment( 'dw.action.entry.edit', 1,
|
||
|
|
[ 'journal_type:' . $uowner->journaltype_readable ] );
|
||
|
|
|
||
|
|
# fired to copy the post over to the Sphinx search database
|
||
|
|
my @jobs;
|
||
|
|
if (@LJ::SPHINX_SEARCHD) {
|
||
|
|
push @jobs,
|
||
|
|
DW::Task::SphinxCopier->new(
|
||
|
|
{ userid => $ownerid, jitemid => $itemid, source => "entryedt" } );
|
||
|
|
}
|
||
|
|
LJ::Hooks::run_hooks( "editpost", $entry, \@jobs );
|
||
|
|
DW::TaskQueue->dispatch(@jobs) if @jobs;
|
||
|
|
|
||
|
|
# ensure our xposted edit fires
|
||
|
|
$schedule_xposts->();
|
||
|
|
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub getevents {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
return undef unless check_altusage( $req, $err, $flags );
|
||
|
|
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
my $uowner = $flags->{'u_owner'} || $u;
|
||
|
|
|
||
|
|
### shared-journal support
|
||
|
|
my $posterid = $u->{'userid'};
|
||
|
|
my $ownerid = $flags->{'ownerid'};
|
||
|
|
|
||
|
|
my $dbr = LJ::get_db_reader();
|
||
|
|
my $sth;
|
||
|
|
|
||
|
|
my $dbcr = LJ::get_cluster_reader($uowner);
|
||
|
|
return fail( $err, 502 ) unless $dbcr && $dbr;
|
||
|
|
|
||
|
|
# can't pull events from deleted/suspended journal
|
||
|
|
return fail( $err, 307 ) unless $uowner->is_visible || $uowner->is_readonly;
|
||
|
|
|
||
|
|
my $reject_code = $LJ::DISABLE_PROTOCOL{getevents};
|
||
|
|
if ( ref $reject_code eq "CODE" ) {
|
||
|
|
my $apache_r = eval { BML::get_request() };
|
||
|
|
my $errmsg = $reject_code->( $req, $flags, $apache_r );
|
||
|
|
if ($errmsg) { return fail( $err, "311", $errmsg ); }
|
||
|
|
}
|
||
|
|
|
||
|
|
# if this is on, we sort things different (logtime vs. posttime)
|
||
|
|
# to avoid timezone issues
|
||
|
|
my $is_community = $uowner->is_community;
|
||
|
|
|
||
|
|
# in some cases we'll use the master, to ensure there's no
|
||
|
|
# replication delay. useful cases: getting one item, use master
|
||
|
|
# since user might have just made a typo and realizes it as they
|
||
|
|
# post, or wants to append something they forgot, etc, etc. in
|
||
|
|
# other cases, slave is pretty sure to have it.
|
||
|
|
my $use_master = 0;
|
||
|
|
|
||
|
|
# the benefit of this mode over actually doing 'lastn/1' is
|
||
|
|
# the $use_master usage.
|
||
|
|
if ( $req->{'selecttype'} eq "one" && $req->{'itemid'} eq "-1" ) {
|
||
|
|
$req->{'selecttype'} = "lastn";
|
||
|
|
$req->{'howmany'} = 1;
|
||
|
|
undef $req->{'itemid'};
|
||
|
|
$use_master = 1; # see note above.
|
||
|
|
}
|
||
|
|
|
||
|
|
# build the query to get log rows. each selecttype branch is
|
||
|
|
# responsible for either populating the following 3 variables
|
||
|
|
# OR just populating $sql
|
||
|
|
my ( $orderby, $where, $limit );
|
||
|
|
my $sql;
|
||
|
|
if ( $req->{'selecttype'} eq "day" ) {
|
||
|
|
return fail( $err, 203 )
|
||
|
|
unless ( $req->{'year'} =~ /^\d\d\d\d$/
|
||
|
|
&& $req->{'month'} =~ /^\d\d?$/
|
||
|
|
&& $req->{'day'} =~ /^\d\d?$/
|
||
|
|
&& $req->{'month'} >= 1
|
||
|
|
&& $req->{'month'} <= 12
|
||
|
|
&& $req->{'day'} >= 1
|
||
|
|
&& $req->{'day'} <= 31 );
|
||
|
|
|
||
|
|
my $qyear = $dbr->quote( $req->{'year'} );
|
||
|
|
my $qmonth = $dbr->quote( $req->{'month'} );
|
||
|
|
my $qday = $dbr->quote( $req->{'day'} );
|
||
|
|
$where = "AND year=$qyear AND month=$qmonth AND day=$qday";
|
||
|
|
$limit = "LIMIT 200"; # FIXME: unhardcode this constant (also in ljviews.pl)
|
||
|
|
|
||
|
|
# see note above about why the sort order is different
|
||
|
|
$orderby = $is_community ? "ORDER BY logtime" : "ORDER BY eventtime";
|
||
|
|
}
|
||
|
|
elsif ( $req->{'selecttype'} eq "lastn" ) {
|
||
|
|
my $howmany = $req->{'howmany'} || 20;
|
||
|
|
if ( $howmany > 50 ) { $howmany = 50; }
|
||
|
|
$howmany = $howmany + 0;
|
||
|
|
$limit = "LIMIT $howmany";
|
||
|
|
|
||
|
|
# okay, follow me here... see how we add the revttime predicate
|
||
|
|
# even if no beforedate key is present? you're probably saying,
|
||
|
|
# what, huh? -- you're saying: "revttime > 0", that's like
|
||
|
|
# saying, "if entry occurred at all." yes yes, but that hints
|
||
|
|
# mysql's optimizer to use the right index.
|
||
|
|
my $rtime_after = 0;
|
||
|
|
my $rtime_what = $is_community ? "rlogtime" : "revttime";
|
||
|
|
if ( $req->{'beforedate'} ) {
|
||
|
|
return fail( $err, 203, "Invalid beforedate format." )
|
||
|
|
unless ( $req->{'beforedate'} =~ /^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$/ );
|
||
|
|
my $qd = $dbr->quote( $req->{'beforedate'} );
|
||
|
|
$rtime_after = "$LJ::EndOfTime-UNIX_TIMESTAMP($qd)";
|
||
|
|
}
|
||
|
|
$where .= "AND $rtime_what > $rtime_after ";
|
||
|
|
$orderby = "ORDER BY $rtime_what";
|
||
|
|
}
|
||
|
|
elsif ( $req->{'selecttype'} eq "one" ) {
|
||
|
|
my $id = $req->{'itemid'} + 0;
|
||
|
|
$where = "AND jitemid=$id";
|
||
|
|
}
|
||
|
|
elsif ( $req->{'selecttype'} eq "syncitems" ) {
|
||
|
|
return fail( $err, 506 ) unless LJ::is_enabled('syncitems');
|
||
|
|
my $date = $req->{'lastsync'} || "0000-00-00 00:00:00";
|
||
|
|
return fail( $err, 203, "Invalid syncitems date format" )
|
||
|
|
unless ( $date =~ /^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/ );
|
||
|
|
|
||
|
|
my $now = time();
|
||
|
|
|
||
|
|
# broken client loop prevention
|
||
|
|
if ( $req->{'lastsync'} ) {
|
||
|
|
my $pname = "rl_syncitems_getevents_loop";
|
||
|
|
|
||
|
|
# format is: time/date/time/date/time/date/... so split
|
||
|
|
# it into a hash, then delete pairs that are older than an hour
|
||
|
|
my %reqs = split( m!/!, $u->prop($pname) );
|
||
|
|
foreach ( grep { $_ < $now - 60 * 60 } keys %reqs ) {
|
||
|
|
delete $reqs{$_};
|
||
|
|
}
|
||
|
|
my $count = grep { $_ eq $date } values %reqs;
|
||
|
|
$reqs{$now} = $date;
|
||
|
|
if ( $count >= 2 ) {
|
||
|
|
|
||
|
|
# 2 prior, plus this one = 3 repeated requests for same synctime.
|
||
|
|
# their client is busted. (doesn't understand syncitems semantics)
|
||
|
|
return fail( $err, 406 );
|
||
|
|
}
|
||
|
|
$u->set_prop(
|
||
|
|
$pname,
|
||
|
|
join(
|
||
|
|
'/', map { $_, $reqs{$_} }
|
||
|
|
sort { $b <=> $a } keys %reqs
|
||
|
|
)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
my %item;
|
||
|
|
$sth = $dbcr->prepare(
|
||
|
|
"SELECT jitemid, logtime FROM log2 WHERE " . "journalid=? and logtime > ?" );
|
||
|
|
$sth->execute( $ownerid, $date );
|
||
|
|
while ( my ( $id, $dt ) = $sth->fetchrow_array ) {
|
||
|
|
$item{$id} = $dt;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $p_revtime = LJ::get_prop( "log", "revtime" );
|
||
|
|
$sth =
|
||
|
|
$dbcr->prepare( "SELECT jitemid, FROM_UNIXTIME(value) "
|
||
|
|
. "FROM logprop2 WHERE journalid=? "
|
||
|
|
. "AND propid=$p_revtime->{'id'} "
|
||
|
|
. "AND value+0 > UNIX_TIMESTAMP(?)" );
|
||
|
|
$sth->execute( $ownerid, $date );
|
||
|
|
while ( my ( $id, $dt ) = $sth->fetchrow_array ) {
|
||
|
|
$item{$id} = $dt;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $limit = 100;
|
||
|
|
my @ids = sort { $item{$a} cmp $item{$b} } keys %item;
|
||
|
|
if ( @ids > $limit ) { @ids = @ids[ 0 .. $limit - 1 ]; }
|
||
|
|
|
||
|
|
my $in = join( ',', @ids ) || "0";
|
||
|
|
$where = "AND jitemid IN ($in)";
|
||
|
|
}
|
||
|
|
elsif ( $req->{'selecttype'} eq "multiple" ) {
|
||
|
|
my @ids;
|
||
|
|
foreach my $num ( split( /\s*,\s*/, $req->{'itemids'} ) ) {
|
||
|
|
return fail( $err, 203, "Non-numeric itemid" ) unless $num =~ /^\d+$/;
|
||
|
|
push @ids, $num;
|
||
|
|
}
|
||
|
|
my $limit = 100;
|
||
|
|
return fail( $err, 209, "Can't retrieve more than $limit entries at once" )
|
||
|
|
if @ids > $limit;
|
||
|
|
my $in = join( ',', @ids );
|
||
|
|
$where = "AND jitemid IN ($in)";
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return fail( $err, 200, "Invalid selecttype." );
|
||
|
|
}
|
||
|
|
|
||
|
|
my $mask = 0;
|
||
|
|
if ( $u && ( $u->is_person || $u->is_identity ) && $posterid != $ownerid ) {
|
||
|
|
|
||
|
|
# if this is a community we're viewing, fake the mask to select on, as communities
|
||
|
|
# no longer have masks to users
|
||
|
|
if ( $uowner->is_community ) {
|
||
|
|
$mask = $u->member_of($uowner) ? 1 : 0;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$mask = $uowner->trustmask($u);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# check security!
|
||
|
|
my $secwhere;
|
||
|
|
if ( $u && $u->can_manage($uowner) ) {
|
||
|
|
|
||
|
|
# journal owners and community admins can see everything
|
||
|
|
$secwhere = "";
|
||
|
|
}
|
||
|
|
elsif ($mask) {
|
||
|
|
|
||
|
|
# can see public or things with them in the mask
|
||
|
|
$secwhere = "AND (security='public' OR (security='usemask' AND allowmask & $mask != 0))";
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# not on access list or a member; only see public.
|
||
|
|
$secwhere = "AND security='public'";
|
||
|
|
}
|
||
|
|
|
||
|
|
# common SQL template:
|
||
|
|
unless ($sql) {
|
||
|
|
$sql = "SELECT jitemid, eventtime, logtime, security, allowmask, anum, posterid "
|
||
|
|
. "FROM log2 WHERE journalid=$ownerid $where $secwhere $orderby $limit";
|
||
|
|
}
|
||
|
|
|
||
|
|
# whatever selecttype might have wanted us to use the master db.
|
||
|
|
$dbcr = LJ::get_cluster_def_reader($uowner) if $use_master;
|
||
|
|
|
||
|
|
return fail( $err, 502 ) unless $dbcr;
|
||
|
|
|
||
|
|
## load the log rows
|
||
|
|
( $sth = $dbcr->prepare($sql) )->execute;
|
||
|
|
return fail( $err, 501, $dbcr->errstr ) if $dbcr->err;
|
||
|
|
|
||
|
|
my $count = 0;
|
||
|
|
my @itemids = ();
|
||
|
|
my $res = {};
|
||
|
|
my $events = $res->{events} = [];
|
||
|
|
my %evt_from_itemid;
|
||
|
|
|
||
|
|
while ( my ( $itemid, $eventtime, $logtime, $sec, $mask, $anum, $jposterid ) =
|
||
|
|
$sth->fetchrow_array )
|
||
|
|
{
|
||
|
|
$count++;
|
||
|
|
my $evt = {};
|
||
|
|
$evt->{itemid} = $itemid;
|
||
|
|
push @itemids, $itemid;
|
||
|
|
|
||
|
|
$evt_from_itemid{$itemid} = $evt;
|
||
|
|
|
||
|
|
$evt->{eventtime} = $eventtime;
|
||
|
|
$evt->{logtime} = $logtime;
|
||
|
|
if ( $sec ne "public" ) {
|
||
|
|
$evt->{security} = $sec;
|
||
|
|
$evt->{allowmask} = $mask if $sec eq "usemask";
|
||
|
|
}
|
||
|
|
$evt->{anum} = $anum;
|
||
|
|
$evt->{poster} = LJ::get_username($jposterid)
|
||
|
|
if $jposterid != $ownerid;
|
||
|
|
$evt->{url} = LJ::item_link( $uowner, $itemid, $anum );
|
||
|
|
push @$events, $evt;
|
||
|
|
}
|
||
|
|
|
||
|
|
# load properties. Even if the caller doesn't want them, we need
|
||
|
|
# them in Unicode installations to recognize older 8bit non-UTF-8
|
||
|
|
# entries.
|
||
|
|
{
|
||
|
|
### do the properties now
|
||
|
|
$count = 0;
|
||
|
|
my %props = ();
|
||
|
|
LJ::load_log_props2( $dbcr, $ownerid, \@itemids, \%props );
|
||
|
|
|
||
|
|
# load the tags for these entries, unless told not to
|
||
|
|
unless ( $req->{notags} ) {
|
||
|
|
|
||
|
|
# construct %idsbycluster for the multi call to get these tags
|
||
|
|
my $tags = LJ::Tags::get_logtags( $uowner, \@itemids );
|
||
|
|
|
||
|
|
# add to props
|
||
|
|
foreach my $itemid (@itemids) {
|
||
|
|
next unless $tags->{$itemid};
|
||
|
|
$props{$itemid}->{taglist} = join( ', ', values %{ $tags->{$itemid} } );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach my $itemid ( keys %props ) {
|
||
|
|
|
||
|
|
# 'replycount' is a pseudo-prop, don't send it.
|
||
|
|
# FIXME: this goes away after we restructure APIs and
|
||
|
|
# replycounts cease being transferred in props
|
||
|
|
delete $props{$itemid}->{'replycount'};
|
||
|
|
|
||
|
|
# the xpost property is not something we should be distributing
|
||
|
|
# as it's a serialized string and confuses clients
|
||
|
|
delete $props{$itemid}->{xpost};
|
||
|
|
|
||
|
|
my $evt = $evt_from_itemid{$itemid};
|
||
|
|
$evt->{'props'} = {};
|
||
|
|
foreach my $name ( keys %{ $props{$itemid} } ) {
|
||
|
|
my $value = $props{$itemid}->{$name};
|
||
|
|
$value =~ s/\n/ /g;
|
||
|
|
$evt->{'props'}->{$name} = $value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
## load the text
|
||
|
|
my $text = LJ::DB::cond_no_cache(
|
||
|
|
$use_master,
|
||
|
|
sub {
|
||
|
|
return LJ::get_logtext2( $uowner, @itemids );
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
foreach my $i (@itemids) {
|
||
|
|
my $t = $text->{$i};
|
||
|
|
my $evt = $evt_from_itemid{$i};
|
||
|
|
|
||
|
|
# if they want subjects to be events, replace event
|
||
|
|
# with subject when requested.
|
||
|
|
if ( $req->{prefersubject} && length( $t->[0] ) ) {
|
||
|
|
$t->[1] = $t->[0]; # event = subject
|
||
|
|
$t->[0] = undef; # subject = undef
|
||
|
|
}
|
||
|
|
|
||
|
|
# re-generate the picture_keyword prop for the returned data, as a mapid will mean nothing
|
||
|
|
my $pu = $uowner;
|
||
|
|
$pu = LJ::load_user( $evt->{poster} ) if $evt->{poster};
|
||
|
|
$evt->{props}->{picture_keyword} =
|
||
|
|
$pu->get_keyword_from_mapid( $evt->{props}->{picture_mapid} )
|
||
|
|
if $pu->userpic_have_mapid;
|
||
|
|
|
||
|
|
# now that we have the subject, the event and the props,
|
||
|
|
# auto-translate them to UTF-8 if they're not in UTF-8.
|
||
|
|
if ( $req->{ver} >= 1 && $evt->{props}->{unknown8bit} ) {
|
||
|
|
LJ::item_toutf8( $uowner, \$t->[0], \$t->[1], $evt->{props} );
|
||
|
|
$evt->{converted_with_loss} = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $req->{'ver'} < 1 && !$evt->{'props'}->{'unknown8bit'} ) {
|
||
|
|
unless ( LJ::is_ascii( $t->[0] )
|
||
|
|
&& LJ::is_ascii( $t->[1] )
|
||
|
|
&& LJ::is_ascii( join( ' ', values %{ $evt->{'props'} } ) ) )
|
||
|
|
{
|
||
|
|
# we want to fail the client that wants to get this entry
|
||
|
|
# but we make an exception for selecttype=day, in order to allow at least
|
||
|
|
# viewing the daily summary
|
||
|
|
|
||
|
|
if ( $req->{'selecttype'} eq 'day' ) {
|
||
|
|
$t->[0] = $t->[1] = $CannotBeShown;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return fail( $err, 207,
|
||
|
|
"Cannot display/edit a Unicode post with a non-Unicode client. Please see $LJ::SITEROOT/support/encodings for more information."
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $t->[0] ) {
|
||
|
|
$t->[0] =~ s/[\r\n]/ /g;
|
||
|
|
$evt->{'subject'} = $t->[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
# truncate
|
||
|
|
if ( $req->{'truncate'} >= 4 ) {
|
||
|
|
my $original = $t->[1];
|
||
|
|
if ( $req->{'ver'} > 1 ) {
|
||
|
|
$t->[1] = LJ::text_trim( $t->[1], $req->{'truncate'} - 3, 0 );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$t->[1] = LJ::text_trim( $t->[1], 0, $req->{'truncate'} - 3 );
|
||
|
|
}
|
||
|
|
|
||
|
|
# only append the elipsis if the text was actually truncated
|
||
|
|
$t->[1] .= "..." if $t->[1] ne $original;
|
||
|
|
}
|
||
|
|
|
||
|
|
# line endings
|
||
|
|
$t->[1] =~ s/\r//g;
|
||
|
|
if ( $req->{'lineendings'} eq "unix" ) {
|
||
|
|
|
||
|
|
# do nothing. native format.
|
||
|
|
}
|
||
|
|
elsif ( $req->{'lineendings'} eq "mac" ) {
|
||
|
|
$t->[1] =~ s/\n/\r/g;
|
||
|
|
}
|
||
|
|
elsif ( $req->{'lineendings'} eq "space" ) {
|
||
|
|
$t->[1] =~ s/\n/ /g;
|
||
|
|
}
|
||
|
|
elsif ( $req->{'lineendings'} eq "dots" ) {
|
||
|
|
$t->[1] =~ s/\n/ ... /g;
|
||
|
|
}
|
||
|
|
else { # "pc" -- default
|
||
|
|
$t->[1] =~ s/\n/\r\n/g;
|
||
|
|
}
|
||
|
|
$evt->{'event'} = $t->[1];
|
||
|
|
}
|
||
|
|
|
||
|
|
# maybe we don't need the props after all
|
||
|
|
if ( $req->{'noprops'} ) {
|
||
|
|
foreach (@$events) { delete $_->{'props'}; }
|
||
|
|
}
|
||
|
|
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
# deprecated
|
||
|
|
sub editfriends {
|
||
|
|
return fail( $_[1], 504 );
|
||
|
|
}
|
||
|
|
|
||
|
|
# deprecated
|
||
|
|
sub editfriendgroups {
|
||
|
|
return fail( $_[1], 504 );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub editcircle {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
|
||
|
|
my $u = $flags->{u};
|
||
|
|
my $res = {};
|
||
|
|
|
||
|
|
if ( ref $req->{settrustgroups} eq 'HASH' ) {
|
||
|
|
while ( my ( $bit, $group ) = each %{ $req->{settrustgroups} } ) {
|
||
|
|
my $name = $group->{name};
|
||
|
|
my $sortorder = $group->{sort};
|
||
|
|
my $public = $group->{public};
|
||
|
|
my %params = (
|
||
|
|
id => $bit,
|
||
|
|
groupname => $name,
|
||
|
|
_force_create => 1
|
||
|
|
);
|
||
|
|
|
||
|
|
$params{sortorder} = $sortorder if defined $sortorder;
|
||
|
|
$params{is_public} = $public if defined $public;
|
||
|
|
$u->edit_trust_group(%params);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ref $req->{deletetrustgroups} eq 'ARRAY' ) {
|
||
|
|
foreach my $bit ( @{ $req->{deletetrustgroups} } ) {
|
||
|
|
$u->delete_trust_group( id => $bit );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ref $req->{setcontentfilters} eq 'HASH' ) {
|
||
|
|
while ( my ( $bit, $group ) = each %{ $req->{setcontentfilters} } ) {
|
||
|
|
my $name = $group->{name};
|
||
|
|
my $public = $group->{public};
|
||
|
|
my $sortorder = $group->{sort};
|
||
|
|
my $cf = $u->content_filters( id => $bit );
|
||
|
|
if ($cf) {
|
||
|
|
$cf->name($name)
|
||
|
|
if $name && $name ne $cf->name;
|
||
|
|
$cf->public($public)
|
||
|
|
if ( defined $public ) && $public ne $cf->public;
|
||
|
|
$cf->sortorder($sortorder)
|
||
|
|
if ( defined $sortorder ) && $sortorder ne $cf->sortorder;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
my $fid = $u->create_content_filter(
|
||
|
|
name => $name,
|
||
|
|
public => $public,
|
||
|
|
sortorder => $sortorder
|
||
|
|
);
|
||
|
|
my $added = {
|
||
|
|
id => $fid,
|
||
|
|
name => $name,
|
||
|
|
};
|
||
|
|
push @{ $res->{addedcontentfilters} }, $added;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ref $req->{deletecontentfilters} eq 'ARRAY' ) {
|
||
|
|
foreach my $bit ( @{ $req->{deletecontentfilters} } ) {
|
||
|
|
$u->delete_content_filter( id => $bit );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ref $req->{add} eq 'ARRAY' ) {
|
||
|
|
foreach my $row ( @{ $req->{add} } ) {
|
||
|
|
my $other_user = LJ::load_user( $row->{username} );
|
||
|
|
return fail( $err, 203 ) unless $other_user;
|
||
|
|
my $other_userid = $other_user->{userid};
|
||
|
|
|
||
|
|
if ( defined( $row->{groupmask} ) ) {
|
||
|
|
$u->add_edge(
|
||
|
|
$other_userid,
|
||
|
|
trust => {
|
||
|
|
mask => $row->{groupmask},
|
||
|
|
nonotify => 1,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
if ( $row->{edge} & 1 ) {
|
||
|
|
$u->add_edge(
|
||
|
|
$other_userid,
|
||
|
|
trust => {
|
||
|
|
nonotify => $u->trusts($other_userid) ? 1 : 0,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$u->remove_edge(
|
||
|
|
$other_userid,
|
||
|
|
trust => {
|
||
|
|
nonotify => $u->trusts($other_userid) ? 0 : 1,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if ( $row->{edge} & 2 ) {
|
||
|
|
my $fg = $row->{fgcolor} || "#000000";
|
||
|
|
my $bg = $row->{bgcolor} || "#FFFFFF";
|
||
|
|
$u->add_edge(
|
||
|
|
$other_userid,
|
||
|
|
watch => {
|
||
|
|
fgcolor => LJ::color_todb($fg),
|
||
|
|
bgcolor => LJ::color_todb($bg),
|
||
|
|
nonotify => $u->watches($other_userid) ? 1 : 0,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$u->remove_edge(
|
||
|
|
$other_userid,
|
||
|
|
watch => {
|
||
|
|
nonotify => $u->watches($other_userid) ? 0 : 1,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if ( $row->{edge} ) {
|
||
|
|
my $myid = $u->userid;
|
||
|
|
my $added = {
|
||
|
|
username => $other_user->{user},
|
||
|
|
fullname => $other_user->{name},
|
||
|
|
trusted => $u->trusts($other_userid),
|
||
|
|
trustedby => $other_user->trusts($myid),
|
||
|
|
watched => $u->watches($other_userid),
|
||
|
|
watchedby => $other_user->watches($myid)
|
||
|
|
};
|
||
|
|
push @{ $res->{added} }, $added;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# if ( ref $req->{delete} eq 'ARRAY' ) {
|
||
|
|
# foreach my $row ( @{$req->{delete}} ) {
|
||
|
|
# not implemented yet - maybe unnecessary
|
||
|
|
# }
|
||
|
|
# }
|
||
|
|
|
||
|
|
if ( ref $req->{addtocontentfilters} eq 'ARRAY' ) {
|
||
|
|
foreach my $row ( @{ $req->{addtocontentfilters} } ) {
|
||
|
|
my $other_user = LJ::load_user( $row->{username} );
|
||
|
|
return fail( $err, 203 ) unless $other_user;
|
||
|
|
my $other_userid = $other_user->{userid};
|
||
|
|
my $cf = $u->content_filters( id => $row->{id} );
|
||
|
|
$cf->add_row( userid => $other_userid ) if $cf;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ref $req->{deletefromcontentfilters} eq 'ARRAY' ) {
|
||
|
|
foreach my $row ( @{ $req->{deletefromcontentfilters} } ) {
|
||
|
|
my $other_user = LJ::load_user( $row->{username} );
|
||
|
|
return fail( $err, 203 ) unless $other_user;
|
||
|
|
my $other_userid = $other_user->{userid};
|
||
|
|
my $cf = $u->content_filters( id => $row->{id} );
|
||
|
|
$cf->delete_row($other_userid) if $cf;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub sessionexpire {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
my $u = $flags->{u};
|
||
|
|
|
||
|
|
# expunge one? or all?
|
||
|
|
if ( $req->{expireall} ) {
|
||
|
|
$u->kill_all_sessions;
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
# just expire a list
|
||
|
|
my $list = $req->{expire} || [];
|
||
|
|
return {} unless @$list;
|
||
|
|
return fail( $err, 502 ) unless $u->writer;
|
||
|
|
$u->kill_sessions(@$list);
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
sub sessiongenerate {
|
||
|
|
|
||
|
|
# generate a session
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
|
||
|
|
# sanitize input
|
||
|
|
$req->{expiration} = 'short' unless $req->{expiration} eq 'long';
|
||
|
|
my $boundip;
|
||
|
|
$boundip = LJ::get_remote_ip() if $req->{bindtoip};
|
||
|
|
|
||
|
|
my $u = $flags->{u};
|
||
|
|
my $sess_opts = {
|
||
|
|
exptype => $req->{expiration},
|
||
|
|
ipfixed => $boundip,
|
||
|
|
};
|
||
|
|
|
||
|
|
# do not let locked people do this
|
||
|
|
return fail( $err, 308 ) if $u->is_locked;
|
||
|
|
|
||
|
|
my $sess = LJ::Session->create( $u, %$sess_opts );
|
||
|
|
|
||
|
|
# return our hash
|
||
|
|
return { ljsession => $sess->master_cookie_string, };
|
||
|
|
}
|
||
|
|
|
||
|
|
sub list_friends {
|
||
|
|
my ( $u, $opts ) = @_;
|
||
|
|
|
||
|
|
# do not show people in here
|
||
|
|
my %hide; # userid -> 1
|
||
|
|
|
||
|
|
# TAG:FR:protocol:list_friends
|
||
|
|
my $sql;
|
||
|
|
unless ( $opts->{'friendof'} ) {
|
||
|
|
$sql = "SELECT friendid, fgcolor, bgcolor, groupmask FROM friends WHERE userid=?";
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$sql = "SELECT userid FROM friends WHERE friendid=?";
|
||
|
|
|
||
|
|
if ( my $list = LJ::load_rel_user( $u, 'B' ) ) {
|
||
|
|
$hide{$_} = 1 foreach @$list;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $dbr = LJ::get_db_reader();
|
||
|
|
my $sth = $dbr->prepare($sql);
|
||
|
|
$sth->execute( $u->{'userid'} );
|
||
|
|
|
||
|
|
my @frow;
|
||
|
|
while ( my @row = $sth->fetchrow_array ) {
|
||
|
|
next if $hide{ $row[0] };
|
||
|
|
push @frow, [@row];
|
||
|
|
}
|
||
|
|
|
||
|
|
my $us = LJ::load_userids( map { $_->[0] } @frow );
|
||
|
|
my $limitnum = $opts->{'limit'} + 0;
|
||
|
|
|
||
|
|
my $res = [];
|
||
|
|
foreach my $f (
|
||
|
|
sort { $us->{ $a->[0] }{'user'} cmp $us->{ $b->[0] }{'user'} }
|
||
|
|
grep { $us->{ $_->[0] } } @frow
|
||
|
|
)
|
||
|
|
{
|
||
|
|
my $u = $us->{ $f->[0] };
|
||
|
|
next if $opts->{'friendof'} && !$u->is_visible;
|
||
|
|
|
||
|
|
my $r = {
|
||
|
|
'username' => $u->{'user'},
|
||
|
|
'fullname' => $u->{'name'},
|
||
|
|
};
|
||
|
|
|
||
|
|
if ( $u->identity ) {
|
||
|
|
my $i = $u->identity;
|
||
|
|
$r->{'identity_type'} = $i->pretty_type;
|
||
|
|
$r->{'identity_value'} = $i->value;
|
||
|
|
$r->{'identity_display'} = $u->display_name;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $opts->{'includebdays'}
|
||
|
|
&& $u->{'bdate'}
|
||
|
|
&& $u->{'bdate'} ne "0000-00-00"
|
||
|
|
&& $u->can_show_full_bday )
|
||
|
|
{
|
||
|
|
$r->{'birthday'} = $u->{'bdate'};
|
||
|
|
}
|
||
|
|
|
||
|
|
unless ( $opts->{'friendof'} ) {
|
||
|
|
$r->{'fgcolor'} = LJ::color_fromdb( $f->[1] );
|
||
|
|
$r->{'bgcolor'} = LJ::color_fromdb( $f->[2] );
|
||
|
|
$r->{"groupmask"} = $f->[3] if $f->[3] != 1;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$r->{'fgcolor'} = "#000000";
|
||
|
|
$r->{'bgcolor'} = "#ffffff";
|
||
|
|
}
|
||
|
|
|
||
|
|
$r->{"type"} = {
|
||
|
|
'C' => 'community',
|
||
|
|
'Y' => 'syndicated',
|
||
|
|
'I' => 'identity',
|
||
|
|
}->{ $u->journaltype }
|
||
|
|
unless $u->is_person;
|
||
|
|
|
||
|
|
$r->{"status"} = {
|
||
|
|
'D' => "deleted",
|
||
|
|
'S' => "suspended",
|
||
|
|
'X' => "purged",
|
||
|
|
}->{ $u->statusvis }
|
||
|
|
unless $u->is_visible;
|
||
|
|
|
||
|
|
push @$res, $r;
|
||
|
|
|
||
|
|
# won't happen for zero limit (which means no limit)
|
||
|
|
last if @$res == $limitnum;
|
||
|
|
}
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub list_users {
|
||
|
|
my ( $u, %opts ) = @_;
|
||
|
|
|
||
|
|
my %hide;
|
||
|
|
my $list = LJ::load_rel_user( $u, 'B' );
|
||
|
|
$hide{$_} = 1 foreach @{ $list || [] };
|
||
|
|
|
||
|
|
my $friendof = $opts{trustedby} || $opts{watchedby};
|
||
|
|
my ( $filter, @userids );
|
||
|
|
if ($friendof) {
|
||
|
|
@userids = $opts{trustedby} ? $u->trusted_by_userids : $u->watched_by_userids;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$filter = $opts{trusted} ? $u->trust_list : $u->watch_list;
|
||
|
|
@userids = keys %{$filter};
|
||
|
|
}
|
||
|
|
|
||
|
|
my $limitnum = $opts{limit} + 0;
|
||
|
|
my @res;
|
||
|
|
|
||
|
|
my $us = LJ::load_userids(@userids);
|
||
|
|
while ( my ( $userid, $u ) = each %$us ) {
|
||
|
|
next unless LJ::isu($u);
|
||
|
|
next if $friendof && !$u->is_visible;
|
||
|
|
next if $hide{$userid};
|
||
|
|
|
||
|
|
my $r = {
|
||
|
|
username => $u->user,
|
||
|
|
fullname => $u->display_name
|
||
|
|
};
|
||
|
|
|
||
|
|
if ( $u->identity ) {
|
||
|
|
my $i = $u->identity;
|
||
|
|
$r->{identity_type} = $i->pretty_type;
|
||
|
|
$r->{identity_value} = $i->value;
|
||
|
|
$r->{identity_display} = $u->display_name;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $opts{includebdays} ) {
|
||
|
|
$r->{birthday} = $u->bday_string;
|
||
|
|
}
|
||
|
|
|
||
|
|
unless ($friendof) {
|
||
|
|
$r->{fgcolor} = LJ::color_fromdb( $filter->{$userid}->{fgcolor} );
|
||
|
|
$r->{bgcolor} = LJ::color_fromdb( $filter->{$userid}->{bgcolor} );
|
||
|
|
$r->{groupmask} = $filter->{$userid}->{groupmask};
|
||
|
|
}
|
||
|
|
|
||
|
|
$r->{type} = {
|
||
|
|
C => 'community',
|
||
|
|
Y => 'syndicated',
|
||
|
|
I => 'identity',
|
||
|
|
}->{ $u->journaltype }
|
||
|
|
unless $u->is_person;
|
||
|
|
|
||
|
|
$r->{status} = {
|
||
|
|
D => 'deleted',
|
||
|
|
S => 'suspended',
|
||
|
|
X => 'purged',
|
||
|
|
}->{ $u->statusvis }
|
||
|
|
unless $u->is_visible;
|
||
|
|
|
||
|
|
push @res, $r;
|
||
|
|
|
||
|
|
# won't happen for zero limit (which means no limit)
|
||
|
|
last if scalar @res == $limitnum;
|
||
|
|
}
|
||
|
|
return \@res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub syncitems {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
return undef unless authenticate( $req, $err, $flags );
|
||
|
|
return undef unless check_altusage( $req, $err, $flags );
|
||
|
|
return fail( $err, 506 ) unless LJ::is_enabled('syncitems');
|
||
|
|
|
||
|
|
my $ownerid = $flags->{'ownerid'};
|
||
|
|
my $uowner = $flags->{'u_owner'} || $flags->{'u'};
|
||
|
|
my $sth;
|
||
|
|
|
||
|
|
my $db = LJ::get_cluster_reader($uowner);
|
||
|
|
return fail( $err, 502 ) unless $db;
|
||
|
|
|
||
|
|
## have a valid date?
|
||
|
|
my $date = $req->{'lastsync'};
|
||
|
|
if ($date) {
|
||
|
|
return fail( $err, 203, "Invalid date format" )
|
||
|
|
unless ( $date =~ /^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/ );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$date = "0000-00-00 00:00:00";
|
||
|
|
}
|
||
|
|
|
||
|
|
my $LIMIT = 500;
|
||
|
|
|
||
|
|
my %item;
|
||
|
|
$sth =
|
||
|
|
$db->prepare( "SELECT jitemid, logtime FROM log2 WHERE " . "journalid=? and logtime > ?" );
|
||
|
|
$sth->execute( $ownerid, $date );
|
||
|
|
while ( my ( $id, $dt ) = $sth->fetchrow_array ) {
|
||
|
|
$item{$id} = [ 'L', $id, $dt, "create" ];
|
||
|
|
}
|
||
|
|
|
||
|
|
my %cmt;
|
||
|
|
my $p_calter = LJ::get_prop( "log", "commentalter" );
|
||
|
|
my $p_revtime = LJ::get_prop( "log", "revtime" );
|
||
|
|
$sth =
|
||
|
|
$db->prepare( "SELECT jitemid, propid, FROM_UNIXTIME(value) "
|
||
|
|
. "FROM logprop2 WHERE journalid=? "
|
||
|
|
. "AND propid IN ($p_calter->{'id'}, $p_revtime->{'id'}) "
|
||
|
|
. "AND value+0 > UNIX_TIMESTAMP(?)" );
|
||
|
|
$sth->execute( $ownerid, $date );
|
||
|
|
while ( my ( $id, $prop, $dt ) = $sth->fetchrow_array ) {
|
||
|
|
if ( $prop == $p_calter->{'id'} ) {
|
||
|
|
$cmt{$id} = [ 'C', $id, $dt, "update" ];
|
||
|
|
}
|
||
|
|
elsif ( $prop == $p_revtime->{'id'} ) {
|
||
|
|
$item{$id} = [ 'L', $id, $dt, "update" ];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my @ev = sort { $a->[2] cmp $b->[2] } ( values %item, values %cmt );
|
||
|
|
|
||
|
|
my $res = {};
|
||
|
|
my $list = $res->{'syncitems'} = [];
|
||
|
|
$res->{'total'} = scalar @ev;
|
||
|
|
my $ct = 0;
|
||
|
|
while ( my $ev = shift @ev ) {
|
||
|
|
$ct++;
|
||
|
|
push @$list,
|
||
|
|
{
|
||
|
|
'item' => "$ev->[0]-$ev->[1]",
|
||
|
|
'time' => $ev->[2],
|
||
|
|
'action' => $ev->[3],
|
||
|
|
};
|
||
|
|
last if $ct >= $LIMIT;
|
||
|
|
}
|
||
|
|
$res->{'count'} = $ct;
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub consolecommand {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
|
||
|
|
# logging in isn't necessary, but most console commands do require it
|
||
|
|
LJ::set_remote( $flags->{'u'} ) if authenticate( $req, $err, $flags );
|
||
|
|
|
||
|
|
my $res = {};
|
||
|
|
my $cmdout = $res->{'results'} = [];
|
||
|
|
|
||
|
|
foreach my $cmd ( @{ $req->{'commands'} } ) {
|
||
|
|
|
||
|
|
# callee can pre-parse the args, or we can do it bash-style
|
||
|
|
my @args =
|
||
|
|
ref $cmd eq "ARRAY"
|
||
|
|
? @$cmd
|
||
|
|
: LJ::Console->parse_line($cmd);
|
||
|
|
my $c = LJ::Console->parse_array(@args);
|
||
|
|
my $rv = $c->execute_safely;
|
||
|
|
|
||
|
|
my @output;
|
||
|
|
push @output, [ $_->status, $_->text ] foreach $c->responses;
|
||
|
|
|
||
|
|
push @{$cmdout},
|
||
|
|
{
|
||
|
|
'success' => $rv,
|
||
|
|
'output' => \@output,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub getchallenge {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
my $res = {};
|
||
|
|
my $now = time();
|
||
|
|
my $etime = 60;
|
||
|
|
$res->{'challenge'} = DW::Auth::Challenge->generate($etime);
|
||
|
|
$res->{'server_time'} = $now;
|
||
|
|
$res->{'expire_time'} = $now + $etime;
|
||
|
|
$res->{'auth_scheme'} = "c0"; # fixed for now, might support others later
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub login_message {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
|
||
|
|
my $msg = sub {
|
||
|
|
my $code = shift;
|
||
|
|
my $args = shift || {};
|
||
|
|
$args->{'sitename'} = $LJ::SITENAME;
|
||
|
|
$args->{'siteroot'} = $LJ::SITEROOT;
|
||
|
|
my $pre = delete $args->{'pre'};
|
||
|
|
$res->{'message'} = $pre . translate( $u, $code, $args );
|
||
|
|
};
|
||
|
|
|
||
|
|
return $msg->("readonly") if $u->is_readonly;
|
||
|
|
return $msg->("not_validated") if $u->{'status'} eq "N";
|
||
|
|
return $msg->("must_revalidate") if $u->{'status'} eq "T";
|
||
|
|
|
||
|
|
return $msg->("old_win32_client") if $req->{'clientversion'} =~ /^Win32-MFC\/(1.2.[0123456])$/;
|
||
|
|
return $msg->("old_win32_client") if $req->{'clientversion'} =~ /^Win32-MFC\/(1.3.[01234])\b/;
|
||
|
|
return $msg->("hello_test") if grep { $u->{user} eq $_ } @LJ::TESTACCTS;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub list_friendgroups {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
# warn "LJ::Protocol: list_friendgroups called.\n";
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
|
||
|
|
sub list_trustgroups {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
my $groups = $u->trust_groups;
|
||
|
|
return undef unless $groups;
|
||
|
|
|
||
|
|
# we got all of the groups, so put them into an arrayref sorted by the
|
||
|
|
# group sortorder; also note that the map is used to construct a new hashref
|
||
|
|
# out of the old group hashref so that we have all of the field names converted
|
||
|
|
# to a format our callers can recognize
|
||
|
|
my @res = map {
|
||
|
|
{
|
||
|
|
id => $_->{groupnum},
|
||
|
|
name => $_->{groupname},
|
||
|
|
public => $_->{is_public},
|
||
|
|
sortorder => $_->{sortorder},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sort { $a->{sortorder} <=> $b->{sortorder} } values %$groups;
|
||
|
|
|
||
|
|
return \@res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub list_contentfilters {
|
||
|
|
my $u = shift;
|
||
|
|
my @filters = $u->content_filters;
|
||
|
|
return [] unless @filters;
|
||
|
|
|
||
|
|
my @res = map {
|
||
|
|
{
|
||
|
|
id => $_->{id},
|
||
|
|
name => $_->{name},
|
||
|
|
public => $_->{public},
|
||
|
|
sortorder => $_->{sortorder},
|
||
|
|
data => join(
|
||
|
|
' ',
|
||
|
|
map {
|
||
|
|
my $uid = $_;
|
||
|
|
LJ::load_userid($uid)->user
|
||
|
|
} ( keys %{ $u->content_filters( id => $_->id )->data } )
|
||
|
|
)
|
||
|
|
}
|
||
|
|
} @filters;
|
||
|
|
|
||
|
|
return \@res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub list_usejournals {
|
||
|
|
my $u = shift;
|
||
|
|
|
||
|
|
my @us = $u->posting_access_list;
|
||
|
|
my @unames = map { $_->{user} } @us;
|
||
|
|
|
||
|
|
return \@unames;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub hash_menus {
|
||
|
|
my $u = shift;
|
||
|
|
my $user = $u->{'user'};
|
||
|
|
|
||
|
|
my $menu = [
|
||
|
|
{
|
||
|
|
'text' => "Recent Entries",
|
||
|
|
'url' => "$LJ::SITEROOT/users/$user/",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
'text' => "Calendar View",
|
||
|
|
'url' => "$LJ::SITEROOT/users/$user/archive",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
'text' => "Friends View",
|
||
|
|
'url' => "$LJ::SITEROOT/users/$user/read",
|
||
|
|
},
|
||
|
|
{ 'text' => "-", },
|
||
|
|
{
|
||
|
|
'text' => "Your Profile",
|
||
|
|
'url' => "$LJ::SITEROOT/profile?user=$user",
|
||
|
|
},
|
||
|
|
{ 'text' => "-", },
|
||
|
|
{
|
||
|
|
'text' => "Change Settings",
|
||
|
|
'sub' => [
|
||
|
|
{
|
||
|
|
'text' => "Personal Info",
|
||
|
|
'url' => "$LJ::SITEROOT/manage/profile/",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
'text' => "Customize Journal",
|
||
|
|
'url' => "$LJ::SITEROOT/customize/",
|
||
|
|
},
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{ 'text' => "-", },
|
||
|
|
{
|
||
|
|
'text' => "Support",
|
||
|
|
'url' => "$LJ::SITEROOT/support/",
|
||
|
|
}
|
||
|
|
];
|
||
|
|
|
||
|
|
LJ::Hooks::run_hooks(
|
||
|
|
"modify_login_menu",
|
||
|
|
{
|
||
|
|
'menu' => $menu,
|
||
|
|
'u' => $u,
|
||
|
|
'user' => $user,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
return $menu;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub list_pickws {
|
||
|
|
my ($u) = @_;
|
||
|
|
return [] unless LJ::isu($u);
|
||
|
|
|
||
|
|
my $pi = $u->get_userpic_info;
|
||
|
|
my @res;
|
||
|
|
|
||
|
|
my %seen; # mashifiedptr -> 1
|
||
|
|
|
||
|
|
# FIXME: should be a utf-8 sort
|
||
|
|
foreach my $kw ( sort keys %{ $pi->{kw} } ) {
|
||
|
|
my $pic = $pi->{kw}{$kw};
|
||
|
|
$seen{$pic} = 1;
|
||
|
|
next if $pic->{state} eq "I";
|
||
|
|
push @res, [ $kw, $pic->{picid} ];
|
||
|
|
}
|
||
|
|
|
||
|
|
# now add all the pictures that don't have a keyword
|
||
|
|
foreach my $picid ( keys %{ $pi->{pic} } ) {
|
||
|
|
my $pic = $pi->{pic}{$picid};
|
||
|
|
next if $seen{$pic};
|
||
|
|
next if $pic->{state} eq "I";
|
||
|
|
push @res, [ "pic#$picid", $picid ];
|
||
|
|
}
|
||
|
|
|
||
|
|
return \@res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub list_moods {
|
||
|
|
my $mood_max = int(shift);
|
||
|
|
DW::Mood->load_moods;
|
||
|
|
|
||
|
|
my $res = [];
|
||
|
|
return $res if $mood_max >= $LJ::CACHED_MOOD_MAX;
|
||
|
|
|
||
|
|
for ( my $id = $mood_max + 1 ; $id <= $LJ::CACHED_MOOD_MAX ; $id++ ) {
|
||
|
|
next unless defined $LJ::CACHE_MOODS{$id};
|
||
|
|
my $mood = $LJ::CACHE_MOODS{$id};
|
||
|
|
next unless $mood->{'name'};
|
||
|
|
push @$res,
|
||
|
|
{
|
||
|
|
'id' => $id,
|
||
|
|
'name' => $mood->{'name'},
|
||
|
|
'parent' => $mood->{'parent'}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub check_altusage {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
|
||
|
|
my $alt = $req->{'usejournal'};
|
||
|
|
my $u = $flags->{'u'};
|
||
|
|
unless ($u) {
|
||
|
|
my $username = $req->{'username'};
|
||
|
|
return fail( $err, 200 ) unless $username;
|
||
|
|
return fail( $err, 100 ) unless LJ::canonical_username($username);
|
||
|
|
|
||
|
|
my $dbr = LJ::get_db_reader();
|
||
|
|
return fail( $err, 502 ) unless $dbr;
|
||
|
|
$u = $flags->{'u'} = LJ::load_user($username);
|
||
|
|
}
|
||
|
|
|
||
|
|
$flags->{'ownerid'} = $u->{'userid'};
|
||
|
|
|
||
|
|
# all good if not using an alt journal
|
||
|
|
return 1 unless $alt;
|
||
|
|
|
||
|
|
# complain if the username is invalid
|
||
|
|
return fail( $err, 206 ) unless LJ::canonical_username($alt);
|
||
|
|
|
||
|
|
# we are going to load the alt user
|
||
|
|
$flags->{u_owner} = LJ::load_user($alt);
|
||
|
|
$flags->{ownerid} = $flags->{u_owner} ? $flags->{u_owner}->id : undef;
|
||
|
|
my $apache_r = eval { BML::get_request() };
|
||
|
|
$apache_r->notes->{journalid} = $flags->{ownerid}
|
||
|
|
if $apache_r && !$apache_r->notes->{journalid};
|
||
|
|
|
||
|
|
# allow usage if we're told explicitly that it's okay
|
||
|
|
if ( $flags->{usejournal_okay} ) {
|
||
|
|
return 1 if $flags->{ownerid};
|
||
|
|
return fail( $err, 206 );
|
||
|
|
}
|
||
|
|
|
||
|
|
# or, if they have explicitly said to ignore canuse
|
||
|
|
return 1 if $flags->{ignorecanuse};
|
||
|
|
|
||
|
|
# otherwise, check for access
|
||
|
|
return 1 if $u->can_post_to( $flags->{u_owner} );
|
||
|
|
|
||
|
|
# not allowed to access it, bad user, no post
|
||
|
|
return fail( $err, 300 );
|
||
|
|
}
|
||
|
|
|
||
|
|
# Validate login/talk md5 responses. THIS IS DEPRECATED. This now only checks
|
||
|
|
# against a user's API keys as a pseudo "
|
||
|
|
#
|
||
|
|
# Return 1 on valid, 0 on invalid.
|
||
|
|
sub check_login {
|
||
|
|
my ( $u, $chal, $res, $banned, $opts ) = @_;
|
||
|
|
return 0 unless $u;
|
||
|
|
|
||
|
|
my @keys = @{ DW::API::Key->get_keys_for_user($u) || [] };
|
||
|
|
return 0 unless @keys;
|
||
|
|
|
||
|
|
# set the IP banned flag, if it was provided.
|
||
|
|
my $fake_scalar;
|
||
|
|
my $ref = ref $banned ? $banned : \$fake_scalar;
|
||
|
|
if ( LJ::login_ip_banned($u) ) {
|
||
|
|
$$ref = 1;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$$ref = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
# check the challenge string validity
|
||
|
|
return 0 unless DW::Auth::Challenge->check( $chal, $opts );
|
||
|
|
|
||
|
|
# Validate password against the user's list of API keys
|
||
|
|
foreach my $key (@keys) {
|
||
|
|
my $hashed = Digest::MD5::md5_hex( $chal . Digest::MD5::md5_hex( $key->hash ) );
|
||
|
|
if ( $hashed eq $res ) {
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Login failed against all keys
|
||
|
|
LJ::handle_bad_login($u);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub authenticate {
|
||
|
|
my ( $req, $err, $flags ) = @_;
|
||
|
|
|
||
|
|
my $username = $req->{username};
|
||
|
|
return fail( $err, 200 ) unless $username;
|
||
|
|
return fail( $err, 100 ) unless LJ::canonical_username($username);
|
||
|
|
|
||
|
|
my $u = $flags->{u};
|
||
|
|
unless ($u) {
|
||
|
|
my $dbr = LJ::get_db_reader()
|
||
|
|
or return fail( $err, 502 );
|
||
|
|
$u = LJ::load_user($username);
|
||
|
|
}
|
||
|
|
|
||
|
|
return fail( $err, 100 ) unless $u;
|
||
|
|
return fail( $err, 100 ) if $u->is_expunged;
|
||
|
|
return fail( $err, 309 ) if $u->is_memorial; # memorial users can't do anything
|
||
|
|
return fail( $err, 505 ) unless $u->{clusterid};
|
||
|
|
|
||
|
|
my $r = DW::Request->get;
|
||
|
|
my $ip = LJ::get_remote_ip();
|
||
|
|
|
||
|
|
if ($r) {
|
||
|
|
$r->note( ljuser => $u->user )
|
||
|
|
unless $r->note('ljuser');
|
||
|
|
$r->note( journalid => $u->id )
|
||
|
|
unless $r->note('journalid');
|
||
|
|
}
|
||
|
|
|
||
|
|
my $ip_banned = 0;
|
||
|
|
my $chal_expired = 0;
|
||
|
|
my $auth_check = sub {
|
||
|
|
|
||
|
|
my $auth_meth = $req->{auth_method} || 'clear';
|
||
|
|
if ( $auth_meth eq 'clear' ) {
|
||
|
|
return LJ::auth_okay(
|
||
|
|
$u, $req->{password} // $req->{hpassword},
|
||
|
|
is_ip_banned => \$ip_banned,
|
||
|
|
allow_hpassword => 1,
|
||
|
|
allow_api_keys => 1
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if ( $auth_meth eq 'challenge' ) {
|
||
|
|
my $chal_opts = {};
|
||
|
|
my $chal_ok = check_login( $u, $req->{auth_challenge},
|
||
|
|
$req->{auth_response}, \$ip_banned, $chal_opts );
|
||
|
|
$chal_expired = 1 if $chal_opts->{expired};
|
||
|
|
return $chal_ok;
|
||
|
|
}
|
||
|
|
if ( $auth_meth eq 'cookie' ) {
|
||
|
|
return unless $r && $r->header_in('X-LJ-Auth') eq 'cookie';
|
||
|
|
|
||
|
|
my $remote = LJ::get_remote();
|
||
|
|
return $remote && $remote->user eq $username ? 1 : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
unless ( $flags->{nopassword}
|
||
|
|
|| $flags->{noauth}
|
||
|
|
|| $auth_check->() )
|
||
|
|
{
|
||
|
|
return fail( $err, 402 ) if $ip_banned;
|
||
|
|
return fail( $err, 105 ) if $chal_expired;
|
||
|
|
return fail( $err, 101 );
|
||
|
|
}
|
||
|
|
|
||
|
|
# remember the user record for later.
|
||
|
|
$flags->{u} = $u;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub fail {
|
||
|
|
my $err = shift;
|
||
|
|
my $code = shift;
|
||
|
|
my $des = shift;
|
||
|
|
$code .= ":$des" if $des;
|
||
|
|
$$err = $code if ( ref $err eq "SCALAR" );
|
||
|
|
return undef;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub un_utf8_request {
|
||
|
|
my $req = shift;
|
||
|
|
$req->{$_} = LJ::no_utf8_flag( $req->{$_} ) foreach qw(subject event);
|
||
|
|
my $props = $req->{props} || {};
|
||
|
|
foreach my $k ( keys %$props ) {
|
||
|
|
next if ref $props->{$k}; # if this is multiple levels deep? don't think so.
|
||
|
|
$props->{$k} = LJ::no_utf8_flag( $props->{$k} );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# xmlrpc_method: dispatch an XMLRPC method call to do_request and wrap the
|
||
|
|
# result in SOAP types. Originally lived in Apache/LiveJournal.pm but moved
|
||
|
|
# here so it is available under Plack as well.
|
||
|
|
sub xmlrpc_method {
|
||
|
|
my $method = shift;
|
||
|
|
shift; # get rid of package name that dispatcher includes.
|
||
|
|
my $req = shift;
|
||
|
|
|
||
|
|
if (@_) {
|
||
|
|
|
||
|
|
# don't allow extra arguments
|
||
|
|
die SOAP::Fault->faultstring( LJ::Protocol::error_message(202) )->faultcode(202);
|
||
|
|
}
|
||
|
|
my $error = 0;
|
||
|
|
if ( ref $req eq "HASH" ) {
|
||
|
|
|
||
|
|
# get rid of the UTF8 flag in scalars
|
||
|
|
while ( my ( $k, $v ) = each %$req ) {
|
||
|
|
$req->{$k} = Encode::encode_utf8($v) if Encode::is_utf8($v);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
my $res = LJ::Protocol::do_request( $method, $req, \$error );
|
||
|
|
if ($error) {
|
||
|
|
|
||
|
|
# FIXME [#1709]: which errors don't start with numbers?
|
||
|
|
print STDERR "[#1709] xmlrpc error for $method needs faultcode: $error\n"
|
||
|
|
unless $error =~ /^\d{3}/;
|
||
|
|
|
||
|
|
# existing behavior
|
||
|
|
die SOAP::Fault->faultstring( LJ::Protocol::error_message($error) )
|
||
|
|
->faultcode( substr( $error, 0, 3 ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
# Perl is untyped language and XML-RPC is typed.
|
||
|
|
# When library XMLRPC::Lite tries to guess type, it errors sometimes
|
||
|
|
# (e.g. string username goes as int, if username contains digits only).
|
||
|
|
# As workaround, we can select some elements by it's names
|
||
|
|
# and label them by correct types.
|
||
|
|
|
||
|
|
# Key - field name, value - type.
|
||
|
|
my %lj_types_map = (
|
||
|
|
journalname => 'string',
|
||
|
|
fullname => 'string',
|
||
|
|
username => 'string',
|
||
|
|
poster => 'string',
|
||
|
|
postername => 'string',
|
||
|
|
name => 'string',
|
||
|
|
);
|
||
|
|
|
||
|
|
my $recursive_mark_elements;
|
||
|
|
$recursive_mark_elements = sub {
|
||
|
|
my $structure = shift;
|
||
|
|
my $ref = ref($structure);
|
||
|
|
|
||
|
|
if ( $ref eq 'HASH' ) {
|
||
|
|
foreach my $hash_key ( keys %$structure ) {
|
||
|
|
if ( exists( $lj_types_map{$hash_key} ) ) {
|
||
|
|
$structure->{$hash_key} = SOAP::Data->type( $lj_types_map{$hash_key} )
|
||
|
|
->value( $structure->{$hash_key} );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$recursive_mark_elements->( $structure->{$hash_key} );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
elsif ( $ref eq 'ARRAY' ) {
|
||
|
|
foreach my $idx (@$structure) {
|
||
|
|
$recursive_mark_elements->($idx);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
$recursive_mark_elements->($res);
|
||
|
|
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
#### Old interface (flat key/values) -- wrapper aruond LJ::Protocol
|
||
|
|
package LJ;
|
||
|
|
|
||
|
|
sub do_request {
|
||
|
|
|
||
|
|
# get the request and response hash refs
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
# initialize some stuff
|
||
|
|
%{$res} = (); # clear the given response hash
|
||
|
|
$flags = {} unless ( ref $flags eq "HASH" );
|
||
|
|
|
||
|
|
# did they send a mode?
|
||
|
|
unless ( $req->{'mode'} ) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = "Client error: No mode specified.";
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
# this method doesn't require auth
|
||
|
|
if ( $req->{'mode'} eq "getchallenge" ) {
|
||
|
|
return getchallenge( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
|
||
|
|
# mode from here on out require a username
|
||
|
|
my $user = LJ::canonical_username( $req->{'user'} );
|
||
|
|
unless ($user) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = "Client error: No username sent.";
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
## dispatch wrappers
|
||
|
|
if ( $req->{'mode'} eq "login" ) {
|
||
|
|
return login( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "getfriendgroups" ) {
|
||
|
|
return getfriendgroups( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "gettrustgroups" ) {
|
||
|
|
return gettrustgroups( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "getfriends" ) {
|
||
|
|
return getfriends( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "friendof" ) {
|
||
|
|
return friendof( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "checkfriends" ) {
|
||
|
|
return checkfriends( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "checkforupdates" ) {
|
||
|
|
return checkforupdates( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "getdaycounts" ) {
|
||
|
|
return getdaycounts( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "postevent" ) {
|
||
|
|
return postevent( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "editevent" ) {
|
||
|
|
return editevent( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "syncitems" ) {
|
||
|
|
return syncitems( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "getevents" ) {
|
||
|
|
return getevents( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "editfriends" ) {
|
||
|
|
return editfriends( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "editfriendgroups" ) {
|
||
|
|
return editfriendgroups( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "consolecommand" ) {
|
||
|
|
return consolecommand( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "sessiongenerate" ) {
|
||
|
|
return sessiongenerate( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "sessionexpire" ) {
|
||
|
|
return sessionexpire( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "getusertags" ) {
|
||
|
|
return getusertags( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "getfriendspage" ) {
|
||
|
|
return getfriendspage( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
if ( $req->{'mode'} eq "getreadpage" ) {
|
||
|
|
return getreadpage( $req, $res, $flags );
|
||
|
|
}
|
||
|
|
|
||
|
|
### unknown mode!
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = "Client error: Unknown mode ($req->{'mode'})";
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub getfriendspage {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "getfriendspage", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub getreadpage {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "getreadpage", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $ect = 0;
|
||
|
|
foreach my $evt ( @{ $rs->{'entries'} } ) {
|
||
|
|
$ect++;
|
||
|
|
foreach
|
||
|
|
my $f (qw(subject_raw journalname journaltype postername postertype ditemid security))
|
||
|
|
{
|
||
|
|
if ( defined $evt->{$f} ) {
|
||
|
|
$res->{"entries_${ect}_$f"} = $evt->{$f};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$res->{"entries_${ect}_event"} = LJ::eurl( $evt->{'event_raw'} );
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'entries_count'} = $ect;
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub login {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "login", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
$res->{'name'} = $rs->{'fullname'};
|
||
|
|
$res->{'message'} = $rs->{'message'} if $rs->{'message'};
|
||
|
|
$res->{'fastserver'} = 1 if $rs->{'fastserver'};
|
||
|
|
$res->{'caps'} = $rs->{'caps'} if $rs->{'caps'};
|
||
|
|
|
||
|
|
# shared journals
|
||
|
|
my $access_count = 0;
|
||
|
|
foreach my $user ( @{ $rs->{'usejournals'} } ) {
|
||
|
|
$access_count++;
|
||
|
|
$res->{"access_${access_count}"} = $user;
|
||
|
|
}
|
||
|
|
if ($access_count) {
|
||
|
|
$res->{"access_count"} = $access_count;
|
||
|
|
}
|
||
|
|
|
||
|
|
# friend groups
|
||
|
|
populate_friend_groups( $res, $rs->{'friendgroups'} );
|
||
|
|
|
||
|
|
my $flatten = sub {
|
||
|
|
my ( $prefix, $listref ) = @_;
|
||
|
|
my $ct = 0;
|
||
|
|
foreach (@$listref) {
|
||
|
|
$ct++;
|
||
|
|
$res->{"${prefix}_$ct"} = $_;
|
||
|
|
}
|
||
|
|
$res->{"${prefix}_count"} = $ct;
|
||
|
|
};
|
||
|
|
|
||
|
|
### picture keywords
|
||
|
|
$flatten->( "pickw", $rs->{'pickws'} )
|
||
|
|
if defined $req->{"getpickws"};
|
||
|
|
$flatten->( "pickwurl", $rs->{'pickwurls'} )
|
||
|
|
if defined $req->{"getpickwurls"};
|
||
|
|
$res->{'defaultpicurl'} = $rs->{'defaultpicurl'} if $rs->{'defaultpicurl'};
|
||
|
|
|
||
|
|
### report new moods that this client hasn't heard of, if they care
|
||
|
|
if ( defined $req->{"getmoods"} ) {
|
||
|
|
my $mood_count = 0;
|
||
|
|
foreach my $m ( @{ $rs->{'moods'} } ) {
|
||
|
|
$mood_count++;
|
||
|
|
$res->{"mood_${mood_count}_id"} = $m->{'id'};
|
||
|
|
$res->{"mood_${mood_count}_name"} = $m->{'name'};
|
||
|
|
$res->{"mood_${mood_count}_parent"} = $m->{'parent'};
|
||
|
|
}
|
||
|
|
if ($mood_count) {
|
||
|
|
$res->{"mood_count"} = $mood_count;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#### send web menus
|
||
|
|
if ( $req->{"getmenus"} == 1 ) {
|
||
|
|
my $menu = $rs->{'menus'};
|
||
|
|
my $menu_num = 0;
|
||
|
|
populate_web_menu( $res, $menu, \$menu_num );
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub getfriendgroups {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "getfriendgroups", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
populate_friend_groups( $res, $rs->{'friendgroups'} );
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub gettrustgroups {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( 'gettrustgroups', $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{success} = "FAIL";
|
||
|
|
$res->{errmsg} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
$res->{success} = "OK";
|
||
|
|
populate_groups( $res, 'tr', $rs->{trustgroups} );
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub getusertags {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "getusertags", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
|
||
|
|
my $ct = 0;
|
||
|
|
foreach my $tag ( @{ $rs->{tags} } ) {
|
||
|
|
$ct++;
|
||
|
|
$res->{"tag_${ct}_security"} = $tag->{security_level};
|
||
|
|
$res->{"tag_${ct}_uses"} = $tag->{uses} if $tag->{uses};
|
||
|
|
$res->{"tag_${ct}_display"} = $tag->{display} if $tag->{display};
|
||
|
|
$res->{"tag_${ct}_name"} = $tag->{name};
|
||
|
|
foreach my $lev (qw(friends private public)) {
|
||
|
|
$res->{"tag_${ct}_sb_$_"} = $tag->{security}->{$_}
|
||
|
|
if $tag->{security}->{$_};
|
||
|
|
}
|
||
|
|
my $gm = 0;
|
||
|
|
foreach my $grpid ( keys %{ $tag->{security}->{groups} } ) {
|
||
|
|
next unless $tag->{security}->{groups}->{$grpid};
|
||
|
|
$gm++;
|
||
|
|
$res->{"tag_${ct}_sb_group_${gm}_id"} = $grpid;
|
||
|
|
$res->{"tag_${ct}_sb_group_${gm}_count"} = $tag->{security}->{groups}->{$grpid};
|
||
|
|
}
|
||
|
|
$res->{"tag_${ct}_sb_group_count"} = $gm if $gm;
|
||
|
|
}
|
||
|
|
$res->{'tag_count'} = $ct;
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub getfriends {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "getfriends", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
if ( $req->{'includegroups'} ) {
|
||
|
|
populate_friend_groups( $res, $rs->{'friendgroups'} );
|
||
|
|
}
|
||
|
|
if ( $req->{'includefriendof'} ) {
|
||
|
|
populate_friends( $res, "friendof", $rs->{'friendofs'} );
|
||
|
|
}
|
||
|
|
populate_friends( $res, "friend", $rs->{'friends'} );
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub friendof {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "friendof", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
populate_friends( $res, "friendof", $rs->{'friendofs'} );
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub checkfriends {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "checkfriends", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
$res->{'new'} = $rs->{'new'};
|
||
|
|
$res->{'lastupdate'} = $rs->{'lastupdate'};
|
||
|
|
$res->{'interval'} = $rs->{'interval'};
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub checkforupdates {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "checkforupdates", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{success} = "FAIL";
|
||
|
|
$res->{errmsg} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{success} = "OK";
|
||
|
|
$res->{new} = $rs->{new};
|
||
|
|
$res->{lastupdate} = $rs->{lastupdate};
|
||
|
|
$res->{interval} = $rs->{interval};
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub getdaycounts {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "getdaycounts", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
foreach my $d ( @{ $rs->{'daycounts'} } ) {
|
||
|
|
$res->{ $d->{'date'} } = $d->{'count'};
|
||
|
|
}
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub syncitems {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "syncitems", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
$res->{'sync_total'} = $rs->{'total'};
|
||
|
|
$res->{'sync_count'} = $rs->{'count'};
|
||
|
|
|
||
|
|
my $ct = 0;
|
||
|
|
foreach my $s ( @{ $rs->{'syncitems'} } ) {
|
||
|
|
$ct++;
|
||
|
|
foreach my $a (qw(item action time)) {
|
||
|
|
$res->{"sync_${ct}_$a"} = $s->{$a};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper: limited functionality. (1 command only, server-parsed only)
|
||
|
|
sub consolecommand {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
delete $rq->{'command'};
|
||
|
|
|
||
|
|
$rq->{'commands'} = [ $req->{'command'} ];
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "consolecommand", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'cmd_success'} = $rs->{'results'}->[0]->{'success'};
|
||
|
|
$res->{'cmd_line_count'} = 0;
|
||
|
|
foreach my $l ( @{ $rs->{'results'}->[0]->{'output'} } ) {
|
||
|
|
$res->{'cmd_line_count'}++;
|
||
|
|
my $line = $res->{'cmd_line_count'};
|
||
|
|
$res->{"cmd_line_${line}_type"} = $l->[0]
|
||
|
|
if $l->[0];
|
||
|
|
$res->{"cmd_line_${line}"} = $l->[1];
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub getchallenge {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
my $err = 0;
|
||
|
|
my $rs = LJ::Protocol::do_request( "getchallenge", $req, \$err, $flags );
|
||
|
|
|
||
|
|
# stupid copy (could just return $rs), but it might change in the future
|
||
|
|
# so this protects us from future accidental harm.
|
||
|
|
foreach my $k (qw(challenge server_time expire_time auth_scheme)) {
|
||
|
|
$res->{$k} = $rs->{$k};
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub editfriends {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
$rq->{'add'} = [];
|
||
|
|
$rq->{'delete'} = [];
|
||
|
|
|
||
|
|
foreach ( keys %$req ) {
|
||
|
|
if (/^editfriend_add_(\d+)_user$/) {
|
||
|
|
my $n = $1;
|
||
|
|
next unless ( $req->{"editfriend_add_${n}_user"} =~ /\S/ );
|
||
|
|
my $fa = {
|
||
|
|
'username' => $req->{"editfriend_add_${n}_user"},
|
||
|
|
'fgcolor' => $req->{"editfriend_add_${n}_fg"},
|
||
|
|
'bgcolor' => $req->{"editfriend_add_${n}_bg"},
|
||
|
|
'groupmask' => $req->{"editfriend_add_${n}_groupmask"},
|
||
|
|
};
|
||
|
|
push @{ $rq->{'add'} }, $fa;
|
||
|
|
}
|
||
|
|
elsif (/^editfriend_delete_(\w+)$/) {
|
||
|
|
push @{ $rq->{'delete'} }, $1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "editfriends", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
|
||
|
|
my $ct = 0;
|
||
|
|
foreach my $fa ( @{ $rs->{'added'} } ) {
|
||
|
|
$ct++;
|
||
|
|
$res->{"friend_${ct}_user"} = $fa->{'username'};
|
||
|
|
$res->{"friend_${ct}_name"} = $fa->{'fullname'};
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'friends_added'} = $ct;
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub editfriendgroups {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
$rq->{'groupmasks'} = {};
|
||
|
|
$rq->{'set'} = {};
|
||
|
|
$rq->{'delete'} = [];
|
||
|
|
|
||
|
|
foreach ( keys %$req ) {
|
||
|
|
if (/^efg_set_(\d+)_name$/) {
|
||
|
|
next unless ( $req->{$_} ne "" );
|
||
|
|
my $n = $1;
|
||
|
|
my $fs = {
|
||
|
|
'name' => $req->{"efg_set_${n}_name"},
|
||
|
|
'sort' => $req->{"efg_set_${n}_sort"},
|
||
|
|
};
|
||
|
|
if ( defined $req->{"efg_set_${n}_public"} ) {
|
||
|
|
$fs->{'public'} = $req->{"efg_set_${n}_public"};
|
||
|
|
}
|
||
|
|
$rq->{'set'}->{$n} = $fs;
|
||
|
|
}
|
||
|
|
elsif (/^efg_delete_(\d+)$/) {
|
||
|
|
if ( $req->{$_} ) {
|
||
|
|
|
||
|
|
# delete group if value is true
|
||
|
|
push @{ $rq->{'delete'} }, $1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
elsif (/^editfriend_groupmask_(\w+)$/) {
|
||
|
|
$rq->{'groupmasks'}->{$1} = $req->{$_};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "editfriendgroups", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub flatten_props {
|
||
|
|
my ( $req, $rq ) = @_;
|
||
|
|
|
||
|
|
## changes prop_* to props hashref
|
||
|
|
foreach my $k ( keys %$req ) {
|
||
|
|
next unless ( $k =~ /^prop_(.+)/ );
|
||
|
|
$rq->{'props'}->{$1} = $req->{$k};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub postevent {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
flatten_props( $req, $rq );
|
||
|
|
|
||
|
|
$rq->{'props'}->{'interface'} = "flat";
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "postevent", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{'message'} = $rs->{'message'} if $rs->{'message'};
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
$res->{'itemid'} = $rs->{'itemid'};
|
||
|
|
$res->{'anum'} = $rs->{'anum'} if defined $rs->{'anum'};
|
||
|
|
$res->{'url'} = $rs->{'url'} if defined $rs->{'url'};
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub editevent {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
flatten_props( $req, $rq );
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "editevent", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{'success'} = "FAIL";
|
||
|
|
$res->{'errmsg'} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{message} = $rs->{message} if $rs->{message};
|
||
|
|
$res->{'success'} = "OK";
|
||
|
|
$res->{'itemid'} = $rs->{'itemid'};
|
||
|
|
$res->{'anum'} = $rs->{'anum'} if defined $rs->{'anum'};
|
||
|
|
$res->{'url'} = $rs->{'url'} if defined $rs->{'url'};
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub sessiongenerate {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( 'sessiongenerate', $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{success} = 'FAIL';
|
||
|
|
$res->{errmsg} = LJ::Protocol::error_message($err);
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{success} = 'OK';
|
||
|
|
$res->{ljsession} = $rs->{ljsession};
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrappre
|
||
|
|
sub sessionexpire {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
$rq->{expire} = [];
|
||
|
|
foreach my $k ( keys %$rq ) {
|
||
|
|
push @{ $rq->{expire} }, $1
|
||
|
|
if $k =~ /^expire_id_(\d+)$/;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( 'sessionexpire', $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{success} = 'FAIL';
|
||
|
|
$res->{errmsg} = LJ::Protocol::error_message($err);
|
||
|
|
}
|
||
|
|
|
||
|
|
$res->{success} = 'OK';
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
## flat wrapper
|
||
|
|
sub getevents {
|
||
|
|
my ( $req, $res, $flags ) = @_;
|
||
|
|
|
||
|
|
my $err = 0;
|
||
|
|
my $rq = upgrade_request($req);
|
||
|
|
|
||
|
|
my $rs = LJ::Protocol::do_request( "getevents", $rq, \$err, $flags );
|
||
|
|
unless ($rs) {
|
||
|
|
$res->{success} = "FAIL";
|
||
|
|
$res->{errmsg} = LJ::Protocol::error_message($err);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $ect = 0;
|
||
|
|
my $pct = 0;
|
||
|
|
foreach my $evt ( @{ $rs->{events} } ) {
|
||
|
|
$ect++;
|
||
|
|
foreach my $f (
|
||
|
|
qw(itemid eventtime logtime security allowmask subject anum url poster converted_with_loss)
|
||
|
|
)
|
||
|
|
{
|
||
|
|
if ( defined $evt->{$f} ) {
|
||
|
|
$res->{"events_${ect}_$f"} = $evt->{$f};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$res->{"events_${ect}_event"} = LJ::eurl( $evt->{event} );
|
||
|
|
|
||
|
|
if ( $evt->{props} ) {
|
||
|
|
foreach my $k ( sort keys %{ $evt->{props} } ) {
|
||
|
|
$pct++;
|
||
|
|
$res->{"prop_${pct}_itemid"} = $evt->{itemid};
|
||
|
|
$res->{"prop_${pct}_name"} = $k;
|
||
|
|
$res->{"prop_${pct}_value"} = $evt->{props}->{$k};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
unless ( $req->{noprops} ) {
|
||
|
|
$res->{prop_count} = $pct;
|
||
|
|
}
|
||
|
|
$res->{events_count} = $ect;
|
||
|
|
$res->{success} = "OK";
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub populate_friends {
|
||
|
|
my ( $res, $pfx, $list ) = @_;
|
||
|
|
my $count = 0;
|
||
|
|
foreach my $f (@$list) {
|
||
|
|
$count++;
|
||
|
|
$res->{"${pfx}_${count}_name"} = $f->{'fullname'};
|
||
|
|
$res->{"${pfx}_${count}_user"} = $f->{'username'};
|
||
|
|
$res->{"${pfx}_${count}_birthday"} = $f->{'birthday'} if $f->{'birthday'};
|
||
|
|
$res->{"${pfx}_${count}_bg"} = $f->{'bgcolor'};
|
||
|
|
$res->{"${pfx}_${count}_fg"} = $f->{'fgcolor'};
|
||
|
|
if ( defined $f->{'groupmask'} ) {
|
||
|
|
$res->{"${pfx}_${count}_groupmask"} = $f->{'groupmask'};
|
||
|
|
}
|
||
|
|
if ( defined $f->{'type'} ) {
|
||
|
|
$res->{"${pfx}_${count}_type"} = $f->{'type'};
|
||
|
|
if ( $f->{'type'} eq 'identity' ) {
|
||
|
|
$res->{"${pfx}_${count}_identity_type"} = $f->{'identity_type'};
|
||
|
|
$res->{"${pfx}_${count}_identity_value"} = $f->{'identity_value'};
|
||
|
|
$res->{"${pfx}_${count}_identity_display"} = $f->{'identity_display'};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ( defined $f->{'status'} ) {
|
||
|
|
$res->{"${pfx}_${count}_status"} = $f->{'status'};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$res->{"${pfx}_count"} = $count;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub upgrade_request {
|
||
|
|
my $r = shift;
|
||
|
|
my $new = { %{$r} };
|
||
|
|
$new->{'username'} = $r->{'user'};
|
||
|
|
|
||
|
|
# but don't delete $r->{'user'}, as it might be, say, %FORM,
|
||
|
|
# that'll get reused in a later request in, say, update.bml after
|
||
|
|
# the login before postevent. whoops.
|
||
|
|
|
||
|
|
return $new;
|
||
|
|
}
|
||
|
|
|
||
|
|
## given a $res hashref and friend group subtree (arrayref), flattens it
|
||
|
|
sub populate_friend_groups {
|
||
|
|
my ( $res, $fr ) = @_;
|
||
|
|
|
||
|
|
my $maxnum = 0;
|
||
|
|
foreach my $fg (@$fr) {
|
||
|
|
my $num = $fg->{'id'};
|
||
|
|
$res->{"frgrp_${num}_name"} = $fg->{'name'};
|
||
|
|
$res->{"frgrp_${num}_sortorder"} = $fg->{'sortorder'};
|
||
|
|
if ( $fg->{'public'} ) {
|
||
|
|
$res->{"frgrp_${num}_public"} = 1;
|
||
|
|
}
|
||
|
|
if ( $num > $maxnum ) { $maxnum = $num; }
|
||
|
|
}
|
||
|
|
$res->{'frgrp_maxnum'} = $maxnum;
|
||
|
|
}
|
||
|
|
|
||
|
|
## given a $res hashref and trust group (arrayref), flattens it
|
||
|
|
sub populate_groups {
|
||
|
|
my ( $res, $pfx, $fr ) = @_;
|
||
|
|
|
||
|
|
my $maxnum = 0;
|
||
|
|
foreach my $fg (@$fr) {
|
||
|
|
my $num = $fg->{id};
|
||
|
|
$res->{"${pfx}_${num}_name"} = $fg->{name};
|
||
|
|
$res->{"${pfx}_${num}_sortorder"} = $fg->{sortorder};
|
||
|
|
$res->{"${pfx}_${num}_public"} = 1 if $fg->{public};
|
||
|
|
$maxnum = $num if ( $num > $maxnum );
|
||
|
|
}
|
||
|
|
$res->{"${pfx}_maxnum"} = $maxnum;
|
||
|
|
}
|
||
|
|
|
||
|
|
## given a menu tree, flattens it into $res hashref
|
||
|
|
sub populate_web_menu {
|
||
|
|
my ( $res, $menu, $numref ) = @_;
|
||
|
|
my $mn = $$numref; # menu number
|
||
|
|
my $mi = 0; # menu item
|
||
|
|
foreach my $it (@$menu) {
|
||
|
|
$mi++;
|
||
|
|
$res->{"menu_${mn}_${mi}_text"} = $it->{'text'};
|
||
|
|
if ( $it->{'text'} eq "-" ) { next; }
|
||
|
|
if ( $it->{'sub'} ) {
|
||
|
|
$$numref++;
|
||
|
|
$res->{"menu_${mn}_${mi}_sub"} = $$numref;
|
||
|
|
&populate_web_menu( $res, $it->{'sub'}, $numref );
|
||
|
|
next;
|
||
|
|
|
||
|
|
}
|
||
|
|
$res->{"menu_${mn}_${mi}_url"} = $it->{'url'};
|
||
|
|
}
|
||
|
|
$res->{"menu_${mn}_count"} = $mi;
|
||
|
|
}
|
||
|
|
|
||
|
|
1;
|