| 1 | #!/usr/bin/perl |
|---|
| 2 | # |
|---|
| 3 | |
|---|
| 4 | use strict; |
|---|
| 5 | |
|---|
| 6 | require "$ENV{'LJHOME'}/cgi-bin/ljpoll.pl"; |
|---|
| 7 | require "$ENV{'LJHOME'}/cgi-bin/ljconfig.pl"; |
|---|
| 8 | require "$ENV{'LJHOME'}/cgi-bin/console.pl"; |
|---|
| 9 | |
|---|
| 10 | #### New interface (meta handler) ... other handlers should call into this. |
|---|
| 11 | package LJ::Protocol; |
|---|
| 12 | |
|---|
| 13 | sub error_message |
|---|
| 14 | { |
|---|
| 15 | my $code = shift; |
|---|
| 16 | my $des; |
|---|
| 17 | if ($code =~ /^(\d\d\d):(.+)/) { |
|---|
| 18 | ($code, $des) = ($1, $2); |
|---|
| 19 | } |
|---|
| 20 | my %e = ( |
|---|
| 21 | # User Errors |
|---|
| 22 | "100" => "Invalid username", |
|---|
| 23 | "101" => "Invalid password", |
|---|
| 24 | "102" => "Can't use custom security on shared/community journals.", |
|---|
| 25 | "103" => "Poll error", |
|---|
| 26 | "104" => "Error adding one or more friends", |
|---|
| 27 | "150" => "Can't post as non-user", |
|---|
| 28 | "151" => "Banned from journal", |
|---|
| 29 | "152" => "Can't make back-dated entries in non-personal journal.", |
|---|
| 30 | |
|---|
| 31 | # Client Errors |
|---|
| 32 | "200" => "Missing required argument(s)", |
|---|
| 33 | "201" => "Unknown method", |
|---|
| 34 | "202" => "Too many arguments", |
|---|
| 35 | "203" => "Invalid argument(s)", |
|---|
| 36 | "204" => "Invalid metadata datatype", |
|---|
| 37 | "205" => "Unknown metadata", |
|---|
| 38 | "206" => "Invalid destination journal username.", |
|---|
| 39 | |
|---|
| 40 | # Access Errors |
|---|
| 41 | "300" => "Don't have access to shared/community journal", |
|---|
| 42 | "301" => "Access of restricted feature", |
|---|
| 43 | "302" => "Can't edit post from requested journal", |
|---|
| 44 | "303" => "Can't edit post in community journal", |
|---|
| 45 | "304" => "Can't delete post in this community journal", |
|---|
| 46 | "305" => "Action forbidden; account is suspended.", |
|---|
| 47 | "306" => "This journal is temporarily in read-only mode. Try again in a couple minutes.", |
|---|
| 48 | |
|---|
| 49 | # Server Errors |
|---|
| 50 | "500" => "Internal server error", |
|---|
| 51 | "501" => "Database error", |
|---|
| 52 | "502" => "Database temporarily unavailable", |
|---|
| 53 | ); |
|---|
| 54 | |
|---|
| 55 | my $prefix = ""; |
|---|
| 56 | my $error = $e{$code} || "BUG: Unknown error code!"; |
|---|
| 57 | if ($code >= 200) { $prefix = "Client error: "; } |
|---|
| 58 | if ($code >= 500) { $prefix = "Server error: "; } |
|---|
| 59 | my $totalerror = "$prefix$error"; |
|---|
| 60 | $totalerror .= ": $des" if $des; |
|---|
| 61 | return $totalerror; |
|---|
| 62 | } |
|---|
| 63 | |
|---|
| 64 | # returns result, or undef on failure |
|---|
| 65 | sub do_request_without_db |
|---|
| 66 | { |
|---|
| 67 | my ($method, $req, $err, $flags) = @_; |
|---|
| 68 | my $dbs = LJ::get_dbs(); |
|---|
| 69 | return fail($err,500) unless $dbs; |
|---|
| 70 | return do_request($dbs, $method, $req, $err, $flags); |
|---|
| 71 | } |
|---|
| 72 | |
|---|
| 73 | sub do_request |
|---|
| 74 | { |
|---|
| 75 | # get the request and response hash refs |
|---|
| 76 | my ($dbs, $method, $req, $err, $flags) = @_; |
|---|
| 77 | |
|---|
| 78 | $flags ||= {}; |
|---|
| 79 | my @args = ($dbs, $req, $err, $flags); |
|---|
| 80 | |
|---|
| 81 | if ($method eq "login") { return login(@args); } |
|---|
| 82 | if ($method eq "getfriendgroups") { return getfriendgroups(@args); } |
|---|
| 83 | if ($method eq "getfriends") { return getfriends(@args); } |
|---|
| 84 | if ($method eq "friendof") { return friendof(@args); } |
|---|
| 85 | if ($method eq "checkfriends") { return checkfriends(@args); } |
|---|
| 86 | if ($method eq "getdaycounts") { return getdaycounts(@args); } |
|---|
| 87 | if ($method eq "postevent") { return postevent(@args); } |
|---|
| 88 | if ($method eq "editevent") { return editevent(@args); } |
|---|
| 89 | if ($method eq "syncitems") { return syncitems(@args); } |
|---|
| 90 | if ($method eq "getevents") { return getevents(@args); } |
|---|
| 91 | if ($method eq "editfriends") { return editfriends(@args); } |
|---|
| 92 | if ($method eq "editfriendgroups") { return editfriendgroups(@args); } |
|---|
| 93 | if ($method eq "consolecommand") { return consolecommand(@args); } |
|---|
| 94 | |
|---|
| 95 | return fail($err,201); |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | sub login |
|---|
| 99 | { |
|---|
| 100 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 101 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 102 | |
|---|
| 103 | my $dbh = $dbs->{'dbh'}; |
|---|
| 104 | return fail($err,502) unless $dbh && $dbs->{'reader'}; |
|---|
| 105 | |
|---|
| 106 | my $u = $flags->{'u'}; |
|---|
| 107 | my $res = {}; |
|---|
| 108 | |
|---|
| 109 | ## return a message to the client to be displayed (optional) |
|---|
| 110 | login_message($dbs, $req, $res, $flags); |
|---|
| 111 | |
|---|
| 112 | ## report what shared journals this user may post in |
|---|
| 113 | $res->{'usejournals'} = list_usejournals($dbs, $u); |
|---|
| 114 | |
|---|
| 115 | ## return their friend groups |
|---|
| 116 | $res->{'friendgroups'} = list_friendgroups($dbs, $u); |
|---|
| 117 | |
|---|
| 118 | ## if they gave us a number of moods to get higher than, then return them |
|---|
| 119 | if (defined $req->{'getmoods'}) { |
|---|
| 120 | $res->{'moods'} = list_moods($dbs, $req->{'getmoods'}); |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | ### picture keywords, if they asked for them. |
|---|
| 124 | if ($req->{'getpickws'}) { |
|---|
| 125 | my $pickws = list_pickws($dbs, $u); |
|---|
| 126 | $res->{'pickws'} = [ map { $_->[0] } @$pickws ]; |
|---|
| 127 | if ($req->{'getpickwurls'}) { |
|---|
| 128 | $res->{'pickwurls'} = [ map { |
|---|
| 129 | "$LJ::SITEROOT/userpic/$_->[1]" |
|---|
| 130 | } @$pickws ]; |
|---|
| 131 | } |
|---|
| 132 | } |
|---|
| 133 | |
|---|
| 134 | ## return client menu tree, if requested |
|---|
| 135 | if ($req->{'getmenus'}) { |
|---|
| 136 | $res->{'menus'} = hash_menus($dbs, $u); |
|---|
| 137 | } |
|---|
| 138 | |
|---|
| 139 | ## tell some users they can hit the fast servers later. |
|---|
| 140 | $res->{'fastserver'} = 1 if LJ::get_cap($u, "fastserver"); |
|---|
| 141 | |
|---|
| 142 | ## user info |
|---|
| 143 | $res->{'userid'} = $u->{'userid'}; |
|---|
| 144 | $res->{'fullname'} = $u->{'name'}; |
|---|
| 145 | |
|---|
| 146 | ## update or add to clientusage table |
|---|
| 147 | if ($req->{'clientversion'} =~ /^\S+\/\S+$/) { |
|---|
| 148 | my $qclient = $dbh->quote($req->{'clientversion'}); |
|---|
| 149 | my $cu_sql = "REPLACE INTO clientusage (userid, clientid, lastlogin) " . |
|---|
| 150 | "SELECT $u->{'userid'}, clientid, NOW() FROM clients WHERE client=$qclient"; |
|---|
| 151 | my $sth = $dbh->prepare($cu_sql); |
|---|
| 152 | $sth->execute; |
|---|
| 153 | unless ($sth->rows) { |
|---|
| 154 | # only way this can be 0 is if client doesn't exist in clients table, so |
|---|
| 155 | # we need to add a new row there, to get a new clientid for this new client: |
|---|
| 156 | $dbh->do("INSERT INTO clients (client) VALUES ($qclient)"); |
|---|
| 157 | # and now we can do the query from before and it should work: |
|---|
| 158 | $sth = $dbh->prepare($cu_sql); |
|---|
| 159 | $sth->execute; |
|---|
| 160 | } |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | return $res; |
|---|
| 164 | } |
|---|
| 165 | |
|---|
| 166 | sub getfriendgroups |
|---|
| 167 | { |
|---|
| 168 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 169 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 170 | my $u = $flags->{'u'}; |
|---|
| 171 | my $res = {}; |
|---|
| 172 | $res->{'friendgroups'} = list_friendgroups($dbs, $u); |
|---|
| 173 | return $res; |
|---|
| 174 | } |
|---|
| 175 | |
|---|
| 176 | sub getfriends |
|---|
| 177 | { |
|---|
| 178 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 179 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 180 | return fail($req,502) unless $dbs->{'reader'}; |
|---|
| 181 | my $u = $flags->{'u'}; |
|---|
| 182 | my $res = {}; |
|---|
| 183 | if ($req->{'includegroups'}) { |
|---|
| 184 | $res->{'friendgroups'} = list_friendgroups($dbs, $u); |
|---|
| 185 | } |
|---|
| 186 | if ($req->{'includefriendof'}) { |
|---|
| 187 | $res->{'friendofs'} = list_friends($dbs, $u, { |
|---|
| 188 | 'limit' => $req->{'friendoflimit'}, |
|---|
| 189 | 'friendof' => 1, |
|---|
| 190 | }); |
|---|
| 191 | } |
|---|
| 192 | $res->{'friends'} = list_friends($dbs, $u, { |
|---|
| 193 | 'limit' => $req->{'friendlimit'} |
|---|
| 194 | }); |
|---|
| 195 | return $res; |
|---|
| 196 | } |
|---|
| 197 | |
|---|
| 198 | sub friendof |
|---|
| 199 | { |
|---|
| 200 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 201 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 202 | return fail($req,502) unless $dbs->{'reader'}; |
|---|
| 203 | my $u = $flags->{'u'}; |
|---|
| 204 | my $res = {}; |
|---|
| 205 | $res->{'friendofs'} = list_friends($dbs, $u, { |
|---|
| 206 | 'friendof' => 1, |
|---|
| 207 | 'limit' => $req->{'friendoflimit'}, |
|---|
| 208 | }); |
|---|
| 209 | return $res; |
|---|
| 210 | } |
|---|
| 211 | |
|---|
| 212 | sub checkfriends |
|---|
| 213 | { |
|---|
| 214 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 215 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 216 | my $u = $flags->{'u'}; |
|---|
| 217 | my $res = {}; |
|---|
| 218 | |
|---|
| 219 | # return immediately if they can't use this mode |
|---|
| 220 | unless (LJ::get_cap($u, "checkfriends")) { |
|---|
| 221 | $res->{'new'} = 0; |
|---|
| 222 | $res->{'interval'} = 36000; # tell client to bugger off |
|---|
| 223 | return $res; |
|---|
| 224 | } |
|---|
| 225 | |
|---|
| 226 | my $dbr = $dbs->{'reader'}; |
|---|
| 227 | my ($lastdate, $sth); |
|---|
| 228 | |
|---|
| 229 | ## have a valid date? |
|---|
| 230 | my $lastupdate = $req->{'lastupdate'}; |
|---|
| 231 | if ($lastupdate) { |
|---|
| 232 | return fail($err,203) unless |
|---|
| 233 | ($lastupdate =~ /^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/); |
|---|
| 234 | } else { |
|---|
| 235 | $lastupdate = "0000-00-00 00:00:00"; |
|---|
| 236 | } |
|---|
| 237 | |
|---|
| 238 | my $interval = LJ::get_cap_min($u, "checkfriends_interval"); |
|---|
| 239 | $res->{'interval'} = $interval; |
|---|
| 240 | |
|---|
| 241 | unless ($dbr) { |
|---|
| 242 | # rather than return a 502 no-db error, just say no updates, |
|---|
| 243 | # because problem'll be fixed soon enough by db admins |
|---|
| 244 | $res->{'new'} = 0; |
|---|
| 245 | $res->{'lastupdate'} = $lastupdate; |
|---|
| 246 | return $res; |
|---|
| 247 | } |
|---|
| 248 | |
|---|
| 249 | my $sql = "SELECT MAX(u.timeupdate) FROM userusage u, friends f ". |
|---|
| 250 | "WHERE u.userid=f.friendid AND f.userid=$u->{'userid'}"; |
|---|
| 251 | if ($req->{'mask'} and $req->{'mask'} !~ /\D/) { |
|---|
| 252 | $sql .= " AND f.groupmask & $req->{mask} > 0"; |
|---|
| 253 | } |
|---|
| 254 | |
|---|
| 255 | $sth = $dbr->prepare($sql); |
|---|
| 256 | $sth->execute; |
|---|
| 257 | my ($update) = $sth->fetchrow_array; |
|---|
| 258 | $update ||= "0000-00-00 00:00:00"; |
|---|
| 259 | |
|---|
| 260 | if ($req->{'lastupdate'} && $update gt $lastupdate) { |
|---|
| 261 | $res->{'new'} = 1; |
|---|
| 262 | } else { |
|---|
| 263 | $res->{'new'} = 0; |
|---|
| 264 | } |
|---|
| 265 | |
|---|
| 266 | $res->{'lastupdate'} = $update; |
|---|
| 267 | |
|---|
| 268 | return $res; |
|---|
| 269 | } |
|---|
| 270 | |
|---|
| 271 | sub getdaycounts |
|---|
| 272 | { |
|---|
| 273 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 274 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 275 | return undef unless check_altusage($dbs, $req, $err, $flags); |
|---|
| 276 | |
|---|
| 277 | my $u = $flags->{'u'}; |
|---|
| 278 | my $uowner = $flags->{'u_owner'} || $u; |
|---|
| 279 | my $ownerid = $flags->{'ownerid'}; |
|---|
| 280 | |
|---|
| 281 | my $res = {}; |
|---|
| 282 | my ($db, $table, $ownercol) = ($dbs->{'reader'}, "log", "ownerid"); |
|---|
| 283 | if ($uowner->{'clusterid'}) { |
|---|
| 284 | $db = LJ::get_cluster_reader($uowner); |
|---|
| 285 | ($table, $ownercol) = ("log2", "journalid"); |
|---|
| 286 | } |
|---|
| 287 | return fail($err,502) unless $db; |
|---|
| 288 | |
|---|
| 289 | my $sth = $db->prepare("SELECT year, month, day, COUNT(*) AS 'count' ". |
|---|
| 290 | "FROM $table WHERE $ownercol=$ownerid GROUP BY 1, 2, 3"); |
|---|
| 291 | $sth->execute; |
|---|
| 292 | while (my ($y, $m, $d, $c) = $sth->fetchrow_array) { |
|---|
| 293 | my $date = sprintf("%04d-%02d-%02d", $y, $m, $d); |
|---|
| 294 | push @{$res->{'daycounts'}}, { 'date' => $date, 'count' => $c }; |
|---|
| 295 | } |
|---|
| 296 | return $res; |
|---|
| 297 | } |
|---|
| 298 | |
|---|
| 299 | sub common_event_validation |
|---|
| 300 | { |
|---|
| 301 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 302 | my $dbr = $dbs->{'reader'}; |
|---|
| 303 | |
|---|
| 304 | # date validation |
|---|
| 305 | if ($req->{'year'} !~ /^\d\d\d\d$/ || |
|---|
| 306 | $req->{'year'} < 1980 || |
|---|
| 307 | $req->{'year'} > 2037) |
|---|
| 308 | { |
|---|
| 309 | return fail($err,203,"Invalid year value."); |
|---|
| 310 | } |
|---|
| 311 | if ($req->{'mon'} !~ /^\d{1,2}$/ || |
|---|
| 312 | $req->{'mon'} < 1 || |
|---|
| 313 | $req->{'mon'} > 12) |
|---|
| 314 | { |
|---|
| 315 | return fail($err,203,"Invalid month value."); |
|---|
| 316 | } |
|---|
| 317 | if ($req->{'day'} !~ /^\d{1,2}$/ || $req->{'day'} < 1 || |
|---|
| 318 | $req->{'day'} > LJ::days_in_month($req->{'month'}, |
|---|
| 319 | $req->{'year'})) |
|---|
| 320 | { |
|---|
| 321 | return fail($err,203,"Invalid day of month value."); |
|---|
| 322 | } |
|---|
| 323 | if ($req->{'hour'} !~ /^\d{1,2}$/ || |
|---|
| 324 | $req->{'hour'} < 0 || $req->{'hour'} > 23) |
|---|
| 325 | { |
|---|
| 326 | return fail($err,203,"Invalid hour value."); |
|---|
| 327 | } |
|---|
| 328 | if ($req->{'min'} !~ /^\d{1,2}$/ || |
|---|
| 329 | $req->{'min'} < 0 || $req->{'min'} > 59) |
|---|
| 330 | { |
|---|
| 331 | return fail($err,203,"Invalid minute value."); |
|---|
| 332 | } |
|---|
| 333 | |
|---|
| 334 | # setup non-user meta-data. it's important we define this here to |
|---|
| 335 | # 0. if it's not defined at all, then an editevent where a user |
|---|
| 336 | # removes random 8bit data won't remove the metadata. not that |
|---|
| 337 | # that matters much. but having this here won't hurt. false |
|---|
| 338 | # meta-data isn't saved anyway. so the only point of this next |
|---|
| 339 | # line is making the metadata be deleted on edit. |
|---|
| 340 | $req->{'props'}->{'unknown8bit'} = 0; |
|---|
| 341 | |
|---|
| 342 | # non-ASCII? |
|---|
| 343 | if ($req->{'event'} =~ /[\x80-\xFF]/ || $req->{'subject'} =~ /[\x80-\xFF]/) |
|---|
| 344 | { |
|---|
| 345 | if ($LJ::UNICODE) { |
|---|
| 346 | # TODO (avva): validate its UTF-8-ness, complain if not |
|---|
| 347 | # well-formed, or has overlong characters |
|---|
| 348 | } else { |
|---|
| 349 | # so rest of site can change chars to ? marks until |
|---|
| 350 | # default user's encoding is set. (legacy support) |
|---|
| 351 | $req->{'props'}->{'unknown8bit'} = 1; |
|---|
| 352 | } |
|---|
| 353 | } |
|---|
| 354 | |
|---|
| 355 | ## handle meta-data (properties) |
|---|
| 356 | LJ::load_props($dbs, "log"); |
|---|
| 357 | foreach my $pname (keys %{$req->{'props'}}) |
|---|
| 358 | { |
|---|
| 359 | my $p = LJ::get_prop("log", $pname); |
|---|
| 360 | |
|---|
| 361 | # does the property even exist? |
|---|
| 362 | unless ($p) { |
|---|
| 363 | $pname =~ s/[^\w]//g; |
|---|
| 364 | return fail($err,205,$pname); |
|---|
| 365 | } |
|---|
| 366 | |
|---|
| 367 | # don't validate its type if it's 0 or undef (deleting) |
|---|
| 368 | next unless ($req->{'props'}->{$pname}); |
|---|
| 369 | |
|---|
| 370 | my $ptype = $p->{'datatype'}; |
|---|
| 371 | my $val = $req->{'props'}->{$pname}; |
|---|
| 372 | |
|---|
| 373 | if ($ptype eq "bool" && $val !~ /^[01]$/) { |
|---|
| 374 | return fail($err,204,"Property \"$pname\" should be 0 or 1"); |
|---|
| 375 | } |
|---|
| 376 | if ($ptype eq "num" && $val =~ /[^\d]/) { |
|---|
| 377 | return fail($err,204,"Property \"$pname\" should be numeric"); |
|---|
| 378 | } |
|---|
| 379 | } |
|---|
| 380 | |
|---|
| 381 | return 1; |
|---|
| 382 | } |
|---|
| 383 | |
|---|
| 384 | sub postevent |
|---|
| 385 | { |
|---|
| 386 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 387 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 388 | return undef unless check_altusage($dbs, $req, $err, $flags); |
|---|
| 389 | |
|---|
| 390 | my $u = $flags->{'u'}; |
|---|
| 391 | my $ownerid = $flags->{'ownerid'}; |
|---|
| 392 | my $uowner = $flags->{'u_owner'} || $u; |
|---|
| 393 | my $dbr = $dbs->{'reader'}; |
|---|
| 394 | my $dbh = $dbs->{'dbh'}; |
|---|
| 395 | |
|---|
| 396 | return fail($err,306) unless $dbh; |
|---|
| 397 | return fail($err,200) unless ($req->{'event'} =~ /\S/); |
|---|
| 398 | |
|---|
| 399 | ### make sure community, shared, or news journals don't post |
|---|
| 400 | ### note: shared and news journals are deprecated. every shared journal |
|---|
| 401 | ## should one day be a community journal, of some form. |
|---|
| 402 | return fail($err,150) if ($u->{'journaltype'} eq "C" || |
|---|
| 403 | $u->{'journaltype'} eq "S" || |
|---|
| 404 | $u->{'journaltype'} eq "N"); |
|---|
| 405 | |
|---|
| 406 | # suspended users can't post |
|---|
| 407 | return fail($err,305) if ($u->{'statusvis'} eq "S"); |
|---|
| 408 | |
|---|
| 409 | # check the journal's read-only bit |
|---|
| 410 | return fail($err,306) if LJ::get_cap($uowner, "readonly"); |
|---|
| 411 | |
|---|
| 412 | #### clean up the event text |
|---|
| 413 | my $event = $req->{'event'}; |
|---|
| 414 | |
|---|
| 415 | # remove surrounding whitespace |
|---|
| 416 | $event =~ s/^\s+//; |
|---|
| 417 | $event =~ s/\s+$//; |
|---|
| 418 | |
|---|
| 419 | # convert line endings to unix format |
|---|
| 420 | if ($req->{'lineendings'} eq "mac") { |
|---|
| 421 | $event =~ s/\r/\n/g; |
|---|
| 422 | } else { |
|---|
| 423 | $event =~ s/\r//g; |
|---|
| 424 | } |
|---|
| 425 | |
|---|
| 426 | return undef |
|---|
| 427 | unless common_event_validation($dbs, $req, $err, $flags); |
|---|
| 428 | |
|---|
| 429 | ### allow for posting to journals that aren't yours (if you have permission) |
|---|
| 430 | my $posterid = $u->{'userid'}; |
|---|
| 431 | my $ownerid = $flags->{'ownerid'}; |
|---|
| 432 | |
|---|
| 433 | # make the proper date format |
|---|
| 434 | my $qeventtime = $dbh->quote(sprintf("%04d-%02d-%02d %02d:%02d", |
|---|
| 435 | $req->{'year'}, $req->{'mon'}, |
|---|
| 436 | $req->{'day'}, $req->{'hour'}, |
|---|
| 437 | $req->{'min'})); |
|---|
| 438 | my $qsubject = $dbh->quote($req->{'subject'}); |
|---|
| 439 | my $qallowmask = $req->{'allowmask'}+0; |
|---|
| 440 | my $qsecurity = "public"; |
|---|
| 441 | my $uselogsec = 0; |
|---|
| 442 | if ($req->{'security'} eq "usemask" || $req->{'security'} eq "private") { |
|---|
| 443 | $qsecurity = $req->{'security'}; |
|---|
| 444 | } |
|---|
| 445 | if ($req->{'security'} eq "usemask") { |
|---|
| 446 | $uselogsec = 1; |
|---|
| 447 | } |
|---|
| 448 | $qsecurity = $dbh->quote($qsecurity); |
|---|
| 449 | |
|---|
| 450 | ### make sure user can't post with "custom security" on shared journals |
|---|
| 451 | return fail($err,102) |
|---|
| 452 | if ($req->{'security'} eq "usemask" && |
|---|
| 453 | $qallowmask != 1 && ($ownerid != $posterid)); |
|---|
| 454 | |
|---|
| 455 | ### do processing of embedded polls |
|---|
| 456 | my @polls = (); |
|---|
| 457 | if (LJ::Poll::contains_new_poll(\$event)) |
|---|
| 458 | { |
|---|
| 459 | return fail($err,301,"Your account type doesn't permit creating polls.") |
|---|
| 460 | unless (LJ::get_cap($u, "makepoll")); |
|---|
| 461 | |
|---|
| 462 | my $error = ""; |
|---|
| 463 | @polls = LJ::Poll::parse($dbs, \$event, \$error, { |
|---|
| 464 | 'journalid' => $ownerid, |
|---|
| 465 | 'posterid' => $posterid, |
|---|
| 466 | }); |
|---|
| 467 | return fail($err,103,$error) if $error; |
|---|
| 468 | } |
|---|
| 469 | |
|---|
| 470 | # make sure this user isn't banned from posting here (if |
|---|
| 471 | # this is a community journal) |
|---|
| 472 | return fail($err,151) if |
|---|
| 473 | LJ::is_banned($dbs, $posterid, $ownerid); |
|---|
| 474 | |
|---|
| 475 | # don't allow backdated posts in communities |
|---|
| 476 | return fail($err,152) if |
|---|
| 477 | ($req->{'props'}->{"opt_backdated"} && |
|---|
| 478 | $uowner->{'journaltype'} ne "P"); |
|---|
| 479 | |
|---|
| 480 | my $qownerid = $ownerid+0; |
|---|
| 481 | my $qposterid = $posterid+0; |
|---|
| 482 | |
|---|
| 483 | # by default we record the true reverse time that the item was entered. |
|---|
| 484 | # however, if backdate is on, we put the reverse time at the end of time |
|---|
| 485 | # (which makes it equivalent to 1969, but get_recent_items will never load |
|---|
| 486 | # it... where clause there is: < $LJ::EndOfTime). but this way we can |
|---|
| 487 | # have entries that don't show up on friends view, now that we don't have |
|---|
| 488 | # the hints table to not insert into. |
|---|
| 489 | my $rlogtime = "$LJ::EndOfTime"; |
|---|
| 490 | unless ($req->{'props'}->{"opt_backdated"}) { |
|---|
| 491 | $rlogtime .= "-UNIX_TIMESTAMP()"; |
|---|
| 492 | } |
|---|
| 493 | |
|---|
| 494 | my $dbcm = $dbh; |
|---|
| 495 | my $clustered = 0; |
|---|
| 496 | my $anum = int(rand(256)); |
|---|
| 497 | |
|---|
| 498 | if ($uowner->{'clusterid'}) { |
|---|
| 499 | $dbcm = LJ::get_cluster_master($uowner); |
|---|
| 500 | $clustered = 1; |
|---|
| 501 | |
|---|
| 502 | return fail($err,306) unless $dbcm; |
|---|
| 503 | |
|---|
| 504 | # before we get going here, we want to make sure to purge this user's |
|---|
| 505 | # delitem cmd buffer, otherwise we could have a race and that might |
|---|
| 506 | # wake up later and delete this item which is replacing in the database |
|---|
| 507 | # the old last item which is marked for deletion: |
|---|
| 508 | LJ::cmd_buffer_flush($dbh, $dbcm, "delitem", $ownerid); |
|---|
| 509 | |
|---|
| 510 | $dbcm->do("INSERT INTO log2 (journalid, posterid, eventtime, logtime, security, ". |
|---|
| 511 | "allowmask, replycount, year, month, day, revttime, rlogtime, anum) ". |
|---|
| 512 | "VALUES ($qownerid, $qposterid, $qeventtime, NOW(), $qsecurity, $qallowmask, ". |
|---|
| 513 | "0, $req->{'year'}, $req->{'mon'}, $req->{'day'}, $LJ::EndOfTime-". |
|---|
| 514 | "UNIX_TIMESTAMP($qeventtime), $rlogtime, $anum)"); |
|---|
| 515 | } else { |
|---|
| 516 | $dbcm->do("INSERT INTO log (ownerid, posterid, eventtime, logtime, security, ". |
|---|
| 517 | "allowmask, replycount, year, month, day, revttime, rlogtime) ". |
|---|
| 518 | "VALUES ($qownerid, $qposterid, $qeventtime, NOW(), $qsecurity, $qallowmask, ". |
|---|
| 519 | "0, $req->{'year'}, $req->{'mon'}, $req->{'day'}, $LJ::EndOfTime-". |
|---|
| 520 | "UNIX_TIMESTAMP($qeventtime), $rlogtime)"); |
|---|
| 521 | } |
|---|
| 522 | return fail($err,501,$dbcm->errstr) if $dbcm->err; |
|---|
| 523 | |
|---|
| 524 | my $itemid = $dbcm->{'mysql_insertid'}; |
|---|
| 525 | return fail($err,501,"No itemid could be generated.") unless $itemid; |
|---|
| 526 | |
|---|
| 527 | my $ditemid = $clustered ? ($itemid * 256 + $anum) : $itemid; |
|---|
| 528 | |
|---|
| 529 | ### finish embedding stuff now that we have the itemid |
|---|
| 530 | { |
|---|
| 531 | ### this should NOT return an error, and we're mildly fucked by now |
|---|
| 532 | ### if it does (would have to delete the log row up there), so we're |
|---|
| 533 | ### not going to check it for now. |
|---|
| 534 | |
|---|
| 535 | my $error = ""; |
|---|
| 536 | LJ::Poll::register($dbs, \$event, \$error, $ditemid, @polls); |
|---|
| 537 | } |
|---|
| 538 | #### /embedding |
|---|
| 539 | |
|---|
| 540 | ### extract links for meme tracking |
|---|
| 541 | unless ($req->{'security'} eq "usemask" || |
|---|
| 542 | $req->{'security'} eq "private") |
|---|
| 543 | { |
|---|
| 544 | foreach my $url (LJ::get_urls($event)) { |
|---|
| 545 | my $jid = $clustered ? $ownerid : 0; |
|---|
| 546 | LJ::record_meme($dbs, $url, $posterid, $ditemid, $jid); |
|---|
| 547 | } |
|---|
| 548 | } |
|---|
| 549 | |
|---|
| 550 | # record journal's disk usage (clustered users only) |
|---|
| 551 | if ($clustered) |
|---|
| 552 | { |
|---|
| 553 | my $bytes = length($event) + length($req->{'subject'}); |
|---|
| 554 | LJ::dudata_set($dbcm, $ownerid, 'L', $itemid, $bytes); |
|---|
| 555 | } |
|---|
| 556 | |
|---|
| 557 | my $qevent = $dbh->quote($event); |
|---|
| 558 | $event = ""; |
|---|
| 559 | |
|---|
| 560 | my @prefix = (""); |
|---|
| 561 | if ($LJ::USE_RECENT_TABLES) { push @prefix, "recent_"; } |
|---|
| 562 | foreach my $pfx (@prefix) |
|---|
| 563 | { |
|---|
| 564 | if ($clustered) { |
|---|
| 565 | # clustered recents have times now (for purger process.. can't use itemid key prefix) |
|---|
| 566 | my ($ec, $ev); |
|---|
| 567 | if ($pfx) { ($ec, $ev) = (", logtime", ", NOW()"); } |
|---|
| 568 | $dbcm->do("REPLACE INTO ${pfx}logtext2 (journalid, jitemid, subject, event $ec) ". |
|---|
| 569 | "VALUES ($ownerid, $itemid, $qsubject, $qevent $ev)"); |
|---|
| 570 | if ($dbcm->err) { |
|---|
| 571 | my $msg = $dbcm->errstr; |
|---|
| 572 | LJ::delete_item2($dbh, $dbcm, $ownerid, $itemid); # roll-back |
|---|
| 573 | return fail($err,501,"logtext:$msg"); |
|---|
| 574 | } |
|---|
| 575 | } else { |
|---|
| 576 | $dbh->do("INSERT INTO ${pfx}logtext (itemid, subject, event) ". |
|---|
| 577 | "VALUES ($itemid, $qsubject, $qevent)"); |
|---|
| 578 | if ($dbh->err) { |
|---|
| 579 | my $msg = $dbh->errstr; |
|---|
| 580 | LJ::delete_item($dbh, $ownerid, $itemid); # roll-back |
|---|
| 581 | return fail($err,501,$msg); |
|---|
| 582 | } |
|---|
| 583 | } |
|---|
| 584 | } |
|---|
| 585 | |
|---|
| 586 | # this is to speed month view and other places that don't need full text. |
|---|
| 587 | if ($clustered) { |
|---|
| 588 | $dbcm->do("REPLACE INTO logsubject2 (journalid, jitemid, subject) ". |
|---|
| 589 | "VALUES ($ownerid, $itemid, $qsubject)"); |
|---|
| 590 | if ($dbcm->err) { |
|---|
| 591 | my $msg = $dbcm->errstr; |
|---|
| 592 | LJ::delete_item2($dbh, $dbcm, $ownerid, $itemid); # roll-back |
|---|
| 593 | return fail($err,501,"logsubject:$msg"); |
|---|
| 594 | } |
|---|
| 595 | } else { |
|---|
| 596 | $dbh->do("INSERT INTO logsubject (itemid, subject) VALUES ($itemid, $qsubject)"); |
|---|
| 597 | if ($dbh->err) { |
|---|
| 598 | my $msg = $dbh->errstr; |
|---|
| 599 | LJ::delete_item($dbh, $ownerid, $itemid); # roll-back |
|---|
| 600 | return fail($err,501,$msg); |
|---|
| 601 | } |
|---|
| 602 | } |
|---|
| 603 | |
|---|
| 604 | ## update sync table (selected from log table, so logtime is identical!) |
|---|
| 605 | if ($clustered) { |
|---|
| 606 | $dbcm->do("REPLACE INTO syncupdates2 (userid, atime, nodetype, nodeid, atype) ". |
|---|
| 607 | "SELECT journalid, logtime, 'L', jitemid, 'create' FROM log2 ". |
|---|
| 608 | "WHERE journalid=$ownerid AND jitemid=$itemid"); |
|---|
| 609 | if ($dbcm->err) { |
|---|
| 610 | my $msg = $dbcm->errstr; |
|---|
| 611 | LJ::delete_item2($dbh, $dbcm, $ownerid, $itemid); # roll-back |
|---|
| 612 | return fail($err,501,$msg); |
|---|
| 613 | } |
|---|
| 614 | } else { |
|---|
| 615 | $dbh->do("REPLACE INTO syncupdates (userid, atime, nodetype, nodeid, atype) ". |
|---|
| 616 | "SELECT ownerid, logtime, 'L', itemid, 'create' FROM log WHERE itemid=$itemid"); |
|---|
| 617 | if ($dbh->err) { |
|---|
| 618 | my $msg = $dbh->errstr; |
|---|
| 619 | LJ::delete_item($dbh, $ownerid, $itemid); # roll-back |
|---|
| 620 | return fail($err,501,$msg); |
|---|
| 621 | } |
|---|
| 622 | } |
|---|
| 623 | |
|---|
| 624 | # keep track of custom security stuff in other table. |
|---|
| 625 | if ($uselogsec) { |
|---|
| 626 | if ($clustered) { |
|---|
| 627 | $dbcm->do("REPLACE INTO logsec2 (journalid, jitemid, allowmask) ". |
|---|
| 628 | "VALUES ($qownerid, $itemid, $qallowmask)"); |
|---|
| 629 | if ($dbcm->err) { |
|---|
| 630 | my $msg = $dbcm->errstr; |
|---|
| 631 | LJ::delete_item2($dbh, $dbcm, $ownerid, $itemid); # roll-back |
|---|
| 632 | return fail($err,501,"logsec2:$msg"); |
|---|
| 633 | } |
|---|
| 634 | } else { |
|---|
| 635 | $dbh->do("INSERT INTO logsec (ownerid, itemid, allowmask) ". |
|---|
| 636 | "VALUES ($qownerid, $itemid, $qallowmask)"); |
|---|
| 637 | if ($dbh->err) { |
|---|
| 638 | my $msg = $dbh->errstr; |
|---|
| 639 | LJ::delete_item($dbh, $ownerid, $itemid); # roll-back |
|---|
| 640 | return fail($err,501,$msg); |
|---|
| 641 | } |
|---|
| 642 | } |
|---|
| 643 | } |
|---|
| 644 | |
|---|
| 645 | # meta-data |
|---|
| 646 | if (%{$req->{'props'}}) { |
|---|
| 647 | my $propinsert = ""; |
|---|
| 648 | foreach my $pname (keys %{$req->{'props'}}) { |
|---|
| 649 | next unless $req->{'props'}->{$pname}; |
|---|
| 650 | if ($propinsert) { |
|---|
| 651 | $propinsert .= ", "; |
|---|
| 652 | } else { |
|---|
| 653 | if ($clustered) { |
|---|
| 654 | $propinsert = "REPLACE INTO logprop2 (journalid, jitemid, propid, value) VALUES "; |
|---|
| 655 | } else { |
|---|
| 656 | $propinsert = "INSERT INTO logprop (itemid, propid, value) VALUES "; |
|---|
| 657 | } |
|---|
| 658 | } |
|---|
| 659 | my $p = LJ::get_prop("log", $pname); |
|---|
| 660 | if ($p) { |
|---|
| 661 | my $qvalue = $dbh->quote($req->{'props'}->{$pname}); |
|---|
| 662 | if ($clustered) { |
|---|
| 663 | $propinsert .= "($ownerid, $itemid, $p->{'id'}, $qvalue)"; |
|---|
| 664 | } else { |
|---|
| 665 | $propinsert .= "($itemid, $p->{'id'}, $qvalue)"; |
|---|
| 666 | } |
|---|
| 667 | } |
|---|
| 668 | } |
|---|
| 669 | if ($propinsert) { |
|---|
| 670 | $dbcm->do($propinsert); # note: $dbcm may be $dbh |
|---|
| 671 | if ($dbcm->err) { |
|---|
| 672 | my $msg = $dbh->errstr; |
|---|
| 673 | if ($clustered) { |
|---|
| 674 | LJ::delete_item2($dbh, $dbcm, $ownerid, $itemid); # roll-back |
|---|
| 675 | } else { |
|---|
| 676 | LJ::delete_item($dbh, $ownerid, $itemid); # roll-back |
|---|
| 677 | } |
|---|
| 678 | return fail($err,501,"logprop2:$msg"); |
|---|
| 679 | } |
|---|
| 680 | } |
|---|
| 681 | } |
|---|
| 682 | |
|---|
| 683 | $dbh->do("UPDATE userusage SET timeupdate=NOW(), lastitemid=$itemid ". |
|---|
| 684 | "WHERE userid=$qownerid"); |
|---|
| 685 | |
|---|
| 686 | if ($u->{'track'} eq "yes") { |
|---|
| 687 | # dear community, relax. if we get a court order to provide data on somebody, |
|---|
| 688 | # we're legally required to. this doesn't enable us to do that. it enables |
|---|
| 689 | # us to do it without killing the database and/or servers as we do O(n) scans |
|---|
| 690 | # over everything and grep the hell out of hundreds of gigs of webserver logs. |
|---|
| 691 | my $quserid = $u->{'userid'}+0; |
|---|
| 692 | my $qip = $dbh->quote($ENV{'REMOTE_ADDR'}); |
|---|
| 693 | $dbh->do("INSERT INTO tracking (userid, acttime, ip, actdes, associd) ". |
|---|
| 694 | "VALUES ($quserid, NOW(), $qip, 'post', $itemid)"); |
|---|
| 695 | } |
|---|
| 696 | |
|---|
| 697 | my $res = {}; |
|---|
| 698 | $res->{'itemid'} = $itemid; # by request of mart |
|---|
| 699 | $res->{'anum'} = $anum if $clustered; |
|---|
| 700 | return $res; |
|---|
| 701 | } |
|---|
| 702 | |
|---|
| 703 | sub editevent |
|---|
| 704 | { |
|---|
| 705 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 706 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 707 | return undef unless check_altusage($dbs, $req, $err, $flags); |
|---|
| 708 | |
|---|
| 709 | my $u = $flags->{'u'}; |
|---|
| 710 | my $ownerid = $flags->{'ownerid'}; |
|---|
| 711 | my $uowner = $flags->{'u_owner'} || $u; |
|---|
| 712 | my $posterid = $u->{'userid'}; |
|---|
| 713 | my $dbr = $dbs->{'reader'}; |
|---|
| 714 | my $dbh = $dbs->{'dbh'}; |
|---|
| 715 | my $sth; |
|---|
| 716 | |
|---|
| 717 | my $qitemid = $req->{'itemid'}+0; |
|---|
| 718 | |
|---|
| 719 | # check the journal's read-only bit |
|---|
| 720 | return fail($err,306) if LJ::get_cap($uowner, "readonly"); |
|---|
| 721 | |
|---|
| 722 | my ($dbcm, $dbcr, $clustered) = ($dbh, $dbr, 0); |
|---|
| 723 | if ($uowner->{'clusterid'}) { |
|---|
| 724 | $dbcm = LJ::get_cluster_master($uowner); |
|---|
| 725 | $clustered = 1; |
|---|
| 726 | } |
|---|
| 727 | return fail($err,306) unless $dbcm; |
|---|
| 728 | |
|---|
| 729 | # fetch the old entry from master database so we know what we |
|---|
| 730 | # really have to update later. usually people just edit one part, |
|---|
| 731 | # not every field in every table. reads are quicker than writes, |
|---|
| 732 | # so this is worth it. |
|---|
| 733 | my $oldevent; |
|---|
| 734 | if ($clustered) |
|---|
| 735 | { |
|---|
| 736 | $oldevent = $dbcm->selectrow_hashref |
|---|
| 737 | ("SELECT l.journalid AS 'ownerid', l.posterid, l.eventtime, l.logtime, ". |
|---|
| 738 | "l.compressed, l.security, l.allowmask, l.year, l.month, l.day, lt.subject, ". |
|---|
| 739 | "MD5(lt.event) AS 'md5event', l.rlogtime, l.anum FROM log2 l, logtext2 lt ". |
|---|
| 740 | "WHERE l.journalid=$ownerid AND lt.journalid=$ownerid ". |
|---|
| 741 | "AND l.jitemid=$qitemid AND lt.jitemid=$qitemid"); |
|---|
| 742 | } else { |
|---|
| 743 | $oldevent = $dbcm->selectrow_hashref |
|---|
| 744 | ("SELECT l.ownerid, l.posterid, l.eventtime, l.logtime, ". |
|---|
| 745 | "l.compressed, l.security, l.allowmask, l.year, l.month, l.day, lt.subject, ". |
|---|
| 746 | "MD5(lt.event) AS 'md5event', l.rlogtime FROM log l, logtext lt ". |
|---|
| 747 | "WHERE l.itemid=$qitemid AND lt.itemid=$qitemid"); |
|---|
| 748 | } |
|---|
| 749 | |
|---|
| 750 | ### make sure this user is allowed to edit this entry |
|---|
| 751 | return fail($err,302) |
|---|
| 752 | unless ($ownerid == $oldevent->{'ownerid'}); |
|---|
| 753 | |
|---|
| 754 | ### what can they do to somebody elses entry? (in shared journal) |
|---|
| 755 | if ($posterid != $oldevent->{'posterid'}) |
|---|
| 756 | { |
|---|
| 757 | ## deleting. |
|---|
| 758 | return fail($err,304) |
|---|
| 759 | if ($req->{'event'} !~ /\S/ && ! |
|---|
| 760 | ($ownerid == $u->{'userid'} || |
|---|
| 761 | # community account can delete it (ick) |
|---|
| 762 | |
|---|
| 763 | LJ::check_priv($dbr, $u, |
|---|
| 764 | "sharedjournal", $req->{'usejournal'}) |
|---|
| 765 | # if user is a community maintainer they can delete |
|---|
| 766 | # it too (good) |
|---|
| 767 | )); |
|---|
| 768 | |
|---|
| 769 | ## editing: |
|---|
| 770 | return fail($err,303) |
|---|
| 771 | if ($req->{'event'} =~ /\S/); |
|---|
| 772 | } |
|---|
| 773 | |
|---|
| 774 | ## update sync table (before we actually do it! in case updates |
|---|
| 775 | ## partially fail below) |
|---|
| 776 | if ($clustered) { |
|---|
| 777 | my $synctype = "update"; |
|---|
| 778 | if ($req->{'event'} !~ /\S/) { $synctype = "del"; } |
|---|
| 779 | $dbcm->do("REPLACE INTO syncupdates2 (userid, atime, nodetype, nodeid, atype) ". |
|---|
| 780 | "VALUES ($ownerid, NOW(), 'L', $qitemid, '$synctype')"); |
|---|
| 781 | } else { |
|---|
| 782 | $dbh->do("REPLACE INTO syncupdates (userid, atime, nodetype, nodeid, atype) ". |
|---|
| 783 | "VALUES ($ownerid, NOW(), 'L', $qitemid, 'update')"); |
|---|
| 784 | } |
|---|
| 785 | |
|---|
| 786 | # simple logic for deleting an entry |
|---|
| 787 | if ($req->{'event'} !~ /\S/) |
|---|
| 788 | { |
|---|
| 789 | if ($clustered) { |
|---|
| 790 | LJ::delete_item2($dbh, $dbcm, $ownerid, $req->{'itemid'}, |
|---|
| 791 | 'quick', $oldevent->{'anum'}); |
|---|
| 792 | } else { |
|---|
| 793 | LJ::delete_item($dbh, $ownerid, $req->{'itemid'}); |
|---|
| 794 | } |
|---|
| 795 | my $res = { 'itemid' => $qitemid, |
|---|
| 796 | 'anum' => $oldevent->{'anum'} }; |
|---|
| 797 | return $res; |
|---|
| 798 | } |
|---|
| 799 | |
|---|
| 800 | # don't allow backdated posts in communities |
|---|
| 801 | return fail($err,152) if |
|---|
| 802 | ($req->{'props'}->{"opt_backdated"} && |
|---|
| 803 | $uowner->{'journaltype'} ne "P"); |
|---|
| 804 | |
|---|
| 805 | # updating an entry: |
|---|
| 806 | return undef |
|---|
| 807 | unless common_event_validation($dbs, $req, $err, $flags); |
|---|
| 808 | |
|---|
| 809 | ### load existing meta-data |
|---|
| 810 | my %curprops; |
|---|
| 811 | |
|---|
| 812 | if ($clustered) { |
|---|
| 813 | LJ::load_props($dbs, "log"); |
|---|
| 814 | LJ::load_log_props2($dbcm, $ownerid, [ $qitemid ], \%curprops); |
|---|
| 815 | } else { |
|---|
| 816 | LJ::load_log_props($dbh, [ $qitemid ], \%curprops); |
|---|
| 817 | } |
|---|
| 818 | |
|---|
| 819 | ## handle meta-data (properties) |
|---|
| 820 | my %props_byname = (); |
|---|
| 821 | foreach my $key (keys %{$req->{'props'}}) { |
|---|
| 822 | ## changing to something else? |
|---|
| 823 | if ($curprops{$qitemid}->{$key} ne $req->{'props'}->{$key}) { |
|---|
| 824 | $props_byname{$key} = $req->{'props'}->{$key}; |
|---|
| 825 | } |
|---|
| 826 | } |
|---|
| 827 | |
|---|
| 828 | #### clean up the event text |
|---|
| 829 | my $event = $req->{'event'}; |
|---|
| 830 | |
|---|
| 831 | # remove surrounding whitespace |
|---|
| 832 | $event =~ s/^\s+//; |
|---|
| 833 | $event =~ s/\s+$//; |
|---|
| 834 | |
|---|
| 835 | # convert line endings to unix format |
|---|
| 836 | if ($req->{'lineendings'} eq "mac") { |
|---|
| 837 | $event =~ s/\r/\n/g; |
|---|
| 838 | } else { |
|---|
| 839 | $event =~ s/\r//g; |
|---|
| 840 | } |
|---|
| 841 | my $qevent = $dbh->quote($event); |
|---|
| 842 | my $bytes = length($event) + length($req->{'subject'}); |
|---|
| 843 | $event = ""; |
|---|
| 844 | |
|---|
| 845 | my $eventtime = sprintf("%04d-%02d-%02d %02d:%02d", |
|---|
| 846 | map { $req->{$_} } qw(year mon day hour min)); |
|---|
| 847 | my $qeventtime = $dbh->quote($eventtime); |
|---|
| 848 | |
|---|
| 849 | my $qallowmask = $req->{'allowmask'}+0; |
|---|
| 850 | my $security = "public"; |
|---|
| 851 | if ($req->{'security'} eq "private" || $req->{'security'} eq "usemask") { |
|---|
| 852 | $security = $req->{'security'}; |
|---|
| 853 | } |
|---|
| 854 | |
|---|
| 855 | my $qyear = $req->{'year'}+0; |
|---|
| 856 | my $qmonth = $req->{'mon'}+0; |
|---|
| 857 | my $qday = $req->{'day'}+0; |
|---|
| 858 | |
|---|
| 859 | if ($qyear != $oldevent->{'year'} || |
|---|
| 860 | $qmonth != $oldevent->{'month'} || |
|---|
| 861 | $qday != $oldevent->{'day'} || |
|---|
| 862 | $eventtime ne $oldevent->{'eventtime'} || |
|---|
| 863 | $security ne $oldevent->{'security'} || |
|---|
| 864 | $qallowmask != $oldevent->{'allowmask'} |
|---|
| 865 | ) |
|---|
| 866 | { |
|---|
| 867 | my $qsecurity = $dbh->quote($security); |
|---|
| 868 | if ($clustered) { |
|---|
| 869 | $dbcm->do("UPDATE log2 SET eventtime=$qeventtime, revttime=$LJ::EndOfTime-". |
|---|
| 870 | "UNIX_TIMESTAMP($qeventtime), year=$qyear, month=$qmonth, day=$qday, ". |
|---|
| 871 | "security=$qsecurity, allowmask=$qallowmask WHERE journalid=$ownerid ". |
|---|
| 872 | "AND jitemid=$qitemid"); |
|---|
| 873 | } else { |
|---|
| 874 | $dbh->do("UPDATE log SET eventtime=$qeventtime, revttime=$LJ::EndOfTime-". |
|---|
| 875 | "UNIX_TIMESTAMP($qeventtime), year=$qyear, month=$qmonth, day=$qday, ". |
|---|
| 876 | "security=$qsecurity, allowmask=$qallowmask WHERE itemid=$qitemid"); |
|---|
| 877 | } |
|---|
| 878 | } |
|---|
| 879 | |
|---|
| 880 | if ($security ne $oldevent->{'security'} || |
|---|
| 881 | $qallowmask != $oldevent->{'allowmask'}) |
|---|
| 882 | { |
|---|
| 883 | if ($security eq "public" || $security eq "private") { |
|---|
| 884 | if ($clustered) { |
|---|
| 885 | $dbcm->do("DELETE FROM logsec2 WHERE journalid=$ownerid AND jitemid=$qitemid"); |
|---|
| 886 | } else { |
|---|
| 887 | $dbh->do("DELETE FROM logsec WHERE ownerid=$ownerid AND itemid=$qitemid"); |
|---|
| 888 | } |
|---|
| 889 | } else { |
|---|
| 890 | my $qsecurity = $dbh->quote($security); |
|---|
| 891 | if ($clustered) { |
|---|
| 892 | $dbcm->do("REPLACE INTO logsec2 (journalid, jitemid, allowmask) ". |
|---|
| 893 | "VALUES ($ownerid, $qitemid, $qallowmask)"); |
|---|
| 894 | } else { |
|---|
| 895 | $dbh->do("REPLACE INTO logsec (ownerid, itemid, allowmask) ". |
|---|
| 896 | "VALUES ($ownerid, $qitemid, $qallowmask)"); |
|---|
| 897 | } |
|---|
| 898 | } |
|---|
| 899 | return fail($err,501,$dbcm->errstr) if $dbcm->err; |
|---|
| 900 | } |
|---|
| 901 | |
|---|
| 902 | if (Digest::MD5::md5_hex($event) ne $oldevent->{'md5event'} || |
|---|
| 903 | $req->{'subject'} ne $oldevent->{'subject'}) |
|---|
| 904 | { |
|---|
| 905 | my $qsubject = $dbh->quote($req->{'subject'}); |
|---|
| 906 | |
|---|
| 907 | my @prefix = (""); |
|---|
| 908 | if ($LJ::USE_RECENT_TABLES) { push @prefix, "recent_"; } |
|---|
| 909 | foreach my $pfx (@prefix) { |
|---|
| 910 | if ($clustered) { |
|---|
| 911 | $dbcm->do("UPDATE ${pfx}logtext2 SET event=$qevent, subject=$qsubject ". |
|---|
| 912 | "WHERE journalid=$ownerid AND jitemid=$qitemid"); |
|---|
| 913 | } else { |
|---|
| 914 | $dbh->do("UPDATE ${pfx}logtext SET event=$qevent, subject=$qsubject ". |
|---|
| 915 | "WHERE itemid=$qitemid"); |
|---|
| 916 | } |
|---|
| 917 | return fail($err,501,$dbcm->errstr) if $dbcm->err; |
|---|
| 918 | } |
|---|
| 919 | if ($clustered) { |
|---|
| 920 | $dbcm->do("REPLACE INTO logsubject2 (journalid, jitemid, subject) ". |
|---|
| 921 | "VALUES ($ownerid, $qitemid, $qsubject)"); |
|---|
| 922 | } else { |
|---|
| 923 | $dbh->do("REPLACE INTO logsubject (itemid, subject) ". |
|---|
| 924 | "VALUES ($qitemid, $qsubject)"); |
|---|
| 925 | } |
|---|
| 926 | |
|---|
| 927 | # update disk usage |
|---|
| 928 | if ($clustered) { |
|---|
| 929 | LJ::dudata_set($dbcm, $ownerid, 'L', $qitemid, $bytes); |
|---|
| 930 | } |
|---|
| 931 | |
|---|
| 932 | return fail($err,501,$dbcm->errstr) if $dbcm->err; |
|---|
| 933 | } |
|---|
| 934 | |
|---|
| 935 | if (%{$req->{'props'}}) { |
|---|
| 936 | my $propinsert = ""; |
|---|
| 937 | my @props_to_delete; |
|---|
| 938 | foreach my $pname (keys %{$req->{'props'}}) { |
|---|
| 939 | my $p = LJ::get_prop("log", $pname); |
|---|
| 940 | next unless $p; |
|---|
| 941 | my $val = $req->{'props'}->{$pname}; |
|---|
| 942 | unless ($val) { |
|---|
| 943 | push @props_to_delete, $p->{'id'}; |
|---|
| 944 | next; |
|---|
| 945 | } |
|---|
| 946 | if ($propinsert) { |
|---|
| 947 | $propinsert .= ", "; |
|---|
| 948 | } else { |
|---|
| 949 | if ($clustered) { |
|---|
| 950 | $propinsert = "REPLACE INTO logprop2 (journalid, jitemid, propid, value) VALUES "; |
|---|
| 951 | } else { |
|---|
| 952 | $propinsert = "REPLACE INTO logprop (itemid, propid, value) VALUES "; |
|---|
| 953 | } |
|---|
| 954 | } |
|---|
| 955 | my $qvalue = $dbh->quote($val); |
|---|
| 956 | if ($clustered) { |
|---|
| 957 | $propinsert .= "($ownerid, $qitemid, $p->{'id'}, $qvalue)"; |
|---|
| 958 | } else { |
|---|
| 959 | $propinsert .= "($qitemid, $p->{'id'}, $qvalue)"; |
|---|
| 960 | } |
|---|
| 961 | } |
|---|
| 962 | if ($propinsert) { $dbcm->do($propinsert); } |
|---|
| 963 | if (@props_to_delete) { |
|---|
| 964 | my $propid_in = join(", ", @props_to_delete); |
|---|
| 965 | if ($clustered) { |
|---|
| 966 | $dbcm->do("DELETE FROM logprop2 WHERE journalid=$ownerid AND ". |
|---|
| 967 | "jitemid=$qitemid AND propid IN ($propid_in)"); |
|---|
| 968 | } else { |
|---|
| 969 | $dbh->do("DELETE FROM logprop WHERE itemid=$qitemid AND propid IN ($propid_in)"); |
|---|
| 970 | } |
|---|
| 971 | } |
|---|
| 972 | } |
|---|
| 973 | |
|---|
| 974 | # deal with backdated changes. if the entry's rlogtime is |
|---|
| 975 | # $EndOfTime, then it's backdated. if they want that off, need to |
|---|
| 976 | # reset rlogtime to real reverse log time. also need to set |
|---|
| 977 | # rlogtime to $EndOfTime if they're turning backdate on. |
|---|
| 978 | if ($req->{'props'}->{'opt_backdated'} eq "1" && |
|---|
| 979 | $oldevent->{'rlogtime'} != $LJ::EndOfTime) { |
|---|
| 980 | if ($clustered) { |
|---|
| 981 | $dbh->do("UPDATE log SET rlogtime=$LJ::EndOfTime WHERE ". |
|---|
| 982 | "itemid=$qitemid"); |
|---|
| 983 | } else { |
|---|
| 984 | $dbcm->do("UPDATE log2 SET rlogtime=$LJ::EndOfTime WHERE ". |
|---|
| 985 | "journalid=$ownerid AND jitemid=$qitemid"); |
|---|
| 986 | } |
|---|
| 987 | } |
|---|
| 988 | if ($req->{'props'}->{'opt_backdated'} eq "0" && |
|---|
| 989 | $oldevent->{'rlogtime'} == $LJ::EndOfTime) { |
|---|
| 990 | if ($clustered) { |
|---|
| 991 | $dbcm->do("UPDATE log2 SET rlogtime=$LJ::EndOfTime-UNIX_TIMESTAMP(logtime) ". |
|---|
| 992 | "WHERE journalid=$ownerid AND jitemid=$qitemid"); |
|---|
| 993 | } else { |
|---|
| 994 | $dbh->do("UPDATE log SET rlogtime=$LJ::EndOfTime-UNIX_TIMESTAMP(logtime) ". |
|---|
| 995 | "WHERE itemid=$qitemid"); |
|---|
| 996 | } |
|---|
| 997 | } |
|---|
| 998 | |
|---|
| 999 | return fail($err,501,$dbcm->errstr) if $dbcm->err; |
|---|
| 1000 | |
|---|
| 1001 | my $res = { 'itemid' => $qitemid }; |
|---|
| 1002 | $res->{'anum'} = $oldevent->{'anum'} if defined $oldevent->{'anum'}; |
|---|
| 1003 | return $res; |
|---|
| 1004 | } |
|---|
| 1005 | |
|---|
| 1006 | sub getevents |
|---|
| 1007 | { |
|---|
| 1008 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 1009 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 1010 | return undef unless check_altusage($dbs, $req, $err, $flags); |
|---|
| 1011 | |
|---|
| 1012 | my $u = $flags->{'u'}; |
|---|
| 1013 | my $uowner = $flags->{'u_owner'} || $u; |
|---|
| 1014 | |
|---|
| 1015 | ### shared-journal support |
|---|
| 1016 | my $posterid = $u->{'userid'}; |
|---|
| 1017 | my $ownerid = $flags->{'ownerid'}; |
|---|
| 1018 | |
|---|
| 1019 | my $dbr = $dbs->{'reader'}; |
|---|
| 1020 | my $dbh = $dbs->{'dbh'}; |
|---|
| 1021 | my $sth; |
|---|
| 1022 | |
|---|
| 1023 | my ($dbcr, $clustered) = ($dbr, 0); |
|---|
| 1024 | if ($uowner->{'clusterid'}) { |
|---|
| 1025 | $dbcr = LJ::get_cluster_reader($uowner); |
|---|
| 1026 | $clustered = 1; |
|---|
| 1027 | } |
|---|
| 1028 | return fail($err,502) unless $dbcr; |
|---|
| 1029 | |
|---|
| 1030 | # if this is on, we sort things different (logtime vs. posttime) |
|---|
| 1031 | # to avoid timezone issues |
|---|
| 1032 | my $is_community = ($uowner->{'journaltype'} eq "C" || |
|---|
| 1033 | $uowner->{'journaltype'} eq "S"); |
|---|
| 1034 | |
|---|
| 1035 | # in some cases we'll use the master, to ensure there's no |
|---|
| 1036 | # replication delay. useful cases: getting one item, use master |
|---|
| 1037 | # since user might have just made a typo and realizes it as they |
|---|
| 1038 | # post, or wants to append something they forgot, etc, etc. in |
|---|
| 1039 | # other cases, slave is pretty sure to have it. |
|---|
| 1040 | my $use_master = 0; |
|---|
| 1041 | |
|---|
| 1042 | # the benefit of this mode over actually doing 'lastn/1' is |
|---|
| 1043 | # the $use_master usage. |
|---|
| 1044 | if ($req->{'selecttype'} eq "one" && $req->{'itemid'} eq "-1") { |
|---|
| 1045 | $req->{'selecttype'} = "lastn"; |
|---|
| 1046 | $req->{'howmany'} = 1; |
|---|
| 1047 | undef $req->{'itemid'}; |
|---|
| 1048 | $use_master = 1; # see note above. |
|---|
| 1049 | } |
|---|
| 1050 | |
|---|
| 1051 | # build the query to get log rows. each selecttype branch is |
|---|
| 1052 | # responsible for either populating the following 3 variables |
|---|
| 1053 | # OR just populating $sql |
|---|
| 1054 | my ($orderby, $where, $limit); |
|---|
| 1055 | my $sql; |
|---|
| 1056 | if ($req->{'selecttype'} eq "day") |
|---|
| 1057 | { |
|---|
| 1058 | return fail($err,203) |
|---|
| 1059 | unless ($req->{'year'} =~ /^\d\d\d\d$/ && |
|---|
| 1060 | $req->{'month'} =~ /^\d\d?$/ && |
|---|
| 1061 | $req->{'day'} =~ /^\d\d?$/ && |
|---|
| 1062 | $req->{'month'} >= 1 && $req->{'month'} <= 12 && |
|---|
| 1063 | $req->{'day'} >= 1 && $req->{'day'} <= 31); |
|---|
| 1064 | |
|---|
| 1065 | my $qyear = $dbh->quote($req->{'year'}); |
|---|
| 1066 | my $qmonth = $dbh->quote($req->{'month'}); |
|---|
| 1067 | my $qday = $dbh->quote($req->{'day'}); |
|---|
| 1068 | $where = "AND year=$qyear AND month=$qmonth AND day=$qday"; |
|---|
| 1069 | $limit = "LIMIT 200"; # FIXME: unhardcode this constant (also in ljviews.pl) |
|---|
| 1070 | |
|---|
| 1071 | # see note above about why the sort order is different |
|---|
| 1072 | $orderby = $is_community ? "ORDER BY logtime" : "ORDER BY eventtime"; |
|---|
| 1073 | } |
|---|
| 1074 | elsif ($req->{'selecttype'} eq "lastn") |
|---|
| 1075 | { |
|---|
| 1076 | my $howmany = $req->{'howmany'} || 20; |
|---|
| 1077 | if ($howmany > 50) { $howmany = 50; } |
|---|
| 1078 | $howmany = $howmany + 0; |
|---|
| 1079 | $limit = "LIMIT $howmany"; |
|---|
| 1080 | |
|---|
| 1081 | # okay, follow me here... see how we add the revttime predicate |
|---|
| 1082 | # even if no beforedate key is present? you're probably saying, |
|---|
| 1083 | # that's retarded -- you're saying: "revttime > 0", that's like |
|---|
| 1084 | # saying, "if entry occured at all." yes yes, but that hints |
|---|
| 1085 | # mysql's braindead optimizer to use the right index. |
|---|
| 1086 | my $rtime_after = 0; |
|---|
| 1087 | my $rtime_what = $is_community ? "rlogtime" : "revttime"; |
|---|
| 1088 | if ($req->{'beforedate'}) { |
|---|
| 1089 | return fail($err,203,"Invalid beforedate format.") |
|---|
| 1090 | unless ($req->{'beforedate'} =~ |
|---|
| 1091 | /^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$/); |
|---|
| 1092 | my $qd = $dbh->quote($req->{'beforedate'}); |
|---|
| 1093 | $rtime_after = "$LJ::EndOfTime-UNIX_TIMESTAMP($qd)"; |
|---|
| 1094 | } |
|---|
| 1095 | $where .= "AND $rtime_what > $rtime_after "; |
|---|
| 1096 | $orderby = "ORDER BY $rtime_what"; |
|---|
| 1097 | } |
|---|
| 1098 | elsif ($req->{'selecttype'} eq "one") |
|---|
| 1099 | { |
|---|
| 1100 | my $id = $req->{'itemid'} + 0; |
|---|
| 1101 | $where = $clustered ? "AND jitemid=$id" : "AND itemid=$id"; |
|---|
| 1102 | } |
|---|
| 1103 | elsif ($req->{'selecttype'} eq "syncitems") |
|---|
| 1104 | { |
|---|
| 1105 | my ($date); |
|---|
| 1106 | ## have a valid date? |
|---|
| 1107 | $date = $req->{'lastsync'} || "0000-00-00 00:00:00"; |
|---|
| 1108 | if ($date !~ /^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/) { |
|---|
| 1109 | return fail($err,203,"Invalid syncitems date format."); |
|---|
| 1110 | } |
|---|
| 1111 | |
|---|
| 1112 | my $LIMIT = 300; |
|---|
| 1113 | if ($clustered) { |
|---|
| 1114 | $sql = "SELECT jitemid, eventtime, security, allowmask, anum ". |
|---|
| 1115 | "FROM log2 l, syncupdates2 s ". |
|---|
| 1116 | "WHERE s.userid=$ownerid AND l.journalid=$ownerid ". |
|---|
| 1117 | "AND s.atime>='$date' AND s.nodetype='L' AND s.nodeid=l.jitemid ". |
|---|
| 1118 | "AND s.nodeid=l.jitemid ORDER BY s.atime LIMIT $LIMIT"; |
|---|
| 1119 | } else { |
|---|
| 1120 | $use_master = 1; |
|---|
| 1121 | $sql = "SELECT itemid, eventtime, security, allowmask ". |
|---|
| 1122 | "FROM log l, syncupdates s WHERE s.userid=$ownerid ". |
|---|
| 1123 | "AND s.atime>='$date' AND s.nodetype='L' AND s.nodeid=l.itemid ". |
|---|
| 1124 | "AND s.nodeid=l.itemid ORDER BY s.atime LIMIT $LIMIT"; |
|---|
| 1125 | } |
|---|
| 1126 | } |
|---|
| 1127 | else |
|---|
| 1128 | { |
|---|
| 1129 | return fail($err,200,"Invalid selecttype."); |
|---|
| 1130 | } |
|---|
| 1131 | |
|---|
| 1132 | # common SQL template: |
|---|
| 1133 | unless ($sql) { |
|---|
| 1134 | if ($clustered) { |
|---|
| 1135 | $sql = "SELECT jitemid, eventtime, security, allowmask, anum ". |
|---|
| 1136 | "FROM log2 WHERE journalid=$ownerid $where $orderby $limit"; |
|---|
| 1137 | } else { |
|---|
| 1138 | $sql = "SELECT itemid, eventtime, security, allowmask ". |
|---|
| 1139 | "FROM log WHERE ownerid=$ownerid $where $orderby $limit"; |
|---|
| 1140 | } |
|---|
| 1141 | } |
|---|
| 1142 | |
|---|
| 1143 | # whatever selecttype might have wanted us to use the master db. |
|---|
| 1144 | $dbcr = $clustered ? LJ::get_cluster_master($uowner) : $dbh |
|---|
| 1145 | if $use_master; |
|---|
| 1146 | |
|---|
| 1147 | return fail($err,502) unless $dbcr; |
|---|
| 1148 | |
|---|
| 1149 | ## load the log rows |
|---|
| 1150 | ($sth = $dbcr->prepare($sql))->execute; |
|---|
| 1151 | return fail($err,501,$dbcr->errstr) if $dbcr->err; |
|---|
| 1152 | |
|---|
| 1153 | my $count = 0; |
|---|
| 1154 | my @itemids = (); |
|---|
| 1155 | my $res = {}; |
|---|
| 1156 | my $events = $res->{'events'} = []; |
|---|
| 1157 | my %evt_from_itemid; |
|---|
| 1158 | |
|---|
| 1159 | while (my ($itemid, $eventtime, $sec, $mask, $anum) = $sth->fetchrow_array) |
|---|
| 1160 | { |
|---|
| 1161 | $count++; |
|---|
| 1162 | my $evt = {}; |
|---|
| 1163 | $evt->{'itemid'} = $itemid; |
|---|
| 1164 | push @itemids, $itemid; |
|---|
| 1165 | |
|---|
| 1166 | $evt_from_itemid{$itemid} = $evt; |
|---|
| 1167 | |
|---|
| 1168 | $evt->{"eventtime"} = $eventtime; |
|---|
| 1169 | if ($sec ne "public") { |
|---|
| 1170 | $evt->{'security'} = $sec; |
|---|
| 1171 | $evt->{'allowmask'} = $mask if $sec eq "usemask"; |
|---|
| 1172 | } |
|---|
| 1173 | $evt->{'anum'} = $anum if $clustered; |
|---|
| 1174 | push @$events, $evt; |
|---|
| 1175 | } |
|---|
| 1176 | |
|---|
| 1177 | ## load the text |
|---|
| 1178 | my $text; |
|---|
| 1179 | my $gt_opts = { |
|---|
| 1180 | 'prefersubjects' => $req->{'prefersubject'} , |
|---|
| 1181 | 'usemaster' => $use_master, |
|---|
| 1182 | }; |
|---|
| 1183 | if ($clustered) { |
|---|
| 1184 | $text = LJ::get_logtext2($uowner, $gt_opts, @itemids); |
|---|
| 1185 | } else { |
|---|
| 1186 | $text = LJ::get_logtext($dbs, $gt_opts, @itemids); |
|---|
| 1187 | } |
|---|
| 1188 | foreach my $i (@itemids) |
|---|
| 1189 | { |
|---|
| 1190 | my $t = $text->{$i}; |
|---|
| 1191 | my $evt = $evt_from_itemid{$i}; |
|---|
| 1192 | if ($t->[0]) { |
|---|
| 1193 | $t->[0] =~ s/[\r\n]/ /g; |
|---|
| 1194 | $evt->{'subject'} = $t->[0]; |
|---|
| 1195 | } |
|---|
| 1196 | |
|---|
| 1197 | # truncate |
|---|
| 1198 | $t->[1] = substr($t->[1], 0, $req->{'truncate'}-3) . "..." |
|---|
| 1199 | if ($req->{'truncate'} >= 4 && length($t->[1]) > $req->{'truncate'}); |
|---|
| 1200 | |
|---|
| 1201 | # line endings |
|---|
| 1202 | $t->[1] =~ s/\r//g; |
|---|
| 1203 | if ($req->{'lineendings'} eq "unix") { |
|---|
| 1204 | # do nothing. native format. |
|---|
| 1205 | } elsif ($req->{'lineendings'} eq "mac") { |
|---|
| 1206 | $t->[1] =~ s/\n/\r/g; |
|---|
| 1207 | } elsif ($req->{'lineendings'} eq "space") { |
|---|
| 1208 | $t->[1] =~ s/\n/ /g; |
|---|
| 1209 | } elsif ($req->{'lineendings'} eq "dots") { |
|---|
| 1210 | $t->[1] =~ s/\n/ ... /g; |
|---|
| 1211 | } else { # "pc" -- default |
|---|
| 1212 | $t->[1] =~ s/\n/\r\n/g; |
|---|
| 1213 | } |
|---|
| 1214 | $evt->{'event'} = $t->[1]; |
|---|
| 1215 | } |
|---|
| 1216 | |
|---|
| 1217 | unless ($req->{'noprops'}) |
|---|
| 1218 | { |
|---|
| 1219 | ### do the properties now |
|---|
| 1220 | $count = 0; |
|---|
| 1221 | my %props = (); |
|---|
| 1222 | if ($clustered) { |
|---|
| 1223 | LJ::load_props($dbs, "log"); |
|---|
| 1224 | LJ::load_log_props2($dbcr, $ownerid, \@itemids, \%props); |
|---|
| 1225 | } else { |
|---|
| 1226 | LJ::load_log_props($dbcr, \@itemids, \%props); |
|---|
| 1227 | } |
|---|
| 1228 | foreach my $itemid (keys %props) { |
|---|
| 1229 | my $evt = $evt_from_itemid{$itemid}; |
|---|
| 1230 | $evt->{'props'} = {}; |
|---|
| 1231 | foreach my $name (keys %{$props{$itemid}}) { |
|---|
| 1232 | my $value = $props{$itemid}->{$name}; |
|---|
| 1233 | $value =~ s/\n/ /g; |
|---|
| 1234 | $evt->{'props'}->{$name} = $value; |
|---|
| 1235 | } |
|---|
| 1236 | } |
|---|
| 1237 | } |
|---|
| 1238 | |
|---|
| 1239 | return $res; |
|---|
| 1240 | } |
|---|
| 1241 | |
|---|
| 1242 | sub editfriends |
|---|
| 1243 | { |
|---|
| 1244 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 1245 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 1246 | |
|---|
| 1247 | my $u = $flags->{'u'}; |
|---|
| 1248 | my $userid = $u->{'userid'}; |
|---|
| 1249 | my $dbr = $dbs->{'reader'}; |
|---|
| 1250 | my $dbh = $dbs->{'dbh'}; |
|---|
| 1251 | my $sth; |
|---|
| 1252 | |
|---|
| 1253 | return fail($err,306) unless $dbh; |
|---|
| 1254 | |
|---|
| 1255 | my $res = {}; |
|---|
| 1256 | |
|---|
| 1257 | ## first, figure out who the current friends are to save us work later |
|---|
| 1258 | my %curfriend; |
|---|
| 1259 | my $friend_count = 0; |
|---|
| 1260 | $sth = $dbh->prepare("SELECT u.user FROM user u, friends f ". |
|---|
| 1261 | "WHERE u.userid=f.friendid AND f.userid=$userid ". |
|---|
| 1262 | "AND u.statusvis='V'"); |
|---|
| 1263 | $sth->execute; |
|---|
| 1264 | while (my ($friend) = $sth->fetchrow_array) { |
|---|
| 1265 | $curfriend{$friend} = 1; |
|---|
| 1266 | $friend_count++; |
|---|
| 1267 | } |
|---|
| 1268 | $sth->finish; |
|---|
| 1269 | |
|---|
| 1270 | # perform the deletions |
|---|
| 1271 | DELETEFRIEND: |
|---|
| 1272 | foreach (@{$req->{'delete'}}) |
|---|
| 1273 | { |
|---|
| 1274 | my $deluser = LJ::canonical_username($_); |
|---|
| 1275 | next DELETEFRIEND unless ($curfriend{$deluser}); |
|---|
| 1276 | |
|---|
| 1277 | my $friendid = LJ::get_userid($dbh, $deluser); |
|---|
| 1278 | $sth = $dbh->prepare("DELETE FROM friends ". |
|---|
| 1279 | "WHERE userid=$userid AND friendid=$friendid"); |
|---|
| 1280 | $sth->execute; |
|---|
| 1281 | $friend_count--; |
|---|
| 1282 | } |
|---|
| 1283 | |
|---|
| 1284 | my $error_flag = 0; |
|---|
| 1285 | my $friends_added = 0; |
|---|
| 1286 | |
|---|
| 1287 | # perform the adds |
|---|
| 1288 | ADDFRIEND: |
|---|
| 1289 | foreach my $fa (@{$req->{'add'}}) |
|---|
| 1290 | { |
|---|
| 1291 | unless (ref $fa eq "HASH") { |
|---|
| 1292 | $fa = { 'username' => $fa }; |
|---|
| 1293 | } |
|---|
| 1294 | |
|---|
| 1295 | my $aname = LJ::canonical_username($fa->{'username'}); |
|---|
| 1296 | unless ($aname) { |
|---|
| 1297 | $error_flag = 1; |
|---|
| 1298 | next ADDFRIEND; |
|---|
| 1299 | } |
|---|
| 1300 | |
|---|
| 1301 | $friend_count++ unless $curfriend{$aname}; |
|---|
| 1302 | |
|---|
| 1303 | my $maxfriends = LJ::get_cap($u, "maxfriends"); |
|---|
| 1304 | return fail($err,104,"Exceeded $maxfriends friends limit (now: $friend_count)") |
|---|
| 1305 | if ($friend_count > $maxfriends); |
|---|
| 1306 | |
|---|
| 1307 | my $fg = $fa->{'fgcolor'} || "#000000"; |
|---|
| 1308 | my $bg = $fa->{'bgcolor'} || "#FFFFFF"; |
|---|
| 1309 | if ($fg !~ /^\#[0-9A-F]{6,6}$/i || $bg !~ /^\#[0-9A-F]{6,6}$/i) { |
|---|
| 1310 | return fail($err,203,"Invalid color values"); |
|---|
| 1311 | } |
|---|
| 1312 | |
|---|
| 1313 | my $row = LJ::load_user($dbs, $aname); |
|---|
| 1314 | unless ($row && $row->{'statusvis'} eq "V") { |
|---|
| 1315 | $error_flag = 1; |
|---|
| 1316 | } else { |
|---|
| 1317 | $friends_added++; |
|---|
| 1318 | my $added = { 'username' => $aname, |
|---|
| 1319 | 'fullname' => $row->{'name'}, |
|---|
| 1320 | }; |
|---|
| 1321 | push @{$res->{'added'}}, $added; |
|---|
| 1322 | |
|---|
| 1323 | my $qfg = LJ::color_todb($fg); |
|---|
| 1324 | my $qbg = LJ::color_todb($bg); |
|---|
| 1325 | |
|---|
| 1326 | my $friendid = $row->{'userid'}; |
|---|
| 1327 | |
|---|
| 1328 | my $gmask = $fa->{'groupmask'}; |
|---|
| 1329 | if (! $gmask && $curfriend{$aname}) { |
|---|
| 1330 | # if no group mask sent, use the existing one if this is an existing friend |
|---|
| 1331 | my $sth = $dbh->prepare("SELECT groupmask FROM friends ". |
|---|
| 1332 | "WHERE userid=$userid AND friendid=$friendid"); |
|---|
| 1333 | $sth->execute; |
|---|
| 1334 | $gmask = $sth->fetchrow_array; |
|---|
| 1335 | } |
|---|
| 1336 | # force bit 0 on. |
|---|
| 1337 | $gmask |= 1; |
|---|
| 1338 | |
|---|
| 1339 | $sth = $dbh->prepare("REPLACE INTO friends (userid, friendid, fgcolor, bgcolor, groupmask) ". |
|---|
| 1340 | "VALUES ($userid, $friendid, $qfg, $qbg, $gmask)"); |
|---|
| 1341 | $sth->execute; |
|---|
| 1342 | return fail($err,501,$dbh->errstr) if $dbh->err; |
|---|
| 1343 | |
|---|
| 1344 | } |
|---|
| 1345 | } |
|---|
| 1346 | |
|---|
| 1347 | return fail($err,104) if $error_flag; |
|---|
| 1348 | return $res; |
|---|
| 1349 | } |
|---|
| 1350 | |
|---|
| 1351 | sub editfriendgroups |
|---|
| 1352 | { |
|---|
| 1353 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 1354 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 1355 | |
|---|
| 1356 | my $u = $flags->{'u'}; |
|---|
| 1357 | my $userid = $u->{'userid'}; |
|---|
| 1358 | my $dbr = $dbs->{'reader'}; |
|---|
| 1359 | my $dbh = $dbs->{'dbh'}; |
|---|
| 1360 | my $sth; |
|---|
| 1361 | |
|---|
| 1362 | return fail($err,306) unless $dbh; |
|---|
| 1363 | return fail($err,502) unless $dbr; |
|---|
| 1364 | |
|---|
| 1365 | my $res = {}; |
|---|
| 1366 | |
|---|
| 1367 | ## make sure tree is how we want it |
|---|
| 1368 | $req->{'groupmasks'} = {} unless |
|---|
| 1369 | (ref $req->{'groupmasks'} eq "HASH"); |
|---|
| 1370 | $req->{'set'} = {} unless |
|---|
| 1371 | (ref $req->{'set'} eq "HASH"); |
|---|
| 1372 | $req->{'delete'} = [] unless |
|---|
| 1373 | (ref $req->{'delete'} eq "ARRAY"); |
|---|
| 1374 | |
|---|
| 1375 | ### |
|---|
| 1376 | ## Keep track of what bits are already set, so we can know later whether to INSERT |
|---|
| 1377 | # or UPDATE. |
|---|
| 1378 | |
|---|
| 1379 | my %bitset; |
|---|
| 1380 | $sth = $dbr->prepare("SELECT groupnum FROM friendgroup WHERE userid=$userid"); |
|---|
| 1381 | $sth->execute; |
|---|
| 1382 | while (my ($bit) = $sth->fetchrow_array) { |
|---|
| 1383 | $bitset{$bit} = 1; |
|---|
| 1384 | } |
|---|
| 1385 | |
|---|
| 1386 | ## figure out deletions we'll do later |
|---|
| 1387 | foreach my $bit (@{$req->{'delete'}}) |
|---|
| 1388 | { |
|---|
| 1389 | $bit += 0; |
|---|
| 1390 | next unless ($bit >= 1 && $bit <= 30); |
|---|
| 1391 | $bitset{$bit} = 0; # so later we replace into, not update. |
|---|
| 1392 | } |
|---|
| 1393 | |
|---|
| 1394 | ## change friends' masks |
|---|
| 1395 | foreach my $friend (keys %{$req->{'groupmasks'}}) |
|---|
| 1396 | { |
|---|
| 1397 | my $mask = int($req->{'groupmasks'}->{$friend}) | 1; |
|---|
| 1398 | |
|---|
| 1399 | my $friendid = LJ::get_userid($dbs, $friend); |
|---|
| 1400 | if ($friendid) { |
|---|
| 1401 | $sth = $dbh->prepare("UPDATE friends SET groupmask=$mask ". |
|---|
| 1402 | "WHERE userid=$userid AND friendid=$friendid"); |
|---|
| 1403 | $sth->execute; |
|---|
| 1404 | } |
|---|
| 1405 | } |
|---|
| 1406 | |
|---|
| 1407 | ## do additions/modifications ('set' hash) |
|---|
| 1408 | my %added; |
|---|
| 1409 | foreach my $bit (keys %{$req->{'set'}}) |
|---|
| 1410 | { |
|---|
| 1411 | $bit += 0; |
|---|
| 1412 | next unless ($bit >= 1 && $bit <= 30); |
|---|
| 1413 | my $sa = $req->{'set'}->{$bit}; |
|---|
| 1414 | my $name = $sa->{'name'}; |
|---|
| 1415 | |
|---|
| 1416 | # setting it to name is like deleting it. |
|---|
| 1417 | unless ($name =~ /\S/) { |
|---|
| 1418 | push @{$req->{'delete'}}, $bit; |
|---|
| 1419 | next; |
|---|
| 1420 | } |
|---|
| 1421 | |
|---|
| 1422 | my $qname = $dbh->quote($name); |
|---|
| 1423 | my $qsort = defined $sa->{'sort'} ? ($sa->{'sort'}+0) : 50; |
|---|
| 1424 | my $qpublic = $dbh->quote(defined $sa->{'public'} ? ($sa->{'public'}+0) : 0); |
|---|
| 1425 | |
|---|
| 1426 | if ($bitset{$bit}) { |
|---|
| 1427 | # so update it |
|---|
| 1428 | my $sets; |
|---|
| 1429 | if (defined $sa->{'public'}) { |
|---|
| 1430 | $sets .= ", is_public=$qpublic"; |
|---|
| 1431 | } |
|---|
| 1432 | $sth = $dbh->prepare("UPDATE friendgroup SET groupname=$qname, sortorder=$qsort ". |
|---|
| 1433 | "$sets WHERE userid=$userid AND groupnum=$bit"); |
|---|
| 1434 | } else { |
|---|
| 1435 | $sth = $dbh->prepare("REPLACE INTO friendgroup (userid, groupnum, ". |
|---|
| 1436 | "groupname, sortorder, is_public) VALUES ". |
|---|
| 1437 | "($userid, $bit, $qname, $qsort, $qpublic)"); |
|---|
| 1438 | } |
|---|
| 1439 | $sth->execute; |
|---|
| 1440 | $added{$bit} = 1; |
|---|
| 1441 | } |
|---|
| 1442 | |
|---|
| 1443 | |
|---|
| 1444 | ## do deletions ('delete' array) |
|---|
| 1445 | foreach my $bit (@{$req->{'delete'}}) |
|---|
| 1446 | { |
|---|
| 1447 | $bit += 0; |
|---|
| 1448 | next unless ($bit >= 1 && $bit <= 30); |
|---|
| 1449 | |
|---|
| 1450 | # Old note: remove all friend's priviledges on that bit |
|---|
| 1451 | # number? No, client should do this. |
|---|
| 1452 | |
|---|
| 1453 | # remove all posts from allowing that group: |
|---|
| 1454 | my @posts_to_clean = (); |
|---|
| 1455 | $sth = $dbr->prepare("SELECT itemid FROM logsec WHERE ownerid=$userid AND allowmask & (1 << $bit)"); |
|---|
| 1456 | $sth->execute; |
|---|
| 1457 | while (my ($id) = $sth->fetchrow_array) { push @posts_to_clean, $id; } |
|---|
| 1458 | while (@posts_to_clean) { |
|---|
| 1459 | my @batch; |
|---|
| 1460 | if (scalar(@posts_to_clean) < 20) { |
|---|
| 1461 | @batch = @posts_to_clean; |
|---|
| 1462 | @posts_to_clean = (); |
|---|
| 1463 | } else { |
|---|
| 1464 | @batch = splice(@posts_to_clean, 0, 20); |
|---|
| 1465 | } |
|---|
| 1466 | my $in = join(",", @batch); |
|---|
| 1467 | $dbh->do("UPDATE log SET allowmask=allowmask & ~(1 << $bit) ". |
|---|
| 1468 | "WHERE itemid IN ($in) AND security='usemask'"); |
|---|
| 1469 | $dbh->do("UPDATE logsec SET allowmask=allowmask & ~(1 << $bit) ". |
|---|
| 1470 | "WHERE ownerid=$userid AND itemid IN ($in)"); |
|---|
| 1471 | } |
|---|
| 1472 | |
|---|
| 1473 | # remove the friend group, unless we just added it this transaction |
|---|
| 1474 | unless ($added{$bit}) { |
|---|
| 1475 | $sth = $dbh->prepare("DELETE FROM friendgroup WHERE ". |
|---|
| 1476 | "userid=$userid AND groupnum=$bit"); |
|---|
| 1477 | $sth->execute; |
|---|
| 1478 | } |
|---|
| 1479 | } |
|---|
| 1480 | |
|---|
| 1481 | # return value for this is nothing. |
|---|
| 1482 | return {}; |
|---|
| 1483 | } |
|---|
| 1484 | |
|---|
| 1485 | sub list_friends |
|---|
| 1486 | { |
|---|
| 1487 | my ($dbs, $u, $opts) = @_; |
|---|
| 1488 | my $dbr = $dbs->{'reader'}; |
|---|
| 1489 | |
|---|
| 1490 | my $limitnum = $opts->{'limit'}+0; |
|---|
| 1491 | my $where = "u.userid=f.friendid AND f.userid=$u->{'userid'}"; |
|---|
| 1492 | if ($opts->{'friendof'}) { |
|---|
| 1493 | $where = "u.userid=f.userid AND f.friendid=$u->{'userid'}"; |
|---|
| 1494 | } |
|---|
| 1495 | |
|---|
| 1496 | my $limit = $limitnum ? "LIMIT $limitnum" : ""; |
|---|
| 1497 | my $sth = $dbr->prepare("SELECT u.user AS 'friend', u.name, u.journaltype, f.fgcolor, f.bgcolor, f.groupmask ". |
|---|
| 1498 | "FROM user u, friends f WHERE $where AND u.statusvis='V' ORDER BY u.user $limit"); |
|---|
| 1499 | $sth->execute; |
|---|
| 1500 | my @friends; |
|---|
| 1501 | push @friends, $_ while $_ = $sth->fetchrow_hashref; |
|---|
| 1502 | $sth->finish; |
|---|
| 1503 | |
|---|
| 1504 | my $res = []; |
|---|
| 1505 | foreach my $f (@friends) |
|---|
| 1506 | { |
|---|
| 1507 | my $r = { 'username' => $f->{'friend'}, |
|---|
| 1508 | 'fullname' => $f->{'name'}, |
|---|
| 1509 | }; |
|---|
| 1510 | $r->{'fgcolor'} = LJ::color_fromdb($f->{'fgcolor'}); |
|---|
| 1511 | $r->{'bgcolor'} = LJ::color_fromdb($f->{'bgcolor'}); |
|---|
| 1512 | if (! $opts->{'friendof'} && $f->{'groupmask'} != 1) { |
|---|
| 1513 | $r->{"groupmask"} = $f->{'groupmask'}; |
|---|
| 1514 | } |
|---|
| 1515 | if ($f->{'journaltype'} eq "C") { |
|---|
| 1516 | $r->{"type"} = "community"; |
|---|
| 1517 | } |
|---|
| 1518 | |
|---|
| 1519 | push @$res, $r; |
|---|
| 1520 | } |
|---|
| 1521 | return $res; |
|---|
| 1522 | } |
|---|
| 1523 | |
|---|
| 1524 | sub syncitems |
|---|
| 1525 | { |
|---|
| 1526 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 1527 | return undef unless authenticate($dbs, $req, $err, $flags); |
|---|
| 1528 | return undef unless check_altusage($dbs, $req, $err, $flags); |
|---|
| 1529 | |
|---|
| 1530 | my $ownerid = $flags->{'ownerid'}; |
|---|
| 1531 | my $uowner = $flags->{'u_owner'} || $flags->{'u'}; |
|---|
| 1532 | my $dbr = $dbs->{'reader'}; |
|---|
| 1533 | my ($date, $sth); |
|---|
| 1534 | |
|---|
| 1535 | # cluster differences |
|---|
| 1536 | my ($db, $table) = ($dbs->{'dbh'}, "syncupdates"); |
|---|
| 1537 | ($db, $table) = (LJ::get_cluster_reader($uowner), "syncupdates2") |
|---|
| 1538 | if $uowner->{'clusterid'}; |
|---|
| 1539 | |
|---|
| 1540 | return fail($err,502) unless $db; |
|---|
| 1541 | |
|---|
| 1542 | ## have a valid date? |
|---|
| 1543 | $date = $req->{'lastsync'}; |
|---|
| 1544 | if ($date) { |
|---|
| 1545 | return fail($err,203,"Invalid date format") |
|---|
| 1546 | unless ($date =~ /^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/); |
|---|
| 1547 | } else { |
|---|
| 1548 | $date = "0000-00-00 00:00:00"; |
|---|
| 1549 | } |
|---|
| 1550 | |
|---|
| 1551 | my $LIMIT = 500; |
|---|
| 1552 | |
|---|
| 1553 | my $total = $db->selectrow_array("SELECT COUNT(*) FROM $table WHERE ". |
|---|
| 1554 | "userid=$ownerid AND atime >= '$date'"); |
|---|
| 1555 | |
|---|
| 1556 | $sth = $db->prepare("SELECT atime, nodetype, nodeid, atype FROM ". |
|---|
| 1557 | "$table WHERE userid=$ownerid AND ". |
|---|
| 1558 | "atime >= '$date' ORDER BY atime LIMIT $LIMIT"); |
|---|
| 1559 | $sth->execute; |
|---|
| 1560 | return fail($err,501,$db->errstr) if $db->err; |
|---|
| 1561 | |
|---|
| 1562 | my $res = {}; |
|---|
| 1563 | my $list = $res->{'syncitems'} = []; |
|---|
| 1564 | my $ct = 0; |
|---|
| 1565 | while (my ($atime, $nodetype, $nodeid, $atype) = $sth->fetchrow_array) { |
|---|
| 1566 | $ct++; |
|---|
| 1567 | push @$list, { 'item' => "$nodetype-$nodeid", |
|---|
| 1568 | 'action' => $atype, |
|---|
| 1569 | 'time' => $atime, |
|---|
| 1570 | }; |
|---|
| 1571 | } |
|---|
| 1572 | $res->{'count'} = $ct; |
|---|
| 1573 | $res->{'total'} = $total; |
|---|
| 1574 | return $res; |
|---|
| 1575 | } |
|---|
| 1576 | |
|---|
| 1577 | sub consolecommand |
|---|
| 1578 | { |
|---|
| 1579 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 1580 | |
|---|
| 1581 | # TODO: LJ::Con doesn't yet support $dbs/$dbarg |
|---|
| 1582 | my $dbh = $dbs->{'dbh'}; |
|---|
| 1583 | return fail($err,502) unless $dbh; |
|---|
| 1584 | |
|---|
| 1585 | # logging in isn't necessary, but most console commands do require it |
|---|
| 1586 | my $remote = undef; |
|---|
| 1587 | $remote = $flags->{'u'} if authenticate($dbs, $req, $err, $flags); |
|---|
| 1588 | |
|---|
| 1589 | my $res = {}; |
|---|
| 1590 | my $cmdout = $res->{'results'} = []; |
|---|
| 1591 | |
|---|
| 1592 | foreach my $cmd (@{$req->{'commands'}}) |
|---|
| 1593 | { |
|---|
| 1594 | # callee can pre-parse the args, or we can do it bash-style |
|---|
| 1595 | $cmd = [ LJ::Con::parse_line($cmd) ] unless (ref $cmd eq "ARRAY"); |
|---|
| 1596 | |
|---|
| 1597 | my @output; |
|---|
| 1598 | my $rv = LJ::Con::execute($dbh, $remote, $cmd, \@output); |
|---|
| 1599 | push @{$cmdout}, { |
|---|
| 1600 | 'success' => $rv, |
|---|
| 1601 | 'output' => \@output, |
|---|
| 1602 | }; |
|---|
| 1603 | } |
|---|
| 1604 | |
|---|
| 1605 | return $res; |
|---|
| 1606 | } |
|---|
| 1607 | |
|---|
| 1608 | sub login_message |
|---|
| 1609 | { |
|---|
| 1610 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 1611 | |
|---|
| 1612 | my $u = $flags->{'u'}; |
|---|
| 1613 | |
|---|
| 1614 | if ($u eq "test") { |
|---|
| 1615 | $res->{'message'} = "Hello Test Account!"; |
|---|
| 1616 | } |
|---|
| 1617 | if ($req->{'clientversion'} =~ /^Win32-MFC\/(1.2.[0123456])$/ || |
|---|
| 1618 | $req->{'clientversion'} =~ /^Win32-MFC\/(1.3.[01234])\b/) |
|---|
| 1619 | { |
|---|
| 1620 | $res->{'message'} = "There's a significantly newer version of LiveJournal for Windows available."; |
|---|
| 1621 | } |
|---|
| 1622 | unless ($LJ::EVERYONE_VALID) |
|---|
| 1623 | { |
|---|
| 1624 | if ($u->{'status'} eq "N") { $res->{'message'} = "You are currently not validated. You may continue to use LiveJournal, but please validate your email address for continued use. See the instructions that were mailed to you when you created your journal, or see $LJ::SITEROOT/support/ for more information."; } |
|---|
| 1625 | if ($u->{'status'} eq "T") { $res->{'message'} = "You need to validate your new email address. Your old one was good, but since you've changed it, you need to re-validate the new one. Visit the support area for more information."; } |
|---|
| 1626 | } |
|---|
| 1627 | if ($u->{'status'} eq "B") { $res->{'message'} = "You are currently using a bad email address. All mail we try to send you is bouncing. We require a valid email address for continued use. Visit the support area for more information."; } |
|---|
| 1628 | |
|---|
| 1629 | if (LJ::get_cap($u, "readonly")) { |
|---|
| 1630 | $res->{'message'} = "Your account is temporarily in read-only mode. Some operations will fail for a few minutes."; |
|---|
| 1631 | } |
|---|
| 1632 | |
|---|
| 1633 | } |
|---|
| 1634 | |
|---|
| 1635 | sub list_friendgroups |
|---|
| 1636 | { |
|---|
| 1637 | my $dbs = shift; |
|---|
| 1638 | my $u = shift; |
|---|
| 1639 | |
|---|
| 1640 | my $res = []; |
|---|
| 1641 | my $dbr = $dbs->{'reader'}; |
|---|
| 1642 | |
|---|
| 1643 | my $sth = $dbr->prepare("SELECT groupnum, groupname, sortorder, is_public ". |
|---|
| 1644 | "FROM friendgroup WHERE userid=$u->{'userid'} ". |
|---|
| 1645 | "ORDER BY sortorder"); |
|---|
| 1646 | $sth->execute; |
|---|
| 1647 | while (my ($gid, $name, $sort, $public) = $sth->fetchrow_array) { |
|---|
| 1648 | push @$res, { 'id' => $gid, |
|---|
| 1649 | 'name' => $name, |
|---|
| 1650 | 'sortorder' => $sort, |
|---|
| 1651 | 'public' => $public }; |
|---|
| 1652 | } |
|---|
| 1653 | $sth->finish; |
|---|
| 1654 | return $res; |
|---|
| 1655 | } |
|---|
| 1656 | |
|---|
| 1657 | sub list_usejournals |
|---|
| 1658 | { |
|---|
| 1659 | my $dbs = shift; |
|---|
| 1660 | my $u = shift; |
|---|
| 1661 | |
|---|
| 1662 | my $res = []; |
|---|
| 1663 | |
|---|
| 1664 | my $dbr = $dbs->{'reader'}; |
|---|
| 1665 | my $sth = $dbr->prepare("SELECT u.user FROM useridmap u, logaccess la ". |
|---|
| 1666 | "WHERE la.ownerid=u.userid AND ". |
|---|
| 1667 | "la.posterid=$u->{'userid'} ORDER BY u.user"); |
|---|
| 1668 | $sth->execute; |
|---|
| 1669 | while (my $u = $sth->fetchrow_array) { |
|---|
| 1670 | push @$res, $u; |
|---|
| 1671 | } |
|---|
| 1672 | $sth->finish; |
|---|
| 1673 | return $res; |
|---|
| 1674 | } |
|---|
| 1675 | |
|---|
| 1676 | sub hash_menus |
|---|
| 1677 | { |
|---|
| 1678 | my $dbs = shift; |
|---|
| 1679 | my $u = shift; |
|---|
| 1680 | my $user = $u->{'user'}; |
|---|
| 1681 | |
|---|
| 1682 | my $menu = [ |
|---|
| 1683 | { 'text' => "Recent Entries", |
|---|
| 1684 | 'url' => "$LJ::SITEROOT/users/$user/", }, |
|---|
| 1685 | { 'text' => "Calendar View", |
|---|
| 1686 | 'url' => "$LJ::SITEROOT/users/$user/calendar", }, |
|---|
| 1687 | { 'text' => "Friends View", |
|---|
| 1688 | 'url' => "$LJ::SITEROOT/users/$user/friends", }, |
|---|
| 1689 | { 'text' => "-", }, |
|---|
| 1690 | { 'text' => "Your Profile", |
|---|
| 1691 | 'url' => "$LJ::SITEROOT/userinfo.bml?user=$user", }, |
|---|
| 1692 | { 'text' => "Your To-Do List", |
|---|
| 1693 | 'url' => "$LJ::SITEROOT/todo/?user=$user", }, |
|---|
| 1694 | { 'text' => "-", }, |
|---|
| 1695 | { 'text' => "Change Settings", |
|---|
| 1696 | 'sub' => [ { 'text' => "Personal Info", |
|---|
| 1697 | 'url' => "$LJ::SITEROOT/editinfo.bml", }, |
|---|
| 1698 | { 'text' => "Journal Settings", |
|---|
| 1699 | 'url' =>"$LJ::SITEROOT/modify.bml", }, ] }, |
|---|
| 1700 | { 'text' => "-", }, |
|---|
| 1701 | { 'text' => "Support", |
|---|
| 1702 | 'url' => "$LJ::SITEROOT/support/", } |
|---|
| 1703 | ]; |
|---|
| 1704 | |
|---|
| 1705 | LJ::run_hooks("modify_login_menu", { |
|---|
| 1706 | 'dbs' => $dbs, |
|---|
| 1707 | 'menu' => $menu, |
|---|
| 1708 | 'u' => $u, |
|---|
| 1709 | 'user' => $user, |
|---|
| 1710 | }); |
|---|
| 1711 | |
|---|
| 1712 | return $menu; |
|---|
| 1713 | } |
|---|
| 1714 | |
|---|
| 1715 | sub list_pickws |
|---|
| 1716 | { |
|---|
| 1717 | my $dbs = shift; |
|---|
| 1718 | my $u = shift; |
|---|
| 1719 | |
|---|
| 1720 | my $dbr = $dbs->{'reader'}; |
|---|
| 1721 | my $res = []; |
|---|
| 1722 | |
|---|
| 1723 | my $sth = $dbr->prepare("SELECT k.keyword, m.picid FROM userpicmap m, keywords k ". |
|---|
| 1724 | "WHERE m.userid=$u->{'userid'} AND m.kwid=k.kwid ". |
|---|
| 1725 | "ORDER BY k.keyword"); |
|---|
| 1726 | $sth->execute; |
|---|
| 1727 | while (my ($kw, $id) = $sth->fetchrow_array) { |
|---|
| 1728 | $kw =~ s/[\n\r\0]//g; # used to be a bug that allowed these characters to get in. |
|---|
| 1729 | push @$res, [ $kw, $id ]; |
|---|
| 1730 | } |
|---|
| 1731 | return $res; |
|---|
| 1732 | } |
|---|
| 1733 | |
|---|
| 1734 | sub list_moods |
|---|
| 1735 | { |
|---|
| 1736 | my $dbs = shift; |
|---|
| 1737 | my $mood_max = int(shift); |
|---|
| 1738 | |
|---|
| 1739 | LJ::load_moods($dbs); |
|---|
| 1740 | |
|---|
| 1741 | my $res = []; |
|---|
| 1742 | return $res unless ($mood_max < $LJ::CACHED_MOOD_MAX); |
|---|
| 1743 | |
|---|
| 1744 | for (my $id = $mood_max+1; $id <= $LJ::CACHED_MOOD_MAX; $id++) { |
|---|
| 1745 | next unless defined $LJ::CACHE_MOODS{$id}; |
|---|
| 1746 | my $mood = $LJ::CACHE_MOODS{$id}; |
|---|
| 1747 | push @$res, { 'id' => $id, |
|---|
| 1748 | 'name' => $mood->{'name'}, |
|---|
| 1749 | 'parent' => $mood->{'parent'} }; |
|---|
| 1750 | } |
|---|
| 1751 | |
|---|
| 1752 | return $res; |
|---|
| 1753 | } |
|---|
| 1754 | |
|---|
| 1755 | sub check_altusage |
|---|
| 1756 | { |
|---|
| 1757 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 1758 | |
|---|
| 1759 | # see note in ljlib.pl::can_use_journal about why we return |
|---|
| 1760 | # both 'ownerid' and 'u_owner' in $flags |
|---|
| 1761 | |
|---|
| 1762 | my $alt = $req->{'usejournal'}; |
|---|
| 1763 | my $u = $flags->{'u'}; |
|---|
| 1764 | $flags->{'ownerid'} = $u->{'userid'}; |
|---|
| 1765 | |
|---|
| 1766 | # all good if not using an alt journal |
|---|
| 1767 | return 1 unless $alt; |
|---|
| 1768 | |
|---|
| 1769 | # complain if the username is invalid |
|---|
| 1770 | return fail($err,206) unless LJ::canonical_username($alt); |
|---|
| 1771 | |
|---|
| 1772 | # allow usage if we're told explicitly that it's okay |
|---|
| 1773 | if ($flags->{'usejournal_okay'}) { |
|---|
| 1774 | $flags->{'u_owner'} = LJ::load_user($dbs, $alt); |
|---|
| 1775 | $flags->{'ownerid'} = $flags->{'u_owner'}->{'userid'}; |
|---|
| 1776 | return 1 if $flags->{'ownerid'}; |
|---|
| 1777 | return fail($err,206); |
|---|
| 1778 | } |
|---|
| 1779 | |
|---|
| 1780 | # otherwise, check logaccess table: |
|---|
| 1781 | my $info = {}; |
|---|
| 1782 | if (LJ::can_use_journal($dbs, $u->{'userid'}, $req->{'usejournal'}, $info)) { |
|---|
| 1783 | $flags->{'ownerid'} = $info->{'ownerid'}; |
|---|
| 1784 | $flags->{'u_owner'} = $info->{'u_owner'}; |
|---|
| 1785 | return 1; |
|---|
| 1786 | } |
|---|
| 1787 | |
|---|
| 1788 | # not allowed to access it |
|---|
| 1789 | return fail($err,300); |
|---|
| 1790 | } |
|---|
| 1791 | |
|---|
| 1792 | sub authenticate |
|---|
| 1793 | { |
|---|
| 1794 | my ($dbs, $req, $err, $flags) = @_; |
|---|
| 1795 | |
|---|
| 1796 | my $username = $req->{'username'}; |
|---|
| 1797 | return fail($err,200) unless $username; |
|---|
| 1798 | return fail($err,100) unless LJ::canonical_username($username); |
|---|
| 1799 | |
|---|
| 1800 | my $u = $flags->{'u'}; |
|---|
| 1801 | unless ($u) { |
|---|
| 1802 | my $dbr = $dbs->{'reader'}; |
|---|
| 1803 | return fail($err,502) unless $dbr; |
|---|
| 1804 | my $quser = $dbr->quote($username); |
|---|
| 1805 | my $sth = $dbr->prepare("SELECT user, userid, journaltype, name, ". |
|---|
| 1806 | "password, status, statusvis, caps, ". |
|---|
| 1807 | "clusterid, dversion, ". |
|---|
| 1808 | "track FROM user WHERE user=$quser"); |
|---|
| 1809 | $sth->execute; |
|---|
| 1810 | $u = $sth->fetchrow_hashref; |
|---|
| 1811 | } |
|---|
| 1812 | |
|---|
| 1813 | return fail($err,100) unless $u; |
|---|
| 1814 | return fail($err,100) if ($u->{'statusvis'} eq "X"); |
|---|
| 1815 | return fail($err,101) unless ($flags->{'nopassword'} || |
|---|
| 1816 | $flags->{'noauth'} || |
|---|
| 1817 | LJ::auth_okay($username, |
|---|
| 1818 | $req->{'password'}, |
|---|
| 1819 | $req->{'hpassword'}, |
|---|
| 1820 | $u->{'password'})); |
|---|
| 1821 | # remember the user record for later. |
|---|
| 1822 | $flags->{'u'} = $u; |
|---|
| 1823 | return 1; |
|---|
| 1824 | } |
|---|
| 1825 | |
|---|
| 1826 | sub fail |
|---|
| 1827 | { |
|---|
| 1828 | my $err = shift; |
|---|
| 1829 | my $code = shift; |
|---|
| 1830 | my $des = shift; |
|---|
| 1831 | $code .= ":$des" if $des; |
|---|
| 1832 | $$err = $code if (ref $err eq "SCALAR"); |
|---|
| 1833 | return undef; |
|---|
| 1834 | } |
|---|
| 1835 | |
|---|
| 1836 | #### Old interface (flat key/values) -- wrapper aruond LJ::Protocol |
|---|
| 1837 | package LJ; |
|---|
| 1838 | |
|---|
| 1839 | sub do_request |
|---|
| 1840 | { |
|---|
| 1841 | # get the request and response hash refs |
|---|
| 1842 | my ($db_arg, $req, $res, $flags) = @_; |
|---|
| 1843 | |
|---|
| 1844 | # initialize some stuff |
|---|
| 1845 | my $dbs = LJ::make_dbs_from_arg($db_arg); |
|---|
| 1846 | my $dbh = $dbs->{'dbh'}; |
|---|
| 1847 | my $dbr = $dbs->{'reader'}; |
|---|
| 1848 | %{$res} = (); # clear the given response hash |
|---|
| 1849 | $flags = {} unless (ref $flags eq "HASH"); |
|---|
| 1850 | |
|---|
| 1851 | my ($user, $userid, $journaltype, $name, $correctpassword, $status, $statusvis, $track, $sth); |
|---|
| 1852 | $user = LJ::canonical_username($req->{'user'}); |
|---|
| 1853 | my $quser = $dbh->quote($user); |
|---|
| 1854 | |
|---|
| 1855 | # check for an alive database connection |
|---|
| 1856 | unless ($dbh) { |
|---|
| 1857 | $res->{'success'} = "FAIL"; |
|---|
| 1858 | $res->{'errmsg'} = "Server error: cannot connect to database."; |
|---|
| 1859 | return; |
|---|
| 1860 | } |
|---|
| 1861 | |
|---|
| 1862 | # did they send a mode? |
|---|
| 1863 | unless ($req->{'mode'}) { |
|---|
| 1864 | $res->{'success'} = "FAIL"; |
|---|
| 1865 | $res->{'errmsg'} = "Client error: No mode specified."; |
|---|
| 1866 | return; |
|---|
| 1867 | } |
|---|
| 1868 | |
|---|
| 1869 | unless ($user) { |
|---|
| 1870 | $res->{'success'} = "FAIL"; |
|---|
| 1871 | $res->{'errmsg'} = "Client error: No username sent."; |
|---|
| 1872 | return; |
|---|
| 1873 | } |
|---|
| 1874 | |
|---|
| 1875 | ### see if the server's under maintenance now |
|---|
| 1876 | if ($LJ::SERVER_DOWN) { |
|---|
| 1877 | $res->{'success'} = "FAIL"; |
|---|
| 1878 | $res->{'errmsg'} = $LJ::SERVER_DOWN_MESSAGE; |
|---|
| 1879 | return; |
|---|
| 1880 | } |
|---|
| 1881 | |
|---|
| 1882 | ## dispatch wrappers |
|---|
| 1883 | if ($req->{'mode'} eq "login") { |
|---|
| 1884 | return login($dbs, $req, $res, $flags); |
|---|
| 1885 | } |
|---|
| 1886 | if ($req->{'mode'} eq "getfriendgroups") { |
|---|
| 1887 | return getfriendgroups($dbs, $req, $res, $flags); |
|---|
| 1888 | } |
|---|
| 1889 | if ($req->{'mode'} eq "getfriends") { |
|---|
| 1890 | return getfriends($dbs, $req, $res, $flags); |
|---|
| 1891 | } |
|---|
| 1892 | if ($req->{'mode'} eq "friendof") { |
|---|
| 1893 | return friendof($dbs, $req, $res, $flags); |
|---|
| 1894 | } |
|---|
| 1895 | if ($req->{'mode'} eq "checkfriends") { |
|---|
| 1896 | return checkfriends($dbs, $req, $res, $flags); |
|---|
| 1897 | } |
|---|
| 1898 | if ($req->{'mode'} eq "getdaycounts") { |
|---|
| 1899 | return getdaycounts($dbs, $req, $res, $flags); |
|---|
| 1900 | } |
|---|
| 1901 | if ($req->{'mode'} eq "postevent") { |
|---|
| 1902 | return postevent($dbs, $req, $res, $flags); |
|---|
| 1903 | } |
|---|
| 1904 | if ($req->{'mode'} eq "editevent") { |
|---|
| 1905 | return editevent($dbs, $req, $res, $flags); |
|---|
| 1906 | } |
|---|
| 1907 | if ($req->{'mode'} eq "syncitems") { |
|---|
| 1908 | return syncitems($dbs, $req, $res, $flags); |
|---|
| 1909 | } |
|---|
| 1910 | if ($req->{'mode'} eq "getevents") { |
|---|
| 1911 | return getevents($dbs, $req, $res, $flags); |
|---|
| 1912 | } |
|---|
| 1913 | if ($req->{'mode'} eq "editfriends") { |
|---|
| 1914 | return editfriends($dbs, $req, $res, $flags); |
|---|
| 1915 | } |
|---|
| 1916 | if ($req->{'mode'} eq "editfriendgroups") { |
|---|
| 1917 | return editfriendgroups($dbs, $req, $res, $flags); |
|---|
| 1918 | } |
|---|
| 1919 | if ($req->{'mode'} eq "consolecommand") { |
|---|
| 1920 | return consolecommand($dbs, $req, $res, $flags); |
|---|
| 1921 | } |
|---|
| 1922 | |
|---|
| 1923 | ### unknown mode! |
|---|
| 1924 | $res->{'success'} = "FAIL"; |
|---|
| 1925 | $res->{'errmsg'} = "Client error: Unknown mode ($req->{'mode'})"; |
|---|
| 1926 | return; |
|---|
| 1927 | } |
|---|
| 1928 | |
|---|
| 1929 | ## flat wrapper |
|---|
| 1930 | sub login |
|---|
| 1931 | { |
|---|
| 1932 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 1933 | |
|---|
| 1934 | my $err = 0; |
|---|
| 1935 | my $rq = upgrade_request($req); |
|---|
| 1936 | |
|---|
| 1937 | my $rs = LJ::Protocol::do_request($dbs, "login", $rq, \$err, $flags); |
|---|
| 1938 | unless ($rs) { |
|---|
| 1939 | $res->{'success'} = "FAIL"; |
|---|
| 1940 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 1941 | return 0; |
|---|
| 1942 | } |
|---|
| 1943 | |
|---|
| 1944 | $res->{'success'} = "OK"; |
|---|
| 1945 | $res->{'name'} = $rs->{'fullname'}; |
|---|
| 1946 | $res->{'message'} = $rs->{'message'} if $rs->{'message'}; |
|---|
| 1947 | $res->{'fastserver'} = 1 if $rs->{'fastserver'}; |
|---|
| 1948 | |
|---|
| 1949 | # shared journals |
|---|
| 1950 | my $access_count = 0; |
|---|
| 1951 | foreach my $user (@{$rs->{'usejournals'}}) { |
|---|
| 1952 | $access_count++; |
|---|
| 1953 | $res->{"access_${access_count}"} = $user; |
|---|
| 1954 | } |
|---|
| 1955 | if ($access_count) { |
|---|
| 1956 | $res->{"access_count"} = $access_count; |
|---|
| 1957 | } |
|---|
| 1958 | |
|---|
| 1959 | # friend groups |
|---|
| 1960 | populate_friend_groups($res, $rs->{'friendgroups'}); |
|---|
| 1961 | |
|---|
| 1962 | my $flatten = sub { |
|---|
| 1963 | my ($prefix, $listref) = @_; |
|---|
| 1964 | my $ct = 0; |
|---|
| 1965 | foreach (@$listref) { |
|---|
| 1966 | $ct++; |
|---|
| 1967 | $res->{"${prefix}_$ct"} = $_; |
|---|
| 1968 | } |
|---|
| 1969 | $res->{"${prefix}_count"} = $ct; |
|---|
| 1970 | }; |
|---|
| 1971 | |
|---|
| 1972 | ### picture keywords |
|---|
| 1973 | $flatten->("pickw", $rs->{'pickws'}) |
|---|
| 1974 | if defined $req->{"getpickws"}; |
|---|
| 1975 | $flatten->("pickwurl", $rs->{'pickwurls'}) |
|---|
| 1976 | if defined $req->{"getpickwurls"}; |
|---|
| 1977 | |
|---|
| 1978 | ### report new moods that this client hasn't heard of, if they care |
|---|
| 1979 | if (defined $req->{"getmoods"}) { |
|---|
| 1980 | my $mood_count = 0; |
|---|
| 1981 | foreach my $m (@{$rs->{'moods'}}) { |
|---|
| 1982 | $mood_count++; |
|---|
| 1983 | $res->{"mood_${mood_count}_id"} = $m->{'id'}; |
|---|
| 1984 | $res->{"mood_${mood_count}_name"} = $m->{'name'}; |
|---|
| 1985 | } |
|---|
| 1986 | if ($mood_count) { |
|---|
| 1987 | $res->{"mood_count"} = $mood_count; |
|---|
| 1988 | } |
|---|
| 1989 | } |
|---|
| 1990 | |
|---|
| 1991 | #### send web menus |
|---|
| 1992 | if ($req->{"getmenus"} == 1) { |
|---|
| 1993 | my $menu = $rs->{'menus'}; |
|---|
| 1994 | my $menu_num = 0; |
|---|
| 1995 | populate_web_menu($res, $menu, \$menu_num); |
|---|
| 1996 | } |
|---|
| 1997 | |
|---|
| 1998 | return 1; |
|---|
| 1999 | } |
|---|
| 2000 | |
|---|
| 2001 | ## flat wrapper |
|---|
| 2002 | sub getfriendgroups |
|---|
| 2003 | { |
|---|
| 2004 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2005 | |
|---|
| 2006 | my $err = 0; |
|---|
| 2007 | my $rq = upgrade_request($req); |
|---|
| 2008 | |
|---|
| 2009 | my $rs = LJ::Protocol::do_request($dbs, "getfriendgroups", $rq, \$err, $flags); |
|---|
| 2010 | unless ($rs) { |
|---|
| 2011 | $res->{'success'} = "FAIL"; |
|---|
| 2012 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2013 | return 0; |
|---|
| 2014 | } |
|---|
| 2015 | $res->{'success'} = "OK"; |
|---|
| 2016 | populate_friend_groups($res, $rs->{'friendgroups'}); |
|---|
| 2017 | |
|---|
| 2018 | return 1; |
|---|
| 2019 | } |
|---|
| 2020 | |
|---|
| 2021 | ## flat wrapper |
|---|
| 2022 | sub getfriends |
|---|
| 2023 | { |
|---|
| 2024 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2025 | |
|---|
| 2026 | my $err = 0; |
|---|
| 2027 | my $rq = upgrade_request($req); |
|---|
| 2028 | |
|---|
| 2029 | my $rs = LJ::Protocol::do_request($dbs, "getfriends", $rq, \$err, $flags); |
|---|
| 2030 | unless ($rs) { |
|---|
| 2031 | $res->{'success'} = "FAIL"; |
|---|
| 2032 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2033 | return 0; |
|---|
| 2034 | } |
|---|
| 2035 | |
|---|
| 2036 | $res->{'success'} = "OK"; |
|---|
| 2037 | if ($req->{'includegroups'}) { |
|---|
| 2038 | populate_friend_groups($res, $rs->{'friendgroups'}); |
|---|
| 2039 | } |
|---|
| 2040 | if ($req->{'includefriendof'}) { |
|---|
| 2041 | populate_friends($res, "friendof", $rs->{'friendofs'}); |
|---|
| 2042 | } |
|---|
| 2043 | populate_friends($res, "friend", $rs->{'friends'}); |
|---|
| 2044 | |
|---|
| 2045 | return 1; |
|---|
| 2046 | } |
|---|
| 2047 | |
|---|
| 2048 | ## flat wrapper |
|---|
| 2049 | sub friendof |
|---|
| 2050 | { |
|---|
| 2051 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2052 | |
|---|
| 2053 | my $err = 0; |
|---|
| 2054 | my $rq = upgrade_request($req); |
|---|
| 2055 | |
|---|
| 2056 | my $rs = LJ::Protocol::do_request($dbs, "friendof", $rq, \$err, $flags); |
|---|
| 2057 | unless ($rs) { |
|---|
| 2058 | $res->{'success'} = "FAIL"; |
|---|
| 2059 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2060 | return 0; |
|---|
| 2061 | } |
|---|
| 2062 | |
|---|
| 2063 | $res->{'success'} = "OK"; |
|---|
| 2064 | populate_friends($res, "friendof", $rs->{'friendofs'}); |
|---|
| 2065 | return 1; |
|---|
| 2066 | } |
|---|
| 2067 | |
|---|
| 2068 | ## flat wrapper |
|---|
| 2069 | sub checkfriends |
|---|
| 2070 | { |
|---|
| 2071 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2072 | |
|---|
| 2073 | my $err = 0; |
|---|
| 2074 | my $rq = upgrade_request($req); |
|---|
| 2075 | |
|---|
| 2076 | my $rs = LJ::Protocol::do_request($dbs, "checkfriends", $rq, \$err, $flags); |
|---|
| 2077 | unless ($rs) { |
|---|
| 2078 | $res->{'success'} = "FAIL"; |
|---|
| 2079 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2080 | return 0; |
|---|
| 2081 | } |
|---|
| 2082 | |
|---|
| 2083 | $res->{'success'} = "OK"; |
|---|
| 2084 | $res->{'new'} = $rs->{'new'}; |
|---|
| 2085 | $res->{'lastupdate'} = $rs->{'lastupdate'}; |
|---|
| 2086 | $res->{'interval'} = $rs->{'interval'}; |
|---|
| 2087 | return 1; |
|---|
| 2088 | } |
|---|
| 2089 | |
|---|
| 2090 | ## flat wrapper |
|---|
| 2091 | sub getdaycounts |
|---|
| 2092 | { |
|---|
| 2093 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2094 | |
|---|
| 2095 | my $err = 0; |
|---|
| 2096 | my $rq = upgrade_request($req); |
|---|
| 2097 | |
|---|
| 2098 | my $rs = LJ::Protocol::do_request($dbs, "getdaycounts", $rq, \$err, $flags); |
|---|
| 2099 | unless ($rs) { |
|---|
| 2100 | $res->{'success'} = "FAIL"; |
|---|
| 2101 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2102 | return 0; |
|---|
| 2103 | } |
|---|
| 2104 | |
|---|
| 2105 | $res->{'success'} = "OK"; |
|---|
| 2106 | foreach my $d (@{ $rs->{'daycounts'} }) { |
|---|
| 2107 | $res->{$d->{'date'}} = $d->{'count'}; |
|---|
| 2108 | } |
|---|
| 2109 | return 1; |
|---|
| 2110 | } |
|---|
| 2111 | |
|---|
| 2112 | ## flat wrapper |
|---|
| 2113 | sub syncitems |
|---|
| 2114 | { |
|---|
| 2115 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2116 | |
|---|
| 2117 | my $err = 0; |
|---|
| 2118 | my $rq = upgrade_request($req); |
|---|
| 2119 | |
|---|
| 2120 | my $rs = LJ::Protocol::do_request($dbs, "syncitems", $rq, \$err, $flags); |
|---|
| 2121 | unless ($rs) { |
|---|
| 2122 | $res->{'success'} = "FAIL"; |
|---|
| 2123 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2124 | return 0; |
|---|
| 2125 | } |
|---|
| 2126 | |
|---|
| 2127 | $res->{'success'} = "OK"; |
|---|
| 2128 | $res->{'sync_total'} = $rs->{'total'}; |
|---|
| 2129 | $res->{'sync_count'} = $rs->{'count'}; |
|---|
| 2130 | |
|---|
| 2131 | my $ct = 0; |
|---|
| 2132 | foreach my $s (@{ $rs->{'syncitems'} }) { |
|---|
| 2133 | $ct++; |
|---|
| 2134 | foreach my $a (qw(item action time)) { |
|---|
| 2135 | $res->{"sync_${ct}_$a"} = $s->{$a}; |
|---|
| 2136 | } |
|---|
| 2137 | } |
|---|
| 2138 | return 1; |
|---|
| 2139 | } |
|---|
| 2140 | |
|---|
| 2141 | ## flat wrapper: limited functionality. (1 command only, server-parsed only) |
|---|
| 2142 | sub consolecommand |
|---|
| 2143 | { |
|---|
| 2144 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2145 | |
|---|
| 2146 | my $err = 0; |
|---|
| 2147 | my $rq = upgrade_request($req); |
|---|
| 2148 | delete $rq->{'command'}; |
|---|
| 2149 | |
|---|
| 2150 | $rq->{'commands'} = [ $req->{'command'} ]; |
|---|
| 2151 | |
|---|
| 2152 | my $rs = LJ::Protocol::do_request($dbs, "consolecommand", $rq, \$err, $flags); |
|---|
| 2153 | unless ($rs) { |
|---|
| 2154 | $res->{'success'} = "FAIL"; |
|---|
| 2155 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2156 | return 0; |
|---|
| 2157 | } |
|---|
| 2158 | |
|---|
| 2159 | $res->{'cmd_success'} = $rs->{'results'}->[0]->{'success'}; |
|---|
| 2160 | $res->{'cmd_line_count'} = 0; |
|---|
| 2161 | foreach my $l (@{$rs->{'results'}->[0]->{'output'}}) { |
|---|
| 2162 | $res->{'cmd_line_count'}++; |
|---|
| 2163 | my $line = $res->{'cmd_line_count'}; |
|---|
| 2164 | $res->{"cmd_line_${line}_type"} = $l->[0] |
|---|
| 2165 | if $l->[0]; |
|---|
| 2166 | $res->{"cmd_line_${line}"} = $l->[1]; |
|---|
| 2167 | } |
|---|
| 2168 | |
|---|
| 2169 | $res->{'success'} = "OK"; |
|---|
| 2170 | |
|---|
| 2171 | } |
|---|
| 2172 | |
|---|
| 2173 | ## flat wrapper |
|---|
| 2174 | sub editfriends |
|---|
| 2175 | { |
|---|
| 2176 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2177 | |
|---|
| 2178 | my $err = 0; |
|---|
| 2179 | my $rq = upgrade_request($req); |
|---|
| 2180 | |
|---|
| 2181 | $rq->{'add'} = []; |
|---|
| 2182 | $rq->{'delete'} = []; |
|---|
| 2183 | |
|---|
| 2184 | foreach (keys %$req) { |
|---|
| 2185 | if (/^editfriend_add_(\d+)_user$/) { |
|---|
| 2186 | my $n = $1; |
|---|
| 2187 | next unless ($req->{"editfriend_add_${n}_user"} =~ /\S/); |
|---|
| 2188 | my $fa = { 'username' => $req->{"editfriend_add_${n}_user"}, |
|---|
| 2189 | 'fgcolor' => $req->{"editfriend_add_${n}_fg"}, |
|---|
| 2190 | 'bgcolor' => $req->{"editfriend_add_${n}_bg"}, |
|---|
| 2191 | 'groupmask' => $req->{"editfriend_add_${n}_groupmask"}, |
|---|
| 2192 | }; |
|---|
| 2193 | push @{$rq->{'add'}}, $fa; |
|---|
| 2194 | } elsif (/^editfriend_delete_(\w+)$/) { |
|---|
| 2195 | push @{$rq->{'delete'}}, $1; |
|---|
| 2196 | } |
|---|
| 2197 | } |
|---|
| 2198 | |
|---|
| 2199 | my $rs = LJ::Protocol::do_request($dbs, "editfriends", $rq, \$err, $flags); |
|---|
| 2200 | unless ($rs) { |
|---|
| 2201 | $res->{'success'} = "FAIL"; |
|---|
| 2202 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2203 | return 0; |
|---|
| 2204 | } |
|---|
| 2205 | |
|---|
| 2206 | $res->{'success'} = "OK"; |
|---|
| 2207 | |
|---|
| 2208 | my $ct = 0; |
|---|
| 2209 | foreach my $fa (@{ $rs->{'added'} }) { |
|---|
| 2210 | $ct++; |
|---|
| 2211 | $res->{"friend_${ct}_user"} = $fa->{'username'}; |
|---|
| 2212 | $res->{"friend_${ct}_name"} = $fa->{'fullname'}; |
|---|
| 2213 | } |
|---|
| 2214 | |
|---|
| 2215 | $res->{'friends_added'} = $ct; |
|---|
| 2216 | |
|---|
| 2217 | return 1; |
|---|
| 2218 | } |
|---|
| 2219 | |
|---|
| 2220 | ## flat wrapper |
|---|
| 2221 | sub editfriendgroups |
|---|
| 2222 | { |
|---|
| 2223 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2224 | |
|---|
| 2225 | my $err = 0; |
|---|
| 2226 | my $rq = upgrade_request($req); |
|---|
| 2227 | |
|---|
| 2228 | $rq->{'groupmasks'} = {}; |
|---|
| 2229 | $rq->{'set'} = {}; |
|---|
| 2230 | $rq->{'delete'} = []; |
|---|
| 2231 | |
|---|
| 2232 | foreach (keys %$req) { |
|---|
| 2233 | if (/^efg_set_(\d+)_name$/) { |
|---|
| 2234 | next unless ($req->{$_} ne ""); |
|---|
| 2235 | my $n = $1; |
|---|
| 2236 | my $fs = { |
|---|
| 2237 | 'name' => $req->{"efg_set_${n}_name"}, |
|---|
| 2238 | 'sort' => $req->{"efg_set_${n}_sort"}, |
|---|
| 2239 | }; |
|---|
| 2240 | if (defined $req->{"efg_set_${n}_public"}) { |
|---|
| 2241 | $fs->{'public'} = $req->{"efg_set_${n}_public"}; |
|---|
| 2242 | } |
|---|
| 2243 | $rq->{'set'}->{$n} = $fs; |
|---|
| 2244 | } |
|---|
| 2245 | elsif (/^efg_delete_(\d+)$/) { |
|---|
| 2246 | if ($req->{$_}) { |
|---|
| 2247 | # delete group if value is true |
|---|
| 2248 | push @{$rq->{'delete'}}, $1; |
|---|
| 2249 | } |
|---|
| 2250 | } |
|---|
| 2251 | elsif (/^editfriend_groupmask_(\w+)$/) { |
|---|
| 2252 | $rq->{'groupmasks'}->{$1} = $req->{$_}; |
|---|
| 2253 | } |
|---|
| 2254 | } |
|---|
| 2255 | |
|---|
| 2256 | my $rs = LJ::Protocol::do_request($dbs, "editfriendgroups", $rq, \$err, $flags); |
|---|
| 2257 | unless ($rs) { |
|---|
| 2258 | $res->{'success'} = "FAIL"; |
|---|
| 2259 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2260 | return 0; |
|---|
| 2261 | } |
|---|
| 2262 | |
|---|
| 2263 | $res->{'success'} = "OK"; |
|---|
| 2264 | return 1; |
|---|
| 2265 | } |
|---|
| 2266 | |
|---|
| 2267 | sub flatten_props |
|---|
| 2268 | { |
|---|
| 2269 | my ($req, $rq) = @_; |
|---|
| 2270 | |
|---|
| 2271 | ## changes prop_* to props hashref |
|---|
| 2272 | foreach my $k (keys %$req) { |
|---|
| 2273 | next unless ($k =~ /^prop_(.+)/); |
|---|
| 2274 | $rq->{'props'}->{$1} = $req->{$k}; |
|---|
| 2275 | } |
|---|
| 2276 | } |
|---|
| 2277 | |
|---|
| 2278 | ## flat wrapper |
|---|
| 2279 | sub postevent |
|---|
| 2280 | { |
|---|
| 2281 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2282 | |
|---|
| 2283 | my $err = 0; |
|---|
| 2284 | my $rq = upgrade_request($req); |
|---|
| 2285 | flatten_props($req, $rq); |
|---|
| 2286 | |
|---|
| 2287 | my $rs = LJ::Protocol::do_request($dbs, "postevent", $rq, \$err, $flags); |
|---|
| 2288 | unless ($rs) { |
|---|
| 2289 | $res->{'success'} = "FAIL"; |
|---|
| 2290 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2291 | return 0; |
|---|
| 2292 | } |
|---|
| 2293 | |
|---|
| 2294 | $res->{'success'} = "OK"; |
|---|
| 2295 | $res->{'itemid'} = $rs->{'itemid'}; |
|---|
| 2296 | $res->{'anum'} = $rs->{'anum'} if defined $rs->{'anum'}; |
|---|
| 2297 | return 1; |
|---|
| 2298 | } |
|---|
| 2299 | |
|---|
| 2300 | ## flat wrapper |
|---|
| 2301 | sub editevent |
|---|
| 2302 | { |
|---|
| 2303 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2304 | |
|---|
| 2305 | my $err = 0; |
|---|
| 2306 | my $rq = upgrade_request($req); |
|---|
| 2307 | flatten_props($req, $rq); |
|---|
| 2308 | |
|---|
| 2309 | my $rs = LJ::Protocol::do_request($dbs, "editevent", $rq, \$err, $flags); |
|---|
| 2310 | unless ($rs) { |
|---|
| 2311 | $res->{'success'} = "FAIL"; |
|---|
| 2312 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2313 | return 0; |
|---|
| 2314 | } |
|---|
| 2315 | |
|---|
| 2316 | $res->{'success'} = "OK"; |
|---|
| 2317 | $res->{'itemid'} = $rs->{'itemid'}; |
|---|
| 2318 | $res->{'anum'} = $rs->{'anum'} if defined $rs->{'anum'}; |
|---|
| 2319 | return 1; |
|---|
| 2320 | } |
|---|
| 2321 | |
|---|
| 2322 | ## flat wrapper |
|---|
| 2323 | sub getevents |
|---|
| 2324 | { |
|---|
| 2325 | my ($dbs, $req, $res, $flags) = @_; |
|---|
| 2326 | |
|---|
| 2327 | my $err = 0; |
|---|
| 2328 | my $rq = upgrade_request($req); |
|---|
| 2329 | |
|---|
| 2330 | my $rs = LJ::Protocol::do_request($dbs, "getevents", $rq, \$err, $flags); |
|---|
| 2331 | unless ($rs) { |
|---|
| 2332 | $res->{'success'} = "FAIL"; |
|---|
| 2333 | $res->{'errmsg'} = LJ::Protocol::error_message($err); |
|---|
| 2334 | return 0; |
|---|
| 2335 | } |
|---|
| 2336 | |
|---|
| 2337 | my $ect = 0; |
|---|
| 2338 | my $pct = 0; |
|---|
| 2339 | foreach my $evt (@{$rs->{'events'}}) { |
|---|
| 2340 | $ect++; |
|---|
| 2341 | foreach my $f (qw(itemid eventtime security allowmask subject anum)) { |
|---|
| 2342 | if (defined $evt->{$f}) { |
|---|
| 2343 | $res->{"events_${ect}_$f"} = $evt->{$f}; |
|---|
| 2344 | } |
|---|
| 2345 | } |
|---|
| 2346 | $res->{"events_${ect}_event"} = LJ::eurl($evt->{'event'}); |
|---|
| 2347 | |
|---|
| 2348 | if ($evt->{'props'}) { |
|---|
| 2349 | foreach my $k (sort keys %{$evt->{'props'}}) { |
|---|
| 2350 | $pct++; |
|---|
| 2351 | $res->{"prop_${pct}_itemid"} = $evt->{'itemid'}; |
|---|
| 2352 | $res->{"prop_${pct}_name"} = $k; |
|---|
| 2353 | $res->{"prop_${pct}_value"} = $evt->{'props'}->{$k}; |
|---|
| 2354 | } |
|---|
| 2355 | } |
|---|
| 2356 | } |
|---|
| 2357 | |
|---|
| 2358 | unless ($req->{'noprops'}) { |
|---|
| 2359 | $res->{'prop_count'} = $pct; |
|---|
| 2360 | } |
|---|
| 2361 | $res->{'events_count'} = $ect; |
|---|
| 2362 | $res->{'success'} = "OK"; |
|---|
| 2363 | |
|---|
| 2364 | return 1; |
|---|
| 2365 | } |
|---|
| 2366 | |
|---|
| 2367 | |
|---|
| 2368 | sub populate_friends |
|---|
| 2369 | { |
|---|
| 2370 | my ($res, $pfx, $list) = @_; |
|---|
| 2371 | my $count = 0; |
|---|
| 2372 | foreach my $f (@$list) |
|---|
| 2373 | { |
|---|
| 2374 | $count++; |
|---|
| 2375 | $res->{"${pfx}_${count}_name"} = $f->{'fullname'}; |
|---|
| 2376 | $res->{"${pfx}_${count}_user"} = $f->{'username'}; |
|---|
| 2377 | $res->{"${pfx}_${count}_bg"} = $f->{'bgcolor'}; |
|---|
| 2378 | $res->{"${pfx}_${count}_fg"} = $f->{'fgcolor'}; |
|---|
| 2379 | if (defined $f->{'groupmask'}) { |
|---|
| 2380 | $res->{"${pfx}_${count}_groupmask"} = $f->{'groupmask'}; |
|---|
| 2381 | } |
|---|
| 2382 | if (defined $f->{'type'}) { |
|---|
| 2383 | $res->{"${pfx}_${count}_type"} = $f->{'type'}; |
|---|
| 2384 | } |
|---|
| 2385 | } |
|---|
| 2386 | $res->{"${pfx}_count"} = $count; |
|---|
| 2387 | } |
|---|
| 2388 | |
|---|
| 2389 | |
|---|
| 2390 | sub upgrade_request |
|---|
| 2391 | { |
|---|
| 2392 | my $r = shift; |
|---|
| 2393 | my $new = { %{ $r } }; |
|---|
| 2394 | $new->{'username'} = $r->{'user'}; |
|---|
| 2395 | |
|---|
| 2396 | # but don't delete $r->{'user'}, as it might be, say, %FORM, |
|---|
| 2397 | # that'll get reused in a later request in, say, update.bml after |
|---|
| 2398 | # the login before postevent. whoops. |
|---|
| 2399 | |
|---|
| 2400 | return $new; |
|---|
| 2401 | } |
|---|
| 2402 | |
|---|
| 2403 | ## given a $res hashref and friend group subtree (arrayref), flattens it |
|---|
| 2404 | sub populate_friend_groups |
|---|
| 2405 | { |
|---|
| 2406 | my ($res, $fr) = @_; |
|---|
| 2407 | |
|---|
| 2408 | my $maxnum = 0; |
|---|
| 2409 | foreach my $fg (@$fr) |
|---|
| 2410 | { |
|---|
| 2411 | my $num = $fg->{'id'}; |
|---|
| 2412 | $res->{"frgrp_${num}_name"} = $fg->{'name'}; |
|---|
| 2413 | $res->{"frgrp_${num}_sortorder"} = $fg->{'sortorder'}; |
|---|
| 2414 | if ($fg->{'public'}) { |
|---|
| 2415 | $res->{"frgrp_${num}_public"} = 1; |
|---|
| 2416 | } |
|---|
| 2417 | if ($num > $maxnum) { $maxnum = $num; } |
|---|
| 2418 | } |
|---|
| 2419 | $res->{'frgrp_maxnum'} = $maxnum; |
|---|
| 2420 | } |
|---|
| 2421 | |
|---|
| 2422 | ## given a menu tree, flattens it into $res hashref |
|---|
| 2423 | sub populate_web_menu |
|---|
| 2424 | { |
|---|
| 2425 | my ($res, $menu, $numref) = @_; |
|---|
| 2426 | my $mn = $$numref; # menu number |
|---|
| 2427 | my $mi = 0; # menu item |
|---|
| 2428 | foreach my $it (@$menu) { |
|---|
| 2429 | $mi++; |
|---|
| 2430 | $res->{"menu_${mn}_${mi}_text"} = $it->{'text'}; |
|---|
| 2431 | if ($it->{'text'} eq "-") { next; } |
|---|
| 2432 | if ($it->{'sub'}) { |
|---|
| 2433 | $$numref++; |
|---|
| 2434 | $res->{"menu_${mn}_${mi}_sub"} = $$numref; |
|---|
| 2435 | &populate_web_menu($res, $it->{'sub'}, $numref); |
|---|
| 2436 | next; |
|---|
| 2437 | |
|---|
| 2438 | } |
|---|
| 2439 | $res->{"menu_${mn}_${mi}_url"} = $it->{'url'}; |
|---|
| 2440 | } |
|---|
| 2441 | $res->{"menu_${mn}_count"} = $mi; |
|---|
| 2442 | } |
|---|
| 2443 | |
|---|
| 2444 | 1; |
|---|