root/trunk/cgi-bin/ljlib.pl @ 14

Revision 14, 53.0 KB (checked in by bradfitz, 12 years ago)

initial import

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
RevLine 
[14]1#!/usr/bin/perl
2
3use DBI;
4use Digest::MD5 qw(md5_hex);
5
6########################
7# CONSTANTS
8#
9
10require '/home/lj/cgi-bin/ljconfig.pl';
11require '/home/lj/cgi-bin/ljlang.pl';
12require '/home/lj/cgi-bin/ljpoll.pl';
13
14@LJ::views = qw(lastn friends calendar day);
15%LJ::viewinfo = (
16                 "lastn" => {
17                     "creator" => \&create_view_lastn,
18                     "des" => "Most Recent Events",
19                 },
20                 "calendar" => {
21                     "creator" => \&create_view_calendar,
22                     "des" => "Calendar",
23                 },
24                 "day" => {
25                     "creator" => \&create_view_day,
26                     "des" => "Day View",
27                 },
28                 "friends" => {
29                     "creator" => \&create_view_friends,
30                     "des" => "Friends View",
31                 },
32                 );
33
34## for use in style system's %%cons:.+%% mapping
35%LJ::constant_map = ('siteroot' => $LJ::SITEROOT,
36                     'sitename' => $LJ::SITENAME,
37                     'img' => $LJ::IMGPREFIX,
38                     );
39
40$SIG{'HUP'} = sub {
41    print STDERR "HUP caught.  Clearing caches.\n";
42    %LJ::CACHE_STYLE = ();
43    %LJ::CACHE_PROPS = ();
44    $LJ::CACHED_MOODS = 0;
45    $LJ::CACHED_MOOD_MAX = 0;
46    %LJ::CACHE_MOODS = ();
47    %LJ::CACHE_MOOD_THEME = ();
48    %LJ::CACHE_USERID = ();
49    %LJ::CACHE_USERNAME = ();
50    %LJ::CACHE_USERPIC_SIZE = ();
51    %LJ::CACHE_CODES = ();
52    %LJ::CACHE_USERPROP = ();  # {$prop}->{ 'upropid' => ... , 'indexed' => 0|1 };
53};
54
55sub send_mail
56{
57    my $opt = shift;
58    &LJ::send_mail($opt);
59}
60
61## for stupid AOL mail client, wraps a plain-text URL in an anchor tag since AOL
62## incorrectly renders regular text as HTML.  fucking AOL.  die.
63sub make_text_link
64{
65    my ($url, $email) = @_;
66    if ($email =~ /\@aol.com$/i) {
67        return "<A HREF=\"$url\">$url</A>";
68    }
69    return $url;
70}
71
72sub is_valid_authaction
73{
74    &connect_db();
75    my ($aaid, $auth) = map { $dbh->quote($_) } @_;
76    my $sth = $dbh->prepare("SELECT aaid, userid, datecreate, authcode, action, arg1 FROM authactions WHERE aaid=$aaid AND authcode=$auth");
77    $sth->execute;
78    return $sth->fetchrow_hashref;
79}
80
81## authenticates the user at the remote end and returns a hashref containing:
82##    user, userid
83## or returns undef if no logged-in remote or errors.
84## optional argument is arrayref to push errors
85sub get_remote
86{
87    my $errors = shift;
88    my $cgi = shift;   # optional CGI.pm reference
89
90    ### are they logged in?
91    my $remuser = $cgi ? $cgi->cookie('ljuser') : $BMLClient::COOKIE{"ljuser"};
92    return undef unless ($remuser);
93
94    my $hpass = $cgi ? $cgi->cookie('ljhpass') : $BMLClient::COOKIE{"ljhpass"};
95
96    ### does their login password match their login?
97    return undef unless ($hpass =~ /^$remuser:(.+)/);
98    my $remhpass = $1;
99
100    &connect_db();
101
102    ### do they exist?
103    my $userid = &get_userid($remuser);
104    $userid += 0;
105    return undef unless ($userid);
106
107    ### is their password correct?
108    my $password;
109    my $sth = $dbh->prepare("SELECT password FROM user WHERE userid=$userid");
110    $sth->execute;
111    ($password) = $sth->fetchrow_array;
112    return undef unless (&valid_password($password, { 'hpassword' => $remhpass }));
113
114    return { 'user' => $remuser,
115             'userid' => $userid, };
116}
117
118# this is like get_remote, but it only returns who they say they are,
119# not who they really are.  so if they're faking out their cookies,
120# they'll fake this out.  but this is fast.
121#
122sub get_remote_noauth
123{
124    ### are they logged in?
125    my $remuser = $BMLClient::COOKIE{"ljuser"};
126    return undef unless ($remuser);
127
128    ### does their login password match their login?
129    return undef unless ($BMLClient::COOKIE{"ljhpass"} =~ /^$remuser:(.+)/);
130    return { 'user' => $remuser, };
131}
132
133sub remote_has_priv { return &LJ::remote_has_priv($dbh, @_); }
134
135sub register_authaction
136{
137    &connect_db();
138    my $userid = shift;  $userid += 0;
139    my $action = $dbh->quote(shift);
140    my $arg1 = $dbh->quote(shift);
141   
142    # make the authcode
143    my $authcode = "";
144    my $vchars = "abcdefghijklmnopqrstuvwxyz0123456789";
145    srand();
146    for (1..15) {
147        $authcode .= substr($vchars, int(rand()*36), 1);
148    }
149    my $qauthcode = $dbh->quote($authcode);
150
151    my $sth = $dbh->prepare("INSERT INTO authactions (aaid, userid, datecreate, authcode, action, arg1) VALUES (NULL, $userid, NOW(), $qauthcode, $action, $arg1)");
152    $sth->execute;
153
154    if ($dbh->err) {
155        return 0;
156    } else {
157        return { 'aaid' => $dbh->{'mysql_insertid'},
158                 'authcode' => $authcode,
159             };
160    }
161}
162
163
164sub auth_fields
165{
166    my $opts = shift;
167    my $remote = &get_remote_noauth();
168    my $ret = "";
169    if (!$FORM{'altlogin'} && !$opts->{'user'} && $remote->{'user'}) {
170        my $hpass;
171        if ($BMLClient::COOKIE{"ljhpass"} =~ /^$remote->{'user'}:(.+)/) {
172            $hpass = $1;
173        }
174        my $alturl = $ENV{'REQUEST_URI'};
175        $alturl .= ($alturl =~ /\?/) ? "&" : "?";
176        $alturl .= "altlogin=1";
177
178        $ret .= "<TR><TD COLSPAN=2>You are currently logged in as <B>$remote->{'user'}</B>.<BR>If this is not you, <A HREF=\"$alturl\">click here</A>.\n";
179        $ret .= "<INPUT TYPE=HIDDEN NAME=user VALUE=\"$remote->{'user'}\">\n";
180        $ret .= "<INPUT TYPE=HIDDEN NAME=hpassword VALUE=\"$hpass\"><BR>&nbsp;\n";
181        $ret .= "</TD></TR>\n";
182    } else {
183        $ret .= "<TR><TD>Username:</TD><TD><INPUT TYPE=TEXT NAME=user SIZE=15 MAXLENGTH=15 VALUE=\"";
184        my $user = $opts->{'user'};
185        unless ($user || $ENV{'QUERY_STRING'} =~ /=/) { $user=$ENV{'QUERY_STRING'}; }
186        $ret .= &BMLUtil::escapeall($user) unless ($FORM{'altlogin'});
187        $ret .= "\"></TD></TR>\n";
188        $ret .= "<TR><TD>Password:</TD><TD>\n";
189        $ret .= "<INPUT TYPE=password NAME=password SIZE=15 MAXLENGTH=30 VALUE=\"" . &ehtml($opts->{'password'}) . "\">";
190        $ret .= "</TD></TR>\n";
191    }
192    return $ret;
193}
194
195
196sub valid_password { return &LJ::valid_password(@_); }
197sub hash_password { return md5_hex($_[0]); }
198
199
200sub remap_event_links
201{
202    my ($eventref, $baseurl) = @_;
203    return unless $baseurl;
204    $$eventref =~ s/(<IMG\s+[^>]*SRC=)(("(.+?)")|([^\s>]+))/"$1\"" . &abs_url($2, $baseurl). '"'/ieg;
205    $$eventref =~ s/(<A\s+[^>]*HREF=)(("(.+?)")|([^\s>]+))/"$1\"" . &abs_url($2, $baseurl). '"'/ieg;
206}
207
208sub abs_url
209{
210    use URI::URL;
211    my ($uri, $base) = @_;
212    $uri =~ s/^"//;
213        $uri =~ s/"$//;
214    return url($uri)->abs($base)->as_string;
215}
216
217sub load_user_props
218{
219    &connect_db();
220
221    ## user reference
222    my ($uref, @props) = @_;
223    my $uid = $uref->{'userid'}+0;
224    unless ($uid) {
225        $uid = LJ::get_userid($dbh, $uref->{'user'});
226    }
227   
228    my $propname_where;
229    if (@props) {
230        $propname_where = "AND upl.name IN (" . join(",", map { $dbh->quote($_) } @props) . ")";
231    }
232   
233    my ($sql, $sth);
234
235    # FIXME: right now we read userprops from both tables (indexed and lite).  we always have to do this
236    #        for cases when we're loading all props, but when loading a subset, we might be able to
237    #        eliminate one query or the other if we cache somewhere the userproplist and which props
238    #        are in which table.  For now, though, this works:
239
240    foreach my $table (qw(userprop userproplite))
241    {
242        $sql = "SELECT upl.name, up.value FROM $table up, userproplist upl WHERE up.userid=$uid AND up.upropid=upl.upropid $propname_where";
243        $sth = $dbh->prepare($sql);
244        $sth->execute;
245        while ($_ = $sth->fetchrow_hashref) {
246            $uref->{$_->{'name'}} = $_->{'value'};
247        }
248        $sth->finish;
249    }
250}
251
252sub set_userprop
253{
254    my ($dbh, $userid, $propname, $value) = @_;
255    my $p;
256
257    if ($LJ::CACHE_USERPROP{$propname}) {
258        $p = $LJ::CACHE_USERPROP{$propname};
259    } else {
260        my $qpropname = $dbh->quote($propname);
261        $userid += 0;
262        my $propid;
263        my $sth;
264       
265        $sth = $dbh->prepare("SELECT upropid, indexed FROM userproplist WHERE name=$qpropname");
266        $sth->execute;
267        $p = $sth->fetchrow_hashref;
268        return unless ($p);
269        $LJ::CACHE_USERPROP{$propname} = $p;
270    }
271
272    my $table = $p->{'indexed'} ? "userprop" : "userproplite";
273    $value = $dbh->quote($value);
274
275    $sth = $dbh->prepare("REPLACE INTO $table (userid, upropid, value) VALUES ($userid, $p->{'upropid'}, $value)");
276    $sth->execute;
277}
278
279
280sub load_moods
281{
282    return if ($LJ::CACHED_MOODS);
283    &connect_db();
284    my $sth = $dbh->prepare("SELECT moodid, mood, parentmood FROM moods");
285    $sth->execute;
286    while (my ($id, $mood, $parent) = $sth->fetchrow_array) {
287        $LJ::CACHE_MOODS{$id} = { 'name' => $mood, 'parent' => $parent };
288        if ($id > $LJ::CACHED_MOOD_MAX) { $LJ::CACHED_MOOD_MAX = $id; }
289    }
290    $LJ::CACHED_MOODS = 1;
291}
292
293sub load_mood_theme
294{
295    my $themeid = shift;
296    return if ($LJ::CACHE_MOOD_THEME{$themeid});
297
298    &connect_db();
299    $themeid += 0;
300    my $sth = $dbh->prepare("SELECT moodid, picurl, width, height FROM moodthemedata WHERE moodthemeid=$themeid");
301    $sth->execute;
302    while (my ($id, $pic, $w, $h) = $sth->fetchrow_array) {
303        $LJ::CACHE_MOOD_THEME{$themeid}->{$id} = { 'pic' => $pic, 'w' => $w, 'h' => $h };
304    }
305}
306
307##
308## returns 1 and populates %$retref if successful, else returns 0
309##
310sub get_mood_picture
311{
312    my ($themeid, $moodid, $ref) = @_;
313    do
314    {
315        if ($LJ::CACHE_MOOD_THEME{$themeid}->{$moodid}) {
316            %{$ref} = %{$LJ::CACHE_MOOD_THEME{$themeid}->{$moodid}};
317            if ($ref->{'pic'} =~ m!^/!) {
318                $ref->{'pic'} =~ s!^/img!!;
319                $ref->{'pic'} = $LJ::IMGPREFIX . $ref->{'pic'};
320            }
321            $ref->{'moodid'} = $moodid;
322            return 1;
323        } else {
324            $moodid = $LJ::CACHE_MOODS{$moodid}->{'parent'};
325        }
326    }
327    while ($moodid);
328    return 0;
329}
330
331
332sub server_down_html
333{
334    return &LJ::server_down_html();
335}
336
337sub make_journal
338{
339    &connect_db();
340    return &LJ::make_journal($dbh, @_);
341}
342
343sub load_codes
344{
345    my ($req) = $_[0];
346    &connect_db();
347    foreach my $type (keys %{$req})
348    {
349        unless ($LJ::CACHE_CODES{$type})
350        {
351            $LJ::CACHE_CODES{$type} = [];
352            my $qtype = $dbh->quote($type);
353            my $sth = $dbh->prepare("SELECT code, item FROM codes WHERE type=$qtype ORDER BY sortorder");
354            $sth->execute;
355            while (my ($code, $item) = $sth->fetchrow_array)
356            {
357                push @{$LJ::CACHE_CODES{$type}}, [ $code, $item ];
358            }
359        }
360
361        foreach my $it (@{$LJ::CACHE_CODES{$type}})
362        {
363            if (ref $req->{$type} eq "HASH") {
364                $req->{$type}->{$it->[0]} = $it->[1];
365            } elsif (ref $req->{$type} eq "ARRAY") {
366                push @{$req->{$type}}, { 'code' => $it->[0], 'item' => $it->[1] };
367            }
368        }
369    }
370}
371
372sub get_userid { return &LJ::get_userid($dbh, @_); }
373sub get_username { return &LJ::get_username($dbh, @_); }
374sub load_userpics { return &LJ::load_userpics($dbh, @_); }
375
376sub ago_text
377{
378    my $secondsold = shift;
379    return "Never." unless ($secondsold);
380    my $num;
381    my $unit;
382    if ($secondsold > 60*60*24*7) {
383        $num = int($secondsold / (60*60*24*7));
384        $unit = "week";
385    } elsif ($secondsold > 60*60*24) {
386        $num = int($secondsold / (60*60*24));
387        $unit = "day";
388    } elsif ($secondsold > 60*60) {
389        $num = int($secondsold / (60*60));
390        $unit = "hour";
391    } elsif ($secondsold > 60) {
392        $num = int($secondsold / (60));
393        $unit = "minute";
394    } else {
395        $num = $secondsold;
396        $unit = "second";
397    }
398    return "$num $unit" . ($num==1?"":"s") . " ago";
399}
400
401## get the friends id
402sub get_friend_itemids
403{
404    my $opts = shift;
405
406    my $userid = $opts->{'userid'}+0;
407    my $remoteid = $opts->{'remoteid'}+0;
408    my @items = ();
409    my $itemshow = $opts->{'itemshow'}+0;
410    my $skip = $opts->{'skip'}+0;
411    my $getitems = $itemshow+$skip;
412    my $owners_ref = (ref $opts->{'owners'} eq "HASH") ? $opts->{'owners'} : {};
413    my $filter = $opts->{'filter'}+0;
414
415    ### what do your friends think of remote viewer?  what security level?
416    my %usermask;
417    if ($remoteid)
418    {
419        $sth = $dbh->prepare("SELECT ff.userid, ff.groupmask FROM friends fu, friends ff WHERE fu.userid=$userid AND fu.friendid=ff.userid AND ff.friendid=$remoteid");
420        $sth->execute;
421        while (my ($friendid, $mask) = $sth->fetchrow_array) {
422            $usermask{$friendid} = $mask;
423        }
424    }
425
426    my $filtersql;
427    if ($filter) {
428        if ($remoteid == $userid) {
429            $filtersql = "AND f.groupmask & $filter";
430        }
431    }
432
433    $sth = $dbh->prepare("SELECT u.userid, u.timeupdate FROM friends f, user u WHERE f.userid=$userid AND f.friendid=u.userid $filtersql AND u.statusvis='V'");
434    $sth->execute;
435
436    my @friends = ();
437    while (my ($userid, $update) = $sth->fetchrow_array) {
438        push @friends, [ $userid, $update ];
439    }
440    @friends = sort { $b->[1] cmp $a->[1] } @friends;
441
442    my $loop = 1;
443    my $queries = 0;
444    my $oldest = "";
445    while ($loop)
446    {
447        my @ids = ();
448        while (scalar(@ids) < 20 && @friends) {
449            my $f = shift @friends;
450            if ($oldest && $f->[1] lt $oldest) { last; }
451            push @ids, $f->[0];
452        }
453        last unless (@ids);
454        my $in = join(',', @ids);
455       
456        my $sql;
457        if ($remoteid) {
458            $sql = "SELECT l.ownerid, h.itemid, l.logtime, l.security, l.allowmask FROM hintlastnview h, log l WHERE h.userid IN ($in) AND h.itemid=l.itemid";
459        } else {
460            $sql = "SELECT l.ownerid, h.itemid, l.logtime FROM hintlastnview h, log l WHERE h.userid IN ($in) AND h.itemid=l.itemid AND l.security='public'";
461        }
462        if ($oldest) { $sql .= " AND l.logtime > '$oldest'";  }
463
464        # this causes MySQL to do use a temporary table and do an extra pass also (use file sort).  so, we'll do it in memory here.  yay.
465        # $sql .= " ORDER BY l.logtime DESC";
466       
467        $sth = $dbh->prepare($sql);
468        $sth->execute;
469
470        my $rows = $sth->rows;
471        if ($rows == 0) { last; }
472
473        ## see comment above.  this is our "ORDER BY l.logtime DESC".  pathetic, huh?
474        my @hintrows;   
475        while (my ($owner, $itemid, $logtime, $sec, $allowmask) = $sth->fetchrow_array)
476        {
477            push @hintrows, [ $owner, $itemid, $logtime, $sec, $allowmask ];
478        }
479        $sth->finish;
480        @hintrows = sort { $b->[2] cmp $a->[2] } @hintrows;
481       
482        my $count;
483        while (@hintrows)
484        {
485            my $rec = shift @hintrows;
486            my ($owner, $itemid, $logtime, $sec, $allowmask) = @{$rec};
487
488            if ($sec eq "private" && $owner != $remoteid) { next; }
489            if ($sec eq "usemask" && $owner != $remoteid && ! (($usermask{$owner}+0) & ($allowmask+0))) { next; }
490            push @items, [ $itemid, $logtime, $owner ];
491            $count++;
492            if ($count >= $getitems) { last; }
493        }
494        @items = sort { $b->[1] cmp $a->[1] } @items;
495        my $size = scalar(@items);
496        if ($size < $getitems) { next; }
497        @items = @items[0..($getitems-1)];
498        $oldest = $items[$getitems-1]->[1];
499    }
500
501    my $size = scalar(@items);
502
503    my @ret;
504    my $max = $skip+$itemshow;
505    if ($size < $max) { $max = $size; }
506    foreach my $it (@items[$skip..($max-1)]) {
507        push @ret, $it->[0];
508        $owners_ref->{$it->[2]} = 1;
509    }
510    return @ret;
511}
512
513
514# do all the current music/mood/weather/whatever stuff
515sub prepare_currents
516{
517    my $args = shift;
518
519    my %currents = ();
520    my $val;
521    if ($val = $args->{'props'}->{$args->{'itemid'}}->{'current_music'}) {
522        $currents{'Music'} = $val;
523    }
524    if ($val = $args->{'props'}->{$args->{'itemid'}}->{'current_mood'}) {
525        $currents{'Mood'} = $val;
526    }
527    if ($val = $args->{'props'}->{$args->{'itemid'}}->{'current_moodid'}) {
528        my $theme = $args->{'user'}->{'moodthemeid'};
529        &load_mood_theme($theme);
530        my %pic;
531        if (&get_mood_picture($theme, $val, \%pic)) {
532            $currents{'Mood'} = "<IMG SRC=\"$pic{'pic'}\" ALIGN=ABSMIDDLE WIDTH=$pic{'w'} HEIGHT=$pic{'h'} VSPACE=1> $LJ::CACHE_MOODS{$val}->{'name'}";
533        } else {
534            $currents{'Mood'} = $LJ::CACHE_MOODS{$val}->{'name'};
535        }
536    }
537    if (%currents) {
538        if ($args->{'vars'}->{$args->{'prefix'}.'_CURRENTS'})
539        {
540            ### PREFIX_CURRENTS is defined, so use the correct style vars
541
542            my $fvp = { 'currents' => "" };
543            foreach (sort keys %currents) {
544                $fvp->{'currents'} .= &fill_var_props($args->{'vars'}, $args->{'prefix'}.'_CURRENT', {
545                    'what' => $_,
546                    'value' => $currents{$_},
547                });
548            }
549            $args->{'event'}->{'currents'} =
550                &fill_var_props($args->{'vars'}, $args->{'prefix'}.'_CURRENTS', $fvp);
551        } else
552        {
553            ### PREFIX_CURRENTS is not defined, so just add to %%events%%
554            $args->{'event'}->{'event'} .= "<BR>&nbsp;";
555            foreach (sort keys %currents) {
556                $args->{'event'}->{'event'} .= "<BR><B>Current $_</B>: " . $currents{$_} . "\n";
557            }
558        }
559    }
560}
561   
562
563
564sub fill_var_props
565{
566    my ($vars, $key, $hashref) = @_;
567    my $data = $vars->{$key};
568    $data =~ s/%%(?:([\w:]+:))?(\S+?)%%/$1 ? &fvp_transform(lc($1), $vars, $hashref, $2) : $hashref->{$2}/eg;
569    return $data;
570}
571
572sub fvp_transform
573{
574    my ($transform, $vars, $hashref, $attr) = @_;
575    my $ret = $hashref->{$attr};
576    while ($transform =~ s/(\w+):$//) {
577        my $trans = $1;
578        if ($trans eq "ue") {
579            $ret = &eurl($ret);
580        }
581        elsif ($trans eq "xe") {
582            $ret = &exml($ret);
583        }
584        elsif ($trans eq "lc") {
585            $ret = lc($ret);
586        }
587        elsif ($trans eq "uc") {
588            $ret = uc($ret);
589        } 
590        elsif ($trans eq "color") {
591            $ret = $vars->{"color-$attr"};
592        }
593        elsif ($trans eq "cons") {
594            $ret = $LJ::constant_map{$attr};
595        }
596        elsif ($trans eq "ad") {
597            $ret = "<LJAD $attr>";
598        }
599    }
600    return $ret;
601}
602
603sub eurl
604{
605    my $a = $_[0];
606    $a =~ s/([^a-zA-Z0-9_\,\-.\/\\\: ])/uc sprintf("%%%02x",ord($1))/eg;
607    $a =~ tr/ /+/;
608    return $a;
609}
610
611### escape stuff so it can be used in XML attributes or elements
612sub exml
613{
614    my $a = shift;
615    $a =~ s/\&/&amp;/g;
616    $a =~ s/\"/&quot;/g;
617    $a =~ s/\'/&apos;/g;
618    $a =~ s/</&lt;/g;
619    $a =~ s/>/&gt;/g;
620    return $a;
621}
622
623sub ehtml
624{
625    my $a = $_[0];
626    $a =~ s/\&/&amp;/g;
627    $a =~ s/\"/&quot;/g;
628    $a =~ s/</&lt;/g;
629    $a =~ s/>/&gt;/g;
630    return $a; 
631}
632
633# pass this a hashref, and it'll populate it.
634sub get_form_data
635{
636    my ($hashref) = shift;
637    my $buffer = shift;
638
639    if ($ENV{'REQUEST_METHOD'} eq 'POST') {
640        read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
641    } else {
642        $buffer = $ENV{'QUERY_STRING'} || $ENV{'REDIRECT_QUERY_STRING'};
643        if ($buffer eq "" && $ENV{'REQUEST_URI'} =~ /\?(.+)/) {
644            $buffer = $1;
645        }
646    }
647   
648    # Split the name-value pairs
649    my $pair;
650    my @pairs = split(/&/, $buffer);
651    my ($name, $value);
652    foreach $pair (@pairs)
653    {
654        ($name, $value) = split(/=/, $pair);
655        $value =~ tr/+/ /;
656        $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
657        $name =~ tr/+/ /;
658        $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
659        $hashref->{$name} .= $hashref->{$name} ? "\0$value" : $value;
660    }
661}
662
663### WTF is this?
664sub modify_time
665{
666    my $id = $_[0];
667    return if ($id =~ /[^a-z0-9\-\_]/);
668    return (stat("$DATADIR/bin/$id.mod"))[9];
669}
670
671sub bullet_errors
672{
673    my ($errorref) = @_;
674    my $ret = "(=BADCONTENT=)\n<UL>\n";
675    foreach (@{$errorref})
676    {
677        $ret .= "<LI>$_\n";
678    }
679    $ret .= "</UL>\n";
680    return $ret;
681}
682
683sub icq_send
684{
685    my ($uin, $msg) = @_;
686    if (length($msg) > 450) { $msg = substr($msg, 0, 447) . "..."; }
687    return unless ($uin eq "489151" || $uin eq "19639663");
688    my $time = time();
689    my $rand = "0000";
690    my $file;
691    $file = "$ICQSPOOL/$time.$rand";
692    while (-e $file) {
693        $rand = sprintf("%04d", int(rand()*10000));
694        $file = "$ICQSPOOL/$time.$rand";
695    }
696    open (FIL, ">$file");
697    print FIL "send $uin $msg";
698    close FIL;
699}
700
701sub create_password
702{
703    my @c = split(/ */, "bcdfghjklmnprstvwxyz");
704    my @v = split(/ */, "aeiou");
705    my $l = int(rand(2)) + 4;
706    my $password = "";
707    for(my $i = 1; $i <= $l; $i++)
708    {
709        $password .= "$c[int(rand(20))]$v[int(rand(5))]";
710    }
711    return $password;
712}
713
714sub age
715{
716    my ($age) = $_[0];   # seconds
717    my $sec = $age;
718    my $unit;
719    if ($age < 60)
720    {
721        $unit="sec";
722    }
723    elsif ($age < 3600)
724    {
725        $age = int($age/60);
726        $unit=" min";
727    }
728    elsif ($age < 3600*24)
729    {
730        $age = (int($age/3600));
731        $unit="hr";
732    }
733    else
734    {
735        $age = (int($age/(3600*24)));
736        $unit = "day";
737    }
738    if ($age != 1)
739    {
740        $unit .= "s";
741    }
742    return "$age $unit";
743}
744
745# XXX DEPRECATED
746sub strip_bad_code
747{
748    return &LJ::strip_bad_code(@_);
749}
750
751sub self_link
752{
753    my $newvars = shift;
754    my $link = $ENV{'REQUEST_URI'};
755    $link =~ s/\?.+//;
756    $link .= "?";
757    foreach (keys %$newvars) {
758        if (! exists $FORM{$_}) { $FORM{$_} = ""; }
759    }
760    foreach (sort keys %FORM) {
761        if (defined $newvars->{$_} && ! $newvars->{$_}) { next; }
762        my $val = $newvars->{$_} || $FORM{$_};
763        next unless $val;
764        $link .= &BMLUtil::eurl($_) . "=" . &BMLUtil::eurl($val) . "&";
765    }
766    chop $link;
767    return $link;
768}
769
770sub make_link
771{
772    my $url = shift;
773    my $vars = shift;
774    my $append = "?";
775    foreach (keys %$vars) {
776        next if ($vars->{$_} eq "");
777        $url .= "${append}${_}=$vars->{$_}";
778        $append = "&";
779    }
780    return $url;
781}
782
783
784#### UTILITY
785
786sub trim
787{
788    my $a = $_[0];
789    $a =~ s/^\s+//;
790    $a =~ s/\s+$//;
791    return $a; 
792}
793
794sub durl
795{
796    my ($a) = @_;
797    $a =~ tr/+/ /;
798    $a =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
799    return $a;
800}
801
802sub can_use_journal {
803    &connect_db();
804    return &LJ::can_use_journal($dbh, @_);
805}
806sub get_recent_itemids {
807    &connect_db();
808    return &LJ::get_recent_itemids($dbh, @_);
809}
810sub load_log_props {
811    &connect_db();
812    return &LJ::load_log_props($dbh, @_);
813}
814sub days_in_month {
815    return &LJ::days_in_month(@_);
816}
817
818sub html_select
819{
820    return LJ::html_select(@_);
821}
822
823sub html_datetime_decode
824{
825    my $opts = shift;
826    my $hash = shift;
827    my $name = $opts->{'name'};
828    return sprintf("%04d-%02d-%02d %02d:%02d:%02d",
829                   $hash->{"${name}_yyyy"},
830                   $hash->{"${name}_mm"},
831                   $hash->{"${name}_dd"},
832                   $hash->{"${name}_hh"},
833                   $hash->{"${name}_nn"},
834                   $hash->{"${name}_ss"});
835}
836
837sub html_datetime
838{
839    my $opts = shift;
840    my $lang = $opts->{'lang'} || "EN";
841    my ($yyyy, $mm, $dd, $hh, $nn, $ss);
842    my $ret;
843    my $name = $opts->{'name'};
844    my $disabled = $opts->{'disabled'} ? "DISABLED" : "";
845    if ($opts->{'default'} =~ /^(\d\d\d\d)-(\d\d)-(\d\d)(?: (\d\d):(\d\d):(\d\d))/) {
846        ($yyyy, $mm, $dd, $hh, $nn, $ss) = ($1 > 0 ? $1 : "",
847                                            $2+0,
848                                            $3 > 0 ? $3+0 : "",
849                                            $4 > 0 ? $4 : "",
850                                            $5 > 0 ? $5 : "",
851                                            $6 > 0 ? $6 : "");
852    }
853    $ret .= &html_select({ 'name' => "${name}_mm", 'selected' => $mm, 'disabled' => $opts->{'disabled'} },
854                         map { $_, &LJ::Lang::month_long($lang, $_) } (0..12));
855    $ret .= "<INPUT SIZE=2 MAXLENGTH=2 NAME=${name}_dd VALUE=\"$dd\" $disabled>, <INPUT SIZE=4 MAXLENGTH=4 NAME=${name}_yyyy VALUE=\"$yyyy\" $disabled>";
856    unless ($opts->{'notime'}) {
857        $ret.= " <INPUT SIZE=2 MAXLENGTH=2 NAME=${name}_hh VALUE=\"$hh\" $disabled>:<INPUT SIZE=2 MAXLENGTH=2 NAME=${name}_nn VALUE=\"$nn\" $disabled>";
858        if ($opts->{'seconds'}) {
859            $ret .= "<INPUT SIZE=2 MAXLENGTH=2 NAME=${name}_ss VALUE=\"$ss\" $disabled>";
860        }
861    }
862
863    return $ret;
864}
865
866sub get_query_string
867{
868    my $q = $ENV{'QUERY_STRING'} || $ENV{'REDIRECT_QUERY_STRING'};
869    if ($q eq "" && $ENV{'REQUEST_URI'} =~ /\?(.+)/) {
870        $q = $1;
871    }
872    return $q;
873}
874
875# this is here only for upwards compatability.  the good function to use is
876# LJ::get_dbh, which this function now calls.
877sub connect_db
878{
879    $dbh = ($BMLPersist::dbh = LJ::get_dbh("master"));
880}
881
882sub parse_vars
883{
884    return &LJ::parse_vars(@_);
885}
886
887sub load_user_theme
888{
889    &connect_db();
890    return &LJ::load_user_theme(@_);
891}
892
893package LJ;
894
895### hashref, arrayref
896sub load_userpics
897{
898    my ($dbh, $upics, $idlist) = @_;
899    my @load_list;
900    foreach my $id (@{$idlist})
901    {
902        if ($LJ::CACHE_USERPIC_SIZE{$id}) {
903            $upics->{$id}->{'width'} = $LJ::CACHE_USERPIC_SIZE{$id}->{'width'};
904            $upics->{$id}->{'height'} = $LJ::CACHE_USERPIC_SIZE{$id}->{'height'};
905        } elsif ($id+0) {
906            push @load_list, ($id+0);
907        }
908    }
909    return unless (@load_list);
910    my $picid_in = join(",", @load_list);
911    my $sth = $dbh->prepare("SELECT picid, width, height FROM userpic WHERE picid IN ($picid_in)");
912    $sth->execute;
913    while ($_ = $sth->fetchrow_hashref) {
914        my $id = $_->{'picid'};
915        undef $_->{'picid'};   
916        $upics->{$id} = $_;
917        $LJ::CACHE_USERPIC_SIZE{$id}->{'width'} = $_->{'width'};
918        $LJ::CACHE_USERPIC_SIZE{$id}->{'height'} = $_->{'height'};
919    }
920}
921
922sub send_mail
923{
924    my $opt = shift;
925    open (MAIL, "|$LJ::SENDMAIL");
926    my $toname;
927    if ($opt->{'toname'}) {
928        $toname = " ($opt->{'toname'})";
929    }
930    print MAIL "To: $opt->{'to'}$toname\n";
931    print MAIL "Cc: $opt->{'bcc'}\n" if ($opt->{'cc'});
932    print MAIL "Bcc: $opt->{'bcc'}\n" if ($opt->{'bcc'});
933    print MAIL "From: $opt->{'from'}";
934    if ($opt->{'fromname'}) {
935        print MAIL " ($opt->{'fromname'})";
936    }
937    print MAIL "\nSubject: $opt->{'subject'}\n\n";
938    print MAIL $opt->{'body'};
939    close MAIL;
940}
941
942sub strip_bad_code
943{
944    my $data = shift;
945    my $newdata;
946    use HTML::TokeParser;
947    $p = HTML::TokeParser->new($data);
948
949    while (my $token = $p->get_token)
950    {
951        my $type = $token->[0];
952        if ($type eq "S") {
953            if ($token->[1] eq "script") {
954                $p->unget_token($token);
955                $p->get_tag("/script");
956            } else {
957                my $tag = $token->[1];
958                my $hash = $token->[2];
959                delete $hash->{'onabort'};
960                delete $hash->{'onblur'};
961                delete $hash->{'onchange'};
962                delete $hash->{'onclick'};
963                delete $hash->{'onerror'};
964                delete $hash->{'onfocus'};
965                delete $hash->{'onload'};
966                delete $hash->{'onmouseout'};
967                delete $hash->{'onmouseover'};
968                delete $hash->{'onreset'};
969                delete $hash->{'onselect'};
970                delete $hash->{'onsubmit'};
971                delete $hash->{'onunload'};
972                if ($tag eq "a") {
973                    if ($hash->{'href'} =~ /^\s*javascript:/) { $hash->{'href'} = "about:"; }
974                } elsif ($tag eq "meta") {
975                    if ($hash->{'content'} =~ /javascript:/) { delete $hash->{'content'}; }
976                } elsif ($tag eq "img") {
977                    if ($hash->{'src'} =~ /javascript:/) { delete $hash->{'src'}; }
978                    if ($hash->{'dynsrc'} =~ /javascript:/) { delete $hash->{'dynsrc'}; }
979                    if ($hash->{'lowsrc'} =~ /javascript:/) { delete $hash->{'lowsrc'}; }
980                }
981                $newdata .= "<" . $tag;
982                foreach (keys %$hash) {
983                    $newdata .= " $_=\"$hash->{$_}\"";
984                }
985                $newdata .= ">";
986            }
987        }
988        elsif ($type eq "E") {
989            $newdata .= "</" . $token->[1] . ">";
990        }
991        elsif ($type eq "T" || $type eq "D") {
992            $newdata .= $token->[1];
993        }
994        elsif ($type eq "C") {
995            # ignore comments
996        }
997        elsif ($type eq "PI") {
998            $newdata .= "<?$token->[1]>";
999        }
1000        else {
1001            $newdata .= "<!-- OTHER: " . $type . "-->\n";
1002        }
1003    } # end while
1004    $$data = $newdata;
1005}
1006
1007#sub strip_bad_code
1008#{
1009#    my $data = shift;
1010#    require '/home/lj/cgi-bin/cleanhtml.pl';
1011#    &LJ::CleanHTML::clean($data, {
1012#       'mode' => 'allow',
1013#       'keepcomments' => 1,
1014#    });
1015#}
1016
1017%acct_name = ("paid" => "Paid Account",
1018              "off" => "Free Account",
1019              "early" => "Early Adopter",
1020              "on" => "Permanent Account");
1021
1022sub load_user_theme
1023{
1024    # hashref, hashref
1025    my ($dbh, $user, $u, $vars) = @_;
1026    my $sth;
1027    my $quser = $dbh->quote($user);
1028
1029    if ($u->{'_contesttheme'}) {
1030        my $qnum = $dbh->quote($u->{'_contesttheme'});
1031        $sth = $dbh->prepare("SELECT name AS 'coltype', value AS 'color' FROM contest1data WHERE contestid=$qnum");
1032    } elsif ($u->{'themeid'} == 0) {
1033        $sth = $dbh->prepare("SELECT coltype, color FROM themecustom WHERE user=$quser");
1034    } else {
1035        my $qtid = $dbh->quote($u->{'themeid'});
1036        $sth = $dbh->prepare("SELECT coltype, color FROM themedata WHERE themeid=$qtid");
1037    }
1038    $sth->execute;
1039    $vars->{"color-$_->{'coltype'}"} = $_->{'color'} while ($_ = $sth->fetchrow_hashref);
1040}
1041
1042sub parse_vars
1043{
1044    my ($dataref, $hashref) = @_;
1045    my @data = split(/\n/, $$dataref);
1046    my $curitem = "";
1047   
1048    foreach (@data)
1049    {
1050        $_ .= "\n";
1051        s/\r//g;
1052        if ($curitem eq "" && /^([A-Z0-9\_]+)=>([^\n\r]*)/)
1053        {
1054            $hashref->{$1} = $2;
1055        }
1056        elsif ($curitem eq "" && /^([A-Z0-9\_]+)<=\s*$/)
1057        {
1058            $curitem = $1;
1059            $hashref->{$curitem} = "";
1060        }
1061        elsif ($curitem && /^<=$curitem\s*$/)
1062        {
1063            chop $hashref->{$curitem};  # remove the false newline
1064            $curitem = "";
1065        }
1066        else
1067        {
1068            $hashref->{$curitem} .= $_ if ($curitem =~ /\S/);
1069        }
1070    }
1071}
1072
1073sub server_down_html
1074{
1075    return "<B>$LJ::SERVER_DOWN_SUBJECT</B><BR>$LJ::SERVER_DOWN_MESSAGE";
1076}
1077
1078##
1079## loads a style and takes into account caching (don't reload a system style
1080## until 60 seconds)
1081##
1082sub load_style_fast
1083{
1084    ### styleid -- numeric, primary key
1085    ### dataref -- pointer where to store data
1086    ### typeref -- optional pointer where to store style type (undef for none)
1087    ### nocache -- flag to say don't cache
1088
1089    my ($dbh, $styleid, $dataref, $typeref, $nocache) = @_;
1090    $styleid += 0;
1091    my $now = time();
1092   
1093    if ((defined $LJ::CACHE_STYLE{$styleid}) &&
1094        ($LJ::CACHE_STYLE{$styleid}->{'lastpull'} > ($now-300)) &&
1095        (! $nocache)
1096        )
1097    {
1098        $$dataref = $LJ::CACHE_STYLE{$styleid}->{'data'};
1099        if (ref $typeref eq "SCALAR") { $$typeref = $LJ::CACHE_STYLE{$styleid}->{'type'}; }
1100    }
1101    else
1102    {
1103        $sth = $dbh->prepare("SELECT formatdata, type, opt_cache FROM style WHERE styleid=$styleid");
1104        $sth->execute;
1105        my ($data, $type, $cache) = $sth->fetchrow_array;
1106        if ($cache eq "Y") {
1107            $LJ::CACHE_STYLE{$styleid} = { 'lastpull' => $now,
1108                                       'data' => $data,
1109                                       'type' => $type,
1110                                   };
1111        }
1112        $$dataref = $data;
1113        if (ref $typeref eq "SCALAR") { $$typeref = $type; }
1114    }
1115}
1116
1117sub make_journal
1118{
1119    my ($dbh, $user, $view, $remote, $opts) = @_;
1120
1121    if ($LJ::SERVER_DOWN) {
1122        if ($opts->{'vhost'} eq "customview") {
1123            return "<!-- LJ down for maintenance -->";
1124        }
1125        return &server_down_html();
1126    }
1127   
1128    my ($styleid);
1129    if ($opts->{'styleid'}) {
1130        $styleid = $opts->{'styleid'}+0;
1131    } else {
1132        $view ||= "lastn";    # default view when none specified explicitly in URLs
1133        if ($LJ::viewinfo{$view})  {
1134            $styleid = -1;    # to get past the return, then checked later for -1 and fixed, once user is loaded.
1135            $view = $view;
1136        } else {
1137            $opts->{'badargs'} = 1;
1138        }
1139    }
1140    return "" unless ($styleid);
1141
1142    my $quser = $dbh->quote($user);
1143    my $u;
1144    if ($opts->{'u'}) {
1145        $u = $opts->{'u'};
1146    } else {
1147        $sth = $dbh->prepare("SELECT * FROM user WHERE user=$quser");
1148        $sth->execute;
1149        $u = $sth->fetchrow_hashref;
1150    }
1151
1152    unless ($u)
1153    {
1154        $opts->{'baduser'} = 1;
1155        return "<H1>Error</H1>No such user <B>$user</B>";
1156    }
1157
1158    if ($styleid == -1) {
1159        $styleid = $u->{"${view}_style"};
1160    }
1161
1162    ## temporary, for contest1 themes
1163    $u->{'_contesttheme'} = $opts->{'contesttheme'};
1164
1165    if ($LJ::USER_VHOSTS && $opts->{'vhost'} eq "users" && $u->{'paidfeatures'} eq "off")
1166    {
1167        return "<B>Notice</B><BR>Addresses like <TT>http://<I>username</I>.$LJ::USER_DOMAIN</TT> only work for users with <A HREF=\"$LJ::SITEROOT/paidaccounts/\">paid accounts</A>.  The journal you're trying to view is available here:<UL><FONT FACE=\"Verdana,Arial\"><B><A HREF=\"$LJ::SITEROOT/users/$user/\">$LJ::SITEROOT/users/$user/</A></B></FONT></UL>";
1168    }
1169    if ($opts->{'vhost'} eq "customview" && $u->{'paidfeatures'} eq "off")
1170    {
1171        return "<B>Notice</B><BR>Only users with <A HREF=\"$LJ::SITEROOT/paidaccounts/\">paid accounts</A> can create and embed styles.";
1172    }
1173    if ($opts->{'vhost'} eq "community" && $u->{'journaltype'} ne "C") {
1174        return "<B>Notice</B><BR>This account isn't a community journal.";
1175    }
1176
1177    return "<H1>Error</H1>Journal has been deleted.  If you are <B>$user</B>, you have a period of 30 days to decide to undelete your journal." if ($u->{'statusvis'} eq "D");
1178    return "<H1>Error</H1>This journal has been suspended." if ($u->{'statusvis'} eq "S");
1179
1180    my %vars = ();
1181    # load the base style
1182    my $basevars = "";
1183    &load_style_fast($dbh, $styleid, \$basevars, \$view);
1184
1185    # load the overrides
1186    my $overrides = "";
1187    if ($opts->{'nooverride'}==0 && $u->{'useoverrides'} eq "Y")
1188    {
1189        $sth = $dbh->prepare("SELECT override FROM overrides WHERE user=$quser");
1190        $sth->execute;
1191        ($overrides) = $sth->fetchrow_array;
1192    }
1193
1194    # populate the variable hash
1195    &parse_vars(\$basevars, \%vars);
1196    &parse_vars(\$overrides, \%vars);
1197    &load_user_theme($dbh, $user, $u, \%vars);
1198   
1199    # kinda free some memory
1200    $basevars = "";
1201    $overrides = "";
1202
1203    # instruct some function to make this specific view type
1204    return "" unless (defined $LJ::viewinfo{$view}->{'creator'});
1205    my $ret = "";
1206
1207    # call the view creator w/ the buffer to fill and the construction variables
1208    &{$LJ::viewinfo{$view}->{'creator'}}(\$ret, $u, \%vars, $remote, $opts);
1209
1210    # remove bad stuff
1211    unless ($opts->{'trusted_html'}) {
1212        &strip_bad_code(\$ret);
1213    }
1214
1215    # return it...
1216    return $ret;   
1217}
1218
1219
1220sub html_select
1221{
1222    my $opts = shift;
1223    my @items = @_;
1224    my $disabled = $opts->{'disabled'} ? " DISABLED" : "";
1225    my $ret;
1226    $ret .= "<select";
1227    if ($opts->{'name'}) { $ret .= " name=\"$opts->{'name'}\""; }
1228    $ret .= "$disabled>";
1229    while (my ($value, $text) = splice(@items, 0, 2)) {
1230        my $sel = "";
1231        if ($value eq $opts->{'selected'}) { $sel = " selected"; }
1232        $ret .= "<option value=\"$value\"$sel>$text";
1233    }
1234    $ret .= "</select>";
1235    return $ret;
1236}
1237
1238sub html_check
1239{
1240    my $opts = shift;
1241
1242    my $disabled = $opts->{'disabled'} ? " DISABLED" : "";
1243    my $ret;
1244    $ret .= "<input type=checkbox ";
1245    if ($opts->{'selected'}) { $ret .= " checked"; }
1246    if ($opts->{'name'}) { $ret .= " name=\"$opts->{'name'}\""; }
1247    if ($opts->{'value'}) { $ret .= " value=\"$opts->{'value'}\""; }
1248    $ret .= "$disabled>";
1249    return $ret;
1250}
1251
1252sub html_text
1253{
1254    my $opts = shift;
1255
1256    my $disabled = $opts->{'disabled'} ? " DISABLED" : "";
1257    my $ret;
1258    $ret .= "<input type=text";
1259    if ($opts->{'size'}) { $ret .= " size=\"$opts->{'size'}\""; }
1260    if ($opts->{'maxlength'}) { $ret .= " maxlength=\"$opts->{'maxlength'}\""; }
1261    if ($opts->{'name'}) { $ret .= " name=\"" . &ehtml($opts->{'name'}) . "\""; }
1262    if ($opts->{'value'}) { $ret .= " value=\"" . &ehtml($opts->{'value'}) . "\""; }
1263    $ret .= "$disabled>";
1264    return $ret;
1265}
1266
1267#
1268# returns the canonical username given, or blank if the username is not well-formed
1269#
1270sub canonical_username
1271{
1272    my $user = shift;
1273    if ($user =~ /^[\w\-]{1,15}$/) {
1274        $user = lc($user);
1275        $user =~ s/-/_/g;
1276        return $user;
1277    }
1278    return "";  # not a good username.
1279}
1280
1281sub decode_url_string
1282{
1283    my $buffer = shift;   # input scalarref
1284    my $hashref = shift;  # output hash
1285
1286    my $pair;
1287    my @pairs = split(/&/, $$buffer);
1288    my ($name, $value);
1289    foreach $pair (@pairs)
1290    {
1291        ($name, $value) = split(/=/, $pair);
1292        $value =~ tr/+/ /;
1293        $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
1294        $name =~ tr/+/ /;
1295        $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
1296        $hashref->{$name} .= $hashref->{$name} ? "\0$value" : $value;
1297    }
1298}
1299
1300# called by nearly all the other functions
1301sub get_dbh
1302{
1303    my $type = shift;  # 'master' or 'slave'
1304    my $dbh;
1305
1306    ## already have a dbh of this type open?
1307    if (ref $LJ::DBCACHE{$type}) {
1308        $dbh = $LJ::DBCACHE{$type};
1309
1310        # make sure connection is still good.
1311        my $sth = $dbh->prepare("SELECT CONNECTION_ID()");  # mysql specific
1312        $sth->execute;
1313        my ($id) = $sth->fetchrow_array;
1314        if ($id) { return $dbh; }
1315        undef $dbh;
1316        undef $LJ::DBCACHE{$type};
1317    }
1318
1319    ### if we don't have a dbh cached already, which one would we try to connect to?
1320    my $key;
1321    if ($type eq "slave") {
1322        my $ct = $LJ::DBINFO{'slavecount'};
1323        if ($ct) {
1324            $key = "slave" . int(rand($ct)+1);
1325        } else {
1326            $key = "master";
1327        }
1328    } else {
1329        $key = "master";
1330    }
1331
1332    $dbh = DBI->connect("DBI:mysql:livejournal:$LJ::DBINFO{$key}->{'host'}",
1333                        $LJ::DBINFO{$key}->{'user'},
1334                        $LJ::DBINFO{$key}->{'pass'},
1335                        {
1336                            PrintError => 0,
1337                        });
1338                       
1339    # save a reference to the database handle for later
1340    $LJ::DBCACHE{$type} = $dbh;
1341
1342    return $dbh;
1343}
1344 
1345## turns a date (yyyy-mm-dd) into links to year calendar, month view, and day view, given
1346## also a user object (hashref)
1347sub date_to_view_links
1348{
1349    my ($u, $date) = @_;
1350   
1351    return unless ($date =~ /(\d\d\d\d)-(\d\d)-(\d\d)/);
1352    my ($y, $m, $d) = ($1, $2, $3);
1353    my ($nm, $nd) = ($m+0, $d+0);   # numeric, without leading zeros
1354    my $user = $u->{'user'};
1355
1356    my $ret;
1357    $ret .= "<a href=\"$LJ::SITEROOT/users/$user/calendar/$y\">$y</a>-";
1358    $ret .= "<a href=\"$LJ::SITEROOT/view/?type=month&user=$user&y=$y&m=$nm\">$m</a>-";
1359    $ret .= "<a href=\"$LJ::SITEROOT/users/$user/day/$y/$m/$d\">$d</a>";
1360    return $ret;
1361}
1362
1363sub item_link
1364{
1365    my ($u, $itemid) = @_;
1366    return "$LJ::SITEROOT/talkread.bml?itemid=$itemid";
1367}
1368
1369sub make_graphviz_dot_file
1370{
1371    my $dbh = shift;
1372    my $user = shift;
1373
1374    my $quser = $dbh->quote($user);
1375    my $sth;
1376    my $ret;
1377 
1378    $sth = $dbh->prepare("SELECT *, UNIX_TIMESTAMP()-UNIX_TIMESTAMP(timeupdate) AS 'secondsold' FROM user WHERE user=$quser");
1379    $sth->execute;
1380    my $u = $sth->fetchrow_hashref;
1381   
1382    unless ($u) {
1383        return "";     
1384    }
1385   
1386    $ret .= "digraph G {\n";
1387    $ret .= "  node [URL=\"$LJ::SITEROOT/userinfo.bml?user=\\N\"]\n";
1388    $ret .= "  node [fontsize=10, color=lightgray, style=filled]\n";
1389    $ret .= "  \"$user\" [color=yellow, style=filled]\n";
1390   
1391    my @friends = ();
1392    $sth = $dbh->prepare("SELECT friendid FROM friends WHERE userid=$u->{'userid'} AND userid<>friendid");
1393    $sth->execute;
1394    while ($_ = $sth->fetchrow_hashref) {
1395        push @friends, $_->{'friendid'};
1396    }
1397   
1398    my $friendsin = join(", ", map { $dbh->quote($_); } ($u->{'userid'}, @friends));
1399    my $sql = "SELECT uu.user, uf.user AS 'friend' FROM friends f, user uu, user uf WHERE f.userid=uu.userid AND f.friendid=uf.userid AND f.userid<>f.friendid AND uu.statusvis='V' AND uf.statusvis='V' AND (f.friendid=$u->{'userid'} OR (f.userid IN ($friendsin) AND f.friendid IN ($friendsin)))";
1400    $sth = $dbh->prepare($sql);
1401    $sth->execute;
1402    while ($_ = $sth->fetchrow_hashref) {
1403        $ret .= "  \"$_->{'user'}\"->\"$_->{'friend'}\"\n";
1404        $mark{$_->{'user'}}++;
1405        $mark{$_->{'friend'}}++;
1406    }
1407   
1408    $ret .= "}\n";
1409   
1410    return $ret;
1411}
1412
1413sub expand_embedded
1414{
1415    my $dbh = shift;
1416    my $itemid = shift;
1417    my $remote = shift;
1418    my $eventref = shift;
1419
1420    &LJ::Poll::show_polls($dbh, $itemid, $remote, $eventref);
1421}
1422
1423sub make_remote
1424{
1425    my $user = shift;
1426    my $userid = shift;
1427    if ($userid && $userid =~ /^\d+$/) {
1428        return { 'user' => $user,
1429                 'userid' => $userid, };
1430    }
1431    return undef;
1432}
1433
1434sub escapeall
1435{
1436    my $a = $_[0];
1437
1438    ### escape HTML
1439    $a =~ s/\&/&amp;/g;
1440    $a =~ s/\"/&quot;/g;
1441    $a =~ s/</&lt;/g;
1442    $a =~ s/>/&gt;/g;
1443
1444    ### and escape BML
1445    $a =~ s/\(=/\(&#0061;/g;
1446    $a =~ s/=\)/&#0061;\)/g;
1447    return $a;
1448}
1449
1450sub load_user
1451{
1452    my $dbh = shift;
1453    my $user = shift;
1454    my $quser = $dbh->quote($user);
1455    my $sth = $dbh->prepare("SELECT * FROM user WHERE user=$quser");
1456    $sth->execute;
1457    my $u = $sth->fetchrow_hashref;
1458    $sth->finish;
1459    return $u;
1460}
1461
1462sub load_moods
1463{
1464    return if ($LJ::CACHED_MOODS);
1465    my $dbh = shift;
1466    my $sth = $dbh->prepare("SELECT moodid, mood, parentmood FROM moods");
1467    $sth->execute;
1468    while (my ($id, $mood, $parent) = $sth->fetchrow_array) {
1469        $LJ::CACHE_MOODS{$id} = { 'name' => $mood, 'parent' => $parent };
1470        if ($id > $LJ::CACHED_MOOD_MAX) { $LJ::CACHED_MOOD_MAX = $id; }
1471    }
1472    $LJ::CACHED_MOODS = 1;
1473}
1474
1475sub query_buffer_add
1476{
1477    my ($dbh, $table, $query) = @_;
1478   
1479    if ($LJ::BUFFER_QUERIES)
1480    {
1481        # if this is a high load site, you'll want to batch queries up and send them at once.
1482
1483        my $table = $dbh->quote($table);
1484        my $query = $dbh->quote($query);
1485        $dbh->do("INSERT INTO querybuffer (qbid, tablename, instime, query) VALUES (NULL, $table, NOW(), $query)");
1486    }
1487    else
1488    {
1489        # low load sites can skip this, and just have queries go through immediately.
1490
1491        $dbh->do($query);
1492    }
1493}
1494
1495sub query_buffer_flush
1496{
1497    my ($dbh, $table) = @_;
1498    return -1 unless ($table);
1499    return -1 if ($table =~ /[^\w]/);
1500   
1501    $dbh->do("LOCK TABLES $table WRITE, querybuffer WRITE");
1502   
1503    my $count = 0;
1504    my $max = 0;
1505    my $qtable = $dbh->quote($table);
1506    $sth = $dbh->prepare("SELECT qbid, query FROM querybuffer WHERE tablename=$qtable ORDER BY qbid");
1507    if ($dbh->err) { $dbh->do("UNLOCK TABLES"); die $dbh->errstr; }
1508    $sth->execute;
1509    if ($dbh->err) { $dbh->do("UNLOCK TABLES"); die $dbh->errstr; }     
1510    while (my ($id, $query) = $sth->fetchrow_array)
1511    {
1512        $dbh->do($query);
1513        $count++;
1514        $max = $id;
1515    }
1516    $sth->finish;
1517   
1518    $dbh->do("DELETE FROM querybuffer WHERE tablename=$qtable");
1519    if ($dbh->err) { $dbh->do("UNLOCK TABLES"); die $dbh->errstr; }             
1520   
1521    $dbh->do("UNLOCK TABLES");
1522    return $count;
1523}
1524
1525sub journal_base
1526{
1527    my ($user, $vhost) = @_;
1528    if ($vhost eq "users") {
1529        my $he_user = $user;
1530        $he_user =~ s/_/-/g;
1531        return "http://$he_user.$LJ::USER_DOMAIN";
1532    } elsif ($vhost eq "tilde") {
1533        return "$LJ::SITEROOT/~$user";
1534    } elsif ($vhost eq "community") {
1535        return "$LJ::SITEROOT/community/$user";
1536    } else {
1537        return "$LJ::SITEROOT/users/$user";
1538    }
1539}
1540
1541# check to see if the given remote user has a certain privledge
1542sub remote_has_priv
1543{
1544    my $dbh = shift;
1545    my $remote = shift;
1546    my $privcode = shift;     # required.  priv code to check for.
1547    my $ref = shift;  # optional, arrayref or hashref to populate
1548
1549    return 0 unless ($remote);
1550
1551    ### authentication done.  time to authorize...
1552
1553    my $qprivcode = $dbh->quote($privcode);
1554    my $sth = $dbh->prepare("SELECT pm.arg FROM priv_map pm, priv_list pl WHERE pm.prlid=pl.prlid AND pl.privcode=$qprivcode AND pm.userid=$remote->{'userid'}");
1555    $sth->execute;
1556   
1557    my $match = 0;
1558    if (ref $ref eq "ARRAY") { @$ref = (); }
1559    if (ref $ref eq "HASH") { %$ref = (); }
1560    while ($_ = $sth->fetchrow_hashref) {
1561        $match++;
1562        if (ref $ref eq "ARRAY") { push @$ref, $_->{'arg'}; }
1563        if (ref $ref eq "HASH") { $ref->{$_->{'arg'}} = 1; }
1564    }
1565    return $match;
1566}
1567
1568## get a userid from a username (returns 0 if invalid user)
1569sub get_userid
1570{
1571    my $dbh = shift;
1572    my $user = shift;
1573    my $userid;
1574    if ($CACHE_USERID{$user}) { return $CACHE_USERID{$user}; }
1575
1576    my $quser = $dbh->quote($user);
1577    my $sth = $dbh->prepare("SELECT userid FROM user WHERE user=$quser");
1578    $sth->execute;
1579    ($userid) = $sth->fetchrow_array;
1580    if ($userid) { $CACHE_USERID{$user} = $userid; }
1581    return ($userid+0);
1582}
1583
1584## get a username from a userid (returns undef if invalid user)
1585sub get_username
1586{
1587    my $dbh = shift;
1588    my $userid = shift;
1589    my $user;
1590    $userid += 0;
1591    if ($CACHE_USERNAME{$userid}) { return $CACHE_USERNAME{$userid}; }
1592   
1593    my $sth = $dbh->prepare("SELECT user FROM user WHERE userid=$userid");
1594    $sth->execute;
1595    ($user) = $sth->fetchrow_array;
1596    if ($user) { $CACHE_USERNAME{$userid} = $user; }
1597    return ($user);
1598}
1599
1600sub get_itemid_near
1601{
1602    my $dbh = shift;
1603    my $ownerid = shift;
1604    my $date = shift;
1605    my $after_before = shift;
1606    return 0 unless ($date =~ /^(\d{4})-(\d{2})-\d{2} \d{2}:\d{2}:\d{2}$/);
1607    my ($year, $month) = ($1, $2);
1608
1609    my ($op, $inc, $func);
1610    if ($after_before eq "after") {
1611        ($op, $inc, $func) = (">",  1, "MIN");
1612    } elsif ($after_before eq "before") {
1613        ($op, $inc, $func) = ("<", -1, "MAX");
1614    } else {
1615        return 0;
1616    }
1617
1618    my $qeventtime = $dbh->quote($date);
1619
1620    my $item = 0;
1621    my $tries = 0;
1622    while ($item==0 && $tries<2)
1623    {
1624        my $sql = "SELECT $func(itemid) FROM log WHERE ownerid=$ownerid AND year=$year AND month=$month AND eventtime $op $qeventtime";
1625        my $sth = $dbh->prepare($sql);
1626        $sth->execute;
1627        ($item) = $sth->fetchrow_array;
1628
1629        unless ($item) {
1630            $tries++;
1631            $month += $inc;
1632            if ($month == 13) { $month = 1;  $year++; }
1633            if ($month == 0)  { $month = 12; $year--; }
1634        }
1635    }
1636    return ($item+0);
1637}
1638
1639sub get_itemid_after  { return get_itemid_near(@_, "after");  }
1640sub get_itemid_before { return get_itemid_near(@_, "before"); }
1641
1642sub mysql_time
1643{
1644    my $time = shift;
1645    $time ||= time();
1646    my @ltime = localtime($time);
1647    return sprintf("%04d-%02d-%02d %02d:%02d:%02d",
1648                   $ltime[5]+1900,
1649                   $ltime[4]+1,
1650                   $ltime[3],
1651                   $ltime[2],
1652                   $ltime[1],
1653                   $ltime[0]);
1654}
1655
1656sub get_keyword_id
1657{
1658    my $dbh = shift;
1659    my $kw = shift;
1660    unless ($kw =~ /\S/) { return 0; }
1661    my $qkw = $dbh->quote($kw);
1662
1663    my $sth = $dbh->prepare("SELECT kwid FROM keywords WHERE keyword=$qkw");
1664    $sth->execute;
1665    my ($kwid) = $sth->fetchrow_array;
1666    unless ($kwid) {
1667        $sth = $dbh->prepare("INSERT INTO keywords (kwid, keyword) VALUES (NULL, $qkw)");
1668        $sth->execute;
1669        $kwid = $dbh->{'mysql_insertid'};
1670    }
1671    return $kwid;
1672}
1673
1674sub trim
1675{
1676    my $a = $_[0];
1677    $a =~ s/^\s+//;
1678    $a =~ s/\s+$//;
1679    return $a; 
1680}
1681
1682# returns true if $formref->{'password'} matches cleartext password or if
1683# $formref->{'hpassword'} is the hash of the cleartext password
1684sub valid_password
1685{
1686    my ($clearpass, $formref) = @_;
1687    if ($formref->{'password'} && $formref->{'password'} eq $clearpass)
1688    {
1689        return 1;
1690    }
1691    if ($formref->{'hpassword'} && lc($formref->{'hpassword'}) eq &hash_password($clearpass))
1692    {
1693        return 1;
1694    }
1695    return 0;   
1696}
1697
1698sub delete_user
1699{
1700    my $dbh = shift;
1701    my $user = shift;
1702    my $quser = $dbh->quote($user);
1703    my $sth;
1704    $sth = $dbh->prepare("SELECT user, userid FROM user WHERE user=$quser");
1705    my $u = $sth->fetchrow_hashref;
1706    unless ($u) { return; }
1707   
1708    ### so many issues.     
1709}
1710
1711sub hash_password
1712{
1713    return Digest::MD5::md5_hex($_[0]);
1714}
1715
1716sub can_use_journal
1717{
1718    my ($dbh, $posterid, $reqownername, $res) = @_;
1719    my $qreqownername = $dbh->quote($reqownername);
1720    my $qposterid = $posterid+0;
1721
1722    ## find the journal owner's userid
1723    my $sth = $dbh->prepare("SELECT userid FROM user WHERE user=$qreqownername");
1724    $sth->execute;
1725    my ($ownerid) = $sth->fetchrow_array;
1726    unless ($ownerid) {
1727        $res->{'errmsg'} = "User \"$reqownername\" does not exist.";
1728        return 0;
1729    }
1730   
1731    ## check if user has access
1732    $sth = $dbh->prepare("SELECT COUNT(*) AS 'count' FROM logaccess WHERE ownerid=$ownerid AND posterid=$qposterid");
1733
1734    $sth->execute;
1735    my $row = $sth->fetchrow_hashref;
1736    if ($row && $row->{'count'}==1) {
1737        $res->{'ownerid'} = $ownerid;
1738        return 1;
1739    } else {
1740        $res->{'errmsg'} = "You do not have access to post to this journal.";
1741        return 0;
1742    }
1743}
1744
1745## internal function to most efficiently retrieve the last 'n' items
1746## for either the lastn or friends view
1747sub get_recent_itemids
1748{
1749    my $dbh = shift;
1750    my ($opts) = shift;
1751
1752    my @itemids = ();
1753    my $userid = $opts->{'userid'}+0;
1754    my $view = $opts->{'view'};
1755    my $remid = $opts->{'remoteid'}+0;
1756
1757    my $max_hints = 0;
1758    my $sort_key = "eventtime";
1759    if ($view eq "lastn") { $max_hints = $LJ::MAX_HINTS_LASTN; }
1760    if ($view eq "friends") {
1761        $max_hints = $LJ::MAX_HINTS_FRIENDS;
1762        $sort_key = "logtime";
1763    }
1764    unless ($max_hints) { return @itemids; }
1765
1766    my $skip = $opts->{'skip'}+0;
1767    my $itemshow = $opts->{'itemshow'}+0;
1768    if ($itemshow > $max_hints) { $itemshow = $max_hints; }
1769    my $maxskip = $max_hints - $itemshow;
1770    if ($skip < 0) { $skip = 0; }
1771    if ($skip > $maxskip) { $skip = $maxskip; }
1772    my $itemload = $itemshow+$skip;
1773   
1774    ### get all the known hints, right off the bat.
1775
1776    $sth = $dbh->prepare("SELECT hintid, itemid FROM hint${view}view WHERE userid=$userid");
1777    $sth->execute;
1778    my %iteminf;
1779    my $numhints = 0;
1780    while ($_ = $sth->fetchrow_arrayref) {
1781        $numhints++;
1782        $iteminf{$_->[1]} = { 'hintid' => $_->[0] };
1783    }
1784    if ($numhints > $max_hints * 4) {
1785        my @extra = sort { $b->{'hintid'} <=> $a->{'hintid'} } values %iteminf;
1786        my $minextra = $extra[$max_hints]->{'hintid'};
1787        $dbh->do("DELETE FROM hint${view}view WHERE userid=$userid AND hintid<=$minextra");
1788        foreach my $itemid (keys %iteminf) {
1789            if ($iteminf{$itemid}->{'hintid'} <= $minextra) {
1790                delete $iteminf{$itemid};
1791            }
1792        }
1793       
1794    }
1795
1796    if (%iteminf)
1797    {
1798        my %gmask_from;  # group mask of remote user from context of userid in key
1799        my $itemid_in = join(",", keys %iteminf);
1800
1801        if ($remid) {
1802            if ($view eq "lastn")
1803            {
1804                ## then we need to load the group mask for this friend
1805                $sth = $dbh->prepare("SELECT groupmask FROM friends WHERE userid=$userid AND friendid=$remid");
1806                $sth->execute;
1807                my ($mask) = $sth->fetchrow_array;
1808                $gmask_from{$userid} = $mask;
1809            }
1810        }
1811
1812        $sth = $dbh->prepare("SELECT itemid, security, allowmask, $sort_key FROM log WHERE itemid IN ($itemid_in)");
1813        $sth->execute;
1814        while (my $li = $sth->fetchrow_hashref)
1815        {
1816            my $this_ownerid = $li->{'ownerid'} || $userid;
1817           
1818            if ($li->{'security'} eq "public" ||
1819                ($li->{'security'} eq "usemask" &&
1820                 (($li->{'allowmask'} + 0) & $gmask_from{$this_ownerid})) ||
1821                ($remid && $this_ownerid == $remid))
1822            {
1823                push @itemids, { 'hintid' => $iteminf{$li->{'itemid'}}->{'hintid'},
1824                                 'itemid' => $li->{'itemid'},
1825                                 'ownerid' => $this_ownerid,
1826                                 $sort_key => $li->{$sort_key},
1827                             };
1828            }
1829        }
1830    }
1831   
1832    %iteminf = ();  # free some memory (like perl would care!)
1833
1834    @itemids = sort { $b->{$sort_key} cmp $a->{$sort_key} } @itemids;
1835   
1836    my $hintcount = scalar(@itemids);
1837
1838    if ($hintcount >= $itemload)
1839    {
1840        # we can delete some items from the hints table.
1841        if ($hintcount > $max_hints) {
1842            my @remove = splice (@itemids, $max_hints, ($hintcount-$max_hints));
1843            $hintcount = scalar(@itemids);
1844            if (@remove) {
1845                my $sql = "REPLACE INTO batchdelete (what, itsid) VALUES ";
1846                $sql .= join(",", map { "('hint${view}', $_->{'hintid'})" } @remove);
1847                $dbh->do($sql);
1848
1849                # my $removein = join(",", map { $_->{'hintid'} } @remove);
1850                # $dbh->do("DELETE FROM hint${view}view WHERE hintid IN ($removein)");
1851            }
1852        }
1853    }
1854    elsif (! $opts->{'dont_add_hints'})
1855    {
1856        ## this hints table was too small.  populate it again.
1857
1858        #print "Not enough in hint table!  hintcount ($hintcount) < itemload ($itemload)\n";
1859
1860        if ($view eq "lastn")
1861        {
1862            my $sql = "
1863REPLACE INTO hintlastnview (hintid, userid, itemid)
1864SELECT NULL, $userid, l.itemid
1865FROM log l
1866WHERE l.ownerid=$userid
1867ORDER BY l.eventtime DESC, l.logtime DESC
1868LIMIT $max_hints
1869";
1870
1871            # FUCK IT!  This kills MySQL!  Maybe later.
1872            # $dbh->do($sql);
1873        }
1874
1875        ## call ourselves recursively, now that we've populated the hints table
1876        ## however, we set this flag so we don't recurse again.  this may be true
1877        ## for new journals that don't yet have $max_hints entries in them
1878
1879        $opts->{'dont_add_hints'} = 1;
1880        return &get_recent_itemids($dbh, $opts);
1881    }
1882
1883    ### remove the ones we're skipping
1884    if ($skip) {
1885        splice (@itemids, 0, $skip);
1886    }
1887    if (@itemids > $itemshow) {
1888        splice (@itemids, $itemshow, (scalar(@itemids)-$itemshow));
1889    }
1890
1891    ## change the list of hashrefs to a list of integers (don't need other info now)
1892    if (ref $opts->{'owners'} eq "HASH") {
1893        grep { $opts->{'owners'}->{$_->{'ownerid'}}++ } @itemids;
1894    }
1895
1896    @itemids = map { $_->{'itemid'} } @itemids;
1897    return @itemids;
1898}
1899
1900sub load_log_props
1901{
1902    my ($dbh, $listref, $hashref) = @_;
1903    my $itemin = join(", ", map { $_+0; } @{$listref});
1904    unless ($itemin) { return ; }
1905    unless (ref $hashref eq "HASH") { return; }
1906   
1907    my $sth = $dbh->prepare("SELECT p.itemid, l.name, p.value FROM logprop p, logproplist l WHERE p.propid=l.propid AND p.itemid IN ($itemin)");
1908    $sth->execute;
1909    while ($_ = $sth->fetchrow_hashref) {
1910        $hashref->{$_->{'itemid'}}->{$_->{'name'}} = $_->{'value'};
1911    }
1912    $sth->finish;
1913}
1914
1915sub load_talk_props
1916{
1917    my ($dbh, $listref, $hashref) = @_;
1918    my $itemin = join(", ", map { $_+0; } @{$listref});
1919    unless ($itemin) { return ; }
1920    unless (ref $hashref eq "HASH") { return; }
1921   
1922    my $sth = $dbh->prepare("SELECT tp.talkid, tpl.name, tp.value FROM talkproplist tpl, talkprop tp WHERE tp.tpropid=tpl.tpropid AND tp.talkid IN ($itemin)");
1923    $sth->execute;
1924    while (my ($id, $name, $val) = $sth->fetchrow_array) {
1925        $hashref->{$id}->{$name} = $val;
1926    }
1927    $sth->finish;
1928}
1929
1930
1931sub eurl
1932{
1933    my $a = $_[0];
1934    $a =~ s/([^a-zA-Z0-9_\,\-.\/\\\: ])/uc sprintf("%%%02x",ord($1))/eg;
1935    $a =~ tr/ /+/;
1936    return $a;
1937}
1938
1939### escape stuff so it can be used in XML attributes or elements
1940sub exml
1941{
1942    my $a = shift;
1943    $a =~ s/\&/&amp;/g;
1944    $a =~ s/\"/&quot;/g;
1945    $a =~ s/\'/&apos;/g;
1946    $a =~ s/</&lt;/g;
1947    $a =~ s/>/&gt;/g;
1948    return $a;
1949}
1950
1951sub ehtml
1952{
1953    my $a = $_[0];
1954    $a =~ s/\&/&amp;/g;
1955    $a =~ s/\"/&quot;/g;
1956    $a =~ s/</&lt;/g;
1957    $a =~ s/>/&gt;/g;
1958    return $a; 
1959}
1960
1961sub days_in_month
1962{
1963    my ($month, $year) = @_;
1964    if ($month == 2)
1965    {
1966        if ($year % 4 == 0)
1967        {
1968          # years divisible by 400 are leap years
1969          return 29 if ($year % 400 == 0);
1970
1971          # if they're divisible by 100, they aren't.
1972          return 28 if ($year % 100 == 0);
1973
1974          # otherwise, if divisible by 4, they are.
1975          return 29;
1976        }
1977    }
1978    return ((31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[$month-1]);
1979}
1980
1981sub populate_web_menu {
1982    my ($res, $menu, $numref) = @_;
1983    my $mn = $$numref;  # menu number
1984    my $mi = 0;         # menu item
1985    foreach my $it (@$menu) {
1986        $mi++;
1987        $res->{"menu_${mn}_${mi}_text"} = $it->{'text'};
1988        if ($it->{'text'} eq "-") { next; }
1989        if ($it->{'sub'}) {
1990            $$numref++;
1991            $res->{"menu_${mn}_${mi}_sub"} = $$numref;
1992            &populate_web_menu($res, $it->{'sub'}, $numref);
1993            next;
1994           
1995        }
1996        $res->{"menu_${mn}_${mi}_url"} = $it->{'url'};
1997    }
1998    $res->{"menu_${mn}_count"} = $mi;
1999}
2000
2001
2002####
2003### delete an itemid.  if $quick is specified, that means items are being deleted en-masse
2004##  and the batch deleter will take care of some of the stuff, so this doesn't have to
2005#
2006sub delete_item
2007{
2008    my ($dbh, $ownerid, $itemid, $quick) = @_;
2009    my $sth;
2010    $ownerid += 0;
2011    $itemid += 0;
2012
2013    $dbh->do("DELETE FROM hintlastnview WHERE itemid=$itemid") unless ($quick);
2014    $dbh->do("DELETE FROM memorable WHERE itemid=$itemid");
2015    $dbh->do("UPDATE user SET lastitemid=0 WHERE userid=$ownerid AND lastitemid=$itemid") unless ($quick);
2016    $dbh->do("DELETE FROM log WHERE itemid=$itemid");
2017    $dbh->do("DELETE FROM logtext WHERE itemid=$itemid");
2018    $dbh->do("DELETE FROM logsubject WHERE itemid=$itemid");
2019    $dbh->do("DELETE FROM logprop WHERE itemid=$itemid");
2020    $dbh->do("DELETE FROM logsec WHERE ownerid=$ownerid AND itemid=$itemid");
2021
2022    my @talkids = ();
2023    $sth = $dbh->prepare("SELECT talkid FROM talk WHERE nodetype='L' AND nodeid=$itemid");
2024    $sth->execute;
2025    while (my ($tid) = $sth->fetchrow_array) {
2026        push @talkids, $tid;
2027    }
2028    if (@talkids) {
2029        my $in = join(",", @talkids);
2030        $dbh->do("DELETE FROM talk WHERE talkid IN ($in)");
2031        $dbh->do("DELETE FROM talktext WHERE talkid IN ($in)");
2032        $dbh->do("DELETE FROM talkprop WHERE talkid IN ($in)");
2033    }
2034   
2035}
2036
20371;
Note: See TracBrowser for help on using the browser.