svnno****@sourc*****
svnno****@sourc*****
2008年 4月 7日 (月) 09:26:30 JST
Revision: 572 http://svn.sourceforge.jp/cgi-bin/viewcvs.cgi?root=slashdotjp&view=rev&rev=572 Author: tach Date: 2008-04-07 09:26:30 +0900 (Mon, 07 Apr 2008) Log Message: ----------- merged from upstream/2.5.0.200 branch Modified Paths: -------------- slashjp/trunk/Slash/Apache/User/User.pm slashjp/trunk/Slash/DB/MySQL/MySQL.pm slashjp/trunk/Slash/Utility/Comments/Comments.pm slashjp/trunk/Slash/Utility/Environment/Environment.pm slashjp/trunk/debian/changelog slashjp/trunk/plugins/Ajax/PLUGIN slashjp/trunk/plugins/Ajax/htdocs/ajax.pl slashjp/trunk/plugins/Ajax/htdocs/images/admin.js slashjp/trunk/plugins/Ajax/htdocs/images/common.js slashjp/trunk/plugins/Ajax/htdocs/images/nodnix.js slashjp/trunk/plugins/Ajax/htdocs/images/sd_autocomplete.js slashjp/trunk/plugins/Ajax/htdocs/images/sd_calendar.js slashjp/trunk/plugins/Ajax/htdocs/images/slashbox.js slashjp/trunk/plugins/Ajax/templates/data;ajax;default slashjp/trunk/plugins/Ajax/templates/edit_comment;ajax;default slashjp/trunk/plugins/Console/console.pl slashjp/trunk/plugins/FireHose/FireHose.pm slashjp/trunk/plugins/FireHose/templates/data;firehose;default slashjp/trunk/plugins/FireHose/templates/dispTopicFireHose;misc;default slashjp/trunk/plugins/FireHose/templates/fireHoseForm;misc;default slashjp/trunk/plugins/FireHose/templates/firehose_pages;misc;default slashjp/trunk/plugins/FireHose/templates/list;firehose;default slashjp/trunk/plugins/HumanConf/HumanConf.pm slashjp/trunk/plugins/Journal/journal.pl slashjp/trunk/plugins/Moderation/Moderation.pm slashjp/trunk/plugins/ResKey/MANIFEST slashjp/trunk/plugins/ResKey/ResKey/Checks/Duration.pm slashjp/trunk/plugins/ResKey/ResKey/Key.pm slashjp/trunk/plugins/ResKey/example.plx slashjp/trunk/plugins/ResKey/mysql_dump.sql slashjp/trunk/plugins/ResKey/templates/data;reskey;default slashjp/trunk/plugins/TagDataView/TagDataView.pm slashjp/trunk/plugins/TagModeration/TagModeration.pm slashjp/trunk/plugins/Tags/Clout/Describe.pm slashjp/trunk/plugins/Tags/Clout/Vote.pm slashjp/trunk/plugins/Tags/PLUGIN slashjp/trunk/plugins/Tags/Tags.pm slashjp/trunk/plugins/Tags/mysql_dump.sql slashjp/trunk/plugins/Tags/tags.pl slashjp/trunk/plugins/Tags/templates/usertaghistory;users;default slashjp/trunk/plugins/Tags/templates/usertags;users;default slashjp/trunk/sql/mysql/defaults.sql slashjp/trunk/sql/mysql/upgrades slashjp/trunk/tagboxes/FHEditorPop/FHEditorPop.pm slashjp/trunk/tagboxes/FHEditorPop/mysql_dump.sql slashjp/trunk/tagboxes/Top/Top.pm slashjp/trunk/themes/slashcode/THEME slashjp/trunk/themes/slashcode/htdocs/comments.css slashjp/trunk/themes/slashcode/htdocs/comments.pl slashjp/trunk/themes/slashcode/htdocs/images/comments.js slashjp/trunk/themes/slashcode/htdocs/slashcode_lite.css slashjp/trunk/themes/slashcode/htdocs/users.pl slashjp/trunk/themes/slashcode/templates/dispComment;misc;default slashjp/trunk/themes/slashcode/templates/dispLinkComment;misc;default slashjp/trunk/themes/slashcode/templates/editComm;users;default slashjp/trunk/themes/slashcode/templates/printCommComments;misc;default slashjp/trunk/themes/slashcode/templates/printCommentsMain;misc;default slashjp/trunk/utils/createTestTags Added Paths: ----------- slashjp/trunk/plugins/Ajax/templates/hc_comment;ajax;default slashjp/trunk/plugins/ResKey/ResKey/Checks/HumanConf.pm slashjp/trunk/plugins/Tags/templates/usertagnames;users;default slashjp/trunk/plugins/Tags/templates/usertagsforname;users;default slashjp/trunk/tagboxes/RecentTags/ slashjp/trunk/themes/slashcode/htdocs/images/corner_w_bl.png slashjp/trunk/themes/slashcode/htdocs/images/corner_w_br.png slashjp/trunk/themes/slashcode/htdocs/images/corner_w_tl.png slashjp/trunk/themes/slashcode/htdocs/images/corner_w_tr.png slashjp/trunk/themes/slashcode/htdocs/users2.pl slashjp/trunk/themes/slashcode/templates/userInfo2;users;default slashjp/trunk/themes/slashcode/templates/userboxes2;misc;default -------------- next part -------------- Modified: slashjp/trunk/Slash/Apache/User/User.pm =================================================================== --- slashjp/trunk/Slash/Apache/User/User.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/Slash/Apache/User/User.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -597,7 +597,14 @@ $r->filename($constants->{basedir} . '/help.pl'); return OK; } - + + # This is a temporary addition! + if ($uri =~ m[^/(?:%5[eE]|\^)]) { + $r->uri('/users2.pl'); + $r->filename($constants->{basedir} . '/users2.pl'); + return OK; + } + # for self-references (/~/ and /my/) if (($saveuri =~ m[^/(?:%7[eE]|~)] && $uri =~ m[^/~ (?: /(.*) | /? ) $]x) # /my/ or /my can match, but not /mything @@ -612,6 +619,7 @@ } my($op, $extra) = split /\//, $string, 2; + $extra ||= ''; my $logged_in = $r->header_in('Cookie') =~ $USER_MATCH; my $try_login = !$logged_in && $logtoken; @@ -634,9 +642,9 @@ $found_the_op = 1; if ($op eq 'journal') { my $args; - if ($extra && $extra =~ /^\d+$/) { + if ($extra =~ /^\d+$/) { $args = "id=$extra&op=edit"; - } elsif ($extra && $extra eq 'friends') { + } elsif ($extra eq 'friends') { $args = "op=friendview"; } else { $args = "op=list"; @@ -703,7 +711,10 @@ $r->filename($constants->{basedir} . '/journal.pl'); } elsif ($op eq 'tags') { - $r->args("op=showtags"); + my $args = 'op=showtags'; + # XXX "!" is a 'reserved' char in URI, escape it here? + $args .= "&tagname=$extra" if $extra; + $r->args($args); $r->uri('/users.pl'); $r->filename($constants->{basedir} . '/users.pl'); @@ -861,7 +872,10 @@ $r->filename($constants->{basedir} . '/journal.pl'); } elsif ($op eq 'tags') { - $r->args("op=showtags&nick=$nick&uid=$uid"); + my $args = "op=showtags&nick=$nick&uid=$uid"; + # XXX "!" is a 'reserved' char in URI, escape it here? + $args .= "&tagname=$extra" if $extra; + $r->args($args); $r->uri('/users.pl'); $r->filename($constants->{basedir} . '/users.pl'); Modified: slashjp/trunk/Slash/DB/MySQL/MySQL.pm =================================================================== --- slashjp/trunk/Slash/DB/MySQL/MySQL.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/Slash/DB/MySQL/MySQL.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -3491,6 +3491,7 @@ # Of course, markStoryClean and -Dirty work too my($dirty_change, $dirty_newval); + if ($change_hr->{writestatus}) { $dirty_change = 1; $dirty_newval = $change_hr->{writestatus} eq 'dirty' ? 1 : 0; @@ -3504,6 +3505,11 @@ delete $change_hr->{is_dirty} } + if ($change_hr->{introtext} && $change_hr->{introtext} =~ /href=\"SELF\"/) { + my $link_url = $self->_getStorySelfLink($stoid, $change_hr); + $change_hr->{introtext} =~ s/href=\"SELF\"/href="$link_url"/; + } + $change_hr->{is_archived} = $change_hr->{is_archived} ? 'yes' : 'no' if defined $change_hr->{is_archived}; $change_hr->{in_trash} = $change_hr->{in_trash} ? 'yes' : 'no' @@ -7923,6 +7929,7 @@ $signoff_type ||= ''; $self->sqlInsert("signoff", { stoid => $stoid, uid => $uid, signoff_type => $signoff_type }); + $self->setStory($stoid, { thumb_signoff_needed => 0 }); if ($constants->{plugin}{FireHose}) { my $firehose = getObject("Slash::FireHose"); @@ -7993,6 +8000,19 @@ ); } +sub deleteSignoffsForStory { + my($self, $stoid) = @_; + my $constants = getCurrentStatic(); + my $stoid_q = $self->sqlQuote($stoid); + $self->sqlDelete("signoff", "stoid=$stoid_q"); + if ($constants->{plugin}{FireHose}) { + my $firehose = getObject("Slash::FireHose"); + my ($id) = $self->sqlSelect("id", "firehose", "type='story' and srcid=$stoid_q"); + $firehose->setFireHose($id, { signoffs => '' }); + + } +} + sub getSignoffsInLastMinutes { my ($self, $mins) = @_; $mins ||= getCurrentStatic("admin_timeout"); @@ -12629,6 +12649,23 @@ return $answer; } +sub _getStorySelfLink { + my($self, $stoid, $change_hr) = @_; + my $story = $self->getStory($stoid); + my $data = {}; + my $link = $change_hr->{title} || $story->{title}; + my $tid = $change_hr->{tid} || $story->{tid}; + my $skin = $change_hr->{primaryskid} || $story->{primary_skid}; + + my $story_link_ar = linkStory({ + sid => $story->{sid}, + link => $link, + tid => $tid, + skin => $skin + }); + return $story_link_ar->[0]; +} + ######################################################## sub DESTROY { my($self) = @_; Modified: slashjp/trunk/Slash/Utility/Comments/Comments.pm =================================================================== --- slashjp/trunk/Slash/Utility/Comments/Comments.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/Slash/Utility/Comments/Comments.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -38,7 +38,7 @@ @EXPORT = qw( constrain_score dispComment displayThread printComments jsSelectComments commentCountThreshold commentThresholds discussion2 - tempUofmLinkGenerate tempUofmCipherObj selectComments preProcessReplyForm + selectComments preProcessReplyForm getPoints preProcessComment postProcessComment prevComment saveComment ); @@ -156,8 +156,7 @@ #slashProf("sC main sort", "sC setup"); my($oldComment, %old_comments); - # XXXd2 disable for sub-threads for now ($cid) - if ($discussion2 && !$cid && !$options->{no_d2}) { + if ($discussion2 && !$options->{no_d2}) { my $limits = $slashdb->getDescriptions('d2_comment_limits'); my $max = $d2_comment_q ? $limits->{ $d2_comment_q } : 0; $max = int($max/2) if $shtml; @@ -165,6 +164,19 @@ $options->{existing} ||= {}; @$thisComment = sort { $a->{cid} <=> $b->{cid} } @$thisComment; + # we need to filter which comments are descendants of $cid + my %cid_seen; + if ($cid) { + # for display later + $user->{state}{d2_defaultclass}{$cid} = 'full'; + # this only works because we are already in cid order + for my $C (@$thisComment) { + if ($cid == $C->{cid} || $cid_seen{$C->{pid}}) { + $cid_seen{$C->{cid}} = 1; + } + } + } + my $sort_comments; if (!$user->{d2_comment_order}) { # score $sort_comments = [ sort { @@ -176,16 +188,21 @@ $sort_comments = $thisComment; } + for my $C (@$sort_comments) { next if $options->{existing}{$C->{cid}}; if ($max && @new_comments >= $max) { if ($cid) { + # still include $cid even if it would + # otherwise be excluded (should only + # matter if not sorting by date push @new_comments, $C if $cid == $C->{cid}; } else { last; } } else { + next if $cid && !$cid_seen{$C->{cid}}; push @new_comments, $C; } } @@ -193,9 +210,9 @@ my @seen; my $lastcid = 0; my %check = (%{$options->{existing}}, map { $_->{cid} => 1 } @new_comments); - for my $cid (sort { $a <=> $b } keys(%check)) { - push @seen, $lastcid ? $cid - $lastcid : $cid; - $lastcid = $cid; + for my $this_cid (sort { $a <=> $b } keys(%check)) { + push @seen, $lastcid ? $this_cid - $lastcid : $this_cid; + $lastcid = $this_cid; } $comments->{0}{d2_seen} = join ',', @seen; @@ -285,32 +302,38 @@ } # get the total visible kids for each comment --Pater - countTotalVisibleKids($comments); + countTotalVisibleKids($comments) unless $discussion2; - _print_cchp($discussion, $count, $comments->{0}{totals}); - -##slashProf("sC reparenting", "sC counting"); -#slashProf("sC reparenting"); - reparentComments($comments, $reader, $options); - -##slashProf("sC d2 fudging", "sC reparenting"); -#slashProf("", "sC reparenting"); +##slashProf("sC d2 fudging", "sC counting"); if ($oldComment) { - for my $cid (sort { $a <=> $b } keys %$comments) { - my $C = $comments->{$cid}; + my @new_seen; + for my $this_cid (sort { $a <=> $b } keys %$comments) { + next unless $this_cid; + my $C = $comments->{$this_cid}; # && !$options->{existing}{ $C->{pid} } while ($C->{pid}) { my $parent = $comments->{ $C->{pid} } || {}; + if (!$parent || !$parent->{kids} || !$parent->{cid} || !defined($parent->{pid}) || !defined($parent->{points})) { - $parent = $comments->{ $C->{pid} } = { - cid => $C->{pid}, - pid => ($old_comments{ $C->{pid} } && $old_comments{ $C->{pid} }{ pid }) || 0, - kids => [ ], - points => -2, - dummy => 1, - %$parent, - }; + # parents of our main cid, so spend time + # finding it ... + if ($cid && $C->{pid} < $cid) { + $user->{state}{d2_defaultclass}{$C->{pid}} = 'oneline'; + $parent = $old_comments{ $C->{pid} }; + push @new_seen, $C->{pid}; + $count++; + } else { + $parent = { + cid => $C->{pid}, + pid => ($old_comments{ $C->{pid} } && $old_comments{ $C->{pid} }{ pid }) || 0, + kids => [ ], + points => -2, + dummy => 1, + %$parent, + }; + } + $comments->{ $C->{pid} } = $parent; } unless (grep { $_ == $C->{cid} } @{$parent->{kids}}) { @@ -325,10 +348,29 @@ $C = $parent; } } + + # fix d2_seen + my @seen; + my $lastcid = 0; + for my $this_cid (sort { $a <=> $b } @new_seen) { + push @seen, $lastcid ? $this_cid - $lastcid : $this_cid; + $lastcid = $this_cid; + } + my @old_seen = split /,/, $comments->{0}{d2_seen}; + if (@seen && @old_seen) { + $old_seen[0] = $old_seen[0] - $lastcid; + } + $comments->{0}{d2_seen} = join ',', @seen, @old_seen; } ##slashProf("", "sC d2 fudging"); + _print_cchp($discussion, $count, $comments->{0}{totals}); + +#slashProf("sC reparenting"); + reparentComments($comments, $reader, $options); +#slashProf("", "sC reparenting"); + return($comments, $count); } @@ -344,7 +386,6 @@ $gSkin ||= getCurrentSkin(); my $id = $form->{sid}; - my $pid = $form->{cid} || 0; return unless $id; my $threshold = defined $user->{d2_threshold} ? $user->{d2_threshold} : $user->{threshold}; @@ -360,12 +401,8 @@ my($comments) = $user->{state}{selectComments}{comments}; my $d2_seen_0 = $comments->{0}{d2_seen} || ''; - #delete $comments->{0}; # non-comment data - if ($pid && exists $comments->{$pid}) { - $comments = _get_thread($comments, $pid); - } - my @roots = $pid ? $pid : @{$comments->{$pid}{kids}}; + my @roots = @{$comments->{0}{kids} || []}; my %roots_hash = ( map { $_ => 1 } @roots ); my $thresh_totals; @@ -392,7 +429,7 @@ } } - $thresh_totals = commentCountThreshold($comments, $pid, \%roots_hash); + $thresh_totals = commentCountThreshold($comments, 0, \%roots_hash); $comments = $comments_new; } @@ -408,6 +445,7 @@ $user->{is_anon} ||= 0; $user->{is_admin} ||= 0; $user->{is_subscriber} ||= 0; + my $root_comment = $user->{state}{selectComments}{cidorpid} || 0; my $extra = ''; if ($d2_seen_0) { @@ -435,7 +473,7 @@ thresh_totals = $anon_thresh; -root_comment = $pid; +root_comment = $root_comment; root_comments = $anon_roots; root_comments_hash = $anon_rootsh; max_cid = $max_cid; @@ -723,6 +761,7 @@ my $max_depth_allowed = $user->{state}{max_depth} || $constants->{max_depth} || 7; + # even if !reparent, we still want to be here so we can set comments at max depth return if $user->{state}{noreparent} || (!$max_depth_allowed && !$user->{reparent}); # Adjust the max_depth_allowed for the root pid or cid. @@ -731,7 +770,7 @@ # when $form->{cid|pid} is set. And besides, max depth we # display is for display, so it should be based on how much # we're displaying, not on absolute depth of this thread. - my $root_cid_or_pid = $form->{cid} || $form->{pid} || 0; + my $root_cid_or_pid = discussion2($user) ? 0 : ($form->{cid} || $form->{pid} || 0); if ($root_cid_or_pid) { my $tmpcid = $root_cid_or_pid; while ($tmpcid) { @@ -965,8 +1004,9 @@ #slashProf("", "selectComments"); if ($discussion2) { $user->{state}{selectComments} = { - comments => $comments, - count => $count + cidorpid => $cidorpid, + comments => $comments, + count => $count }; } @@ -1026,7 +1066,7 @@ return if $user->{state}{nocomment} || $user->{mode} eq 'nocomment'; my($comment, $next, $previous); - if ($cid) { + if ($cid && !$discussion2) { my($next, $previous); $comment = $comments->{$cid}; if (my $sibs = $comments->{$comment->{pid}}{kids}) { @@ -1241,6 +1281,9 @@ my $highlight = ($comment->{points} >= $highlightthresh && $class ne 'hidden') ? 1 : 0; $class = 'full' if $highlight; + if ($discussion2 && $user->{state}{d2_defaultclass}{$cid}) { + $class = $user->{state}{d2_defaultclass}{$cid}; + } $comment->{class} = $class; $user->{state}{comments}{totals}{$class}++ unless $comment->{dummy}; @@ -1902,8 +1945,10 @@ $score_to_display .= "Score:"; if (length $comment->{points}) { $score_to_display .= $comment->{points}; - $score_to_display = qq[<a href="#" onclick="getModalPrefs('modcommentlog', 'Moderation Comment Log', $comment->{cid}); return false">$score_to_display</a>] - if $constants->{modal_prefs_active} && !$user->{is_anon}; + if ($constants->{modal_prefs_active}) { + my $func = "getModalPrefs('modcommentlog', 'Moderation Comment Log', $comment->{cid})"; + $score_to_display = qq[<a href="#" onclick="$func; return false">$score_to_display</a>]; + } } else { $score_to_display .= '?'; } @@ -1991,25 +2036,23 @@ && $comment->{nickname} ne "-") { # this last test probably useless my @link = ( ); - push @link, (qq'<span id="reply_link_$comment->{cid}">' . linkComment({ + push @link, (qq'<span id="reply_link_$comment->{cid}" class="nbutton"><p><b>' . linkComment({ sid => $comment->{sid}, pid => $comment->{cid}, op => 'Reply', subject => 'Reply to This', subject_only => 1, - onclick => (($discussion2 && (!$constants->{subscribe} || $user->{is_subscriber})) ? "replyTo($comment->{cid}); return false;" : '') - }) . '</span>') unless $user->{state}{discussion_archived}; + onclick => (($discussion2 && !$user->{is_anon}) ? "replyTo($comment->{cid}); return false;" : '') + }) . '</b></p></span>') unless $user->{state}{discussion_archived}; - push @link, linkComment({ + push @link, (qq'<span class="nbutton"><p><b>' . linkComment({ sid => $comment->{sid}, cid => $comment->{original_pid}, pid => $comment->{original_pid}, subject => 'Parent', subject_only => 1, onclick => ($discussion2 ? "return selectParent($comment->{original_pid})" : '') - }, 1) if $comment->{original_pid};# && !($discussion2 && -# (!$form->{cid} || $form->{cid} != $comment->{cid}) -# ); + }, 1) . '</b></p></span>') if $comment->{original_pid}; #use Data::Dumper; print STDERR "_hard_dispComment createSelect can_mod='$can_mod' disc_arch='$user->{state}{discussion_archived}' modd_arch='$constants->{comments_moddable_archived}' cid='$comment->{cid}' reasons: " . Dumper($reasons); @@ -2025,10 +2068,10 @@ push @link, qq|<input type="checkbox" name="del_$comment->{cid}">| if $user->{is_admin}; - my $link = join(" | ", @link); + my $link = join(" ", @link); if (@link) { - $commentsub = "[ $link ]"; + $commentsub = $link; } } @@ -2084,6 +2127,7 @@ my $class = $comment->{class}; my $classattr = $discussion2 ? qq[ class="$class"] : ''; + my $contain = $class eq 'full' ? ' contain' : ''; my $head = $discussion2 ? <<EOT1 : <<EOT2; <h4><a id="comment_link_$comment->{cid}" name="comment_link_$comment->{cid}" href="$gSkin->{rootdir}/comments.pl?sid=$comment->{sid}&cid=$comment->{cid}" onclick="return setFocusComment($comment->{cid})">$comment->{subject}</a> @@ -2093,7 +2137,7 @@ my $return = ''; $return = <<EOT if !$options->{noshow_show}; -<li id="tree_$comment->{cid}" class="comment"> +<li id="tree_$comment->{cid}" class="comment$contain"> <div id="comment_status_$comment->{cid}" class="commentstatus"></div> <div id="comment_$comment->{cid}"$classattr> EOT @@ -2506,43 +2550,11 @@ if (getCurrentStatic('no_d2')) { return 0; } - return $user->{discussion2} =~ /^(?:slashdot|uofm)$/ + return $user->{discussion2} eq 'slashdot' ? $user->{discussion2} : 0; } -sub tempUofmLinkGenerate { - require URI::Escape; - - my $constants = getCurrentStatic(); - my $user = getCurrentUser(); - - my $cipher = tempUofmCipherObj() or return; - - my $encrypted = $cipher->encrypt($user->{uid} . '|' . $user->{nickname}); - return sprintf($constants->{uofm_address}, URI::Escape::uri_escape($encrypted)); -} - -sub tempUofmCipherObj { - my $constants = getCurrentStatic(); - return unless $constants->{uofm_key} && $constants->{uofm_iv}; - - require Crypt::CBC; - - my $cipher = Crypt::CBC->new({ - key => $constants->{uofm_key}, - iv => $constants->{uofm_iv}, - cipher => 'Blowfish', - regenerate_key => 0, - padding => 'null', - prepend_iv => 0 - }); - - return $cipher; -} - - - 1; __END__ Modified: slashjp/trunk/Slash/Utility/Environment/Environment.pm =================================================================== --- slashjp/trunk/Slash/Utility/Environment/Environment.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/Slash/Utility/Environment/Environment.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -1884,10 +1884,12 @@ colorblock => sub { $_[0] =~ s|[^\w#,]+||g }, # What I actually want to do for userfield is allow it to match # [\w.]+, or pass emailValid(), or be changed to the return value -# from nickFix(). For technical reasons I'm putting that off -# until probably next week. Until then this breaks some very -# minor functionality. - Jamie 2008-01-09 +# from nickFix(). But nickFix() uses constants, which might not +# be set up at this point. - Jamie 2008-01-09 userfield => sub { $_[0] =~ s|[^\w.@ -]||g }, +# Ditto here, really - Jamie 2008-03-24 + tagname => sub { $_[0] = '' unless + $_[0] =~ /^\!?[a-z][a-z0-9]{0,62}$/ }, ); Modified: slashjp/trunk/debian/changelog =================================================================== --- slashjp/trunk/debian/changelog 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/debian/changelog 2008-04-07 00:26:30 UTC (rev 572) @@ -1,3 +1,9 @@ +slash (2.5.0.200-1) unstable; urgency=low + + * New upstream CVS release + + -- Taku YASUI <tach****@osdn*****> Mon, 07 Apr 2008 09:26:13 +0900 + slash (2.5.0.198-1) unstable; urgency=low * New upstream CVS release Modified: slashjp/trunk/plugins/Ajax/PLUGIN =================================================================== --- slashjp/trunk/plugins/Ajax/PLUGIN 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Ajax/PLUGIN 2008-04-07 00:26:30 UTC (rev 572) @@ -36,6 +36,7 @@ template=templates/data;ajax;default template=templates/datewidget;misc;default template=templates/edit_comment;ajax;default +template=templates/hc_comment;ajax;default template=templates/modal_footer;misc;default template=templates/prefs_d2;ajax;default template=templates/prefs_d2_posting;ajax;default Modified: slashjp/trunk/plugins/Ajax/htdocs/ajax.pl =================================================================== --- slashjp/trunk/plugins/Ajax/htdocs/ajax.pl 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Ajax/htdocs/ajax.pl 2008-04-07 00:26:30 UTC (rev 572) @@ -40,8 +40,6 @@ ); # print STDERR "AJAX3 $$: $user->{uid}, $op\n"; -#$Slash::ResKey::DEBUG = 2; - $ops->{$op}{function} ||= loadCoderef($ops->{$op}{class}, $ops->{$op}{subroutine}); $op = 'default' unless $ops->{$op}{function}; @@ -58,7 +56,7 @@ if ($reskey_name ne 'NA') { my $reskey = getObject('Slash::ResKey'); - my $rkey = $reskey->key($reskey_name); + my $rkey = $reskey->key($reskey_name); #, { debug => 1 }); if (!$rkey) { print STDERR scalar(localtime) . " ajax.pl main no rkey for op='$op' name='$reskey_name'\n"; return; @@ -72,17 +70,17 @@ $rkey->use; } if (!$rkey->success) { + # feel free to send msgdiv => 'thisdivhere' to the ajax call, + # and any reskey error messages will be sent to it if ($form->{msgdiv}) { header_ajax({ content_type => 'application/json' }); (my $msgdiv = $form->{msgdiv}) =~ s/[^\w-]+//g; print Data::JavaScript::Anon->anon_dump({ - html => { $msgdiv => $rkey->errstr }, + html => { $msgdiv => $rkey->errstr }, + eval_last => "\$('#$msgdiv').show()" }); } - printf STDERR "AJAXE %d: UID:%d, op:%s: %s (%s:%s:%s:%s:%s:%s:%s)\n", - $$, $user->{uid}, $op, $rkey->errstr, $rkey->reskey, - $rkey->type, $rkey->resname, $rkey->rkrid, $rkey->code, $rkey->static, - $user->{srcids}{ 24 }; + $rkey->ERROR($op); return; } } @@ -281,12 +279,21 @@ my $discussion = $slashdb->getDiscussion($sid); my $comment = preProcessComment($form, $user, $discussion, \$error_message); if (!$error_message) { - $options->{rkey}->use or $error_message = $options->{rkey}->errstr; + unless ($options->{rkey}->use) { + $error_message = $options->{rkey}->errstr; + } } $saved_comment = saveComment($form, $comment, $user, $discussion, \$error_message) unless $error_message; my $cid = $saved_comment && $saved_comment ne '-1' ? $saved_comment->{cid} : 0; + if ($error_message) { + $error_message = getData('inline preview warning') . $error_message + unless $options->{rkey}->death; + # go back to HumanConf if we still have errors left to display + $error_message .= slashDisplay('hc_comment', { pid => $pid }, { Return => 1 }); + } + $options->{content_type} = 'application/json'; my %to_dump = ( cid => $cid, error => $error_message ); #use Data::Dumper; print STDERR Dumper \%to_dump; @@ -301,15 +308,18 @@ $user->{state}{ajax_accesslog_op} = 'comments_preview_reply'; - my($error_message, $preview, $html); + my $html = my $error_message = ''; my $discussion = $slashdb->getDiscussion($sid); my $comment = preProcessComment($form, $user, $discussion, \$error_message); if ($comment && $comment ne '-1') { - $preview = postProcessComment({ %$comment, %$form, %$user }, 0, $discussion); + my $preview = postProcessComment({ %$comment, %$form, %$user }, 0, $discussion); $html = prevComment($preview, $user); } - $error_message ||= 'This comment will not be saved until you click the Submit button below.'; + if ($html) { + $error_message = getData('inline preview warning') . $error_message; + $error_message .= slashDisplay('hc_comment', { pid => $pid }, { Return => 1 }); + } $options->{content_type} = 'application/json'; my %to_dump = ( error => $error_message, @@ -339,7 +349,7 @@ preProcessReplyForm($form, $reply); my $reskey = getObject('Slash::ResKey'); - my $rkey = $reskey->key('comments', { nostate => 1 }); + my $rkey = $reskey->key('comments', { nostate => 1 }); #, debug => 1 }); $rkey->create; my %to_dump; @@ -386,15 +396,14 @@ sub fetchComments { my($slashdb, $constants, $user, $form, $options) = @_; - my $cids = [ grep /^\d+$/, split /,/, ($form->{cids} || '') ]; + my $cids = [ grep { defined && /^\d+$/ } ($form->{_multi}{cids} ? @{$form->{_multi}{cids}} : $form->{cids}) ]; my $id = $form->{discussion_id} || 0; my $cid = $form->{cid} || 0; # root id my $d2_seen = $form->{d2_seen}; - my $placeholders = $form->{placeholders}; - my @placeholders; + my $placeholders = [ grep { defined && /^\d+$/ } ($form->{_multi}{placeholders} ? @{$form->{_multi}{placeholders}} : $form->{placeholders}) ]; $user->{state}{ajax_accesslog_op} = "ajax_comments_fetch"; -#use Data::Dumper; print STDERR Dumper [ $cids, $id, $cid, $d2_seen ]; +#use Data::Dumper; print STDERR Dumper [ $form, $cids, $id, $cid, $d2_seen ]; # XXX error? return unless $id && (@$cids || $d2_seen); @@ -439,17 +448,16 @@ #delete $comments->{0}; # non-comment data my %data; - if ($d2_seen || $placeholders) { + if ($d2_seen || @$placeholders) { my $special_cids; if ($d2_seen) { $special_cids = $cids = [ sort { $a <=> $b } grep { $_ && !$seen{$_} } keys %$comments ]; - } elsif ($placeholders) { - @placeholders = split /[,;]/, $placeholders; - $special_cids = [ sort { $a <=> $b } @placeholders ]; + } elsif (@$placeholders) { + $special_cids = [ sort { $a <=> $b } @$placeholders ]; if ($form->{d2_seen_ex}) { my @seen; my $lastcid = 0; - my %check = (%seen, map { $_ => 1 } @placeholders); + my %check = (%seen, map { $_ => 1 } @$placeholders); for my $cid (sort { $a <=> $b } keys(%check)) { push @seen, $lastcid ? $cid - $lastcid : $cid; $lastcid = $cid; @@ -587,9 +595,9 @@ $to_dump{eval_first} ||= ''; $to_dump{eval_first} .= "d2_seen = '$d2_seen_0'; updateMoreNum($total);"; } - if ($placeholders) { + if (@$placeholders) { $to_dump{eval_first} ||= ''; - $to_dump{eval_first} .= "placeholder_no_update = " . Data::JavaScript::Anon->anon_dump({ map { $_ => 1 } @placeholders }) . ';'; + $to_dump{eval_first} .= "placeholder_no_update = " . Data::JavaScript::Anon->anon_dump({ map { $_ => 1 } @$placeholders }) . ';'; } writeLog($id); return Data::JavaScript::Anon->anon_dump(\%to_dump); @@ -746,7 +754,7 @@ my $moddb = getObject("Slash::$constants->{m1_pluginname}"); if ($moddb) { # we hijack "tabbed" as our cid -- pudge - return $moddb->dispModCommentLog('cid', $form->{'tabbed'}, { + my $return = $moddb->dispModCommentLog('cid', $form->{'tabbed'}, { show_m2s => ($constants->{m2} ? (defined($form->{show_m2s}) ? $form->{show_m2s} @@ -756,6 +764,8 @@ need_m2_button => $constants->{m2}, title => " " }); + $return ||= getData('no modcommentlog'); + return $return; } } else { Modified: slashjp/trunk/plugins/Ajax/htdocs/images/admin.js =================================================================== --- slashjp/trunk/plugins/Ajax/htdocs/images/admin.js 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Ajax/htdocs/images/admin.js 2008-04-07 00:26:30 UTC (rev 572) @@ -200,7 +200,7 @@ if(use_fh_interval) { interval = getFirehoseUpdateInterval(); } - setTimeout("console_update(" + use_fh_interval + "," + fh_is_timed_out +")", interval); + setTimeout("console_update(" + use_fh_interval + "," + fh_is_timed_out +")", interval * 2); } function firehose_usage() { Modified: slashjp/trunk/plugins/Ajax/htdocs/images/common.js =================================================================== --- slashjp/trunk/plugins/Ajax/htdocs/images/common.js 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Ajax/htdocs/images/common.js 2008-04-07 00:26:30 UTC (rev 572) @@ -5,6 +5,42 @@ return document.getElementById(id); } +jQuery.fn.extend({ + + mapClass: function( map ) { + map['?'] = map['?'] || []; + return this.each(function() { + var unique = {}; + var cl = []; + $.each($.map(this.className.split(/\s+/), function(k){ + return k in map ? map[k] : ('*' in map ? map['*'] : k) + }).concat(map['+']), function(i, k) { + if ( k && !(k in unique) ) { + unique[k] = true; + cl.push(k); + } + }); + this.className = (cl.length ? cl : map['?']).join(' '); + }); + }, + + setClass: function( c1 ) { + return this.each(function() { + this.className = c1 + }); + }, + + toggleClasses: function( c1, c2, force ) { + var map = { '?': force }; + map[c1]=c2; + map[c2]=c1; + return this.mapClass(map); + } + +}); + +var reskey_static = ''; + // global settings, but a firehose might use a local settings object instead var firehose_settings = {}; firehose_settings.startdate = ''; @@ -18,8 +54,10 @@ firehose_settings.is_embedded = 0; firehose_settings.not_id = 0; firehose_settings.section = 0; + firehose_settings.more_num = 0; // Settings to port out of settings object + firehose_item_count = 0; firehose_updates = Array(0); firehose_updates_size = 0; firehose_ordered = Array(0); @@ -78,23 +116,11 @@ } function createPopupButtons() { - var buttons = ""; - if (arguments.length > 0) { - buttons = '<span class="buttons">'; - } - for (var i=0; i<arguments.length; i++) { - buttons = buttons + "<span>" + arguments[i] + "</span>"; - } - - buttons = buttons + "</span>"; - return buttons; + return '<span class="buttons"><span>' + $.makeArray(arguments).join('</span><span>') + '</span></span>'; } function closePopup(id, refresh) { - var el = $dom(id); - if (el) { - el.parentNode.removeChild(el); - } + $('#'+id).remove(); if (refresh) { window.location.reload(); } @@ -134,87 +160,44 @@ } function getXYForId(id, addWidth, addHeight) { - var div = $dom(id); - var offset = jQuery(div).offset(); - var xy = [ offset.left, offset.top ]; - if (addWidth) { - xy[0] = xy[0] + div.offsetWidth; - } - if (addHeight) { - xy[1] = xy[1] + div.offsetHeight; - } - return xy; + var div = $('#'+id); + var offset = div.offset(); + if (addWidth) offset.left += div.attr('offsetWidth'); + if (addHeight) offset.top += div.attr('offsetHeight'); + return [ offset.left, offset.top ]; } function firehose_toggle_advpref() { - var obj = $dom('fh_advprefs'); - if (obj.className == 'hide') { - obj.className = ""; - } else { - obj.className = "hide"; - } + $('#fh_advprefs').toggleClass('hide'); } function firehose_open_prefs() { - var obj = $dom('fh_advprefs'); - obj.className = ""; + $('#fh_advprefs').removeClass(); } -function toggleId(id, first, second) { - var obj = $dom(id); - if (obj.className == first) { - obj.className = second; - } else if (obj.className == second) { - obj.className = first; - } else { - obj.className = first; - } +function toggleId(id, c1, c2) { + $('#'+id).toggleClasses(c1, c2, c1); } function toggleIntro(id, toggleid) { - var obj = $dom(id); - var toggle = $dom(toggleid); - if (obj.className == 'introhide') { - obj.className = "intro" - toggle.innerHTML = "[-]"; - toggle.className = "expanded"; - } else { - obj.className = "introhide" - toggle.innerHTML = "[+]"; - toggle.className = "condensed"; + var new_class = 'condensed'; + var new_html = '[+]'; + if ( $('#'+id).toggleClasses('introhide', 'intro').hasClass('intro') ) { + new_class = 'expanded'; + new_html = '[-]'; } + $('#'+toggleid).setClass(new_class).html(new_html); } function tagsToggleStoryDiv(id, is_admin, type) { - var bodyid = 'toggletags-body-' + id; - var tagsbody = $dom(bodyid); - if (tagsbody.className == 'tagshide') { - tagsShowBody(id, is_admin, '', type); - } else { - tagsHideBody(id); - } + ($('#toggletags-body-'+id).hasClass('tagshide') ? tagsShowBody : tagsHideBody)(id, is_admin, '', type); } function tagsHideBody(id) { - // Make the body of the tagbox vanish - var tagsbodyid = 'toggletags-body-' + id; - var tagsbody = $dom(tagsbodyid); - tagsbody.className = "tagshide" - - // Make the title of the tagbox change back to regular - var titleid = 'tagbox-title-' + id; - var title = $dom(titleid); - title.className = "tagtitleclosed"; - - // Make the tagbox change back to regular. - var tagboxid = 'tagbox-' + id; - var tagbox = $dom(tagboxid); - tagbox.className = "tags"; - - // Toggle the button back. - var tagsbuttonid = 'toggletags-button-' + id; - var tagsbutton = $dom(tagsbuttonid); - tagsbutton.innerHTML = "[+]"; + $('#toggletags-body-'+id).setClass('tagshide'); // Make the body of the tagbox vanish + $('#tagbox-title-'+id).setClass('tagtitleclosed'); // Make the title of the tagbox change back to regular + $('#tagbox-'+id).setClass('tags'); // Make the tagbox change back to regular. + $('#toggletags-button-'+id).html('[+]'); // Toggle the button back. } function tagsShowBody(id, is_admin, newtagspreloadtext, type) { @@ -229,37 +212,19 @@ } //alert("Tags show body / Type: " + type ); + $('#toggletags-button-'+id).html("[-]"); // Toggle the button to show the click was received + $('#tagbox-'+id).setClass("tags"); // Make the tagbox change to the slashbox class + $('#tagbox-title-'+id).setClass("tagtitleopen"); // Make the title of the tagbox change to white-on-green + $('#toggletags-body-'+id).setClass("tagbody"); // Make the body of the tagbox visible - // Toggle the button to show the click was received - var tagsbuttonid = 'toggletags-button-' + id; - var tagsbutton = $dom(tagsbuttonid); - tagsbutton.innerHTML = "[-]"; - - // Make the tagbox change to the slashbox class - var tagboxid = 'tagbox-' + id; - var tagbox = $dom(tagboxid); - tagbox.className = "tags"; - - // Make the title of the tagbox change to white-on-green - var titleid = 'tagbox-title-' + id; - var title = $dom(titleid); - title.className = "tagtitleopen"; - - // Make the body of the tagbox visible - var tagsbodyid = 'toggletags-body-' + id; - var tagsbody = $dom(tagsbodyid); - - tagsbody.className = "tagbody"; - // If the tags-user div hasn't been filled, fill it. - var tagsuserid = 'tags-user-' + id; - var tagsuser = $dom(tagsuserid); - if (tagsuser.innerHTML == "") { + var tagsuser = $('#tags-user-' + id); + if (tagsuser.html() == "") { // The tags-user-123 div is empty, and needs to be // filled with the tags this user has already // specified for this story, and a reskey to allow // the user to enter more tags. - tagsuser.innerHTML = "Retrieving..."; + tagsuser.html("Retrieving..."); var params = {}; if (type == "stories") { params['op'] = 'tags_get_user_story'; @@ -275,12 +240,10 @@ params['newtagspreloadtext'] = newtagspreloadtext; var handlers = { onComplete: function() { - var textid = 'newtags-' + id; - var input = $dom(textid); - input.focus(); + $dom('newtags-'+id).focus(); } } - ajax_update(params, tagsuserid, handlers); + ajax_update(params, 'tags-user-' + id, handlers); //alert('after ajax_update ' + tagsuserid); // Also fill the admin div. Note that if the user @@ -311,9 +274,8 @@ // that we append some text to the user text. // We can't do that by passing it in, so do it // manually now. - var textinputid = 'newtags-' + id; - var textinput = $dom(textinputid); - textinput.value = textinput.value + ' ' + newtagspreloadtext; + var textinput = $dom('newtags-'+id); + textinput.value += ' ' + newtagspreloadtext; textinput.focus(); } } @@ -391,7 +353,7 @@ params['type'] = type; if ( fh_is_admin && ("_#)^*".indexOf(tag[0]) != -1) ) { params['op'] = 'tags_admin_commands'; - params['reskey'] = $dom('admin_commands-reskey-' + id).value; + params['reskey'] = $('#admin_commands-reskey-' + id).val(); params['command'] = tag; } else { params['op'] = 'tags_create_tag'; @@ -405,68 +367,53 @@ } function tagsCreateForStory(id) { - var toggletags_message_id = 'toggletags-message-' + id; - var toggletags_message_el = $dom(toggletags_message_id); - toggletags_message_el.innerHTML = 'Saving tags...'; + var status = $('#toggletags-message-'+id).html('Saving tags...'); - var params = {}; - params['op'] = 'tags_create_for_story'; - params['sidenc'] = id; - var newtagsel = $dom('newtags-' + id); - params['tags'] = newtagsel.value; - var reskeyel = $dom('newtags-reskey-' + id); - params['reskey'] = reskeyel.value; + ajax_update({ + op: 'tags_create_for_story', + sidenc: id, + tags: $('#newtags-'+id).val(), + reskey: $('#newtags-reskey-'+id).val() + }, 'tags-user-' + id); - ajax_update(params, 'tags-user-' + id); - // XXX How to determine failure here? - toggletags_message_el.innerHTML = 'Tags saved.'; + status.html('Tags saved.'); } function tagsCreateForUrl(id) { - var toggletags_message_id = 'toggletags-message-' + id; - var toggletags_message_el = $dom(toggletags_message_id); - toggletags_message_el.innerHTML = 'Saving tags...'; + var status = $('#toggletags-message-'+id).html('Saving tags...'); - var params = {}; - params['op'] = 'tags_create_for_url'; - params['id'] = id; - var newtagsel = $dom('newtags-' + id); - params['tags'] = newtagsel.value; - var reskeyel = $dom('newtags-reskey-' + id); - params['reskey'] = reskeyel.value; + ajax_update({ + op: 'tags_create_for_url', + id: id, + tags: $('#newtags-'+id).val(), + reskey: $('#newtags-reskey-'+id).val() + }, 'tags-user-' + id); - ajax_update(params, 'tags-user-' + id); - // XXX How to determine failure here? - toggletags_message_el.innerHTML = 'Tags saved.'; + status.html('Tags saved.'); } //Firehose functions begin function setOneTopTagForFirehose(id, newtag) { - var params = {}; - params['op'] = 'firehose_update_one_tag'; - params['id'] = id; - params['tags'] = newtag; - // params['reskey'] = reskeyel.value; - ajax_update(params, ''); + ajax_update({ + op: 'firehose_update_one_tag', + id: id, + tags: newtag + }); } function tagsCreateForFirehose(id) { - var toggletags_message_id = 'toggletags-message-' + id; - var toggletags_message_el = $dom(toggletags_message_id); - toggletags_message_el.innerHTML = 'Saving tags...'; + var status = $('#toggletags-message-'+id).html('Saving tags...'); - var params = {}; - params['op'] = 'tags_create_for_firehose'; - params['id'] = id; - var newtagsel = $dom('newtags-' + id); - params['tags'] = newtagsel.value; - var reskeyel = $dom('newtags-reskey-' + id); - params['reskey'] = reskeyel.value; + ajax_update({ + op: 'tags_create_for_firehose', + id: id, + tags: $('#newtags-'+id).val(), + reskey: $('#newtags-reskey-'+id).val() + }, 'tags-user-'+id); - ajax_update(params, 'tags-user-' + id); - toggletags_message_el.innerHTML = 'Tags saved.'; + status.html('Tags saved.'); } function toggle_firehose_body(id, is_admin) { @@ -487,11 +434,7 @@ } }; params['reskey'] = reskey_static; - if (is_admin) { - ajax_update(params, 'fhbody-'+id, handlers); - } else { - ajax_update(params, 'fhbody-'+id); - } + ajax_update(params, 'fhbody-'+id, is_admin ? handlers : null); fhbody.className = "body"; fh.className = "article" + usertype; if (is_admin) @@ -510,12 +453,7 @@ } function toggleFirehoseTagbox(id) { - var fhtb = $dom('fhtagbox-'+id); - if (fhtb.className == "hide") { - fhtb.className = "tagbox"; - } else { - fhtb.className = "hide"; - } + $('#fhtagbox-'+id).toggleClasses('tagbox', 'hide'); } function firehose_set_options(name, value) { @@ -555,14 +493,7 @@ } if (classname) { - var els = document.getElementsByClassName(classname, $dom('firehoselist')); - var classval = classname; - if (value) { - classval = classval + " hide"; - } - for (i = 0; i< els.length; i++) { - els[i].className = classval; - } + $('#firehoselist .'+classname).setClass(classname + value ? ' hide' : ''); } } @@ -573,6 +504,7 @@ } } firehose_settings.page = 0; + firehose_settings.more_num = 0; } if (name != "color") { for (i=0; i< pairs.length; i++) { @@ -628,20 +560,19 @@ firehose_settings.startdate = value; firehose_settings.duration = 1; firehose_settings.page = 0; + firehose_settings.more_num = 0; var issuedate = firehose_settings.issue.substr(5,2) + "/" + firehose_settings.issue.substr(8,2) + "/" + firehose_settings.issue.substr(10,2); - if ($dom('fhcalendar')) { - $dom('fhcalendar')._widget.setDate(issuedate, "day"); - } - if ($dom('fhcalendar_pag')) { - $dom('fhcalendar_pag')._widget.setDate(issuedate, "day"); - } + $('#fhcalendar, #fhcalendar_pag').each(function(){ + this._widget.setDate(issuedate, "day"); + }); } if (name == "color") { firehose_settings.color = value; } if (name == "pagesize") { firehose_settings.page = 0; + firehose_settings.more_num = 0; } } @@ -669,14 +600,7 @@ } function firehose_remove_all_items() { - var fhl = $dom('firehoselist'); - var children = fhl.childNodes; - for (var i = children.length -1 ; i >= 0; i--) { - var el = children[i]; - if (el.id) { - el.parentNode.removeChild(el); - } - } + $('#firehoselist').children().remove(); } @@ -684,24 +608,15 @@ if (!check_logged_in()) return; setFirehoseAction(); - var params = {}; - var handlers = { - onComplete: json_handler - }; - params['op'] = 'firehose_up_down'; - params['id'] = id; - params['reskey'] = reskey_static; - params['dir'] = dir; - var updown = $dom('updown-' + id); - ajax_update(params, '', handlers); - if (updown) { - if (dir == "+") { - updown.className = "votedup"; - } else if (dir == "-") { - updown.className = "voteddown"; - } - } + ajax_update({ + op: 'firehose_up_down', + id: id, + reskey: reskey_static, + dir: dir + }, '', { onComplete: json_handler }); + $('#updown-'+id).setClass(dir=='+' ? 'votedup' : 'voteddown'); + if (dir == "-" && fh_is_admin) { firehose_collapse_entry(id); } @@ -709,15 +624,12 @@ function firehose_remove_tab(tabid) { setFirehoseAction(); - var params = {}; - var handlers = { - onComplete: json_handler - }; - params['op'] = 'firehose_remove_tab'; - params['tabid'] = tabid; - params['reskey'] = reskey_static; - params['section'] = firehose_settings.section; - ajax_update(params, '', handlers); + ajax_update({ + op: 'firehose_remove_tab', + tabid: tabid, + reskey: reskey_static, + section: firehose_settings.section + }, '', { onComplete: json_handler }); } @@ -733,12 +645,12 @@ url: request_url || '/ajax.pl', data: request_params, type: 'POST', - contentType: 'application/x-www-form-urlencoded', + contentType: 'application/x-www-form-urlencoded' }; if ( id ) { opts['success'] = function(html){ - jQuery('#'+id).html(html); + $('#'+id).html(html); } } @@ -782,37 +694,34 @@ if (response.html) { for (el in response.html) { - if ($dom(el)) - $dom(el).innerHTML = response.html[el]; + $('#'+el).html(response.html[el]); } } if (response.value) { for (el in response.value) { - if ($dom(el)) - $dom(el).value = response.value[el]; + $('#'+el).val(response.value[el]); } } if (response.html_append) { for (el in response.html_append) { - if ($dom(el)) - $dom(el).innerHTML = $dom(el).innerHTML + response.html_append[el]; + $('#'+el).each(function(){ + this.innerHTML += response.html_append[el]; + }); } } if (response.html_append_substr) { for (el in response.html_append_substr) { - if ($dom(el)) { - var this_html = $dom(el).innerHTML; - var i = $dom(el).innerHTML.search(/<span class="?substr"?> ?<\/span>[\s\S]*$/i); - if (i == -1) { - $dom(el).innerHTML += response.html_append_substr[el]; - } else { - $dom(el).innerHTML = $dom(el).innerHTML.substr(0, i) + - response.html_append_substr[el]; - } + var found = $('#'+el); + if (found.size()) { + var this_html = found.html(); + var pos = this_html.search(/<span class="?substr"?> ?<\/span>[\s\S]*$/i); + if ( pos != -1 ) + this_html = this_html.substr(0, pos); + found.html(this_html + response.html_append_substr[el]); } } } @@ -833,14 +742,14 @@ var fh = 'firehose-' + el[1]; var wait_interval = 800; if(el[0] == "add") { - if (firehose_before[el[1]] && jQuery('#firehose-' + firehose_before[el[1]]).size()) { - jQuery('#firehose-' + firehose_before[el[1]]).after(el[2]); - } else if (firehose_after[el[1]] && jQuery('#firehose-' + firehose_after[el[1]]).size()) { - jQuery('#firehose-' + firehose_after[el[1]]).before(el[2]); + if (firehose_before[el[1]] && $('#firehose-' + firehose_before[el[1]]).size()) { + $('#firehose-' + firehose_before[el[1]]).after(el[2]); + } else if (firehose_after[el[1]] && $('#firehose-' + firehose_after[el[1]]).size()) { + $('#firehose-' + firehose_after[el[1]]).before(el[2]); } else if (insert_new_at == "bottom") { - jQuery('#firehoselist').append(el[2]); + $('#firehoselist').append(el[2]); } else { - jQuery('#firehoselist').prepend(el[2]); + $('#firehoselist').prepend(el[2]); } var toheight = 50; @@ -937,28 +846,21 @@ function firehose_reorder() { if (firehose_ordered) { - var fhlist = $dom('firehoselist'); + var fhlist = $('#firehoselist'); if (fhlist) { - var item_count = 0; - for (i = 0; i < firehose_ordered.length; i++) { - if (/^\d+$/.test(firehose_ordered[i])) { - item_count++; + firehose_item_count = firehose_ordered.length; + for (i = 0; i < firehose_ordered.length; ++i) { + if (!/^\d+$/.test(firehose_ordered[i])) { + --firehose_item_count; } - var fhel = $dom('firehose-' + firehose_ordered[i]); - if (fhlist && fhel) { - fhlist.appendChild(fhel); - } + $('#firehose-'+firehose_ordered[i]).appendTo(fhlist); if ( firehose_future[firehose_ordered[i]] ) { - if ($dom("ttype-" + firehose_ordered[i])) { - $dom("ttype-" + firehose_ordered[i]).className = "future"; - } + $('#ttype-'+firehose_ordered[i]).setClass('future'); } else { - if ($dom("ttype-" + firehose_ordered[i]) && $dom("ttype-" + firehose_ordered[i]).className == "future") { - $dom("ttype-" + firehose_ordered[i]).className = "story"; - } + $('#ttype-'+firehose_ordered[i]+'.future').setClass('story'); } } - document.title = "[% sitename %] - " + (console_updating ? "Console" : "Firehose") + " (" + item_count + ")"; + document.title = "[% sitename %] - " + (console_updating ? "Console" : "Firehose") + " (" + firehose_item_count + ")"; } } @@ -973,9 +875,7 @@ function firehose_get_updates_handler(transport) { - if ($dom('busy')) { - $dom('busy').className = "hide"; - } + $('#busy').setClass('hide'); var response = eval_response(transport); var processed = 0; firehose_removals = response.update_data.removals; @@ -1005,24 +905,9 @@ } function firehose_get_item_idstring() { - var fhl = $dom('firehoselist'); - var str = ""; - var children; - if (fhl) { - var id; - children = fhl.childNodes; - if (children) { - for (var i = 0; i < children.length; i++) { - if (children[i].id) { - id = children[i].id; - id = id.replace(/^firehose-/g, ""); - id = id.replace(/^\s+|\s+$/g, ""); - str = str + id + ","; - } - } - } - } - return str; + return $('#firehoselist > [id]').map(function(){ + return this.id.replace(/firehose-(\S+)/, '$1'); + }).get().join(','); } @@ -1039,26 +924,20 @@ while(id = fh_update_timerids.pop()) { clearTimeout(id) }; } fh_is_updating = 1 - var params = {}; - var handlers = { - onComplete: firehose_get_updates_handler + var params = { + op: 'firehose_get_updates', + ids: firehose_get_item_idstring(), + updatetime: update_time, + fh_pageval: firehose_settings.pageval, + embed: firehose_settings.is_embedded }; - params['op'] = 'firehose_get_updates'; - params['ids'] = firehose_get_item_idstring(); - params['updatetime'] = update_time; for (i in firehose_settings) { params[i] = firehose_settings[i]; } - if ( firehose_settings.is_embedded ) { - params['embed'] = 1; - } - params['fh_pageval'] = firehose_settings.pageval; - if ($dom('busy')) { - $dom('busy').className = ""; - } - ajax_update(params, '', handlers); + $('#busy').removeClass(); + ajax_update(params, '', { onComplete: firehose_get_updates_handler }); } @@ -1100,8 +979,7 @@ var secs = getSecsSinceLastFirehoseAction(); if (secs > inactivity_timeout) { fh_is_timed_out = 1; - if ($dom('message_area')) - $dom('message_area').innerHTML = "サーバの反応が良くないので自動更新は遅れます"; + $('#message_area').html("Automatic updates have been slowed due to inactivity") //firehose_pause(); } } @@ -1110,36 +988,21 @@ fh_play = 1; setFirehoseAction(); firehose_set_options('pause', '0'); - var pausepanel = $dom('pauseorplay'); - if ($dom('message_area')) - $dom('message_area').innerHTML = ""; - if (pausepanel) { - pausepanel.innerHTML = "更新"; - } - var pause = $dom('pause'); - - var play_div = $dom('play'); - if (play_div) { - play_div.className = "hide"; - } - if (pause) { - pause.className = "show"; - } + $('#message_area').html(''); + $('#pauseorplay').html('Updated'); + $('#play').setClass('hide'); + $('#pause').setClass('show'); } function is_firehose_playing() { - return YAHOO.util.Dom.hasClass('play', 'hide'); + return fh_play==1; } function firehose_pause() { fh_play = 0; - var pause = $dom('pause'); - var play_div = $dom('play'); - pause.className = "hide"; - play_div.className = "show"; - if ($dom('pauseorplay')) { - $dom('pauseorplay').innerHTML = "停止"; - } + $('#pause').setClass('hide'); + $('#play').setClass('show'); + $('#pauseorplay').html('Paused'); firehose_set_options('pause', '1'); } @@ -1148,14 +1011,8 @@ } function firehose_collapse_entry(id) { - var fhbody = $dom('fhbody-'+id); - var fh = $dom('firehose-'+id); - if (fhbody && fhbody.className == "body") { - fhbody.className = "hide"; - } - if (fh) { - fh.className = "briefarticle"; - } + $('#fhbody-'+id+'.body').setClass('hide'); + $('#firehose-'+id).setClass('briefarticle'); tagsHideBody(id) } @@ -1275,38 +1132,25 @@ } function logToDiv(id, message) { - var div = $dom(id); - if (div) { - div.innerHTML = div.innerHTML + message + "<br>"; - } + $('#'+id).append(message + '<br>'); } function firehose_open_tab(id) { - var tf = $dom('tab-form-'+id); - var tt = $dom('tab-text-'+id); - var ti = $dom('tab-input-'+id); - tf.className=""; - ti.focus(); - tt.className="hide"; + $('#tab-form-'+id).removeClass(); + $dom('tab-input-'+id).focus(); + $('#tab-text-'+id).setClass('hide'); } function firehose_save_tab(id) { - var tf = $dom('tab-form-'+id); - var tt = $dom('tab-text-'+id); - var ti = $dom('tab-input-'+id); - var params = {}; - var handlers = { - onComplete: json_handler - }; - params['op'] = 'firehose_save_tab'; - params['tabname'] = ti.value; - params['section'] = firehose_settings.section; - - params['tabid'] = id; - ajax_update(params, '', handlers); - tf.className = "hide"; - tt.className = ""; + ajax_update({ + op: 'firehose_save_tab', + tabname: $('#tab-input-'+id).val(), + section: firehose_settings.section, + tabid: id + }, '', { onComplete: json_handler }); + $('#tab-form-'+id).setClass('hide'); + $('#tab-text-'+id).removeClass(); } @@ -1424,6 +1268,8 @@ } function getModalPrefs(section, title, tabbed) { + if (!reskey_static) + return show_login_box(); document.getElementById('preference_title').innerHTML = title; var params = {}; params['op'] = 'getModalPrefs'; @@ -1464,28 +1310,17 @@ } function ajaxSaveSlashboxes() { - var wrapper = document.getElementById('slashboxes'); - var titles = YAHOO.util.Dom.getElementsByClassName('title', 'div', wrapper); - var sep = ""; - var all = ""; - for ( i=0; i<titles.length; ++i) { - var bid = titles[i].id.slice(0,-6); - all += sep + bid; - sep = ","; - } - - var params = {}; - params['op'] = 'page_save_user_boxes'; - params['reskey'] = reskey_static; - params['bids'] = all; - ajax_update(params, ''); + ajax_update({ + op: 'page_save_user_boxes', + reskey: reskey_static, + bids: $('#slashboxes div.title').map(function(){ + return this.id.slice(0,-6); + }).get().join(',') + }); } function ajaxRemoveSlashbox( id ) { - var slashboxes = document.getElementById('slashboxes'); - var box = document.getElementById(id); - if ( box.parentNode === slashboxes ) { - slashboxes.removeChild(box); + if ( $('#slashboxes > #'+id).remove().size() ) { ajaxSaveSlashboxes(); } } @@ -1517,7 +1352,7 @@ } function admin_signoff(stoid, type, id) { - var params = []; + var params = {}; params['op'] = 'admin_signoff'; params['stoid'] = stoid; params['reskey'] = reskey_static; @@ -1530,7 +1365,6 @@ function scrollWindowToFirehose(fhid) { var firehose_y = getOffsetTop($('firehose-' + fhid)); - console.log(firehose_y); scroll(viewWindowLeft(), firehose_y); } @@ -1621,6 +1455,7 @@ var pos = firehose_get_pos_of_id(cur); if (pos < (firehose_ordered.length - 1)) { pos++; + } else { } firehose_set_cur(firehose_ordered[pos]); scrollWindowToFirehose(firehose_cur); @@ -1637,4 +1472,14 @@ } +function firehose_more() { + var increment_by = 10; + firehose_settings.more_num = firehose_settings.more_num + increment_by; + + if (((firehose_item_count + increment_by) >= 200) && !fh_is_admin) { + $('#firehose_more').hide(); + } + firehose_set_options('more_num', firehose_settings.more_num); +} + Modified: slashjp/trunk/plugins/Ajax/htdocs/images/nodnix.js =================================================================== --- slashjp/trunk/plugins/Ajax/htdocs/images/nodnix.js 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Ajax/htdocs/images/nodnix.js 2008-04-07 00:26:30 UTC (rev 572) @@ -17,16 +17,17 @@ } function get_predefined_nodnix_tags() { - var tags = []; - var query = _get_nodnix('input').getAttribute("updown"); - var listEl = query=="+" ? document.getElementById('static-nod-completions') - : document.getElementById('static-nix-completions'); - if ( listEl ) { - var itemEls = listEl.getElementsByTagName('li'); - for ( var i=0; i<itemEls.length; ++i ) - tags.push([itemEls[i].textContent]); - } - return tags; + var tags = []; + var query = _get_nodnix('input').getAttribute("updown"); + var listEl = query=="+" ? document.getElementById('static-nod-completions') + : document.getElementById('static-nix-completions'); + + if ( listEl ) { + var itemEls = listEl.getElementsByTagName('li'); + for ( var i=0; i<itemEls.length; ++i ) + tags.push([itemEls[i].textContent]); + } + return tags; } var predefinedDS = new YAHOO.widget.DS_JSFunction(get_predefined_nodnix_tags); @@ -34,54 +35,55 @@ var proxyDS = new Object(); proxyDS.__proto__ = YAHOO.slashdot.dataSources[0]; proxyDS.doQuery = function( oCallbackFn, sQuery, oParent ) { - if ( sQuery && sQuery.length ) - this.__proto__.doQuery(oCallbackFn, sQuery, oParent) - else - predefinedDS.doQuery(oCallbackFn, sQuery, oParent); + if ( sQuery && sQuery.length ) + this.__proto__.doQuery(oCallbackFn, sQuery, oParent) + else + predefinedDS.doQuery(oCallbackFn, sQuery, oParent); } function get_nodnix_listener() { - if ( !get_nodnix_listener.nodnix_listener ) { - var keylist = new Array(); // must be an actual Array(), not just [], for YUI to do-the-right-thing - keylist.push(YAHOO.util.KeyListener.KEY.ESCAPE); + if ( !get_nodnix_listener.nodnix_listener ) { + var keylist = new Array(); // must be an actual Array(), not just [], for YUI to do-the-right-thing + keylist.push(YAHOO.util.KeyListener.KEY.ESCAPE); - var a='A'.charCodeAt(0), z='Z'.charCodeAt(0); - for ( var kc = a; kc <= z; ++kc ) - keylist.push(kc); - var extras = "!_#)^*"; - for ( var i=0; i<extras.length; ++i ) - keylist.push(extras.charCodeAt(i)); + var a='A'.charCodeAt(0), z='Z'.charCodeAt(0); + for ( var kc = a; kc <= z; ++kc ) + keylist.push(kc); + var extras = "!_#)^*"; + for ( var i=0; i<extras.length; ++i ) + keylist.push(extras.charCodeAt(i)); - get_nodnix_listener.nodnix_listener = new YAHOO.util.KeyListener(document, {keys:keylist}, - {fn:handle_nodnix_key}); + get_nodnix_listener.nodnix_listener = new YAHOO.util.KeyListener( + document, {keys:keylist}, {fn:handle_nodnix_key} + ); - var keylist2 = new Array(); - keylist2.push(YAHOO.util.KeyListener.KEY.SPACE); - keylist2.push(YAHOO.util.KeyListener.KEY.ESCAPE); - keylist2.push(YAHOO.util.KeyListener.KEY.ENTER); + var keylist2 = new Array(); + keylist2.push(YAHOO.util.KeyListener.KEY.SPACE); + keylist2.push(YAHOO.util.KeyListener.KEY.ESCAPE); + keylist2.push(YAHOO.util.KeyListener.KEY.ENTER); - var setupCompleter = function(inputEl, containerEl) { - var ac = new YAHOO.widget.AutoComplete(inputEl, containerEl, proxyDS); - //ac.typeAhead = true; - ac.allowBrowserAutocomplete = false; - ac.highlightClassName = "selected"; - ac.minQueryLength = 0; + var setupCompleter = function(inputEl, containerEl) { + var ac = new YAHOO.widget.AutoComplete(inputEl, containerEl, proxyDS); + //ac.typeAhead = true; + ac.allowBrowserAutocomplete = false; + ac.highlightClassName = "selected"; + ac.minQueryLength = 0; - ac.textboxBlurEvent.subscribe(handle_nodnix_blur); - ac.itemSelectEvent.subscribe(handle_nodnix_select); - ac.unmatchedItemSelectEvent.subscribe(handle_nodnix_select); + ac.textboxBlurEvent.subscribe(handle_nodnix_blur); + ac.itemSelectEvent.subscribe(handle_nodnix_select); + ac.unmatchedItemSelectEvent.subscribe(handle_nodnix_select); - var listener = new YAHOO.util.KeyListener(inputEl, {keys:keylist2}, {fn:handle_completer_key}); - listener.enable(); + var listener = new YAHOO.util.KeyListener(inputEl, {keys:keylist2}, {fn:handle_completer_key}); + listener.enable(); - return ac; - } + return ac; + } - nod_completer = setupCompleter("nod-input", "nod-completions"); - nix_completer = setupCompleter("nix-input", "nix-completions"); - } - return get_nodnix_listener.nodnix_listener; + nod_completer = setupCompleter("nod-input", "nod-completions"); + nix_completer = setupCompleter("nix-input", "nix-completions"); + } + return get_nodnix_listener.nodnix_listener; } @@ -101,10 +103,10 @@ } function nodnix_not_tag( old_tag ) { - var new_tag = old_tag[0]=='!' ? old_tag.slice(1) : '!'+old_tag; + var new_tag = old_tag[0]=='!' ? old_tag.slice(1) : '!'+old_tag; createTag(new_tag, g_nodnix_item_id, "firehose"); var tag_list = _get_nodnix('ol'); - // XXX not a good idea if the tag happens to be 'span' or 'li', etc + // XXX not a good idea if the tag happens to be 'span' or 'li', etc tag_list.innerHTML = tag_list.innerHTML.replace(old_tag, new_tag, "g"); } @@ -178,122 +180,122 @@ } function _get_nodnix( tag ) { - var menu; - ((menu=get_nod_menu()).style.display != 'none') - || ((menu=get_nix_menu()).style.display != 'none') - || (menu=null); + var menu; + ((menu=get_nod_menu()).style.display != 'none') + || ((menu=get_nix_menu()).style.display != 'none') + || (menu=null); - if ( ! YAHOO.util.Dom.hasClass(menu, 'editing') ) - return; + if ( ! YAHOO.util.Dom.hasClass(menu, 'editing') ) + return; - return menu.getElementsByTagName(tag)[0]; + return menu.getElementsByTagName(tag)[0]; } function handle_nodnix_key( type, args, obj ) { - if ( args ) { - var event = args[1]; - if ( event ) { - // space key initiates editing, but _doesn't_ go into the text field (swallow it) - // escape key hides the menu before we even start editing (and we swallow it) - // any other key initiates editing and goes into the text field (don't swallow it) - var isSPACE = event.keyCode == YAHOO.util.KeyListener.KEY.SPACE; - var isESCAPE = event.keyCode == YAHOO.util.KeyListener.KEY.ESCAPE; + if ( args ) { + var event = args[1]; + if ( event ) { + // space key initiates editing, but _doesn't_ go into the text field (swallow it) + // escape key hides the menu before we even start editing (and we swallow it) + // any other key initiates editing and goes into the text field (don't swallow it) + var isSPACE = event.keyCode == YAHOO.util.KeyListener.KEY.SPACE; + var isESCAPE = event.keyCode == YAHOO.util.KeyListener.KEY.ESCAPE; - if ( isSPACE || isESCAPE ) - YAHOO.util.Event.stopEvent(event); + if ( isSPACE || isESCAPE ) + YAHOO.util.Event.stopEvent(event); - if ( isESCAPE ) - hide_nodnix_menu(); - else - begin_nodnix_editing(); - } - } + if ( isESCAPE ) + hide_nodnix_menu(); + else + begin_nodnix_editing(); + } + } } function soon_is_now() { - YAHOO.util.Dom.removeClass(get_nod_menu(), 'soon'); - YAHOO.util.Dom.removeClass(get_nix_menu(), 'soon'); + YAHOO.util.Dom.removeClass(get_nod_menu(), 'soon'); + YAHOO.util.Dom.removeClass(get_nix_menu(), 'soon'); } function refresh_tag_bar( tag_list ) { - // ajax request to fill the user tags list - var params = {}; - params['op'] = 'tags_get_user_firehose'; - params['id'] = g_nodnix_item_id; - params['nodnix'] = 1; - ajax_update(params, tag_list, {}); + // ajax request to fill the user tags list + var params = {}; + params['op'] = 'tags_get_user_firehose'; + params['id'] = g_nodnix_item_id; + params['nodnix'] = 1; + ajax_update(params, tag_list, {}); } function begin_nodnix_editing() { - if ( is_firehose_playing() ) { - firehose_pause(); - end_nodnix_editing.restore_firehose_state = firehose_play; - } + if ( is_firehose_playing() ) { + firehose_pause(); + end_nodnix_editing.restore_firehose_state = firehose_play; + } - get_nodnix_listener().disable(); - YAHOO.util.Dom.addClass(get_nod_menu(), 'soon'); - YAHOO.util.Dom.addClass(get_nix_menu(), 'soon'); - YAHOO.util.Dom.addClass(get_nod_menu(), 'editing'); - YAHOO.util.Dom.addClass(get_nix_menu(), 'editing'); - dont_hide_nodnix_menu(); + get_nodnix_listener().disable(); + YAHOO.util.Dom.addClass(get_nod_menu(), 'soon'); + YAHOO.util.Dom.addClass(get_nix_menu(), 'soon'); + YAHOO.util.Dom.addClass(get_nod_menu(), 'editing'); + YAHOO.util.Dom.addClass(get_nix_menu(), 'editing'); + dont_hide_nodnix_menu(); - var input = _get_nodnix('input'); - input.value = ""; - input.focus(); + var input = _get_nodnix('input'); + input.value = ""; + input.focus(); - var tag_list = _get_nodnix('ol'); - tag_list.innerHTML = ""; - refresh_tag_bar(tag_list); + var tag_list = _get_nodnix('ol'); + tag_list.innerHTML = ""; + refresh_tag_bar(tag_list); - (input.getAttribute("updown")=="+" ? nod_completer : nix_completer).sendQuery(); - setTimeout(soon_is_now, 225); + (input.getAttribute("updown")=="+" ? nod_completer : nix_completer).sendQuery(); + setTimeout(soon_is_now, 225); } function end_nodnix_editing() { - YAHOO.util.Dom.removeClass(get_nod_menu(), 'editing'); - YAHOO.util.Dom.removeClass(get_nix_menu(), 'editing'); - end_nodnix_editing.restore_firehose_state(); - end_nodnix_editing.restore_firehose_state = function(){} + YAHOO.util.Dom.removeClass(get_nod_menu(), 'editing'); + YAHOO.util.Dom.removeClass(get_nix_menu(), 'editing'); + end_nodnix_editing.restore_firehose_state(); + end_nodnix_editing.restore_firehose_state = function(){} } end_nodnix_editing.restore_firehose_state = function(){} function handle_nodnix_blur( type, args ) { - hide_nodnix_menu(); + hide_nodnix_menu(); } function handle_nodnix_select( type, args, stay_open ) { - var tagname = args[2]; - if ( tagname !== undefined && tagname !== null ) { - if ( typeof tagname != 'string' ) - tagname = tagname[0]; - nodnix_tag(tagname); - // now 'harden' the tag - var list = _get_nodnix('ol'); - list.innerHTML = handle_nodnix_select.template_string.split('$').join(tagname) + list.innerHTML; - _get_nodnix('input').value = ""; - } - if ( !stay_open ) - hide_nodnix_menu(); + var tagname = args[2]; + if ( tagname !== undefined && tagname !== null ) { + if ( typeof tagname != 'string' ) + tagname = tagname[0]; + nodnix_tag(tagname); + // now 'harden' the tag + var list = _get_nodnix('ol'); + list.innerHTML = handle_nodnix_select.template_string.split('$').join(tagname) + list.innerHTML; + _get_nodnix('input').value = ""; + } + if ( !stay_open ) + hide_nodnix_menu(); } - // WARNING: keep this string in sync with tagsnodnixuser;misc;default +// WARNING: keep this string in sync with tagsnodnixuser;misc;default handle_nodnix_select.template_string = '<li>$<span class="tag-actions"><a class="not-tag" onmousedown="nodnix_not_tag(\'$\'); return false" href="#">!</a> <a class="del-tag" onmousedown="nodnix_del_tag(\'$\'); return false" href="#">x</a></span></li>'; function handle_completer_key( type, args ) { - var key = args[0]; - var event = args[1]; - var stay_open = false; - switch ( key ) { - case YAHOO.util.KeyListener.KEY.ESCAPE: - hide_nodnix_menu(); - break; - case YAHOO.util.KeyListener.KEY.SPACE: - YAHOO.util.Event.stopEvent(event); - stay_open = true; - // fall through - case YAHOO.util.KeyListener.KEY.ENTER: - handle_nodnix_select("", [null, null, _get_nodnix('input').value], stay_open); - break; - } + var key = args[0]; + var event = args[1]; + var stay_open = false; + switch ( key ) { + case YAHOO.util.KeyListener.KEY.ESCAPE: + hide_nodnix_menu(); + break; + case YAHOO.util.KeyListener.KEY.SPACE: + YAHOO.util.Event.stopEvent(event); + stay_open = true; + // fall through + case YAHOO.util.KeyListener.KEY.ENTER: + handle_nodnix_select("", [null, null, _get_nodnix('input').value], stay_open); + break; + } } Modified: slashjp/trunk/plugins/Ajax/htdocs/images/sd_autocomplete.js =================================================================== --- slashjp/trunk/plugins/Ajax/htdocs/images/sd_autocomplete.js 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Ajax/htdocs/images/sd_autocomplete.js 2008-04-07 00:26:30 UTC (rev 572) @@ -3,67 +3,66 @@ YAHOO.namespace("slashdot"); -YAHOO.slashdot.DS_JSArray = function(aData, oConfigs) - { - if ( typeof oConfigs == "object" ) - for ( var sConfig in oConfigs ) - this[sConfig] = oConfigs[sConfig]; +YAHOO.slashdot.DS_JSArray = function(aData, oConfigs) { + if ( typeof oConfigs == "object" ) + for ( var sConfig in oConfigs ) + this[sConfig] = oConfigs[sConfig]; - if ( !aData || (aData.constructor != Array) ) - return + if ( !aData || (aData.constructor != Array) ) + return - this.data = aData; - this._init(); - } + this.data = aData; + this._init(); +} YAHOO.slashdot.DS_JSArray.prototype = new YAHOO.widget.DataSource(); YAHOO.slashdot.DS_JSArray.prototype.data = null; YAHOO.slashdot.DS_JSArray.prototype.doQuery = function(oCallbackFn, sQuery, oParent) { - var aData = this.data; // the array - var aResults = []; // container for results - var bMatchFound = false; - var bMatchContains = this.queryMatchContains; + var aData = this.data; // the array + var aResults = []; // container for results + var bMatchFound = false; + var bMatchContains = this.queryMatchContains; - if(sQuery && !this.queryMatchCase) { - sQuery = sQuery.toLowerCase(); - } + if(sQuery && !this.queryMatchCase) { + sQuery = sQuery.toLowerCase(); + } - // Loop through each element of the array... - // which can be a string or an array of strings - for(var i = aData.length-1; i >= 0; i--) { - var aDataset = []; + // Loop through each element of the array... + // which can be a string or an array of strings + for(var i = aData.length-1; i >= 0; i--) { + var aDataset = []; - if(aData[i]) { - if(aData[i].constructor == String) { - aDataset[0] = aData[i]; - } - else if(aData[i].constructor == Array) { - aDataset = aData[i]; - } - } + if(aData[i]) { + if(aData[i].constructor == String) { + aDataset[0] = aData[i]; + } + else if(aData[i].constructor == Array) { + aDataset = aData[i]; + } + } - if(aDataset[0] && (aDataset[0].constructor == String)) { - var sKeyIndex = 0; - if (sQuery) { - sKeyIndex = (this.queryMatchCase) ? - encodeURIComponent(aDataset[0]).indexOf(sQuery): - encodeURIComponent(aDataset[0]).toLowerCase().indexOf(sQuery); - } + if(aDataset[0] && (aDataset[0].constructor == String)) { + var sKeyIndex = 0; + if (sQuery) { + sKeyIndex = (this.queryMatchCase) ? + encodeURIComponent(aDataset[0]).indexOf(sQuery): + encodeURIComponent(aDataset[0]).toLowerCase().indexOf(sQuery); + } - // A STARTSWITH match is when the query is found at the beginning of the key string... - if((!bMatchContains && (sKeyIndex === 0)) || - // A CONTAINS match is when the query is found anywhere within the key string... - (bMatchContains && (sKeyIndex > -1))) { - // Stash a match into aResults[]. - aResults.unshift(aDataset); - } - } - } + // A STARTSWITH match is when the query is found at the beginning of the key string... + if((!bMatchContains && (sKeyIndex === 0)) || + // A CONTAINS match is when the query is found anywhere within the key string... + (bMatchContains && (sKeyIndex > -1))) { + // Stash a match into aResults[]. + aResults.unshift(aDataset); + } + } + } - this.getResultsEvent.fire(this, oParent, sQuery, aResults); - oCallbackFn(sQuery, aResults, oParent); + this.getResultsEvent.fire(this, oParent, sQuery, aResults); + oCallbackFn(sQuery, aResults, oParent); }; @@ -282,242 +281,219 @@ "neverdisplay" ]; - var feedbackDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.feedbackTags); - var actionsDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.actionTags); - var sectionsDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.sectionTags); - var topicsDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.topicTags); - var fhitemDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.fhitemOpts); - var storyDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.storyOpts); +var feedbackDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.feedbackTags); +var actionsDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.actionTags); +var sectionsDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.sectionTags); +var topicsDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.topicTags); +var fhitemDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.fhitemOpts); +var storyDS = new YAHOO.slashdot.DS_JSArray(YAHOO.slashdot.storyOpts); - var tagsDS = new YAHOO.widget.DS_XHR("./ajax.pl", ["\n", "\t"]); - // tagsDS.maxCacheEntries = 0; // turn off local cacheing, because Jamie says the query is fast - tagsDS.queryMatchSubset = false; - tagsDS.responseType = YAHOO.widget.DS_XHR.TYPE_FLAT; - tagsDS.scriptQueryParam = "prefix"; - tagsDS.scriptQueryAppend = "op=tags_list_tagnames"; - tagsDS.queryMethod = "POST"; +var tagsDS = new YAHOO.widget.DS_XHR("./ajax.pl", ["\n", "\t"]); +// tagsDS.maxCacheEntries = 0; // turn off local cacheing, because Jamie says the query is fast +tagsDS.queryMatchSubset = false; +tagsDS.responseType = YAHOO.widget.DS_XHR.TYPE_FLAT; +tagsDS.scriptQueryParam = "prefix"; +tagsDS.scriptQueryAppend = "op=tags_list_tagnames"; +tagsDS.queryMethod = "POST"; - var fhtabsDS = new YAHOO.widget.DS_XHR("./ajax.pl", ["\n", "\t"]); - fhtabsDS.queryMatchSubset = false; - fhtabsDS.responseType = YAHOO.widget.DS_XHR.TYPE_FLAT; - fhtabsDS.scriptQueryParam = "prefix"; - fhtabsDS.scriptQueryAppend = "op=firehose_list_tabs"; - fhtabsDS.queryMethod = "POST"; +var fhtabsDS = new YAHOO.widget.DS_XHR("./ajax.pl", ["\n", "\t"]); +fhtabsDS.queryMatchSubset = false; +fhtabsDS.responseType = YAHOO.widget.DS_XHR.TYPE_FLAT; +fhtabsDS.scriptQueryParam = "prefix"; +fhtabsDS.scriptQueryAppend = "op=firehose_list_tabs"; +fhtabsDS.queryMethod = "POST"; YAHOO.slashdot.dataSources = [tagsDS, actionsDS, sectionsDS, topicsDS, feedbackDS, storyDS, fhitemDS, fhtabsDS ]; -YAHOO.slashdot.AutoCompleteWidget = function() - { - this._widget = document.getElementById("ac-select-widget"); - this._spareInput = document.getElementById("ac-select-input"); +YAHOO.slashdot.AutoCompleteWidget = function() { + this._widget = document.getElementById("ac-select-widget"); + this._spareInput = document.getElementById("ac-select-input"); - this._sourceEl = null; - this._denyNextAttachTo = null; + this._sourceEl = null; + this._denyNextAttachTo = null; - YAHOO.util.Event.addListener(document.body, "click", this._onSdClick, this, true); - // add body/window blur to detect changing windows? - } + YAHOO.util.Event.addListener(document.body, "click", this._onSdClick, this, true); + // add body/window blur to detect changing windows? +} -YAHOO.slashdot.AutoCompleteWidget.prototype._textField = function() - { - if ( this._sourceEl==null || this._sourceEl.type=='text' || this._sourceEl.type=='textarea' ) - return this._sourceEl; +YAHOO.slashdot.AutoCompleteWidget.prototype._textField = function() { + if ( this._sourceEl==null || this._sourceEl.type=='text' || this._sourceEl.type=='textarea' ) + return this._sourceEl; - return this._spareInput; - } + return this._spareInput; +} -YAHOO.slashdot.AutoCompleteWidget.prototype._needsSpareInput = function() - { - // return this._textField() == this._spareInput; - return this._sourceEl && (this._sourceEl.type != "text") && (this._sourceEl.type != "textarea"); - } +YAHOO.slashdot.AutoCompleteWidget.prototype._needsSpareInput = function() { + // return this._textField() == this._spareInput; + return this._sourceEl && (this._sourceEl.type != "text") && (this._sourceEl.type != "textarea"); +} -YAHOO.slashdot.AutoCompleteWidget.prototype._newCompleter = function( tagDomain ) - { - var c = null; - if ( this._needsSpareInput() ) - { - c = new YAHOO.widget.AutoComplete("ac-select-input", "ac-choices", YAHOO.slashdot.dataSources[tagDomain]); - c.minQueryLength = 0; +YAHOO.slashdot.AutoCompleteWidget.prototype._newCompleter = function( tagDomain ) { + var c = null; + if ( this._needsSpareInput() ) { + c = new YAHOO.widget.AutoComplete("ac-select-input", "ac-choices", YAHOO.slashdot.dataSources[tagDomain]); + c.minQueryLength = 0; - // hack? -- override YUI's private member function so that for top tags auto-complete, right arrow means select - c._jumpSelection = function() { if ( this._oCurItem ) this._selectItem(this._oCurItem); }; - } - else - { - c = new YAHOO.widget.AutoComplete(this._sourceEl, "ac-choices", YAHOO.slashdot.dataSources[tagDomain]); - c.delimChar = " "; - c.minQueryLength = 3; - } - c.typeAhead = false; - c.forceSelection = false; - c.allowBrowserAutocomplete = false; - c.maxResultsDisplayed = 25; - c.animVert = false; + // hack? -- override YUI's private member function so that for top tags auto-complete, right arrow means select + c._jumpSelection = function() { if ( this._oCurItem ) this._selectItem(this._oCurItem); }; + } else { + c = new YAHOO.widget.AutoComplete(this._sourceEl, "ac-choices", YAHOO.slashdot.dataSources[tagDomain]); + c.delimChar = " "; + c.minQueryLength = 3; + } + c.typeAhead = false; + c.forceSelection = false; + c.allowBrowserAutocomplete = false; + c.maxResultsDisplayed = 25; + c.animVert = false; - return c; - } + return c; +} -YAHOO.slashdot.AutoCompleteWidget.prototype._show = function( obj, callbackParams, tagDomain ) - { - // onTextboxBlur should have already hidden the previous instance (if any), but if events - // come out of order, we must hide now to prevent broken listeners - if ( this._sourceEl ) - this._hide(); +YAHOO.slashdot.AutoCompleteWidget.prototype._show = function( obj, callbackParams, tagDomain ) { + // onTextboxBlur should have already hidden the previous instance (if any), but if events + // come out of order, we must hide now to prevent broken listeners + if ( this._sourceEl ) + this._hide(); - this._sourceEl = obj; + this._sourceEl = obj; - if ( this._sourceEl ) - { - this._callbackParams = callbackParams; - this._callbackParams._tagDomain = tagDomain; - this._completer = this._newCompleter(tagDomain); - - if ( typeof callbackParams.yui == "object" ) - for ( var field in callbackParams.yui ) - this._completer[field] = callbackParams.yui[field]; + if ( this._sourceEl ) { + this._callbackParams = callbackParams; + this._callbackParams._tagDomain = tagDomain; + this._completer = this._newCompleter(tagDomain); - if ( callbackParams.delayAutoHighlight ) - this._completer.autoHighlight = false; - + if ( typeof callbackParams.yui == "object" ) + for ( var field in callbackParams.yui ) + this._completer[field] = callbackParams.yui[field]; - // widget must be visible to move - YAHOO.util.Dom.removeClass(this._widget, "hidden"); - // move widget to be near the 'source' - var pos = YAHOO.util.Dom.getXY(this._sourceEl); - pos[1] += this._sourceEl.offsetHeight; - YAHOO.util.Dom.setXY(this._widget, pos); + if ( callbackParams.delayAutoHighlight ) + this._completer.autoHighlight = false; - YAHOO.util.Dom.addClass(this._sourceEl, "ac-source"); + // widget must be visible to move + YAHOO.util.Dom.removeClass(this._widget, "hidden"); + // move widget to be near the 'source' + var pos = YAHOO.util.Dom.getXY(this._sourceEl); + pos[1] += this._sourceEl.offsetHeight; + YAHOO.util.Dom.setXY(this._widget, pos); - if ( this._needsSpareInput() ) - { - YAHOO.util.Dom.removeClass(this._spareInput, "hidden"); - this._spareInput.value = ""; - this._spareInput.focus(); - this._pending_hide = setTimeout(YAHOO.slashdot.gCompleterWidget._hide, 15000); - } - else - YAHOO.util.Dom.addClass(this._spareInput, "hidden"); + YAHOO.util.Dom.addClass(this._sourceEl, "ac-source"); - this._completer.itemSelectEvent.subscribe(this._onSdItemSelectEvent, this); - this._completer.unmatchedItemSelectEvent.subscribe(this._onSdItemSelectEvent, this); - this._completer.textboxBlurEvent.subscribe(this._onSdTextboxBlurEvent, this); + if ( this._needsSpareInput() ) { + YAHOO.util.Dom.removeClass(this._spareInput, "hidden"); + this._spareInput.value = ""; + this._spareInput.focus(); + this._pending_hide = setTimeout(YAHOO.slashdot.gCompleterWidget._hide, 15000); + } else + YAHOO.util.Dom.addClass(this._spareInput, "hidden"); - YAHOO.util.Event.addListener(this._textField(), "keydown", this._onSdTextboxKeyDown, this, true); - } - } + this._completer.itemSelectEvent.subscribe(this._onSdItemSelectEvent, this); + this._completer.unmatchedItemSelectEvent.subscribe(this._onSdItemSelectEvent, this); + this._completer.textboxBlurEvent.subscribe(this._onSdTextboxBlurEvent, this); -YAHOO.slashdot.AutoCompleteWidget.prototype._hide = function() - { - if ( this._pending_hide ) - { - clearTimeout(this._pending_hide); - this._pending_hide = null; - } + YAHOO.util.Event.addListener(this._textField(), "keydown", this._onSdTextboxKeyDown, this, true); + } +} - YAHOO.util.Dom.addClass(this._widget, "hidden"); - YAHOO.util.Dom.addClass(this._spareInput, "hidden"); - if ( this._sourceEl ) - { - YAHOO.util.Dom.removeClass(this._sourceEl, "ac-source"); +YAHOO.slashdot.AutoCompleteWidget.prototype._hide = function() { + if ( this._pending_hide ) { + clearTimeout(this._pending_hide); + this._pending_hide = null; + } - YAHOO.util.Event.removeListener(this._textField(), "keydown", this._onSdTextboxKeyDown, this, true); - this._completer.itemSelectEvent.unsubscribe(this._onSdItemSelectEvent, this); - this._completer.unmatchedItemSelectEvent.unsubscribe(this._onSdItemSelectEvent, this); - this._completer.textboxBlurEvent.unsubscribe(this._onSdTextboxBlurEvent, this); + YAHOO.util.Dom.addClass(this._widget, "hidden"); + YAHOO.util.Dom.addClass(this._spareInput, "hidden"); + if ( this._sourceEl ) { + YAHOO.util.Dom.removeClass(this._sourceEl, "ac-source"); - this._sourceEl = null; - this._callbackParams = null; - this._completer = null; - } + YAHOO.util.Event.removeListener(this._textField(), "keydown", this._onSdTextboxKeyDown, this, true); + this._completer.itemSelectEvent.unsubscribe(this._onSdItemSelectEvent, this); + this._completer.unmatchedItemSelectEvent.unsubscribe(this._onSdItemSelectEvent, this); + this._completer.textboxBlurEvent.unsubscribe(this._onSdTextboxBlurEvent, this); - this._denyNextAttachTo = null; - } + this._sourceEl = null; + this._callbackParams = null; + this._completer = null; + } -YAHOO.slashdot.AutoCompleteWidget.prototype.attach = function( obj, callbackParams, tagDomain ) - { - var newSourceEl = obj; - if ( typeof obj == "string" ) - newSourceEl = document.getElementById(obj); + this._denyNextAttachTo = null; +} - // act like a menu: if we click on the same trigger while visible, hide - var denyThisAttach = this._denyNextAttachTo == newSourceEl; - this._denyNextAttachTo = null; - if ( denyThisAttach ) - return; +YAHOO.slashdot.AutoCompleteWidget.prototype.attach = function( obj, callbackParams, tagDomain ) { + var newSourceEl = obj; + if ( typeof obj == "string" ) + newSourceEl = document.getElementById(obj); - if ( newSourceEl && newSourceEl !== this._sourceEl ) - { - callbackParams._sourceEl = newSourceEl; - this._show(newSourceEl, callbackParams, tagDomain); + // act like a menu: if we click on the same trigger while visible, hide + var denyThisAttach = this._denyNextAttachTo == newSourceEl; + this._denyNextAttachTo = null; + if ( denyThisAttach ) + return; - var q = callbackParams.queryOnAttach; - if ( q ) - this._completer.sendQuery((typeof q == "string") ? q : ""); - } - } + if ( newSourceEl && newSourceEl !== this._sourceEl ) { + callbackParams._sourceEl = newSourceEl; + this._show(newSourceEl, callbackParams, tagDomain); -YAHOO.slashdot.AutoCompleteWidget.prototype._onSdClick = function( e, me ) - { - // if the user re-clicked the item to which I'm attached, then they mean to hide me - // I'm going to hide automatically, because a click outside the text will blur, and that makes me go away - // but I need to remember _not_ to let the current click re-show me - var reclicked = me._sourceEl && YAHOO.util.Event.getTarget(e, true) == me._sourceEl; - me._denyNextAttachTo = reclicked ? me._sourceEl : null; - } + var q = callbackParams.queryOnAttach; + if ( q ) + this._completer.sendQuery((typeof q == "string") ? q : ""); + } +} -YAHOO.slashdot.AutoCompleteWidget.prototype._onSdItemSelectEvent = function( type, args, me ) - { - var tagname = args[2]; - if ( tagname !== undefined && tagname !== null ) { - if ( typeof tagname != 'string' ) - tagname = tagname[0]; +YAHOO.slashdot.AutoCompleteWidget.prototype._onSdClick = function( e, me ) { + // if the user re-clicked the item to which I'm attached, then they mean to hide me + // I'm going to hide automatically, because a click outside the text will blur, and that makes me go away + // but I need to remember _not_ to let the current click re-show me + var reclicked = me._sourceEl && YAHOO.util.Event.getTarget(e, true) == me._sourceEl; + me._denyNextAttachTo = reclicked ? me._sourceEl : null; +} - var p = me._callbackParams; - if ( p.action0 !== undefined ) - p.action0(tagname, p); - me._hide(); - if ( p.action1 !== undefined ) - p.action1(tagname, p); +YAHOO.slashdot.AutoCompleteWidget.prototype._onSdItemSelectEvent = function( type, args, me ) { + var tagname = args[2]; + if ( tagname !== undefined && tagname !== null ) { + if ( typeof tagname != 'string' ) + tagname = tagname[0]; - } else { - me._hide(); - } - } + var p = me._callbackParams; + if ( p.action0 !== undefined ) + p.action0(tagname, p); + me._hide(); + if ( p.action1 !== undefined ) + p.action1(tagname, p); -YAHOO.slashdot.AutoCompleteWidget.prototype._onSdTextboxBlurEvent = function( type, args, me ) - { - var o = me._denyNextAttachTo; - me._hide(); - me._denyNextAttachTo = o; - } + } else { + me._hide(); + } +} -YAHOO.slashdot.AutoCompleteWidget.prototype._onSdTextboxKeyDown = function( e, me ) - { - if ( me._callbackParams && me._callbackParams.delayAutoHighlight ) - { - me._callbackParams.delayAutoHighlight = false; - me._completer.autoHighlight = true; - } +YAHOO.slashdot.AutoCompleteWidget.prototype._onSdTextboxBlurEvent = function( type, args, me ) { + var o = me._denyNextAttachTo; + me._hide(); + me._denyNextAttachTo = o; +} - switch ( e.keyCode ) - { - case 27: // esc - // any other keys?... - me._hide(); - break; - case 13: - // I'm sorry to say we have to test first, something somehow somewhere can still - // leave this listener dangling; want to look deeper into this, as this would _still_ - // leave the listener dangling - if ( me._completer ) - me._completer.unmatchedItemSelectEvent.fire(me._completer, me, me._completer._sCurQuery); - break; - default: - if ( me._pending_hide ) - clearTimeout(me._pending_hide); - if ( me._needsSpareInput() ) - me._pending_hide = setTimeout(YAHOO.slashdot.gCompleterWidget._hide, 15000); - } - } +YAHOO.slashdot.AutoCompleteWidget.prototype._onSdTextboxKeyDown = function( e, me ) { + if ( me._callbackParams && me._callbackParams.delayAutoHighlight ) { + me._callbackParams.delayAutoHighlight = false; + me._completer.autoHighlight = true; + } + + switch ( e.keyCode ) { + case 27: // esc + // any other keys?... + me._hide(); + break; + case 13: + // I'm sorry to say we have to test first, something somehow somewhere can still + // leave this listener dangling; want to look deeper into this, as this would _still_ + // leave the listener dangling + if ( me._completer ) + me._completer.unmatchedItemSelectEvent.fire(me._completer, me, me._completer._sCurQuery); + break; + default: + if ( me._pending_hide ) + clearTimeout(me._pending_hide); + if ( me._needsSpareInput() ) + me._pending_hide = setTimeout(YAHOO.slashdot.gCompleterWidget._hide, 15000); + } +} Modified: slashjp/trunk/plugins/Ajax/htdocs/images/sd_calendar.js =================================================================== --- slashjp/trunk/plugins/Ajax/htdocs/images/sd_calendar.js 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Ajax/htdocs/images/sd_calendar.js 2008-04-07 00:26:30 UTC (rev 572) @@ -1,275 +1,275 @@ // _*_ Mode: JavaScript; tab-width: 8; indent-tabs-mode: true _*_ -// $Id: sd_calendar.js,v 1.7 2007/06/05 19:04:12 scc Exp $ +// $Id: sd_calendar.js,v 1.9 2008/03/28 20:53:43 pudge Exp $ YAHOO.namespace("slashdot"); function _datesToSelector( selectorFormat, dates ) { - function format( d ) { - return selectorFormat(d.getFullYear(), d.getMonth()+1, d.getDate(), d.getDay()); - } + function format( d ) { + return selectorFormat(d.getFullYear(), d.getMonth()+1, d.getDate(), d.getDay()); + } - var s = format(dates[0]); - if ( dates[1] !== undefined ) - s += "-" + format(dates[1]); - return s; + var s = format(dates[0]); + if ( dates[1] !== undefined ) + s += "-" + format(dates[1]); + return s; } function _bundleDates( date1, date2 ) { - if ( date1 instanceof Array ) - return date1; - else if ( date2 === undefined ) - return [ date1 ]; - else - return [ date1, date2 ]; + if ( date1 instanceof Array ) + return date1; + else if ( date2 === undefined ) + return [ date1 ]; + else + return [ date1, date2 ]; } function datesToHumanReadable( date1, date2 ) { - var day_name = ["日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"]; + var day_name = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; - function day_ordinal( d ) { - switch ( d ) { - case 1: case 21: case 31: return d+"st"; - case 2: case 22: return d+"nd"; - case 3: case 23: return d+"rd"; - default: return d+"th"; - } - } + function day_ordinal( d ) { + switch ( d ) { + case 1: case 21: case 31: return d+"st"; + case 2: case 22: return d+"nd"; + case 3: case 23: return d+"rd"; + default: return d+"th"; + } + } - var month_name = ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"]; + var month_name = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; - function minimalHumanReadable( y, m, d, wd ) { - var now = new Date(); - if ( now.getFullYear() == y ) { - if ( now.getMonth()+1 == m ) { - } - } - } + function minimalHumanReadable( y, m, d, wd ) { + var now = new Date(); + if ( now.getFullYear() == y ) { + if ( now.getMonth()+1 == m ) { + } + } + } - - return _datesToSelector(function(y,m,d){return ""+y+"年"+month_name[m-1]+d+"日";}, _bundleDates(date1, date2)); + + return _datesToSelector(function(y,m,d){return ""+d+" "+month_name[m-1]+" "+y;}, _bundleDates(date1, date2)); } function datesToYUISelector( date1, date2 ) { - return _datesToSelector(function(y,m,d){return ""+m+"/"+d+"/"+y;}, _bundleDates(date1, date2)); + return _datesToSelector(function(y,m,d){return ""+m+"/"+d+"/"+y;}, _bundleDates(date1, date2)); } function datesToKinoSelector( date1, date2 ) { - function formatter( y, m, d ) { - if ( m < 10 ) m = "0" + m; - if ( d < 10 ) d = "0" + d; - return "" + y + m + d; - } - return _datesToSelector(formatter, _bundleDates(date1, date2)); + function formatter( y, m, d ) { + if ( m < 10 ) m = "0" + m; + if ( d < 10 ) d = "0" + d; + return "" + y + m + d; + } + return _datesToSelector(formatter, _bundleDates(date1, date2)); } // Kino format to YUI format: "20070428".replace(/(....)(..)(..)/, "$2/$3/$1") function weekOf( date ) { - var dayStart = new Date(datesToYUISelector(date)); - var dayCount = Math.round(dayStart.getTime() / 86400000); - var weekStart = dayCount - dayStart.getDay() + 1; - var weekStop = weekStart + 6; - var startDate = new Date(weekStart * 86400000); - var endDate = new Date(weekStop * 86400000); - return [startDate, endDate]; + var dayStart = new Date(datesToYUISelector(date)); + var dayCount = Math.round(dayStart.getTime() / 86400000); + var weekStart = dayCount - dayStart.getDay() + 1; + var weekStop = weekStart + 6; + var startDate = new Date(weekStart * 86400000); + var endDate = new Date(weekStop * 86400000); + return [startDate, endDate]; } var gOpenCalendarPane = null; YAHOO.slashdot.DateWidget = function( params ) { - this.init(params); + this.init(params); } YAHOO.slashdot.DateWidget.prototype.init = function( params ) { // id, mode, date, initCallback - var peer = null; - if ( params.peer !== undefined ) { - peer = document.getElementById(params.peer); - params.mode = peer._widget._mode; - params.date = peer._widget.getDate(); + var peer = null; + if ( params.peer !== undefined ) { + peer = document.getElementById(params.peer); + params.mode = peer._widget._mode; + params.date = peer._widget.getDate(); - this.subscribeToPeer(peer); - this._peer = peer; - } + this.subscribeToPeer(peer); + this._peer = peer; + } - this._mode = (params.mode !== undefined) ? params.mode : "now"; + this._mode = (params.mode !== undefined) ? params.mode : "now"; - var root = document.getElementById(params.id); - var find1st = function(name, kind) { - return YAHOO.util.Dom.getElementsByClassName(name, kind, root)[0]; - } + var root = document.getElementById(params.id); + var find1st = function(name, kind) { + return YAHOO.util.Dom.getElementsByClassName(name, kind, root)[0]; + } - var widget = this; + var widget = this; - this._element = root; - this._dateTab = find1st('date-tab', 'span'); - this._dateTab._widget = this; - this._label = find1st('day-label', 'option'); - this._calendarPane = find1st('calendar-pane', 'div'); - this.toggleCalendarPane(false); + this._element = root; + this._dateTab = find1st('date-tab', 'span'); + this._dateTab._widget = this; + this._label = find1st('day-label', 'option'); + this._calendarPane = find1st('calendar-pane', 'div'); + this.toggleCalendarPane(false); - this._popup = find1st('date-span-popup', 'select'); - this._popup._widget = this; + this._popup = find1st('date-span-popup', 'select'); + this._popup._widget = this; - this._calendar = new YAHOO.widget.Calendar(params.id+'-calendar-table', this._calendarPane.id, {maxdate:datesToYUISelector(new Date())}); - this._calendar.selectEvent.subscribe(this.handleCalendarSelect, this, true); + this._calendar = new YAHOO.widget.Calendar(params.id+'-calendar-table', this._calendarPane.id, {maxdate:datesToYUISelector(new Date())}); + this._calendar.selectEvent.subscribe(this.handleCalendarSelect, this, true); - root._widget = this; - root.setDate = function(d, m) { widget.setDate(d, m); } - root.getDateRange = function() { return widget.getDateRange(); } - root.changeEvent = new YAHOO.util.CustomEvent("change"); + root._widget = this; + root.setDate = function(d, m) { widget.setDate(d, m); } + root.getDateRange = function() { return widget.getDateRange(); } + root.changeEvent = new YAHOO.util.CustomEvent("change"); - this._muteEvents = 0; + this._muteEvents = 0; - this.setDate(params.date); + this.setDate(params.date); - if ( peer ) - peer._widget.subscribeToPeer(this._element); + if ( peer ) + peer._widget.subscribeToPeer(this._element); - if ( params.init !== undefined ) - params.init(root); + if ( params.init !== undefined ) + params.init(root); } function attachDateWidgetTo( params ) { - return new YAHOO.slashdot.DateWidget(params); + return new YAHOO.slashdot.DateWidget(params); } YAHOO.slashdot.DateWidget.prototype.muteEvents = function() { - ++this._muteEvents; + ++this._muteEvents; } YAHOO.slashdot.DateWidget.prototype.unmuteEvents = function() { - --this._muteEvents; + --this._muteEvents; } YAHOO.slashdot.DateWidget.prototype._reportChanged = function() { - if ( ! this._muteEvents ) - this._element.changeEvent.fire(this.getDateRange(), this._mode, this.getDate()); + if ( ! this._muteEvents ) + this._element.changeEvent.fire(this.getDateRange(), this._mode, this.getDate()); } YAHOO.slashdot.DateWidget.prototype.severPeer = function() { - if ( this._peer !== undefined ) { - this._peer._widget.unsubscribeFromPeer(this); - this.unsubscribeFromPeer(this._peer); - delete this._peer; - } + if ( this._peer !== undefined ) { + this._peer._widget.unsubscribeFromPeer(this); + this.unsubscribeFromPeer(this._peer); + delete this._peer; + } } YAHOO.slashdot.DateWidget.prototype.subscribeToPeer = function( peer ) { - peer.changeEvent.subscribe(this.handlePeerChange, this, true); + peer.changeEvent.subscribe(this.handlePeerChange, this, true); } YAHOO.slashdot.DateWidget.prototype.unsubscribeFromPeer = function( peer ) { - peer.changeEvent.unsubscribe(this.handlePeerChange, this); + peer.changeEvent.unsubscribe(this.handlePeerChange, this); } YAHOO.slashdot.DateWidget.prototype.setMode = function( newMode ) { - var oldMode = this._mode; - var modeChanged = (newMode !== undefined) && (newMode != oldMode); - if ( modeChanged ) { - if ( newMode == "all" ) - this.toggleCalendarPane(false); - YAHOO.util.Dom.replaceClass(this._element, oldMode, newMode); - this._mode = newMode; - this._popup.value = newMode; - } + var oldMode = this._mode; + var modeChanged = (newMode !== undefined) && (newMode != oldMode); + if ( modeChanged ) { + if ( newMode == "all" ) + this.toggleCalendarPane(false); + YAHOO.util.Dom.replaceClass(this._element, oldMode, newMode); + this._mode = newMode; + this._popup.value = newMode; + } - if ( modeChanged ) - this._reportChanged(); + if ( modeChanged ) + this._reportChanged(); - return modeChanged; + return modeChanged; } YAHOO.slashdot.DateWidget.prototype.setDate = function( date, mode ) { - this.muteEvents(); - if ( date === undefined ) - date = new Date(); - this._calendar.select(date); - this._calendar.render(); - var dateChanged = this._setDateFromSelection(date); + this.muteEvents(); + if ( date === undefined ) + date = new Date(); + this._calendar.select(date); + this._calendar.render(); + var dateChanged = this._setDateFromSelection(date); - var modeChanged = false; - if ( mode !== undefined ) - modeChanged = this.setMode(mode); - this.unmuteEvents(); + var modeChanged = false; + if ( mode !== undefined ) + modeChanged = this.setMode(mode); + this.unmuteEvents(); - if ( dateChanged || modeChanged ) - this._reportChanged(); + if ( dateChanged || modeChanged ) + this._reportChanged(); } YAHOO.slashdot.DateWidget.prototype._setDateFromSelection = function( date, allowModeChange ) { - var oldLabel = this._label.innerHTML; - var newLabel = "" + datesToHumanReadable(date); - var labelChanged = oldLabel != newLabel; - if ( labelChanged ) - this._label.innerHTML = newLabel; + var oldLabel = this._label.innerHTML; + var newLabel = "Day of " + datesToHumanReadable(date); + var labelChanged = oldLabel != newLabel; + if ( labelChanged ) + this._label.innerHTML = newLabel; - var modeChanged = false; - if ( allowModeChange==true ) { - var today = new Date(); - var newMode = ( date.getFullYear() == today.getFullYear() - && date.getMonth() == today.getMonth() - && date.getDate() == today.getDate() ) ? "now" : "day"; + var modeChanged = false; + if ( allowModeChange==true ) { + var today = new Date(); + var newMode = ( date.getFullYear() == today.getFullYear() + && date.getMonth() == today.getMonth() + && date.getDate() == today.getDate() ) ? "now" : "day"; - this.muteEvents(); - modeChanged = this.setMode(newMode); - this.unmuteEvents(); - } + this.muteEvents(); + modeChanged = this.setMode(newMode); + this.unmuteEvents(); + } - if ( labelChanged || modeChanged ) - this._reportChanged(); + if ( labelChanged || modeChanged ) + this._reportChanged(); - return labelChanged || modeChanged; + return labelChanged || modeChanged; } YAHOO.slashdot.DateWidget.prototype.getDate = function() { - return this._calendar.getSelectedDates()[0]; + return this._calendar.getSelectedDates()[0]; } YAHOO.slashdot.DateWidget.prototype.getDateRange = function() { - var range = { duration: -1 }; + var range = { duration: -1 }; - var start = null; - if ( this._mode == "day" ) { - start = this.getDate(); - range.duration = 1; - } else if ( this._mode == "now" ) { - range.duration = 7; - } + var start = null; + if ( this._mode == "day" ) { + start = this.getDate(); + range.duration = 1; + } else if ( this._mode == "now" ) { + range.duration = 7; + } - if ( start !== null ) - range.startdate = datesToKinoSelector(start); + if ( start !== null ) + range.startdate = datesToKinoSelector(start); - return range; + return range; } YAHOO.slashdot.DateWidget.prototype.toggleCalendarPane = function( show ) { - if ( gOpenCalendarPane !== null && gOpenCalendarPane !== this ) { - gOpenCalendarPane.toggleCalendarPane(false); - } - this._calendarPane.style.display = show ? 'block' : 'none'; - YAHOO.util.Dom[ show ? 'addClass' : 'removeClass' ](this._dateTab, 'active'); - gOpenCalendarPane = show ? this : null; + if ( gOpenCalendarPane !== null && gOpenCalendarPane !== this ) { + gOpenCalendarPane.toggleCalendarPane(false); + } + this._calendarPane.style.display = show ? 'block' : 'none'; + YAHOO.util.Dom[ show ? 'addClass' : 'removeClass' ](this._dateTab, 'active'); + gOpenCalendarPane = show ? this : null; } YAHOO.slashdot.DateWidget.prototype.handleDateTabClick = function() { - this.toggleCalendarPane( ! YAHOO.util.Dom.hasClass(this._dateTab, 'active') ); + this.toggleCalendarPane( ! YAHOO.util.Dom.hasClass(this._dateTab, 'active') ); } YAHOO.slashdot.DateWidget.prototype.handleCalendarSelect = function( type, args, obj ) { - this._setDateFromSelection(this._calendar._toDate(args[0][0]), true); - this.toggleCalendarPane(false); + this._setDateFromSelection(this._calendar._toDate(args[0][0]), true); + this.toggleCalendarPane(false); } YAHOO.slashdot.DateWidget.prototype.handleRangePopupSelect = function( obj ) { - this.setMode(obj.value); + this.setMode(obj.value); } YAHOO.slashdot.DateWidget.prototype.handlePeerChange = function( type, args, obj ) { - this.muteEvents(); - this.setDate(args[2], args[1]); - this.unmuteEvents(); + this.muteEvents(); + this.setDate(args[2], args[1]); + this.unmuteEvents(); } Modified: slashjp/trunk/plugins/Ajax/htdocs/images/slashbox.js =================================================================== --- slashjp/trunk/plugins/Ajax/htdocs/images/slashbox.js 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Ajax/htdocs/images/slashbox.js 2008-04-07 00:26:30 UTC (rev 572) @@ -1,101 +1,90 @@ YAHOO.namespace("slashdot"); -YAHOO.slashdot.SlashBox = function( id, sGroup, config ) - { - if ( id ) - { - this.init(id, sGroup, config); - this.initFrame(); - this.logger = this.logger || YAHOO; - } +YAHOO.slashdot.SlashBox = function( id, sGroup, config ) { + if ( id ) { + this.init(id, sGroup, config); + this.initFrame(); + this.logger = this.logger || YAHOO; + } - this.deleteBoundaryId = sGroup; - } + this.deleteBoundaryId = sGroup; +} YAHOO.extend(YAHOO.slashdot.SlashBox, YAHOO.util.DDProxy); -YAHOO.slashdot.SlashBox.prototype.createFrame() - { - // ... - } +YAHOO.slashdot.SlashBox.prototype.createFrame = function() { + // ... +} -YAHOO.slashdot.SlashBox.prototype.startDrag = function(x, y) - { - var dragEl = this.getDragEl(); - var clickEl = this.getEl(); +YAHOO.slashdot.SlashBox.prototype.startDrag = function(x, y) { + var dragEl = this.getDragEl(); + var clickEl = this.getEl(); - dragEl.innerHTML = clickEl.innerHTML; - dragEl.className = clickEl.className; + dragEl.innerHTML = clickEl.innerHTML; + dragEl.className = clickEl.className; - YAHOO.util.Dom.addClass(clickEl, "to-be-moved"); - // so we can style the object-to-be-moved in CSS - } + YAHOO.util.Dom.addClass(clickEl, "to-be-moved"); + // so we can style the object-to-be-moved in CSS +} -YAHOO.slashdot.SlashBox.prototype.endDrag = function(e) - { - YAHOO.util.Dom.removeClass(this.getEl(), "to-be-moved"); - // done moving, back to your regularly scheduled CSS (see this.startDrag) - } +YAHOO.slashdot.SlashBox.prototype.endDrag = function(e) { + YAHOO.util.Dom.removeClass(this.getEl(), "to-be-moved"); + // done moving, back to your regularly scheduled CSS (see this.startDrag) +} -YAHOO.slashdot.SlashBox.prototype.onDragOver = function(e, id) - { - if ( id == this.deleteBoundaryId ) - return; +YAHOO.slashdot.SlashBox.prototype.onDragOver = function(e, id) { + if ( id == this.deleteBoundaryId ) + return; - var pointer_y = YAHOO.util.Event.getPageY(e); - var dragged_box = this.getEl(); - var fixed_box; - - if ("string" == typeof id) - fixed_box = YAHOO.util.DDM.getElement(id); - else - fixed_box = YAHOO.util.DDM.getBestMatch(id).getEl(); + var pointer_y = YAHOO.util.Event.getPageY(e); + var dragged_box = this.getEl(); + var fixed_box; - var parent = fixed_box.parentNode; + if ("string" == typeof id) + fixed_box = YAHOO.util.DDM.getElement(id); + else + fixed_box = YAHOO.util.DDM.getBestMatch(id).getEl(); - var dragged_top = YAHOO.util.DDM.getPosY(dragged_box); - var fixed_top = YAHOO.util.DDM.getPosY(fixed_box); - - var fixed_mid = fixed_top + ( Math.floor(fixed_box.offsetHeight / 2)); + var parent = fixed_box.parentNode; - var dragging_down = dragged_top < fixed_top; + var dragged_top = YAHOO.util.DDM.getPosY(dragged_box); + var fixed_top = YAHOO.util.DDM.getPosY(fixed_box); + + var fixed_mid = fixed_top + ( Math.floor(fixed_box.offsetHeight / 2)); + var dragging_down = dragged_top < fixed_top; - if ( dragging_down && pointer_y > fixed_mid ) - parent.insertBefore(fixed_box, dragged_box); - else if ( !dragging_down && pointer_y < fixed_mid ) - parent.insertBefore(dragged_box, fixed_box); - else - return; - } -YAHOO.slashdot.SlashBox.prototype.onDragEnter = function(e, id) - { - if ( id == this.deleteBoundaryId ) - { - var dragEl = this.getDragEl(); - var clickEl = this.getEl(); - YAHOO.util.Dom.removeClass(dragEl, "to-be-deleted"); - YAHOO.util.Dom.removeClass(clickEl, "to-be-deleted"); - // so we can style the object-to-be-moved in CSS - } - } + if ( dragging_down && pointer_y > fixed_mid ) + parent.insertBefore(fixed_box, dragged_box); + else if ( !dragging_down && pointer_y < fixed_mid ) + parent.insertBefore(dragged_box, fixed_box); + else + return; +} -YAHOO.slashdot.SlashBox.prototype.onDragOut = function(e, id) - { - if ( id == this.deleteBoundaryId ) - { - var dragEl = this.getDragEl(); - var clickEl = this.getEl(); - YAHOO.util.Dom.addClass(dragEl, "to-be-deleted"); - YAHOO.util.Dom.addClass(clickEl, "to-be-deleted"); - // so we can style the object-to-be-moved in CSS - } - } +YAHOO.slashdot.SlashBox.prototype.onDragEnter = function(e, id) { + if ( id == this.deleteBoundaryId ) { + var dragEl = this.getDragEl(); + var clickEl = this.getEl(); + YAHOO.util.Dom.removeClass(dragEl, "to-be-deleted"); + YAHOO.util.Dom.removeClass(clickEl, "to-be-deleted"); + // so we can style the object-to-be-moved in CSS + } +} -YAHOO.slashdot.SlashBox.prototype.onDragDrop = function(e, id) - { - ajaxSaveSlashboxes(); - } +YAHOO.slashdot.SlashBox.prototype.onDragOut = function(e, id) { + if ( id == this.deleteBoundaryId ) { + var dragEl = this.getDragEl(); + var clickEl = this.getEl(); + YAHOO.util.Dom.addClass(dragEl, "to-be-deleted"); + YAHOO.util.Dom.addClass(clickEl, "to-be-deleted"); + // so we can style the object-to-be-moved in CSS + } +} + +YAHOO.slashdot.SlashBox.prototype.onDragDrop = function(e, id) { + ajaxSaveSlashboxes(); +} Modified: slashjp/trunk/plugins/Ajax/templates/data;ajax;default =================================================================== --- slashjp/trunk/plugins/Ajax/templates/data;ajax;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Ajax/templates/data;ajax;default 2008-04-07 00:26:30 UTC (rev 572) @@ -10,11 +10,20 @@ en_US __name__ data -__seclev__ -10000 __template__ [% SWITCH value %] [% CASE 'set_section_prefs_success_msg' %] <a href="#" onclick="window.location.reload(); return false" style="color:#fff;">Close</a> + +[% CASE 'inline preview warning' %] + <p>This comment will not be saved until you click the Submit button below.</p> + +[% CASE 'no modcommentlog' %] + <p>No comment history available.</p> + [% END %] +__seclev__ +1000 +__version__ +$Id$ Modified: slashjp/trunk/plugins/Ajax/templates/edit_comment;ajax;default =================================================================== --- slashjp/trunk/plugins/Ajax/templates/edit_comment;ajax;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Ajax/templates/edit_comment;ajax;default 2008-04-07 00:26:30 UTC (rev 572) @@ -19,57 +19,67 @@ __name__ edit_comment __template__ -<div id="wide"> -[% this_title = pid ? reply.subject : discussion.title; - this_title = this_title | strip_html; - PROCESS titlebar title="Reply to: $this_title" %] +<div class="inline_comment"> +[% IF user.is_anon %] + <p class="warning"> + [% IF constants.allow_anonymous %] + You are not logged in. You can <a href=\"${gSkin.rootdir}/login.pl\">log + in now</a>, or <a href="[% gSkin.rootdir %]/users.pl">Create an Account</a>. + [% ELSE %] + You are not logged in. You can <a href=\"${gSkin.rootdir}/login.pl\">log + in now</a>, <a href="[% gSkin.rootdir %]/users.pl">Create an Account</a>, + or post as <b>[% user.nickname | strip_literal %]</b>. + [% END %] + </p> +[% END %] -<p>If you have difficulty with this form, please use the <a href="[% gSkin.rootdir %]/comments.pl?op=Reply&sid=[% sid %]&pid=[% pid %]">old form</a>.</p> - -[% IF user.is_anon %]<p> - [% IF constants.allow_anonymous %] -You are not logged in. You can <a href=\"${gSkin.rootdir}/login.pl\">log -in now</a>, or <a href="[% gSkin.rootdir %]/users.pl">Create an Account</a>. - [% ELSE %] -You are not logged in. You can <a href=\"${gSkin.rootdir}/login.pl\">log -in now</a>, <a href="[% gSkin.rootdir %]/users.pl">Create an Account</a>, -or post as <b>[% user.nickname | strip_literal %]</b>. - [% END %] -</p>[% END %] - [% IF !user.is_anon || constants.allow_anonymous %] -<form action="[% gSkin.rootdir %]/comments.pl" method="post"> - <div id="replyto_preview_[% pid %]" class="replyto_reply" onclick="editReply([% pid %])"></div> - <div id="replyto_reply_[% pid %]" class="replyto_reply"> -<input type="hidden" name="sid" value="[% sid %]"> -[% IF pid %]<input type="hidden" name="pid" value="[% pid %]">[% END %] -<input type="hidden" name="gotmodwarning_[% pid %]" id="gotmodwarning_[% pid %]" value="0"> -[% reskey_label = "reskey_reply_$pid"; PROCESS reskey_tag %] -<p><input type="text" name="postersubj_[% pid %]" id="postersubj_[% pid %]" value="[% form.postersubj | strip_attribute %]" size="50" maxlength="50"> -[% UNLESS user.is_anon %][<a href="[% gSkin.rootdir %]/my/comments" onclick="getModalPrefs('d2_posting', 'Discussion 2'); return false;">Options</a>] -[%- IF constants.allow_anonymous && user.karma > -1 && (discussion.commentstatus == 'enabled' || discussion.commentstatus == 'logged_in') -%] - <input type="checkbox" name="postanon_[% pid %]" id="postanon_[% pid %]"> Post Anonymously -[%- END %]</p>[% END %] -<p><textarea wrap="virtual" name="postercomment_[% pid %]" id="postercomment_[% pid %]" rows="[% user.textarea_rows || constants.textarea_rows %]" cols="[% user.textarea_cols || constants.textarea_cols %]"></textarea></p> - - </div> - <div id="replyto_msg_[% pid %]" class="replyto_msg"></div> - <div class="replyto_buttons"> - <span id="replyto_buttons_1_[% pid %]"> -<input type="button" name="preview_[% pid %]" id="preview_[% pid %]" value="Preview" class="button" onclick="previewReply([% pid %]); return false;"> -[%- IF pid # not for root-level reply %] -<input type="button" name="quotereply_[% pid %]" id="quotereply_[% pid %]" value="Quote Parent" class="button" onclick="quoteReply([% pid %]); return false;">[% END %] - </span><span id="replyto_buttons_2_[% pid %]" style="display: none"> -<input type="button" name="submit_[% pid %]" id="submit_[% pid %]" value="Submit" class="button" onclick="submitReply([% pid %]); return false;"> -<input type="button" name="edit_[% pid %]" id="edit_[% pid %]" value="Continue Editing" class="button" onclick="editReply([% pid %]); return false;"> - </span><span id="replyto_buttons_3_[% pid %]"> -<input type="button" name="cancel_[% pid %]" id="cancel_[% pid %]" value="Cancel" class="button" onclick="cancelReply([% pid %]); return false;"> - </span> - </div> -</form> + <form action="[% gSkin.rootdir %]/comments.pl" method="post"> + <div id="replyto_preview_[% pid %]" class="replyto_reply" ondblclick="editReply([% pid %])"></div> + <div id="replyto_reply_[% pid %]" class="replyto_reply"> + <input type="hidden" name="sid" value="[% sid %]"> + [% IF pid %]<input type="hidden" name="pid" value="[% pid %]">[% END %] + <input type="hidden" name="gotmodwarning_[% pid %]" id="gotmodwarning_[% pid %]" value="0"> + [% reskey_label = "reskey_reply_$pid"; PROCESS reskey_tag %] + <div class="generaltitle"> + <div class="title"> + <h3> + <input type="text" name="postersubj_[% pid %]" id="postersubj_[% pid %]" value="[% form.postersubj | strip_attribute %]" size="50" maxlength="50"> + [% UNLESS user.is_anon %] + [%- IF constants.allow_anonymous && user.karma > -1 && (discussion.commentstatus == 'enabled' || discussion.commentstatus == 'logged_in') -%] + <input type="checkbox" name="postanon_[% pid %]" id="postanon_[% pid %]"> Post Anonymously + [%- END %] + [% END %] + </h3> + [% UNLESS user.is_anon %]<span class="pref"><a href="[% gSkin.rootdir %]/my/comments" onclick="getModalPrefs('d2_posting', 'Discussion 2'); return false;">Preferences</a></span>[% END %] + </div> + </div> + <div class="generalbody"> + <p>If you have difficulty with this form, please use the <a href="[% gSkin.rootdir %]/comments.pl?op=Reply&sid=[% sid %]&pid=[% pid %]">old form</a>.</p> + <textarea wrap="virtual" name="postercomment_[% pid %]" id="postercomment_[% pid %]" rows="[% user.textarea_rows || constants.textarea_rows %]" cols="[% user.textarea_cols || constants.textarea_cols %]"></textarea> + </div> + </div> + <div id="replyto_msg_[% pid %]" class="replyto_msg" style="display: none"></div> + <div class="replyto_buttons"> + <span id="replyto_buttons_1_[% pid %]"> + <input type="button" name="preview_[% pid %]" id="preview_[% pid %]" value="Preview" class="button" onclick="previewReply([% pid %]); return false;"> + [%- IF pid # not for root-level reply %] + <input type="button" name="quotereply_[% pid %]" id="quotereply_[% pid %]" value="Quote Parent" class="button" onclick="quoteReply([% pid %]); return false;">[% END %] + [%- UNLESS user.is_anon %] + <input type="button" name="prefs_[% pid %]" id="prefs_[% pid %]" value="Options" class="button" onclick="getModalPrefs('d2_posting', 'Discussion 2'); return false;">[% END %] + </span> + <span id="replyto_buttons_2_[% pid %]" style="display: none"> + <input type="button" name="submit_[% pid %]" id="submit_[% pid %]" value="Submit" class="button" onclick="submitReply([% pid %]); return false;"> + <input type="button" name="edit_[% pid %]" id="edit_[% pid %]" value="Continue Editing" class="button" onclick="editReply([% pid %]); return false;"> + <span class="state">Preview</span> + </span> + <span id="replyto_buttons_3_[% pid %]"> + <input type="button" name="cancel_[% pid %]" id="cancel_[% pid %]" value="Cancel" class="button" onclick="cancelReply([% pid %]); return false;"> + </span> + </div> + </form> [% END # IF !user.is_anon || constants.allow_anonymous %] </div> - __seclev__ 1000 __version__ Copied: slashjp/trunk/plugins/Ajax/templates/hc_comment;ajax;default (from rev 571, slashjp/branches/upstream/current/plugins/Ajax/templates/hc_comment;ajax;default) =================================================================== --- slashjp/trunk/plugins/Ajax/templates/hc_comment;ajax;default (rev 0) +++ slashjp/trunk/plugins/Ajax/templates/hc_comment;ajax;default 2008-04-07 00:26:30 UTC (rev 572) @@ -0,0 +1,29 @@ +__section__ +default +__description__ +Template which renders the comment editor. + +* error_message = error message if there is an error +* preview = preview of comment, if applicable +* reply = hashref of comment replying to +* hide_name = hide name / log out link +* hide_email = hide email display +* extras = array of any extras associated with this comment + +__title__ + +__page__ +ajax +__lang__ +en_US +__name__ +hc_comment +__template__ +[% IF user.state.hc && !user.state.hcinvalid %] +<p>[% user.state.hcquestion; user.state.hchtml %] +<input type="text" id="hcanswer_[% pid %]" value="[% form.hcanswer | strip_attribute %]" size="8" maxlength="8"> +[% END %] +__seclev__ +1000 +__version__ +$Id: hc_comment;ajax;default,v 1.1 2008/03/25 18:46:24 pudge Exp $ Modified: slashjp/trunk/plugins/Console/console.pl =================================================================== --- slashjp/trunk/plugins/Console/console.pl 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Console/console.pl 2008-04-07 00:26:30 UTC (rev 572) @@ -65,8 +65,7 @@ my $tagnamesbox = ''; my $tags = getObject('Slash::Tags'); if ($tags) { - my $rtoi_ar = $tags->getRecentTagnamesOfInterest(); - $tagnamesbox = $tags->showRecentTagnamesBox(); + $tagnamesbox = $tags->showRecentTagnamesBox({ box_only => 1}); } slashDisplay('display', { Modified: slashjp/trunk/plugins/FireHose/FireHose.pm =================================================================== --- slashjp/trunk/plugins/FireHose/FireHose.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/FireHose/FireHose.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -380,6 +380,9 @@ $options ||= {}; $options->{limit} ||= 50; + my $ps = $options->{limit}; + + $options->{limit} += $options->{more_num} if $options->{more_num}; my $pop; $pop = $self->getMinPopularityForColorLevel($colors->{$options->{color}}) @@ -609,9 +612,10 @@ $other = 'GROUP BY firehose.id' if $options->{tagged_by_uid}; my $count_other = $other; + my $offset; if (1 || !$doublecheck) { # do always for now - my $offset = defined $options->{offset} ? $options->{offset} : ''; + $offset = defined $options->{offset} ? $options->{offset} : ''; $offset = '' if $offset !~ /^\d+$/; $offset = "$offset, " if length $offset; $limit_str = "LIMIT $offset $options->{limit}" unless $options->{nolimit}; @@ -630,10 +634,11 @@ } - my $page_size = $options->{limit} || 1; + my $page_size = $ps || 1; $results->{records_pages} ||= ceil($count / $page_size); $results->{records_page} ||= (int(($options->{offset} || 0) / $options->{limit}) + 1) || 1; + my $future_count = $count - $options->{limit} - ($options->{offset} || 0); if (keys %$filter_globjids) { for my $i (0 .. $#{$hr_ar}) { @@ -664,7 +669,7 @@ $items = $hr_ar; } - return($items, $results, $count); + return($items, $results, $count, $future_count); } # A single-globjid wrapper around getUserFireHoseVotesForGlobjs. @@ -1017,7 +1022,7 @@ } my $eval_first = ""; - for my $o (qw(startdate mode fhfilter orderdir orderby startdate duration color)) { + for my $o (qw(startdate mode fhfilter orderdir orderby startdate duration color more_num)) { my $value = $opts->{$o}; if ($o eq 'orderby' && $value eq 'editorpop') { $value = 'popularity'; @@ -1025,6 +1030,9 @@ if ($o eq 'startdate') { $value =~ s/-//g; } + if ($o eq 'more_num') { + $value ||= 0; + } $eval_first .= "firehose_settings.$o = " . Data::JavaScript::Anon->anon_dump("$value") . "; "; } @@ -1162,7 +1170,7 @@ my %ids = map { $_ => 1 } @ids; my %ids_orig = ( %ids ) ; my $opts = $firehose->getAndSetOptions({ no_set => 1 }); - my($items, $results) = $firehose_reader->getFireHoseEssentials($opts); + my($items, $results, $count, $future_count) = $firehose_reader->getFireHoseEssentials($opts); my $num_items = scalar @$items; my $future = {}; my $globjs = []; @@ -1304,6 +1312,7 @@ $html->{filter_text} = "Filtered to ".strip_literal($opts->{color})." '".strip_literal($opts->{fhfilter})."'"; $html->{gmt_update_time} = " (".timeCalc($slashdb->getTime(), "%H:%M", 0)." GMT) " if $user->{is_admin}; $html->{itemsreturned} = $num_items == 0 ? getData("noitems", { options => $opts }, 'firehose') : ""; + $html->{firehose_more} = getData("firehose_more_link", { options => $opts, future_count => $future_count, contentsonly => 1}, 'firehose'); my $data_dump = Data::JavaScript::Anon->anon_dump({ html => $html, @@ -2153,6 +2162,15 @@ if ($form->{not_id} && $form->{not_id} =~ /^\d+$/) { $options->{not_id} = $form->{not_id}; } + + + if ($form->{more_num} && $form->{more_num} =~ /^\d+$/) { + $options->{more_num} = $form->{more_num}; + if (!$user->{is_admin} && (($options->{limit} + $options->{more_num}) > 200)) { + $options->{more_num} = 200 - $options->{limit} ; + } + } + return $options; } @@ -2277,7 +2295,7 @@ if ($featured && $featured->{id}) { $options->{not_id} = $featured->{id}; } - my($items, $results) = $firehose_reader->getFireHoseEssentials($options); + my($items, $results, $count, $future_count) = $firehose_reader->getFireHoseEssentials($options); my $itemnum = scalar @$items; @@ -2346,6 +2364,8 @@ $section = $gSkin->{skid}; } + my $firehose_more = getData('firehose_more_link', { future_count => $future_count, options => $options }, 'firehose'); + slashDisplay("list", { itemstext => $itemstext, itemnum => $itemnum, @@ -2361,7 +2381,8 @@ fh_page => $base_page, search_results => $results, featured => $featured, - section => $section + section => $section, + firehose_more => $firehose_more }, { Page => "firehose", Return => 1 }); } Modified: slashjp/trunk/plugins/FireHose/templates/data;firehose;default =================================================================== --- slashjp/trunk/plugins/FireHose/templates/data;firehose;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/FireHose/templates/data;firehose;default 2008-04-07 00:26:30 UTC (rev 572) @@ -93,6 +93,24 @@ [% END %] </b> <br><br> +[% CASE 'firehose_more_link' %] + [% extra_onclick = '' %] + [% IF !contentsonly %] + <div id="firehose_more"> + [% END %] + [% IF day_label && day_count; + label = day_count _ " more " _ $day_label _ "..."; + ELSIF future_count > 0; + label = future_count _ " more..."; + ELSIF future_count <= 0 && !options.startdate && options.duration != -1 && options.orderby == "createtime"; + label = "Get more..."; + extra_onclick = 'firehose_set_options(\'duration\', -1); '; + END; + %] + <a href="#" onclick="[% extra_onclick %] firehose_more(); return false;">[% label %]</a> + [% IF !contentsonly %] + </div> + [% END %] [% CASE 'notavailable' %] The item you're trying to view either does not exist, or is not viewable to you. [% END %] Modified: slashjp/trunk/plugins/FireHose/templates/dispTopicFireHose;misc;default =================================================================== --- slashjp/trunk/plugins/FireHose/templates/dispTopicFireHose;misc;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/FireHose/templates/dispTopicFireHose;misc;default 2008-04-07 00:26:30 UTC (rev 572) @@ -14,12 +14,16 @@ 10000 __template__ [% topic = Slash.db.getTopic(item.tid) %] -[% IF (item.thumb && (item.type == "story" || (item.type == "submission" && adminmode))) %] +[% IF item.type == "story"; + story = Slash.db.getStory(item.srcid); + + END %] +[% IF item.thumb && ((item.type == "story" && (!story.thumb_signoff_needed || adminmode)) || (item.type == "submission" && adminmode)) %] [% file = Slash.db.getStaticFile(item.thumb); %] [% fh = Slash.getObject("Slash::FireHose"); link_url = fh.linkFireHose(item); %] - <a href="[% link_url %]"><img src="[% constants.imagedir %][% file.name %]" alt="thumbnail" [% IF file.width; "width='$file.width'"; END; IF file.height; " height='$file.height'"; END; %] [% IF item.media %]onclick="firehose_get_media_popup('[% item.id %]'); return false;"[% END %]></a><br>[% IF item.media %]<a href="[% link_url %]" onclick="firehose_get_media_popup('[% item.id %]')">[% END %]<br>[% IF item.media %]<a href="[% link_url %]" onclick="firehose_get_media_popup('[% item.id %]'); return false;">Watch</a>[% END %] + <a href="[% link_url %]"><img src="[% constants.imagedir %][% file.name %]" alt="thumbnail" [% IF file.width; "width='$file.width'"; IF file.height; " height='$file.height'"; END; %] [% IF item.media %]onclick="firehose_get_media_popup('[% item.id %]'); return false;"[% END %]></a><br>[% IF item.media %]<a href="[% link_url %]" onclick="firehose_get_media_popup('[% item.id %]')">[% END %]<br>[% IF item.media %]<a href="[% link_url %]" onclick="firehose_get_media_popup('[% item.id %]'); return false;">Watch</a>[% END %][% END %] [% ELSIF user.noicons || user.simpledesign || user.lowbandwidth %] [ <a href="[% gSkin.rootdir %]/search.pl?tid=[% topic.tid %]">[% topic.textname %]</a> ] Modified: slashjp/trunk/plugins/FireHose/templates/fireHoseForm;misc;default =================================================================== --- slashjp/trunk/plugins/FireHose/templates/fireHoseForm;misc;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/FireHose/templates/fireHoseForm;misc;default 2008-04-07 00:26:30 UTC (rev 572) @@ -48,7 +48,7 @@ [% END %] [% IF needjssubmit %] <script type="text/javascript"> - $('postform-[% item.id %]').submit(); + $dom('postform-[% item.id %]').submit(); </script> [% END %] __version__ Modified: slashjp/trunk/plugins/FireHose/templates/firehose_pages;misc;default =================================================================== --- slashjp/trunk/plugins/FireHose/templates/firehose_pages;misc;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/FireHose/templates/firehose_pages;misc;default 2008-04-07 00:26:30 UTC (rev 572) @@ -66,7 +66,7 @@ IF i_page == start_page_num %]<b>‹</b> [% END; IF i_page == page_cur %]<strong>[% i_page %]</strong>[% ELSE; i_page; END; - IF i_page == end_page_num %] <b>›</b>[% END %]</a> + IF i_page == end_page_num %] [% END %]</a> [% END; END; Modified: slashjp/trunk/plugins/FireHose/templates/list;firehose;default =================================================================== --- slashjp/trunk/plugins/FireHose/templates/list;firehose;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/FireHose/templates/list;firehose;default 2008-04-07 00:26:30 UTC (rev 572) @@ -153,6 +153,7 @@ <span id="itemsreturned"> [% IF itemnum == 0; Slash.getData('noitems', { options => options }, 'firehose'); END %] </span> +[% firehose_more %] [% PROCESS paginate options = options ulid = "fh-paginate" divid = "fh-pag-div" num_items = itemnum fh_page = fh_page %] <script type="text/javascript"> [% FOR opt = [ 'startdate', 'mode', 'fhfilter', 'orderdir', 'orderby', 'startdate', 'duration', 'color'] %] Modified: slashjp/trunk/plugins/HumanConf/HumanConf.pm =================================================================== --- slashjp/trunk/plugins/HumanConf/HumanConf.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/HumanConf/HumanConf.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -36,25 +36,26 @@ } sub _formnameNeedsHC { - my($self, $formname) = @_; + my($self, $formname, $options) = @_; + return 1 if $options->{needs_hc}; my $regex = getCurrentStatic('hc_formname_regex') || '^comments$'; return 1 if $formname =~ /$regex/; return 0; } sub createFormkeyHC { - my($self, $formname) = @_; + my($self, $formname, $options) = @_; # Only certain formnames need human confirmation. From any # other formname, just return 1, meaning everything is ok # (no humanconf necessary). - return 'ok' if !$self->_formnameNeedsHC($formname); + return 'ok' if !$self->_formnameNeedsHC($formname, $options); my $slashdb = getCurrentDB(); my $form = getCurrentForm(); my $user = getCurrentUser(); my $constants = getCurrentStatic(); - my $formkey = $form->{formkey}; + my $formkey = $options->{frkey} || $form->{formkey}; return 0 unless $formkey; # Decide which question we're asking. @@ -113,7 +114,7 @@ return 0 unless $success; my $hcid = $slashdb->getLastInsertId(); - $user->{state}{hcid} = $hcid; # for debugging + $user->{state}{hcid} = $hcid; $user->{state}{hc} = 1; $user->{state}{hcinvalid} = 0; $user->{state}{hcquestion} = $question; @@ -121,25 +122,34 @@ return 1; } +sub updateFormkeyHCValue { + my($self, $hcid, $formkey) = @_; + my $slashdb = getCurrentDB(); + return $slashdb->sqlUpdate('humanconf', { + formkey => $formkey + }, 'hcid=' . $slashdb->sqlQuote($hcid) + ); +} + sub reloadFormkeyHC { - my($self, $formname) = @_; + my($self, $formname, $options) = @_; my $user = getCurrentUser(); # Only certain formnames need human confirmation. Other formnames # won't even have HC data created for them, so there's no need to # waste time hitting the DB. - if (!$self->_formnameNeedsHC($formname)) { + if (!$self->_formnameNeedsHC($formname, $options)) { $user->{state}{hc} = 0; - return ; + return; } $user->{state}{hc} = 1; my $slashdb = getCurrentDB(); my $form = getCurrentForm(); my $constants = getCurrentStatic(); - my $formkey = $form->{formkey}; - my $formkey_quoted = $slashdb->sqlQuote($form->{formkey}); + my $formkey = $options->{frkey} || $form->{formkey}; + my $formkey_quoted = $slashdb->sqlQuote($formkey); my($hcid, $html, $question, $tries_left) = $slashdb->sqlSelect( "hcid, html, question, tries_left", @@ -156,22 +166,23 @@ $user->{state}{hcinvalid} = 1; $user->{state}{hcerror} = getData('nomorechances', {}, 'humanconf'); } + return !$user->{state}{hcinvalid}; } sub validFormkeyHC { - my($self, $formname) = @_; + my($self, $formname, $options) = @_; # Only certain formnames need human confirmation. Other formnames # won't even have HC data created for them, so there's no need to # waste time hitting the DB. - return 'ok' if !$self->_formnameNeedsHC($formname); + return 'ok' if !$self->_formnameNeedsHC($formname, $options); my $slashdb = getCurrentDB(); my $form = getCurrentForm(); - my $formkey = $form->{formkey}; + my $formkey = $options->{frkey} || $form->{formkey}; return 'invalidhc' unless $formkey; - my $formkey_quoted = $slashdb->sqlQuote($form->{formkey}); + my $formkey_quoted = $slashdb->sqlQuote($formkey); # If this formkey is valid, and there is a corresponding humanconf # entry, check that as well. Note that if there is an hcid in the @@ -183,7 +194,7 @@ "humanconf, humanconf_pool", "humanconf.formkey = $formkey_quoted AND humanconf_pool.hcpid = humanconf.hcpid - AND tries_left > 0" + AND tries_left > 0" ); if (!$hcid) { # No humanconf associated with this formkey. Either there Modified: slashjp/trunk/plugins/Journal/journal.pl =================================================================== --- slashjp/trunk/plugins/Journal/journal.pl 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Journal/journal.pl 2008-04-07 00:26:30 UTC (rev 572) @@ -593,6 +593,7 @@ } my $slashdb = getCurrentDB(); + my $event_id; if ($form->{id}) { my %update; my $article = $journal_reader->get($form->{id}); @@ -624,6 +625,7 @@ url => "$rootdir/~" . fixparam($user->{nickname}) . "/journal/$form->{id}", }); $update{discussion} = $did; + $event_id = $did; # update description if changed } elsif (!$form->{comments_on} && $article->{discussion} && $article->{description} ne $description) { @@ -665,6 +667,7 @@ url => "$rootdir/~" . fixparam($user->{nickname}) . "/journal/$id", }); $journal->set($id, { discussion => $did }); + $event_id = $did; } slashHook('journal_save_success', { id => $id }); @@ -705,6 +708,44 @@ }) if $validator; } + # Add the User2 event. + my $events = $slashdb->sqlSelectAllHashref( + 'eid', 'eid, date', 'user_events', "uid = " . $user->{uid} . " and code = 2"); + + if ((scalar keys %$events) == 5) { + my $eid = [sort keys %$events]->[0]; + $slashdb->sqlDelete('user_events', "uid = " . $user->{uid} . " and code = 2 and eid = $eid"); + } + + $slashdb->sqlInsert('user_events', { + code => 2, + uid => $user->{uid}, + event => $event_id, + -date => 'NOW()', + }); + + my $event_blocks = $slashdb->sqlSelectAllHashref( + 'uid', 'bid, uid, block', 'user_event_blocks', "uid = " . $user->{uid} . " and code = 2"); + + if (!%$event_blocks) { + $slashdb->sqlInsert('user_event_blocks', { + code => 2, + uid => $user->{uid}, + block => $event_id, + }); + } else { + my @blocks = split(/,/, $event_blocks->{$user->{uid}}->{block}); + + if (scalar @blocks == 5) { + @blocks = @blocks[1 .. 4]; + } + + $blocks[$#blocks + 1] = $event_id; + my $new_blocks = join(",", @blocks); + + $slashdb->sqlUpdate('user_event_blocks', { block => $new_blocks }, "uid = " . $user->{uid} . " and code = 2"); + } + return 0; } Modified: slashjp/trunk/plugins/Moderation/Moderation.pm =================================================================== --- slashjp/trunk/plugins/Moderation/Moderation.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Moderation/Moderation.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -955,7 +955,7 @@ } } - my $removed_text = slashDisplay('undo_mod', { removed => $removed }, { Return => 1 }); + my $removed_text = slashDisplay('undo_mod', { removed => $removed }, { Return => 1, Page => 'comments' }); return $removed_text; } Modified: slashjp/trunk/plugins/ResKey/MANIFEST =================================================================== --- slashjp/trunk/plugins/ResKey/MANIFEST 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/ResKey/MANIFEST 2008-04-07 00:26:30 UTC (rev 572) @@ -6,8 +6,11 @@ ResKey/Checks/AL2/AnonNoPost.pm ResKey/Checks/AL2/NoPost.pm ResKey/Checks/AL2/NoPostAnon.pm +ResKey/Checks/AL2/NoSubmit.pm +ResKey/Checks/AL2/Spammer.pm ResKey/Checks/AL2.pm ResKey/Checks/Duration.pm +ResKey/Checks/HumanConf.pm ResKey/Checks/Moderate.pm ResKey/Checks/Post.pm ResKey/Checks/ProxyScan.pm Modified: slashjp/trunk/plugins/ResKey/ResKey/Checks/Duration.pm =================================================================== --- slashjp/trunk/plugins/ResKey/ResKey/Checks/Duration.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/ResKey/ResKey/Checks/Duration.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -140,9 +140,8 @@ my($self, $reskey_obj) = @_; my $slashdb = getCurrentDB(); - my $check_vars = $self->getCheckVars; - my $limit = $check_vars->{duration_uses}; + my $limit = &duration; if ($limit) { my $where = $self->getWhereUserClause; $where .= ' AND rkrid=' . $self->rkrid; @@ -186,4 +185,56 @@ } +sub duration { + my($self, $reskey_obj) = @_; + (my $caller = (caller(1))[3]) =~ s/^.*:(\w+)$/$1/; + + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $constants = getCurrentStatic(); + my $form = getCurrentForm(); + my $user = getCurrentUser(); + + my $check_vars = $self->getCheckVars; + my $limit = $constants->{reskey_timeframe}; + my $duration = 0; + + if ($caller eq 'minDurationBetweenUses') { + my $duration_name = 'duration_uses'; + my $duration_name_anon = "$duration_name-anon"; + # this is kinda ugly ... i'd like a better way to know anon, and + # this constant should be a reskey constant i think -- pudge 2008.03.21 + my $is_anon = $user->{is_anon} || $form->{postanon} || $user->{karma} < $constants->{formkey_minloggedinkarma}; + $duration = $check_vars->{$is_anon ? $duration_name_anon : $duration_name} || 0; + + # If this user has access modifiers applied, check for possible + # different speed limits based on those. First match, if any, + # wins. (taken from MySQL::checkPostInterval() + my $al2_hr = $user->{srcids} ? $reader->getAL2($user->{srcids}) : { }; + my $al2_name_used = "_none_"; # for debugging + for my $al2_name (sort keys %$al2_hr) { + my $sl_name_al2 = $is_anon + ? "$duration_name_anon-$al2_name" + : "$duration_name-$al2_name"; + if (defined $check_vars->{$sl_name_al2}) { + $al2_name_used = $al2_name; + $duration = $check_vars->{$sl_name_al2}; + last; + } + } + + if ($self->resname eq 'comments' && $is_anon) { + my $multiplier = $check_vars->{"$duration_name_anon-mult"}; + if ($multiplier && $multiplier != 1) { + my $num_comm = $reader->getNumCommPostedAnonByIPID($user->{ipid}); + $duration *= ($multiplier ** $num_comm); + $duration = int($duration + 0.5); + } + } + } + + + return $duration; +} + + 1; Copied: slashjp/trunk/plugins/ResKey/ResKey/Checks/HumanConf.pm (from rev 571, slashjp/branches/upstream/current/plugins/ResKey/ResKey/Checks/HumanConf.pm) =================================================================== --- slashjp/trunk/plugins/ResKey/ResKey/Checks/HumanConf.pm (rev 0) +++ slashjp/trunk/plugins/ResKey/ResKey/Checks/HumanConf.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -0,0 +1,105 @@ +# This code is a part of Slash, and is released under the GPL. +# Copyright 1997-2005 by Open Source Technology Group. See README +# and COPYING for more information, or see http://slashcode.com/. +# $Id: HumanConf.pm,v 1.3 2008/04/02 15:15:45 pudge Exp $ + +package Slash::ResKey::Checks::HumanConf; + +use warnings; +use strict; + +use Slash::Utility; +use Slash::Constants ':reskey'; + +use base 'Slash::ResKey::Key'; + +our($VERSION) = ' $Revision: 1.3 $ ' =~ /\$Revision:\s+([^\s]+)/; + +sub updateResKey { + my($self) = @_; + return unless useHumanConf($self); + + my $user = getCurrentUser(); + return unless $user; + + my $hc = getObject('Slash::HumanConf'); + return unless $hc; + + $hc->updateFormkeyHCValue($user->{state}{hcid}, $self->reskey); +} + +sub doCheckCreate { + my($self) = @_; + + return RESKEY_SUCCESS unless useHumanConf($self); + + my $hc = getObject('Slash::HumanConf'); + + if (!$hc || !$hc->createFormkeyHC($self->resname, { frkey => $self->reskey, needs_hc => 1 })) { + return(RESKEY_DEATH, ["HumanConf failure"]); + } + + return RESKEY_SUCCESS; +} + +sub doCheckTouch { + my($self) = @_; + return RESKEY_SUCCESS unless useHumanConf($self); + + my $hc = getObject('Slash::HumanConf'); + if (!$hc || !$hc->reloadFormkeyHC($self->resname, { frkey => $self->reskey, needs_hc => 1 })) { + return(RESKEY_DEATH, ['invalidhc']); + } + + return RESKEY_SUCCESS; +} + +sub doCheckUse { + my($self) = @_; + return RESKEY_SUCCESS unless useHumanConf($self); + + my $hc = getObject('Slash::HumanConf'); + return(RESKEY_DEATH, ["HumanConf failure"]) if !$hc; + + # form->{hcanswer} + my $return = $hc->validFormkeyHC($self->resname, { frkey => $self->reskey, needs_hc => 1 }); + if ($return ne 'ok') { + $hc->reloadFormkeyHC($self->resname, { frkey => $self->reskey, needs_hc => 1 }); + return(($return =~ /retry/ ? RESKEY_FAILURE : RESKEY_DEATH), [$return]); + } + + return RESKEY_SUCCESS; +} + + +sub useHumanConf { + my($self) = @_; + my $constants = getCurrentStatic(); + my $user = getCurrentUser(); + + return 0 if + # HumanConf is not running... + !$constants->{plugin}{HumanConf} + || !$constants->{hc}; + + # stolen from comments.pl + if ($self->resname eq 'comments') { + return 0 if + # ...or it's turned off for comments... + $constants->{hc_sw_comments} == 0 + # ...or it's turned off for logged-in users + # and this user is logged-in... + || $constants->{hc_sw_comments} == 1 + && !$user->{is_anon} + # ...or it's turned off for logged-in users + # with high enough karma, and this user + # qualifies. + || $constants->{hc_sw_comments} == 2 + && !$user->{is_anon} + && $user->{karma} > $constants->{hc_maxkarma}; + } + + return 1; +} + +1; Modified: slashjp/trunk/plugins/ResKey/ResKey/Key.pm =================================================================== --- slashjp/trunk/plugins/ResKey/ResKey/Key.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/ResKey/ResKey/Key.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -141,7 +141,15 @@ if ($reskey) { $reskey =~ s|[^a-zA-Z0-9_]+||g; } elsif (!defined $reskey) { - $reskey = $opts->{nostate} ? '' : getCurrentForm('reskey'); + $reskey = getCurrentForm('reskey') unless $opts->{nostate}; # if we already have one + if (!$reskey) { + if ($self->static) { + $reskey = $self->makeStaticKey; + } else { + $reskey = $self->createResKey; + $self->unsaved(1); # still needs to be inserted/checked + } + } } # reskey() to set the value is called only here and from dbCreate @@ -233,7 +241,7 @@ if ($name =~ /^(?:noop|success|failure|death)$/) { $sub = _createStatusAccessor($name, \@_); - } elsif ($name =~ /^(?:error|reskey|debug|rkrid|resname|origtype|type|code|opts|static)$/) { + } elsif ($name =~ /^(?:error|reskey|debug|rkrid|resname|origtype|type|code|opts|static|unsaved)$/) { $sub = _createAccessor($name, \@_); } elsif ($name =~ /^(?:create|touch|use|createuse)$/) { @@ -315,9 +323,7 @@ # create is done for use, too. if ($self->type eq 'createuse') { $self->type('use'); - unless ($self->reskey) { - $self->dbCreate; - } + $self->dbCreate if $self->unsaved; } if ($self->type eq 'use' && !$self->static) { @@ -413,6 +419,11 @@ } #======================================================================== +sub createResKey { + return getAnonId(1, 20); +} + +#======================================================================== sub dbCreate { my($self) = @_; $self->_flow; @@ -431,16 +442,16 @@ # bothering). When we do pull it out, remember to lose # the 'use Time::HiRes' :) if ($self->static) { - $self->reskey($self->makeStaticKey); $ok = 1; } else { - my $reskey = ''; + my $reskey; + $reskey = $self->reskey if $self->unsaved; my $srcid = $self->getSrcid; my $try_num = 1; my $num_tries = 10; while ($try_num < $num_tries) { - $reskey = getAnonId(1, 20); + $reskey ||= $self->createResKey; my $rows = $slashdb->sqlInsert('reskeys', { reskey => $reskey, rkrid => $self->rkrid, @@ -451,10 +462,14 @@ if ($rows > 0) { $self->reskey($reskey); + $self->unsaved(0); $ok = 1; last; } + # blank for next try + $reskey = ''; + # The INSERT failed because $reskey is already being # used. Presumably this would be due to a collision # in the randomly-generated string, which indicates @@ -470,7 +485,13 @@ Time::HiRes::sleep(rand($try_num)); } if ($try_num > 1) { + $try_num--; errorLog("Slash::ResKey::Key->create INSERT failed $try_num times: uid=$user->{uid} rkrid=$self->{rkrid} reskey=$reskey"); + # XXX: this should be more modularized, bad to keep + # this all here, but OK to hack in for now -- pudge + if (defined &Slash::ResKey::Checks::HumanConf::updateResKey) { + Slash::ResKey::Checks::HumanConf::updateResKey($self); + } } } @@ -973,6 +994,15 @@ return $salts; } +sub ERROR { + my($self, $extra, $user) = @_; + $extra ||= ''; + $user ||= getCurrentUser(); + printf STDERR "AJAXE %d: UID:%d, extra:%s: %s (%s) (%s:%s:%s:%s:%s:%s:%s)\n", + $$, $user->{uid}, $extra, $self->errstr, $self->error->[0], $self->reskey, + $self->type, $self->resname, $self->rkrid, $self->code, $self->static, + $user->{srcids}{ 24 }; +} 1; Modified: slashjp/trunk/plugins/ResKey/example.plx =================================================================== --- slashjp/trunk/plugins/ResKey/example.plx 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/ResKey/example.plx 2008-04-07 00:26:30 UTC (rev 572) @@ -11,28 +11,35 @@ for (1..1) { $reskey = getObject('Slash::ResKey'); - $lkey = $reskey->key('comments-moderation-ajax', { - debug => $debug - }); +# $lkey = $reskey->key('comments-moderation-ajax', { +# debug => $debug +# }); +# +# handle($lkey, 'create'); +# handle($lkey, 'use') for 0..19; +# +# +# $rkey = $reskey->key('pollbooth', { +# debug => $debug, +# qid => 1 +# }); +# handle($rkey, 'createuse'); - handle($lkey, 'create'); - handle($lkey, 'use') for 0..19; - - $rkey = $reskey->key('pollbooth', { - debug => $debug, - qid => 1 - }); - handle($rkey, 'createuse'); - - $rkey1 = $reskey->key('comments', { debug => $debug }); handle($rkey1, 'create'); handle($rkey1, 'touch'); + chomp(my $answer = <>); + getCurrentForm()->{hcanswer} = $answer; + handle($rkey1, 'use'); +use Data::Dumper; +print $rkey1; +exit; + $rkey2 = $reskey->key('comments', { debug => $debug, reskey => $rkey1->reskey, Modified: slashjp/trunk/plugins/ResKey/mysql_dump.sql =================================================================== --- slashjp/trunk/plugins/ResKey/mysql_dump.sql 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/ResKey/mysql_dump.sql 2008-04-07 00:26:30 UTC (rev 572) @@ -54,6 +54,7 @@ INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::AL2::NoPost', 501); INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::AL2::Spammer', 531); INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::Duration', 601); +INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::HumanConf', 701); #INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'use', 'Slash::ResKey::Checks::ProxyScan', 1001); # dummy example of how to disable the Slash::ResKey::Checks::User check for "touch" @@ -68,6 +69,10 @@ INSERT INTO reskey_vars VALUES (1, 'duration_max-failures', 10, 'how many failures per reskey'); INSERT INTO reskey_vars VALUES (1, 'duration_uses', 60, 'min duration (in seconds) between uses'); INSERT INTO reskey_vars VALUES (1, 'duration_creation-use', 10, 'min duration between (in seconds) creation and use'); +INSERT INTO reskey_vars VALUES (1, 'duration_uses-anon', 300, 'duration_uses for anon'); +INSERT INTO reskey_vars VALUES (1, 'duration_uses-anon-trolla', 3600, 'duration_uses for anon + trolla AL2'); +INSERT INTO reskey_vars VALUES (1, 'duration_uses-trolla', 300, 'duration_uses for tolla AL2'); +INSERT INTO reskey_vars VALUES (1, 'duration_uses-anon-mult', 1.5, 'multiply by this amount for each comment previously posted in the past 24 hours'); Modified: slashjp/trunk/plugins/ResKey/templates/data;reskey;default =================================================================== --- slashjp/trunk/plugins/ResKey/templates/data;reskey;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/ResKey/templates/data;reskey;default 2008-04-07 00:26:30 UTC (rev 572) @@ -75,6 +75,16 @@ You must wait a little bit before using this resource; please try again later. +[%# HumanConf %] +[% CASE 'invalidhcretry' %] + You failed to confirm you are a human. Please double-check the image and + make sure you typed in what it says. + +[% CASE 'invalidhc' %] + You failed to confirm you are a human. Please start from the beginning + and try again. If you are a human, we apologize for the inconvenience. + + [%# ProxyScan %] [% CASE 'open proxy' %] You may not post using an open proxy. Modified: slashjp/trunk/plugins/TagDataView/TagDataView.pm =================================================================== --- slashjp/trunk/plugins/TagDataView/TagDataView.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/TagDataView/TagDataView.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -105,7 +105,6 @@ my $tagsdb = getObject('Slash::Tags'); my $tagnames = $tagsdb->getNegativeTags; $tagnames = ['nix'] unless @$tagnames; - #$constants->{tags_negative_tagnames} || $constants->{tags_downvote_tagname} || 'nix'; my $tagnameids = join ',', grep $_, map { s/\s+//g; $tagsdb->getTagnameidFromNameIfExists($_) Modified: slashjp/trunk/plugins/TagModeration/TagModeration.pm =================================================================== --- slashjp/trunk/plugins/TagModeration/TagModeration.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/TagModeration/TagModeration.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -955,7 +955,7 @@ } } - my $removed_text = slashDisplay('undo_mod', { removed => $removed }, { Return => 1 }); + my $removed_text = slashDisplay('undo_mod', { removed => $removed }, { Return => 1, Page => 'comments' }); return $removed_text; } Modified: slashjp/trunk/plugins/Tags/Clout/Describe.pm =================================================================== --- slashjp/trunk/plugins/Tags/Clout/Describe.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Tags/Clout/Describe.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -19,19 +19,36 @@ # propagate up to 50% of the weight, the second another 25%, the # third another 12.5% etc. $self->{cumfrac} = 0.5; - $self->{debug_uids} = { }; + my $constants = getCurrentStatic(); + $self->{debug_uids} = { map { ($_, 1) } split / /, + ($constants->{tags_updateclouts_debuguids} || '') + }; $self->{debug} = 0; 1; } sub getUserClout { my($self, $user_stub) = @_; - my $clout = $user_stub->{karma} >= -3 - ? log($user_stub->{karma}+10) - : 0; + my $clout; + my $karma = $user_stub->{karma}; + if ($karma >= 1) { + # Full graduated clout for positive karma. + $clout = log($karma+5); # karma 1 clout 1.8 ; karma 50 clout 4.0 + } elsif ($karma == 0) { + # Karma of 0 means low clout. + $clout = 0.2; + } elsif ($karma >= -2) { + # Mild negative karma means extremely low clout. + $clout = ($karma+3)*0.01; + } else { + # Significant negative karma means no clout. + $clout = 0; + } $clout += 5 if $user_stub->{seclev} > 1; $clout *= $user_stub->{tag_clout}; + # An account created within the past 3 days has low clout. + # Once an account reaches 30 days old, it gets full clout. my $created_at_ut; if (defined($user_stub->{created_at_ut})) { $created_at_ut = $user_stub->{created_at_ut}; Modified: slashjp/trunk/plugins/Tags/Clout/Vote.pm =================================================================== --- slashjp/trunk/plugins/Tags/Clout/Vote.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Tags/Clout/Vote.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -19,16 +19,33 @@ # propagate up to 50% of the weight, the second another 25%, the # third another 12.5% etc. $self->{cumfrac} = 0.45; - $self->{debug_uids} = { }; + my $constants = getCurrentStatic(); + $self->{debug_uids} = { map { ($_, 1) } split / /, + ($constants->{tags_updateclouts_debuguids} || '') + }; $self->{debug} = 0; 1; } sub getUserClout { my($self, $user_stub) = @_; - my $clout = $user_stub->{karma} >= -3 - ? log($user_stub->{karma}+10)/50 - : 0; + + my $clout; + my $karma = $user_stub->{karma}; + if ($karma >= 1) { + # Full graduated clout for positive karma. + $clout = log($karma+5); # karma 1 clout 1.8 ; karma 50 clout 4.0 + } elsif ($karma == 0) { + # Karma of 0 means low clout. + $clout = 0.3; + } elsif ($karma >= -2) { + # Mild negative karma means extremely low clout. + $clout = ($karma+3)*0.01; + } else { + # Significant negative karma means no clout. + $clout = 0; + } + $clout *= $user_stub->{tag_clout}; my $created_at_ut; @@ -38,7 +55,7 @@ $created_at_ut = str2time( $user_stub->{created_at} ) || 0; } my $secs_since = time - $created_at_ut; - my $frac = $secs_since / (120*86400); + my $frac = $secs_since / (30*86400); $frac = 0.1 if $frac < 0.1; $frac = 1 if $frac > 1; $clout *= $frac; Modified: slashjp/trunk/plugins/Tags/PLUGIN =================================================================== --- slashjp/trunk/plugins/Tags/PLUGIN 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Tags/PLUGIN 2008-04-07 00:26:30 UTC (rev 572) @@ -20,4 +20,6 @@ template=templates/tagsurldivadmin;misc;default template=templates/taghistory;misc;default template=templates/usertaghistory;users;default +template=templates/usertagnames;users;default template=templates/usertags;users;default +template=templates/usertagsforname;users;default Modified: slashjp/trunk/plugins/Tags/Tags.pm =================================================================== --- slashjp/trunk/plugins/Tags/Tags.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Tags/Tags.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -728,6 +728,8 @@ my $orderdir = uc($options->{orderdir}) eq "DESC" ? "DESC" : "ASC"; my $inact_clause = $options->{include_inactive} ? '' : ' AND inactivated IS NULL'; my $private_clause = $options->{include_private} ? '' : " AND private='no'"; + my $tagname_clause = $options->{tagnameid} + ? ' AND tags.tagnameid=' . $self->sqlQuote($options->{tagnameid}) : ''; my($table_extra, $where_extra) = ("",""); my $uid_q = $self->sqlQuote($uid); @@ -752,7 +754,7 @@ 'tags.*', "tags $table_extra", "tags.uid = $uid_q - $inact_clause $private_clause $where_extra", + $inact_clause $private_clause $tagname_clause $where_extra", "ORDER BY $orderby $orderdir $limit"); return [ ] unless $ar && @$ar; $self->dataConversionForHashrefArray($ar); @@ -845,6 +847,7 @@ sub getAllObjectsTagname { my($self, $name, $options) = @_; + my $constants = getCurrentStatic(); # my $mcd = undef; # my $mcdkey = undef; # if (!$options->{include_private}) { @@ -855,15 +858,24 @@ # my $value = $mcd->get("$mcdkey$name"); # return $value if defined $value; # } - my $private_clause = ref($options) && $options->{include_private} ? '' : " AND private='no'"; + $options = { } if !$options || !ref $options; + my $private_clause = $options->{include_private} ? '' : " AND private='no'"; my $id = $self->getTagnameidFromNameIfExists($name); return [ ] if !$id; + # XXX make this degrade gracefully if plugins/FireHose not installed + my $firehose_db = getObject('Slash::FireHose'); + my $min_pop = $options->{min_pop} + || $firehose_db->getMinPopularityForColorLevel( $constants->{tags_active_mincare} || 5 ); + # 117K rows unjoined, 7 seconds ; 10K rows unjoined, 3 seconds ; 10K rows joined, 18 seconds my $hr_ar = $self->sqlSelectAllHashrefArray( '*, UNIX_TIMESTAMP(created_at) AS created_at_ut', - 'tags', - "tagnameid=$id AND inactivated IS NULL $private_clause", - 'ORDER BY tagid'); + 'tags, firehose', + "tags.globjid=firehose.globjid AND popularity >= $min_pop + AND tagnameid=$id AND inactivated IS NULL $private_clause", + 'ORDER BY tagid DESC LIMIT 5000'); + # 117K rows, 6 minutes ; 10K rows, 30 seconds $self->addGlobjEssentialsToHashrefArray($hr_ar); + # 117K rows, 8 minutes ; 10K rows, 60 seconds $self->addCloutsToTagArrayref($hr_ar); # if ($mcd) { # my $constants = getCurrentStatic(); @@ -1701,6 +1713,7 @@ sub listTagnamesAll { my($self, $clout_type, $options) = @_; + $options = { } if !$options || !ref $options; my $tagname_ar; if ($options->{really_all}) { $tagname_ar = $self->sqlSelectColArrayref('tagname', 'tagnames', @@ -1720,10 +1733,12 @@ sub listTagnamesActive { my($self, $options) = @_; my $constants = getCurrentStatic(); - my $max_num = ref($options) && $options->{max_num} || 100; - my $seconds = ref($options) && $options->{seconds} || (3600*6); - my $include_private = ref($options) && $options->{include_private} || 0; - my $min_slice = ref($options) && $options->{min_slice} || 0; + $options = { } if !$options || !ref $options; + my $max_num = defined($options->{max_num}) ? $options->{max_num} : 100; + my $seconds = defined($options->{seconds}) ? $options->{seconds} : (3600*6); + my $include_private = defined($options->{include_private}) ? $options->{include_private} : 0; + my $min_slice = defined($options->{min_slice}) ? $options->{min_slice} : 0; + my $min_clout = defined($options->{min_clout}) ? $options->{min_clout} : $constants->{tags_stories_top_minscore} || 0; $min_slice = 0 if !$constants->{plugin}{FireHose}; # This seems like a horrendous query, but I _think_ it will run @@ -1809,7 +1824,7 @@ # List all tags with at least a minimum clout. my @tagnames = grep - { $tagname_clout{$_} >= $constants->{tags_stories_top_minscore} } + { $tagname_clout{$_} >= $min_clout } keys %tagname_clout; # Sort by sum of normalized clout and (opposite of) last-seen time. @@ -1828,8 +1843,9 @@ sub listTagnamesRecent { my($self, $options) = @_; my $constants = getCurrentStatic(); - my $seconds = ref($options) && $options->{seconds} || (3600*6); - my $include_private = ref($options) && $options->{include_private} || 0; + $options = { } if !$options || !ref $options; + my $seconds = $options->{seconds} || (3600*6); + my $include_private = $options->{include_private} || 0; my $private_clause = $include_private ? '' : " AND private='no'"; my $recent_ar = $self->sqlSelectColArrayref( 'DISTINCT tagnames.tagname', @@ -2019,19 +2035,48 @@ 'tagnameid, tagname', 'tagnames', "tagname IN ($tagname_str)"); - my $tagnameid_str = join(',', map { $self->sqlQuote($_) } sort keys %$tagnameid_to_name); + my $tagnameid_recent_ar = [ sort { $a <=> $b } keys %$tagnameid_to_name ]; my $tagname_to_id = { reverse %$tagnameid_to_name }; - # Next, build a hash identifying which of those are new tagnames, - # i.e. which were used for the first time within the same recent - # time interval we're looking at. - my $tagname_firstrecent_ar = $self->sqlSelectColArrayref( - 'tagname, MIN(created_at) AS firstuse', - 'tagnames, tags', - "tagnames.tagnameid IN ($tagnameid_str) - AND tagnames.tagnameid=tags.tagnameid", - "GROUP BY tagname - HAVING firstuse >= DATE_SUB(NOW(), INTERVAL $secsback SECOND)"); + # Heuristic to optimize the selection process. Right now we have + # a list of many tagnameids (say, around 1000), most of which are + # not new (were used prior to the time interval in question). + # We eliminate those known not to be new by finding the newest + # tagnameid for the day prior to the time interval, then grepping + # out tagnameids in our list less than that. + # + # That should leave us with a much shorter list which will be + # processed much faster. On a non-busy site or during a + # pathological case where nobody types in any new tagnames for an + # entire day, the worst case here is that the processing is as + # slow as it was prior to this optimization (up to a minute or so + # on Slashdot). + my $tagid_secsback = $self->sqlSelect('MIN(tagid)', 'tags', + "created_at >= DATE_SUB(NOW(), INTERVAL $secsback SECOND)") + || 0; + my $secsback_1moreday = $secsback + 86400; + my $tagid_secsback_1moreday = $self->sqlSelect('MIN(tagid)', 'tags', + "created_at >= DATE_SUB(NOW(), INTERVAL $secsback_1moreday SECOND)") + || 0; + my $max_previously_known_tagnameid = $self->sqlSelect('MAX(tagnameid)', 'tags', + "tagid BETWEEN $tagid_secsback_1moreday AND $tagid_secsback") + || 0; + $tagnameid_recent_ar = [ + grep { $_ > $max_previously_known_tagnameid } + @$tagnameid_recent_ar ] + if $max_previously_known_tagnameid > 0; + my $tagnameid_str = join(',', map { $self->sqlQuote($_) } @$tagnameid_recent_ar); + + # Now do the select to find the actually-new tagnameids. + my $tagnameid_firstrecent_ar = $self->sqlSelectColArrayref( + 'DISTINCT tagnameid', + 'tags', + "tagid >= $tagid_secsback AND tagnameid IN ($tagnameid_str)"); + + # Run through the hash we built earlier to convert ids back to names. + my $tagname_firstrecent_ar = [ + map { $tagnameid_to_name->{$_} } + @$tagnameid_firstrecent_ar ]; my %tagname_firstrecent = ( map { ($_, 1) } @$tagname_firstrecent_ar ); # Build a regex that will identify tagnames that begin with an @@ -2085,7 +2130,7 @@ '*', 'tags', "tagnameid IN ($tagnameids_of_interest_str) - AND created_at >= DATE_SUB(NOW(), INTERVAL $secsback SECOND)"); + AND tagid >= $tagid_secsback"); $self->addCloutsToTagArrayref($tags_ar); my %tagnameid_weightsum = ( ); my %t_globjid_weightsum = ( ); @@ -2158,11 +2203,16 @@ sub showRecentTagnamesBox { my($self, $options) = @_; - my $rtoi_ar = $self->getRecentTagnamesOfInterest($options); + $options ||= {}; - my $text = slashDisplay('recenttagnamesbox', { - rtoi => $rtoi_ar, - }, { Return => 1 }); + my $text = " "; + + unless ($options->{box_only}) { + my $rtoi_ar = $self->getRecentTagnamesOfInterest(); + $text = slashDisplay('recenttagnamesbox', { + rtoi => $rtoi_ar, + }, { Return => 1 }); + } return $text if $options->{contents_only}; Modified: slashjp/trunk/plugins/Tags/mysql_dump.sql =================================================================== --- slashjp/trunk/plugins/Tags/mysql_dump.sql 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Tags/mysql_dump.sql 2008-04-07 00:26:30 UTC (rev 572) @@ -4,6 +4,7 @@ INSERT INTO vars (name, value, description) VALUES ('memcached_exptime_tags', '3600', 'Seconds to cache tag data in memcached'); INSERT INTO vars (name, value, description) VALUES ('memcached_exptime_tags_brief', '300', 'Seconds to cache tag data that only needs brief caching, in memcached'); +INSERT INTO vars (name, value, description) VALUES ('tags_active_maxshow', '200', 'Maximum number of tagged objects to display on /tags/foo'); INSERT INTO vars (name, value, description) VALUES ('tags_active_mincare', '5', 'Minimum color slice number to "care" about tags for the Slashdot Recent Tags box (only matters if FireHose installed and if you are Slashdot)'); INSERT INTO vars (name, value, description) VALUES ('tags_admin_private_tags', '', 'List of tags separated by | that are private for admins'); INSERT INTO vars (name, value, description) VALUES ('tags_admin_autoaddstorytopics', '1', 'Auto-add tags for story topic keywords?'); @@ -34,10 +35,11 @@ INSERT INTO vars (name, value, description) VALUES ('tags_urls_neg_tags', 'minus|binspam', '| separated list of tags applied which negatively affect url popularity'); INSERT INTO vars (name, value, description) VALUES ('tags_userfrac_read', '1', 'Fraction (0.0-1.0) of user UIDs which are allowed to read tags, if tags_*_allow* is set that way'); INSERT INTO vars (name, value, description) VALUES ('tags_userfrac_write', '0.95', 'Fraction (0.0-1.0) of user UIDs which are allowed to tag, if tags_*_allow* is set that way'); +INSERT INTO vars (name, value, description) VALUES ('tags_usershow_cutoff', '200', 'More tags than this, and instead of showing the full taglist /~user/tags will show the list of tagnames'); INSERT INTO vars (name, value, description) VALUES ('tags_tagname_regex', '^\!?[a-z][a-z0-9/]{0,63}$', 'Regex that tag names must conform to'); +INSERT INTO vars (name, value, description) VALUES ('tags_updateclouts_debuguids', '', 'UIDs to print debug info on during clout recalculation'); INSERT INTO vars (name, value, description) VALUES ('tags_upvote_tagname', 'nod', 'Tag for upvote'); INSERT INTO vars (name, value, description) VALUES ('tags_downvote_tagname', 'nix', 'Tag for downvote'); -INSERT INTO vars (name, value, description) VALUES ('tags_negative_tagnames', 'nix,dupe,whocares', 'Negative tags (comma separated)'); INSERT INTO vars (name, value, description) VALUES ('tags_viewed_tagname', 'viewed', 'Tagname to assign to stories and other items a user has viewed'); INSERT INTO ajax_ops VALUES (NULL, 'tags_get_user_story', 'Slash::Tags', 'ajaxGetUserStory', 'ajax_tags_write', 'createuse'); Modified: slashjp/trunk/plugins/Tags/tags.pl =================================================================== --- slashjp/trunk/plugins/Tags/tags.pl 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Tags/tags.pl 2008-04-07 00:26:30 UTC (rev 572) @@ -35,11 +35,11 @@ : 'active'; if ($type eq 'all') { - $index_hr->{tagnames} = $tags_reader->listTagnamesAll('describe'); + $index_hr->{tagnames} = $tags_reader->listTagnamesAll(); } elsif ($type eq 'active') { - $index_hr->{tagnames} = $tags_reader->listTagnamesActive('describe'); + $index_hr->{tagnames} = $tags_reader->listTagnamesActive(); } else { # recent - $index_hr->{tagnames} = $tags_reader->listTagnamesRecent('describe'); + $index_hr->{tagnames} = $tags_reader->listTagnamesRecent(); } $title = getData('head1'); @@ -61,7 +61,7 @@ @objects = @$value; #print STDERR "tags.pl got '$mcdkey$tagname' as " . scalar(@objects) . " objects\n"; } else { - my $objects = $tags_reader->getAllObjectsTagname($tagname, 'describe'); + my $objects = $tags_reader->getAllObjectsTagname($tagname); my %globjids = ( map { ( $_->{globjid}, 1 ) } @$objects ); my $mintc = defined($constants->{tags_list_mintc}) ? $constants->{tags_list_mintc} : 4; for my $globjid (keys %globjids) { @@ -73,12 +73,18 @@ push @objects, { url => $objs[0]{url}, title => $objs[0]{title}, - count => $sum_tc, + clout => $sum_tc, } if $sum_tc >= $mintc; } - @objects = sort { $b->{count} <=> $a->{count} || ($a->{title}||'') cmp ($b->{title}||'') } @objects; + @objects = sort { + $b->{clout} <=> $a->{clout} + || ($a->{title}||'') cmp ($b->{title}||'') + } @objects; + my $max_display = $constants->{tags_active_maxshow} || 200; + if (scalar @objects > $max_display) { + $#objects = $max_display-1; + } if ($mcd) { - my $constants = getCurrentStatic(); my $secs = $constants->{memcached_exptime_tags_brief} || 300; $mcd->set("$mcdkey$tagname", \@objects, $secs); #print STDERR "tags.pl set '$mcdkey$tagname' to " . scalar(@objects) . " objects\n"; Modified: slashjp/trunk/plugins/Tags/templates/usertaghistory;users;default =================================================================== --- slashjp/trunk/plugins/Tags/templates/usertaghistory;users;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Tags/templates/usertaghistory;users;default 2008-04-07 00:26:30 UTC (rev 572) @@ -24,7 +24,7 @@ [% IF tag.globj_type == "stories" %] [% PROCESS linkStory dynamic=1 sid=tag.story.sid text=tag.story.title title=tag.story.title %] [% ELSIF tag.globj_type == "urls" %] - <a href="[% tag.url.url %]">[% tag.url.bookmark.title || tag.url.validtitle || tag.url.initialtitle | strip_literal %]</a> + <a href="[% tag.url.url %]" rel="nofollow">[% tag.url.bookmark.title || tag.url.validtitle || tag.url.initialtitle | strip_literal %]</a> [% ELSIF tag.globj_type == "journals" %] [% nick = Slash.db.getUser(tag.journal.uid, 'nickname') %] <a href="[% gSkin.rootdir %]/~[% nick | fixparam %]/journal/[% tag.journal.id %]/">[% tag.journal.description | strip_literal %]</a> Copied: slashjp/trunk/plugins/Tags/templates/usertagnames;users;default (from rev 571, slashjp/branches/upstream/current/plugins/Tags/templates/usertagnames;users;default) =================================================================== --- slashjp/trunk/plugins/Tags/templates/usertagnames;users;default (rev 0) +++ slashjp/trunk/plugins/Tags/templates/usertagnames;users;default 2008-04-07 00:26:30 UTC (rev 572) @@ -0,0 +1,43 @@ +__section__ +default +__description__ +useredit = user whose tags are being displayed +user_submenu = html text of user submenu, if any +tagnames = array of tagnames + +This template and its API will likely change. + +__title__ + +__page__ +users +__lang__ +en_US +__name__ +usertagnames +__template__ +[% thisnickname = useredit.nickname | strip_literal; + url_nick = useredit.nickname | strip_paramattr; + url_base = "$gSkin.rootdir/~$url_nick/"; + uid = useredit.uid; + title = "Tags by <a href=\"$url_base\">$thisnickname ($uid)</a>"; + PROCESS user_titlebar tab_selected='tags' %] + +[% IF user.tags_canread_stories && tagnames.size %] +<div><table border="0" cellpadding="0" cellspacing="0" width="100%" class="tagslist"> +[% FOREACH tagname = tagnames %] + <tr><td class="tagname" valign="top"><a href="[% url_base %]tags/[% tagname | strip_paramattr %]">[% tagname | strip_html %]</a></td></tr> +[% END %] +</table></div> +[% ELSE %] + [% IF !user.tags_canread_stories %] + <div>You are unable to read tags at this time.</div> + [% ELSE %] + <div>No tags have been assigned.</div> + [% END %] +[% END %] + +__seclev__ +10000 +__version__ +$Id: usertagnames;users;default,v 1.1 2008/03/27 00:10:26 jamiemccarthy Exp $ Modified: slashjp/trunk/plugins/Tags/templates/usertags;users;default =================================================================== --- slashjp/trunk/plugins/Tags/templates/usertags;users;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/plugins/Tags/templates/usertags;users;default 2008-04-07 00:26:30 UTC (rev 572) @@ -18,6 +18,7 @@ __template__ [% thisnickname = useredit.nickname | strip_literal; url_nick = useredit.nickname | strip_paramattr; + url_base = "$gSkin.rootdir/~$url_nick/"; uid = useredit.uid; title = "Tags by <a href=\"$gSkin.rootdir/~$url_nick/\">$thisnickname ($uid)</a>"; PROCESS user_titlebar tab_selected='tags' %] @@ -28,18 +29,18 @@ tagname_shown = 0; FOREACH tag = tags_grouped.$tagname %] [% UNLESS tag.globj_type == "submissions" %] - <tr>[% IF !tagname_shown; tagname_shown = 1 %]<td class="tagname" valign="top">[% tagname | strip_html %]</td>[% ELSE %]<td> </td>[% END %] + <tr>[% IF !tagname_shown; tagname_shown = 1 %]<td class="tagname" valign="top"><a href="[% url_base %]tags/[% tagname | strip_paramattr %]">[% tagname | strip_literal %]</a></td>[% ELSE %]<td> </td>[% END %] <td> </td> - <td valign="top"> - [% IF tag.globj_type == "stories" %] - [% PROCESS linkStory dynamic=1 sid=tag.story.sid text=tag.story.title title=tag.story.title %] - [% ELSIF tag.globj_type == "urls" %] - <a href="[% tag.url.url %]">[% tag.url.bookmark.title || tag.url.validtitle || tag.url.initialtitle | strip_literal %]</a> - [% ELSIF tag.globj_type == "journals" %] - [% nick = Slash.db.getUser(tag.journal.uid, 'nickname') %] - <a href="[% gSkin.rootdir %]/~[% nick | fixparam %]/journal/[% tag.journal.id %]/">[% tag.journal.description | strip_literal %]</a> - [% END %] - </td> + <td valign="top"> + [% IF tag.globj_type == "stories" %] + [% PROCESS linkStory dynamic=1 sid=tag.story.sid text=tag.story.title title=tag.story.title %] + [% ELSIF tag.globj_type == "urls" %] + <a href="[% tag.url.url %]" rel="nofollow">[% tag.url.bookmark.title || tag.url.validtitle || tag.url.initialtitle | strip_literal %]</a> + [% ELSIF tag.globj_type == "journals" %] + [% nick = Slash.db.getUser(tag.journal.uid, 'nickname') %] + <a href="[% gSkin.rootdir %]/~[% nick | fixparam %]/journal/[% tag.journal.id %]/">[% tag.journal.description | strip_literal %]</a> + [% END %] + </td> </tr> [% END %] [% END; Copied: slashjp/trunk/plugins/Tags/templates/usertagsforname;users;default (from rev 571, slashjp/branches/upstream/current/plugins/Tags/templates/usertagsforname;users;default) =================================================================== --- slashjp/trunk/plugins/Tags/templates/usertagsforname;users;default (rev 0) +++ slashjp/trunk/plugins/Tags/templates/usertagsforname;users;default 2008-04-07 00:26:30 UTC (rev 572) @@ -0,0 +1,58 @@ +__section__ +default +__description__ +useredit = user whose tags for a particular tagname are being displayed +tagname = the tagname +tags = an arrayref of hashrefs of the public tags applied by that user with that tagname + +This template and its API will likely change. + +__title__ + +__page__ +users +__lang__ +en_US +__name__ +usertagsforname +__template__ +[% thisnickname = useredit.nickname | strip_literal; + thistagname = tagname | strip_literal; + url_nick = useredit.nickname | strip_paramattr; + url_tagname = tagname | strip_paramattr; + url_base = "$gSkin.rootdir/~$url_nick/"; + uid = useredit.uid; + title = "Items tagged <a href=\"$gSkin.rootdir/tags/$url_tagname\">$thistagname</a> by <a href=\"$url_base\">$thisnickname ($uid)</a>"; + PROCESS user_titlebar tab_selected='tags' %] + +[% IF user.tags_canread_stories && tags.size %] +<div><table border="0" cellpadding="0" cellspacing="0" width="100%" class="tagslist"> +[% FOREACH tag = tags %] + [% UNLESS tag.globj_type == "submissions" %] + <tr> + <td valign="top"> + [% IF tag.globj_type == "stories" %] + [% PROCESS linkStory dynamic=1 sid=tag.story.sid text=tag.story.title title=tag.story.title %] + [% ELSIF tag.globj_type == "urls" %] + <a href="[% tag.url.url %]" rel="nofollow">[% tag.url.bookmark.title || tag.url.validtitle || tag.url.initialtitle | strip_literal %]</a> + [% ELSIF tag.globj_type == "journals" %] + [% nick = Slash.db.getUser(tag.journal.uid, 'nickname') %] + <a href="[% gSkin.rootdir %]/~[% nick | fixparam %]/journal/[% tag.journal.id %]/">[% tag.journal.description | strip_literal %]</a> + [% END %] + </td> + </tr> + [% END; +END %] +</table></div> +[% ELSE %] + [% IF !user.tags_canread_stories %] + <div>You are unable to read tags at this time.</div> + [% ELSE %] + <div>This user hasn't publicly tagged anything "[% thistagname %]".</div> + [% END %] +[% END %] + +__seclev__ +10000 +__version__ +$Id: usertagsforname;users;default,v 1.2 2008/04/02 15:27:13 jamiemccarthy Exp $ Modified: slashjp/trunk/sql/mysql/defaults.sql =================================================================== --- slashjp/trunk/sql/mysql/defaults.sql 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/sql/mysql/defaults.sql 2008-04-07 00:26:30 UTC (rev 572) @@ -833,7 +833,7 @@ INSERT INTO vars (name, value, description) VALUES ('cur_performance_stats_lastid', '0', 'accesslogid to start searching at'); INSERT INTO vars (name, value, description) VALUES ('cur_performance_stats_weeks', '8', 'number of weeks back to compare current stats to'); INSERT INTO vars (name, value, description) VALUES ('currentqid',1,'The Current Question on the homepage pollbooth'); -INSERT INTO vars (name, value, description) VALUES ('cvs_tag_currentcode','T_2_5_0_197','The current cvs tag that the code was updated to - this does not affect site behavior but may be useful for your records'); +INSERT INTO vars (name, value, description) VALUES ('cvs_tag_currentcode','T_2_5_0_200','The current cvs tag that the code was updated to - this does not affect site behavior but may be useful for your records'); INSERT INTO vars (name, value, description) VALUES ('datadir','/usr/local/slash/www.example.com','What is the root of the install for Slash'); INSERT INTO vars (name, value, description) VALUES ('db_auto_increment_increment','1','If your master DB uses auto_increment_increment, i.e. multiple master replication, echo its value into this var'); INSERT INTO vars (name, value, description) VALUES ('dbsparklines_disp','0','Display dbsparklines in the currentAdminUsers box?'); Modified: slashjp/trunk/sql/mysql/upgrades =================================================================== --- slashjp/trunk/sql/mysql/upgrades 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/sql/mysql/upgrades 2008-04-07 00:26:30 UTC (rev 572) @@ -5157,8 +5157,6 @@ # 2008-03-12 UPDATE vars SET value = 'T_2_5_0_197' WHERE name = 'cvs_tag_currentcode'; -# SLASHDOT LAST UPDATED HERE - # for plugins/Ajax UPDATE ajax_ops set reskey_name = 'ajax_user_static', reskey_type='createuse' WHERE op='admin_signoff'; @@ -5190,10 +5188,66 @@ UNIQUE tagname (tagname) ) TYPE=InnoDB; - # 2008-03-19 UPDATE vars SET value = 'T_2_5_0_198' WHERE name = 'cvs_tag_currentcode'; +# PUDGE LAST UPDATED HERE + +# for plugins/ResKey +INSERT INTO reskey_resource_checks VALUES (NULL, 1, 'all', 'Slash::ResKey::Checks::HumanConf', 701); +INSERT INTO reskey_vars VALUES (1, 'duration_uses-anon', 300, 'duration_uses for anon'); +INSERT INTO reskey_vars VALUES (1, 'duration_uses-anon-trolla', 3600, 'duration_uses for anon + trolla AL2'); +INSERT INTO reskey_vars VALUES (1, 'duration_uses-trolla', 300, 'duration_uses for tolla AL2'); +INSERT INTO reskey_vars VALUES (1, 'duration_uses-anon-mult', 1.5, 'multiply by this amount for each comment previously posted in the past 24 hours'); + +# for plugins/Tags +INSERT INTO vars (name, value, description) VALUES ('tags_usershow_cutoff', '200', 'More tags than this, and instead of showing the full taglist /~user/tags will show the list of tagnames'); +INSERT INTO vars (name, value, description) VALUES ('tags_active_maxshow', '200', 'Maximum number of tagged objects to display on /tags/foo'); + +# for tagboxes/FHEditorPop +INSERT INTO vars (name, value, description) VALUES ('tagbox_fheditorpop_susp_flagpop', '185', 'Admin score to artificially raise a suspicious host item to until an admin tags it'); +INSERT INTO vars (name, value, description) VALUES ('tagbox_fheditorpop_susp_maxtaggers', '7', 'Max number of unique users tagging (not voting) a low-scoring firehose item that should not raise suspicion'); +INSERT INTO vars (name, value, description) VALUES ('tagbox_fheditorpop_susp_minscore', '100', 'Min score required to not raise suspicion'); + +# for User2 +CREATE TABLE user_events ( + eid int UNSIGNED NOT NULL auto_increment, + code int UNSIGNED NOT NULL default 0, + uid mediumint(8) UNSIGNED NOT NULL default 0, + event int UNSIGNED NOT NULL default 0, + date datetime NOT NULL default '0000-00-00 00:00:00', + PRIMARY KEY eid (eid), + KEY uid (uid) +) TYPE=InnoDB; + +CREATE TABLE user_event_blocks ( + bid int UNSIGNED NOT NULL auto_increment, + uid mediumint UNSIGNED NOT NULL default 0, + code int UNSIGNED NOT NULL default 0, + block varchar(255) default NULL, + PRIMARY KEY bid (bid), + KEY uid (uid) +) TYPE=InnoDB; + +CREATE TABLE user_event_types ( + code smallint UNSIGNED NOT NULL auto_increment, + type varchar(32) NOT NULL default '', + PRIMARY KEY code (code) +) TYPE=InnoDB; + +# 2008-04-02 +UPDATE vars SET value = 'T_2_5_0_199' WHERE name = 'cvs_tag_currentcode'; + +# for tagboxes/RecentTags +INSERT IGNORE INTO vars (name, value, description) VALUES ('tagbox_recenttags_minclout', '4', 'Minimum clout sum to count a tagname'); + +# for plugins/Tags +INSERT INTO vars (name, value, description) VALUES ('tags_updateclouts_debuguids', '', 'UIDs to print debug info on during clout recalculation'); + +# 2008-04-03 +UPDATE vars SET value = 'T_2_5_0_200' WHERE name = 'cvs_tag_currentcode'; + # SLASHCODE/USEPERL LAST UPDATED HERE -# PUDGE LAST UPDATED HERE +# SLASHDOT LAST UPDATED HERE + Modified: slashjp/trunk/tagboxes/FHEditorPop/FHEditorPop.pm =================================================================== --- slashjp/trunk/tagboxes/FHEditorPop/FHEditorPop.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/tagboxes/FHEditorPop/FHEditorPop.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -217,9 +217,30 @@ $popularity += $extra_pop; } + # If more than a certain number of users have tagged this item with + # public non-voting tags and its popularity is low, there may be a + # bad reason why. Boost its editor score up so that an editor sees + # it and can review it. + if ($popularity >= ($constants->{tagbox_fheditorpop_susp_minscore} || 100)) { + my $max_taggers = $constants->{tagbox_fheditorpop_susp_maxtaggers} || 7; + my $flag_pop = $constants->{tagbox_fheditorpop_susp_flagpop} || 185; + my %tagger_uids = ( ); + my $tagged_by_admin = 0; + for my $tag_hr (@$tags_ar) { + next if $tag_hr->{tagnameid} == $upvoteid + || $tag_hr->{tagnameid} == $downvoteid + || $tag_hr->{private}; + my $uid = $tag_hr->{uid}; + $tagged_by_admin = 1, last if $admins->{$uid}; + $tagger_uids{$uid} = 1; + } + if (!$tagged_by_admin && scalar(keys %tagger_uids) > $max_taggers) { + $popularity = $flag_pop; + } + } + # If this is spam, its score goes way down. - my $firehose_db = getObject('Slash::FireHose'); - if ($fhitem->{is_spam} eq 'yes' || $firehose_db->itemHasSpamURL($fhitem)) { + if ($fhitem->{is_spam} eq 'yes' || $firehose->itemHasSpamURL($fhitem)) { my $max = defined($constants->{firehose_spam_score}) ? $constants->{firehose_spam_score} : -50; @@ -227,12 +248,12 @@ } # Set the corresponding firehose row to have this popularity. - warn "Slash::Tagbox::FHEditorPop->run bad data, fhid='$fhid' db='$firehose_db'" if !$fhid || !$firehose_db; + warn "Slash::Tagbox::FHEditorPop->run bad data, fhid='$fhid' db='$firehose'" if !$fhid || !$firehose; if ($options->{return_only}) { return $popularity; } main::tagboxLog(sprintf("FHEditorPop->run setting %d (%d) to %.6f", $fhid, $affected_id, $popularity)); - $firehose_db->setFireHose($fhid, { editorpop => $popularity }); + $firehose->setFireHose($fhid, { editorpop => $popularity }); } { # closure Modified: slashjp/trunk/tagboxes/FHEditorPop/mysql_dump.sql =================================================================== --- slashjp/trunk/tagboxes/FHEditorPop/mysql_dump.sql 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/tagboxes/FHEditorPop/mysql_dump.sql 2008-04-07 00:26:30 UTC (rev 572) @@ -2,4 +2,7 @@ INSERT INTO tagboxes (tbid, name, affected_type, clid, weight, last_run_completed, last_tagid_logged, last_tdid_logged, last_tuid_logged) VALUES (NULL, 'FHEditorPop', 'globj', 2, 1, '2000-01-01 00:00:00', 0, 0, 0); INSERT IGNORE INTO vars (name, value, description) VALUES ('tagbox_fheditorpop_edmult', '10', 'Multiplier by which editor nod/nixes are weighted for editor view'); INSERT IGNORE INTO vars (name, value, description) VALUES ('tagbox_fheditorpop_udcbasis', '1000', 'Basis for tags_udc vote clout weighting'); +INSERT IGNORE INTO vars (name, value, description) VALUES ('tagbox_fheditorpop_susp_flagpop', '185', 'Admin score to artificially raise a suspicious host item to until an admin tags it'); +INSERT IGNORE INTO vars (name, value, description) VALUES ('tagbox_fheditorpop_susp_maxtaggers', '7', 'Max number of unique users tagging (not voting) a low-scoring firehose item that should not raise suspicion'); +INSERT IGNORE INTO vars (name, value, description) VALUES ('tagbox_fheditorpop_susp_minscore', '100', 'Min score required to not raise suspicion'); Copied: slashjp/trunk/tagboxes/RecentTags (from rev 571, slashjp/branches/upstream/current/tagboxes/RecentTags) Modified: slashjp/trunk/tagboxes/Top/Top.pm =================================================================== --- slashjp/trunk/tagboxes/Top/Top.pm 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/tagboxes/Top/Top.pm 2008-04-07 00:26:30 UTC (rev 572) @@ -205,7 +205,6 @@ grep { $_ } map { ($_, $tags_reader->getOppositeTagname($_)) } @{$tags_reader->getExcludedTags} -# split / /, ($constants->{tagbox_top_excludetagnames} || '') ); # Eliminate tagnames that are just the author's name. my @names = map { lc } @{ $tags_reader->getAuthorNames() }; Modified: slashjp/trunk/themes/slashcode/THEME =================================================================== --- slashjp/trunk/themes/slashcode/THEME 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/themes/slashcode/THEME 2008-04-07 00:26:30 UTC (rev 572) @@ -59,6 +59,10 @@ image=htdocs/images/comm-minus.gif image=htdocs/images/comm-plus.gif image=htdocs/images/contract.gif +image=htdocs/images/corner_w_br.png +image=htdocs/images/corner_w_bl.png +image=htdocs/images/corner_w_tr.png +image=htdocs/images/corner_w_tl.png image=htdocs/images/cr.gif image=htdocs/images/ctl_grey.gif image=htdocs/images/ctl_red.gif @@ -126,6 +130,7 @@ htdoc=htdocs/robots.txt htdoc=htdocs/topics.pl htdoc=htdocs/users.pl +htdoc=htdocs/users2.pl htdoc=htdocs/images/comments.js htdoc=htdocs/images/dumper.js htdoc=htdocs/badge.pl @@ -317,9 +322,11 @@ template=templates/undo_mod;comments;default template=templates/url_related;misc;default template=templates/userboxes;misc;default +template=templates/userboxes2;misc;default template=templates/userCom;users;default template=templates/userFireHose;users;default template=templates/userInfo;users;default +template=templates/userInfo2;users;default template=templates/userSub;users;default template=templates/user_titlebar;misc;default template=templates/userlogin;misc;default Modified: slashjp/trunk/themes/slashcode/htdocs/comments.css =================================================================== --- slashjp/trunk/themes/slashcode/htdocs/comments.css 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/themes/slashcode/htdocs/comments.css 2008-04-07 00:26:30 UTC (rev 572) @@ -86,6 +86,7 @@ border: 1px solid #ddd; border-bottom: none; padding: .3em; + overflow: hidden; } .commentshrunk, .commentstatus, .commentload { font-weight: bold } @@ -118,7 +119,7 @@ margin: 0 0 0 -.3em; padding: 0 .3em; text-decoration: none; - background: url("http://images.slashcode.com/comm-minus.gif") 0 .2em no-repeat; + background: url("/images/comm-minus.gif") 0 .2em no-repeat; right: -1.4em; z-index: 2; cursor: pointer; @@ -129,7 +130,7 @@ margin: 0; padding: 0 .3em; text-decoration: none; - background: url("http://images.slashcode.com/comm-plus.gif") 0 .2em no-repeat; + background: url("/images/comm-plus.gif") 0 .2em no-repeat; right: -1.4em; z-index: 1; cursor: pointer; @@ -160,7 +161,7 @@ .comment > .oneline { border: 1px solid #e5e5e5; margin: 1em 0 0 0; - padding: .3em 0 0 0; + padding: 0.35em 0 0 0.25em; position: relative; width: 99%; height: 1.3em; @@ -168,7 +169,8 @@ } .comment > .currcomment { - border: 1px dotted #666; + margin: 0.85em 0px -0.15em -0.15em; + border: .15em solid rgb(142,142,142); } /* lots of space between lines */ @@ -379,3 +381,53 @@ #ccw-hide-bar-tab { border-top-width: 0; } .horizontal #ccw-abbr-bar-tab { border-bottom-width: 1px; border-right: none; } .horizontal #ccw-hide-bar-tab { border-top-width: 1px; border-left: none; } + + +/* Action Buttons */ + +.nbutton{ + background: #666 none top left no-repeat; + color:#fff; + margin:0.25em; + padding: .4em 0; +} +.nbutton p{ + display:inline; + background: transparent none top right no-repeat; + padding: .4em 0; +} +.nbutton p b{ + display:inline; + background: transparent none bottom left no-repeat; + padding: .4em 0; +} +.nbutton p b a { + background: transparent none bottom right no-repeat !important; + padding: .4em 1em; + color:#fff !important; + font-weight:normal; + text-decoration:none; +} +.nbutton:hover {background-color: #444} + +span.current {position: absolute; left: .5em; margin-top: -.2em; z-index: 0; font-weight: bold; font-size: 197%;} + +.contain { + border: .15em solid #d5d5d5; + border-top: none; +} + +.inline_comment {border: 2px solid #666; margin: 1em; position: relative;} +.inline_comment input[type="text"] {width: auto} +.inline_comment textarea {margin: 1em; width: auto} +.inline_comment .generalbody {margin: 0; padding: 0;} +.inline_comment .pref {position: absolute; right: 0; top: -.8em} +.inline_comment .pref a {right: 10px; background-image: url("/images/sic_icons.png"); color: #fff; background-repeat: no-repeat; background-position: 0px -1198px;padding: 5px 10px; position:absolute; text-decoration:none; text-indent:-7000em; top: .8em; } +.inline_comment .state { display: none } +.inline_comment .replyto_buttons, .inlinemsg {margin: .5em 1em;} +.inline_comment #commentlisting {padding: 0; margin: 0;} +.warning {background: #ffd; border: 1px solid #fe6; padding: .5em; margin: .5em 0 1em 0; color: #000;} +.inline_comment .replyto_msg {background: #fdd; border: 1px solid #fbb; padding: .5em; margin: .5em 1em 0; color: #000 } +.inline_comment .commentload {margin: 0 1em; font-weight: bold; color: #aaa;} +.inline_comment .contain {margin: 0; padding: 0; border: none} + Modified: slashjp/trunk/themes/slashcode/htdocs/comments.pl =================================================================== --- slashjp/trunk/themes/slashcode/htdocs/comments.pl 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/themes/slashcode/htdocs/comments.pl 2008-04-07 00:26:30 UTC (rev 572) @@ -609,6 +609,46 @@ ); } + # Add user2 event. This will eventually live in its own method. + if (!$user->{is_anon}) { + my $uid = $user->{uid}; + my $cid = $saved_comment->{cid}; + my $events = $slashdb->sqlSelectAllHashref( + 'eid', 'eid, date', 'user_events', "uid = $uid and code = 1"); + + if ((scalar keys %$events) == 5) { + my $eid = [sort keys %$events]->[0]; + $slashdb->sqlDelete('user_events', "uid = $uid and code = 1 and eid = $eid"); + } + + $slashdb->sqlInsert('user_events', { + code => 1, + uid => $uid, + event => $cid, + -date => 'NOW()', + }); + + my $event_blocks = $slashdb->sqlSelectAllHashref( + 'uid', 'bid, uid, block', 'user_event_blocks', "uid = $uid and code = 1"); + + if (!%$event_blocks) { + $slashdb->sqlInsert('user_event_blocks', { + code => 1, + uid => $uid, + block => $cid, + }); + } else { + my @blocks = split(/,/, $event_blocks->{$uid}->{block}); + @blocks = @blocks[1 .. 4] if (scalar @blocks == 5); + $blocks[$#blocks + 1] = $cid; + my $new_blocks = join(",", @blocks); + + $slashdb->sqlUpdate('user_event_blocks', { block => $new_blocks }, "uid = $uid and code = 1"); + } + } + + + # OK -- if we make it all the way here, and there were # no errors so no header has been emitted, and we were # asked to redirect to a new URL, NOW we can finally Modified: slashjp/trunk/themes/slashcode/htdocs/images/comments.js =================================================================== --- slashjp/trunk/themes/slashcode/htdocs/images/comments.js 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/themes/slashcode/htdocs/images/comments.js 2008-04-07 00:26:30 UTC (rev 572) @@ -84,7 +84,7 @@ if (!cd.innerHTML) { var cs = fetchEl('comment_sub_' + cid); if (cs) - cs.innerHTML = '<span class="commentload">Loading ...</span>'; + cs.innerHTML = '<span class="commentload">Loading...</span>'; fetch_comments.push(cid); fetch_comments_pieces[cid] = 1; doshort = 1; @@ -93,7 +93,14 @@ } // if (doshort) setShortSubject(cid, mode, cl); - existingdiv.className = existingdiv.className.replace(/full|hidden|oneline/, mode); + var new_class = existingdiv.className.replace(/full|hidden|oneline/, mode); + if (new_class != existingdiv.className) { + existingdiv.className = new_class; + var parentdiv = fetchEl('tree_' + cid); + parentdiv.className = parentdiv.className.replace(' contain', ''); + if (mode == 'full') + parentdiv.className = parentdiv.className + ' contain'; + } if (adTimerUrl) { var addiv = fetchEl('comment_ad_' + cid); if (addiv) { @@ -262,7 +269,7 @@ changeThreshold(user_threshold + ''); // needs to be a string value } -function changeT(delta) { +function changeT(delta, skip_ht) { if (!delta) return void(0); @@ -271,14 +278,19 @@ threshold = Math.min(Math.max(threshold, -1), 6); // HT moves with T, but that is taken care of by changeThreshold() - changeThreshold(threshold + ''); // needs to be a string value + changeThreshold(threshold + '', skip_ht); // needs to be a string value } -function changeThreshold(threshold) { +function changeThreshold(threshold, skip_ht) { var threshold_num = parseInt(threshold); var t_delta = threshold_num + (user_highlightthresh - user_threshold); - user_highlightthresh = Math.min(Math.max(t_delta, -1), 6); user_threshold = threshold_num; + if (skip_ht) { // don't move highlightthresh with thresh + if (user_threshold > user_highlightthresh) + user_highlightthresh = user_threshold; + } else { + user_highlightthresh = Math.min(Math.max(t_delta, -1), 6); + } for (var root = 0; root < root_comments.length; root++) { updateCommentTree(root_comments[root], threshold); @@ -389,10 +401,17 @@ return false; } -function vertBarClick (pid) { - comments_started = 1; - setCurrentComment(pid); - return selectParent(pid, 2); +function vertBarClick() { + if (this && this.id) { + var pid = this.id.match(/_(\d+)/); + pid = pid[1]; + + if (pid) { + comments_started = 1; + setCurrentComment(pid); + return selectParent(pid, 2); + } + } } function setShortSubject(cid, mode, cl) { @@ -569,9 +588,9 @@ if (!comment) { return } var defmode = comment.className.match(/full|hidden|oneline/); - if (!defmode) { return } + if (!defmode || !defmode.length || !defmode[0]) { return } - futuredisplaymode[cid] = prehiddendisplaymode[cid] = displaymode[cid] = defmode; + futuredisplaymode[cid] = prehiddendisplaymode[cid] = displaymode[cid] = defmode[0]; } function updateDisplayMode(cid, mode, newdefault) { @@ -720,9 +739,11 @@ params['highlightthresh'] = user_highlightthresh; } - params['cid'] = root_comment; +// we could use this in future if we want to restrict "More" to +// the cid's thread when possible +// if (root_comment) +// params['cid'] = root_comment; params['discussion_id'] = discussion_id; -// params['reskey'] = reskey_static; var abbrev = {}; for (var i = 0; i < cids.length; i++) { @@ -798,7 +819,7 @@ for (var i = 0; i < update.new_cids_order.length; i++) { var this_cid = update.new_cids_order[i]; - if (!placeholder_no_update[this_cid]) { + if (!placeholder_no_update[this_cid] && comments[this_cid]['points'] >= -1) { var mode = determineMode(this_cid); updateDisplayMode(this_cid, mode, 1); currents[displaymode[this_cid]]++; @@ -936,7 +957,7 @@ } }; - shrunkdiv.innerHTML = 'Loading...'; + shrunkdiv.innerHTML = '<span class="loading">Loading...</span>'; ajax_update(params, 'comment_body_' + cid, handlers); return false; @@ -975,8 +996,12 @@ replydiv.innerHTML = ''; if (pid) { // XXX var reply_link = $dom('reply_link_' + pid); - reply_link.innerHTML = reply_link_html[pid]; - reply_link_html[pid] = ''; + // in some cases this won't exist; if not, fine, we + // just don't do it + if (reply_link || !reply_link_html[pid]) { + reply_link.innerHTML = reply_link_html[pid]; + reply_link_html[pid] = ''; + } } } @@ -987,6 +1012,7 @@ if (!replydiv || !reply || !preview) return false; + setReplyMsg(pid, ''); preview.style.display = 'none'; reply.style.display = 'block'; @@ -994,15 +1020,26 @@ $dom('replyto_buttons_1_' + pid).style.display = 'inline'; } +function setReplyMsg(pid, msg) { + var msgdiv = $('#replyto_msg_' + (pid || 0)); + if (!msgdiv) + return; + + msgdiv.html(msg); + if (msg) + msgdiv.show(); + else + msgdiv.hide(); +} + function replyPreviewOrSubmit (pid, op, handlers) { var replydiv = $dom('replyto_' + pid); var reply = $dom('replyto_reply_' + pid); var preview = $dom('replyto_preview_' + pid); var this_reskey = $dom('reskey_reply_' + pid); var msgdiv = 'replyto_msg_' + pid; - var msg = $dom(msgdiv); - if (!replydiv || !reply || !preview || !this_reskey || !msg) + if (!replydiv || !reply || !preview || !this_reskey) return false; var params = {}; @@ -1015,24 +1052,26 @@ params['postersubj'] = $dom('postersubj_' + pid).value; params['postercomment'] = $dom('postercomment_' + pid).value; + var hcanswer = $dom('hcanswer_' + pid); + if (hcanswer) + params['hcanswer'] = hcanswer.value; + var postanon = $dom('postanon_' + pid); if (postanon && postanon.checked) params['postanon'] = postanon.value; - msg.innerHTML = 'Loading...'; + setReplyMsg(pid, '<span class="loading">Loading...</span>'); ajax_update(params, '', handlers); } function submitReply(pid) { return replyPreviewOrSubmit(pid, 'comments_submit_reply', { onComplete: function(transport) { - var msg = $dom('replyto_msg_' + pid); - msg.innerHTML = ''; + setReplyMsg(pid, ''); var response = json_handler(transport); - var cid = response.cid; if (response.error) - msg.innerHTML = response.error; + setReplyMsg(pid, response.error); else if (cid) { cancelReply(pid); addComment(cid, { pid: pid, kids: [] }, '', 1); @@ -1046,17 +1085,15 @@ function previewReply(pid) { return replyPreviewOrSubmit(pid, 'comments_preview_reply', { onComplete: function(transport) { - var msg = $dom('replyto_msg_' + pid); - msg.innerHTML = ''; + setReplyMsg(pid, ''); var response = json_handler(transport); - if (response.error) - msg.innerHTML = response.error; + setReplyMsg(pid, response.error); if (response.html) { - $dom('replyto_reply_' + pid).style.display = 'none'; - $dom('replyto_preview_' + pid).style.display = 'block'; - $dom('replyto_buttons_1_' + pid).style.display = 'none'; - $dom('replyto_buttons_2_' + pid).style.display = 'inline'; + $('#replyto_reply_' + pid).hide(); + $('#replyto_preview_' + pid).show(); + $('#replyto_buttons_1_' + pid).hide(); + $('#replyto_buttons_2_' + pid).show(); } } }); @@ -1078,15 +1115,19 @@ params['pid'] = pid; params['sid'] = discussion_id; - replydiv.innerHTML = 'Loading...'; + replydiv.innerHTML = '<span class="loading">Loading...</span>'; var handlers = { onComplete: function(transport) { json_handler(transport); if (pid) { // XXX var reply_link = $dom('reply_link_' + pid); - reply_link_html[pid] = reply_link.innerHTML; - reply_link.innerHTML = '<a href="#" onclick="cancelReply(' + pid + '); return false;">Cancel Reply</a>'; + // in some cases this won't exist; if not, fine, we + // just don't do it + if (reply_link) { + reply_link_html[pid] = reply_link.innerHTML; + reply_link.innerHTML = '<p><b><a href="#" onclick="cancelReply(' + pid + '); return false;">Cancel Reply</a></b></p>'; + } } $dom('postercomment_' + pid).focus(); } @@ -1213,9 +1254,6 @@ loadAllElements('a'); } - if (root_comment) - currents['full'] += 1; - for (var i = 0; i < root_comments.length; i++) { root_comments_hash[ root_comments[i] ] = 1; } @@ -1248,11 +1286,12 @@ document.body.onkeydown = keyHandler; } - setCurrentComment(last_updated_comments[last_updated_comments_index]); + setCurrentComment(root_comment || last_updated_comments[last_updated_comments_index]); + //$('.contain').click(vertBarClick); + if (more_comments_num) updateMoreNum(more_comments_num); - updateTotals(); enableControls(); //setTimeout('ajaxFetchComments()', 10*1000); @@ -1487,13 +1526,13 @@ /* code for the draggable threshold widget */ function showPrefs( category ) { - var panel = document.getElementById("d2prefs"); + var panel = $dom("d2prefs"); panel.className = category; panel.style.display = "block"; } function hidePrefs() { - var panel = document.getElementById("d2prefs"); + var panel = $dom("d2prefs"); panel.className = ""; panel.style.display = "none"; } @@ -1590,6 +1629,7 @@ YAHOO.slashdot.ThresholdWidget.prototype.setTHT = function( T, HT ) { this._setTs(pinToRange(this.constraintRange, [HT, T])); + updateTotals(); } YAHOO.slashdot.ThresholdWidget.prototype.getTHT = function() { @@ -1600,6 +1640,7 @@ var ts = this.displayedTs.slice(); ts[threshold] += step; this._setTs(pinToRange(this.constraintRange, ts)); + updateTotals(); } YAHOO.slashdot.ThresholdWidget.prototype.setCounts = function( counts ) { @@ -1645,6 +1686,10 @@ this._getEl(prefix+"-count-pos").style.top = 0; } this._setTs(); + // setOrientation can rewrite our totals for us, even if they + // are different from defaults, so let's set them again + // sure they are correct + updateTotals(); } } @@ -1771,6 +1816,7 @@ YAHOO.slashdot.ThresholdBar.prototype.endDrag = function( e ) { this.parentWidget._onBarEndDrag(this.whichBar); + updateTotals(); } YAHOO.slashdot.ThresholdBar.prototype.alignElWithMouse = function( el, iPageX, iPageY ) { @@ -1840,19 +1886,19 @@ if (cid == current_cid) return; - this_id = fetchEl('comment_top_' + current_cid); - if (this_id) - this_id.className = this_id.className.replace(' newcomment', ' oldcomment'); + this_id = $('#comment_top_' + current_cid); + this_id.removeClass('newcomment'); + this_id.addClass('oldcomment'); - this_id = fetchEl('comment_' + current_cid); - if (this_id) - this_id.className = this_id.className.replace(' currcomment', ''); + this_id = $('#comment_' + current_cid); + this_id.removeClass('currcomment'); + $('.current').remove(); } - this_id = fetchEl('comment_' + cid); - if (this_id) - this_id.className = this_id.className + ' currcomment'; + this_id = $('#comment_' + cid); + this_id.addClass('currcomment'); + this_id.before('<span class="current">›</span>'); current_cid = cid; } @@ -1866,7 +1912,18 @@ prev comm chrono: Q next comm chrono: E next unread comm: F -reply: R +reply to current comment: R +parent of current comment: P +history (modlog) of current comment: M +skip to end (last): V +skip to top (first): T +get more comments: G +lower top threshold: [ +raise top threshold: ] +lower bottom threshold: , +raise bottom threshold: . +toggle d2 widget: / +hide_modal_box(): esc XXX */ var validkeys = { @@ -1877,7 +1934,21 @@ Q: { chrono : 1, prev: 1, comment: 1 }, E: { chrono : 1, next: 1, comment: 1 }, F: { thread : 1, next: 1, comment: 1, unread: 1 }, - R: { reply : 1 }, + + R: { current : 1, reply : 1 }, + P: { current : 1, parent : 1 }, + M: { current : 1, history : 1 }, + + G: { nav: 1, more : 1 }, + T: { nav: 1, skip : 1, top : 1 }, + V: { nav: 1, skip : 1, bottom : 1 }, + + 219 : { chr: '[', thresh : 1, top : 1, down: 1 }, + 221 : { chr: ']', thresh : 1, top : 1, up : 1 }, + 188 : { chr: ',', thresh : 1, bottom : 1, down: 1 }, + 190 : { chr: '.', thresh : 1, bottom : 1, up : 1 }, + + 191 : { chr: '/', toggle : 1, widget : 1 } }; validkeys['H'] = validkeys['A']; @@ -1885,6 +1956,13 @@ validkeys['J'] = validkeys['S']; validkeys['K'] = validkeys['W']; +//testing +//validkeys['1'] = validkeys['[']; +//validkeys['2'] = validkeys[']']; +//validkeys['3'] = validkeys[',']; +//validkeys['4'] = validkeys['.']; + + function keyHandler(e, k) { if (!k) e = e || window.event; @@ -1912,71 +1990,112 @@ var update = 0; var next_cid = 0; - var key = k || String.fromCharCode(c); + var key = k || validkeys[c] ? c : String.fromCharCode(c); var keyo = validkeys[key]; - if (keyo && keyo['reply'] && user_is_subscriber && current_cid) { // XXX - replyTo(current_cid); + if (keyo) { + if (keyo['toggle']) { + if (keyo['widget']) + toggleDisplayOptions(); - // forward and back between comments, in order of how they were loaded - } else if (keyo && keyo['chrono']) { - var i = last_updated_comments_index; - var l = last_updated_comments.length - 1; - update = 1; + // keys that rely on current comment + } else if (keyo['current'] && current_cid) { + if (keyo['reply'] && !user_is_anon) // XXX will be anon too + replyTo(current_cid); - if (keyo['prev']) { - if (i <= 0) { - // this did go back to end; nothing, for now - //i = l; - } else - i = i - 1; - } else if (keyo['next']) { - if (i >= l) { - if (ajaxCommentsWait()) - return; - update = 2; - ajaxFetchComments(0, 1, '', 1); - } else { - if (!i && noSeeFirstComment(last_updated_comments[i])) - comments_started = 1; // only come here once - else - i = i + 1; + else if (keyo['history']) + getModalPrefs('modcommentlog', 'Moderation Comment Log', current_cid); + + else if (keyo['parent']) { + if (current_cid && comments[current_cid] && comments[current_cid]['pid']) + selectParent(comments[current_cid]['pid']); } - } - if (update == 1) { - last_updated_comments_index = i; - next_cid = last_updated_comments[i]; - } - } - // forward and back between threads, and comments within each thread - else if (keyo && keyo['thread']) { - update = 1; - if (keyo['next']) { - if (noSeeFirstComment(current_cid)) - next_cid = current_cid; - else { - if (keyo['unread']) - getNextUnread = 1; - if (keyo['comment']) { - next_cid = commTreeNextComm(current_cid, 0, getNextUnread); - if (!next_cid) { // && getNextUnread) { - if (ajaxCommentsWait()) - return; - update = 2; - var highlight = 1 + collapseCurrent; - ajaxFetchComments(0, 1, '', highlight); - } + // misc. navigation keys + } else if (keyo['nav']) { + if (keyo['more']) + ajaxFetchComments(0, 1); + + else if (keyo['skip']) { // XXX how to find top/bottom? + if (keyo['top']) { + next_cid = commTreeFirstComm(); + update = 1; + } else if (keyo['bottom']) { + next_cid = commTreeLastComm(); + update = 1; + } + } + + // threshold keys keys + } else if (keyo['thresh']) { + if (keyo['top']) + changeHT(keyo['up'] ? 1 : -1); + if (keyo['bottom']) + changeT((keyo['up'] ? 1 : -1), 1); + gCommentControlWidget.setTHT(user_threshold, user_highlightthresh); + + + // forward and back between comments, in order of how they were loaded + } else if (keyo['chrono']) { + var i = last_updated_comments_index; + var l = last_updated_comments.length - 1; + update = 1; + + if (keyo['prev']) { + if (i <= 0) { + // this did go back to end; nothing, for now + //i = l; } else - next_cid = commTreeNextComm(comments[current_cid].pid, current_cid, getNextUnread); + i = i - 1; + } else if (keyo['next']) { + if (i >= l) { + if (ajaxCommentsWait()) + return; + update = 2; + ajaxFetchComments(0, 1, '', 1); + } else { + if (!i && noSeeFirstComment(last_updated_comments[i])) + comments_started = 1; // only come here once + else + i = i + 1; + } } + + if (update == 1) { + last_updated_comments_index = i; + next_cid = last_updated_comments[i]; + } } - else if (keyo['prev'] && keyo['comment']) - next_cid = commTreePrevComm(current_cid); - - else if (keyo['prev']) - next_cid = commTreePrevComm(current_cid, 1); + // forward and back between threads, and comments within each thread + else if (keyo['thread']) { + update = 1; + if (keyo['next']) { + if (noSeeFirstComment(current_cid)) + next_cid = current_cid; + else { + if (keyo['unread']) + getNextUnread = 1; + if (keyo['comment']) { + next_cid = commTreeNextComm(current_cid, 0, getNextUnread); + if (!next_cid) { // && getNextUnread) { + if (ajaxCommentsWait()) + return; + update = 2; + var highlight = 1 + collapseCurrent; + ajaxFetchComments(0, 1, '', highlight); + } + } else + next_cid = commTreeNextComm(comments[current_cid].pid, current_cid, getNextUnread); + } + } + + else if (keyo['prev'] && keyo['comment']) + next_cid = commTreePrevComm(current_cid); + + else if (keyo['prev']) + next_cid = commTreePrevComm(current_cid, 1); + } } if (update && next_cid) { @@ -2032,6 +2151,30 @@ return commTreeNextComm(comments[cid].pid, cid, getNextUnread); } +function commTreeLastComm () { + var this_cid = current_cid; + if (!current_cid) + this_cid = last_updated_comments[0]; + for (;;) { + var new_cid = commTreeNextComm(this_cid); + if (!new_cid) + return this_cid; + this_cid = new_cid; + } +} + +function commTreeFirstComm () { + var this_cid = current_cid; + if (!current_cid) + this_cid = last_updated_comments[0]; + for (;;) { + var new_cid = commTreePrevComm(this_cid, 2); + if (!new_cid) + return this_cid; + this_cid = new_cid; + } +} + function commTreePrevComm (cid, to_parent) { var root_kids = rootSort(); var comm = comments[cid]; Copied: slashjp/trunk/themes/slashcode/htdocs/images/corner_w_bl.png (from rev 571, slashjp/branches/upstream/current/themes/slashcode/htdocs/images/corner_w_bl.png) =================================================================== (Binary files differ) Copied: slashjp/trunk/themes/slashcode/htdocs/images/corner_w_br.png (from rev 571, slashjp/branches/upstream/current/themes/slashcode/htdocs/images/corner_w_br.png) =================================================================== (Binary files differ) Copied: slashjp/trunk/themes/slashcode/htdocs/images/corner_w_tl.png (from rev 571, slashjp/branches/upstream/current/themes/slashcode/htdocs/images/corner_w_tl.png) =================================================================== (Binary files differ) Copied: slashjp/trunk/themes/slashcode/htdocs/images/corner_w_tr.png (from rev 571, slashjp/branches/upstream/current/themes/slashcode/htdocs/images/corner_w_tr.png) =================================================================== (Binary files differ) Modified: slashjp/trunk/themes/slashcode/htdocs/slashcode_lite.css =================================================================== --- slashjp/trunk/themes/slashcode/htdocs/slashcode_lite.css 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/themes/slashcode/htdocs/slashcode_lite.css 2008-04-07 00:26:30 UTC (rev 572) @@ -156,3 +156,6 @@ { background: #ddd !important;} +div.commentTop div.title h4, .comment .oneline div.title h4 a {background: transparent !important;} + .comment .oneline div.title h4 a {color: #00E !important} + Modified: slashjp/trunk/themes/slashcode/htdocs/users.pl =================================================================== --- slashjp/trunk/themes/slashcode/htdocs/users.pl 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/themes/slashcode/htdocs/users.pl 2008-04-07 00:26:30 UTC (rev 572) @@ -1611,6 +1611,11 @@ my $constants = getCurrentStatic(); my $tags_reader = getObject('Slash::Tags', { db_type => 'reader' }); + # XXX if $user_edit->{acl}{spammer}, either abort or put ref=nofollow in all links + + my $tagname = $form->{tagname} || ''; + $tagname = '' if !$tags_reader->tagnameSyntaxOK($tagname); + my($uid, $user_edit); if ($form->{uid} || $form->{nick}) { $uid = $form->{uid} || $tags_reader->getUserUID($form->{nick}); @@ -1627,11 +1632,41 @@ return; } - my $tags_ar = $tags_reader->getGroupedTagsFromUser($user_edit->{uid}); - slashDisplay('usertags', { - useredit => $user_edit, - tags_grouped => $tags_ar, - }); + my $tagnameid = $tags_reader->getTagnameidFromNameIfExists($tagname); + if ($tagnameid) { + # Show all user's tags for one particular tagname. + my $tags_hr = $tags_reader->getGroupedTagsFromUser($user_edit->{uid}, + { tagnameid => $tagnameid }); + my $tags_ar = $tags_hr->{$tagname} || [ ]; + slashDisplay('usertagsforname', { + useredit => $user_edit, + tagname => $tagname, + tags => $tags_ar, + }); + + } else { + my $tags_hr = $tags_reader->getGroupedTagsFromUser($user_edit->{uid}); + my $num_tags = 0; + for my $tn (keys %$tags_hr) { + $num_tags += scalar @{ $tags_hr->{$tn} }; + } + my $cutoff = $constants->{tags_usershow_cutoff} || 200; + if ($num_tags <= $cutoff) { + # Show all user's tags, grouped by tagname. + slashDisplay('usertags', { + useredit => $user_edit, + tags_grouped => $tags_hr, + }); + } else { + # Show all user's tagnames, with links to show all + # tags for each particular tagname. + my $tagname_ar = [ sort keys %$tags_hr ]; + slashDisplay('usertagnames', { + useredit => $user_edit, + tagnames => $tagname_ar, + }); + } + } } ################################################################# Copied: slashjp/trunk/themes/slashcode/htdocs/users2.pl (from rev 571, slashjp/branches/upstream/current/themes/slashcode/htdocs/users2.pl) =================================================================== --- slashjp/trunk/themes/slashcode/htdocs/users2.pl (rev 0) +++ slashjp/trunk/themes/slashcode/htdocs/users2.pl 2008-04-07 00:26:30 UTC (rev 572) @@ -0,0 +1,3762 @@ +#!/usr/bin/perl -w +# This code is a part of Slash, and is released under the GPL. +# Copyright 1997-2005 by Open Source Technology Group. See README +# and COPYING for more information, or see http://slashcode.com/. +# $Id: users2.pl,v 1.1 2008/04/02 14:38:23 entweichen Exp $ + +use strict; +use Digest::MD5 'md5_hex'; +use Slash; +use Slash::Display; +use Slash::Utility; +use Slash::Constants qw(:messages); + +################################################################# +sub main { + my $slashdb = getCurrentDB(); + my $constants = getCurrentStatic(); + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $gSkin = getCurrentSkin(); + my $formname = $0; + $formname =~ s/.*\/(\w+)\.pl/$1/; + + my $error_flag = 0; + my $formkey = $form->{formkey}; + + my $suadmin_flag = $user->{seclev} >= 10000 ? 1 : 0 ; + my $postflag = $user->{state}{post}; + my $op = lc($form->{op}); + + return if (!$user->{is_admin}); + + # savepasswd is a special case, because once it's called, you + # have to reload the form, and you don't want to do any checks if + # you've just saved. + my $savepass_flag = $op eq 'savepasswd' ? 1 : 0 ; + + my $ops = { + admin => { + function => \&adminDispatch, + seclev => 10000, # if this should be lower, + # then something else is + # broken, because it allows + # anyone with this seclev + # to change their own seclev + formname => $formname, + # just in case we need it for something else, we have it ... + checks => [ qw (generate_formkey) ], + }, + no_user => { + function => \&noUser, + seclev => 0, + formname => $formname, + checks => [], + }, + userinfo => { + function => \&showInfo, + #I made this change, not all sites are going to care. -Brian + seclev => $constants->{users_show_info_seclev}, + formname => $formname, + checks => [], + tab_selected_1 => 'me', + tab_selected_2 => 'info', + }, + userfirehose => { + function => \&showFireHose, + seclev => 0, + formname => $formname, + checks => [], + tab_selected_1 => 'me', + tab_selected_2 => 'firehose' + }, + usersubmissions => { + function => \&showSubmissions, + #I made this change, not all sites are going to care. -Brian + seclev => $constants->{users_show_info_seclev}, + checks => [], + tab_selected_1 => 'me', + }, + usercomments => { + function => \&showComments, + #I made this change, not all sites are going to care. -Brian + seclev => $constants->{users_show_info_seclev}, + checks => [], + tab_selected_1 => 'me', + }, + display => { + function => \&showInfo, + #I made this change, not all sites are going to care. -Brian + seclev => $constants->{users_show_info_seclev}, + formname => $formname, + checks => [], + tab_selected_1 => 'me', + tab_selected_2 => 'info', + }, +# savepasswd => { +# function => \&savePasswd, +# seclev => 1, +# post => 1, +# formname => $formname, +# checks => [ qw (max_post_check valid_check +# formkey_check regen_formkey) ], +# tab_selected_1 => 'preferences', +# tab_selected_2 => 'password', +# }, + saveuseradmin => { + function => \&saveUserAdmin, + seclev => 10000, + post => 1, + formname => $formname, + checks => [], + }, + savehome => { + function => \&saveHome, + seclev => 1, + post => 1, + formname => $formname, + checks => [ qw (valid_check + formkey_check regen_formkey) ], + tab_selected_1 => 'preferences', + tab_selected_2 => 'home', + }, + savecomm => { + function => \&saveComm, + seclev => 1, + post => 1, + formname => $formname, + checks => [ qw (valid_check + formkey_check regen_formkey) ], + tab_selected_1 => 'preferences', + tab_selected_2 => 'comments', + }, + saveuser => { + function => \&saveUser, + seclev => 1, + post => 1, + formname => $formname, + checks => [ qw (valid_check + formkey_check regen_formkey) ], + tab_selected_1 => 'preferences', + tab_selected_2 => 'user', + }, +# changepasswd => { +# function => \&changePasswd, +# seclev => 1, +# formname => $formname, +# checks => $savepass_flag ? [] : +# [ qw (generate_formkey) ], +# tab_selected_1 => 'preferences', +# tab_selected_2 => 'password', +# }, + editmiscopts => { + function => \&editMiscOpts, + seclev => 1, + formname => $formname, + checks => [ ], + tab_selected_1 => 'preferences', + tab_selected_2 => 'misc', + }, + savemiscopts => { + function => \&saveMiscOpts, + seclev => 1, + formname => $formname, + checks => [ ], + tab_selected_1 => 'preferences', + tab_selected_2 => 'misc', + }, + edituser => { + function => \&editUser, + seclev => 1, + formname => $formname, + checks => [ qw (generate_formkey) ], + tab_selected_1 => 'preferences', + tab_selected_2 => 'user', + }, + authoredit => { + function => \&editUser, + seclev => 10000, + formname => $formname, + checks => [], + }, + edithome => { + function => \&editHome, + seclev => 1, + formname => $formname, + checks => [ qw (generate_formkey) ], + tab_selected_1 => 'preferences', + tab_selected_2 => 'home', + }, + editcomm => { + function => \&editComm, + seclev => 1, + formname => $formname, + checks => [ qw (generate_formkey) ], + tab_selected_1 => 'preferences', + tab_selected_2 => 'comments', + }, +# newuser => { +# function => \&newUser, +# seclev => 0, +# formname => "${formname}/nu", +# checks => [ qw (max_post_check valid_check +# formkey_check regen_formkey) ], +# }, + newuseradmin => { + function => \&newUserForm, + seclev => 10000, + formname => "${formname}/nu", + checks => [], + }, + previewbox => { + function => \&previewSlashbox, + seclev => 0, + formname => $formname, + checks => [], + }, +# mailpasswd => { +# function => \&mailPasswd, +# seclev => 0, +# formname => "${formname}/mp", +# checks => [ qw (max_post_check valid_check +# interval_check formkey_check ) ], +# tab_selected_1 => 'preferences', +# tab_selected_2 => 'password', +# }, + validateuser => { + function => \&validateUser, + seclev => 1, + formname => $formname, + checks => ['regen_formkey'], + }, + showtags => { + function => \&showTags, + seclev => 1, + formname => $formname, + checks => [], + tab_selected => 'tags', + }, + showbookmarks => { + function => \&showBookmarks, + seclev => 0, + formname => $formname, + checks => [], + tab_selected => 'bookmarks', + }, + edittags => { + function => \&editTags, + seclev => 1, + formname => $formname, + checks => [], + tab_selected => 'tags', + }, + savetags => { + function => \&saveTags, + seclev => 1, + formname => $formname, + checks => [], + tab_selected => 'tags', + }, +# userclose => { +# function => \&displayForm, +# seclev => 0, +# formname => $formname, +# checks => [], +# }, +# newuserform => { +# function => \&displayForm, +# seclev => 0, +# formname => "${formname}/nu", +# checks => [ qw (max_post_check +# generate_formkey) ], +# }, +# mailpasswdform => { +# function => \&displayForm, +# seclev => 0, +# formname => "${formname}/mp", +# checks => [ qw (max_post_check +# generate_formkey) ], +# tab_selected_1 => 'preferences', +# tab_selected_2 => 'password', +# }, + displayform => { + function => \&displayForm, + seclev => 0, + formname => $formname, + checks => [ qw (generate_formkey) ], + tab_selected_1 => 'me', + }, + listreadonly => { + function => \&listReadOnly, + seclev => 100, + formname => $formname, + checks => [], + adminmenu => 'security', + tab_selected => 'readonly', + }, + listbanned => { + function => \&listBanned, + seclev => 100, + formname => $formname, + checks => [], + adminmenu => 'security', + tab_selected => 'banned', + }, + topabusers => { + function => \&topAbusers, + seclev => 100, + formname => $formname, + checks => [], + adminmenu => 'security', + tab_selected => 'abusers', + }, + listabuses => { + function => \&listAbuses, + seclev => 100, + formname => $formname, + checks => [], + }, + force_acct_verify => { + function => \&forceAccountVerify, + seclev => 100, + post => 1, + formname => $formname, + checks => [] + } + + } ; + + # Note this is NOT the default op. "userlogin" or "userinfo" is + # the default op, and it's set either 5 lines down or about 100 + # lines down, depending. Yes, that's dumb. Yes, we should + # change it. It would require tracing through a fair bit of logic + # though and I don't have the time right now. - Jamie + $ops->{default} = $ops->{displayform}; + for (qw(newuser newuserform mailpasswd mailpasswdform changepasswd savepasswd userlogin userclose)) { + $ops->{$_} = $ops->{default}; + } + + my $errornote = ""; + if ($form->{op} && ! defined $ops->{$op}) { + $errornote .= getError('bad_op', { op => $form->{op}}, 0, 1); + $op = $user->{is_anon} ? 'userlogin' : 'userinfo'; + } + + if ($op eq 'userlogin' && ! $user->{is_anon}) { + redirect(cleanRedirectUrl($form->{returnto} || '')); + return; + + # this will only redirect if it is a section-based rootdir with + # its rootdir different from real_rootdir + } elsif ($op eq 'userclose' && $gSkin->{rootdir} ne $constants->{real_rootdir}) { + redirect($constants->{real_rootdir} . '/login.pl?op=userclose'); + return; + + } elsif ($op =~ /^(?:newuser|newuserform|mailpasswd|mailpasswdform|changepasswd|savepasswd|userlogin|userclose|displayform)$/) { + my $op = $form->{op}; + $op = 'changeprefs' if $op eq 'changepasswd'; + $op = 'saveprefs' if $op eq 'savepasswd'; + redirect($constants->{real_rootdir} . '/login.pl?op=' . $op); + return; + + # never get here now + } elsif ($op eq 'savepasswd') { + my $error_flag = 0; + if ($user->{seclev} < 100) { + for my $check (@{$ops->{savepasswd}{checks}}) { + # the only way to save the error message is to pass by ref + # $errornote and add the message to note (you can't print + # it out before header is called) + $error_flag = formkeyHandler($check, $formname, $formkey, \$errornote); + last if $error_flag; + } + } + + if (! $error_flag) { + $error_flag = savePasswd({ noteref => \$errornote }) ; + } + # change op to edituser and let fall through; + # we need to have savePasswd set the cookie before + # header() is called -- pudge + if ($user->{seclev} < 100 && ! $error_flag) { + $slashdb->updateFormkey($formkey, length($ENV{QUERY_STRING})); + } + $op = $error_flag ? 'changepasswd' : 'userinfo'; + $form->{userfield} = $user->{uid}; + } + + # Figure out what the op really is. + $op = 'userinfo' if (! $form->{op} && ($form->{uid} || $form->{nick})); + $op ||= $user->{is_anon} ? 'userlogin' : 'userinfo'; + if ($user->{is_anon} && ( ($ops->{$op}{seclev} > 0) || ($op =~ /^newuserform|mailpasswdform|displayform$/) )) { + redirect($constants->{real_rootdir} . '/login.pl'); + return; + } elsif ($user->{seclev} < $ops->{$op}{seclev}) { + $op = 'userinfo'; + } + if ($ops->{$op}{post} && !$postflag) { + $op = $user->{is_anon} ? 'default' : 'userinfo'; + } + + # Print the header and very top stuff on the page. We have + # three ops that (may) end up routing into showInfo(), which + # needs to do some stuff before it calls header(), so for + # those three, don't bother. + my $header; + if ($op !~ /^(userinfo|display|saveuseradmin|admin|userfirehose$)/) { + my $data = { + adminmenu => $ops->{$op}{adminmenu} || 'admin', + tab_selected => $ops->{$op}{tab_selected}, + }; + header(getMessage('user_header'), '', $data) or return; + # This is a hardcoded position, bad idea and should be fixed -Brian + # Yeah, we should pull this into a template somewhere... + print getMessage('note', { note => $errornote }) if defined $errornote; + $header = 1; + } + + if ($constants->{admin_formkeys} || $user->{seclev} < 100) { + + my $done = 0; + $done = 1 if $op eq 'savepasswd'; # special case + $formname = $ops->{$op}{formname}; + + # No need for HumanConf if the constant for it is not + # switched on, or if the user's karma is high enough + # to get out of it. (But for "newuserform," the current + # user's karma doesn't get them out of having to prove + # they're a human for creating a *new* user.) + my $options = {}; + if ( !$constants->{plugin}{HumanConf} + || !$constants->{hc} + || !$constants->{hc_sw_newuser} + && ($formname eq 'users/nu' || $op eq 'newuserform') + || !$constants->{hc_sw_mailpasswd} + && ($formname eq 'users/mp' || $op eq 'mailpasswdform') + || $user->{karma} > $constants->{hc_maxkarma} + && !$user->{is_anon} + && !($op eq 'newuser' || $op eq 'newuserform') + ) { + $options->{no_hc} = 1; + } + + DO_CHECKS: while (!$done) { + for my $check (@{$ops->{$op}{checks}}) { + $ops->{$op}{update_formkey} = 1 if $check eq 'formkey_check'; + $error_flag = formkeyHandler($check, $formname, $formkey, + undef, $options); + if ($error_flag == -1) { + # Special error: HumanConf failed. Go + # back to the previous op, start over. + if ($op =~ /^(newuser|mailpasswd)$/) { + $op .= "form"; + $error_flag = 0; + next DO_CHECKS; + } + } elsif ($error_flag) { + $done = 1; + last; + } + } + $done = 1; + } + + if (!$error_flag && !$options->{no_hc}) { + my $hc = getObject("Slash::HumanConf"); + $hc->reloadFormkeyHC($formname) if $hc; + } + + } + + errorLog("users.pl error_flag '$error_flag'") if $error_flag; + + # call the method + my $retval; + $retval = $ops->{$op}{function}->({ + op => $op, + tab_selected_1 => $ops->{$op}{tab_selected_1} || "", + note => $errornote, + }) if !$error_flag; + + return if !$retval; + + if ($op eq 'mailpasswd' && $retval) { + $ops->{$op}{update_formkey} = 0; + } + + if ($ops->{$op}{update_formkey} && $user->{seclev} < 100 && ! $error_flag) { + # successful save action, no formkey errors, update existing formkey + # why assign to an unused variable? -- pudge + my $updated = $slashdb->updateFormkey($formkey, length($ENV{QUERY_STRING})); + } + # if there were legit error levels returned from the save methods + # I would have it clear the formkey in case of an error, but that + # needs to be sorted out later + # else { resetFormkey($formkey); } + + writeLog($user->{nickname}); + footer(); +} + +################################################################# +sub checkList { + my($string, $len) = @_; + my $constants = getCurrentStatic(); + + $string =~ s/[^\w,-]//g; + my @items = grep { $_ } split /,/, $string; + $string = join ",", @items; + + $len ||= $constants->{checklist_length} || 255; + if (length($string) > $len) { + print getError('checklist_err'); + $string = substr($string, 0, $len); + $string =~ s/,?\w*$//g; + } elsif (length($string) < 1) { + $string = ''; + } + + return $string; +} + +################################################################# +sub previewSlashbox { + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $constants = getCurrentStatic(); + my $user = getCurrentUser(); + my $form = getCurrentForm(); + + my $block = $reader->getBlock($form->{bid}, ['title', 'block', 'url']); + my $is_editable = $user->{seclev} >= 1000; + + my $title = getTitle('previewslashbox_title', { blocktitle => $block->{title} }); + slashDisplay('previewSlashbox', { + width => '100%', + title => $title, + block => $block, + is_editable => $is_editable, + }); + + print portalbox($constants->{fancyboxwidth}, $block->{title}, + $block->{block}, '', $block->{url}); +} + +################################################################# +sub newUserForm { + my $user = getCurrentUser(); + my $suadmin_flag = $user->{seclev} >= 10000; + my $title = getTitle('newUserForm_title'); + + slashDisplay('newUserForm', { + title => $title, + suadmin_flag => $suadmin_flag, + }); +} + +################################################################# +sub newUser { + my $slashdb = getCurrentDB(); + my $form = getCurrentForm(); + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + + my $plugins = $slashdb->getDescriptions('plugins'); + my $title; + my $suadmin_flag = $user->{seclev} >= 10000 ? 1 : 0; + + # Check if User Exists + $form->{newusernick} = nickFix($form->{newusernick}); + my $matchname = nick2matchname($form->{newusernick}); + + if (!$form->{email} || $form->{email} !~ /\@/) { + print getError('email_invalid', 0, 1); + return; + } elsif ($form->{email} ne $form->{email2}) { + print getError('email_do_not_match', 0, 1); + return; + } elsif ($slashdb->existsEmail($form->{email})) { + print getError('emailexists_err', 0, 1); + return; + } elsif ($matchname ne '' && $form->{newusernick} ne '') { + if ($constants->{newuser_portscan}) { + my $is_trusted = $slashdb->checkAL2($user->{srcids}, 'trusted'); + if (!$is_trusted) { + my $is_proxy = $slashdb->checkForOpenProxy($user->{hostip}); + if ($is_proxy) { + print getError('new user open proxy', { + unencoded_ip => $ENV{REMOTE_ADDR}, + port => $is_proxy, + }); + return; + } + } + } + my $uid; + my $rootdir = getCurrentSkin('rootdir'); + + $uid = $slashdb->createUser( + $matchname, $form->{email}, $form->{newusernick} + ); + if ($uid) { + my $data = {}; + getOtherUserParams($data); + + for (qw(tzcode)) { + $data->{$_} = $form->{$_} if defined $form->{$_}; + } + $data->{creation_ipid} = $user->{ipid}; + + $slashdb->setUser($uid, $data) if keys %$data; + $title = getTitle('newUser_title'); + + $form->{pubkey} = $plugins->{'Pubkey'} ? + strip_nohtml($form->{pubkey}, 1) : ''; + print getMessage('newuser_msg', { + suadmin_flag => $suadmin_flag, + title => $title, + uid => $uid + }); + + if ($form->{newsletter} || $form->{comment_reply} || $form->{headlines}) { + my $messages = getObject('Slash::Messages'); + my %params; + $params{MSG_CODE_COMMENT_REPLY()} = MSG_MODE_EMAIL() + if $form->{comment_reply}; + $params{MSG_CODE_NEWSLETTER()} = MSG_MODE_EMAIL() + if $form->{newsletter}; + $params{MSG_CODE_HEADLINES()} = MSG_MODE_EMAIL() + if $form->{headlines}; + $messages->setPrefs($uid, \%params); + } + + mailPasswd({ uid => $uid }); + + return; + } else { + $slashdb->resetFormkey($form->{formkey}); + print getError('duplicate_user', { + nick => $form->{newusernick}, + }); + return; + } + + } else { + print getError('duplicate_user', { + nick => $form->{newusernick}, + }); + return; + } +} + +################################################################# +sub mailPasswd { + my($hr) = @_; + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + + my $uid = $hr->{uid} || 0; + + my $slashdb = getCurrentDB(); + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $form = getCurrentForm(); + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $hr->{tab_selected_1} || "", + }); + + if (! $uid) { + if ($form->{unickname} =~ /\@/) { + $uid = $slashdb->getUserEmail($form->{unickname}); + + } elsif ($form->{unickname} =~ /^\d+$/) { + my $tmpuser = $slashdb->getUser($form->{unickname}, ['uid']); + $uid = $tmpuser->{uid}; + + } else { + $uid = $slashdb->getUserUID($form->{unickname}); + } + } + + my $user_edit; + my $err_name = ''; + my $err_opts = {}; + if (!$uid || isAnon($uid)) { + $err_name = 'mailpasswd_notmailed_err'; + } + if (!$err_name) { + # Check permissions of _this_ user, not the target + # user, to determine whether this IP is OK'd to + # send the mail to the target user. + # XXXSRCID This should check a separate field like + # 'openproxy' instead of piggybacking off of 'nopost' + my $srcids_to_check = $user->{srcids}; + $err_name = 'mailpasswd_readonly_err' + if $reader->checkAL2($srcids_to_check, 'nopost'); + } + if (!$err_name) { + $err_name = 'mailpasswd_toooften_err' + if $slashdb->checkMaxMailPasswords($user_edit); + } + + if (!$err_name) { + if ($constants->{mailpasswd_portscan}) { + my $is_trusted = $slashdb->checkAL2($user->{srcids}, 'trusted'); + if (!$is_trusted) { + my $is_proxy = $slashdb->checkForOpenProxy($user->{hostip}); + if ($is_proxy) { + $err_name = 'mailpasswd open proxy'; + $err_opts = { unencoded_ip => $ENV{REMOTE_ADDR}, port => $is_proxy }; + } + } + + } + } + + if ($err_name) { + print getError($err_name, $err_opts); + $slashdb->resetFormkey($form->{formkey}); + $form->{op} = 'mailpasswdform'; + displayForm(); + return(1); + } + + my $newpasswd = $slashdb->getNewPasswd($uid); + my $tempnick = $user_edit->{nickname}; + + my $emailtitle = getTitle('mailPassword_email_title', { + nickname => $user_edit->{nickname} + }, 1); + + # Pull out some data passed in with the request. Only the IP + # number is actually trustworthy, the others could be forged. + # Note that we strip the forgeable ones to make sure there + # aren't any "<>" chars which could fool a stupid mail client + # into parsing a plaintext email as HTML. + my $r = Apache->request; + my $remote_ip = $r->connection->remote_ip; + my $xff = $r->header_in('X-Forwarded-For') || ''; + $xff =~ s/\s+/ /g; + $xff = substr(strip_notags($xff), 0, 20); + my $ua = $r->header_in('User-Agent') || ''; + $ua =~ s/\s+/ /g; + $ua = substr(strip_attribute($ua), 0, 60); + + my $msg = getMessage('mailpasswd_msg', { + newpasswd => $newpasswd, + tempnick => $tempnick, + remote_ip => $remote_ip, + x_forwarded_for => $xff, + user_agent => $ua, + }, 1); + + doEmail($uid, $emailtitle, $msg) if $user_edit->{nickname}; + print getMessage('mailpasswd_mailed_msg', { name => $user_edit->{nickname} }); + $slashdb->setUserMailPasswd($user_edit); +} + + +################################################################# +sub showSubmissions { + my($hr) = @_; + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $form = getCurrentForm(); + my $constants = getCurrentStatic(); + my $user = getCurrentUser(); + my($uid, $nickname); + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $hr->{tab_selected_1} || "", + }); + + if ($form->{uid} or $form->{nick}) { + $uid = $form->{uid} || $reader->getUserUID($form->{nick}); + $nickname = $reader->getUser($uid, 'nickname'); + } else { + $nickname = $user->{nickname}; + $uid = $user->{uid}; + } + + my $storycount = $reader->countStoriesBySubmitter($uid); + my $stories = $reader->getStoriesBySubmitter( + $uid, + $constants->{user_submitter_display_default} + ) unless !$storycount; + + slashDisplay('userSub', { + nick => $nickname, + uid => $uid, + nickmatch_flag => ($user->{uid} == $uid ? 1 : 0), + stories => $stories, + storycount => $storycount, + }); +} + +################################################################# +sub showComments { + my($hr) = @_; + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $form = getCurrentForm(); + my $constants = getCurrentStatic(); + my $user = getCurrentUser(); + my $commentstruct = []; + my($uid, $nickname); + + my $user_edit; + if ($form->{uid} || $form->{nick}) { + $uid = $form->{uid} || $reader->getUserUID($form->{nick}); + $user_edit = $reader->getUser($uid); + } else { + $uid = $user->{uid}; + $user_edit = $user; + } + $nickname = $user_edit->{nickname}; + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $user_edit->{uid} == $user->{uid} ? 'me' : 'otheruser', + }); + + my $min_comment = $form->{min_comment} || 0; + $min_comment = 0 unless $user->{seclev} > $constants->{comments_more_seclev} + || $constants->{comments_more_seclev} == 2 && $user->{is_subscriber}; + my $comments_wanted = $user->{show_comments_num} + || $constants->{user_comment_display_default}; + my $commentcount = $reader->countCommentsByUID($uid); + my $comments = $reader->getCommentsByUID( + $uid, $comments_wanted, $min_comment + ) if $commentcount; + + if (ref($comments) eq 'ARRAY') { + my $kinds = $reader->getDescriptions('discussion_kinds'); + for my $comment (@$comments) { + # This works since $sid is numeric. + $comment->{replies} = $reader->countCommentsBySidPid($comment->{sid}, $comment->{cid}); + + # This is ok, since with all luck we will not be hitting the DB + # ...however, the "sid" parameter here must be the string + # based SID from either the "stories" table or from + # pollquestions. + my $discussion = $reader->getDiscussion($comment->{sid}); + + if ($kinds->{ $discussion->{dkid} } =~ /^journal(?:-story)?$/) { + $comment->{type} = 'journal'; + } elsif ($kinds->{ $discussion->{dkid} } eq 'poll') { + $comment->{type} = 'poll'; + } else { + $comment->{type} = 'story'; + } + $comment->{disc_title} = $discussion->{title}; + $comment->{url} = $discussion->{url}; + } + } + + my $mod_reader = getObject("Slash::$constants->{m1_pluginname}", { db_type => 'reader' }); + slashDisplay('userCom', { + nick => $nickname, + useredit => $user_edit, + nickmatch_flag => ($user->{uid} == $uid ? 1 : 0), + commentstruct => $comments, + commentcount => $commentcount, + min_comment => $min_comment, + reasons => $mod_reader->getReasons(), + karma_flag => 0, + admin_flag => $user->{is_admin}, + }); +} + +sub noUser { + print getData("no_user"); +} + +sub showFireHose { + my($hr) = @_; + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + + my $uid = $form->{uid} || $user->{uid}; + my $user_edit = $reader->getUser($uid); + + $user->{state}{firehose_page} = "user"; + $user->{state}{firehose_user_uid} = $uid; + + my $firehose = getObject("Slash::FireHose"); + header(getMessage('userfirehose_header', { useredit => $user_edit })) or return; + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $user_edit->{uid} == $user->{uid} ? 'me' : 'otheruser', + }); + + $form->{mode} = "full"; + $form->{color} = "black"; + $form->{orderby} = "createtime"; + $form->{orderdidr} = "DESC"; + $form->{skipmenu} = 1; + $form->{duration} = -1; + $form->{fhfilter} = "\"user:$user_edit->{nickname}\""; + $form->{pause} = 1; + + my $fhbox = $firehose->listView({ fh_page => 'users.pl'}); + slashDisplay("userFireHose", { firehosebox => $fhbox, uid => $uid, useredit => $user_edit }); +} + +################################################################# +# arhgghgh. I love torture. I love pain. This subroutine satisfies +# these needs of mine +sub showInfo { + my($hr) = @_; + my $id = $hr->{uid} || 0; + + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $slashdb = getCurrentDB(); + my $form = getCurrentForm(); + my $constants = getCurrentStatic(); + my $user = getCurrentUser(); + + + my $admin_flag = ($user->{is_admin}) ? 1 : 0; + my($title, $admin_block, $fieldkey) = ('', '', ''); + my $comments = undef; + my $commentcount = 0; + my $commentcount_time = 0; + my $commentstruct = []; + my $requested_user = {}; + my $time_period = $constants->{admin_comment_display_days} || 30; + my $cid_for_time_period = $reader->getVar("min_cid_last_$time_period\_days",'value', 1) || 0; + my $admin_time_period_limit = $constants->{admin_daysback_commentlimit} || 100; + my $admin_non_time_limit = $constants->{admin_comment_subsequent_pagesize} || 24; + + my($points, $nickmatch_flag, $uid, $nick); + my($mod_flag, $karma_flag, $n) = (0, 0, 0); + + if ($admin_flag + && (defined($form->{show_m2s}) || defined($form->{show_m1s}) || defined($form->{m2_listing}))) + { + my $update_hr = {}; + $update_hr->{mod_with_comm} = $form->{show_m1s} + if defined $form->{show_m1s}; + $update_hr->{m2_with_mod} = ($constants->{m2} ? $form->{show_m2s} : undef) + if defined $form->{show_m2s}; + $update_hr->{show_m2_listing} = ($constants->{m2} ? $form->{m2_listing} : undef) + if defined $form->{m2_listing}; + $slashdb->setUser($user->{uid}, $update_hr); + } + + if (!$id && !$form->{userfield}) { + if ($form->{uid} && ! $id) { + $fieldkey = 'uid'; + ($uid, $id) = ($form->{uid}, $form->{uid}); + $requested_user = isAnon($uid) ? $user : $reader->getUser($id); + $nick = $requested_user->{nickname}; + $form->{userfield} = $nick if $admin_flag; + + } elsif ($form->{nick} && ! $id) { + $fieldkey = 'nickname'; + ($nick, $id) = ($form->{nick}, $form->{nick}); + $uid = $reader->getUserUID($id); + if (isAnon($uid)) { + $requested_user = $user; + ($nick, $uid, $id) = @{$user}{qw(nickname uid nickname)}; + } else { + $requested_user = $reader->getUser($uid); + } + $form->{userfield} = $uid if $admin_flag; + + } else { + $fieldkey = 'uid'; + ($id, $uid) = ($user->{uid}, $user->{uid}); + $requested_user = $reader->getUser($uid); + $form->{userfield} = $uid if $admin_flag; + } + + } elsif ($user->{is_admin}) { + $id ||= $form->{userfield} || $user->{uid}; + if ($id =~ /^[0-9a-f]{16}$/) { + $requested_user->{nonuid} = 1; + $fieldkey = "srcid"; + $requested_user->{$fieldkey} = $id; + } elsif ($id =~ /^\d+$/) { + # If it's longer than a uid could possibly be, it + # must be a srcid. The uid column right now is a + # MEDIUMINT (max 16M) but at most might someday + # be an INT (max 4G). + if (length($id) > 11) { + $requested_user->{nonuid} = 1; + $fieldkey = "srcid"; + $requested_user->{$fieldkey} = $id; + } else { + $fieldkey = 'uid'; + $requested_user = $reader->getUser($id); + $uid = $requested_user->{uid}; + $nick = $requested_user->{nickname}; + if ((my $conflict_id = $reader->getUserUID($id)) && $form->{userinfo}) { + #slashDisplay('showInfoConflict', { + #op => 'userinfo', + #id => $uid, + #nick => $nick, + #conflict_id => $conflict_id + #}); + return 1; + } + } + + } elsif (length($id) == 32) { + $requested_user->{nonuid} = 1; + if ($form->{fieldname} + && $form->{fieldname} =~ /^(ipid|subnetid)$/) { + $fieldkey = $form->{fieldname}; + } else { + $fieldkey = 'md5id'; + } + $requested_user->{$fieldkey} = $id; + } elsif ($id =~ /^(\d{1,3}\.\d{1,3}.\d{1,3}\.0)$/ + || $id =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3})\.?$/) { + $fieldkey = 'subnetid'; + $requested_user->{subnetid} = $1; + $requested_user->{subnetid} .= '.0' if $requested_user->{subnetid} =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}$/; + $requested_user->{nonuid} = 1; + $requested_user->{subnetid} = md5_hex($requested_user->{subnetid}); + + } elsif ($id =~ /^([\d+\.]+)$/) { + $fieldkey = 'ipid'; + $requested_user->{nonuid} = 1; + $id ||= $1; + $requested_user->{ipid} = md5_hex($1); + + } elsif ($id =~ /^(.*@.*\..*?)$/) { + # check for email addy, but make it by uid + $fieldkey = 'uid'; + $id = $uid = $reader->getUserEmail($id); + $requested_user = $reader->getUser($uid); + $nick = $requested_user->{nickname}; + + } else { # go by nickname, but make it by uid + $fieldkey = 'uid'; + $id = $uid = $reader->getUserUID($id); + $requested_user = $reader->getUser($uid); + $nick = $requested_user->{nickname}; + } + + } else { + $fieldkey = 'uid'; + ($id, $uid) = ($user->{uid}, $user->{uid}); + $requested_user = $reader->getUser($uid); + } + + # Can't get user data for the anonymous user. + if ($fieldkey eq 'uid' && isAnon($uid)) { + header(getMessage('user_header')) or return; + return displayForm(); + } + + my $user_change = { }; + if ($fieldkey eq 'uid' && !$user->{is_anon} + && $uid != $user->{uid} && !isAnon($uid)) { + # Store the fact that this user last looked at that user. + # For maximal convenience in stalking. + $user_change->{lastlookuid} = $uid; + $user_change->{lastlooktime} = time; + $user->{lastlookuid} = $uid; + $user->{lastlooktime} = time; + $hr->{tab_selected_1} = 'otheruser'; + } + + # showInfo's header information is delayed until here, because + # the target user's info is not available until here. + vislenify($requested_user); + header(getMessage('user_header', { useredit => $requested_user, fieldkey => $fieldkey })) or return; + # This is a hardcoded position, bad idea and should be fixed -Brian + # Yeah, we should pull this into a template somewhere... + print getMessage('note', { note => $hr->{note} }) if defined $hr->{note}; + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $hr->{tab_selected_1} || "", + }); + + my $comments_wanted = $user->{show_comments_num} + || $constants->{user_comment_display_default}; + my $min_comment = $form->{min_comment} || 0; + $min_comment = 0 unless $user->{seclev} > $constants->{comments_more_seclev} + || $constants->{comments_more_seclev} == 2 && $user->{is_subscriber}; + + my($netid, $netid_vis) = ('', ''); + + my $comment_time; + my $non_admin_limit = $comments_wanted; + + if ($requested_user->{nonuid}) { + $requested_user->{fg} = $user->{fg}; + $requested_user->{bg} = $user->{bg}; + + if ($requested_user->{ipid}) { + $netid = $requested_user->{ipid} ; + + } elsif ($requested_user->{md5id}) { + $netid = $requested_user->{md5id} ; + + } elsif ($requested_user->{srcid}) { + $netid = $requested_user->{srcid} ; + } else { + $netid = $requested_user->{subnetid} ; + } + + my $data = { + id => $id, + md5id => $netid, + }; + vislenify($data); # add $data->{md5id_vis} + $netid_vis = $data->{md5id_vis}; + + $title = getTitle('user_netID_user_title', $data); + + $admin_block = getUserAdmin($netid, $fieldkey, 0) if $admin_flag; + + if ($form->{fieldname}) { + if ($form->{fieldname} eq 'ipid') { + $commentcount = $reader->countCommentsByIPID($netid); + $commentcount_time = $reader->countCommentsByIPID($netid, { cid_at_or_after => $cid_for_time_period }); + $comments = getCommentListing("ipid", $netid, + $min_comment, $time_period, $commentcount, $commentcount_time, $cid_for_time_period, + $non_admin_limit, $admin_time_period_limit, $admin_non_time_limit) + if $commentcount; + } elsif ($form->{fieldname} eq 'subnetid') { + $commentcount = $reader->countCommentsBySubnetID($netid); + $commentcount_time = $reader->countCommentsBySubnetID($netid, { cid_at_or_after => $cid_for_time_period }); + $comments = getCommentListing("subnetid", $netid, + $min_comment, $time_period, $commentcount, $commentcount_time, $cid_for_time_period, + $non_admin_limit, $admin_time_period_limit, $admin_non_time_limit) + if $commentcount; + + } else { + delete $form->{fieldname}; + } + } + if (!defined($comments)) { + # Last resort; here for backwards compatibility mostly. + my $type; + ($commentcount,$type) = $reader->countCommentsByIPIDOrSubnetID($netid); + $commentcount_time = $reader->countCommentsByIPIDOrSubnetID($netid, { cid_at_or_after => $cid_for_time_period }); + if ($type eq "ipid") { + $comments = getCommentListing("ipid", $netid, + $min_comment, $time_period, $commentcount, $commentcount_time, $cid_for_time_period, + $non_admin_limit, $admin_time_period_limit, $admin_non_time_limit) + if $commentcount; + } elsif ($type eq "subnetid") { + $comments = getCommentListing("subnetid", $netid, + $min_comment, $time_period, $commentcount, $commentcount_time, $cid_for_time_period, + $non_admin_limit, $admin_time_period_limit, $admin_non_time_limit) + if $commentcount; + } + } + } else { + $admin_block = getUserAdmin($id, $fieldkey, 1) if $admin_flag; + + $commentcount = $reader->countCommentsByUID($requested_user->{uid}); + $commentcount_time = $reader->countCommentsByUID($requested_user->{uid}, { cid_at_or_after => $cid_for_time_period }); + $comments = getCommentListing("uid", $requested_user->{uid}, + $min_comment, $time_period, $commentcount, $commentcount_time, $cid_for_time_period, + $non_admin_limit, $admin_time_period_limit, $admin_non_time_limit, + { use_uid_cid_cutoff => 1 }) + if $commentcount; + $netid = $requested_user->{uid}; + } + + # Grab the nicks of the uids we have, we're going to be adding them + # into the struct. + my @users_extra_cols_wanted = qw( nickname ); + my @discussions_extra_cols_wanted = qw( type ); + my $uid_hr = { }; + my $sid_hr = { }; + if ($comments && @$comments) { + my %uids = (); + my %sids = (); + for my $c (@$comments) { + $uids{$c->{uid}}++; + $sids{$c->{sid}}++; + } + my $uids = join(", ", sort { $a <=> $b } keys %uids); + my $sids = join(", ", sort { $a <=> $b } keys %sids); + $uid_hr = $reader->sqlSelectAllHashref( + "uid", + "uid, " . join(", ", @users_extra_cols_wanted), + "users", + "uid IN ($uids)" + ); + + $sid_hr = $reader->sqlSelectAllHashref( + "id", + "id, " . join(", ", @discussions_extra_cols_wanted), + "discussions", + "id IN ($sids)" + ); + + } + + my $cids_seen = {}; + my $kinds = $slashdb->getDescriptions('discussion_kinds'); + for my $comment (@$comments) { + $cids_seen->{$comment->{cid}}++; + my $type; + # This works since $sid is numeric. + my $replies = $reader->countCommentsBySidPid($comment->{sid}, $comment->{cid}); + + # This is cached. + my $discussion = $reader->getDiscussion($comment->{sid}); +#use Data::Dumper; if ($discussion && !$discussion->{dkid}) { print STDERR scalar(gmtime) . " users.pl discussion but no dkid: " . Dumper($discussion) } + if (!$discussion || !$discussion->{dkid}) { + # A comment with no accompanying discussion; + # basically we pretend it doesn't exist. + next; + } elsif ($kinds->{ $discussion->{dkid} } =~ /^journal(?:-story)?$/) { + $type = 'journal'; + } elsif ($kinds->{ $discussion->{dkid} } eq 'poll') { + $type = 'poll'; + } else { + $type = 'story'; + } + + $comment->{points} += $user->{karma_bonus} + if $user->{karma_bonus} && $comment->{karma_bonus} eq 'yes'; + $comment->{points} += $user->{subscriber_bonus} + if $user->{subscriber_bonus} && $comment->{subscriber_bonus} eq 'yes'; + + # fix points in case they are out of bounds + $comment->{points} = $constants->{comment_minscore} if $comment->{points} < $constants->{comment_minscore}; + $comment->{points} = $constants->{comment_maxscore} if $comment->{points} > $constants->{comment_maxscore}; + vislenify($comment); + my $data = { + pid => $comment->{pid}, + url => $discussion->{url}, + disc_type => $type, + disc_title => $discussion->{title}, + disc_time => $discussion->{ts}, + sid => $comment->{sid}, + cid => $comment->{cid}, + subj => $comment->{subject}, + cdate => $comment->{date}, + pts => $comment->{points}, + reason => $comment->{reason}, + uid => $comment->{uid}, + replies => $replies, + ipid => $comment->{ipid}, + ipid_vis => $comment->{ipid_vis}, + karma => $comment->{karma}, + tweak => $comment->{tweak}, + tweak_orig => $comment->{tweak_orig}, + + }; + #Karma bonus time + + for my $col (@users_extra_cols_wanted) { + $data->{$col} = $uid_hr->{$comment->{uid}}{$col} if defined $uid_hr->{$comment->{uid}}{$col}; + } + for my $col(@discussions_extra_cols_wanted) { + $data->{$col} = $sid_hr->{$comment->{sid}}{$col} if defined $sid_hr->{$comment->{sid}}{$col}; + } + push @$commentstruct, $data; + } +# if (grep { !defined($_->{disc_time}) || !defined($_->{sid}) } @$commentstruct) { use Data::Dumper; print STDERR "showInfo undef in commentstruct for id=$id: " . Dumper($commentstruct) } + # Sort so the chosen group of comments is sorted by discussion + @$commentstruct = sort { + $b->{disc_time} cmp $a->{disc_time} || $b->{sid} <=> $a->{sid} + } @$commentstruct + unless $user->{user_comment_sort_type} && $user->{user_comment_sort_type} == 1; + + my $cid_list = [ keys %$cids_seen ]; + my $cids_to_mods = {}; + my $mod_reader = getObject("Slash::$constants->{m1_pluginname}", { db_type => 'reader' }); + if ($constants->{m1} && $admin_flag && $constants->{show_mods_with_comments}) { + my $comment_mods = $mod_reader->getModeratorCommentLog("DESC", + $constants->{mod_limit_with_comments}, "cidin", $cid_list); + + # Loop through mods and group them by the sid they're attached to + while (my $mod = shift @$comment_mods) { + push @{$cids_to_mods->{$mod->{cid}}}, $mod; + } + } + + my $sub_limit = ((($admin_flag || $user->{uid} == $requested_user->{uid}) ? $constants->{submissions_all_page_size} : $constants->{submissions_accepted_only_page_size}) || ""); + + my $sub_options = { limit_days => 365 }; + $sub_options->{accepted_only} = 1 if !$admin_flag && $user->{uid} != $requested_user->{uid}; + + my $sub_field = $form->{fieldname}; + + my ($subcount, $ret_field) = $reader->countSubmissionsByNetID($netid, $sub_field) + if $requested_user->{nonuid}; + my $submissions = $reader->getSubmissionsByNetID($netid, $ret_field, $sub_limit, $sub_options) + if $requested_user->{nonuid}; + + my $ipid_hoursback = $constants->{istroll_ipid_hours} || 72; + my $uid_hoursback = $constants->{istroll_uid_hours} || 72; + + if ($requested_user->{nonuid}) { + #slashDisplay('netIDInfo', { + #title => $title, + #id => $id, + #useredit => $requested_user, + #commentstruct => $commentstruct || [], + #commentcount => $commentcount, + #min_comment => $min_comment, + #admin_flag => $admin_flag, + #admin_block => $admin_block, + #netid => $netid, + #netid_vis => $netid_vis, + #reasons => $mod_reader->getReasons(), + #subcount => $subcount, + #submissions => $submissions, + #hr_hours_back => $ipid_hoursback, + #cids_to_mods => $cids_to_mods, + #comment_time => $comment_time + #}); + + } else { + if (! $requested_user->{uid}) { + print getError('userinfo_idnf_err', { id => $id, fieldkey => $fieldkey}); + return 1; + } + + $karma_flag = 1 if $admin_flag; + $requested_user->{nick_plain} = $nick ||= $requested_user->{nickname}; + $nick = strip_literal($nick); + + if ($requested_user->{uid} == $user->{uid}) { + $karma_flag = 1; + $nickmatch_flag = 1; + $points = $requested_user->{points}; + + $mod_flag = 1 if $points > 0; + + $title = getTitle('userInfo_main_title', { nick => $nick, uid => $uid }); + + } else { + $title = getTitle('userInfo_user_title', { nick => $nick, uid => $uid }); + } + + my $lastjournal = _get_lastjournal($uid); + + my $subcount = $reader->countSubmissionsByUID($uid); + + my $submissions = $reader->getSubmissionsByUID($uid, $sub_limit, $sub_options); + my $metamods; + if ($constants->{m2} && $admin_flag) { + my $metamod_reader = getObject('Slash::Metamod', { db_type => 'reader' }); + $metamods = $metamod_reader->getMetamodlogForUser($uid, 30); + } + + my $tags_reader = getObject('Slash::Tags', { db_type => 'reader' }); + my $tagshist = []; + if ($tags_reader && $user->{is_admin}) { + $tagshist = $tags_reader->getAllTagsFromUser($requested_user->{uid}, { orderby => 'created_at', orderdir => 'DESC', limit => 30, include_private => 1 }); + } + + my $comment_blocks = $reader->sqlSelectAllHashref( + 'uid', 'bid, uid, block', 'user_event_blocks', "uid = $uid and code = 1"); + + my @block_ids = split(/,/, $comment_blocks->{$uid}->{block}); + + my %latest_comments; + foreach my $comment_block (@block_ids) { + ($latest_comments{$comment_block}->{sid}, $latest_comments{$comment_block}->{subject}) + = $reader->sqlSelect("sid, subject", "comments", "cid = $comment_block"); + #$latest_comments{$comment_block}->{text} = $reader->sqlSelect("comment", "comment_text", "cid = $comment_block"); + } + + my $journal_blocks = $reader->sqlSelectAllHashref( + 'uid', 'bid, uid, block', 'user_event_blocks', "uid = $uid and code = 2"); + + @block_ids = split(/,/, $journal_blocks->{$uid}->{block}); + my %latest_journals; + foreach my $journal_block (@block_ids) { + ($latest_journals{$journal_block}->{id}, $latest_journals{$journal_block}->{desc}) + = $reader->sqlSelect("id, description", "journals", "discussion = $journal_block"); + } + + slashDisplay('userInfo2', { + title => $title, + uid => $uid, + useredit => $requested_user, + points => $points, + commentstruct => $commentstruct || [], + commentcount => $commentcount, + min_comment => $min_comment, + nickmatch_flag => $nickmatch_flag, + mod_flag => $mod_flag, + karma_flag => $karma_flag, + admin_block => $admin_block, + admin_flag => $admin_flag, + reasons => $mod_reader->getReasons(), + lastjournal => $lastjournal, + hr_hours_back => $ipid_hoursback, + cids_to_mods => $cids_to_mods, + comment_time => $comment_time, + submissions => $submissions, + subcount => $subcount, + metamods => $metamods, + tagshist => $tagshist, + latest_comments => \%latest_comments, + latest_journals => \%latest_journals, + }, { Page => 'users', Skin => 'default'}); + } + + if ($user_change && %$user_change) { + $slashdb->setUser($user->{uid}, $user_change); + } + + return 1; +} + +sub _get_lastjournal { + my($uid) = @_; + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $lastjournal = undef; + if (my $journal = getObject('Slash::Journal', { db_type => 'reader' })) { + my $j = $journal->getsByUid($uid, 0, 1); + if ($j && @$j) { + # Yep, there are 1 or more journals... get the first. + $j = $j->[0]; + } + if ($j && @$j) { + # Yep, that first journal exists and has entries... + # convert from stupid numeric array to a hashref. + my @field = qw( date article description id + posttype tid discussion ); + $lastjournal = { }; + for my $i (0..$#field) { + $lastjournal->{$field[$i]} = $j->[$i]; + } + } + } + + if ($lastjournal) { + + # Strip the article field for display. + $lastjournal->{article} = strip_mode($lastjournal->{article}, + $lastjournal->{posttype}); + + # For display, include a reduced-size version, where the + # size is based on the user's maxcomment size (which + # defaults to 4K) and can't have too many line-breaking + # tags. + my $art_shrunk = $lastjournal->{article}; + my $maxsize = int($user->{maxcommentsize} / 25); + $maxsize = 80 if $maxsize < 80; + $maxsize = 600 if $maxsize > 600; + $art_shrunk = chopEntity($art_shrunk, $maxsize); + + my $approvedtags_break = $constants->{approvedtags_break} || []; + my $break_tag = join '|', @$approvedtags_break; + if (scalar(() = $art_shrunk =~ /<(?:$break_tag)>/gi) > 2) { + $art_shrunk =~ s/\A + ( + (?: <(?:$break_tag)> )? + .*? <(?:$break_tag)> + .*? + ) <(?:$break_tag)>.* + /$1/six; + if (length($art_shrunk) < 15) { + # This journal entry has too much whitespace + # in its first few chars; scrap it. + undef $art_shrunk; + } + $art_shrunk = chopEntity($art_shrunk) if defined($art_shrunk); + } + + if (defined $art_shrunk) { + if (length($art_shrunk) < length($lastjournal->{article})) { + $art_shrunk .= " ..."; + } + $art_shrunk = strip_html($art_shrunk); + $art_shrunk = balanceTags($art_shrunk); + } + + $lastjournal->{article_shrunk} = $art_shrunk; + + if ($lastjournal->{discussion}) { + $lastjournal->{commentcount} = $reader->getDiscussion( + $lastjournal->{discussion}, 'commentcount'); + } + } + return $lastjournal; +} + +##################################################################### +sub validateUser { + my($hr) = @_; + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $slashdb = getCurrentDB(); + my $constants = getCurrentStatic(); + + # If we aren't expiring accounts in some way, we don't belong here. + if (! allowExpiry()) { + displayForm(); + return; + } + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $hr->{tab_selected_1} || "", + }); + + # Since we are here, if the minimum values for the comment trigger and + # the day trigger are -1, then they should be reset to 1. + $constants->{min_expiry_comm} = $constants->{min_expiry_days} = 1 + if $constants->{min_expiry_comm} <= 0 || + $constants->{min_expiry_days} <= 0; + + if ($user->{is_anon} || $user->{registered}) { + if ($user->{is_anon}) { + print getError('anon_validation_attempt'); + displayForm(); + return; + } else { + print getMessage('no_registration_needed') + if !$user->{reg_id}; + showInfo({ uid => $user->{uid} }); + return; + } + # Maybe this should be taken care of in a more centralized location? + } elsif ($user->{reg_id} eq $form->{id}) { + # We have a user and the registration IDs match. We are happy! + my($maxComm, $maxDays) = ($constants->{max_expiry_comm}, + $constants->{max_expiry_days}); + my($userComm, $userDays) = + ($user->{user_expiry_comm}, $user->{user_expiry_days}); + + # Ensure both $userComm and $userDays aren't -1 (expiry has + # just been turned on). + $userComm = $constants->{min_expiry_comm} + if $userComm < $constants->{min_expiry_comm}; + $userDays = $constants->{min_expiry_days} + if $userDays < $constants->{min_expiry_days}; + + my $exp = $constants->{expiry_exponent}; + + # Increment only the trigger that was used. + my $new_comment_expiry = ($maxComm > 0 && $userComm > $maxComm) + ? $maxComm + : $userComm * (($user->{expiry_comm} < 0) + ? $exp + : 1 + ); + my $new_days_expiry = ($maxDays > 0 && $userDays > $maxDays) + ? $maxDays + : $userDays * (($user->{expiry_days} < 0) + ? $exp + : 1 + ); + + # Reset re-registration triggers for user. + $slashdb->setUser($user->{uid}, { + 'expiry_comm' => $new_comment_expiry, + 'expiry_days' => $new_days_expiry, + 'user_expiry_comm' => $new_comment_expiry, + 'user_expiry_days' => $new_days_expiry, + }); + + # Handles rest of re-registration process. + setUserExpired($user->{uid}, 0); + } + + slashDisplay('regResult'); +} + +##################################################################### +sub editTags { + my($hr) = @_; + my $slashdb = getCurrentDB(); + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + my $note = $hr->{note} || ""; + + return if $user->{is_anon}; # shouldn't be, but can't hurt to check + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $hr->{tab_selected_1} || "", + }); + + my $user_edit = $slashdb->getUser($user->{uid}); + my $title = getTitle('editTags_title'); + + slashDisplay('editTags', { + user_edit => $user_edit, + title => $title, + note => $note, + }); +} + +sub saveTags { + my($hr) = @_; + my $slashdb = getCurrentDB(); + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $constants = getCurrentStatic(); + + return if $user->{is_anon}; # shouldn't be, but can't hurt to check + + $slashdb->setUser($user->{uid}, { + tags_turnedoff => $form->{showtags} ? '' : 1 }); + editTags({ note => getMessage('savetags_msg') }); +} + +##################################################################### +sub showTags { + my($hr) = @_; + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $slashdb = getCurrentDB(); + my $constants = getCurrentStatic(); + my $tags_reader = getObject('Slash::Tags', { db_type => 'reader' }); + + my($uid, $user_edit); + if ($form->{uid} || $form->{nick}) { + $uid = $form->{uid} || $tags_reader->getUserUID($form->{nick}); + $user_edit = $tags_reader->getUser($uid); + } + if (!$user_edit || $user_edit->{is_anon}) { + $uid = $user->{uid}; + $user_edit = $user; + } + my $nickname = $user_edit->{nickname}; + + if (!$constants->{plugin}{Tags}) { + print getError('bad_op', { op => $form->{op}}); + return; + } + + my $tags_ar = $tags_reader->getGroupedTagsFromUser($user_edit->{uid}); + slashDisplay('usertags', { + useredit => $user_edit, + tags_grouped => $tags_ar, + }); +} + +################################################################# +sub showBookmarks { + my($hr) = @_; + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $slashdb = getCurrentDB(); + my $constants = getCurrentStatic(); + my $tags_reader = getObject('Slash::Tags', { db_type => 'reader' }); + + my($uid, $user_edit); + if ($form->{uid} || $form->{nick}) { + $uid = $form->{uid} || $tags_reader->getUserUID($form->{nick}); + $user_edit = $tags_reader->getUser($uid); + } + if (!$user_edit || $user_edit->{is_anon}) { + $uid = $user->{uid}; + $user_edit = $user; + } + my $nickname = $user_edit->{nickname}; + + if (!$constants->{plugin}{Tags}) { + print getError('bad_op', { op => $form->{op}}); + return; + } + + my $tags_ar = $tags_reader->getGroupedTagsFromUser($user_edit->{uid}, { type => "urls", only_bookmarked => 1 }); + + slashDisplay('userbookmarks', { + useredit => $user_edit, + tags_grouped => $tags_ar, + }); +} + +################################################################# +sub editKey { + my($uid) = @_; + + my $slashdb = getCurrentDB(); + + my $pubkey = $slashdb->getUser($uid, 'pubkey'); + my $editkey = slashDisplay('editKey', { pubkey => $pubkey }, 1); + return $editkey; +} + +################################################################# +# We arrive here without header() having been called. Some of the +# functions we dispatch to call it, some do not. +sub adminDispatch { + my($hr) = @_; + my $form = getCurrentForm(); + my $op = $hr->{op} || $form->{op}; + + if ($op eq 'authoredit') { + # editUser() does not call header(), so we DO need to. + header(getMessage('user_header'), '', {}) or return; + editUser($hr); + + } elsif ($form->{saveuseradmin}) { + # saveUserAdmin() tail-calls showInfo(), which calls + # header(), so we need to NOT. + saveUserAdmin($hr); + + } elsif ($form->{userinfo}) { + # showInfo() calls header(), so we need to NOT. + showInfo($hr); + + } elsif ($form->{userfield}) { + # none of these calls header(), so we DO need to. + header(getMessage('user_header'), '', {}) or return; + if ($form->{edituser}) { + editUser($hr); + + } elsif ($form->{edithome}) { + editHome($hr); + + } elsif ($form->{editcomm}) { + editComm($hr); + + } elsif ($form->{changepasswd}) { + changePasswd($hr); + } + + } else { + # showInfo() calls header(), so we need to NOT. + showInfo($hr); + } +} + +################################################################# +sub tildeEd { + my($user_edit) = @_; + + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $constants = getCurrentStatic(); + + my %story023_default = ( + author => { }, + nexus => { }, + topic => { }, + ); + + my %prefs = ( ); + for my $field (qw( + story_never_topic story_never_author story_never_nexus + story_always_topic story_always_author story_always_nexus story_brief_always_nexus + story_full_brief_nexus story_full_best_nexus story_brief_best_nexus + )) { + for my $id ( + grep /^\d+$/, + split /,/, + ($user_edit->{$field} || "") + ) { + $prefs{$field}{$id} = 1; + } + } +#print STDERR scalar(localtime) . " prefs: " . Dumper(\%prefs); + + # Set up $author_hr, @aid_order, and $story023_default{author}. + + my $author_hr = $reader->getDescriptions('authors'); + my @aid_order = sort { lc $author_hr->{$a} cmp lc $author_hr->{$b} } keys %$author_hr; + for my $aid (@aid_order) { + if ($prefs{story_never_author}{$aid}) { + $story023_default{author}{$aid} = 0; + } elsif ($prefs{story_always_author}{$aid}) { + $story023_default{author}{$aid} = 3; + } else { + $story023_default{author}{$aid} = 2; + } + } + + # Set up $topic_hr, @topictid_order, and $story023_default{topic}. + + my $topic_hr = $reader->getDescriptions('non_nexus_topics-storypickable'); + my @topictid_order = sort { lc $topic_hr->{$a} cmp lc $topic_hr->{$b} } keys %$topic_hr; + for my $tid (@topictid_order) { + if ($prefs{story_never_topic}{$tid}) { + $story023_default{topic}{$tid} = 0; + } elsif ($prefs{story_always_topic}{$tid}) { + $story023_default{topic}{$tid} = 3; + } else { + $story023_default{topic}{$tid} = 2; + } + } + + # Set up $nexus_hr, @nexustid_order, and $story023_default{nexus}. + my $topic_tree = $reader->getTopicTree(); + my $nexus_tids_ar = $reader->getStorypickableNexusChildren($constants->{mainpage_nexus_tid}, 1); + my $nexus_hr = { }; + + for my $tid (@$nexus_tids_ar) { + $nexus_hr->{$tid} = $topic_tree->{$tid}{textname}; + } + my @nexustid_order = sort {($b == $constants->{mainpage_nexus_tid}) <=> ($a == $constants->{mainpage_nexus_tid}) || + lc $nexus_hr->{$a} cmp lc $nexus_hr->{$b} } keys %$nexus_hr; + + my $mp_disp_nexuses = $reader->getMainpageDisplayableNexuses(); + my %mp_disp_nexus = ( map { ($_, 1) } @$mp_disp_nexuses ); + for my $tid (@nexustid_order) { + if ($prefs{story_never_nexus}{$tid}) { + $story023_default{nexus}{$tid} = 0; + } elsif ($prefs{story_always_nexus}{$tid}) { + $story023_default{nexus}{$tid} = 5; + } elsif ($prefs{story_full_brief_nexus}{$tid}) { + $story023_default{nexus}{$tid} = 4; + } elsif ($prefs{story_brief_always_nexus}{$tid}) { + $story023_default{nexus}{$tid} = 3; + } elsif ($prefs{story_full_best_nexus}{$tid}) { + $story023_default{nexus}{$tid} = 2; + } elsif ($prefs{story_brief_best_nexus}) { + $story023_default{nexus}{$tid} = 1; + } else { + # If brief_sectional_mainpage is set, then all + # nexuses in getMainpageDisplayableNexuses are, + # by default, shown as brief on the mainpage. + if ($constants->{brief_sectional_mainpage} + && $mp_disp_nexus{$tid} + ) { + $story023_default{nexus}{$tid} = 4; + } else { + $story023_default{nexus}{$tid} = 2; + } + } + } + + # Set up $section_descref and $box_order, used to decide which + # slashboxes appear. Really this doesn't seem to have anything + # to do with sections, so I'm not sure why it's called + # "section"_descref. + + my $section_descref = { }; + my $box_order; + my $sections_description = $reader->getSectionBlocks(); + + # the names of all the boxes in @{$skinBoxes->{$constants->{mainpage_skid}}} + # should be unioned into sections_description. whether the + # values are 0 or 1 is calculated correctly, but we're + # missing some 0's that should appear, I think, under + # some circumstances. ah heck, the whole concept of + # sectional slashboxes should be redone (why the heck + # do we have skinname_more instead of just a block + # called olderstories?) + + my $slashboxes_hr = { }; + my $slashboxes_textlist = $user_edit->{slashboxes}; + if (!$slashboxes_textlist) { + # Use the default. + my($boxes, $skinBoxes) = $reader->getPortalsCommon(); + $slashboxes_textlist = join ",", @{$skinBoxes->{$constants->{mainpage_skid}}}; + } + for my $bid ( + map { /^'?([^']+)'?$/; $1 } + split /,/, + $slashboxes_textlist + ) { + $slashboxes_hr->{$bid} = 1; + } + for my $ary (sort { lc $a->[1] cmp lc $b->[1]} @$sections_description) { + my($bid, $title, $boldflag) = @$ary; + push @$box_order, $bid; + $section_descref->{$bid}{checked} = + $slashboxes_hr->{$bid} + ? $constants->{markup_checked_attribute} + : ''; + $title =~ s/<(.*?)>//g; + $section_descref->{$bid}{title} = $title; + } + +#print STDERR scalar(localtime) . " tildeEd story023_default: " . Dumper(\%story023_default); + + # Userspace. + + my $userspace = $user_edit->{mylinks} || ""; + + # Titles of stuff. + + my $tildeEd_title = getTitle('tildeEd_title'); + my $criteria_msg = getMessage('tilded_criteria_msg'); + my $customize_title = getTitle('tildeEd_customize_title'); + my $tilded_customize_msg = getMessage('tilded_customize_msg', + { userspace => $userspace }); + my $tilded_box_msg = getMessage('tilded_box_msg'); + + my $tilde_ed = slashDisplay('tildeEd', { + user_edit => $user_edit, + title => $tildeEd_title, + criteria_msg => $criteria_msg, + customize_title => $customize_title, + tilded_customize_msg => $tilded_customize_msg, + tilded_box_msg => $tilded_box_msg, + + story023_default => \%story023_default, + authorref => $author_hr, + aid_order => \@aid_order, + topicref => $topic_hr, + topictid_order => \@topictid_order, + nexusref => $nexus_hr, + nexustid_order => \@nexustid_order, + + section_descref => $section_descref, + box_order => $box_order, + + userspace => $userspace, + }, 1); + + return $tilde_ed; +} + +################################################################# +sub changePasswd { + my($hr) = @_; + my $form = getCurrentForm(); + my $slashdb = getCurrentDB(); + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $hr->{tab_selected_1} || "", + }); + + my $user_edit = {}; + my $title; + my $suadmin_flag = ($user->{seclev} >= 10000) ? 1 : 0; + + my $admin_flag = ($user->{is_admin}) ? 1 : 0; + my $admin_block = ''; + + my $id = ''; + if ($admin_flag) { + if ($form->{userfield}) { + $id ||= $form->{userfield}; + if ($id =~ /^\d+$/) { + $user_edit = $slashdb->getUser($id); + } else { + $user_edit = $slashdb->getUser($slashdb->getUserUID($id)); + } + } else { + $user_edit = $id eq '' ? $user : $slashdb->getUser($id); + $id = $user_edit->{uid}; + } + } else { + $id = $user->{uid}; + $user_edit = $user; + } + + $admin_block = getUserAdmin($id, 'uid', 1) if $admin_flag; + + # print getMessage('note', { note => $form->{note}}) if $form->{note}; + + $title = getTitle('changePasswd_title', { user_edit => $user_edit }); + + my $session = $slashdb->getDescriptions('session_login'); + my $session_select = createSelect('session_login', $session, $user_edit->{session_login}, 1); + + my $clocation = $slashdb->getDescriptions('cookie_location'); + my @clocation_order = grep { exists $clocation->{$_} } qw(none classbid subnetid ipid); + my $clocation_select = createSelect('cookie_location', $clocation, + $user_edit->{cookie_location}, 1, 0, \@clocation_order + ); + + my $got_oldpass = 0; + if ($form->{oldpass}) { + my $return_uid = $slashdb->getUserAuthenticate($id, $form->{oldpass}, 1); + $got_oldpass = 1 if $return_uid && $id == $return_uid; + } + + slashDisplay('changePasswd', { + useredit => $user_edit, + admin_flag => $suadmin_flag, + title => $title, + session => $session_select, + clocation => $clocation_select, + admin_block => $admin_block, + got_oldpass => $got_oldpass + }); +} + +################################################################# +sub editUser { + my($hr) = @_; + my $id = $hr->{uid} || ''; + my $note = $hr->{note} || ''; + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $hr->{tab_selected_1} || "", + }); + + my $form = getCurrentForm(); + my $slashdb = getCurrentDB(); + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + my $plugins = $slashdb->getDescriptions('plugins'); + + my $user_edit = {}; + my($admin_block, $title); + my $admin_flag = ($user->{is_admin}) ? 1 : 0; + my $fieldkey; + + if ($admin_flag && $form->{userfield}) { + $id ||= $form->{userfield}; + if ($form->{userfield} =~ /^\d+$/) { + $user_edit = $slashdb->getUser($id); + $fieldkey = 'uid'; + } else { + $user_edit = $slashdb->getUser($slashdb->getUserUID($id)); + $fieldkey = 'nickname'; + } + } else { + $user_edit = $id eq '' ? $user : $slashdb->getUser($id); + $fieldkey = 'uid'; + $id = $user_edit->{uid}; + } + return if isAnon($user_edit->{uid}) && ! $admin_flag; + + $admin_block = getUserAdmin($id, $fieldkey, 1) if $admin_flag; + $user_edit->{homepage} ||= "http://"; + + # Remove domain tags, they'll be added back in, in saveUser. + for my $dat (@{$user_edit}{qw(sig bio)}) { + $dat = parseDomainTags($dat, 0, 1); + } + + $title = getTitle('editUser_title', { user_edit => $user_edit}); + + my $editkey = ""; + $editkey = editKey($user_edit->{uid}) if $fieldkey eq 'uid' && $plugins->{PubKey}; + + slashDisplay('editUser', { + useredit => $user_edit, + admin_flag => $admin_flag, + title => $title, + editkey => $editkey, + admin_block => $admin_block, + note => $note, + }); +} + +################################################################# +sub editHome { + my($hr) = @_; + my $id = $hr->{uid} || ''; + my $note = $hr->{note} || ''; + + my $slashdb = getCurrentDB(); + my $form = getCurrentForm(); + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $hr->{tab_selected_1} || "", + }); + + my($formats, $title, $tzformat_select); + my $user_edit = {}; + my $fieldkey; + + my $admin_flag = ($user->{is_admin}) ? 1 : 0; + my $admin_block = ''; + + if ($admin_flag && $form->{userfield}) { + $id ||= $form->{userfield}; + if ($form->{userfield} =~ /^\d+$/) { + $user_edit = $slashdb->getUser($id); + $fieldkey = 'uid'; + } else { + $user_edit = $slashdb->getUser($slashdb->getUserUID($id)); + $fieldkey = 'nickname'; + } + } else { + $user_edit = $id eq '' ? $user : $slashdb->getUser($id); + $fieldkey = 'uid'; + } +#use Data::Dumper; $Data::Dumper::Sortkeys = 1; print STDERR scalar(localtime) . " user_edit: " . Dumper($user_edit); + + return if isAnon($user_edit->{uid}) && ! $admin_flag; + $admin_block = getUserAdmin($id, $fieldkey, 1) if $admin_flag; + + $title = getTitle('editHome_title'); + + return if $user->{seclev} < 100 && isAnon($user_edit->{uid}); + + $formats = $slashdb->getDescriptions('dateformats'); + $tzformat_select = createSelect('tzformat', $formats, $user_edit->{dfid}, 1); + + my $lb_check = $user_edit->{lowbandwidth} ? $constants->{markup_checked_attribute} : ''; + my $sd_check = $user_edit->{simpledesign} ? $constants->{markup_checked_attribute} : ''; + my $i_check = $user_edit->{noicons} ? $constants->{markup_checked_attribute} : ''; + my $w_check = $user_edit->{willing} ? $constants->{markup_checked_attribute} : ''; + + my $tilde_ed = tildeEd($user_edit); + + slashDisplay('editHome', { + title => $title, + admin_block => $admin_block, + user_edit => $user_edit, + tzformat_select => $tzformat_select, + i_check => $i_check, + w_check => $w_check, + lb_check => $lb_check, + sd_check => $sd_check, + tilde_ed => $tilde_ed, + note => $note, + }); +} + +################################################################# +sub editComm { + my($hr) = @_; + my $id = $hr->{uid} || ''; + my $note = $hr->{note} || ''; + + my $slashdb = getCurrentDB(); + my $form = getCurrentForm(); + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + my $user_edit = {}; + my($formats, $commentmodes_select, $commentsort_select, $title, + $uthreshold_select, $highlightthresh_select, $posttype_select, + $bytelimit_select); + + my $admin_block = ''; + my $fieldkey; + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $hr->{tab_selected_1} || "", + }); + + my $admin_flag = $user->{is_admin} ? 1 : 0; + + if ($admin_flag && $form->{userfield}) { + $id ||= $form->{userfield}; + if ($form->{userfield} =~ /^\d+$/) { + $user_edit = $slashdb->getUser($id); + $fieldkey = 'uid'; + } else { + $user_edit = $slashdb->getUser($slashdb->getUserUID($id)); + $fieldkey = 'nickname'; + } + } else { + $user_edit = $id eq '' ? $user : $slashdb->getUser($id); + $fieldkey = 'uid'; + } + + my $hi = $constants->{comment_maxscore} - $constants->{comment_minscore}; + my $lo = -$hi; + my @range = map { $_ > 0 ? "+$_" : $_ } ($lo .. $hi); + + my @reasons = ( ); + my %reason_select = ( ); + if ($constants->{m1}) { + my $mod_reader = getObject("Slash::$constants->{m1_pluginname}", { db_type => 'reader' }); + my $reasons = $mod_reader->getReasons(); + for my $id (sort { $a <=> $b } keys %$reasons) { + push @reasons, $reasons->{$id}{name}; + } + # Reason modifiers + for my $reason_name (@reasons) { + my $key = "reason_alter_$reason_name"; + $reason_select{$reason_name} = createSelect( + $key, \@range, + $user_edit->{$key} || 0, 1, 1 + ); + } + } + + # Zoo relation modifiers + my %people_select; + my @people = qw(friend foe anonymous fof eof freak fan); + for (@people) { + my $key = "people_bonus_$_"; + $people_select{$_} = createSelect($key, \@range, + $user_edit->{$key} || 0, 1, 1 + ); + } + + # New-user modifier + my $new_user_bonus_select = createSelect('new_user_bonus', \@range, + $user_edit->{new_user_bonus} || 0, 1, 1); + my $new_user_percent_select = createSelect('new_user_percent', + [( 1..15, 20, 25, 30, 35, 40, 45, 50, 55, + 60, 65, 70, 75, 80, 85, 90, 95 )], + $user_edit->{new_user_percent} || 100, 1, 1); + # Karma modifier + my $karma_bonus = createSelect('karma_bonus', \@range, + $user_edit->{karma_bonus} || 0, 1, 1); + # Subscriber modifier + my $subscriber_bonus = createSelect('subscriber_bonus', \@range, + $user_edit->{subscriber_bonus} || 0, 1, 1); + + # Length modifier + my $small_length_bonus_select = createSelect('clsmall_bonus', \@range, + $user_edit->{clsmall_bonus} || 0, 1, 1); + my $long_length_bonus_select = createSelect('clbig_bonus', \@range, + $user_edit->{clbig_bonus} || 0, 1, 1); + + return if isAnon($user_edit->{uid}) && ! $admin_flag; + $admin_block = getUserAdmin($id, $fieldkey, 1) if $admin_flag; + + $title = getTitle('editComm_title'); + + $formats = $slashdb->getDescriptions('commentmodes'); + $commentmodes_select=createSelect('umode', $formats, $user_edit->{mode}, 1); + + $formats = $slashdb->getDescriptions('sortcodes'); + $commentsort_select = createSelect( + 'commentsort', $formats, $user_edit->{commentsort}, 1 + ); + + $formats = $slashdb->getDescriptions('threshcodes'); + $uthreshold_select = createSelect( + 'uthreshold', $formats, $user_edit->{threshold}, 1 + ); + + $formats = $slashdb->getDescriptions('threshcodes'); + $highlightthresh_select = createSelect( + 'highlightthresh', $formats, $user_edit->{highlightthresh}, 1 + ); + + $user_edit->{bytelimit} = $constants->{defaultbytelimit} + if $user_edit->{bytelimit} < 0 || $user_edit->{bytelimit} > 7; + my $bytelimit_desc = $user_edit->{is_subscriber} ? 'bytelimit' : 'bytelimit_sub'; + $formats = $slashdb->getDescriptions($bytelimit_desc); + $bytelimit_select = createSelect( + 'bytelimit', $formats, $user_edit->{bytelimit}, 1 + ); + + my $h_check = $user_edit->{hardthresh} ? $constants->{markup_checked_attribute} : ''; + my $r_check = $user_edit->{reparent} ? $constants->{markup_checked_attribute} : ''; + my $n_check = $user_edit->{noscores} ? $constants->{markup_checked_attribute} : ''; + my $s_check = $user_edit->{nosigs} ? $constants->{markup_checked_attribute} : ''; + my $b_check = $user_edit->{nobonus} ? $constants->{markup_checked_attribute} : ''; + my $sb_check = $user_edit->{nosubscriberbonus} ? $constants->{markup_checked_attribute} : ''; + my $p_check = $user_edit->{postanon} ? $constants->{markup_checked_attribute} : ''; + my $nospell_check = $user_edit->{no_spell} ? $constants->{markup_checked_attribute} : ''; + my $s_mod_check = $user_edit->{mod_with_comm} ? $constants->{markup_checked_attribute} : ''; + my $s_m2_check = $user_edit->{m2_with_mod} ? $constants->{markup_checked_attribute} : ''; + my $s_m2c_check = $user_edit->{m2_with_comm_mod} ? $constants->{markup_checked_attribute} : ''; + + $formats = $slashdb->getDescriptions('postmodes'); + $posttype_select = createSelect( + 'posttype', $formats, $user_edit->{posttype}, 1 + ); + + slashDisplay('editComm', { + title => $title, + admin_block => $admin_block, + user_edit => $user_edit, + h_check => $h_check, + r_check => $r_check, + n_check => $n_check, + s_check => $s_check, + b_check => $b_check, + sb_check => $sb_check, + p_check => $p_check, + s_mod_check => $s_mod_check, + s_m2_check => $s_m2_check, + s_m2c_check => $s_m2c_check, + nospell_check => $nospell_check, + commentmodes_select => $commentmodes_select, + commentsort_select => $commentsort_select, + highlightthresh_select => $highlightthresh_select, + uthreshold_select => $uthreshold_select, + posttype_select => $posttype_select, + reasons => \@reasons, + reason_select => \%reason_select, + people => \@people, + people_select => \%people_select, + new_user_percent_select => $new_user_percent_select, + new_user_bonus_select => $new_user_bonus_select, + note => $note, + karma_bonus => $karma_bonus, + subscriber_bonus => $subscriber_bonus, + small_length_bonus_select => $small_length_bonus_select, + long_length_bonus_select => $long_length_bonus_select, + bytelimit_select => $bytelimit_select, + }); +} + +################################################################# +sub saveUserAdmin { + my($hr) = @_; + my $slashdb = getCurrentDB(); + my $form = getCurrentForm(); + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + + my($user_edits_table, $user_edit) = ({}, {}); + my $author_flag; + my $note = ''; + my $srcid; + my $id; + my $user_editfield_flag; + my $banned = 0; + my $banref; + if ($form->{uid}) { + $user_editfield_flag = 'uid'; + $id = $form->{uid}; + $user_edit = $slashdb->getUser($id); + $srcid = $id; + + } elsif ($form->{subnetid}) { + $user_editfield_flag = 'subnetid'; + ($id, $user_edit->{subnetid}) = ($form->{subnetid}, $form->{subnetid}); + $user_edit->{nonuid} = 1; + $srcid = convert_srcid(subnetid => $id); + + } elsif ($form->{ipid}) { + $user_editfield_flag = 'ipid'; + ($id, $user_edit->{ipid}) = ($form->{ipid}, $form->{ipid}); + $user_edit->{nonuid} = 1; + $srcid = convert_srcid(ipid => $id); + + } elsif ($form->{md5id}) { + $user_editfield_flag = 'md5id'; + my $fieldname = $form->{fieldname} || 'md5id'; + ($id, $user_edit->{$fieldname}) + = ($form->{md5id}, $form->{md5id}); + warn "form field md5id specified, no srcid saving possible"; + + } elsif ($form->{srcid}) { + $user_editfield_flag = 'srcid'; + my $fieldname = $form->{fieldname} || 'srcid'; + ($id, $user_edit->{$fieldname}) + = ($form->{srcid}, $form->{srcid}); + $srcid = $id; + + } else { + # If we were not fed valid data, don't do anything. + return ; + } + + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $all_al2types = $reader->getAL2Types; + my $al2_change = { }; + + # First, get a hash of what aclams (AL2's and ACLs) this user + # had when the previous admin page was loaded. + # XXXSRCID Right now acl_old is just calculated for debugging + # printing purposes, not actually used. + my @al2_old = ( ); + @al2_old = + @{ $form->{al2_old_multiple} } if $form->{al2_old_multiple}; + my %al2_old = ( map { ($_, 1) } @al2_old ); + my @acl_old = ( ); + @acl_old = + @{ $form->{acl_old_multiple} } if $form->{acl_old_multiple}; + my %acl_old = ( map { ($_, 1) } @acl_old ); + + # Next, get the list of the new data submitted. Separate + # it out into AL2's which are legitimate. Anything left + # over is an ACL. + my @al2_new_formfields = ( ); + @al2_new_formfields = @{ $form->{aclams_new_multiple} } if $form->{aclams_new_multiple}; + my @al2_new_submitted = map { s/^aclam_//; $_ } @al2_new_formfields; + my @al2_new = grep { exists $all_al2types->{$_} } @al2_new_submitted; + my %al2_new = ( map { ($_, 1) } @al2_new ); + my @acl_new = grep { !$al2_new{$_} } @al2_new_submitted; + my %acl_new = ( map { ($_, 1) } @acl_new ); + + # Find out what changed for AL2's. + for my $al2 (@al2_old, @al2_new) { + next if defined($al2_old{$al2}) && defined($al2_new{$al2}) + && $al2_old{$al2} == $al2_new{$al2}; + $al2_change->{$al2} = $al2_new{$al2} ? 1 : 0; + } +#print STDERR "al2_change for '$srcid': " . Dumper($al2_change); + # If there's a comment, throw that in. + if ($form->{al2_new_comment}) { + $al2_change->{comment} = $form->{al2_new_comment}; + } + $al2_change = undef if !keys %$al2_change; + + # Find out what changed for ACL's. + my $acl_change = { }; + for my $acl (@acl_old, @acl_new) { + next if $acl_old{$acl} == $acl_new{$acl}; + $acl_change->{$acl} = $acl_new{$acl} ? 1 : 0; + } + $acl_change = undef if !keys %$acl_change; + + if ($user->{is_admin} && $srcid) { + $slashdb->setAL2($srcid, $al2_change); + } + if ($user->{is_admin} && ($user_editfield_flag eq 'uid' || + $user_editfield_flag eq 'nickname')) { + + # This admin user cannot assign a seclev higher than he/she + # already has. + my $seclev = $form->{seclev}; + $seclev = $user->{seclev} if $seclev > $user->{seclev}; + $user_edits_table->{seclev} = $seclev; + + $user_edits_table->{section} = $form->{section}; + $user_edits_table->{author} = $form->{author} ? 1 : 0 ; + $user_edits_table->{defaultpoints} = $form->{defaultpoints}; + $user_edits_table->{tokens} = $form->{tokens}; + $user_edits_table->{tag_clout} = $form->{tag_clout}; + $user_edits_table->{m2info} = $form->{m2info}; + $user_edits_table->{acl} = $acl_change if $acl_change; + + my $author = $slashdb->getAuthor($id); + my $was_author = ($author && $author->{author}) ? 1 : 0; + + $slashdb->setUser($id, $user_edits_table); + + $note .= getMessage('saveuseradmin_saveduser', { field => $user_editfield_flag, id => $id }); + + if ($was_author xor $user_edits_table->{author}) { + # A frequently-asked question for new Slash admins is + # why their authors aren't showing up immediately. + # Give them some help here with an informative message. + $note .= getMessage('saveuseradmin_authorchg', { + basedir => $slashdb->getDescriptions("site_info") + ->{base_install_directory}, + virtuser => $slashdb->{virtual_user}, + }); + + } + } + + if (!$user_edit->{nonuid}) { + if ($form->{expired} && $form->{expired} eq 'on') { +# $slashdb->setExpired($user_edit->{uid}); + + } else { +# $slashdb->setUnexpired($user_edit->{uid}); + } + } + + my $data = { uid => $id }; + $data->{note} = $note if defined $note; + showInfo($data); +} + +################################################################# +sub savePasswd { + my($hr) = @_; + my $note = $hr->{noteref} || undef; + + my $slashdb = getCurrentDB(); + my $form = getCurrentForm(); + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + + my $error_flag = 0; + my $user_edit = {}; + my $uid; + + my $user_edits_table = {}; + + if ($user->{is_admin}) { + $uid = $form->{uid} || $user->{uid}; + } else { + $uid = ($user->{uid} == $form->{uid}) ? $form->{uid} : $user->{uid}; + } + + $user_edit = $slashdb->getUser($uid); + + if (!$user_edit->{nickname}) { + $$note .= getError('cookie_err', { titlebar => 0 }, 0, 1) + if $note; + $error_flag++; + } + + if ($form->{pass1} ne $form->{pass2}) { + $$note .= getError('saveuser_passnomatch_err', { titlebar => 0 }, 0, 1) + if $note; + $error_flag++; + } + + if (!$form->{pass1} || length $form->{pass1} < 6) { + $$note .= getError('saveuser_passtooshort_err', { titlebar => 0 }, 0, 1) + if $note; + $error_flag++; + } + + if (!$user->{is_admin}){ + # not an admin -- check old password before changing passwd + my $return_uid = $slashdb->getUserAuthenticate($uid, $form->{oldpass}, 1); + if (!$return_uid || $return_uid != $uid) { + $$note .= getError('saveuser_badoldpass_err', { titlebar => 0 }, 0, 1) + if $note; + $error_flag++; + + } + } + + if (! $error_flag) { + $user_edits_table->{passwd} = $form->{pass1} if $form->{pass1}; + $user_edits_table->{session_login} = $form->{session_login}; + $user_edits_table->{cookie_location} = $form->{cookie_location}; + + # changed pass, so delete all logtokens + $slashdb->deleteLogToken($form->{uid}, 1); + + if ($user->{admin_clearpass} + && !$user->{state}{admin_clearpass_thisclick}) { + # User is an admin who sent their password in the clear + # some time ago; now that it's been changed, we'll forget + # about that incident, unless this click was sent in the + # clear as well. + $user_edits_table->{admin_clearpass} = ''; + } + + getOtherUserParams($user_edits_table); + $slashdb->setUser($uid, $user_edits_table) ; + $$note .= getMessage('saveuser_passchanged_msg', + { nick => $user_edit->{nickname}, uid => $user_edit->{uid} }, + 0, 1) if $note; + + # only set cookie if user is current user + if ($form->{uid} eq $user->{uid}) { + $user->{logtoken} = bakeUserCookie($uid, $slashdb->getLogToken($form->{uid}, 1)); + setCookie('user', $user->{logtoken}, $user_edits_table->{session_login}); + } + } + + return $error_flag; +} + +################################################################# +sub saveUser { + my($hr) = @_; + my $slashdb = getCurrentDB(); + my $form = getCurrentForm(); + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + my $gSkin = getCurrentSkin(); + my $plugins = $slashdb->getDescriptions('plugins'); + my $uid; + my $user_editfield_flag; + + $uid = $user->{is_admin} && $form->{uid} ? $form->{uid} : $user->{uid}; + my $user_edit = $slashdb->getUser($uid); + + my($note, $formname); + + $note .= getMessage('savenickname_msg', { + nickname => $user_edit->{nickname}, + }, 1); + + if (!$user_edit->{nickname}) { + $note .= getError('cookie_err', 0, 1); + } + + # Check to ensure that if a user is changing his email address, that + # it doesn't already exist in the userbase. + if ($user_edit->{realemail} ne $form->{realemail}) { + if ($slashdb->existsEmail($form->{realemail})) { + $note .= getError('emailexists_err', 0, 1); + $form->{realemail} = $user_edit->{realemail}; # can't change! + } + } + + my(%extr, $err_message, %limit); + $limit{sig} = 120; # schema is 200, give an extra buffer for domain tags + $limit{bio} = $constants->{users_bio_length} || 1024; # can be up to 2^16 + + for my $key (keys %limit) { + my $dat = chopEntity($form->{$key}, $limit{$key}); + $dat = strip_html($dat); + $dat = balanceTags($dat, { deep_nesting => 2, length => $limit{$key} }); + $dat = addDomainTags($dat) if $dat; + + # If the sig becomes too long to fit (domain tagging causes + # string expansion and tag balancing can too), warn the user to + # use shorter domain names and don't save their change. + if ($key eq 'sig' && defined($dat) && length($dat) > 200) { + print getError('sig_too_long_err'); + $extr{sig} = undef; + } + + # really, comment filters should ignore short length IMO ... oh well. + if (length($dat) > 1 && ! filterOk('comments', 'postersubj', $dat, \$err_message)) { + print getError('filter message', { + err_message => $err_message, + item => $key, + }); + $extr{$key} = undef; + } elsif (! compressOk('comments', 'postersubj', $dat)) { + print getError('compress filter', { + ratio => 'postersubj', + item => $key, + }); + $extr{$key} = undef; + } else { + $extr{$key} = $dat; + } + } + + # We should do some conformance checking on a user's pubkey, + # make sure it looks like one of the known types of public + # key. Until then, just make sure it doesn't have HTML. + $form->{pubkey} = $plugins->{'PubKey'} ? strip_nohtml($form->{pubkey}, 1) : ''; + + my $homepage = $form->{homepage}; + $homepage = '' if $homepage eq 'http://'; + $homepage = fudgeurl($homepage); + $homepage = URI->new_abs($homepage, $gSkin->{absolutedir}) + ->canonical + ->as_string if $homepage ne ''; + $homepage = substr($homepage, 0, 100) if $homepage ne ''; + + my $calendar_url = $form->{calendar_url}; + if (length $calendar_url) { + # fudgeurl() doesn't like webcal; will remove later anyway + $calendar_url =~ s/^webcal/http/i; + $calendar_url = fudgeurl($calendar_url); + $calendar_url = URI->new_abs($calendar_url, $gSkin->{absolutedir}) + ->canonical + ->as_string if $calendar_url ne ''; + + $calendar_url =~ s|^http://||i; + $calendar_url = substr($calendar_url, 0, 200) if $calendar_url ne ''; + } + + # for the users table + my $user_edits_table = { + homepage => $homepage, + realname => $form->{realname}, + pubkey => $form->{pubkey}, + copy => $form->{copy}, + quote => $form->{quote}, + calendar_url => $calendar_url, + yahoo => $form->{yahoo}, + jabber => $form->{jabber}, + aim => $form->{aim}, + aimdisplay => $form->{aimdisplay}, + icq => $form->{icq}, + playing => $form->{playing}, + mobile_text_address => $form->{mobile_text_address}, + }; + + for (keys %extr) { + $user_edits_table->{$_} = $extr{$_} if defined $extr{$_}; + } + + # don't want undef, want to be empty string so they + # will overwrite the existing record + for (keys %$user_edits_table) { + $user_edits_table->{$_} = '' unless defined $user_edits_table->{$_}; + } + + if ($user_edit->{realemail} ne $form->{realemail}) { + $user_edits_table->{realemail} = + chopEntity($form->{realemail}, 50); + my $new_fakeemail = ''; # at emaildisplay 0, don't show any email address + if ($user->{emaildisplay}) { + $new_fakeemail = getArmoredEmail($uid, $user_edits_table->{realemail}) + if $user->{emaildisplay} == 1; + $new_fakeemail = $user_edits_table->{realemail} + if $user->{emaildisplay} == 2; + } + $user_edits_table->{fakeemail} = $new_fakeemail; + + $note .= getMessage('changeemail_msg', { + realemail => $user_edit->{realemail} + }, 1); + + my $saveuser_emailtitle = getTitle('saveUser_email_title', { + nickname => $user_edit->{nickname}, + realemail => $form->{realemail} + }, 1); + my $saveuser_email_msg = getMessage('saveuser_email_msg', { + nickname => $user_edit->{nickname}, + realemail => $form->{realemail} + }, 1); + + sendEmail($form->{realemail}, $saveuser_emailtitle, $saveuser_email_msg); + doEmail($uid, $saveuser_emailtitle, $saveuser_email_msg); + } + + getOtherUserParams($user_edits_table); + $slashdb->setUser($uid, $user_edits_table); + + editUser({ uid => $uid, note => $note }); +} + + +################################################################# +sub saveComm { + my($hr) = @_; + my $slashdb = getCurrentDB(); + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $constants = getCurrentStatic(); + my($uid, $user_fakeemail); + + if ($user->{is_admin}) { + $uid = $form->{uid} || $user->{uid}; + } else { + $uid = ($user->{uid} == $form->{uid}) ? + $form->{uid} : $user->{uid}; + } + + # Do the right thing with respect to the chosen email display mode + # and the options that can be displayed. + my $user_edit = $slashdb->getUser($uid); + my $new_fakeemail = ''; # at emaildisplay 0, don't show any email address + if ($form->{emaildisplay}) { + $new_fakeemail = getArmoredEmail($uid) if $form->{emaildisplay} == 1; + $new_fakeemail = $user_edit->{realemail} if $form->{emaildisplay} == 2; + } + + my $name = $user->{seclev} && $form->{name} ? + $form->{name} : $user->{nickname}; + + my $note = getMessage('savenickname_msg', + { nickname => $name }); + + print getError('cookie_err') if isAnon($uid) || !$name; + + # Take care of the lists + # Enforce Ranges for variables that need it + $form->{commentlimit} = 0 if $form->{commentlimit} < 1; + my $cl_max = $constants->{comment_commentlimit} || 0; + $form->{commentlimit} = $cl_max if $cl_max > 0 && $form->{commentlimit} > $cl_max; + $form->{commentspill} = 0 if $form->{commentspill} < 1; + + # For some of these values, namely the ones that we happen to + # know get stored in users_param, we change them to 'undef' + # if they are the default value. This deletes them from the + # users_param table, which has the same effect as storing the + # default except it's faster all around. If we ever change + # the schema to promote these fields from params into a + # proper users_* table, then this will no longer be correct. + # See prepareUser(). + my $max = $constants->{comment_maxscore} - $constants->{comment_minscore}; + my $min = -$max; + my $karma_bonus = ($form->{karma_bonus} !~ /^[\-+]?\d+$/) ? "+1" : $form->{karma_bonus}; + my $subscriber_bonus = ($form->{subscriber_bonus} !~ /^[\-+]?\d+$/) ? "+1" : $form->{subscriber_bonus}; + my $new_user_bonus = ($form->{new_user_bonus} !~ /^[\-+]?\d+$/) ? 0 : $form->{new_user_bonus}; + my $new_user_percent = (($form->{new_user_percent} <= 100 && $form->{new_user_percent} >= 0) + ? $form->{new_user_percent} + : 100); + my $clsmall_bonus = ($form->{clsmall_bonus} !~ /^[\-+]?\d+$/) ? 0 : $form->{clsmall_bonus}; + my $clbig_bonus = ($form->{clbig_bonus} !~ /^[\-+]?\d+$/) ? 0 : $form->{clbig_bonus}; + + my $user_edits_table = { + discussion2 => $form->{discussion2} || undef, + d2_comment_q => $form->{d2_comment_q} || undef, + d2_comment_order => $form->{d2_comment_order} || undef, + clsmall => $form->{clsmall}, + clsmall_bonus => ($clsmall_bonus || undef), + clbig => $form->{clbig}, + clbig_bonus => ($clbig_bonus || undef), + commentlimit => $form->{commentlimit}, + bytelimit => $form->{bytelimit}, + commentsort => $form->{commentsort}, + commentspill => $form->{commentspill}, + domaintags => ($form->{domaintags} != 2 ? $form->{domaintags} : undef), + emaildisplay => $form->{emaildisplay} || undef, + fakeemail => $new_fakeemail, + highlightthresh => $form->{highlightthresh}, + maxcommentsize => $form->{maxcommentsize}, + mode => $form->{umode}, + posttype => $form->{posttype}, + threshold => $form->{uthreshold}, + nosigs => ($form->{nosigs} ? 1 : 0), + reparent => ($form->{reparent} ? 1 : 0), + noscores => ($form->{noscores} ? 1 : 0), + hardthresh => ($form->{hardthresh} ? 1 : 0), + no_spell => ($form->{no_spell} ? 1 : undef), + nobonus => ($form->{nobonus} ? 1 : undef), + nosubscriberbonus => ($form->{nosubscriberbonus} ? 1 : undef), + postanon => ($form->{postanon} ? 1 : undef), + new_user_percent => ($new_user_percent && $new_user_percent != 100 + ? $new_user_percent : undef), + new_user_bonus => ($new_user_bonus || undef), + karma_bonus => ($karma_bonus ne '+1' ? $karma_bonus : undef), + subscriber_bonus => ($subscriber_bonus || undef), + textarea_rows => ($form->{textarea_rows} != $constants->{textarea_rows} + ? $form->{textarea_rows} : undef), + textarea_cols => ($form->{textarea_cols} != $constants->{textarea_cols} + ? $form->{textarea_cols} : undef), + user_comment_sort_type => ($form->{user_comment_sort_type} != 2 + ? $form->{user_comment_sort_type} : undef ), + mod_with_comm => ($form->{mod_with_comm} ? 1 : undef), + m2_with_mod => ($form->{m2_with_mod} ? 1 : undef), + m2_with_comm_mod => ($form->{m2_with_mod_on_comm} ? 1 : undef), + + }; + + # set our default values for the items where an empty-string won't do + my $defaults = { + posttype => 2, + highlightthresh => 4, + maxcommentsize => 4096, + reparent => 1, + commentlimit => 100, + commentspill => 50, + mode => 'thread' + }; + + my $mod_reader = getObject("Slash::$constants->{m1_pluginname}", { db_type => 'reader' }); + my @reasons = ( ); + my $reasons = $mod_reader->getReasons(); + for my $id (sort { $a <=> $b } keys %$reasons) { + push @reasons, $reasons->{$id}{name}; + } + + for my $reason_name (@reasons) { + my $key = "reason_alter_$reason_name"; + my $answer = $form->{$key} || 0; + $answer = 0 if !$answer || $answer !~ /^[\-+]?\d+$/; + $user_edits_table->{$key} = ($answer == 0) ? '' : $answer; + } + + for (qw| friend foe anonymous fof eof freak fan |) { + my $answer = $form->{"people_bonus_$_"}; + $answer = 0 if $answer !~ /^[\-+]?\d+$/; + $user_edits_table->{"people_bonus_$_"} = ($answer == 0) ? '' : $answer; + } + getOtherUserParams($user_edits_table); + setToDefaults($user_edits_table, {}, $defaults) if $form->{restore_defaults}; + $slashdb->setUser($uid, $user_edits_table); + + editComm({ uid => $uid, note => $note }); +} + +################################################################# +sub saveHome { + my($hr) = @_; + my $slashdb = getCurrentDB(); + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $constants = getCurrentStatic(); + my($uid, $error); + + if ($user->{is_admin}) { + $uid = $form->{uid} || $user->{uid} ; + } else { + $uid = ($user->{uid} == $form->{uid}) ? + $form->{uid} : $user->{uid}; + } + my $edit_user = $slashdb->getUser($uid); + + my $name = $user->{seclev} && $form->{name} ? + $form->{name} : $user->{nickname}; + $name = substr($name, 0, 20); + + my $note = getMessage('savenickname_msg', + { nickname => $name }); + + if (isAnon($uid) || !$name) { + my $cookiemsg = getError('cookie_err'); + print $cookiemsg; + } + + # Using the existing list of slashboxes and the set of + # what's checked and not, build up the new list. + # (New arrivals go at the end.) + my $slashboxes = $edit_user->{slashboxes}; + # Only go through all this if the user clicked save, + # not "Restore Slashbox Defaults"! + my($boxes, $skinBoxes) = $slashdb->getPortalsCommon(); + my $default_slashboxes_textlist = join ",", + @{$skinBoxes->{$constants->{mainpage_skid}}}; + if (!$form->{restore_slashbox_defaults}) { + $slashboxes = $default_slashboxes_textlist if !$slashboxes; + my @slashboxes = split /,/, $slashboxes; + my %slashboxes = ( ); + for my $i (0..$#slashboxes) { + $slashboxes{$slashboxes[$i]} = $i; + } + # Add new boxes in. + for my $key (sort grep /^showbox_/, keys %$form) { + my($bid) = $key =~ /^showbox_(\w+)$/; + next if length($bid) < 1 || length($bid) > 30 || $bid !~ /^\w+$/; + if (! exists $slashboxes{$bid}) { + $slashboxes{$bid} = 999; # put it at the end + } + } + # Remove any boxes that weren't checked. + for my $bid (@slashboxes) { + delete $slashboxes{$bid} unless $form->{"showbox_$bid"}; + } + @slashboxes = sort { + $slashboxes{$a} <=> $slashboxes{$b} + || + $a cmp $b + } keys %slashboxes; + # This probably should be a var (and appear in tilded_customize_msg) + $#slashboxes = 19 if $#slashboxes > 19; + $slashboxes = join ",", @slashboxes; + } + # If we're right back to the default, that means the + # empty string. + if ($slashboxes eq $default_slashboxes_textlist) { + $slashboxes = ""; + } + + # Set the story_never and story_always fields. + my $author_hr = $slashdb->getDescriptions('authors'); + my $tree = $slashdb->getTopicTree(); + my(@story_never_topic, @story_never_author, @story_never_nexus); + my(@story_always_topic, @story_always_author); + my(@story_always_nexus, @story_full_brief_nexus, @story_brief_always_nexus, @story_full_best_nexus, @story_brief_best_nexus); + my($story_topic_all, $story_author_all, $story_nexus_all) = (0, 0, 0); + + # Topics are either present (value=2) or absent (value=0). If absent, + # push them onto the never list. Otherwise, do nothing. (There's no + # way to have an "always" topic, at the moment.) If the hidden + # field topictids_present is false, then there are no topic tids, + # skip this. + if ($form->{topictids_present}) { + for my $tid ( + sort { $a <=> $b } + grep { !$tree->{$_}{nexus} } + keys %$tree + ) { + my $key = "topictid$tid"; + $story_topic_all++; + if (!$form->{$key}) { push @story_never_topic, $tid } + } + } + # Authors are either present (value=2) or absent (value=0). If + # absent, push them onto the never list. Otherwise, do nothing. + # (There's no way to have an "always" author, at the moment.) + for my $aid (sort { $a <=> $b } keys %$author_hr) { + my $key = "aid$aid"; + $story_author_all++; + if (!$form->{$key}) { push @story_never_author, $aid } + } + # Nexuses can have value 0, 1, 2, 3, 4, 5. + # 0 means the never list, + # 1 means brief view of mainpage articles only + # 2 means full view of mainpage articles only + # 3 means brief view of all content + # 4 means full view of mainpage content, brief view of sectional + # 5 means full view of all content + for my $tid ( + sort { $a <=> $b } + map { /^nexustid(\d+)$/; $1 } + grep { /^nexustid\d+$/ } + keys %$form + ) { + my $key = "nexustid$tid"; + next unless $tid && $tree->{$tid} && $tree->{$tid}{nexus}; + $story_nexus_all++; + if (!$form->{$key}) { push @story_never_nexus, $tid } + elsif ($form->{$key} == 5 ) { push @story_always_nexus, $tid } + elsif ($form->{$key} == 4 ) { push @story_full_brief_nexus, $tid } + elsif ($form->{$key} == 3 ) { push @story_brief_always_nexus, $tid } + elsif ($form->{$key} == 2 ) { push @story_full_best_nexus, $tid } + elsif ($form->{$key} == 1 ) { push @story_brief_best_nexus, $tid } + + + } +#use Data::Dumper; $Data::Dumper::Sortkeys = 1; print STDERR scalar(localtime) . " s_n_t '@story_never_topic' s_n_a '@story_never_author' s_n_n '@story_never_nexus' s_a_n '@story_always_nexus' form: " . Dumper($form); + # Sanity check. + $#story_never_topic = 299 if $#story_never_topic > 299; + $#story_never_author = 299 if $#story_never_author > 299; + $#story_never_nexus = 299 if $#story_never_nexus > 299; + $#story_always_topic = 299 if $#story_always_topic > 299; + $#story_always_author = 299 if $#story_always_author > 299; + $#story_always_nexus = 299 if $#story_always_nexus > 299; + $#story_full_brief_nexus = 299 if $#story_full_brief_nexus > 299; + $#story_brief_always_nexus = 299 if $#story_brief_always_nexus > 299; + $#story_brief_best_nexus = 299 if $#story_brief_best_nexus > 299; + $#story_full_best_nexus = 299 if $#story_full_best_nexus > 299; + + my $story_never_topic = join ",", @story_never_topic; + $story_never_topic = ($constants->{subscribe} && $user->{is_subscriber}) + ? checkList($story_never_topic, 1024) + : checkList($story_never_topic); + my $story_never_author = checkList(join ",", @story_never_author); + my $story_never_nexus = checkList(join ",", @story_never_nexus); + my $story_always_topic = checkList(join ",", @story_always_topic); + $story_always_topic = ($constants->{subscribe} && $user->{is_subscriber}) + ? checkList($story_always_topic, 1024) + : checkList($story_always_topic); + my $story_always_author = checkList(join ",", @story_always_author); + + my $story_always_nexus = checkList(join ",", @story_always_nexus); + my $story_full_brief_nexus = checkList(join ",", @story_full_brief_nexus); + my $story_brief_always_nexus = checkList(join ",", @story_brief_always_nexus); + my $story_brief_best_nexus = checkList(join ",", @story_brief_best_nexus); + my $story_full_best_nexus = checkList(join ",", @story_full_best_nexus); + + + my $user_edits_table = { + story_never_topic => $story_never_topic, + story_never_author => $story_never_author, + story_never_nexus => $story_never_nexus, + story_always_topic => $story_always_topic, + story_always_author => $story_always_author, + story_always_nexus => $story_always_nexus, + story_brief_always_nexus => $story_brief_always_nexus, + story_full_brief_nexus => $story_full_brief_nexus, + story_full_best_nexus => $story_full_best_nexus, + story_brief_best_nexus => $story_brief_best_nexus, + + slashboxes => checkList($slashboxes, 1024), + + maxstories => 30, # XXXSKIN fix this later + noboxes => ($form->{useslashboxes} ? 0 : 1), + lowbandwidth => ($form->{lowbandwidth} ? 1 : 0), + simpledesign => ($form->{simpledesign} ? 1 : 0), + noicons => ($form->{noicons} ? 1 : 0), + willing => ($form->{willing} ? 1 : 0), + }; + + if (defined $form->{tzcode} && defined $form->{tzformat}) { + $user_edits_table->{tzcode} = $form->{tzcode}; + $user_edits_table->{dfid} = $form->{tzformat}; + $user_edits_table->{dst} = $form->{dst}; + } + + # Force the User Space area to contain only known-good HTML tags. + # Unfortunately the cookie login model makes it just too risky + # to allow scripts in here; CSS's steal passwords. There are + # no known vulnerabilities at this time, but a combination of the + # social engineering taking place (inviting users to put Javascript + # from websites in here, and making available script URLs for that + # purpose), plus the fact that this could be used to amplify the + # seriousness of any future vulnerabilities, means it's way past + # time to shut this feature down. - Jamie 2002/03/06 + + # it's a VARCHAR ... + my $mylinks_limit = 255; + $user_edits_table->{mylinks} = balanceTags(strip_html( + chopEntity($form->{mylinks} || '', $mylinks_limit) + ), { deep_nesting => 2, length => $mylinks_limit }); + + $user_edits_table->{mylinks} = '' unless defined $user_edits_table->{mylinks}; + + $error = 1; + # must select at least 1/4 of nexuses, topics, authors + if ( scalar(@story_never_author) > ($story_author_all * 3/4) ) { + $note = getError('editHome_too_many_disabled'); + } elsif ( scalar(@story_never_nexus) > ($story_nexus_all * 3/4) ) { + $note = getError('editHome_too_many_disabled'); + } elsif ( scalar(@story_never_topic) > ($story_topic_all * 3/4) ) { + $note = getError('editHome_too_many_disabled'); + } else { + $error = 0; + } + + unless ($error) { + # If a user is unwilling to moderate, we should cancel all points, lest + # they be preserved when they shouldn't be. + if (!isAnon($uid) && !$form->{willing}) { + $slashdb->setUser($uid, { points => 0 }); + } + + getOtherUserParams($user_edits_table); + if ($form->{restore_defaults}) { + setToDefaults($user_edits_table, {}, { + maxstories => 30, + tzcode => "EST", + # XXX shouldn't this reset ALL the defaults, + # not just these two? + }); + } + if ($form->{restore_slashbox_defaults}) { + setToDefaults($user_edits_table, { + 'story_never_topic' => 1, + 'story_never_author' => 1, + 'story_never_nexus' => 1, + 'story_always_topic' => 1, + 'story_always_author' => 1, + 'story_always_nexus' => 1, + 'story_full_brief_nexus' => 1, + 'story_brief_always_nexus' => 1, + 'story_full_best_nexus' => 1, + 'story_brief_best_nexus' => 1, + 'maxstories' => 1, + 'noboxes' => 1, + 'light' => 1, + 'noicons' => 1, + 'willing' => 1 + }, { slashboxes => "" }); + } + +#print scalar(localtime) . " uet: " . Dumper($user_edits_table); + $slashdb->setUser($uid, $user_edits_table); + } + + editHome({ uid => $uid, note => $note }); +} + +################################################################# +# A generic way for a site to allow users to edit data about themselves. +# Most useful when your plugin or theme wants to let the user change +# minor settings but you don't want to write a whole new version +# of users.pl to provide a user interface. The user can save any +# param of the format "opt_foo", as long as "foo" shows up in +# getMiscUserOpts which lists all the misc opts that this user can edit. +# This is *not* protected by formkeys (yet), so assume attackers can make +# users click and accidentally edit their own settings: no really important +# data should be stored in this way. +sub editMiscOpts { + my($hr) = @_; + my $slashdb = getCurrentDB(); + my $user = getCurrentUser(); + my $constants = getCurrentStatic(); + my $note = $hr->{note} || ""; + + return if $user->{is_anon}; # shouldn't be, but can't hurt to check + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $hr->{tab_selected_1} || "", + }); + + my $edit_user = $slashdb->getUser($user->{uid}); + my $title = getTitle('editMiscOpts_title'); + + my $opts = $slashdb->getMiscUserOpts(); + for my $opt (@$opts) { + my $opt_name = "opt_" . $opt->{name}; + $opt->{checked} = $edit_user->{$opt_name} ? 1 : 0; + } + + slashDisplay('editMiscOpts', { +# useredit => $user_edit, + title => $title, + opts => $opts, + note => $note, + }); +} + +################################################################# +# +sub saveMiscOpts { + my($hr) = @_; + my $slashdb = getCurrentDB(); + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $constants = getCurrentStatic(); + + return if $user->{is_anon}; # shouldn't be, but can't hurt to check + + my $edit_user = $slashdb->getUser($user->{uid}); + my %opts_ok_hash = ( ); + my $opts = $slashdb->getMiscUserOpts(); + for my $opt (@$opts) { + $opts_ok_hash{"opt_$opt->{name}"} = 1; + } + + my $update = { }; + for my $opt (grep /^opt_/, keys %$form) { + next unless $opts_ok_hash{$opt}; + $update->{$opt} = $form->{$opt} ? 1 : 0; + } + + # Make the changes. + $slashdb->setUser($edit_user->{uid}, $update); + + # Inform the user the change was made. Since we don't + # require formkeys, we always want to print a message to + # make sure the user sees what s/he did. This is done + # by passing in a note which ends up passed to the + # editMiscOpts template, which displays it. + editMiscOpts({ note => getMessage('savemiscopts_msg') }); +} + +################################################################# +sub listReadOnly { + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + + my $readonlylist = $reader->getAL2List('nopost'); + + slashDisplay('listReadOnly', { + readonlylist => $readonlylist, + }); + +} + +################################################################# +sub listBanned { + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + + my $bannedlist = $reader->getAL2List('ban'); + + slashDisplay('listBanned', { + bannedlist => $bannedlist, + }); + +} + +################################################################# +sub topAbusers { + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + + my $topabusers = $reader->getTopAbusers(); + + slashDisplay('topAbusers', { + topabusers => $topabusers, + }); +} + +################################################################# +sub listAbuses { + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $constants = getCurrentStatic(); + + my $abuses = $reader->getAbuses($form->{key}, $form->{abuseid}); + + slashDisplay('listAbuses', { + abuseid => $form->{abuseid}, + abuses => $abuses, + }); +} + +################################################################# +sub forceAccountVerify { + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $slashdb = getCurrentDB(); + my $constants = getCurrentStatic(); + + my $uid = $form->{uid}; + my $useredit = $slashdb->getUser($uid); + + if ($useredit->{uid}) { + my $newpasswd = $slashdb->resetUserAccount($uid); + $slashdb->deleteLogToken($uid, 1); + my $emailtitle = getTitle('reset_acct_email_title', { + nickname => $useredit->{nickname} + }, 1); + + my $msg = getMessage('reset_acct_msg', { + newpasswd => $newpasswd, + tempnick => $useredit->{nickname}, + }, 1); + + $slashdb->setUser($useredit->{uid}, { + waiting_for_account_verify => 1, + account_verify_request_time => $slashdb->getTime() + }); + + doEmail($useredit->{uid}, $emailtitle, $msg) if $useredit->{uid}; + } + + print getMessage("reset_acct_complete", { useredit => $useredit }, 1); +} + +################################################################# +sub displayForm { + my($hr) = @_; + + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $slashdb = getCurrentDB(); + my $constants = getCurrentStatic(); + + my $suadmin_flag = $user->{seclev} >= 10000 ? 1 : 0; + + print createMenu("users", { + style => 'tabbed', + justify => 'right', + color => 'colored', + tab_selected => $hr->{tab_selected_1} || "", + }); + + my $op = $hr->{op} || $form->{op} || 'displayform'; + + my $ops = { + displayform => 'loginForm', + edithome => 'loginForm', + editcomm => 'loginForm', + edituser => 'loginForm', +# mailpasswdform => 'sendPasswdForm', +# newuserform => 'newUserForm', + userclose => 'loginForm', + userlogin => 'loginForm', + editmiscopts => 'loginForm', + savemiscopts => 'loginForm', + default => 'loginForm' + }; + + $op = 'default' if !defined($ops->{$op}); + + my($title, $title2, $msg1, $msg2) = ('', '', '', ''); + + if ($op eq 'userclose') { + $title = getMessage('userclose'); + + } elsif ($op eq 'displayForm') { + $title = $form->{unickname} + ? getTitle('displayForm_err_title') + : getTitle('displayForm_title'); + } elsif ($op eq 'mailpasswdform') { + $title = getTitle('mailPasswdForm_title'); + } elsif ($op eq 'newuserform') { + $title = getTitle('newUserForm_title'); + } else { + $title = getTitle('displayForm_title'); + } + + $form->{unickname} ||= $form->{newusernick}; + + if ($form->{newusernick}) { + $title2 = getTitle('displayForm_dup_title'); + } else { + $title2 = getTitle('displayForm_new_title'); + } + + $msg1 = getMessage('dispform_new_msg_1'); + if (! $form->{newusernick} && $op eq 'newuserform') { + $msg2 = getMessage('dispform_new_msg_2'); + } elsif ($op eq 'displayform' || $op eq 'userlogin') { + $msg2 = getMessage('newuserform_msg'); + } + + slashDisplay($ops->{$op}, { + newnick => nickFix($form->{newusernick}), + suadmin_flag => $suadmin_flag, + title => $title, + title2 => $title2, + logged_in => $user->{is_anon} ? 0 : 1, + msg1 => $msg1, + msg2 => $msg2 + }); +} + +################################################################# +# this groups all the messages together in +# one template, called "messages;users;default" +sub getMessage { + my($value, $hashref, $nocomm) = @_; + $hashref ||= {}; + $hashref->{value} = $value; + return slashDisplay('messages', $hashref, + { Return => 1, Nocomm => $nocomm }); +} + +################################################################# +# this groups all the errors together in +# one template, called "errors;users;default" +sub getError { + my($value, $hashref, $nocomm) = @_; + $hashref ||= {}; + $hashref->{value} = $value; + return slashDisplay('errors', $hashref, + { Return => 1, Nocomm => $nocomm }); +} + +################################################################# +# this groups all the titles together in +# one template, called "users-titles" +sub getTitle { + my($value, $hashref, $nocomm) = @_; + $hashref ||= {}; + $hashref->{value} = $value; + return slashDisplay('titles', $hashref, + { Return => 1, Nocomm => $nocomm }); +} + +################################################################# +# getUserAdmin - returns a block of HTML text that provides +# information and editing capabilities for admin users. +# Most of this data is already in the getUserAdmin template, +# but really, we should try to get more of this logic into +# that template. +sub getUserAdmin { + my($id, $field, $seclev_field) = @_; + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $logdb = getObject('Slash::DB', { db_type => 'log_slave' }); + + + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $constants = getCurrentStatic(); + my $slashdb = getCurrentDB(); + $id ||= $user->{uid}; + + my($expired, $uidstruct, $readonly); + my($user_edit, $user_editfield, $ipstruct, $ipstruct_order, $authors, $author_flag, $topabusers, $thresh_select,$section_select); + my $srcid; + my $proxy_check = {}; + my @accesshits; + my $user_editinfo_flag = (!$form->{op} || $form->{op} eq 'userinfo' + || $form->{userinfo} || $form->{saveuseradmin} + ) ? 1 : 0; + my $authoredit_flag = ($user->{seclev} >= 10000) ? 1 : 0; + my $sectionref = $reader->getDescriptions('skins'); + $sectionref->{''} = getData('all_sections'); + + $field ||= 'uid'; + if ($field eq 'uid') { + $user_edit = $slashdb->getUser($id); + $user_editfield = $user_edit->{uid}; + $srcid = convert_srcid( uid => $id ); + #$expired = $slashdb->checkExpired($user_edit->{uid}) ? $constants->{markup_checked_attribute} : ''; + $ipstruct = $slashdb->getNetIDStruct($user_edit->{uid}); + @accesshits = $logdb->countAccessLogHitsInLastX($field, $user_edit->{uid}) if defined($logdb); + $section_select = createSelect('section', $sectionref, $user_edit->{section}, 1); + + } elsif ($field eq 'nickname') { + $user_edit = $slashdb->getUser($slashdb->getUserUID($id)); + $user_editfield = $user_edit->{nickname}; + #$expired = $slashdb->checkExpired($user_edit->{uid}) ? $constants->{markup_checked_attribute} : ''; + $ipstruct = $slashdb->getNetIDStruct($user_edit->{uid}); + @accesshits = $logdb->countAccessLogHitsInLastX('uid', $user_edit->{uid}) if defined($logdb); + $section_select = createSelect('section', $sectionref, $user_edit->{section}, 1); + + } elsif ($field eq 'md5id') { + $user_edit->{nonuid} = 1; + $user_edit->{md5id} = $id; + if ($form->{fieldname} && $form->{fieldname} =~ /^(ipid|subnetid)$/) { + $uidstruct = $slashdb->getUIDStruct($form->{fieldname}, $user_edit->{md5id}); + @accesshits = $logdb->countAccessLogHitsInLastX($form->{fieldname}, $user_edit->{md5id}) if defined($logdb); + } else { + $uidstruct = $slashdb->getUIDStruct('md5id', $user_edit->{md5id}); + @accesshits = $logdb->countAccessLogHitsInLastX($field, $user_edit->{md5id}) if defined($logdb); + } + + } elsif ($field eq 'ipid') { + $user_edit->{nonuid} = 1; + $user_edit->{ipid} = $id; + $srcid = convert_srcid( ipid => $id ); + $user_editfield = $id; + $uidstruct = $slashdb->getUIDStruct('ipid', $user_edit->{ipid}); + @accesshits = $logdb->countAccessLogHitsInLastX('host_addr', $user_edit->{ipid}) if defined($logdb); + + if ($form->{userfield} =~/^\d+\.\d+\.\d+\.(\d+)$/) { + if ($1 ne "0"){ + $proxy_check->{available} = 1; + $proxy_check->{results} = $slashdb->checkForOpenProxy($form->{userfield}) if $form->{check_proxy}; + } + } + + } elsif ($field eq 'subnetid') { + $user_edit->{nonuid} = 1; + $srcid = convert_srcid( ipid => $id ); + if ($id =~ /^(\d+\.\d+\.\d+)(?:\.\d)?/) { + $id = $1 . ".0"; + $user_edit->{subnetid} = $id; + } else { + $user_edit->{subnetid} = $id; + } + + $user_editfield = $id; + $uidstruct = $slashdb->getUIDStruct('subnetid', $user_edit->{subnetid}); + @accesshits = $logdb->countAccessLogHitsInLastX($field, $user_edit->{subnetid}) if defined($logdb); + + } elsif ($field eq "srcid") { + $user_edit->{nonuid} = 1; + $user_edit->{srcid} = $id; + $srcid = $id; + + } else { + $user_edit = $id ? $slashdb->getUser($id) : $user; + $user_editfield = $user_edit->{uid}; + $ipstruct = $slashdb->getNetIDStruct($user_edit->{uid}); + @accesshits = $logdb->countAccessLogHitsInLastX('uid', $user_edit->{uid}) if defined($logdb); + } + + ########## + # Put together the array and hashref that the template will need + # to construct the list for display to the admin. + # Note that currently a srcid which is not a uid (i.e. which + # represents an IP address or masked IP network) cannot have + # any ACLs assigned to it. This may change in the future. + # The term "aclam" is used because it has a field set for both + # every ACL and every access modifier (i.e. AL2 bit) that is set + # for this user or srcid. + # For now, ACLs will be listed for IPs as well. + # First get the list of ACLs that can be used. + my $all_acls_ar = $reader->getAllACLNames(); + my $all_acls_hr = { map { ( $_, 1 ) } @$all_acls_ar }; + # Add in any ACLs selected for this user (just in case + # getAllACLNames is cached and stale). + for my $acl (keys %{$user_edit->{acl}}) { + $all_acls_hr->{$acl} = 1; + } + # Start creating the $all_aclam_hr data, in which the keys are + # the HTML selection names that all begin with aclam_ and the + # the values are their names/descriptions shown to the admin. + # First put all the ACLs into the hash, if we're editing a user. + my $all_aclam_hr = { }; + if (!$user_edit->{nonuid}) { + $all_aclam_hr = { map { ( "aclam_$_", "ACL: $_" ) } keys %$all_acls_hr }; + } + # Next put in all the al2 types. + my $all_al2types = $reader->getAL2Types; + for my $key (keys %$all_al2types) { + next if $key eq 'comment'; # skip the 'comment' type + $all_aclam_hr->{"aclam_$key"} = $all_al2types->{$key}{title}; + } + # Finally, sort the keys of the hash into the order that we + # want them displayed to the admin (ACLs first). + my $all_acls_longkeys_hr = { map { ( "aclam_$_", 1 ) } keys %$all_acls_hr }; + my $all_aclam_ar = [ + sort { + (exists($all_acls_longkeys_hr->{$a}) ? -1 : 1) <=> (exists($all_acls_longkeys_hr->{$b}) ? -1 : 1) + || + $all_aclam_hr->{$a} cmp $all_aclam_hr->{$b} + } keys %$all_aclam_hr + ]; + # Now put together the hashref that identifies which of those + # items are selected for this user. + my $user_aclam_hr = { }; + for my $acl (keys %{ $user_edit->{acl} }) { + $user_aclam_hr->{"aclam_$acl"} = 1; + } + my $al2_tid_comment = $all_al2types->{comment}{al2tid} || 0; + my $al2_log_ar = [ ]; + my $al2_hr = { }; + # XXXSRCID Once we get rid of the silly 'md5id' field and all the + # other bizarre backward-compatibility code paths early in this + # function, this won't be necessary, but until then we need this + # sanity check... + if ($srcid) { + # getAL2 works with either a srcids hashref or a single srcid + $al2_hr = $slashdb->getAL2($srcid); + for my $al2 (keys %{ $al2_hr }) { + $user_aclam_hr->{"aclam_$al2"} = 1; + } + $al2_log_ar = $slashdb->getAL2Log($srcid); + } + # Generate al2_nick_hr, which will be populated with keys of all + # the (presumably) admin uids who have logged rows for this al2, + # and values of their nicks. + my $al2_nick_hr = { }; + for my $al2_log (@$al2_log_ar) { + my $uid = $al2_log->{adminuid}; + next if !$uid; # odd error, might want to flag this + $al2_nick_hr->{$uid} ||= $slashdb->getUser($uid, 'nickname'); + } + ########## + + $user_edit->{author} = ($user_edit->{author} && $user_edit->{author} == 1) + ? $constants->{markup_checked_attribute} : ''; + if (! $user->{nonuid}) { + my $threshcodes = $reader->getDescriptions('threshcode_values','',1); + $thresh_select = createSelect('defaultpoints', $threshcodes, $user_edit->{defaultpoints}, 1); + } + + if (!ref $ipstruct) { + undef $ipstruct; + } else { + @$ipstruct_order = sort { $ipstruct->{$b}{dmin} cmp $ipstruct->{$a}{dmin} } keys %$ipstruct; + } + + my $m2total = ($user_edit->{m2fair} || 0) + ($user_edit->{m2unfair} || 0); + if ($m2total) { + $user_edit->{m2unfairpercent} = sprintf("%.2f", + $user_edit->{m2unfair}*100/$m2total); + } + my $mod_total = ($user_edit->{totalmods} || 0) + ($user_edit->{stirred} || 0); + if ($mod_total) { + $user_edit->{stirredpercent} = sprintf("%.2f", + $user_edit->{stirred}*100/$mod_total); + } + if ($constants->{subscribe} and my $subscribe = getObject('Slash::Subscribe')) { + $user_edit->{subscribe_payments} = + $subscribe->getSubscriptionsForUser($user_edit->{uid}); + $user_edit->{subscribe_purchases} = + $subscribe->getSubscriptionsPurchasedByUser($user_edit->{uid},{ only_types => [ "grant", "gift" ] }); + } + my $ipid = $user_edit->{ipid}; + my $subnetid = $user_edit->{subnetid}; + my $post_restrictions = {}; + my ($subnet_karma, $ipid_karma); + + if ($ipid && !$subnetid) { + $ipid = md5_hex($ipid) if length($ipid) != 32; + $proxy_check->{ipid} = $ipid; + $proxy_check->{currently} = $slashdb->getKnownOpenProxy($ipid, "ipid"); + # This next call is very slow. + $subnetid = $reader->getSubnetFromIPIDBasedOnComments($ipid); + } + + if ($subnetid) { + $subnetid = md5_hex($subnetid) if length($subnetid) != 32; + # These next three calls can be very slow. In fact, getNetIDKarma + # is actually called twice on the same subnetid; if we can cache + # that data somehow that wouldn't be a bad idea. + $post_restrictions = $reader->getNetIDPostingRestrictions("subnetid", $subnetid); + $subnet_karma = $reader->getNetIDKarma("subnetid", $subnetid); + $ipid_karma = $reader->getNetIDKarma("ipid", $ipid) if $ipid; + } + + return slashDisplay('getUserAdmin', { + field => $field, + useredit => $user_edit, + srcid => $srcid, + all_aclam_ar => $all_aclam_ar, + all_aclam_hr => $all_aclam_hr, + user_aclam_hr => $user_aclam_hr, + al2_old => $al2_hr, + al2_log => $al2_log_ar, + al2_tid_comment => $al2_tid_comment, + al2_nick => $al2_nick_hr, + + userinfo_flag => $user_editinfo_flag, + userfield => $user_editfield, + ipstruct => $ipstruct, + ipstruct_order => $ipstruct_order, + uidstruct => $uidstruct, + accesshits => \@accesshits, + seclev_field => $seclev_field, + expired => $expired, + topabusers => $topabusers, + readonly => $readonly, + thresh_select => $thresh_select, + authoredit_flag => $authoredit_flag, + section_select => $section_select, + all_acls => $all_acls_hr, + proxy_check => $proxy_check, + subnet_karma => $subnet_karma, + ipid_karma => $ipid_karma, + post_restrictions => $post_restrictions + }, 1); +} + +################################################################# +# this is to allow alternate parameters to be specified. pass in +# your hash reference to be passed to setUser(), and this will +# add in those extra parameters. add the parameters to string_param, +# type = otherusersparam, code = name of the param. they will +# be checked for the main user prefs editing screens, and on +# user creation -- pudge +sub getOtherUserParams { + my($data) = @_; + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + + my $user = getCurrentUser(); + my $form = getCurrentForm(); + my $params = $reader->getDescriptions('otherusersparam'); + + for my $param (keys %$params) { + if (exists $form->{$param}) { + # set user too for output in this request + $data->{$param} = $user->{$param} = $form->{$param} || undef; + } + } +} + +############################################################### +# This modifies a hashref to default values -- if nothing +# else we assume the empty string which clears items in the +# user_param table +# +# takes 3 hashrefs currently +# $data - hashref to change to defaults +# $skip - hashref of keys to skip modifying +# $defaults - hashref of defaults to set to something other +# than the empty string +sub setToDefaults { + my($data, $skip, $defaults) = @_; + foreach my $key (keys %$data) { + next if $skip->{$key}; + $data->{$key} = exists $defaults->{$key} ? $defaults->{$key} : ""; + } +} + +################################################################# +sub getCommentListing { + my ($type, $value, + $min_comment, $time_period, $cc_all, $cc_time_period, $cid_for_time_period, + $non_admin_limit, $admin_time_limit, $admin_non_time_limit, + $options) = @_; + my $reader = getObject('Slash::DB', { db_type => 'reader' }); + my $slashdb = getCurrentDB(); + my $constants = getCurrentStatic(); + my $user = getCurrentUser(); + my $store_cutoff = $options->{use_uid_cid_cutoff} ? $constants->{store_com_page1_min_cid_for_user_com_cnt} : 0; + + my $s_opt = {}; + my $num_wanted = 0; + if ($min_comment) { + if ($user->{is_admin}) { + $num_wanted = $admin_non_time_limit; + } else { + $num_wanted = $non_admin_limit; + } + } else { + + if ($user->{is_admin}) { + if ($cc_time_period >= $admin_non_time_limit) { + $s_opt->{cid_at_or_after} = $cid_for_time_period; + $num_wanted = $admin_time_limit; + } else { + $num_wanted = $admin_non_time_limit; + if($store_cutoff){ + my $min_cid = $reader->getUser($value, + "com_num_".$num_wanted."_at_or_after_cid"); + $s_opt->{cid_at_or_after} = $min_cid + if $min_cid && $min_cid =~ /^\d+$/; + } + } + } else { + if ($cc_time_period >= $non_admin_limit ) { + $s_opt->{cid_at_or_after} = $cid_for_time_period; + $num_wanted = $non_admin_limit; + } else { + $num_wanted = $non_admin_limit; + if($store_cutoff){ + my $min_cid = $reader->getUser($value, + "com_num_".$num_wanted."_at_or_after_cid"); + $s_opt->{cid_at_or_after} = $min_cid + if $min_cid && $min_cid =~ /^\d+$/; + } + } + } + } + if ($type eq "uid") { + + my $comments = $reader->getCommentsByUID($value, $num_wanted, $min_comment, $s_opt) if $cc_all; + if ($store_cutoff + && $comments && $cc_all >= $store_cutoff && $min_comment == 0 + && scalar(@$comments) == $num_wanted) { + my $min_cid = 0; + for my $comment (@$comments) { + $min_cid = $comment->{cid} + if !$min_cid || ($comment->{cid} < $min_cid); + } + if ($min_cid && $min_cid =~/^\d+$/) { + $slashdb->setUser($value, { + "com_num_".$num_wanted."_at_or_after_cid" => $min_cid + }); + } + + } + return $comments; + } elsif ($type eq "ipid"){ + return $reader->getCommentsByIPID($value, $num_wanted, $min_comment, $s_opt) if $cc_all; + } elsif ($type eq "subnetid"){ + return $reader->getCommentsBySubnetID($value, $num_wanted, $min_comment, $s_opt) if $cc_all; + } else { + return $reader->getCommentsByIPIDOrSubnetID($value, $num_wanted, $min_comment, $s_opt) if $cc_all; + } +} +createEnvironment(); +main(); + +1; Modified: slashjp/trunk/themes/slashcode/templates/dispComment;misc;default =================================================================== --- slashjp/trunk/themes/slashcode/templates/dispComment;misc;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/themes/slashcode/templates/dispComment;misc;default 2008-04-07 00:26:30 UTC (rev 572) @@ -23,7 +23,7 @@ dispComment __template__ [% IF !options.noshow_show %] -<li id="tree_[% cid %]" class="comment"> +<li id="tree_[% cid %]" class="comment[% IF class == 'full' %] contain[% END %]"> <div id="comment_status_[% cid %]" class="commentstatus"></div> <div id="comment_[% cid %]"[% IF discussion2 %] class="[% class %]"[% END %]> [% END; IF !options.noshow %] @@ -34,7 +34,7 @@ [% ELSE %] <h4><a name="[% cid %]">[% subject %]</a> [%- END %] - [% UNLESS user.noscores %]<span id="comment_score_[% cid %]" class="score">([% IF constants.modal_prefs_active && !user.is_anon %]<a href="#" onclick="getModalPrefs('modcommentlog', 'Moderation Comment Log', [% cid %]); return false">[% END %]Score:[% points.length ? points : "?" %][% IF constants.modal_prefs_active && !user.is_anon %]</a>[% END %][% IF reasons && reason %], [% reasons.$reason.name %][% END %])</span>[% END %]</h4> + [% UNLESS user.noscores %]<span id="comment_score_[% cid %]" class="score">([% IF constants.modal_prefs_active %]<a href="#" onclick="getModalPrefs('modcommentlog', 'Moderation Comment Log', [% cid %]); return false">[% END %]Score:[% points.length ? points : "?" %][% IF constants.modal_prefs_active %]</a>[% END %][% IF reasons && reason %], [% reasons.$reason.name %][% END %])</span>[% END %]</h4> </div> <div class="details"> by Modified: slashjp/trunk/themes/slashcode/templates/dispLinkComment;misc;default =================================================================== --- slashjp/trunk/themes/slashcode/templates/dispLinkComment;misc;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/themes/slashcode/templates/dispLinkComment;misc;default 2008-04-07 00:26:30 UTC (rev 572) @@ -13,38 +13,32 @@ __name__ dispLinkComment __template__ -[% IF user.mode != 'metamod' %] +[% IF user.mode != 'metamod' && user.mode != 'archive' %] [% IF user.is_admin || original_pid || !user.state.discussion_archived %] [% do_parent = ( original_pid ); #&& !(discussion2 && (!form.cid || form.cid != cid)) ); can_del = ( (constants.authors_unlimited && user.seclev >= constants.authors_unlimited) || user.acl.candelcomments_always ) %] [% IF !options.show_pieces %]<div class="commentSub" id="comment_sub_[% cid %]">[% END; IF !options.pieces %] - [ - [% IF !user.state.discussion_archived && !user.state.discussion_future_nopost %] - <span id="reply_link_[% cid %]">[% Slash.linkComment({ + <span id="reply_link_[% cid %]" class="nbutton"><p><b>[% Slash.linkComment({ sid => sid, pid => cid, op => 'Reply', subject => 'Reply to This', subject_only => 1, - onclick => ((discussion2 && (!constants.subscribe || user.is_subscriber)) ? "replyTo($cid); return false;" : '') - }) %]</span> + onclick => ((discussion2 && !user.is_anon) ? "replyTo($cid); return false;" : '') + }) %]</b></p></span> [% END %] - [% IF !(user.state.discussion_archived) && ( do_parent || can_mod || can_del ) %] | [% END %] - - [% IF do_parent %][% Slash.linkComment({ + [% IF do_parent %]<span class="nbutton"><p><b>[% Slash.linkComment({ sid => sid, cid => original_pid, pid => original_pid, subject => 'Parent', subject_only => 1, onclick => (discussion2 ? "return selectParent($original_pid)" : '') - }, 1) %][% END %] + }, 1) %]</b></p></span>[% END %] - [% IF do_parent && ( can_mod || can_del ) %] | [% END %] - [% IF can_mod %] <div id="reasondiv_[% cid %]" class="modsel">[% Slash.createSelect("reason_$cid", reasons, { 'return' => 1, @@ -52,14 +46,10 @@ onchange => (discussion2 ? 'return doModerate(this)' : '') }) %]</div>[% END %] - [% IF can_mod && can_del %] | [% END %] - [% IF can_del %] <input type="checkbox" name="del_[% cid %]"> [% END %] - ] - [% END; IF !options.show_pieces %]</div> [% END; END %] Modified: slashjp/trunk/themes/slashcode/templates/editComm;users;default =================================================================== --- slashjp/trunk/themes/slashcode/templates/editComm;users;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/themes/slashcode/templates/editComm;users;default 2008-04-07 00:26:30 UTC (rev 572) @@ -54,10 +54,7 @@ <td valign="middle"> <p><b>Discussion Style</b><p> <blockquote><div> - <input type="radio" name="discussion2" value="none"[% constants.markup_checked_attribute IF !user_edit.discussion2 || (user_edit.discussion2 && user_edit.discussion2 != "uofm" && user_edit.discussion2 != "slashdot") %]> Slashdot Classic Discussion System<br> - [% IF user.is_admin || user.acl.discussion2_uofm %] - <input type="radio" name="discussion2" value="uofm"[% constants.markup_checked_attribute IF user_edit.discussion2 == "uofm" %]> University of Michigan Testing<br> - [% END %] + <input type="radio" name="discussion2" value="none"[% constants.markup_checked_attribute IF !user_edit.discussion2 || (user_edit.discussion2 && user_edit.discussion2 != "slashdot") %]> Slashdot Classic Discussion System<br> <input type="radio" name="discussion2" value="slashdot"[% constants.markup_checked_attribute IF user_edit.discussion2 == "slashdot" %]> Slashdot Interactive Discussion System (a.k.a. D2)<br> </div></blockquote> Modified: slashjp/trunk/themes/slashcode/templates/printCommComments;misc;default =================================================================== --- slashjp/trunk/themes/slashcode/templates/printCommComments;misc;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/themes/slashcode/templates/printCommComments;misc;default 2008-04-07 00:26:30 UTC (rev 572) @@ -47,7 +47,7 @@ </script> [% END %] -[% IF cid %] +[% IF cid && !discussion2 %] <ul id="commentlisting" class="[% user.mode %]"> [% Slash.dispComment(comment) %] <div class="comment_footer"> @@ -88,21 +88,20 @@ [% END %] [% lcp %] - [% IF lvl %] - [% END %] - [% thread = Slash.displayThread(sid, pid, lvl, comments) %] + [% IF lvl; END %] + [% this_pid = discussion2 ? 0 : pid; + thread = Slash.displayThread(sid, this_pid, lvl, comments) %] [% IF thread || discussion2 %] - [% UNLESS cid %]<ul id="commentlisting">[% END %] + [% IF !cid || discussion2 %]<ul id="commentlisting"[% IF discussion2 %] class="d2"[% END %]>[% END %] [% thread || '' %] <li id="roothiddens" class="hide"></li> - [% UNLESS cid %]</ul>[% END %] + [% IF !cid || discussion2 %]</ul>[% END %] [% END %] - [% IF cid %]</ul>[% END %] - [% IF lvl %] - [% END %] + [% IF cid && !discussion2 %]</ul>[% END %] + [% IF lvl; END %] [% lcp %] -[% IF discussion2 && !cid %] +[% IF discussion2 %] <div id="replyto_0"></div> <div class="prev-next"><a href="#" onclick="ajaxFetchComments(0,1); return false"><span id="more_comments_num_a" class="hide">Check for more</span></a> [% UNLESS user.state.discussion_archived || user.state.discussion_future_nopost %] @@ -112,7 +111,7 @@ op => 'reply', subject => 'Reply', subject_only => 1, - onclick => ((discussion2 && (!constants.subscribe || user.is_subscriber)) ? "replyTo(0); return false;" : '') + onclick => ((discussion2 && !user.is_anon) ? "replyTo(0); return false;" : '') }); END %] </div> @@ -150,8 +149,7 @@ pieces_comments = [[% user.state.comments.pieces.join(',') %]]; init_hiddens = [[% user.state.comments.hiddens.join(',') %]]; - [% IF discussion2 == "slashdot" %]d2act(); - [% END -%]finishLoading(); + finishLoading(); //--> </script> [% END %] Modified: slashjp/trunk/themes/slashcode/templates/printCommentsMain;misc;default =================================================================== --- slashjp/trunk/themes/slashcode/templates/printCommentsMain;misc;default 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/themes/slashcode/templates/printCommentsMain;misc;default 2008-04-07 00:26:30 UTC (rev 572) @@ -44,7 +44,7 @@ <a name="acomments"></a> <div class="commentwrap" id="commentwrap"> -[% IF (!user.is_anon && !user.acl.discussion2_uofm) || user.is_admin %] +[% IF !user.is_anon %] <div class="commentBox" style="font-size: 120%"> <form method="GET" action="[% gSkin.rootdir %]/comments.pl"> <div> @@ -62,10 +62,6 @@ </div> </form> </div> -[% END; IF (user.acl.discussion2_uofm_signup && !user.acl.discussion2_uofm && (user.is_admin || !user.is_subscriber)) && constants.uofm_key && constants.uofm_iv && env.http_user_agent.search('Firefox/1\.5') %] -<div class="commentBox" style="font-size: 120%"> -<a href="[% Slash.tempUofmLinkGenerate() %]">Want to test an experimental interface for comments?</a> -</div> [% END %] [% UNLESS discussion2 %] @@ -183,7 +179,7 @@ }) %] [% END %] <span class="ccw-header-links"> - [% IF discussion2 && !cid && !pid %] + [% IF discussion2 %] <a href="#" onclick="ajaxFetchComments(0,1); return false"><span id="more_comments_num_b"></span> More</a> | [% END; IF user.is_admin && user.d2prefs_debug %] <a href="#" onclick="showPrefs('reading'); return false">Prefs</a> @@ -202,7 +198,7 @@ op => 'reply', subject => 'Reply', subject_only => 1, - onclick => ((discussion2 && (!constants.subscribe || user.is_subscriber)) ? "replyTo(0); return false;" : '') + onclick => ((discussion2 && !user.is_anon) ? "replyTo(0); return false;" : '') }) %] [% END %] </span> @@ -218,6 +214,7 @@ init: function() { gCommentControlWidget = new YAHOO.slashdot.ThresholdWidget([% "'X'" IF horiz %]); gCommentControlWidget.setTHT(user_threshold, user_highlightthresh); + updateTotals(); } }; }(); @@ -269,7 +266,7 @@ </div> </div> <div class="commentControlFooter"> - [% IF discussion2 && !cid && !pid %] + [% IF discussion2 %] <a href="#" onclick="ajaxFetchComments(0,1); return false"><span id="more_comments_num_c"></span> More</a> | [% END; IF user.is_admin && user.d2prefs_debug %] <a href="#" onclick="showPrefs('reading'); return false">Prefs</a> @@ -287,7 +284,7 @@ op => 'reply', subject => 'Reply', subject_only => 1, - onclick => ((discussion2 && (!constants.subscribe || user.is_subscriber)) ? "replyTo(0); return false;" : '') + onclick => ((discussion2 && !user.is_anon) ? "replyTo(0); return false;" : '') }) %] [% END %] <div id="bindings-legend">Keybindings Beta<br> @@ -296,10 +293,16 @@ <a href="#" onclick="keyHandler('','E'); return false" title="next comment by load order" >E</a><br> <a href="#" onclick="keyHandler('','A'); return false" title="previous comment in thread" >A</a> <a href="#" onclick="keyHandler('','S'); return false" title="next thread" >S</a> -<a href="#" onclick="keyHandler('','D'); return false" title="next comment in thread" >D</a> +<a href="#" onclick="keyHandler('','D'); return false" title="next comment in thread" >D</a><br> +<a href="#" onclick="keyHandler('','R'); return false" title="reply to current comment" >R</a> +<a href="#" onclick="keyHandler('','P'); return false" title="parent of current comment" >P</a> +<a href="#" onclick="keyHandler('','M'); return false" title="history of current comment" >M</a><br> +<a href="#" onclick="keyHandler('','T'); return false" title="first comment" >T</a> +<a href="#" onclick="keyHandler('','G'); return false" title="get more comments" >G</a> +<a href="#" onclick="keyHandler('','V'); return false" title="last comment" >V</a> </div> </div> - <div id="commentControlBoxStatus" class="hide"><b>Loading ... Please wait.</b></div> + <div id="commentControlBoxStatus" class="hide"><b>Loading... please wait.</b></div> </div> </div> </div> Copied: slashjp/trunk/themes/slashcode/templates/userInfo2;users;default (from rev 571, slashjp/branches/upstream/current/themes/slashcode/templates/userInfo2;users;default) =================================================================== --- slashjp/trunk/themes/slashcode/templates/userInfo2;users;default (rev 0) +++ slashjp/trunk/themes/slashcode/templates/userInfo2;users;default 2008-04-07 00:26:30 UTC (rev 572) @@ -0,0 +1,44 @@ +__section__ +default +__description__ +Display user's info + +* title = passed to titlebar +* useredit = hashref of info of the user being viewed +* points = available moderation points +* commentstruct = arrayref of comments +* nickmatch_flag = current user is same as viewed user +* mod_flag = is moderator + (no need for this anymore) +* karma_flag = boolean for display karma +* admin_block = admin stuff +* admin_flag = boolean for whether to display admin stuff + (no real need for this, could just use user.is_admin) +* fieldkey = the field key used to decide what we're looking at +* reasons = hashref from $moddb->getReasons() +* lastjournal = last journal posted +* hr_hours_back = number of hours back to show a <hr> for +* cids_to_mods = hashref keyed by cid containing arrays of moderations done to that cid +* comment_time = number of days back we are limiting the comments shown to. If 0 or undefined we're showing comments sequentially w/o time limits + +__title__ + +__page__ +users +__lang__ +en_US +__name__ +userInfo2 +__template__ +[% orig_title = title %] + +<div id="slashboxes"> +[% PROCESS userboxes2 %] +</div> + +[% title = orig_title %] + +__seclev__ +500 +__version__ +$Id: userInfo2;users;default,v 1.1 2008/04/02 14:42:24 entweichen Exp $ Copied: slashjp/trunk/themes/slashcode/templates/userboxes2;misc;default (from rev 571, slashjp/branches/upstream/current/themes/slashcode/templates/userboxes2;misc;default) =================================================================== --- slashjp/trunk/themes/slashcode/templates/userboxes2;misc;default (rev 0) +++ slashjp/trunk/themes/slashcode/templates/userboxes2;misc;default 2008-04-07 00:26:30 UTC (rev 572) @@ -0,0 +1,147 @@ +__section__ +default +__description__ +Displays the three user boxes (fancybox's). + +* useredit = user being viewed ("edit" is for historical reasons) + (if not given, the standard "user" will be used) + +__title__ + +__page__ +misc +__lang__ +en_US +__name__ +userboxes2 +__template__ +[% + +IF !useredit; useredit = user; END; + +IF !useredit.is_anon; + + # First box: general user info + + title = 'User Bio'; + contents = BLOCK; + '<a href="'; gSkin.rootdir; '/~'; + useredit.nickname | strip_paramattr; '/">'; + useredit.nickname | strip_literal; '</a> '; + PROCESS zoo_icons person=useredit.uid implied=""; + IF user.uid == useredit.uid OR user.is_admin; + # Looking at ourselves; show our real name and email info. + '<br>'; IF useredit.realname; useredit.realname | strip_literal; ELSE; '(no real name given)'; END; + '<br><a href="mailto:'; + useredit.realemail | strip_paramattr; '">'; + Slash.ellipsify(Slash.strip_literal(useredit.realemail)); '</a>'; + '<br> '; + IF useredit.fakeemail; + IF useredit.fakeemail == useredit.realemail; + '(shown without obfuscation)'; + ELSE; + 'shown as <a href="mailto:'; + useredit.fakeemail | strip_paramattr; '">'; + Slash.ellipsify(Slash.strip_literal(useredit.fakeemail)); '</a>'; + END; + ELSE; + '(email not shown publicly)'; + END; + ELSE; + # Looking at someone else; show fake email info. + '<br> '; + IF useredit.fakeemail; + '<a href="mailto:'; + useredit.fakeemail | strip_paramattr; '">'; + Slash.ellipsify(Slash.strip_literal(useredit.fakeemail)); '</a>'; + ELSE; + '(email not shown publicly)'; + END; + END; + + IF useredit.homepage; + '<br><a href="'; + useredit.homepage | strip_attribute; + '"'; + IF useredit.karma <= constants.goodkarma; + ' rel="nofollow"'; + END; + '>'; + Slash.ellipsify(Slash.strip_literal(useredit.homepage)); '</a>'; + END; + + IF user.uid == useredit.uid OR user.is_admin; + '<br>Karma: '; + PROCESS karma karma=useredit.karma admin_flag=user.is_admin; + END; + + IF useredit.aim && !useredit.aimdisplay; + '<br><b>AOL IM:</b> '; + useredit.aim | strip_literal; + ' (<b><a href="aim:addbuddy?screenname='; + useredit.aim | strip_attribute; + '">Add Buddy</a>, '; + '<a href="aim:goim?screenname='; + useredit.aim | strip_attribute; + '&message=Greetings!">Send Message</a></b>)'; + END; + + IF useredit.yahoo; + '<br><b>Yahoo! ID:</b> '; + '<a href="http://profiles.yahoo.com/'; + useredit.yahoo | strip_attribute; + '">'; + useredit.yahoo | strip_literal; + '</a> (<b><a href="http://edit.yahoo.com/config/set_buddygrp?'; + '.src=&.cmd=a&.bg=Friends&.bdl='; + useredit.yahoo | strip_attribute; + '">Add User</a>, '; + '<a href="http://edit.yahoo.com/config/send_webmesg?.target='; + useredit.yahoo | strip_attribute; + '">Send Message</a></b>)'; + END; + + IF useredit.jabber; + '<br><b>Jabber:</b> '; + useredit.jabber | strip_literal; + END; + + IF useredit.calendar_url; + '<br><b>Public Calendar:</b> '; + '<a href="webcal://'; + useredit.calendar_url | strip_attribute; + '">Subscribe</a>, <a href="http://'; + useredit.calendar_url | strip_attribute; + '">Download</a>'; + END; + + IF useredit.bio; + '<br><hr>'; + Slash.parseDomainTags(useredit.bio); + END; + + END; + Slash.sidebox(title, contents, "user-info", 1); + + # Latest comments box + contents = ''; + FOREACH cid = latest_comments.keys.sort; + contents = contents _ '<a href="' _ constants.absolutedir_secure _ '/comments.pl?sid=' _ latest_comments.$cid.sid _ '&cid=' _ cid _'">' _ latest_comments.$cid.subject _ '</a><br>'; + END; + title = 'Latest Comments'; + Slash.sidebox(title, contents, "user-info", 1); + + # Latest journals box + contents = ''; + FOREACH jid = latest_journals.keys.sort; + contents = contents _ '<a href="' _ constants.absolutedir_secure _ '/~' _ useredit.nickname _ '/journal/' _ latest_journals.$jid.id _ '">' _ latest_journals.$jid.desc _ '</a><br>'; + END; + title = 'Latest Journal Entries'; + Slash.sidebox(title, contents, "user-info", 1); + +END %] + +__seclev__ +1000 +__version__ +$Id: userboxes2;misc;default,v 1.1 2008/04/02 14:41:12 entweichen Exp $ Modified: slashjp/trunk/utils/createTestTags =================================================================== --- slashjp/trunk/utils/createTestTags 2008-04-06 23:20:44 UTC (rev 571) +++ slashjp/trunk/utils/createTestTags 2008-04-07 00:26:30 UTC (rev 572) @@ -63,7 +63,7 @@ my $stories = $slashdb->getStoriesEssentials({ future_secs => 0, limit => 10, limit_extra => 0, sectioncollapse => (rand(1) < 0.3) ? 1 : 0 }); - + my $priv_tagnames = $tagsdb->getPrivateTagnames(); my $firehose = getObject('Slash::FireHose'); my $minfhcolor = (rand(1) < 0.4 ? 5 : 7); my($firehose_items, $firehose_results) = @@ -88,11 +88,14 @@ $uid = _get_uid($tagsdb, $stoid); $tagnameid = _get_tagnameid($tagsdb, $tagnameid, $stoid, $uid); + my $tagname = $tagsdb->getTagnameDataFromId($tagnameid)->{tagname}; + my $is_private = $priv_tagnames->{$tagname}; my $tagid = $tagsdb->createTag({ uid => $uid, tagnameid => $tagnameid, table => 'stories', id => $stoid, + private => $is_private, }); print "tagid=$tagid tagnameid=$tagnameid uid=$uid stoid=$stoid\n"; } else { @@ -103,10 +106,13 @@ "karma BETWEEN $kmin AND $kmax", 'ORDER BY RAND() LIMIT 1') if !$uid || rand(1) > 0.2; $tagnameid = _get_tagnameid($tagsdb, $tagnameid, $globjid, $uid); + my $tagname = $tagsdb->getTagnameDataFromId($tagnameid)->{tagname}; + my $is_private = $priv_tagnames->{$tagname}; my $tagid = $tagsdb->createTag({ uid => $uid, tagnameid => $tagnameid, globjid => $globjid, + private => $is_private, }); print "tagid=$tagid tagnameid=$tagnameid uid=$uid globjid=$globjid\n"; }