1#!/usr/bin/perl 2package IkiWiki::Plugin::recentchanges; 3 4use warnings; 5use strict; 6use IkiWiki 3.00; 7use Encode; 8use HTML::Entities; 9 10sub 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 => "sessioncgi", id => "recentchanges", call => \&sessioncgi); 17 # Load goto to fix up links from recentchanges 18 IkiWiki::loadplugin("goto"); 19 # ... and transient as somewhere to put our internal pages 20 IkiWiki::loadplugin("transient"); 21} 22 23sub getsetup () { 24 return 25 plugin => { 26 safe => 1, 27 rebuild => 1, 28 }, 29 recentchangespage => { 30 type => "string", 31 example => "recentchanges", 32 description => "name of the recentchanges page", 33 safe => 1, 34 rebuild => 1, 35 }, 36 recentchangesnum => { 37 type => "integer", 38 example => 100, 39 description => "number of changes to track", 40 safe => 1, 41 rebuild => 0, 42 }, 43} 44 45sub checkconfig () { 46 $config{recentchangespage}='recentchanges' unless defined $config{recentchangespage}; 47 $config{recentchangesnum}=100 unless defined $config{recentchangesnum}; 48} 49 50sub refresh ($) { 51 my %seen; 52 53 # add new changes 54 foreach my $change (IkiWiki::rcs_recentchanges($config{recentchangesnum})) { 55 $seen{store($change, $config{recentchangespage})}=1; 56 } 57 58 # delete old and excess changes 59 foreach my $page (keys %pagesources) { 60 if ($pagesources{$page} =~ /\._change$/ && ! $seen{$page}) { 61 unlink($IkiWiki::Plugin::transient::transientdir.'/'.$pagesources{$page}) || unlink($config{srcdir}.'/'.$pagesources{$page}); 62 } 63 } 64} 65 66sub sessioncgi ($$) { 67 my ($q, $session) = @_; 68 my $do = $q->param('do'); 69 my $rev = $q->param('rev'); 70 71 return unless $do eq 'revert' && $rev; 72 73 my @changes=$IkiWiki::hooks{rcs}{rcs_preprevert}{call}->($rev); 74 IkiWiki::check_canchange( 75 cgi => $q, 76 session => $session, 77 changes => \@changes, 78 ); 79 80 eval q{use CGI::FormBuilder}; 81 error($@) if $@; 82 my $form = CGI::FormBuilder->new( 83 name => "revert", 84 header => 0, 85 charset => "utf-8", 86 method => 'POST', 87 javascript => 0, 88 params => $q, 89 action => IkiWiki::cgiurl(), 90 stylesheet => 1, 91 template => { template('revert.tmpl') }, 92 fields => [qw{revertmessage do sid rev}], 93 ); 94 my $buttons=["Revert", "Cancel"]; 95 96 $form->field(name => "revertmessage", type => "text", size => 80); 97 $form->field(name => "sid", type => "hidden", value => $session->id, 98 force => 1); 99 $form->field(name => "do", type => "hidden", value => "revert", 100 force => 1); 101 102 IkiWiki::decode_form_utf8($form); 103 104 if ($form->submitted eq 'Revert' && $form->validate) { 105 IkiWiki::checksessionexpiry($q, $session); 106 my $message=sprintf(gettext("This reverts commit %s"), $rev); 107 if (defined $form->field('revertmessage') && 108 length $form->field('revertmessage')) { 109 $message=$form->field('revertmessage')."\n\n".$message; 110 } 111 my $r = $IkiWiki::hooks{rcs}{rcs_revert}{call}->($rev); 112 error $r if defined $r; 113 IkiWiki::disable_commit_hook(); 114 IkiWiki::rcs_commit_staged( 115 message => $message, 116 session => $session, 117 ); 118 IkiWiki::enable_commit_hook(); 119 120 require IkiWiki::Render; 121 IkiWiki::refresh(); 122 IkiWiki::saveindex(); 123 } 124 elsif ($form->submitted ne 'Cancel') { 125 $form->title(sprintf(gettext("confirm reversion of %s"), $rev)); 126 $form->tmpl_param(diff => encode_entities(scalar IkiWiki::rcs_diff($rev, 200))); 127 $form->field(name => "rev", type => "hidden", value => $rev, force => 1); 128 IkiWiki::showform($form, $buttons, $session, $q); 129 exit 0; 130 } 131 132 IkiWiki::redirect($q, urlto($config{recentchangespage})); 133 exit 0; 134} 135 136# Enable the recentchanges link. 137sub pagetemplate (@) { 138 my %params=@_; 139 my $template=$params{template}; 140 my $page=$params{page}; 141 142 if (defined $config{recentchangespage} && $config{rcs} && 143 $template->query(name => "recentchangesurl") && 144 $page ne $config{recentchangespage}) { 145 $template->param(recentchangesurl => urlto($config{recentchangespage}, $page)); 146 $template->param(have_actions => 1); 147 } 148} 149 150# Pages with extension _change have plain html markup, pass through. 151sub htmlize (@) { 152 my %params=@_; 153 return $params{content}; 154} 155 156sub store ($$$) { 157 my $change=shift; 158 159 my $page="$config{recentchangespage}/change_".titlepage($change->{rev}); 160 161 # Optimisation to avoid re-writing pages. Assumes commits never 162 # change (or that any changes are not important). 163 return $page if exists $pagesources{$page} && ! $config{rebuild}; 164 165 # Limit pages to first 10, and add links to the changed pages. 166 my $is_excess = exists $change->{pages}[10]; 167 delete @{$change->{pages}}[10 .. @{$change->{pages}}] if $is_excess; 168 my $has_diffurl=0; 169 $change->{pages} = [ 170 map { 171 if (length $config{cgiurl}) { 172 $_->{link} = "<a href=\"". 173 IkiWiki::cgiurl( 174 do => "goto", 175 page => $_->{page} 176 ). 177 "\" rel=\"nofollow\">". 178 pagetitle($_->{page}). 179 "</a>" 180 } 181 else { 182 $_->{link} = pagetitle($_->{page}); 183 } 184 if (defined $_->{diffurl} && length($_->{diffurl})) { 185 $has_diffurl=1; 186 } 187 188 $_; 189 } @{$change->{pages}} 190 ]; 191 push @{$change->{pages}}, { link => '...' } if $is_excess; 192 193 if (length $config{cgiurl} && 194 exists $IkiWiki::hooks{rcs}{rcs_preprevert} && 195 exists $IkiWiki::hooks{rcs}{rcs_revert}) { 196 $change->{reverturl} = IkiWiki::cgiurl( 197 do => "revert", 198 rev => $change->{rev} 199 ); 200 } 201 202 $change->{author}=$change->{user}; 203 my $oiduser=eval { IkiWiki::openiduser($change->{user}) }; 204 if (defined $oiduser) { 205 $change->{authorurl}=$change->{user}; 206 $change->{user}=defined $change->{nickname} ? $change->{nickname} : $oiduser; 207 } 208 elsif (length $config{cgiurl}) { 209 $change->{authorurl} = IkiWiki::cgiurl( 210 do => "goto", 211 page => IkiWiki::userpage($change->{author}), 212 ); 213 } 214 215 if (ref $change->{message}) { 216 foreach my $field (@{$change->{message}}) { 217 if (exists $field->{line}) { 218 # escape html 219 $field->{line} = encode_entities($field->{line}); 220 # escape links and preprocessor stuff 221 $field->{line} = encode_entities($field->{line}, '\[\]'); 222 } 223 } 224 } 225 226 # Fill out a template with the change info. 227 my $template=template("change.tmpl", blind_cache => 1); 228 $template->param( 229 %$change, 230 commitdate => displaytime($change->{when}, "%X %x"), 231 wikiname => $config{wikiname}, 232 ); 233 234 $template->param(has_diffurl => 1) if $has_diffurl; 235 236 $template->param(permalink => urlto($config{recentchangespage})."#change-".titlepage($change->{rev})) 237 if exists $config{url}; 238 239 IkiWiki::run_hooks(pagetemplate => sub { 240 shift->(page => $page, destpage => $page, 241 template => $template, rev => $change->{rev}); 242 }); 243 244 my $file=$page."._change"; 245 writefile($file, $IkiWiki::Plugin::transient::transientdir, $template->output); 246 utime $change->{when}, $change->{when}, $IkiWiki::Plugin::transient::transientdir.'/'.$file; 247 248 return $page; 249} 250 2511 252