10 sub linkify ($$) { #{{{
14 $content =~ s{(\\?)$config{wiki_link_regexp}}{
15 $2 ? ( $1 ? "[[$2|$3]]" : htmllink($page, titlepage($3), 0, 0, pagetitle($2)))
16 : ( $1 ? "[[$3]]" : htmllink($page, titlepage($3)))
24 return $_scrubber if defined $_scrubber;
26 eval q{use HTML::Scrubber};
27 # Lists based on http://feedparser.org/docs/html-sanitization.html
28 $_scrubber = HTML::Scrubber->new(
30 a abbr acronym address area b big blockquote br
31 button caption center cite code col colgroup dd del
32 dfn dir div dl dt em fieldset font form h1 h2 h3 h4
33 h5 h6 hr i img input ins kbd label legend li map
34 menu ol optgroup option p pre q s samp select small
35 span strike strong sub sup table tbody td textarea
36 tfoot th thead tr tt u ul var
38 default => [undef, { map { $_ => 1 } qw{
39 abbr accept accept-charset accesskey action
40 align alt axis border cellpadding cellspacing
41 char charoff charset checked cite class
42 clear cols colspan color compact coords
43 datetime dir disabled enctype for frame
44 headers height href hreflang hspace id ismap
45 label lang longdesc maxlength media method
46 multiple name nohref noshade nowrap prompt
47 readonly rel rev rows rowspan rules scope
48 selected shape size span src start summary
49 tabindex target title type usemap valign
56 sub htmlize ($$) { #{{{
60 if (! $INC{"/usr/bin/markdown"}) {
62 $blosxom::version="is a proper perl module too much to ask?";
64 do "/usr/bin/markdown";
67 if ($type eq '.mdwn') {
68 $content=Markdown::Markdown($content);
71 error("htmlization of $type not supported");
74 if ($config{sanitize}) {
75 $content=scrubber()->scrub($content);
81 sub backlinks ($) { #{{{
85 foreach my $p (keys %links) {
86 next if bestlink($page, $p) eq $page;
87 if (grep { length $_ && bestlink($p, $_) eq $page } @{$links{$p}}) {
88 my $href=File::Spec->abs2rel(htmlpage($p), dirname($page));
90 # Trim common dir prefixes from both pages.
92 my $page_trimmed=$page;
94 1 while (($dir)=$page_trimmed=~m!^([^/]+/)!) &&
96 $p_trimmed=~s/^\Q$dir\E// &&
97 $page_trimmed=~s/^\Q$dir\E//;
99 push @links, { url => $href, page => $p_trimmed };
103 return sort { $a->{page} cmp $b->{page} } @links;
106 sub parentlinks ($) { #{{{
113 foreach my $dir (reverse split("/", $page)) {
116 unshift @ret, { url => "$path$dir.html", page => $dir };
122 unshift @ret, { url => length $path ? $path : ".", page => $config{wikiname} };
126 sub preprocess ($$) { #{{{
134 if (length $escape) {
135 return "[[$command $params]]";
137 elsif (exists $hooks{preprocess}{$command}) {
139 while ($params =~ /(\w+)=\"([^"]+)"(\s+|$)/g) {
142 return $hooks{preprocess}{$command}{call}->(page => $page, %params);
145 return "[[$command not processed]]";
149 $content =~ s{(\\?)$config{wiki_processor_regexp}}{$handle->($1, $2, $3)}eg;
153 sub add_depends ($$) { #{{{
157 if (! exists $depends{$page}) {
158 $depends{$page}=$globlist;
161 $depends{$page}=globlist_merge($depends{$page}, $globlist);
165 sub globlist_merge ($$) { #{{{
170 # Only add negated globs if they are not matched by the other globlist.
171 foreach my $i ((map { [ $a, $_ ] } split(" ", $b)),
172 (map { [ $b, $_ ] } split(" ", $a))) {
173 if ($i->[1]=~/^!(.*)/) {
174 if (! globlist_match($1, $i->[0])) {
186 sub genpage ($$$) { #{{{
191 my $title=pagetitle(basename($page));
193 my $template=HTML::Template->new(blind_cache => 1,
194 filename => "$config{templatedir}/page.tmpl");
196 if (length $config{cgiurl}) {
197 $template->param(editurl => cgiurl(do => "edit", page => $page));
198 $template->param(prefsurl => cgiurl(do => "prefs"));
200 $template->param(recentchangesurl => cgiurl(do => "recentchanges"));
204 if (length $config{historyurl}) {
205 my $u=$config{historyurl};
206 $u=~s/\[\[file\]\]/$pagesources{$page}/g;
207 $template->param(historyurl => $u);
209 $template->param(headercontent => $config{headercontent});
213 wikiname => $config{wikiname},
214 parentlinks => [parentlinks($page)],
216 backlinks => [backlinks($page)],
217 discussionlink => htmllink($page, "Discussion", 1, 1),
218 mtime => scalar(gmtime($mtime)),
219 styleurl => styleurl($page),
222 return $template->output;
225 sub check_overwrite ($$) { #{{{
226 # Important security check. Make sure to call this before saving
227 # any files to the source directory.
231 if (! exists $renderedfiles{$src} && -e $dest && ! $config{rebuild}) {
232 error("$dest already exists and was rendered from ".
233 join(" ",(grep { $renderedfiles{$_} eq $dest } keys
235 ", before, so not rendering from $src");
242 return (stat($file))[9];
245 sub findlinks ($$) { #{{{
250 while ($content =~ /(?<!\\)$config{wiki_link_regexp}/g) {
251 push @links, titlepage($2);
253 # Discussion links are a special case since they're not in the text
254 # of the page, but on its template.
255 return @links, "$page/discussion";
258 sub render ($) { #{{{
261 my $type=pagetype($file);
262 my $srcfile=srcfile($file);
263 if ($type ne 'unknown') {
264 my $content=readfile($srcfile);
265 my $page=pagename($file);
266 delete $depends{$page};
268 if (exists $hooks{filter}) {
269 foreach my $id (keys %{$hooks{filter}}) {
270 $content=$hooks{filter}{$id}{call}->(
277 $links{$page}=[findlinks($content, $page)];
279 $content=linkify($content, $page);
280 $content=preprocess($page, $content);
281 $content=htmlize($type, $content);
283 check_overwrite("$config{destdir}/".htmlpage($page), $page);
284 writefile(htmlpage($page), $config{destdir},
285 genpage($content, $page, mtime($srcfile)));
286 $oldpagemtime{$page}=time;
287 $renderedfiles{$page}=htmlpage($page);
290 my $content=readfile($srcfile, 1);
292 delete $depends{$file};
293 check_overwrite("$config{destdir}/$file", $file);
294 writefile($file, $config{destdir}, $content, 1);
295 $oldpagemtime{$file}=time;
296 $renderedfiles{$file}=$file;
304 my $dir=dirname($file);
305 while (rmdir($dir)) {
310 sub refresh () { #{{{
311 # find existing pages
314 eval q{use File::Find};
318 if (/$config{wiki_file_prune_regexp}/) {
319 $File::Find::prune=1;
321 elsif (! -d $_ && ! -l $_) {
322 my ($f)=/$config{wiki_file_regexp}/; # untaint
324 warn("skipping bad filename $_\n");
327 $f=~s/^\Q$config{srcdir}\E\/?//;
329 $exists{pagename($f)}=1;
337 if (/$config{wiki_file_prune_regexp}/) {
338 $File::Find::prune=1;
340 elsif (! -d $_ && ! -l $_) {
341 my ($f)=/$config{wiki_file_regexp}/; # untaint
343 warn("skipping bad filename $_\n");
346 # Don't add files that are in the
348 $f=~s/^\Q$config{underlaydir}\E\/?//;
349 if (! -e "$config{srcdir}/$f" &&
350 ! -l "$config{srcdir}/$f") {
352 $exists{pagename($f)}=1;
357 }, $config{underlaydir});
361 # check for added or removed pages
363 foreach my $file (@files) {
364 my $page=pagename($file);
365 if (! $oldpagemtime{$page}) {
366 debug("new page $page") unless exists $pagectime{$page};
369 $pagesources{$page}=$file;
370 $pagectime{$page}=mtime(srcfile($file))
371 unless exists $pagectime{$page};
375 foreach my $page (keys %oldpagemtime) {
376 if (! $exists{$page}) {
377 debug("removing old page $page");
378 push @del, $pagesources{$page};
379 prune($config{destdir}."/".$renderedfiles{$page});
380 delete $renderedfiles{$page};
381 $oldpagemtime{$page}=0;
382 delete $pagesources{$page};
386 # render any updated files
387 foreach my $file (@files) {
388 my $page=pagename($file);
390 if (! exists $oldpagemtime{$page} ||
391 mtime(srcfile($file)) > $oldpagemtime{$page}) {
392 debug("rendering changed file $file");
398 # if any files were added or removed, check to see if each page
399 # needs an update due to linking to them or inlining them.
400 # TODO: inefficient; pages may get rendered above and again here;
401 # problem is the bestlink may have changed and we won't know until
404 FILE: foreach my $file (@files) {
405 my $page=pagename($file);
406 foreach my $f (@add, @del) {
408 foreach my $link (@{$links{$page}}) {
409 if (bestlink($page, $link) eq $p) {
410 debug("rendering $file, which links to $p");
420 # Handle backlinks; if a page has added/removed links, update the
421 # pages it links to. Also handles rebuilding dependat pages.
422 # TODO: inefficient; pages may get rendered above and again here;
423 # problem is the backlinks could be wrong in the first pass render
425 if (%rendered || @del) {
426 foreach my $f (@files) {
428 if (exists $depends{$p}) {
429 foreach my $file (keys %rendered, @del) {
431 my $page=pagename($file);
432 if (globlist_match($page, $depends{$p})) {
433 debug("rendering $f, which depends on $page");
443 foreach my $file (keys %rendered, @del) {
444 my $page=pagename($file);
446 if (exists $links{$page}) {
447 foreach my $link (map { bestlink($page, $_) } @{$links{$page}}) {
449 (! exists $oldlinks{$page} ||
450 ! grep { bestlink($page, $_) eq $link } @{$oldlinks{$page}})) {
451 $linkchanged{$link}=1;
455 if (exists $oldlinks{$page}) {
456 foreach my $link (map { bestlink($page, $_) } @{$oldlinks{$page}}) {
458 (! exists $links{$page} ||
459 ! grep { bestlink($page, $_) eq $link } @{$links{$page}})) {
460 $linkchanged{$link}=1;
465 foreach my $link (keys %linkchanged) {
466 my $linkfile=$pagesources{$link};
467 if (defined $linkfile) {
468 debug("rendering $linkfile, to update its backlinks");
470 $rendered{$linkfile}=1;
475 if (@del && exists $hooks{delete}) {
476 foreach my $id (keys %{$hooks{delete}}) {
477 $hooks{delete}{$id}{call}->(@del);
480 if (%rendered && exists $hooks{render}) {
481 foreach my $id (keys %{$hooks{render}}) {
482 $hooks{render}{$id}{call}->(keys %rendered);