html escaping complication
[ikiwiki.git] / IkiWiki / Plugin / recentchanges.pm
1 #!/usr/bin/perl
2 package IkiWiki::Plugin::recentchanges;
3
4 use warnings;
5 use strict;
6 use IkiWiki 2.00;
7 use Encode;
8 use HTML::Entities;
9
10 sub import { #{{{
11         hook(type => "getsetup", id => "recentchanges", call => \&getsetup);
12         hook(type => "checkconfig", id => "recentchanges", call => \&checkconfig);
13         hook(type => "refresh", id => "recentchanges", call => \&refresh);
14         hook(type => "pagetemplate", id => "recentchanges", call => \&pagetemplate);
15         hook(type => "htmlize", id => "_change", call => \&htmlize);
16         hook(type => "cgi", id => "recentchanges", call => \&cgi);
17 } #}}}
18
19 sub getsetup () { #{{{
20         return
21                 plugin => {
22                         safe => 1,
23                         rebuild => 1,
24                 },
25                 recentchangespage => {
26                         type => "string",
27                         example => "recentchanges",
28                         description => "name of the recentchanges page",
29                         safe => 1,
30                         rebuild => 1,
31                 },
32                 recentchangesnum => {
33                         type => "integer",
34                         example => 100,
35                         description => "number of changes to track",
36                         safe => 1,
37                         rebuild => 0,
38                 },
39 } #}}}
40
41 sub checkconfig () { #{{{
42         $config{recentchangespage}='recentchanges' unless defined $config{recentchangespage};
43         $config{recentchangesnum}=100 unless defined $config{recentchangesnum};
44 } #}}}
45
46 sub refresh ($) { #{{{
47         my %seen;
48
49         # add new changes
50         foreach my $change (IkiWiki::rcs_recentchanges($config{recentchangesnum})) {
51                 $seen{store($change, $config{recentchangespage})}=1;
52         }
53         
54         # delete old and excess changes
55         foreach my $page (keys %pagesources) {
56                 if ($pagesources{$page} =~ /\._change$/ && ! $seen{$page}) {
57                         unlink($config{srcdir}.'/'.$pagesources{$page});
58                 }
59         }
60 } #}}}
61
62 # Enable the recentchanges link on wiki pages.
63 sub pagetemplate (@) { #{{{
64         my %params=@_;
65         my $template=$params{template};
66         my $page=$params{page};
67
68         if (defined $config{recentchangespage} && $config{rcs} &&
69             $page ne $config{recentchangespage} &&
70             $template->query(name => "recentchangesurl")) {
71                 $template->param(recentchangesurl => urlto($config{recentchangespage}, $page));
72                 $template->param(have_actions => 1);
73         }
74 } #}}}
75
76 # Pages with extension _change have plain html markup, pass through.
77 sub htmlize (@) { #{{{
78         my %params=@_;
79         return $params{content};
80 } #}}}
81
82 sub cgi ($) { #{{{
83         my $cgi=shift;
84         if (defined $cgi->param('do') && $cgi->param('do') eq "recentchanges_link") {
85                 # This is a link from a change page to some
86                 # other page. Since the change pages are only generated
87                 # once, statically, links on them won't be updated if the
88                 # page they link to is deleted, or newly created, or
89                 # changes for whatever reason. So this CGI handles that
90                 # dynamic linking stuff.
91                 my $page=decode_utf8($cgi->param("page"));
92                 if (!defined $page) {
93                         error("missing page parameter");
94                 }
95
96                 IkiWiki::loadindex();
97
98                 my $link=bestlink("", $page);
99                 if (! length $link) {
100                         print "Content-type: text/html\n\n";
101                         print IkiWiki::misctemplate(gettext(gettext("missing page")),
102                                 "<p>".
103                                 sprintf(gettext("The page %s does not exist."),
104                                         htmllink("", "", $page)).
105                                 "</p>");
106                 }
107                 else {
108                         IkiWiki::redirect($cgi, urlto($link, undef, 1));
109                 }
110
111                 exit;
112         }
113 }
114
115 sub store ($$$) { #{{{
116         my $change=shift;
117
118         my $page="$config{recentchangespage}/change_".titlepage($change->{rev});
119
120         # Optimisation to avoid re-writing pages. Assumes commits never
121         # change (or that any changes are not important).
122         return $page if exists $pagesources{$page} && ! $config{rebuild};
123
124         # Limit pages to first 10, and add links to the changed pages.
125         my $is_excess = exists $change->{pages}[10];
126         delete @{$change->{pages}}[10 .. @{$change->{pages}}] if $is_excess;
127         $change->{pages} = [
128                 map {
129                         if (length $config{cgiurl}) {
130                                 $_->{link} = "<a href=\"".
131                                         IkiWiki::cgiurl(
132                                                 do => "recentchanges_link",
133                                                 page => $_->{page}
134                                         ).
135                                         "\" rel=\"nofollow\">".
136                                         pagetitle($_->{page}).
137                                         "</a>"
138                         }
139                         else {
140                                 $_->{link} = pagetitle($_->{page});
141                         }
142                         $_->{baseurl}="$config{url}/" if length $config{url};
143
144                         $_;
145                 } @{$change->{pages}}
146         ];
147         push @{$change->{pages}}, { link => '...' } if $is_excess;
148
149         # See if the committer is an openid.
150         $change->{author}=$change->{user};
151         my $oiduser=eval { IkiWiki::openiduser($change->{user}) };
152         if (defined $oiduser) {
153                 $change->{authorurl}=$change->{user};
154                 $change->{user}=$oiduser;
155         }
156         elsif (length $config{cgiurl}) {
157                 $change->{authorurl} = IkiWiki::cgiurl(
158                         do => "recentchanges_link",
159                         page => (length $config{userdir} ? "$config{userdir}/" : "").$change->{author},
160                 );
161         }
162
163         if (ref $change->{message}) {
164                 foreach my $field (@{$change->{message}}) {
165                         if (exists $field->{line}) {
166                                 # escape html
167                                 $field->{line} = encode_entities($field->{line});
168                                 # escape links and preprocessor stuff
169                                 $field->{line} = encode_entities($field->{line}, '\[\]');
170                         }
171                 }
172         }
173
174         # Fill out a template with the change info.
175         my $template=template("change.tmpl", blind_cache => 1);
176         $template->param(
177                 %$change,
178                 commitdate => displaytime($change->{when}, "%X %x"),
179                 wikiname => $config{wikiname},
180         );
181         
182         $template->param(permalink => "$config{url}/$config{recentchangespage}/#change-".titlepage($change->{rev}))
183                 if exists $config{url};
184         
185         IkiWiki::run_hooks(pagetemplate => sub {
186                 shift->(page => $page, destpage => $page,
187                         template => $template, rev => $change->{rev});
188         });
189
190         my $file=$page."._change";
191         writefile($file, $config{srcdir}, $template->output);
192         utime $change->{when}, $change->{when}, "$config{srcdir}/$file";
193
194         return $page;
195 } #}}}
196
197 1