Minor fix
[amuse-wiki:kolumbo-sandbox.git] / AmuseWiki / lib / AmuseWiki.pm
1 package AmuseWiki;
2 use strict;
3 use warnings;
4 use utf8;
5 use Encode;
6 use HTML::Entities;
7 use Dancer ':syntax';
8 use Dancer::Plugin::FlashMessage;
9 use Data::Dumper;
10 use Image::ExifTool qw/ImageInfo/;
11 use Text::Muse qw/muse_to_html/;
12 use Text::Muse::Preprocessing qw/typography_filter linkify_filter/;
13 use Text::Muse::Wiki::Storage;
14 use Text::Muse::Wiki::Search qw/xapian_search_db/;
15 use File::Spec;
16 use File::Copy;
17 use File::Temp qw/tempfile/;
18 use File::Basename qw/fileparse/;
19 use Text::Muse::Utils qw/muse_naming_algo
20                          write_to_file
21                          read_file_as_string
22                          read_file_as_array/;
23 use YAML::XS qw/LoadFile Dump/;
24 use AmuseWiki::Helpers qw/process_diff_output build_rss create_tag_cloud/;
25 use AmuseWiki::BookBuilder qw/build_book/;
26 use AmuseWiki::Cleaner qw/html_to_muse/;
27 use Storable qw(lock_store lock_retrieve);
28
29 our $VERSION = '0.1';
30 my $MAXUPLOADFILESIZE = 1024 * 1024 * 3; # 4 Mb?
31
32
33 ################################################################################
34 #                             DIRECTORIES SETUP                                #
35 ################################################################################
36
37 my $locations = directory_setup();
38 debug Dumper($locations);
39
40 # when we start up, we read the db.
41 my ($archiveindex, $archiveindexts, $tagcloud);
42 init_db();
43
44 # repo init
45 my $repo = Text::Muse::Wiki::Storage->new($locations->{repo});
46 my $dirty = Text::Muse::Wiki::Storage->new($locations->{dirty});
47
48
49 ################################################################################
50 #                             SPECIAL BLOCK                                    #
51 ################################################################################
52
53 get '/' => sub {
54   forward '/special/index'
55 };
56
57 get '/special/:page' => sub {
58   if (my $content = _get_special_page(params->{page}, "html")){
59     return template 'special' => {thecontent => $content,
60                                   cloud => $tagcloud,
61                                   pagename => params->{page},
62                                   latests => $archiveindex->{latestpieces},
63                                  };
64   } elsif (session->{user}) {
65     my $page = muse_naming_algo(params->{page});
66     return redirect "/editspecial/$page"
67   }
68   else {
69     status 404;
70     send_file "404.html";
71   }
72 };
73
74 any ['get', 'post'] => '/editspecial/:page' => sub {
75   unless (is_authenticated(session->{authenticated})) {
76     return redirect '/login'
77   }
78   my $debug = session->{user} . "is editing " . params->{page};
79   debug $debug;
80   my $destpage = muse_naming_algo(params->{page});
81   if (request->is_get) {
82     params->{maintextarea} = _get_special_page(params->{page})
83   }
84   # filters # duplicated code with the postform
85   if ((params->{maintextarea}) && (! (params->{notypography}))) {
86     params->{maintextarea} = typography_filter("en", params->{maintextarea})
87   }
88   if (params->{linkify}) {
89     params->{maintextarea} = linkify_filter(params->{maintextarea});
90   }
91   my ($body, $directives);
92   if (params->{maintextarea}) {
93     ($body, $directives) = muse_to_html(params->{maintextarea});
94   };
95
96   # we're ready, go.
97   if (params->{commit}
98       && params->{maintextarea}) {
99     manage_special_page($destpage, params->{maintextarea});
100     my $redirect = "/special/" . params->{page};
101     return redirect $redirect;
102   }
103   return template 'editspecial', { wantseditor => 1,
104                                    preview => $body,
105                                  };
106 };
107
108
109 ################################################################################
110 #                             ADMIN BLOCK                                      #
111 ################################################################################
112
113
114 get '/login' => sub {
115   if (is_authenticated(session->{authenticated})) {
116     flash alreadyloggedin => 1;
117     return redirect '/';
118   } else {
119     template 'login';
120   }
121 };
122
123 get '/logout' => sub {
124   session->destroy;
125   return redirect '/';
126 };
127
128 post '/login' => sub {
129   my $user = params->{username};
130   my $passwd = params->{password};
131   if ($user && $passwd &&
132       (exists config->{'amusewiki_users'}->{$user}) &&
133       ($passwd eq config->{'amusewiki_users'}->{$user})) {
134     session 'authenticated' => config->{amusewiki_authkey};
135     session 'user'          => $user;
136     return redirect '/';
137   } else {
138     template 'login' => { failed => 1 }
139   }
140 };
141
142 get '/admin' => sub {
143   unless (is_authenticated(session->{authenticated})) {
144     return redirect '/login';
145   }
146   my $debug = session->{user} . " requested /admin";
147   debug $debug;
148   my $gitlog = $dirty->show_diff_between_branch_with_log('master', 'dirty');
149   my $diff;
150   my $pending;
151   my $err = {};
152   unless (param "ok") {
153     if (defined $gitlog) {
154       if ($gitlog ne "") {
155         $diff = process_diff_output(encode_entities($gitlog));
156         $pending = 1;
157       }
158     } else {
159       # this should not happen anymore. With the lock it would just wait.
160       $err->{gitlocked} = 1;
161     }
162   }
163   template 'gitlog' => { commits => $diff,
164                          err => $err,
165                          pending => $pending,
166                        };
167 };
168
169 get config->{wikiprefix} . '/:name/history' => sub {
170   unless (is_authenticated(session->{authenticated})) {
171     return redirect '/login';
172   }
173   my $debug = session->{user} . " requested " . params->{name} . " history"; 
174   debug  $debug;
175   my $target = muse_naming_algo(params->{name});
176   my $gitlog = $dirty->show_history_for_page($target);
177   my $diff;
178   my $err = {};
179   if (defined $gitlog) {
180     $diff = process_diff_output(encode_entities($gitlog));
181   } else {
182     $err->{gitlocked} = 1;
183   }
184   template 'gitlog' => { commits => $diff,
185                          err => $err,
186                        };
187 };
188
189 post '/admin' => sub {
190   unless (is_authenticated(session->{authenticated})) {
191     return redirect '/login';
192   }
193   my $debug = session->{user} . " is merging";
194   debug $debug;
195   my $output;
196   if (params->{approvequeue}) {
197     $output = $dirty->merge_dirty_branch_in_master('dirty');
198   }
199   template 'gitlog' => { commandout => $output };
200 };
201
202
203 ################################################################################
204 #                             REGULAR ROUTES                                   #
205 ################################################################################
206
207 get '/library' => sub {
208   forward '/titles';
209 };
210
211 foreach my $key (keys %{config->{amusewiki_indexmap}}) {
212   get "/$key" => sub {
213     update_db();
214     template $key => {
215                       sortedlist => $archiveindex->{$key},
216                       pagename => config->{amusewiki_indexmap}->{$key}->{desc},
217                       nav => $key,
218                      }
219   };
220
221   get qr{/$key/(.+?)(.html)?} => sub {
222     my ($name, $ext) = splat;
223     my $hash = $key . '-as-hash';
224     my $realname = muse_naming_algo($name);
225
226     unless (exists $archiveindex->{$hash}->{$realname}) {
227       status 404;
228       return send_file "404.html";
229     }
230     params->{name} = $archiveindex->{$hash}->{$realname}->{name};
231     template "single$key" => {
232                               sortedlist => $archiveindex->{$hash}->{$realname},
233                               nav => $key,
234                              }
235   };
236 };
237
238 prefix config->{wikiprefix};
239
240 # images:
241 get qr{/([^\/]+)(\.(png|jpe?g))} => sub {
242   my ($name, $extwithdot, $ext) = splat;
243   # ask the dirty resolver. Dirty is supposed to be ahead of master
244   my $dirtypath = $dirty->resolver($name, $ext);
245   if (-f $dirtypath->{fullpath}) {
246     send_file($dirtypath->{fullpath},
247               system_path => 1)
248   } else {
249     status 404;
250     return "File not found";
251   }
252 };
253
254 get qr{/([^\/]+?)(\.(a4.pdf|lt.pdf|pdf|tex|html|muse|epub))?} => sub {
255   my ($name, $extwithdot, $ext) = splat;
256   $ext = $ext || "html";
257   # normalize
258   my $canonical = muse_naming_algo($name);
259   # it's an "?edit"? good, don't ask the resolver nor the db, just try
260   # to fetch the existing file and serve the form
261   if (exists params->{edit}) {
262     params->{maintextarea} = $dirty->get_muse_file_for($canonical);
263     params->{name} = $canonical;
264     return template 'postform',
265       { upload_url => uri_for(config->{wikiprefix} . '/' . $canonical),
266         wantseditor => 1,
267       },
268   };
269   my $file; # the file to send
270   # first we see if it's in the db, and if it's not, we try to reload
271   # it, maybe it's not synced
272   unless (exists $archiveindex->{meta}->{$canonical}) {
273     debug "$canonical not in the db, trying to reload it";
274     update_db();
275   }
276   if (exists $archiveindex->{meta}->{$canonical}) {
277     $file = File::Spec->catfile($archiveindex->{meta}->{$canonical}->{path},
278                                 $archiveindex->{meta}->{$canonical}->{file}
279                                 . "." . $ext);
280     # check if it's been deleted
281     if (my $deletion = $archiveindex->{meta}->{$canonical}->{DELETED}) {
282       return template 'error' => { deletedpage => $deletion };
283     }
284     unless (-f $file) {
285       return template 'error', { supposedherebutnotfound => 1 }
286     }
287   } else {
288     return redirect (uri_for(config->{wikiprefix} . '/' . $canonical) . "?edit");
289   }
290   
291   if ($ext eq "html") {
292     if (exists params->{print}) {
293       send_file($file,
294                 system_path => 1);
295     } else {
296     # process the HTML, we reuse the layout
297       my $text = generate_html($file);
298       my $directives = $archiveindex->{meta}->{$canonical};
299       return template 'page' => {
300                                  directives => $directives,
301                                  text => $text,
302                                 };
303     }
304   } elsif ($ext eq "epub") {
305     send_file($file,
306               system_path => 1,
307               content_type => 'application/epub+zip');
308   } elsif ($ext eq "tex") {
309     send_file($file,
310               system_path => 1,
311               content_type => 'application/x-tex;charset=UTF-8');
312   } elsif ($ext eq "muse") {
313     send_file($file,
314               system_path => 1,
315               content_type => 'text/plain;charset=UTF-8');
316   } else {
317     send_file($file, system_path => 1);
318   }
319 };
320
321 ################################################################################
322 #                             UPLOAD BLOCK                                     #
323 ################################################################################
324
325 # general form for uploading
326 post '/:name' => sub {
327   # first, important thing, we clean up the name and save it in the params
328   my $canonical = muse_naming_algo(params->{name});
329   params->{name} = $canonical;
330   # here we dump the session and restore it later.
331   my $contrib = session 'contrib' || {};
332   unless (exists $contrib->{$canonical}) {
333     $contrib->{$canonical} = { preview => 0,
334                                ups => [],
335                              };
336   }
337   my $err = {};
338   my $lastupload = {};
339   my $upload_url = uri_for(config->{wikiprefix} . '/' . $canonical);
340   if (params->{hitme}) {
341     return "Go away, spammer"
342   }
343   my $canupload;
344   if ($contrib->{$canonical}->{preview} || params->{preview}) {
345     $canupload = 1;
346   }
347   # see if we have upload. It looks nice
348   if ((my $file = upload('Upload')) && $canupload) {
349     my $safeimage = process_image($file, $canonical);
350     if (! defined $safeimage) {$err->{errorgitlocked} = 1 }
351     elsif      (! $safeimage) { $err->{uploadinvalid} = 1 }
352     elsif        ($safeimage) {
353       # we save the attachments in the session->{contrib}, under {$canonical}->ups
354       push @{$contrib->{$canonical}->{ups}}, $safeimage;
355       $lastupload->{source} = $file->filename;
356       $lastupload->{dest} = $safeimage;
357     }
358   }
359   # filters
360   if ((params->{maintextarea}) && (! (params->{notypography}))) {
361     params->{maintextarea} = typography_filter("en", params->{maintextarea})
362   }
363   if (params->{linkify}) {
364     params->{maintextarea} = linkify_filter(params->{maintextarea});
365   }
366   my ($body, $directives);
367   if (params->{maintextarea}) {
368     ($body, $directives) = muse_to_html(params->{maintextarea});
369   };
370   ### start conditional to see what we can do
371   if (params->{preview} || (! params->{commit})) {
372     unless ($contrib->{$canonical}->{preview}) {
373       $contrib->{$canonical}->{preview} = time;
374     }
375   }
376   elsif (params->{commit}) {
377     # it wants a commit.
378     if (!$contrib->{$canonical}->{preview}) {
379       $err->{nopreview} = 1;
380       $contrib->{$canonical}->{preview} = time;
381     }
382     elsif ((time - $contrib->{$canonical}->{preview}) < 10) { 
383       $err->{toofast} = 1; }
384     elsif (! params->{magicquestion} ) { $err->{magic}  = 1; }
385     elsif (params->{magicquestion} ne config->{amusewiki_antispam_answer}) {
386       $err->{magic} = 1;
387     }
388     elsif (! params->{messagetolibs} ) { $err->{nomsg} =  1; }
389     elsif (! params->{usernameforgit}) { $err->{nouser} = 1; }
390     elsif (! params->{maintextarea}  ) { $err->{notext} = 1; }
391     elsif (((length params->{messagetolibs}) > 500) || # don't write a book
392            ((length params->{usernameforgit}) > 50) || # it's just a name
393            ((length params->{maintextarea}) > $MAXUPLOADFILESIZE)) {
394       $err->{overflow} = 1; }
395     elsif ((length params->{maintextarea}) < 10) { $err->{tooshort} = 1; }
396     elsif (! $body) { $err->{notext} = 1; }
397     elsif (request->is_post &&
398            (! (keys %$err))) {
399       my $commit_result = process_text_upload($dirty,
400                                               $canonical,
401                                               params->{maintextarea},
402                                               $contrib->{$canonical}->{ups},
403                                               params->{usernameforgit},
404                                               params->{messagetolibs});
405       # all ok
406       if (defined $commit_result) {
407         delete $contrib->{$canonical};
408         session 'contrib' => $contrib;
409         flash committed => $commit_result;
410         return redirect '/';
411       }
412       else {
413         $err->{errorgitlocked} = 1;
414       }
415     }
416   }
417   ### end of checking. Return the preview (as the commit has already
418   ### been caught
419   unless (keys %$err) {
420     $err = undef;
421   }
422   session 'contrib' => $contrib; 
423   template 'postform', { preview => $body,
424                          directives => $directives,
425                          canupload => $canupload,
426                          lastupload => $lastupload, # a message
427                          attachments => $contrib->{$canonical}->{ups},
428                          upload_url => $upload_url,
429                          wantseditor => 1,
430                          err => $err }
431 };
432
433
434
435 ################################################################################
436 #                             BOOK BUILDER                                     #
437 ################################################################################
438
439 prefix undef;
440
441 any ["get", "post"] => '/bookbuilder' => sub {
442   my %errs;
443   my $created;
444   my %params = params;
445   my $books = session "books";
446   unless ($books && (ref($books) eq "ARRAY")) { $books = []; }
447   # nothing to do? Ok, no problem
448   unless (%params) {
449     if (@$books) {
450       return template 'bookbuilder';
451     } else {
452       return forward '/bookbuilder/status';
453     }
454   }
455   # addition. Checked
456   if (params->{addme}) {
457     push @$books, muse_naming_algo(params->{addme});
458     session "books" => $books;
459     return template 'bookbuilder';
460   }
461   # moving. Checked.
462   elsif (my @moving = grep (/^(move(dw|up)|delete)(\d+|all)$/, keys %params)) {
463     manage_book_list($books, @moving);
464     session "books" => $books;
465     if (@$books) {
466       return template 'bookbuilder';
467     } else {
468       return forward '/bookbuilder/status', {}, {method => 'GET'};
469     }
470   }
471   # checking, no nam
472   unless(params->{collectionname}) {
473     $errs{noname} = 1;
474     return template 'bookbuilder' => { errs => \%errs };
475   }
476   unless (params->{magicquestion} &&
477           (params->{magicquestion} eq config->{amusewiki_antispam_answer})) {
478     $errs{magic} = 1;
479     return template 'bookbuilder' => { errs => \%errs };
480   }
481   unless (params->{build} && request->is_post) {
482     return forward '/bookbuilder/status';
483   }
484   my $bboptions = {};
485   $bboptions->{minsig} = checksig(param("signmin"));
486   $bboptions->{maxsig} = checksig(param("signmax"));
487   if (($bboptions->{minsig} || $bboptions->{minsig}) &&
488       ($bboptions->{maxsig} - $bboptions->{minsig}) < 30) {
489     $errs{wrongsigrange} = 1;
490   }
491   if (param("imposed"))                    { $bboptions->{imposed} = 1   };
492   if (param("tocwanted") || (@$books > 1)) { $bboptions->{tocwanted} = 1 };
493   if (param("font"))  { $bboptions->{font}  = checkmode(param("font"))  };
494   if (param("paper")) { $bboptions->{paper} = checkmode(param("paper")) };
495   # wash the books
496   my $washed_books = [];
497   foreach my $b (@$books) {
498     my $musepath = $repo->resolver($b, "muse");
499     if (-f $musepath->{fullpath}) {
500       push @$washed_books, $musepath->{fullpath};
501     } else {
502       $errs{notexists} .= $b;
503       last;
504     }
505   }
506   if (%errs || (! @$washed_books)) { return template 'bookbuilder' => { errs => \%errs }; };
507
508   # all ok?  It's a build, parameters are checked, we have some books. do it
509   my $outfile = find_file_name_free($locations->{customtex},
510                                     params->{collectionname});
511   debug $washed_books;
512   debug $bboptions;
513   # the $washed_books is shifted in the build_book functions
514   # AT THIS POINT ALL THE OPTIONS PASSED SHOULD BE CLEANED AND CHECKED
515   my $tex = build_book($outfile,
516                        $locations->{templatedir},
517                        $washed_books,
518                        $bboptions);
519   session "latestbook" => $tex;
520   session "books" => [];
521   flash 'bookcreated' => $tex;
522   return redirect '/';
523 };
524
525 get '/bookbuilder/clearqueue' => sub {
526   if (session->{latestbook}) {
527     session 'latestbook' => undef;
528   };
529   redirect '/';
530 };
531
532 get '/bookbuilder/status' => sub {
533   my $destdir = $locations->{customtex};
534   my $formats = {};
535   if (my $latest = session->{latestbook}) {
536     #    debug session->{latestbook};
537     my $basenameout = File::Basename::basename($latest);
538     # PDF?
539     my $pdf = File::Spec->catfile($locations->{customtex},
540                                   $basenameout . ".pdf");
541     if (-f $pdf) { $formats->{pdf} = $basenameout };
542     # TEX?
543     my $tex = File::Spec->catfile($locations->{customtex},
544                                   $basenameout . ".tex");
545     if (-f $tex) { $formats->{tex} = $basenameout };
546   }
547
548   # status
549   opendir(my $dh, $destdir)
550     or return "Cannot open the directory with the files! $!\n";
551   my @pdfs = grep {/\.pdf$/ &&
552                      -f File::Spec->catfile($destdir, $_)
553                    } readdir($dh);
554   closedir($dh);
555   @pdfs = sort { -C File::Spec->catfile($destdir, $a) <=>
556                    -C File::Spec->catfile($destdir, $b) } (@pdfs);
557   template 'bookbuilderstatus' => {
558                                    pdfs => \@pdfs,
559                                    formats => $formats,
560                                   };
561 };
562
563
564
565 ################################################################################
566 #                             LEGACY BLOCK                                     #
567 ################################################################################
568
569 if (config->{amusewiki_legacyurls}) { # it's actually a file
570   # html
571   get '/HTML/:name' => sub {
572     my $name = params->{name};
573     my $target = config->{wikiprefix} . '/' . params->{name};
574     debug $target;
575     return forward $target;
576   };
577   # pdf
578   get '/pdfs/a4/*_a4.pdf' => sub {
579     my ($basename) = splat;
580     my $target = config->{wikiprefix} . '/' . $basename . ".pdf";
581     return forward $target;
582   };
583   get '/pdfs/letter/*_letter.pdf' => sub {
584     my ($basename) = splat;
585     my $target = config->{wikiprefix} . '/' . $basename . ".pdf";
586     return forward $target;
587   };
588   get '/pdfs/a4_imposed/*_a4_imposed.pdf' => sub {
589     my ($basename) = splat;
590     my $target = config->{wikiprefix} . '/' . $basename . ".a4.pdf";
591     return forward $target;
592   };
593   get '/pdfs/letter_imposed/*_letter_imposed.pdf' => sub {
594     my ($basename) = splat;
595     my $target = config->{wikiprefix} . '/' . $basename . ".lt.pdf";
596     return forward $target
597   };
598   # epub
599   get '/epub/*.epub' => sub {
600     my ($basename) = splat;
601     my $target = config->{wikiprefix} . '/' . $basename . ".epub";
602     return forward $target
603   };
604   get '/print/*.html' => sub {
605     my ($basename) = splat;
606     my $target = config->{wikiprefix} . '/' . $basename . ".html";
607     return forward $target
608   };
609   get '/text-index.html' => sub {
610     return forward '/titles';
611   };
612   get '/author-index.html' => sub {
613     return forward '/authors';
614   };
615   get '/authors.html' => sub {
616     return forward '/authors';
617   };
618   get '/topics.html' => sub {
619     return forward '/topics';
620   };
621
622 }
623
624 ################################################################################
625 #                              SEARCH ENGINE                                   #
626 ################################################################################
627
628 get '/search/:query' => sub {
629   forward '/search';
630 };
631
632
633 any ['get', 'post'] => '/search' => sub {
634   my ($result, $total, $matchany);
635   if (param 'query') {
636     my $config = config->{amusewiki_xapian_index_map};
637     my $querystring = params->{query}; # we just strip the "<>", just to be on the
638                                        # safe side, but probably not needed
639     # just not to pass garbage
640     if (param 'matchany') { $matchany = 1 };
641     $querystring =~ s/(<|>)//g;
642     if ($querystring =~ m/\w/) {
643       debug " Query: $querystring";
644       ($result, $total) = xapian_search_db($locations->{xapian},
645                                            $config,
646                                            $querystring,
647                                            $matchany);
648     }
649   }
650   if (ref($result) eq 'ARRAY') {
651     foreach my $text (@$result) {
652       $text->{directives} = $archiveindex->{meta}->{$text->{pagename}};
653     }
654   }
655   
656   template 'search' => {
657                         results => $result,
658                         total => $total,
659                         nav => "search",
660                        };
661 };
662
663 ################################################################################
664 #                              NEW ENTRY                                       #
665 ################################################################################
666
667
668 any ['get', 'post'] => '/new' => sub {
669   my %params = params;
670   if (params->{title} &&
671       params->{author} &&
672       params->{SORTtopics} &&
673       params->{source} &&
674       params->{go} &&
675       params->{maineditor}) {
676     my $body;
677     my $destpage = config->{wikiprefix} . "/" .
678       muse_naming_algo(params->{author} . " " . params->{title});
679     foreach my $directive (@{config->{amusewiki_defined_directives}}) {
680       next unless params->{$directive};
681       debug $directive;
682       debug params->{$directive};
683       my $text = html_to_muse(params->{$directive});
684       debug $text;
685       $text =~ s/\r*\n/ /g; # it's a directive, no \n
686       $body .= "#" . $directive . " " . $text . "\n";
687     }
688     $body .= html_to_muse(params->{maineditor});
689     debug $body;
690     return forward $destpage,
691       {maintextarea => $body },
692         { method => 'POST' };
693   };
694   my (@topics, @authors);
695   if (exists $archiveindex->{authors}) {
696     foreach my $item (@{$archiveindex->{authors}}) {
697       push @authors, $item->{name};
698     }
699   }
700   if (exists $archiveindex->{topics}) {
701     foreach my $item (@{$archiveindex->{topics}}) {
702       push @topics, $item->{name};
703     }
704   }
705   template 'newentry' => {
706                           wantsjquery => 1,
707                           wantsck => 1,
708                           jsontopics => to_json(\@topics),
709                           jsonauthors => to_json(\@authors)
710                          };
711 };
712
713
714 ################################################################################
715 #                             END OF ROUTES, start helpers                     #
716 ################################################################################
717
718
719 sub process_image {
720   my ($danceruploaded, $target) = @_;
721   debug $danceruploaded->size;
722   debug $danceruploaded->tempname;
723   # validate the size
724   return 0 unless (($danceruploaded->size > 0) &&
725                        ($danceruploaded->size < $MAXUPLOADFILESIZE));
726
727   # validate the type
728   my $imginfo = ImageInfo($danceruploaded->tempname, 'FileType');
729   debug Dumper($imginfo);
730   my $ext = lc $imginfo->{FileType};
731   return 0 unless ($ext and (($ext eq "png") or
732                              ($ext eq "jpeg")));
733   
734   my ($fhx, $tmpfile) = tempfile();
735   $danceruploaded->copy_to($tmpfile);
736   # all ok? good, store in the git directory, without overwriting and
737   # without commiting. We return the name.
738   return $dirty->drop_attachment_in_tree($tmpfile, $target, $ext);
739 }
740
741 sub init_db {
742   my $db = $locations->{db};
743   $archiveindexts = get_m_time($db);
744   debug "Loading $db";
745   $archiveindex = lock_retrieve($db);
746   debug "Done";
747
748   my $rss_output = build_rss($archiveindex->{sortedbydate},
749                              config->{amusewiki_rss});
750
751   write_to_file($locations->{rss}, $rss_output->{rss});
752   $archiveindex->{latestpieces} = $rss_output->{latests};
753
754   $tagcloud = create_tag_cloud(prepare_hash_for_cloud($archiveindex),
755                                config->{amusewiki_min_cloud_items});
756   #  debug $tagcloud;
757   # the Sitemap
758   my $sitemap = $locations->{sitemap};
759   if ((time - get_m_time($sitemap)) > (60 * 60 * 12) ||
760       ((-s $sitemap) == 0 )) {
761     update_site_map($sitemap, $archiveindex->{meta});
762   }
763 }
764
765 sub get_m_time {
766   my $file = shift;
767   if (-e $file) {
768     my $time = (stat($file))[9];
769     return $time;
770   } else {
771     return 0;
772   }
773 }
774
775 sub update_db {
776   my $db = $locations->{db};
777   debug "Checking if the db is up-to-date";
778   return unless ((get_m_time($db)) > $archiveindexts);
779   debug "Updating $db";
780   undef %$archiveindex; # clear all, just to be sure
781   $archiveindex = undef; # even the pointer
782   init_db();
783 }
784
785 sub process_text_upload {
786   my ($dirty, $canonical, $text, $attachments, $username, $message) = @_;
787   $text =~ s/\r\n/\n/gs;
788   $text =~ s/\t/    /gs;
789   $message =~ s/\r\n/\n/gs;
790   $message =~ s/^\s+$//;
791   $message =~ s/\n+/\n/;
792   if ($username =~ m/(\w+)/) {
793     $username = $1;
794   } else {
795     $username = "Anonymous";
796   }
797   my $commitmsg =  "*** " . $username . " ***\n\n" . $message .
798     "\n\n" . $canonical . "\n";
799   my @attachs;
800   if ((defined $attachments)
801       && (ref($attachments) eq "ARRAY") &&
802       (@$attachments >= 0)) {
803     debug Dumper($attachments);
804     $commitmsg .= join("\n", @$attachments);
805     @attachs = @$attachments;
806   }
807   $commitmsg .= "\n"; # be sure to end with a newline
808   # dump the things in temp files
809   my ($fh, $commitmsgfile) = tempfile();
810   write_to_file($commitmsgfile, $commitmsg, "\n"); # add a newline
811   my ($fhx, $musetmpfile) = tempfile();
812   write_to_file($musetmpfile, $text, "\n");  # add a newline
813   unless (-T $commitmsgfile && -T $musetmpfile) {
814     warn "Some genius uploaded binary data";
815     return 0;
816   }
817   my $exit = $dirty->dump_muse_file_and_commit($canonical, $musetmpfile, \@attachs,
818                                               $commitmsgfile);
819   return $exit;
820 }
821
822 ################################################################################
823 #                       BEGIN BOOK BUILDER STUFF                               #
824 ################################################################################
825
826
827 sub checksig {
828   my $sig = shift;
829   my $washed = 0;
830   if ($sig && $sig =~ m/(\d+)/) {
831     $washed = $1;
832     # if doesn't divide by 4 without modulo, set it to zero.
833     if ($washed % 4) {
834       $washed = 0;
835     }
836   }
837   return $washed;
838 }
839
840 sub checkmode {
841   my $mode = shift;
842   my $washed;
843   if ($mode =~ m/(\w+)/) {
844     $mode = $1;
845     if (config->{amusewiki_bookbuilder_def_modes}->{$mode}) {
846       $washed = $mode;
847     }
848   }
849   return $washed;
850 }
851
852 sub find_file_name_free {
853   my ($dir, $dirtyname) = @_;
854   die "No customtex dir passed $dir\n" unless -d $dir;
855   unless ($dirtyname) {
856     $dirtyname = "no-name-passed";
857   };
858   my $basename = muse_naming_algo($dirtyname);
859   my ($number, $fullname);
860   do {
861     $number++;
862     $fullname = File::Spec->catfile($dir, $basename . "-" . $number . ".tex");
863   } while -e $fullname;
864   debug $fullname;
865   write_to_file($fullname, "% Created by AmuseWiki::BookBuilder $$\n");
866   return $fullname;
867 }
868
869 sub swap_element {
870   my ($arrayref, $first, $second) = @_;
871   my $tmp = $arrayref->[$second];
872   $arrayref->[$second] = $arrayref->[$first];
873   $arrayref->[$first] = $tmp;
874 }
875
876 sub list_moveup {
877   my ($booklist, $index) = @_;
878   my $newindex = $index;
879   if ($index > 0) {
880     $newindex--;
881     if ($booklist->[$newindex]) {
882       swap_element($booklist, $index, $newindex);
883     }
884   }
885 }
886
887 sub list_movedown {
888   my ($booklist, $index) = @_;
889   my $newindex = $index;
890   if ($index < (scalar(@$booklist))) {
891     $newindex++;
892     if ($booklist->[$newindex]) {
893       swap_element($booklist, $index, $newindex);
894     }
895   }
896 }
897
898 sub manage_book_list {
899   my ($books, @params) = @_;
900   foreach my $k (@params) {
901     if ($k =~ m/^movedw(\d+)$/) {
902       my $i = $1;
903       if (exists $books->[$i]) {
904         list_movedown($books, $i)
905       }
906     } elsif ($k =~ m/^moveup(\d+)$/) {
907       my $i = $1;
908       if (exists $books->[$i]) {
909         list_moveup($books, $i)
910       }
911     } elsif ($k =~ m/^delete(\d+)$/) {
912       my $i = $1;
913       if (exists $books->[$i]) {
914         splice(@$books, $i, 1);
915       }
916     } elsif ($k =~ m/^deleteall$/) {
917       @$books = ();
918     }
919   }
920 }
921
922 ################################################################################
923 #                       END BOOK BUILDER STUFF                                 #
924 ################################################################################
925
926
927 sub manage_special_page {
928   my ($page, $text) = @_;
929   $text =~ s/\r\n/\n/gs;
930   my $destfile = File::Spec->catfile($locations->{specials},
931                                      muse_naming_algo($page));
932   # save the old one.
933   if (-f $destfile) {
934     copy $destfile, $destfile . "_pre_" . _getsuffix();
935   }
936   # write the muse (source)
937   write_to_file($destfile, $text);
938   my ($body, $directives) = muse_to_html($text);
939   # write the html
940   write_to_file($destfile . ".html", $body);
941   return
942 }
943
944 sub _getsuffix {
945   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
946     localtime(time);
947   $year = sprintf("%02d", $year % 100);;
948   $mon = sprintf("%02d", $mon + 1);
949   $mday = sprintf("%02d", $mday);
950   $hour = sprintf("%02d", $hour);
951   $min = sprintf("%02d", $min);
952   $sec = sprintf("%02d", $sec);
953   my $result = "-" . $year . "-" . $mon . "-" . $mday . 
954     "-" . $hour . "-" . $min . "-" . $sec;
955   # letter_imposed
956   # 12-05-14-21-28
957   return $result;
958 }
959
960 sub _get_special_page {
961   my ($page, $format) = @_;
962   my $file = File::Spec->catfile($locations->{specials},
963                                  muse_naming_algo(params->{page}));
964   if ($format && ($format eq 'html')) {
965     $file .= ".html";
966   }
967   if (-f $file) {
968     return read_file_as_string($file);
969   }
970 }
971
972 sub update_site_map {
973   my ($file, $targets) = @_;
974   my @output;
975   my $prefix = config->{amusewiki_rss}->{link} . "/";
976
977   # first the indices
978   foreach my $k (keys %{config->{amusewiki_indexmap}}) {
979     push @output, $prefix . $k . "\n"; 
980   }
981   # next the title
982   $prefix = config->{amusewiki_rss}->{link} . config->{wikiprefix} . "/";
983   foreach my $item (keys %$targets) {
984     push @output, $prefix . $item . "\n";
985   }
986   if (my $oldurls = $locations->{legacyurls}) {
987     foreach my $oldurl(read_file_as_array($oldurls)) {
988       push @output, $prefix . $oldurls . "\n";
989     }
990   }
991   write_to_file($file, @output);
992 }
993
994 sub generate_html {
995   my $file = shift;
996   my $rawtext = read_file_as_string($file);
997   my $start = index $rawtext, config->{amusewiki_html_markers}->{start};
998   my $stop = index $rawtext, config->{amusewiki_html_markers}->{stop}, $start;
999   return unless (($start >= 0) and  ($stop >= 0));
1000   my $length = $stop - $start +
1001     length(config->{amusewiki_html_markers}->{stop});
1002   return substr $rawtext, $start, $length;
1003 }
1004
1005 sub directory_setup {
1006   my %out;
1007   $out{repo} = File::Spec->catdir(config->{amusewiki_data_root},
1008                                   config->{amusewiki_build_repo});
1009
1010   $out{dirty} = File::Spec->catdir(config->{amusewiki_data_root},
1011                                    config->{amusewiki_dirty_repo});
1012
1013   $out{xapian} = File::Spec->catdir(config->{amusewiki_data_root},
1014                                     config->{amusewiki_xapian_db});
1015
1016   $out{customtex} = File::Spec->catdir(config->{public},
1017                                        "customtex"); # hardcode this one.
1018
1019   $out{templatedir} = File::Spec->catdir($out{repo},
1020                                          config->{amusewiki_repo_templates_dir});
1021
1022   $out{specials} = File::Spec->catdir(config->{amusewiki_data_root},
1023                                       config->{amusewiki_special_cache});
1024   # check the directories
1025   foreach my $dir (values %out) {
1026     die "$dir doesn't exists\n" unless -d $dir;
1027   }
1028   
1029   if (config->{amusewiki_legacyurls}) {
1030     $out{legacyurls} = File::Spec->catfile(config->{amusewiki_data_root},
1031                                            config->{amusewiki_legacyurls})
1032   }
1033   $out{sitemap} = File::Spec->catfile(config->{public},
1034                                       "sitemap.txt");
1035   $out{rss} = File::Spec->catfile(config->{public},
1036                                   "rss.xml");
1037   $out{db} = File::Spec->catfile($out{repo},
1038                                  config->{amusewiki_text_db});
1039   die "Missing db, cannot start\n" unless -f $out{db};
1040   # sanity check
1041   return \%out;
1042 }
1043
1044 sub prepare_hash_for_cloud {
1045   my $meta = shift;
1046   my @hashes_to_scan;
1047   my $mapping = config->{amusewiki_indexmap};
1048   foreach my $k (keys %$mapping) {
1049     if ($mapping->{$k}->{sortwith} =~ m/^SORT/) {
1050       push @hashes_to_scan, $k;
1051     }
1052   }
1053   return unless (@hashes_to_scan);
1054
1055   my %rawcloud;
1056   my $min = config->{amusewiki_min_cloud_items};
1057   foreach my $hash (@hashes_to_scan) {
1058     # check if it exists. It *must* exists
1059     die "No hash with $hash as key in the db"
1060       unless exists $archiveindex->{$hash};
1061     foreach my $item (@{$archiveindex->{$hash}}) {
1062       my $name = $item->{name};
1063       my $cleanurl = $item->{cleanurl};
1064       my $url = "/" . $hash . "/" . $item->{cleanurl};
1065       my $size = scalar @{$item->{titles}};
1066       next if $size < $min;
1067       if (exists $rawcloud{$cleanurl}) {
1068         next if ($rawcloud{$cleanurl}{size} > $size)
1069       }
1070       $rawcloud{$cleanurl} = {
1071                               name => $name,
1072                               url => $url,
1073                               size => $size,
1074                              };
1075     }
1076   }
1077   return \%rawcloud;
1078 }
1079
1080 sub is_authenticated {
1081   my $auth = shift;
1082   if ($auth && ($auth eq config->{amusewiki_authkey})) {
1083     return 1;
1084   } else {
1085     flash authonly => 1;
1086     return 0;
1087   }
1088 }
1089
1090
1091
1092 1;
1093