Merge branch 'master' into next
[ikiwiki] / IkiWiki / Plugin / recentchanges.pm
1 #!/usr/bin/perl
2 package IkiWiki::Plugin::recentchanges;
3
4 use warnings;
5 use strict;
6 use IkiWiki 3.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                 # If the page is internal (like a comment), see if it has a
99                 # permalink. Comments do.
100                 if (IkiWiki::isinternal($page) &&
101                     defined $pagestate{$page}{meta}{permalink}) {
102                         IkiWiki::redirect($cgi,
103                                           $pagestate{$page}{meta}{permalink});
104                         exit;
105                 }
106
107                 my $link=bestlink("", $page);
108                 if (! length $link) {
109                         print "Content-type: text/html\n\n";
110                         print IkiWiki::misctemplate(gettext(gettext("missing page")),
111                                 "<p>".
112                                 sprintf(gettext("The page %s does not exist."),
113                                         htmllink("", "", $page)).
114                                 "</p>");
115                 }
116                 else {
117                         IkiWiki::redirect($cgi, urlto($link, undef, 1));
118                 }
119
120                 exit;
121         }
122 }
123
124 sub store ($$$) {
125         my $change=shift;
126
127         my $page="$config{recentchangespage}/change_".titlepage($change->{rev});
128
129         # Optimisation to avoid re-writing pages. Assumes commits never
130         # change (or that any changes are not important).
131         return $page if exists $pagesources{$page} && ! $config{rebuild};
132
133         # Limit pages to first 10, and add links to the changed pages.
134         my $is_excess = exists $change->{pages}[10];
135         delete @{$change->{pages}}[10 .. @{$change->{pages}}] if $is_excess;
136         $change->{pages} = [
137                 map {
138                         if (length $config{cgiurl}) {
139                                 $_->{link} = "<a href=\"".
140                                         IkiWiki::cgiurl(
141                                                 do => "recentchanges_link",
142                                                 page => $_->{page}
143                                         ).
144                                         "\" rel=\"nofollow\">".
145                                         pagetitle($_->{page}).
146                                         "</a>"
147                         }
148                         else {
149                                 $_->{link} = pagetitle($_->{page});
150                         }
151                         $_->{baseurl}="$config{url}/" if length $config{url};
152
153                         $_;
154                 } @{$change->{pages}}
155         ];
156         push @{$change->{pages}}, { link => '...' } if $is_excess;
157
158         # See if the committer is an openid.
159         $change->{author}=$change->{user};
160         my $oiduser=eval { IkiWiki::openiduser($change->{user}) };
161         if (defined $oiduser) {
162                 $change->{authorurl}=$change->{user};
163                 $change->{user}=$oiduser;
164         }
165         elsif (length $config{cgiurl}) {
166                 $change->{authorurl} = IkiWiki::cgiurl(
167                         do => "recentchanges_link",
168                         page => (length $config{userdir} ? "$config{userdir}/" : "").$change->{author},
169                 );
170         }
171
172         if (ref $change->{message}) {
173                 foreach my $field (@{$change->{message}}) {
174                         if (exists $field->{line}) {
175                                 # escape html
176                                 $field->{line} = encode_entities($field->{line});
177                                 # escape links and preprocessor stuff
178                                 $field->{line} = encode_entities($field->{line}, '\[\]');
179                         }
180                 }
181         }
182
183         # Fill out a template with the change info.
184         my $template=template("change.tmpl", blind_cache => 1);
185         $template->param(
186                 %$change,
187                 commitdate => displaytime($change->{when}, "%X %x"),
188                 wikiname => $config{wikiname},
189         );
190         
191         $template->param(permalink => "$config{url}/$config{recentchangespage}/#change-".titlepage($change->{rev}))
192                 if exists $config{url};
193         
194         IkiWiki::run_hooks(pagetemplate => sub {
195                 shift->(page => $page, destpage => $page,
196                         template => $template, rev => $change->{rev});
197         });
198
199         my $file=$page."._change";
200         writefile($file, $config{srcdir}, $template->output);
201         utime $change->{when}, $change->{when}, "$config{srcdir}/$file";
202
203         return $page;
204 }
205
206 1