Fixed bug in menu bar
[mspang/www.git] / newsgroup / lib / thread.inc.php
1 <?
2 /*
3  *  NewsPortal: Functions for handling threads
4  *
5  *  Copyright (C) 2002-2004 Florian Amrhein
6  *  E-Mail: newsportal@florian-amrhein.de
7  *  Web: http://florian-amrhein.de
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2 of the License, or
12  *  (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22  */
23
24
25 /*
26  * Shows the little menu on the thread.php where you can select the
27  * different pages with the articles on it
28  */
29 function thread_pageselect($group,$article_count,$first) {
30   global $articles_per_page,$file_thread,$file_framethread,$name;
31   global $text_thread,$thread_show;
32   $pages=ceil($article_count / $articles_per_page);
33   if ($article_count > $articles_per_page)
34     echo $text_thread["pages"];
35     for ($i = 0; $i < $pages; $i++) {
36       // echo '[';
37       if ($first != $i*$articles_per_page+1)
38         echo '<a class="np_pages_unselected" href="'.
39              $file_thread.'?group='.$group.
40              '&amp;first='.($i*$articles_per_page+1).'&amp;last='.
41              ($i+1)*$articles_per_page.'">';
42       else
43         ; //echo '<span class="np_pages_selected">';
44       // echo ($i*$articles_per_page+1).'-';
45       echo $i+1;
46       if ($i == $pages-1) {
47         // echo $article_count;
48       } else {
49         // echo ($i+1)*$articles_per_page;
50       }
51       if ($first != $i*$articles_per_page+1)
52         echo '</a>';
53       else
54         ; //echo '</span>';
55       // echo '] ';
56     }
57 }
58
59 /*
60  * Load a thread from disk
61  *
62  * $group: name of the newsgroup, is needed to create the filename
63  * 
64  * returns: an array of headerType containing the thread.
65  */
66 function thread_cache_load($group) {
67   global $spooldir,$compress_spoolfiles;
68   $filename=$spooldir."/".$group."-data.dat";
69   if (!file_exists($filename)) return false;
70   if ($compress_spoolfiles) {
71     $file=gzopen("$spooldir/$group-data.dat","r");
72     $headers=unserialize(gzread($file,1000000));
73     gzclose($file);
74   } else {
75     $file=fopen($filename,"r");
76     $headers=unserialize(fread($file,filesize($filename)));
77     fclose($file);
78   }
79   return($headers);
80 }
81
82
83 /*
84  * Save the thread to disk
85  *
86  * $header: is an array of headerType containing the thread
87  * $group: name of the newsgroup, is needed to create the filename
88  */
89 function thread_cache_save($headers,$group) {
90   global $spooldir,$compress_spoolfiles;
91   if ($compress_spoolfiles) {
92     $file=gzopen("$spooldir/$group-data.dat","w");
93     gzputs($file,serialize($headers));
94     gzclose($file);
95   } else {
96     $file=fopen("$spooldir/$group-data.dat","w");
97     if($file===false) {
98       die('The spool-directory is not writeable. Please change the user '.
99           'permissions to give the webserver write-access to it.');
100     }
101     fputs($file,serialize($headers));
102     fclose($file);
103   }
104 }
105
106 /*
107  * remove an article from the overview-file
108  * is needed, when article has been canceled, the article is still
109  * in the thread spool on disc and someone wants to read this article.
110  * the message_read function can now call this function to remove
111  * the article.
112  */
113 function thread_cache_removearticle($group,$id) {
114   $thread=thread_cache_load($group);
115   if(!$thread) return false;
116   $changed=false;
117   foreach ($thread as $value) {
118     if(($value->number==$id) || ($value->id==$id)) {
119       // found to be deleted article
120       // now lets rebuild the tree...
121       if(isset($value->answers))
122         foreach ($value->answers as $key => $answer) {
123           $thread[$answer]->isAnswer=false;
124         }
125       if(isset($value->references))
126         foreach ($value->references as $reference) {
127           if(isset($thread[$reference]->answers)) {
128             $search=array_search($value->id,$thread[$reference]->answers);
129             if(!($search===false))
130               unset($thread[$reference]->answers[$search]);
131           }
132         }
133       unset($thread[$value->id]);
134       $changed=true;
135       break;
136     }
137   }
138   if($changed) thread_cache_save($thread,$group);
139 }
140
141 /*
142 function readArticles(&$ns,$groupname,$articleList) {
143   for($i = 0; $i <= count($articleList)-1 ; $i++) {
144     $temp=read_header($ns,$articleList[$i]);
145     $articles[$temp->id] = $temp;
146   }
147   return $articles;
148 }
149 */
150
151 /*
152  * interpret and decode one line of overview-data from the newsserver and
153  * put it into an headerType
154  *
155  * $line: the data to be interpreted
156  * $overviewformat: the format of an overview-line, given by
157  *                  thread_overview_read()
158  * $groupname: the name of the newsgroup
159  *
160  * returns: headerType containing the data
161  */
162 function thread_overview_interpret($line,$overviewformat,$groupname) {
163   $return="";
164   $overviewfmt=explode("\t",$overviewformat);
165   echo " ";                // keep the connection to the webbrowser alive
166   flush();                 // while generating the message-tree
167 //  $over=split("\t",$line,count($overviewfmt)-1);
168   $over=split("\t",$line);
169   //$article=new headerType;
170   for ($i=0; $i<count($overviewfmt)-1; $i++) {
171     if ($overviewfmt[$i]=="Subject:") {
172       $subject=eregi_replace('\[doctalk\]','',headerDecode($over[$i+1]));
173       $article->isReply=splitSubject($subject);
174       $article->subject=$subject;
175     }
176     if ($overviewfmt[$i]=="Date:") {
177       $article->date=getTimestamp($over[$i+1]);
178     }
179     if ($overviewfmt[$i]=="From:") {
180       $fromline=address_decode(headerDecode($over[$i+1]),"nirgendwo");
181       $article->from=$fromline[0]["mailbox"]."@".$fromline[0]["host"];
182       $article->username=$fromline[0]["mailbox"];
183       if (!isset($fromline[0]["personal"])) {
184         $article->name=$fromline[0]["mailbox"];
185         if (strpos($article->name,'%')) {
186           $article->name=substr($article->name,0,strpos($article->name,'%'));
187         }
188         $article->name=strtr($article->name,'_',' ');
189       } else {
190         $article->name=$fromline[0]["personal"];
191       }
192     }
193     if ($overviewfmt[$i]=="Message-ID:") $article->id=$over[$i+1];
194     if (($overviewfmt[$i]=="References:") && ($over[$i+1] != "")) {
195       $article->references=explode(" ",$over[$i+1]);
196     }
197   }
198   $article->number=$over[0];
199   $article->isAnswer=false;
200   return($article);
201 }
202
203 /*
204  * read the overview-format from the newsserver. This data is used
205  * by thread_overview_interpret
206  */
207 function thread_overview_read(&$ns) {
208   $overviewfmt=array();
209   fputs($ns,"LIST overview.fmt\r\n");  // find out the format of the
210   $tmp=line_read($ns);                 // xover-command
211   if(substr($tmp,0,3)=="215") {
212     $line=line_read($ns);
213     while (strcmp($line,".") != 0) {
214       // workaround for braindead CLNews newsserver
215       if($line=="Author:")
216         $overviewfmt[]="From:";
217       else
218         $overviewfmt[]=$line;
219       $line=line_read($ns);
220     }
221   } else {
222     // some stupid newsservers, like changi, don't send their overview
223     // format
224     // let's hope, that the format is like that from INN
225     $overviewfmt=array("Subject:","From:","Date:","Message-ID:",
226                           "References:","Bytes:");
227   }
228   $overviewformat=implode("\t",$overviewfmt);
229   return $overviewformat;
230 }
231
232 function thread_mycompare($a,$b) {
233     global $thread_sort_order,$thread_sort_type;
234     if($thread_sort_type!="thread") {
235       $r=($a->date<$b->date) ? -1 : 1;
236       if ($a->date==$b->date) $r=0;
237     } else {
238       $r=($a->date_thread<$b->date_thread) ? -1 : 1;
239       if ($a->date_thread==$b->date_thread) $r=0;
240     }
241     return $r*$thread_sort_order;
242 }
243
244 /*
245  * this function loads the (missing parts of the) thread from the newsserver.
246  * it also loads the thread from the disk cache to detect which parts
247  * are missing and merges this data with the parts from the
248  * newsserver.
249  * if it detects that the newsserver made major changes in the groups,
250  * for example if it expired parts of the group or reset its counters,
251  * this function deletes the cached data and make a complete rebuild.
252  *
253  * $ns: handle of the connection to the newsserver
254  * $groupname: name of the newsgroup
255  * $poll: if set to 1, this function works in polling-mode, which
256  *        means, that it also read every article from the newsserver.
257  *        This makes only sense if the article cache is activated
258  */
259 function thread_load_newsserver(&$ns,$groupname,$poll) {
260   global $spooldir,$maxarticles,$maxfetch,$initialfetch,$maxarticles_extra;
261   global $text_error,$text_thread,$compress_spoolfiles,$server;
262   global $www_charset,$iconv_enable,$thread_show,$thread_sort_order;
263   $idstring="0.36,".$server.",".$compress_spoolfiles.",".$maxarticles.",".
264             $maxarticles_extra.",".$maxfetch.",".$initialfetch.",".
265             $www_charset.','.$iconv_enable.','.$thread_show["replies"];
266   $overviewformat=thread_overview_read($ns);
267   $spoolfilename=$spooldir."/".$groupname."-data.dat";
268   fputs($ns,"GROUP $groupname\r\n");   // select a group
269   $groupinfo=explode(" ",line_read($ns));
270   if (substr($groupinfo[0],0,1) != 2) {
271     echo "<p>".$text_error["error:"]."</p>";
272     echo "<p>".$text_thread["no_such_group"]."</p>";
273     flush();
274   } else {
275     $infofilename=$spooldir."/".$groupname."-info.txt";
276     // lets find out, in which mode wie want to read articles:
277     // w: complete rebuild of the group-info file
278     // a: add new articles to the group-info file
279     // n: there are no new articles, no rebuild or actualisation
280     $spoolopenmodus="n";
281     // if the group-info file doesn't exist: create it
282     if (!((file_exists($infofilename)) && (file_exists($spoolfilename)) &&
283           (filesize($infofilename)>0) && (filesize($spoolfilename)>0))) {
284       $spoolopenmodus="w";
285     } else {
286       $infofile=fopen($infofilename,"r");
287       $oldid=fgets($infofile,100);
288       if (trim($oldid) != $idstring) {
289         echo "<!-- Database Error, rebuilding Database...-->\n";
290         $spoolopenmodus="w";
291       }
292       $oldgroupinfo=explode(" ",trim(fgets($infofile,200)));
293       fclose($infofile);
294       if ($groupinfo[3] < $oldgroupinfo[1]) {
295         $spoolopenmodus="w";
296       }
297       if ($maxarticles == 0) {
298         if ($groupinfo[2] != $oldgroupinfo[0]) $spoolopenmodus="w";
299       } else {
300         if ($groupinfo[2] > $oldgroupinfo[0]) $spoolopenmodus="w";
301       }
302       // if the high watermark increased, add articles to the existing spool
303       if (($spoolopenmodus == "n") && ($groupinfo[3] > $oldgroupinfo[1]))
304         $spoolopenmodus="a";
305     }
306     if ($spoolopenmodus=="a") {
307       $firstarticle=$oldgroupinfo[1]+1;
308       $lastarticle=$groupinfo[3];
309     }
310     if ($spoolopenmodus=="w") {
311       $firstarticle=$groupinfo[2];
312       $lastarticle=$groupinfo[3];
313     }
314     if ($spoolopenmodus != "n") {
315       if ($maxarticles != 0) {
316         if ($spoolopenmodus == "w") {
317           $firstarticle=$lastarticle-$maxarticles+1;
318           if ($firstarticle < $groupinfo[2])
319             $firstarticle=$groupinfo[2];
320         } else {
321           if ($lastarticle-$oldgroupinfo[0]+1 > $maxarticles + $maxarticles_extra) {
322             $firstarticle=$lastarticle-$maxarticles+1;
323             $spoolopenmodus="w";
324           }
325         }
326       }
327       if (($maxfetch!=0) && (($lastarticle-$firstarticle+1) > $maxfetch)) {
328         if ($spoolopenmodus=="w") {
329           $tofetch=($initialfetch != 0) ? $initialfetch : $maxfetch;
330           $lastarticle=$firstarticle+$tofetch-1;
331         } else {
332           $lastarticle=$firstarticle+$maxfetch-1;
333         }
334       }
335     }
336     echo "<!--openmodus: ".$spoolopenmodus."-->\n";
337     // load the old spool-file, if we do not have a complete rebuild
338     if ($spoolopenmodus != "w") $headers=thread_cache_load($groupname);
339     // read articles from the newsserver
340     if ($spoolopenmodus != "n") {
341       // order the article overviews from the newsserver
342       fputs($ns,"XOVER ".$firstarticle."-".$lastarticle."\r\n");
343       $tmp=line_read($ns);
344       // have the server accepted our order?
345       if (substr($tmp,0,3) == "224") {
346         $line=line_read($ns);
347         // read overview by overview until the data ends
348         while ($line != ".") {
349           // parse the output of the server...
350           $article=thread_overview_interpret($line,$overviewformat,$groupname);
351           // ... and save it in our data structure
352           $article->threadsize++;
353           $article->date_thread=$article->date;
354           $headers[$article->id]=$article;
355           // if we are in poll-mode: print status information and
356           // decode the article itself, so it can be saved in the article
357           // cache
358           if($poll) {
359             echo $article->number.", "; flush();
360             message_read($article->number,0,$groupname);
361           }
362           // read the next line from the newsserver
363           $line=line_read($ns);
364         }
365         // write information about the last article to the spool-directory
366         $infofile=fopen($spooldir."/".urlencode($groupname)."-lastarticleinfo.dat","w");
367         $lastarticleinfo->from=$article->from;
368         $lastarticleinfo->date=$article->date;
369         $lastarticleinfo->name=$article->name;
370         fputs($infofile,serialize($lastarticleinfo));
371         fclose($infofile);
372       }
373       // remove the old spoolfile
374       if (file_exists($spoolfilename)) unlink($spoolfilename);
375       if ((isset($headers)) && (count($headers)>0)) {
376         //$infofile=fopen($infofilename,"w");
377         //if ($spoolopenmodus=="a") $firstarticle=$oldgroupinfo[0];
378         //fputs($infofile,$idstring."\n");
379         //fputs($infofile,$firstarticle." ".$lastarticle."\r\n");
380         //fclose($infofile);
381         foreach($headers as $c) {
382           if (($c->isAnswer == false) &&
383              (isset($c->references))) {   // is the article an answer to an
384                                           // other article?
385             // try to find a matching article to one of the references
386             $refmatch=false;
387             foreach ($c->references as $reference) {
388               if(isset($headers[$reference])) {
389                 $refmatch=$reference;
390               }
391             }
392             // have we found an article, to which this article is an answer?
393             if($refmatch!=false) {
394               $c->isAnswer=true;
395               $c->bestreference=$refmatch;
396               $headers[$c->id]=$c;
397               // the referenced article get the ID af this article as in
398               // its answers-array
399               $headers[$refmatch]->answers[]=$c->id;
400               // propagate down the number of articles in this thread
401               $d =& $headers[$c->bestreference];
402               do {
403                 $d->threadsize+=$c->threadsize;
404                 $d->date_thread=max($c->date,$d->date_thread);
405               } while(($headers[$d->bestreference]) && 
406                         (isset($d->bestreference)) &&
407                         ($d =& $headers[$d->bestreference]));
408             }
409           }
410         }
411         reset($headers);
412         // sort the articles
413         if (($thread_sort_order != 0) && (count($headers)>0))
414           uasort($headers,'thread_mycompare');
415         // Save the thread-informations
416         thread_cache_save($headers,$groupname);
417         // Save the info-file
418         $infofile=fopen($infofilename,"w");
419         if ($spoolopenmodus=="a") $firstarticle=$oldgroupinfo[0];
420         fputs($infofile,$idstring."\n");
421         fputs($infofile,$firstarticle." ".$lastarticle." ".count($headers)."\r\n");
422         fclose($infofile);
423       }
424       // remove cached articles that are not in this group
425       // (expired on the server or canceled)
426       $dirhandle=opendir($spooldir);
427       while ($cachefile = readdir($dirhandle)) {
428         if(substr($cachefile,0,strlen($groupname)+1)==$groupname."_") {
429           $num=eregi_replace('^(.*)_(.*)\.(.*)$','\2',$cachefile);
430           if(($num<$firstarticle) || ($num>$lastarticle))
431             unlink($spooldir.'/'.$cachefile);
432         }
433         // remove the html cache files of this group
434         if((substr($cachefile,strlen($cachefile)-5)==".html") &&
435            (substr($cachefile,0,strlen($groupname)+1)==$groupname."-"))
436           unlink($spooldir.'/'.$cachefile);
437       }
438     }
439     if(isset($headers))
440       return $headers;
441     return false;
442     //return((isset($headers)) ? $headers : false);
443   }
444 }  
445
446
447 /*
448  * Read the Overview.
449  * Format of the overview-file:
450  *    message-id
451  *    date
452  *    subject
453  *    author
454  *    email
455  *    references
456  *
457  * $groupname: name of the newsgroup
458  * $readmode: if set to 0, this function only reads data from the 
459  *            newsserver, if there exists no cached data for this group
460  * $poll: polling mode, see description at thread_load_newsserver()
461  */
462
463 function thread_load($groupname,$readmode = 1,$poll=false) {
464   global $text_error, $maxarticles, $server, $port;
465   global $spooldir,$thread_sort_order,$cache_thread;
466   if (!testGroup($groupname)) {
467     echo $text_error["read_access_denied"];
468     return;
469   }
470   // first assume that we have to query the newsserver
471   $query_ns=true;
472   // name of the file that indicates by it's timestamp when the
473   // last query of the newsserver was
474   $cachefile=$spooldir.'/'.$groupname.'-cache.txt';
475   // should we load the data only from cache if it's recent enough, or
476   // do we have to query the newsserver every time?
477   if($cache_thread>0) {
478     if((file_exists($cachefile)) && 
479        (filemtime($cachefile)+$cache_thread>time())) {
480       // cached file exists and is new enough. so lets read it out.
481       $articles=thread_cache_load($groupname);
482       return $articles;
483       $query_ns=false;
484     }
485   }
486   // do we have to query the newsserver?
487   if($query_ns) {
488     // look if there is new data on the newsserver
489     $ns=nntp_open($server,$port);
490     if ($ns == false) return false;
491     if (($ns!=false) && ($readmode > 0)) 
492       $articles=thread_load_newsserver($ns,$groupname,$poll);
493     if ((isset($articles)) && ($articles)) {
494
495       // write the file which indicates the time of the last newsserver query
496       $fp_cachefile=@fopen($cachefile,"w");
497       if($fp_cachefile!==false) {
498         fputs($fp_cachefile,"");
499         fclose($fp_cachefile);
500         return $articles;
501       }
502     } else {
503       // uh, we didn't get articles from the newsservers...
504       // for now, return false. but it would also make sense to get
505       // the articles from the cache then...
506       return false;
507     }
508     nntp_close($ns);
509   }
510 }
511
512 /*
513  * Remove re:, aw: etc. from a subject.
514  *
515  * $subject: a string containing the complete Subject
516  *
517  * The function removes the re:, aw: etc. from $subject end returns true
518  * if it removed anything, and false if not.
519  */
520 function splitSubject(&$subject) {
521   $s=eregi_replace('^(odp:|aw:|re:|re\[2\]:| )+','',$subject);
522   $return=($s != $subject);
523   $subject=$s;
524   return $return;
525 }
526
527 function str_change($str,$pos,$char) {
528   return(substr($str,0,$pos).$char.substr($str,$pos+1,strlen($str)-$pos));
529 }
530
531 /*
532  * calculate the graphic representation of the thread
533  */
534 function thread_show_calculate($newtree,$depth,$num,$liste,$c) {
535   global $thread_show;
536   // displays the replies to an article?
537   if(!$thread_show["replies"]) {
538     // no
539     if ((isset($c->answers[0])) && (count($c->answers)>0))
540       $newtree.="o";
541     else
542       $newtree.="o";
543   } else {
544     // yes, display the replies
545     if ((isset($c->answers[0])) && (count($c->answers)>0)) {
546       $newtree.="*";
547     } else {
548       if ($depth == 1) {
549         $newtree.="o";
550       } else {
551         $newtree.="-";
552       }
553     }
554     if (($num == count($liste)-1) && ($depth>1)) {
555       $newtree=str_change($newtree,$depth-2,"`");
556     }
557   }
558   return($newtree);
559 }
560
561
562 /*
563  * Format the message-tree
564  * Zeichen im Baum:
565  *  o : leerer Kasten            k1.gif
566  *  * : Kasten mit Zeichen drin  k2.gif
567  *  i : vertikale Linie          I.gif
568  *  - : horizontale Linie        s.gif
569  *  + : T-Stueck                 T.gif
570  *  ` : Winkel                   L.gif
571  */
572 function thread_show_treegraphic($newtree) {
573   global $imgdir;
574   $return="";
575   for ($o=0 ; $o<strlen($newtree) ; $o++) {
576     $return .= '<img src="'.$imgdir.'/';
577     $k=substr($newtree,$o,1);
578     $alt=$k;
579     switch ($k) {
580       case "o":
581         $return .= 'k1.gif';
582         break;
583       case "*":
584         $return .= 'k2.gif';
585         break;
586       case "i":
587         $return .= 'I.gif';
588         $alt='|';
589         break;
590       case "-":
591         $return .= 's.gif';
592         break;
593       case "+":
594         $return .= 'T.gif';
595         break;
596       case "`":
597         $return .= 'L.gif';
598         break;
599       case ".":
600         $return .= 'e.gif';
601         $alt='&nbsp;';
602         break;
603       }
604     $return .= '" alt="'.$alt.'" class="thread_image"';
605     if (strcmp($k,".") == 0) $return .=(' width="12" height="9"');
606     $return .= '>';
607   }
608   return($return);
609 }
610
611 function formatTreeText($tree) {
612   $tree=str_replace("i","|",$tree);
613   $tree=str_replace(".","&nbsp;",$tree);
614   return($tree);
615 }
616
617 /*
618  * format the subject inside the thread
619  */
620 function thread_format_subject($c,$group,$highlightids=false) {
621   global $file_article, $thread_maxSubject, $frame_article;
622   if ($c->isReply) {
623     $re="Re: ";
624   } else {
625     $re="";
626   }
627   // is the current article to be highlighted?
628   if(($highlightids) &&
629      ((in_array($c->id,$highlightids)) ||
630       (in_array($c->number,$highlightids))))
631     $highlight=true;
632   else
633     $highlight=false;
634   if($highlight)
635     $return='<b>';
636   else {
637     $return='<a ';
638     if ((isset($frame_article)) && ($frame_article != ""))
639       $return .= 'target="'.$frame_article.'" ';
640     $return .= 'href="'.$file_article.
641        '?id='.urlencode($c->number).'&group='.urlencode($group).'#'.
642        urlencode($c->number).'">';
643   }
644   $return.=$re.htmlspecialchars(substr(trim($c->subject),0,$thread_maxSubject));
645   if($highlight)
646     $return.='</b>';
647   else
648     $return.='</a>';
649   return($return);
650 }
651
652 /*
653  * colorize the date inside the thread
654  */
655 function thread_format_date_color($date) {
656   global $age_count,$age_time,$age_color;
657   $return="";
658   $currentTime=time();
659   if ($age_count > 0)
660     for($t = $age_count; $t >= 1; $t--) {
661       if ($currentTime - $date < $age_time[$t])
662         $color = $age_color[$t];
663     }
664   if (isset($color))
665     return $color;
666   else
667     return "";
668 }
669
670 /*
671  * format the date inside the thread
672  */
673 function thread_format_date($c) {
674   global $age_count,$age_time,$age_color,$thread_show;
675   $return="";
676   $currentTime=time();
677   $color="";
678   // show the date of the individual article or of the latest article
679   // in the thread?
680   if($thread_show["lastdate"])
681     $date=$c->date_thread;
682   else
683     $date=$c->date;
684   if ($age_count > 0)
685     for($t = $age_count; $t >= 1; $t--)
686       if ($currentTime - $date < $age_time[$t]) $color = $age_color[$t];
687   if ($color != "") $return .= '<font color="'.$color.'">';
688   $return .= date("d.m.",$date);                 // format the date
689   if ($color != "") $return .= '</font>';
690   return($return);
691 }
692
693 /*
694  * format the author inside the thread
695  */
696 function thread_format_author($c) {
697   global $thread_show,$anonym_address;
698   // if the address the anonymous address, only return the name
699   if($c->from==$anonym_address)
700     return $c->name;
701   $return="";
702   if($thread_show["authorlink"])
703     $return .= '<a href="mailto:'.trim($c->from).'">';
704   if (trim($c->name)!="") { 
705     $return .= htmlspecialchars(trim($c->name));
706   } else {
707     if (isset($c->username)) {
708       $s = strpos($c->username,"%");
709       if ($s != false) {
710         $return .= htmlspecialchars(substr($c->username,0,$s));
711       } else {
712         $return .= htmlspecialchars($c->username);
713       }
714     }
715   }
716   if($thread_show["authorlink"])
717     $return .= "</a>";
718   return($return);
719 }
720
721 /*
722  * Displays a part of the thread. This function is recursively called
723  * It is used by thread_show
724  */
725 function thread_show_recursive(&$headers,&$liste,$depth,$tree,$group,$article_first=0,$article_last=0,&$article_count,$highlight=false) {
726   global $thread_treestyle;
727   global $thread_show,$imgdir;
728   global $file_article,$thread_maxSubject;
729   global $age_count,$age_time,$age_color;
730   global $frame_article;
731   $output="";
732   if ($thread_treestyle==3) $output.= "\n<UL>\n";
733   for ($i = 0 ; $i<count($liste) ; $i++) {
734     // CSS class for the actual line
735     $lineclass="np_thread_line".(($article_count%2)+1);
736     // read the first article
737     $c=$headers[$liste[$i]]; 
738     $article_count++; 
739     // Render the graphical tree
740     switch ($thread_treestyle) {
741       case 4:  // thread
742       case 5:  // thread, graphic
743       case 6:  // thread, table
744       case 7:  // thread, table, graphic
745         $newtree=thread_show_calculate($tree,$depth,$i,$liste,$c);
746     }
747     if (($article_first == 0) ||
748         (($article_count >= $article_first) &&
749          ($article_count <= $article_last))) {
750       switch ($thread_treestyle) {
751         case 0: // simple list
752           $output.= '<span class="np_thread_line_text">';
753           if ($thread_show["date"]) $output.= thread_format_date($c)." ";
754           if ($thread_show["subject"]) $output.= thread_format_subject($c,$group)." ";
755           if ($thread_show["author"]) $output.= "(".thread_format_author($c).")";
756           $output.= '</span>';
757           $output.= "<br>\n";
758           break;
759         case 1: // html-auflistung, kein baum
760           $output.= '<li><nobr><span class="np_thread_line_text">';
761           if ($thread_show["date"])
762             $output.= thread_format_date($c).' ';
763           if ($thread_show["subject"])
764             $output.= thread_format_subject($c,$group,$highlight).' ';
765           if ($thread_show["author"])
766             $output.= "<i>(".thread_format_author($c).")</i>";
767           $output.= '</span></nobr></li>';
768           break;
769         case 2:   // table
770           $output.= '<tr>';
771           if ($thread_show["date"]) {
772             $output.= '<td><span class="np_thread_line_text">'.
773                       thread_format_date($c).' </span></td>';
774           }
775           if ($thread_show["subject"]) {
776             $output.= '<td nowrap="nowrap">'.
777                  '<span class="np_thread_line_text">'.
778                  thread_format_subject($c,$group,$highlight).
779                  '</span></td>';
780           }
781           if ($thread_show["author"]) {
782             $output.= '<td></td>'.
783                       '<td nowrap="nowrap">'.
784                       '<span class="np_thread_line_text">'.thread_format_author($c).
785                       '</span></td>';
786           }
787           $output.= "</tr>\n";
788           break;
789         case 3: // html-tree
790           $output.= '<li><nobr><span class="np_thread_line_text">';
791           if ($thread_show["date"])
792             $output.= thread_format_date($c)." ";
793           if ($thread_show["subject"])
794             $output.= thread_format_subject($c,$group,$highlight)." ";
795           if ($thread_show["author"])
796             $output.= "<i>(".thread_format_author($c).")</i>";
797           $output.= "</span></nobr>";
798           break;
799         case 4:  // thread
800           $output.= '<nobr><tt><span class="np_thread_line_text">';
801           if ($thread_show["date"])
802             $output.= thread_format_date($c)." ";
803           $output.= formatTreeText($newtree)." ";
804           if ($thread_show["subject"])
805             $output.= thread_format_subject($c,$group,$highlight)." ";
806           if ($thread_show["author"])
807             $output.= "<i>(".thread_format_author($c).")</i>";
808           $output.= '</span></tt></nobr><br>';
809           break;
810         case 5:  // thread, graphic
811           $output.= '<table cellspacing="0"><tr>';
812           if ($thread_show["date"])
813             $output.= '<td nowrap="nowrap">'.
814                       '<span class="np_thread_line_text">'.
815                       thread_format_date($c).' </span></td>';
816           $output.= '<td><span class="np_thread_line_text">'.
817                     thread_show_treegraphic($newtree).'</span></td>';
818           if ($thread_show["subject"])
819             $output.= '<td nowrap="nowrap">'.
820                       '<span class="np_thread_line_text">&nbsp;'.
821                       thread_format_subject($c,$group,$highlight)." ";
822           if ($thread_show["author"])
823             $output.= '('.thread_format_author($c).')</span></td>';
824           $output.= "</tr></table>";
825           break;
826         case 6:  // thread, table
827           $output.= "<tr>";
828           if ($thread_show["date"])
829             $output.= '<td nowrap="nowrap"><tt>'.
830                       '<span class="np_thread_line_text">'.
831                       thread_format_date($c).' </span></tt></td>';
832           $output.= '<td nowrap="nowrap"><tt>'.
833                     '<span class="np_thread_line_text">'.
834                     formatTreeText($newtree)." ";
835           if ($thread_show["subject"]) {
836             $output.= thread_format_subject($c,$group,$highlight)."</span></tt></td>";
837             $output.= "<td></td>";
838           }
839           if ($thread_show["author"])
840             $output.= '<td nowrap="nowrap"><tt>'.
841                       '<span class="np_thread_line_text">'.
842                       thread_format_author($c).'</span></tt></td>';
843           $output.= "</tr>";
844           break;
845         case 7:  // thread, table, graphic
846           $output.= '<tr class="'.$lineclass;
847           $output.='">';
848           if ($thread_show["date"])
849             $output.= '<td class="'.$lineclass.'" nowrap="nowrap">'.
850                       '<span class="np_thread_line_text">'.
851                       thread_format_date($c)." ".
852                       '</span></td>';
853             $output.= '<td nowrap class="'.$lineclass.'">';
854             $output.= thread_show_treegraphic($newtree);
855             if ($thread_show["subject"])
856               $output.= '<span class="np_thread_line_text">&nbsp;'.
857                 thread_format_subject($c,$group,$highlight).'</span>';
858             $output.='</td>';
859             if($thread_show["threadsize"])
860               $output.= "<td>".$c->threadsize.'</td>';
861             if ($thread_show["subject"]) $output.= "<td></td>";
862             if ($thread_show["author"])
863               $output.= '<td class="'.$lineclass.'" nowrap="nowrap">'.
864                       '<span class="np_thread_line_text">'.
865                       thread_format_author($c).'</span></td>';
866             $output.= "</tr>";
867             break;
868       }
869     }
870     if ((isset($c->answers[0])) && (count($c->answers)>0) &&
871         ($article_count<=$article_last)) {
872       if ($thread_treestyle >= 4) {
873         if (substr($newtree,$depth-2,1) == "+")
874           $newtree=str_change($newtree,$depth-2,"i");
875         $newtree=str_change($newtree,$depth-1,"+");
876         $newtree=strtr($newtree,"`",".");
877       }
878       if (!isset($newtree)) $newtree="";
879       if($thread_show["replies"]) {
880         $output.=thread_show_recursive($headers,$c->answers,$depth+1,$newtree."",$group,
881                    $article_first,$article_last,$article_count,$highlight);
882       }
883     }
884     flush();
885   }
886   if ($thread_treestyle==3) $output.= "</UL>";
887   return $output;
888 }
889
890
891 /*
892  * Displays the Head (table tags, headlines etc.) of a thread
893  */
894 function thread_show_head() {
895   global $thread_show, $thread_showTable;
896   global $text_thread,$thread_treestyle;
897   if (($thread_treestyle==2) || ($thread_treestyle==6) ||
898       ($thread_treestyle==7)) {
899     echo '<table cellspacing="0" class="np_thread_table">';
900     echo '<tr class="np_thread_head">'."\n";
901     if ($thread_show["date"])
902       echo '<td width="1%" class="np_thread_head">'.$text_thread["date"]."&nbsp;</td>";
903     if ($thread_show["subject"])
904       echo '<td class="np_thread_head">'.
905            $text_thread["subject"]."</td>";
906     if ($thread_show["threadsize"])
907       echo '<td class="np_thread_head">'.
908            $text_thread["threadsize"]."</td>";
909     if ($thread_show["author"]) {
910       echo '<td class="np_thread_head">&nbsp;&nbsp;</td>';
911       echo '<td class="np_thread_head">'.$text_thread["author"]."</td>\n";
912     }
913     echo "</tr>\n";
914   } else {
915     if ($thread_treestyle==1) echo "<ul>\n";
916   }
917 }
918
919 /*
920  * Displays the tail (closing table tags, headlines etc.) of a thread
921  */
922 function thread_show_tail() {
923   global $thread_show, $thread_showTable;
924   global $text_thread,$thread_treestyle;
925   if (($thread_treestyle==2) || ($thread_treestyle==6) ||
926       ($thread_treestyle==7)) {
927     echo "</table>\n";
928   } else {
929     if ($thread_treestyle==1) echo "</ul>\n";
930   }
931 }
932
933 /*
934  * Shows a complete thread
935  *
936  * $headers: The thread to be displayed
937  * $group:   name of the newsgroup
938  * $article_first: Number of the first article to be displayed
939  * $article_last: last article
940  */
941 function thread_show(&$headers,$group,$article_first=0,$article_last=0) {
942   global $spooldir,$text_thread;
943   $article_count=0;
944   if ($headers == false) {
945     echo $text_thread["no_articles"];
946   } else {
947     // exists a cached html-output?
948     $filename=$spooldir."/".$group."-".$article_first."-".
949               $article_last.".html";
950     if (!file_exists($filename)) {
951       // no, we need to create a new html-output
952       $output="";
953       reset($headers);
954       $c=current($headers);
955       for ($i=0; $i<=count($headers)-1; $i++) {  // create the array $liste
956         if ($c->isAnswer == false) {             // where are all the articles
957           $liste[]=$c->id;                       // in that don't have
958         }                                        // references
959         $c=next($headers);
960       }
961       reset($liste);
962       if (count($liste)>0) {
963         $output.=thread_show_recursive($headers,$liste,1,"",$group,$article_first,
964                  $article_last,$article_count);
965       }
966       // cache the html-output
967       $file=fopen($filename,"w");
968       fputs($file,$output);
969       fclose($file);
970     } else {
971       // yes, a cached output exists, load it!
972       $file=fopen($filename,"r");
973       $output=fread($file,filesize($filename));
974       fclose($file);
975     }
976     thread_show_head();
977     echo $output;
978     thread_show_tail();
979   }
980 }
981
982
983
984
985 /*
986  * returns the article-numbers of all articles in a given subthread
987  *
988  * $id: article number or message id of a article in a subthread
989  * $thread: thread data, as returned by thread_cache_load()
990  */
991 function thread_getsubthreadids($id,$thread) {
992   // recursive helper function to walk through the subtree
993   function thread_getsubthreadids_recursive($id) {
994     global $thread;
995     $answers=array($thread[$id]->number);
996     // has this article answers?
997     if(isset($thread[$id]->answers)) {
998       // walk through the answers
999       foreach($thread[$id]->answers as $answer) {
1000         $answers=array_merge($answers,
1001              thread_getsubthreadids_recursive($answer));
1002       }
1003     }
1004     return $answers;
1005   }
1006
1007 //echo htmlspecialchars(print_r($thread,true));
1008   // exists the article $id?
1009   if(!isset($thread[$id]))
1010     return false;
1011   // "rewind" the subthread to the first article in the subthread
1012   $current=$id;
1013   flush();
1014   while(isset($thread[$id]->references)) {
1015     foreach($thread[$id]->references as $reference) {
1016       if((trim($reference)!='') && (isset($thread[$reference]))) {
1017         $id=$reference;
1018         continue 2;
1019       }
1020     }
1021     break;
1022   }
1023
1024   // walk through the thread and fill up $subthread
1025   // use the recursive helper-function thread_getsubthreadids_recursive
1026   $subthread=thread_getsubthreadids_recursive($id);
1027   return $subthread;
1028 }
1029
1030 ?>