blib/lib/Labyrinth/Plugin/Wiki.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 18 | 18 | 100.0 |
branch | n/a | ||
condition | n/a | ||
subroutine | 6 | 6 | 100.0 |
pod | n/a | ||
total | 24 | 24 | 100.0 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Labyrinth::Plugin::Wiki; | ||||||
2 | |||||||
3 | 5 | 5 | 85813 | use warnings; | |||
5 | 9 | ||||||
5 | 197 | ||||||
4 | 5 | 5 | 19 | use strict; | |||
5 | 6 | ||||||
5 | 151 | ||||||
5 | |||||||
6 | 5 | 5 | 18 | use vars qw($VERSION); | |||
5 | 9 | ||||||
5 | 333 | ||||||
7 | $VERSION = '1.06'; | ||||||
8 | |||||||
9 | =head1 NAME | ||||||
10 | |||||||
11 | Labyrinth::Plugin::Wiki - Wiki handler for the Labyrinth framework. | ||||||
12 | |||||||
13 | =head1 DESCRIPTION | ||||||
14 | |||||||
15 | Contains all the wiki handling functionality for Labyrinth. | ||||||
16 | |||||||
17 | =cut | ||||||
18 | |||||||
19 | # ------------------------------------- | ||||||
20 | # Library Modules | ||||||
21 | |||||||
22 | 5 | 5 | 3401 | use Algorithm::Diff; | |||
5 | 21908 | ||||||
5 | 246 | ||||||
23 | 5 | 5 | 2498 | use VCS::Lite; | |||
5 | 23517 | ||||||
5 | 157 | ||||||
24 | |||||||
25 | 5 | 5 | 36 | use base qw(Labyrinth::Plugin::Base); | |||
5 | 8 | ||||||
5 | 5289 | ||||||
26 | |||||||
27 | use Labyrinth::Audit; | ||||||
28 | use Labyrinth::DBUtils; | ||||||
29 | use Labyrinth::DTUtils; | ||||||
30 | use Labyrinth::MLUtils; | ||||||
31 | use Labyrinth::Support; | ||||||
32 | use Labyrinth::Variables; | ||||||
33 | |||||||
34 | use Labyrinth::Plugin::Wiki::Text; | ||||||
35 | |||||||
36 | # ------------------------------------- | ||||||
37 | # Variables | ||||||
38 | |||||||
39 | # type: 0 = optional, 1 = mandatory | ||||||
40 | # html: 0 = none, 1 = text, 2 = textarea | ||||||
41 | |||||||
42 | my %fields = ( | ||||||
43 | pagename => { type => 1, html => 1 }, | ||||||
44 | comment => { type => 0, html => 1 }, | ||||||
45 | content => { type => 1, html => 2 }, | ||||||
46 | ); | ||||||
47 | |||||||
48 | my (@mandatory,@allfields); | ||||||
49 | for(keys %fields) { | ||||||
50 | push @mandatory, $_ if($fields{$_}->{type}); | ||||||
51 | push @allfields, $_; | ||||||
52 | } | ||||||
53 | |||||||
54 | my $LEVEL = ADMIN; | ||||||
55 | |||||||
56 | my $wikitext = Labyrinth::Plugin::Wiki::Text->new(); | ||||||
57 | |||||||
58 | my %valid_limits = map {$_ => 1} qw(10 20 50 100 200 500); | ||||||
59 | |||||||
60 | # ------------------------------------- | ||||||
61 | # Public Methods | ||||||
62 | |||||||
63 | =head1 PUBLIC INTERFACE METHODS | ||||||
64 | |||||||
65 | =over 4 | ||||||
66 | |||||||
67 | =item Page | ||||||
68 | |||||||
69 | Checks for alternative page references and redirects if necessary. | ||||||
70 | |||||||
71 | =item Edit | ||||||
72 | |||||||
73 | Provides the edit page for the given page. | ||||||
74 | |||||||
75 | =item View | ||||||
76 | |||||||
77 | Provides the view page for the given page. | ||||||
78 | |||||||
79 | =item Save | ||||||
80 | |||||||
81 | Saves the given page. | ||||||
82 | |||||||
83 | =item History | ||||||
84 | |||||||
85 | Provides the history of edits for the given page. | ||||||
86 | |||||||
87 | =item Diff | ||||||
88 | |||||||
89 | Provides the differences between to versions of the given page. | ||||||
90 | |||||||
91 | =item Search | ||||||
92 | |||||||
93 | Searches through the current set of pages for the given text string. | ||||||
94 | |||||||
95 | =item Recent | ||||||
96 | |||||||
97 | Lists the most recent changes. | ||||||
98 | |||||||
99 | =back | ||||||
100 | |||||||
101 | =cut | ||||||
102 | |||||||
103 | sub Page { | ||||||
104 | $cgiparams{pagename} ||= 'HomePage'; | ||||||
105 | |||||||
106 | # check for special pages first | ||||||
107 | return _redirect($cgiparams{pagename}) | ||||||
108 | if(defined $cgiparams{pagename} && | ||||||
109 | $cgiparams{pagename} =~ /^People|Login|Search|RecentChanges$/); | ||||||
110 | |||||||
111 | # now deal with the page | ||||||
112 | return unless CheckPage(); | ||||||
113 | if($tvars{wikihash}) { | ||||||
114 | #REDIRECT [[PAGE]] | ||||||
115 | return _redirect($1) | ||||||
116 | if($tvars{wikihash}->{content} =~ /^\#REDIRECT\s+\[\[\s*(.*)\s*\]\]/); | ||||||
117 | |||||||
118 | ($tvars{wikihash}{title},$tvars{wikihash}{html}) = $wikitext->Render($tvars{wikihash}); | ||||||
119 | $tvars{wikihash}{showmode} = 1; | ||||||
120 | } else { | ||||||
121 | SetCommand('wiki-edit'); | ||||||
122 | } | ||||||
123 | } | ||||||
124 | |||||||
125 | sub Edit { | ||||||
126 | return unless AccessUser(USER); | ||||||
127 | return if RestrictedPage(); | ||||||
128 | return unless CheckPage(); | ||||||
129 | return if LockedPage(); | ||||||
130 | |||||||
131 | if(!$tvars{wikihash}{version}) { | ||||||
132 | $tvars{wikihash}{pagename} = $cgiparams{pagename}; | ||||||
133 | $tvars{wikihash}{title} = $cgiparams{pagename}; | ||||||
134 | } | ||||||
135 | |||||||
136 | $tvars{wikihash}{editmode} = 1; | ||||||
137 | } | ||||||
138 | |||||||
139 | sub View { | ||||||
140 | return unless AccessUser(USER); | ||||||
141 | return if RestrictedPage(); | ||||||
142 | return unless CheckPage(1); | ||||||
143 | return if LockedPage(); | ||||||
144 | |||||||
145 | for(keys %fields) { | ||||||
146 | if($fields{$_}->{html} == 1) { $cgiparams{$_} = CleanHTML($cgiparams{$_}) } | ||||||
147 | elsif($fields{$_}->{html} == 2) { $cgiparams{$_} = CleanTags($cgiparams{$_}) } | ||||||
148 | elsif($fields{$_}->{html} == 3) { $cgiparams{$_} = CleanLink($cgiparams{$_}) } | ||||||
149 | |||||||
150 | $tvars{wikihash}->{$_} = $cgiparams{$_}; | ||||||
151 | } | ||||||
152 | |||||||
153 | ($tvars{wikihash}{title},$tvars{wikihash}{html}) = $wikitext->Render($tvars{wikihash}); | ||||||
154 | |||||||
155 | $tvars{wikihash}{editmode} = 1; | ||||||
156 | } | ||||||
157 | |||||||
158 | sub Save { | ||||||
159 | return unless AccessUser(USER); | ||||||
160 | return if RestrictedPage(); | ||||||
161 | return unless CheckPage(); | ||||||
162 | return if LockedPage(); | ||||||
163 | |||||||
164 | for(keys %fields) { | ||||||
165 | if($fields{$_}->{html} == 1) { $cgiparams{$_} = CleanHTML($cgiparams{$_}) } | ||||||
166 | elsif($fields{$_}->{html} == 2) { $cgiparams{$_} = CleanTags($cgiparams{$_}) } | ||||||
167 | elsif($fields{$_}->{html} == 3) { $cgiparams{$_} = CleanLink($cgiparams{$_}) } | ||||||
168 | |||||||
169 | $tvars{wikihash}->{$_} = $cgiparams{$_}; | ||||||
170 | } | ||||||
171 | |||||||
172 | return if FieldCheck(\@allfields,\@mandatory); | ||||||
173 | |||||||
174 | $tvars{wikihash}->{'version'}++; | ||||||
175 | |||||||
176 | # normalise line endings | ||||||
177 | $tvars{wikihash}->{'content'} =~ s/\r\n/\n/g; # Win32 | ||||||
178 | $tvars{wikihash}->{'content'} =~ s/\r/\n/g; # Mac | ||||||
179 | |||||||
180 | $dbi->DoQuery('SaveWikiPage', | ||||||
181 | $tvars{wikihash}->{'pagename'}, | ||||||
182 | $tvars{wikihash}->{'version'}, | ||||||
183 | $tvars{user}->{'userid'}, | ||||||
184 | $tvars{wikihash}->{'comment'}, | ||||||
185 | $tvars{wikihash}->{'content'}, | ||||||
186 | formatDate(0) | ||||||
187 | ); | ||||||
188 | |||||||
189 | my @rows = $dbi->GetQuery('hash','GetWikiIndex',$tvars{wikihash}->{'pagename'}); | ||||||
190 | if(@rows) { | ||||||
191 | $dbi->DoQuery('UpdateWikiIndex',$tvars{wikihash}->{'version'},$tvars{wikihash}->{'pagename'}); | ||||||
192 | } else { | ||||||
193 | $dbi->DoQuery('InsertWikiIndex',$tvars{wikihash}->{'version'},$tvars{wikihash}->{'pagename'}); | ||||||
194 | } | ||||||
195 | } | ||||||
196 | |||||||
197 | sub History { | ||||||
198 | return if RestrictedPage(); | ||||||
199 | return unless CheckPage(); | ||||||
200 | return if LockedPage(); | ||||||
201 | |||||||
202 | my @rows = $dbi->GetQuery('hash','GetWikiHistory',$cgiparams{'pagename'}); | ||||||
203 | for (@rows) { | ||||||
204 | $_->{postdate} = formatDate(20,$_->{createdate}); | ||||||
205 | } | ||||||
206 | $tvars{wikihash}{history} = \@rows; | ||||||
207 | $tvars{wikihash}{histmode} = 1; | ||||||
208 | } | ||||||
209 | |||||||
210 | sub Diff { | ||||||
211 | return if RestrictedPage(); | ||||||
212 | return unless CheckPage(); | ||||||
213 | |||||||
214 | if(!$cgiparams{diff1} || !$cgiparams{diff2}) { | ||||||
215 | # $tvars{errcode} = 'MESSAGE'; | ||||||
216 | $tvars{errmess} = 'Need to supply two valid versions to check the differences!'; | ||||||
217 | return; | ||||||
218 | } | ||||||
219 | |||||||
220 | my $diff1 = GetPage($cgiparams{pagename},$cgiparams{diff1}); | ||||||
221 | my $diff2 = GetPage($cgiparams{pagename},$cgiparams{diff2}); | ||||||
222 | |||||||
223 | if(!$diff1 || !$diff2) { | ||||||
224 | # $tvars{errcode} = 'MESSAGE'; | ||||||
225 | $tvars{errmess} = 'Need to supply two valid versions to check the differences!'; | ||||||
226 | return; | ||||||
227 | } | ||||||
228 | |||||||
229 | $tvars{wikihash}{diff1} = $diff1; | ||||||
230 | $tvars{wikihash}{diff2} = $diff2; | ||||||
231 | |||||||
232 | $tvars{wikihash}{diff0} = _differences($diff1,$diff2); | ||||||
233 | } | ||||||
234 | |||||||
235 | sub Search { | ||||||
236 | if(!$cgiparams{data}) { | ||||||
237 | # $tvars{errcode} = 'MESSAGE'; | ||||||
238 | $tvars{errmess} = 'Need to supply a search term!'; | ||||||
239 | return; | ||||||
240 | } | ||||||
241 | |||||||
242 | $tvars{wikihash}{key} = $cgiparams{data}; | ||||||
243 | $tvars{wikihash}{checked} = $cgiparams{allversions}; | ||||||
244 | my $key = $cgiparams{allversions} ? 'WikiSearchFull' : 'WikiSearch'; | ||||||
245 | my @rows = $dbi->GetQuery('hash',$key,'%'.$cgiparams{data}.'%'); | ||||||
246 | for (@rows) { | ||||||
247 | $_->{postdate} = formatDate(20,$_->{createdate}); | ||||||
248 | } | ||||||
249 | $tvars{wikihash}{results} = \@rows if(@rows); | ||||||
250 | } | ||||||
251 | |||||||
252 | sub Recent { | ||||||
253 | my $limit = $cgiparams{entries} || 10; | ||||||
254 | $limit = 10 unless($valid_limits{$limit}); | ||||||
255 | my @rows = $dbi->GetQuery('hash','WikiRecentChanges',{limit => "LIMIT $limit"}); | ||||||
256 | for (@rows) { | ||||||
257 | $_->{postdate} = formatDate(20,$_->{createdate}); | ||||||
258 | } | ||||||
259 | $tvars{wikihash}{recent} = \@rows; | ||||||
260 | $tvars{wikihash}{entries} = $limit; | ||||||
261 | } | ||||||
262 | |||||||
263 | # ------------------------------------- | ||||||
264 | # Private Methods | ||||||
265 | |||||||
266 | sub _redirect { | ||||||
267 | my $page = shift; | ||||||
268 | |||||||
269 | if($page eq 'People') { $tvars{command} = 'user-list'; } | ||||||
270 | elsif($page eq 'Login') { $tvars{command} = 'user-login'; } | ||||||
271 | elsif($page eq 'Search') { $tvars{command} = 'wiki-search'; } | ||||||
272 | elsif($page eq 'RecentChanges') { $tvars{command} = 'wiki-recent'; } | ||||||
273 | else { $tvars{command} = 'wiki-page'; $cgiparams{pagename} = $page; } | ||||||
274 | |||||||
275 | $tvars{errcode} = 'NEXT'; | ||||||
276 | return; | ||||||
277 | } | ||||||
278 | |||||||
279 | # Subroutine code based on CGI::Wiki::Plugin::Diff by Ivor Williams. | ||||||
280 | sub _differences { | ||||||
281 | my ($d1,$d2) = @_; | ||||||
282 | |||||||
283 | my $el1 = VCS::Lite->new($d1->{version},undef,_content_escape($d1->{content})); | ||||||
284 | my $el2 = VCS::Lite->new($d2->{version},undef,_content_escape($d2->{content})); | ||||||
285 | my $dlt = $el2->delta($el1) or return; | ||||||
286 | |||||||
287 | my @out; | ||||||
288 | |||||||
289 | for ($dlt->hunks) { | ||||||
290 | my ($lin1,$lin2,$lin3,$lin4,$out1,$out2); | ||||||
291 | for (@$_) { | ||||||
292 | my ($ind,$line,$text) = @$_; | ||||||
293 | # LogDebug("hunk:[$ind][$line][$text]"); | ||||||
294 | if ($ind =~ /^\+/) { | ||||||
295 | if($lin1) { $lin3 = $line } | ||||||
296 | else { $lin1 = $line } | ||||||
297 | $out1 .= $text; | ||||||
298 | $out1 .= ' ' if($text =~ /^$/); |
||||||
299 | } | ||||||
300 | if ($ind =~ /^\-/) { | ||||||
301 | if($lin2) { $lin4 = $line } | ||||||
302 | else { $lin2 = $line } | ||||||
303 | $out2 .= $text; | ||||||
304 | $out2 .= ' ' if($text =~ /^$/); |
||||||
305 | } | ||||||
306 | } | ||||||
307 | |||||||
308 | my $left = $lin3 ? "== Line $lin1-$lin3 ==" : $lin1 ? "== Line $lin1 ==" : "== END OF FILE =="; | ||||||
309 | my $right = $lin4 ? "== Line $lin2-$lin4 ==" : $lin2 ? "== Line $lin2 ==" : "== END OF FILE =="; | ||||||
310 | |||||||
311 | # push @out,{ left => $left, right => $right }; | ||||||
312 | my ($text1,$text2) = _intradiff($out1,$out2); | ||||||
313 | push @out,{left => "$left $text1", right => "$right $text2"}; |
||||||
314 | } | ||||||
315 | |||||||
316 | return \@out; | ||||||
317 | } | ||||||
318 | |||||||
319 | sub _content_escape { | ||||||
320 | my $txt = shift; | ||||||
321 | my @txt = split(/\n/,$txt); | ||||||
322 | return \@txt; | ||||||
323 | } | ||||||
324 | |||||||
325 | sub _intradiff { | ||||||
326 | my ($str1,$str2) = @_; | ||||||
327 | # LogDebug("diff:[$str1][$str2]"); | ||||||
328 | |||||||
329 | return (qq{$str1},'') unless $str2; | ||||||
330 | return ('',qq{$str2}) unless $str1; | ||||||
331 | |||||||
332 | my $re_wordmatcher = qr( | ||||||
333 | &.+?; #HTML special characters e.g. < | ||||||
334 | | #Line breaks |
||||||
335 | |\w+\s* #Word with trailing spaces | ||||||
336 | |. #Any other single character | ||||||
337 | )xsi; | ||||||
338 | |||||||
339 | my @diffs = Algorithm::Diff::sdiff( | ||||||
340 | [$str1 =~ /$re_wordmatcher/sg], | ||||||
341 | [$str2 =~ /$re_wordmatcher/sg], sub {_get_token(@_)}); | ||||||
342 | my $out1 = ''; | ||||||
343 | my $out2 = ''; | ||||||
344 | my ($mode1,$mode2); | ||||||
345 | |||||||
346 | for (@diffs) { | ||||||
347 | my ($ind,$c1,$c2) = @$_; | ||||||
348 | |||||||
349 | my $newmode1 = $ind =~ /[c\-]/; | ||||||
350 | my $newmode2 = $ind =~ /[c+]/; | ||||||
351 | $out1 .= '' if $newmode1 && !$mode1; | ||||||
352 | $out2 .= '' if $newmode2 && !$mode2; | ||||||
353 | $out1 .= '' if !$newmode1 && $mode1; | ||||||
354 | $out2 .= '' if !$newmode2 && $mode2; | ||||||
355 | ($mode1,$mode2) = ($newmode1,$newmode2); | ||||||
356 | $out1 .= $c1; | ||||||
357 | $out2 .= $c2; | ||||||
358 | } | ||||||
359 | $out1 .= '' if $mode1; | ||||||
360 | $out2 .= '' if $mode2; | ||||||
361 | |||||||
362 | ($out1,$out2); | ||||||
363 | } | ||||||
364 | |||||||
365 | sub _get_token { | ||||||
366 | my $str = shift; | ||||||
367 | $str =~ /^(\S*)\s*$/; # Match all but trailing whitespace | ||||||
368 | $1 || $str; | ||||||
369 | } | ||||||
370 | |||||||
371 | # ------------------------------------- | ||||||
372 | # Admin Methods | ||||||
373 | |||||||
374 | =head1 ADMIN INTERFACE METHODS | ||||||
375 | |||||||
376 | =over 4 | ||||||
377 | |||||||
378 | =item Admin | ||||||
379 | |||||||
380 | Checks whether user has admin priviledges. | ||||||
381 | |||||||
382 | =item Rollback | ||||||
383 | |||||||
384 | Rollbacks the given page by one version. | ||||||
385 | |||||||
386 | =item Delete | ||||||
387 | |||||||
388 | Deletes the given page. | ||||||
389 | |||||||
390 | =item Locks | ||||||
391 | |||||||
392 | Locks or unlocks the given page. | ||||||
393 | |||||||
394 | =back | ||||||
395 | |||||||
396 | =cut | ||||||
397 | |||||||
398 | sub Admin { | ||||||
399 | return unless AccessUser(ADMIN); | ||||||
400 | } | ||||||
401 | |||||||
402 | sub Rollback { | ||||||
403 | return unless AccessUser(ADMIN); | ||||||
404 | LogDebug("Rollback: 1.version=$tvars{wikihash}{version}"); | ||||||
405 | return unless CheckPage(); | ||||||
406 | LogDebug("Rollback: 2.version=$tvars{wikihash}{version}"); | ||||||
407 | |||||||
408 | my $version = $tvars{wikihash}{version} - 1; | ||||||
409 | $dbi->DoQuery('DeleteWikiPage' ,$version,$tvars{wikihash}->{'pagename'}); | ||||||
410 | $dbi->DoQuery('UpdateWikiIndex',$version,$tvars{wikihash}->{'pagename'}); | ||||||
411 | } | ||||||
412 | |||||||
413 | sub Delete { | ||||||
414 | return unless AccessUser(ADMIN); | ||||||
415 | return unless CheckPage(); | ||||||
416 | |||||||
417 | my $version = $tvars{wikihash}{version} - 1; | ||||||
418 | $dbi->DoQuery('DeleteWikiPages',$tvars{wikihash}->{'pagename'}); | ||||||
419 | $dbi->DoQuery('DeleteWikiIndex',$tvars{wikihash}->{'pagename'}); | ||||||
420 | } | ||||||
421 | |||||||
422 | sub Locks { | ||||||
423 | return unless AccessUser(ADMIN); | ||||||
424 | return unless CheckPage(); | ||||||
425 | |||||||
426 | my $lock = $tvars{wikihash}{locked} ? 0 : 1; | ||||||
427 | $dbi->DoQuery('SetWikiLock',$lock,$tvars{wikihash}->{'pagename'}); | ||||||
428 | } | ||||||
429 | |||||||
430 | =head1 INTERNAL METHODS | ||||||
431 | |||||||
432 | =over 4 | ||||||
433 | |||||||
434 | =item CheckPage | ||||||
435 | |||||||
436 | Checks the page exists. | ||||||
437 | |||||||
438 | =item GetPage | ||||||
439 | |||||||
440 | Retrieves the page content for a given version or current page. | ||||||
441 | |||||||
442 | =item RestrictedPage | ||||||
443 | |||||||
444 | Checks whether the page is restricted. | ||||||
445 | |||||||
446 | =item LockedPage | ||||||
447 | |||||||
448 | Checks whether the page is locked. | ||||||
449 | |||||||
450 | =back | ||||||
451 | |||||||
452 | =cut | ||||||
453 | |||||||
454 | sub CheckPage { | ||||||
455 | my $passthru = shift || 0; | ||||||
456 | |||||||
457 | if($cgiparams{'pagename'}) { | ||||||
458 | return 1 if($passthru); | ||||||
459 | |||||||
460 | # retrieve the last known version | ||||||
461 | $tvars{wikihash} = GetPage($cgiparams{'pagename'},$cgiparams{'version'}); | ||||||
462 | return 1; | ||||||
463 | } | ||||||
464 | |||||||
465 | $tvars{errcode} = 'ERROR'; | ||||||
466 | return 0; | ||||||
467 | } | ||||||
468 | |||||||
469 | sub GetPage { | ||||||
470 | my ($p,$v) = @_; | ||||||
471 | return unless($p); | ||||||
472 | |||||||
473 | my @rows; | ||||||
474 | if($v) { | ||||||
475 | @rows = $dbi->GetQuery('hash','GetWikiPageVersion',$p,$v); | ||||||
476 | } else { | ||||||
477 | @rows = $dbi->GetQuery('hash','GetWikiPage',$p); | ||||||
478 | } | ||||||
479 | |||||||
480 | return $rows[0] if(@rows); | ||||||
481 | return; | ||||||
482 | } | ||||||
483 | |||||||
484 | sub RestrictedPage { | ||||||
485 | if($cgiparams{pagename} =~ /^People|Login|Search|RecentChanges$/) { | ||||||
486 | $tvars{errcode} = 'MESSAGE'; | ||||||
487 | $tvars{errmess} = 'This page is restricted.'; | ||||||
488 | return 1; | ||||||
489 | } | ||||||
490 | |||||||
491 | return 0; | ||||||
492 | } | ||||||
493 | |||||||
494 | sub LockedPage { | ||||||
495 | if( $tvars{wikihash}->{locked} ) { | ||||||
496 | $tvars{errcode} = 'MESSAGE'; | ||||||
497 | $tvars{errmess} = 'This page is locked.'; | ||||||
498 | return 1; | ||||||
499 | } | ||||||
500 | |||||||
501 | return 0; | ||||||
502 | } | ||||||
503 | |||||||
504 | 1; | ||||||
505 | |||||||
506 | __END__ |