Some work on Userdetails, minor changes/fixes in Core, Conversations and Bignumbers..
[onis.git] / lib / Onis / Plugins / Core.pm
1 package Onis::Plugins::Core;
2
3 use strict;
4 use warnings;
5
6 use Carp (qw(confess));
7 use Exporter;
8
9 =head1 NAME
10
11 Onis::Plugins::Core
12
13 =head1 DESCRIPTION
14
15 Plugin for the main table and the hourly-statistics. This is the most
16 complicated plugin so far.
17
18 =cut
19
20 use Onis::Config qw/get_config/;
21 use Onis::Html qw/html_escape get_filehandle/;
22 use Onis::Language qw/translate/;
23 use Onis::Users (qw(get_realname get_link get_image ident_to_name));
24 use Onis::Data::Core qw#get_all_nicks nick_to_ident ident_to_nick get_main_nick register_plugin#;
25 use Onis::Data::Persistent;
26
27 @Onis::Plugins::Core::EXPORT_OK = (qw(get_core_nick_counters get_sorted_nicklist));
28 @Onis::Plugins::Core::ISA = ('Exporter');
29
30 our $NickLinesCounter = Onis::Data::Persistent->new ('NickLinesCounter', 'nick',
31         qw(
32                 lines00 lines01 lines02 lines03 lines04 lines05 lines06 lines07 lines08 lines09 lines10 lines11
33                 lines12 lines13 lines14 lines15 lines16 lines17 lines18 lines19 lines20 lines21 lines22 lines23
34         )
35 );
36 our $NickWordsCounter = Onis::Data::Persistent->new ('NickWordsCounter', 'nick',
37         qw(
38                 words00 words01 words02 words03 words04 words05 words06 words07 words08 words09 words10 words11
39                 words12 words13 words14 words15 words16 words17 words18 words19 words20 words21 words22 words23
40         )
41 );
42 our $NickCharsCounter = Onis::Data::Persistent->new ('NickCharsCounter', 'nick',
43         qw(
44                 chars00 chars01 chars02 chars03 chars04 chars05 chars06 chars07 chars08 chars09 chars10 chars11
45                 chars12 chars13 chars14 chars15 chars16 chars17 chars18 chars19 chars20 chars21 chars22 chars23
46         )
47 );
48
49 our $QuoteCache = {}; # Saves per-nick information without any modification
50 our $QuoteData = {};  # Is generated before output. Nicks are merged according to Data::Core.
51 our $NickData = {};  # Same as above, but for nicks rather than quotes.
52 our $SortedNicklist = [];
53
54 our @H_IMAGES = qw#dark-theme/h-red.png dark-theme/h-blue.png dark-theme/h-yellow.png dark-theme/h-green.png#;
55 our $QuoteCacheSize = 10;
56 our $QuoteMin = 30;
57 our $QuoteMax = 80;
58 our $WORD_LENGTH = 5;
59 our $SORT_BY = 'LINES';
60 our $DISPLAY_LINES = 'BOTH';
61 our $DISPLAY_WORDS = 'NONE';
62 our $DISPLAY_CHARS = 'NONE';
63 our $DISPLAY_TIMES = 0;
64 our $DISPLAY_IMAGES = 0;
65 our $DEFAULT_IMAGE = '';
66 our $BAR_HEIGHT = 130;
67 our $BAR_WIDTH  = 100;
68 our $LongLines  = 50;
69 our $ShortLines = 10;
70
71 if (get_config ('quote_cache_size'))
72 {
73         my $tmp = get_config ('quote_cache_size');
74         $tmp =~ s/\D//g;
75         $QuoteCacheSize = $tmp if ($tmp);
76 }
77 if (get_config ('quote_min'))
78 {
79         my $tmp = get_config ('quote_min');
80         $tmp =~ s/\D//g;
81         $QuoteMin = $tmp if ($tmp);
82 }
83 if (get_config ('quote_max'))
84 {
85         my $tmp = get_config ('quote_max');
86         $tmp =~ s/\D//g;
87         $QuoteMax = $tmp if ($tmp);
88 }
89 if (get_config ('min_word_length'))
90 {
91         my $tmp = get_config ('min_word_length');
92         $tmp =~ s/\D//g;
93         $WORD_LENGTH = $tmp if ($tmp);
94 }
95 if (get_config ('display_lines'))
96 {
97         my $tmp = get_config ('display_lines');
98         $tmp = uc ($tmp);
99
100         if (($tmp eq 'NONE') or ($tmp eq 'BAR') or ($tmp eq 'NUMBER') or ($tmp eq 'BOTH'))
101         {
102                 $DISPLAY_LINES = $tmp;
103         }
104         else
105         {
106                 $tmp = get_config ('display_lines');
107                 print STDERR $/, __FILE__, ": ``display_lines'' has been set to the invalid value ``$tmp''. ",
108                 $/, __FILE__, ": Valid values are ``none'', ``bar'', ``number'' and ``both''. Using default value ``both''.";
109         }
110 }
111 if (get_config ('display_words'))
112 {
113         my $tmp = get_config ('display_words');
114         $tmp = uc ($tmp);
115
116         if (($tmp eq 'NONE') or ($tmp eq 'BAR') or ($tmp eq 'NUMBER') or ($tmp eq 'BOTH'))
117         {
118                 $DISPLAY_WORDS = $tmp;
119         }
120         else
121         {
122                 $tmp = get_config ('display_words');
123                 print STDERR $/, __FILE__, ": ``display_words'' has been set to the invalid value ``$tmp''. ",
124                 $/, __FILE__, ": Valid values are ``none'', ``bar'', ``number'' and ``both''. Using default value ``none''.";
125         }
126 }
127 if (get_config ('display_chars'))
128 {
129         my $tmp = get_config ('display_chars');
130         $tmp = uc ($tmp);
131
132         if (($tmp eq 'NONE') or ($tmp eq 'BAR') or ($tmp eq 'NUMBER') or ($tmp eq 'BOTH'))
133         {
134                 $DISPLAY_CHARS = $tmp;
135         }
136         else
137         {
138                 $tmp = get_config ('display_chars');
139                 print STDERR $/, __FILE__, ": ``display_chars'' has been set to the invalid value ``$tmp''. ",
140                 $/, __FILE__, ": Valid values are ``none'', ``bar'', ``number'' and ``both''. Using default value ``none''.";
141         }
142 }
143 if (get_config ('display_times'))
144 {
145         my $tmp = get_config ('display_times');
146
147         if ($tmp =~ m/true|on|yes/i)
148         {
149                 $DISPLAY_TIMES = 1;
150         }
151         elsif ($tmp =~ m/false|off|no/i)
152         {
153                 $DISPLAY_TIMES = 0;
154         }
155         else
156         {
157                 print STDERR $/, __FILE__, ": ``display_times'' has been set to the invalid value ``$tmp''. ",
158                 $/, __FILE__, ": Valid values are ``true'' and ``false''. Using default value ``false''.";
159         }
160 }
161 if (get_config ('display_images'))
162 {
163         my $tmp = get_config ('display_images');
164
165         if ($tmp =~ m/true|on|yes/i)
166         {
167                 $DISPLAY_IMAGES = 1;
168         }
169         elsif ($tmp =~ m/false|off|no/i)
170         {
171                 $DISPLAY_IMAGES = 0;
172         }
173         else
174         {
175                 print STDERR $/, __FILE__, ": ``display_times'' has been set to the invalid value ``$tmp''. ",
176                 $/, __FILE__, ": Valid values are ``true'' and ``false''. Using default value ``false''.";
177         }
178 }
179 if (get_config ('default_image'))
180 {
181         $DEFAULT_IMAGE = get_config ('default_image');
182 }
183 if (get_config ('sort_by'))
184 {
185         my $tmp = get_config ('sort_by');
186         $tmp = uc ($tmp);
187
188         if (($tmp eq 'LINES') or ($tmp eq 'WORDS') or ($tmp eq 'CHARS'))
189         {
190                 $SORT_BY = $tmp;
191         }
192         else
193         {
194                 $tmp = get_config ('sort_by');
195                 print STDERR $/, __FILE__, ": ``sort_by'' has been set to the invalid value ``$tmp''. ",
196                 $/, __FILE__, ": Valid values are ``lines'' and ``words''. Using default value ``lines''.";
197         }
198 }
199 if (get_config ('horizontal_images'))
200 {
201         my @tmp = get_config ('horizontal_images');
202         my $i;
203         
204         if (scalar (@tmp) != 4)
205         {
206                 print STDERR $/, __FILE__, ": The number of horizontal images is not four. The output might look weird.", $/;
207         }
208
209         for ($i = 0; $i < 4; $i++)
210         {
211                 if (!defined ($tmp[$i]))
212                 {
213                         next;
214                 }
215
216                 $H_IMAGES[$i] = $tmp[$i];
217         }
218 }
219 if (get_config ('bar_height'))
220 {
221         my $tmp = get_config ('bar_height');
222         $tmp =~ s/\D//g;
223         $BAR_HEIGHT = $tmp if ($tmp >= 10);
224 }
225 if (get_config ('bar_width'))
226 {
227         my $tmp = get_config ('bar_width');
228         $tmp =~ s/\D//g;
229         $BAR_WIDTH = $tmp if ($tmp >= 10);
230 }
231 if (get_config ('longlines'))
232 {
233         my $tmp = get_config ('longlines');
234         $tmp =~ s/\D//g;
235         $LongLines = $tmp if ($tmp);
236 }
237 if (get_config ('shortlines'))
238 {
239         my $tmp = get_config ('shortlines');
240         $tmp =~ s/\D//g;
241         if ($tmp or ($tmp == 0))
242         {
243                 $ShortLines = $tmp;
244         }
245 }
246
247 register_plugin ('TEXT', \&add);
248 register_plugin ('ACTION', \&add);
249 register_plugin ('OUTPUT', \&output);
250
251 my $VERSION = '$Id$';
252 print STDERR $/, __FILE__, ": $VERSION" if ($::DEBUG);
253
254 return (1);
255
256 sub add
257 {
258         my $data = shift;
259
260         my $nick = $data->{'nick'};
261         my $ident = $data->{'ident'};
262         my $hour = int ($data->{'hour'});
263         my $host = $data->{'host'};
264         my $text = $data->{'text'};
265         my $type = $data->{'type'};
266         my $time = $data->{'epoch'};
267
268         my $words = scalar (@{$data->{'words'}});
269         my $chars = length ($text);
270
271         if ($type eq 'ACTION')
272         {
273                 $chars -= (length ($nick) + 3);
274         }
275
276         my @counter = $NickLinesCounter->get ($nick);
277         if (!@counter)
278         {
279                 @counter = qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0);
280         }
281         $counter[$hour]++;
282         $NickLinesCounter->put ($nick, @counter);
283
284         @counter = $NickWordsCounter->get ($nick);
285         if (!@counter)
286         {
287                 @counter = qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0);
288         }
289         $counter[$hour] += $words;
290         $NickWordsCounter->put ($nick, @counter);
291
292         @counter = $NickCharsCounter->get ($nick);
293         if (!@counter)
294         {
295                 @counter = qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0);
296         }
297         $counter[$hour] += $chars;
298         $NickCharsCounter->put ($nick, @counter);
299
300         if ((length ($text) >= $QuoteMin)
301                                 and (length ($text) <= $QuoteMax))
302         {
303                 if (!defined ($QuoteCache->{$nick}))
304                 {
305                         $QuoteCache->{$nick} = [];
306                 }
307                 push (@{$QuoteCache->{$nick}}, [$time, $text]);
308         }
309
310         if (defined ($QuoteCache->{$nick}))
311         {
312                 while (scalar (@{$QuoteCache->{$nick}}) > $QuoteCacheSize)
313                 {
314                         shift (@{$QuoteCache->{$nick}});
315                 }
316         }
317
318         return (1);
319 }
320
321 sub calculate
322 {
323         for (get_all_nicks ())
324         {
325                 my $nick = $_;
326                 my $main = get_main_nick ($nick);
327
328                 if (!defined ($NickData->{$main}))
329                 {
330                         $NickData->{$main} =
331                         {
332                                 lines => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
333                                 words => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
334                                 chars => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
335                                 lines_total => 0,
336                                 words_total => 0,
337                                 chars_total => 0
338                         };
339                 }
340
341                 my @counter = $NickLinesCounter->get ($nick);
342                 if (@counter)
343                 {
344                         my $sum = 0;
345                         for (my $i = 0; $i < 24; $i++)
346                         {
347                                 $NickData->{$main}{'lines'}[$i] += $counter[$i];
348                                 $sum += $counter[$i];
349                         }
350                         $NickData->{$main}{'lines_total'} = $sum;
351                 }
352
353                 @counter = $NickWordsCounter->get ($nick);
354                 if (@counter)
355                 {
356                         my $sum = 0;
357                         for (my $i = 0; $i < 24; $i++)
358                         {
359                                 $NickData->{$main}{'words'}[$i] += $counter[$i];
360                                 $sum += $counter[$i];
361                         }
362                         $NickData->{$main}{'words_total'} = $sum;
363                 }
364
365                 @counter = $NickWordsCounter->get ($nick);
366                 if (@counter)
367                 {
368                         my $sum = 0;
369                         for (my $i = 0; $i < 24; $i++)
370                         {
371                                 $NickData->{$main}{'words'}[$i] += $counter[$i];
372                                 $sum += $counter[$i];
373                         }
374                         $NickData->{$main}{'chars_total'} = $sum;
375                 }
376
377                 if (!defined ($QuoteData->{$main}))
378                 {
379                         $QuoteData->{$main} = [];
380                 }
381                 if (defined ($QuoteCache->{$nick}))
382                 {
383                         my @new = sort (sub { $b->[0] <=> $a->[0] }, @{$QuoteCache->{$nick}}, @{$QuoteData->{$main}});
384                         splice (@new, $QuoteCacheSize) if (scalar (@new) > $QuoteCacheSize);
385                         $QuoteData->{$main} = \@new;
386                 }
387         }
388 }
389
390 sub output
391 {
392         calculate ();
393         activetimes ();
394         ranking ();
395 }
396         
397 sub activetimes
398 {
399         my $max = 0;            # the most lines that were written in one hour..
400         my $total = 0;          # the total amount of lines we wrote..
401         my $factor = 0;         # used to find a bar's height
402
403         my @data = qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0);
404
405         my @img_urls = get_config ('vertical_images');
406         if (!@img_urls)
407         {
408                 @img_urls = qw#images/ver0n.png images/ver1n.png images/ver2n.png images/ver3n.png#;
409         }
410
411         my $fh = get_filehandle () or die;
412         
413 # this for loop looks for the most amount of lines in one hour and sets
414 # $most_lines
415         for (keys %$NickData)
416         {
417                 my $nick = shift;
418
419                 for (my $i = 0; $i < 24; $i++)
420                 {
421                         $data[$i] += $NickData->{$nick}{'chars'}[$i];
422                 }
423         }
424
425         for (my $i = 0; $i < 24; $i++)
426         {
427                 $max = $data[$i] if ($max < $data[$i]);
428                 $total += $data[$i];
429         }
430
431         if (!$total)
432         {
433                 $total = 1;
434                 $max = 1;
435         }
436
437         $factor = (($BAR_HEIGHT - 1) / $max);
438
439         my $header = translate ('When do we actually talk here?');
440         print $fh "<h2>$header</h2>\n",
441         qq#<table class="hours_of_day">\n#,
442         qq#  <tr>\n#;
443
444 # this for circles through the four colors. Each color represents six hours.
445 # (4 * 6 hours = 24 hours)
446         for (my $i = 0; $i <= 3; $i++)
447         {
448                 for (my $j = 0; $j <= 5; $j++)
449                 {
450                         my $hour = (($i * 6) + $j);
451                         if (!defined ($data[$hour]))
452                         {
453                                 $data[$hour] = 0;
454                         }
455
456                         my $percent = 100 * ($data[$hour] / $total);
457                         my $height = int ($data[$hour] * $factor) + 1;
458                         my $img_url = $img_urls[$i];
459                         
460                         print $fh '    <td>', sprintf ("%2.1f", $percent),
461                         qq#%<br /><img src="$img_url" style="height: $height#,
462                         qq#px;" alt="" /></td>\n#;
463                 }
464         }
465
466         print $fh "  </tr>\n",
467         qq#  <tr class="hour_row">\n#;
468         print $fh map { "    <td>$_</td>\n" } (0 .. 23);
469         print $fh "  </tr>\n",
470         "</table>\n\n";
471 }
472
473 sub ranking
474 {
475         my $count = 0;
476
477         my @nicks = keys (%$NickData);
478
479         return unless (@nicks);
480         
481         my $max_lines = 1;
482         my $max_words = 1;
483         my $max_chars = 1;
484         
485         my $linescount = 0;
486
487         my $fh = get_filehandle () or die;
488
489         my $sort_field = lc ($SORT_BY);
490
491         my $trans;
492
493         my $tmp;
494         ($tmp) = sort { $NickData->{$b}{'lines_total'} <=> $NickData->{$a}{'lines_total'} } (@nicks);
495         $max_lines = $NickData->{$tmp}{'lines_total'} || 0;
496         
497         ($tmp) = sort { $NickData->{$b}{'words_total'} <=> $NickData->{$a}{'words_total'} } (@nicks);
498         $max_words = $NickData->{$tmp}{'words_total'} || 0;
499         
500         ($tmp) = sort { $NickData->{$b}{'chars_total'} <=> $NickData->{$a}{'chars_total'} } (@nicks);
501         $max_chars = $NickData->{$tmp}{'chars_total'} || 0;
502         
503         $trans = translate ('Most active nicks');
504         
505         print $fh "<h2>$trans</h2>\n";
506         if ($SORT_BY eq 'LINES')
507         {
508                 $trans = translate ('Nicks sorted by numbers of lines written');
509         }
510         elsif ($SORT_BY eq 'WORDS')
511         {
512                 $trans = translate ('Nicks sorted by numbers of words written');
513         }
514         else # ($SORT_BY eq 'CHARS')
515         {
516                 $trans = translate ('Nicks sorted by numbers of characters written');
517         }
518         print $fh "<p>($trans)</p>\n";
519
520         print $fh <<EOF;
521
522 <table class="big_ranking">
523   <tr>
524     <td class="invis">&nbsp;</td>
525 EOF
526         if ($DISPLAY_IMAGES)
527         {
528                 $trans = translate ('Image');
529                 print $fh "    <th>$trans</th>\n";
530         }
531         #if (true)
532         {
533                 $trans = translate ('Nick');
534                 print $fh "    <th>$trans</th>\n";
535         }
536         if ($DISPLAY_LINES ne 'NONE')
537         {
538                 $trans = translate ('Number of Lines');
539                 print $fh "    <th>$trans</th>\n";
540         }
541         if ($DISPLAY_WORDS ne 'NONE')
542         {
543                 $trans = translate ('Number of Words');
544                 print $fh "    <th>$trans</th>\n";
545         }
546         if ($DISPLAY_CHARS ne 'NONE')
547         {
548                 $trans = translate ('Number of Characters');
549                 print $fh "    <th>$trans</th>\n";
550         }
551         if ($DISPLAY_TIMES)
552         {
553                 $trans = translate ('When?');
554                 print $fh "    <th>$trans</th>\n";
555         }
556         
557         $trans = translate ('Random Quote');
558         print $fh "    <th>$trans</th>\n",
559         "  </tr>\n";
560
561         @$SortedNicklist = sort
562         {
563                 $NickData->{$b}{"${sort_field}_total"} <=> $NickData->{$a}{"${sort_field}_total"}
564         } (@nicks);
565
566         @nicks = ();
567
568         for (@$SortedNicklist)
569         {
570                 my $nick = $_;
571                 my $ident = nick_to_ident ($nick);
572                 my $name  = ident_to_name ($ident);
573
574                 $linescount++;
575
576                 # As long as we didn't hit the 
577                 # $LongLines-limit we continue
578                 # our table..
579                 if ($linescount <= $LongLines)
580                 {
581                         my $quote = translate ('-- no quote available --');
582
583                         if (defined ($QuoteData->{$nick}))
584                         {
585                                 my $num = scalar (@{$QuoteData->{$nick}});
586                                 my $rand = int (rand ($num));
587                                 $quote = html_escape ($QuoteData->{$nick}[$rand]);
588                         }
589
590                         my $link = '';
591                         my $image = '';
592                         my $realname = '';
593                         if ($name)
594                         {
595                                 $link     = get_link ($name);
596                                 $image    = get_image ($name);
597                                 $realname = get_realname ($name);
598                         }
599                         
600                         print $fh "  <tr>\n",
601                         qq#    <td class="numeration"># . $linescount . "</td>\n";
602
603                         if ($DISPLAY_IMAGES)
604                         {
605                                 if ($DEFAULT_IMAGE and !$image)
606                                 {
607                                         $image = $DEFAULT_IMAGE;
608                                 }
609                                 
610                                 print $fh qq#    <td class="image">#;
611                                 if ($image)
612                                 {
613                                         if ($link)
614                                         {
615                                                 print $fh qq#<a href="$link">#;
616                                         }
617                                         print $fh qq#<img src="$image" alt="$name" />#;
618                                         if ($link)
619                                         {
620                                                 print $fh "</a>";
621                                         }
622                                 }
623                                 else
624                                 {
625                                         print $fh '&nbsp;';
626                                 }
627                                 print $fh "</td>\n";
628                         }
629                         
630                         my $title = $realname;
631                         if (!$title)
632                         {
633                                 $title = "User: $name; " if ($name);
634                                 $title .= "Ident: $ident";
635                         }
636                         print $fh qq#    <td class="nick" title="$title">#;
637
638                         if ($link)
639                         {
640                                 print $fh qq#<a href="$link">$name</a></td>\n#
641                         }
642                         else
643                         {
644                                 print $fh qq#$name</td>\n#;
645                         }
646                 
647                         if ($DISPLAY_LINES ne 'NONE')
648                         {
649                                 print $fh qq#    <td class="bar">#;
650                                 if (($DISPLAY_LINES eq 'BOTH') or ($DISPLAY_LINES eq 'BAR'))
651                                 {
652                                         my $code = bar ($max_lines, $NickData->{$nick}{'lines'});
653                                         print $fh $code;
654                                 }
655                                 print $fh '&nbsp;' if ($DISPLAY_LINES eq 'BOTH');
656                                 if (($DISPLAY_LINES eq 'BOTH') or ($DISPLAY_LINES eq 'NUMBER'))
657                                 {
658                                         print $fh $NickData->{$nick}{'lines_total'};
659                                 }
660                                 print $fh "</td>\n";
661                         }
662
663                         if ($DISPLAY_WORDS ne 'NONE')
664                         {
665                                 print $fh qq#    <td class="bar">#;
666                                 if (($DISPLAY_WORDS eq 'BOTH') or ($DISPLAY_WORDS eq 'BAR'))
667                                 {
668                                         my $code = bar ($max_words, $NickData->{$nick}{'words'});
669                                         print $fh $code;
670                                 }
671                                 print $fh '&nbsp;' if ($DISPLAY_WORDS eq 'BOTH');
672                                 if (($DISPLAY_WORDS eq 'BOTH') or ($DISPLAY_WORDS eq 'NUMBER'))
673                                 {
674                                         print $fh $NickData->{$nick}{'words_total'};
675                                 }
676                                 print $fh "</td>\n";
677                         }
678
679                         if ($DISPLAY_CHARS ne 'NONE')
680                         {
681                                 print $fh qq#    <td class="bar">#;
682                                 if (($DISPLAY_CHARS eq 'BOTH') or ($DISPLAY_CHARS eq 'BAR'))
683                                 {
684                                         my $code = bar ($max_chars, $NickData->{$nick}{'chars'});
685                                         print $fh $code;
686                                 }
687                                 print $fh '&nbsp;' if ($DISPLAY_CHARS eq 'BOTH');
688                                 if (($DISPLAY_CHARS eq 'BOTH') or ($DISPLAY_CHARS eq 'NUMBER'))
689                                 {
690                                         print $fh $NickData->{$nick}{'chars_total'};
691                                 }
692                                 print $fh "</td>\n";
693                         }
694
695                         if ($DISPLAY_TIMES)
696                         {
697                                 my $code = bar ($NickData->{$nick}{'chars_total'}, $NickData->{$nick}{'chars'});
698                                 print $fh qq#    <td class="bar">$code</td>\n#;
699                         }
700
701                         print $fh qq#    <td class="quote">$quote</td>\n#,
702                         qq#  </tr>\n#;
703                         
704                         if ($linescount == $LongLines)
705                         {
706                                 print $fh "</table>\n\n";
707                         }
708                 }
709
710                 # Ok, we have too many people to
711                 # list them all so we start a
712                 # smaller table and just list the
713                 # names.. (Six names per line..)
714                 elsif ($linescount <= ($LongLines + 6 * $ShortLines))
715                 {
716                         my $row_in_this_table = int (($linescount - $LongLines - 1) / 6);
717                         my $col_in_this_table = ($linescount - $LongLines - 1) % 6;
718
719                         my $total = 0;
720                         if ($SORT_BY eq 'LINES')
721                         {
722                                 $total = $NickData->{$nick}{'lines_total'};
723                         }
724                         elsif ($SORT_BY eq 'WORDS')
725                         {
726                                 $total = $NickData->{$nick}{'words_total'};
727                         }
728                         else # ($SORT_BY eq 'CHARS')
729                         {
730                                 $total = $NickData->{$nick}{'chars_total'};
731                         }
732
733                         my $title = $name ? get_realname ($name) : '';
734                         if (!$title)
735                         {
736                                 $title = "User: $name; " if ($name);
737                                 $title .= "Ident: $ident";
738                         }
739                         
740                         if ($row_in_this_table == 0 and $col_in_this_table == 0)
741                         {
742                                 $trans = translate ("They didn't write so much");
743                                 print $fh "<h2>$trans</h2>\n",
744                                 qq#<table class="small_ranking">\n#,
745                                 qq#  <tr>\n#;
746                         }
747                         
748                         if ($col_in_this_table == 0 and $row_in_this_table != 0)
749                         {
750                                 print $fh "  </tr>\n",
751                                 qq#  <tr>\n#;
752                         }
753                         
754                         print $fh qq#    <td title="$title">$name ($total)</td>\n#;
755                         
756                         if ($row_in_this_table == $ShortLines and $col_in_this_table == 5)
757                         {
758                                 print $fh "  </tr>\n",
759                                 qq#</table>\n\n#;
760                         }
761                 }
762
763                 # There is no else. There are
764                 # just too many people around.
765                 # I might add a "There are xyz
766                 # unmentioned nicks"-line..
767         }
768
769         if (($linescount > $LongLines)
770                         and ($linescount <= ($LongLines + 6 * $ShortLines)))
771         {
772                 my $col = ($linescount - $LongLines - 1) % 6;
773
774                 while ($col < 5)
775                 {
776                         print $fh qq#    <td>&nbsp;</td>\n#;
777                         $col++;
778                 }
779
780                 print $fh "  </tr>\n";
781         }
782
783         if ($linescount != $LongLines)
784         {
785                 print $fh "</table>\n\n";
786         }
787 }
788
789 # this is called by "&ranking ();" and prints the horizontal usage-bar in the
790 # detailed nick-table
791 sub bar
792 {
793         my $max_num = shift;
794
795         my $source = shift;
796
797         confess unless (ref ($source eq 'ARRAY'));
798
799         # BAR_WIDTH is a least 10
800         my $max_width = $BAR_WIDTH - 4;
801         my $factor = 1;
802         my $retval = '';
803
804         my $i;
805         my $j;
806
807         if (!$max_num) { return ($retval); }
808         $factor = $max_width / $max_num;
809
810         for ($i = 0; $i < 4; $i++)
811         {
812                 my $sum = 0;
813                 my $width = 1;
814                 my $img = $H_IMAGES[$i];
815
816                 for ($j = 0; $j < 6; $j++)
817                 {
818                         my $hour = ($i * 6) + $j;
819                         $sum += $source->[$hour];
820                 }
821
822                 $width += int (0.5 + ($sum * $factor));
823                 
824                 $retval .= qq#<img src="$img" style="width: # . $width . q#px"#;
825                 if ($i == 0) { $retval .= qq# class="first"#; }
826                 elsif ($i == 3) { $retval .= qq# class="last"#; }
827                 $retval .= ' alt="" />';
828         }
829
830         return ($retval);
831 }
832
833 =head1 EXPORTED FUNCTIONS
834
835 =over 4
836
837 =item B<get_core_nick_counters> (I<$nick>)
838
839 Returns a hash-ref that containes all the nick-counters available. It looks
840 like this:
841
842     {
843         lines => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
844         words => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
845         chars => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
846         lines_total => 0,
847         words_total => 0,
848         chars_total => 0
849     }
850
851 =cut
852
853 sub get_core_nick_counters
854 {
855         my $nick = shift;
856
857         if (!defined ($NickData->{$nick}))
858         {
859                 return ({});
860         }
861
862         return ($NickData->{$nick});
863 }
864
865 =item B<get_sorted_nicklist> ()
866
867 Returns an array-ref that containes all nicks, sorted by the field given in the
868 config-file.
869
870 =cut
871
872 sub get_sorted_nicklist
873 {
874         return ($SortedNicklist);
875 }
876
877 =back
878
879 =head1 AUTHOR
880
881 Florian octo Forster, E<lt>octo at verplant.orgE<gt>
882
883 =cut