File Coverage

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__