File Coverage

blib/lib/Quiki.pm
Criterion Covered Total %
statement 39 218 17.8
branch 0 102 0.0
condition 0 95 0.0
subroutine 13 16 81.2
pod 2 2 100.0
total 54 433 12.4


line stmt bran cond sub pod time code
1             package Quiki;
2              
3 1     1   25898 use Data::Dumper;
  1         11604  
  1         86  
4              
5 1     1   677 use Quiki::Formatter::HTML;
  1         4  
  1         40  
6 1     1   529 use Quiki::Meta;
  1         5  
  1         28  
7 1     1   534 use Quiki::Users;
  1         3  
  1         34  
8 1     1   681 use Quiki::Pages;
  1         5  
  1         28  
9 1     1   525 use Quiki::Attachments;
  1         4  
  1         34  
10              
11 1     1   6 use CGI qw/:standard *div/;
  1         2  
  1         6  
12 1     1   4676 use CGI::Session;
  1         6195  
  1         7  
13 1     1   1300 use HTML::Template::Pro;
  1         5421  
  1         49  
14              
15 1     1   9 use File::Slurp 'slurp';
  1         2  
  1         44  
16 1     1   1098 use Pod::Html;
  1         102685  
  1         102  
17              
18 1     1   10 use warnings;
  1         2  
  1         36  
19 1     1   5 use strict;
  1         2  
  1         3167  
20              
21             =head1 NAME
22              
23             Quiki - A lightweight Wiki in Perl
24              
25             =head1 VERSION
26              
27             Version 0.12
28              
29             =cut
30              
31             our $VERSION = '0.12';
32              
33              
34             =head1 SYNOPSIS
35              
36             use Quiki;
37              
38             my %conf = ( name => 'MyQuiki' );
39             Quiki->new(%conf)->run;
40              
41             =head1 EXPORT FUNCTIONS
42              
43             =head2 new
44              
45             Creates a new Quiki object.
46              
47             =head2 run
48              
49             Runs the Quiki.
50              
51             =cut
52              
53             sub new {
54 0     0 1   my ($class, %args) = @_;
55 0           my $self;
56              
57             # XXX
58 0           my %conf = (
59             name => 'defaultName',
60             index => 'index', # index node
61             );
62 0           $self = {%conf, %args};
63              
64 0           $self->{SCRIPT_NAME} = $ENV{SCRIPT_NAME};
65 0           $self->{SERVER_NAME} = $ENV{SERVER_NAME};
66              
67 0           $self->{DOCROOT} = $ENV{SCRIPT_NAME};
68 0           $self->{DOCROOT} =~ s!/[^/]+$!/!;
69              
70 0           $self->{QUIKI_ID} = $self->{name} . '_' . slurp 'data/quiki_id';
71 0           return bless $self, $class;
72             }
73              
74             ## Sets cookie msg value
75             sub _msg {
76 0     0     my ($self, $msg) = @_;
77 0           $self->{session}->param(msg => $msg);
78             }
79              
80             sub run {
81 0     0 1   my $self = shift;
82              
83 0   0       $self->{sid} = cookie($self->{QUIKI_ID}) || undef;
84 0           $self->{session} = new CGI::Session(undef, $self->{sid}, undef);
85              
86             # XXX
87 0   0       my $node = param('node') || $self->{index};
88 0   0       my $action = param('action') || '';
89              
90             # XXX -- temos de proteger mais coisas, possivelmente
91             # E era giro termos filename e displayname
92 0           $node =~ s/\s/_/g;
93              
94 0           $self->{meta} = Quiki::Meta::get($node);
95 0           $self->{node} = $node;
96              
97 0 0         if ($action eq 'update_perms') {
98 0           my $username = param('edit_user');
99 0 0         if ($username eq "admin") {
100 0           $self->_msg("Admin account can not be changed.");
101             } else {
102 0 0         if (param("admin_action") eq "Delete") {
103 0           Quiki::Users->delete($username);
104 0           $self->_msg("User '$username' deleted.");
105             }
106 0 0         if (param("admin_action") eq "Save") {
107 0           Quiki::Users->update($username, perm_group => param("perms"));
108 0           $self->_msg("Permission rights changed for user '$username'.");
109             }
110             }
111 0           $action = 'admin_page';
112             }
113              
114 0 0 0       if ($action eq 'save_profile' && param('submit') =~ /^Save/) {
115 0 0 0       if (param("new_password1") && (param("new_password1") ne param("new_password2"))) {
116 0           $self->_msg("Passwords do not match. Try again!");
117 0           $action = 'profile_page';
118             }
119             else {
120 0           my %data;
121 0 0         $data{password} = param("new_password1") if param("new_password1");
122 0 0         $data{email} = param("email") if param("email");
123 0           Quiki::Users->update($self->{session}->param("username"), %data);
124 0           $self->_msg("Profile Saved.");
125             }
126             }
127              
128             # XXX
129 0 0         if ($action eq "upload") {
130 0           my $i = 1;
131 0   0       while (param("filename$i") && param("name$i")) {
132             # XXX - Move all this shit to Quiki::Attachments
133 0           my $id = param("name$i");
134 0           my $path = "data/attach/$node";
135 0 0 0       -f $path or (mkdir $path and chown 0777, $path);
136 0           Quiki::Attachments->save_attach("filename$i", "$path/$id");
137 0 0         if (param("description$i")) {
138 0 0         open OUT, ">", "$path/_desc_$id" or die "Can't create out file: $!";
139 0           print OUT param("description$i");
140 0           close OUT;
141             }
142 0           ++$i;
143             }
144 0           --$i;
145 0           $self->_msg("$i file(s) uploaded.");
146             }
147              
148             # XXX
149 0 0 0       if ($action eq 'register' && param('submit_opt') eq "Register") {
150 0   0       my $username = param('username') || '';
151 0   0       my $email = param('email') || '';
152 0 0 0       if ($username and $email and $email =~ m/\@/) { # XXX -- fix regexp :D
      0        
153 0 0         if (Quiki::Users->exists($username)) {
154 0           $self->_msg("User name already in use. Please try again!");
155 0           $action = 'register_page';
156             }
157             else {
158 0           Quiki::Users->create($self, $username, $email);
159 0           $self->_msg("You are registered! You should receive an e-mail with your password soon.");
160             }
161             }
162             else {
163 0           $self->_msg("Sign up failed! Perhaps you forgot to fill in the form?");
164 0           $action = 'register_page';
165             }
166             }
167              
168             # XXX
169 0 0         if ($action eq 'login') {
170 0   0       my $username = param('username') || '';
171 0   0       my $password = param('password') || '';
172 0 0 0       if ($username and $password and Quiki::Users->auth($username,$password)) {
      0        
173 0 0 0       $self->{session}->param('authenticated',1) and
174             $self->{session}->param('username',$username) and
175             $self->_msg("Login successfull! Welcome $username!");
176             }
177             else {
178 0           $self->_msg("Login failed!");
179             }
180             }
181              
182             # XXX
183 0 0         if ($action eq 'logout') {
184 0 0 0       $self->{session}->param('authenticated') and
      0        
185             $self->{session}->param('authenticated',0) and
186             $self->{session}->param('username','') and
187             $self->_msg('Logout successfull!');
188             }
189              
190             # XXX
191 0 0 0       ($action eq 'create') and (-f "data/content/$node") and ($action = '');
192 0 0 0       if( ($action eq 'create') or !-f "data/content/$node") {
193 0           Quiki::Pages->check_in($self, $node, "Edit me!");
194 0           $self->_msg("New node \"$node\" created.");
195             }
196              
197 0 0 0       if ($action eq "edit" && Quiki::Pages->locked($node, $self->{sid})) {
198 0           $action = "";
199 0           $self->_msg("Sorry but someone else is currently editing this node!");
200             } else {
201 0           Quiki::Pages->lock($node, $self->{sid});
202             }
203              
204             # XXX
205 0 0 0       if ($action eq 'save' && param("submit_opt") eq "Save") {
206 0 0         if (Quiki::Pages->locked_for_user($node, $self->{sid})) {
207 0 0         my $text = defined(param('text')) ? param('text') : '';
208 0           Quiki::Pages->check_in($self, $node, $text);
209 0           Quiki::Pages->unlock($node);
210 0           $self->_msg("Content for \"$node\" updated.");
211             } else {
212 0           $self->_msg("You took too much time! You lost your lock.");
213             }
214             }
215              
216             # XXX
217 0           my $content;
218 0   0       $self->{rev} = param('rev') || $self->{meta}{rev};
219             # sanity check revision number
220 0 0 0       if (!($self->{rev} =~ m/\d+/) || $self->{rev}<0 || $self->{rev}>$self->{meta}{rev}) {
      0        
221 0           $self->{rev} = $self->{meta}{rev};
222 0           $self->_msg('Revision requested not found.');
223             }
224 0 0         if ($action eq 'rollback') {
225 0           $content = Quiki::Pages->check_out($self,$node,$self->{rev});
226 0           Quiki::Pages->check_in($self, $node, $content);
227 0           $self->{rev} = $self->{meta}{rev};
228             }
229             else {
230 0           $content = Quiki::Pages->check_out($self,$node,$self->{rev});
231             }
232              
233              
234 0           my $cookie = cookie($self->{QUIKI_ID} => $self->{session}->id);
235 0           print header(-charset=>'UTF-8',-cookie=>$cookie);
236              
237             # Calculate breadcumbs
238 0           my @trace;
239 0 0         @trace = @{$self->{session}->param('trace')} if $self->{session}->param('trace');
  0            
240 0 0         push @trace, $node unless @trace;
241 0 0         if ($trace[-1] ne $node) {
242 0           push @trace, $node;
243 0 0         @trace > 5 and shift @trace;
244             }
245 0           $self->{session}->param('trace',\@trace);
246 0           my $breadcumbs = join(' » ', map { a({-href=>"$self->{SCRIPT_NAME}?node=$_"}, $_); } @trace);
  0            
247              
248              
249 0           my $preview = 0;
250 0 0 0       if ($action eq 'save' && param("submit_opt") eq "Preview") {
251 0           $preview = 1;
252 0           $action = 'edit';
253             }
254 0 0 0       if ($action eq 'save' && param("submit_opt") eq "Cancel") {
255 0           Quiki::Pages->unlock($node);
256             }
257              
258 0 0         my $username = ($self->{session}->param('authenticated')?
259             $self->{session}->param('username'):"guest");
260             ## XXX - Later, join these functions to query database only once
261 0           my $email = Quiki::Users->email($username);
262 0           my $urole = Quiki::Users->role($username);
263 0   0       my $theme = $self->{theme} || 'default';
264              
265 0           my $template = HTML::Template::Pro->new(filename => "themes/$theme/wrapper.tmpl",
266             global_vars => 1);
267 0 0 0       $template->param(WIKINAME => $self->{name},
      0        
268             USERNAME => $username,
269             WIKINODE => $node,
270             WIKISCRIPT => $self->{SCRIPT_NAME},
271             MAINNODE => $self->{index},
272             ACTION => $action,
273             AUTHENTICATED => $self->{session}->param('authenticated'),
274             LAST_REV => (($self->{rev} || 0) == ($self->{meta}{rev} || 0)),
275             REV => $self->{rev},
276             BREADCUMBS => $breadcumbs,
277             SERVERNAME => $self->{SERVER_NAME},
278             DOCROOT => $self->{DOCROOT},
279             OPENSITE => defined($self->{opensite}) ? $self->{opensite} : 1,
280             USER_ROLE => $urole,
281             PREVIEW => $preview,
282             );
283              
284 0 0         if ($action eq 'profile_page') {
285 0           $template->param(EMAIL => $email,
286             GRAVATAR => Quiki::Users->gravatar($username));
287             }
288              
289 0 0         if ($action eq 'edit') { # &&
    0          
    0          
    0          
    0          
290 0 0         if ($preview) {
291 0           my $text = param('text');
292 0 0         $text = '' unless defined $text;
293 0           $template->param(CONTENT=>Quiki::Formatter::HTML::format($self, $text));
294 0           $template->param(TEXT => $text);
295             }
296             else {
297 0           $template->param(TEXT => $content);
298             }
299              
300 0 0         $template->param(ATTACHS => Quiki::Attachments->list($node)) if -d "data/attach/$node";
301             }
302             elsif ($action eq 'history') {
303 0           my @revs;
304 0           for (my $i=$self->{meta}{rev} ; $i>0 ; $i--) {
305 0           my $entry = { VERSION => $i };
306 0 0         if ($i != $self->{meta}{rev}) {
307 0           $entry->{AUTHOR} = $self->{meta}{revs}{$i}{last_update_by};
308 0           $entry->{DATE} = $self->{meta}{revs}{$i}{last_updated_in};
309 0           $entry->{GRAVATAR} = Quiki::Users->gravatar($self->{meta}{revs}{$i}{last_update_by});
310             }
311             else {
312 0           $entry->{AUTHOR} = $self->{meta}{last_update_by};
313 0           $entry->{DATE} = $self->{meta}{last_updated_in};
314 0           $entry->{GRAVATAR} = Quiki::Users->gravatar($self->{meta}{last_update_by});
315             }
316 0           push @revs, $entry;
317             }
318 0           $template->param(REVISIONS => \@revs);
319             }
320             elsif ($action eq 'admin_page') {
321 0           my $users = Quiki::Users->list;
322 0           $template->param(USERS => $users);
323 0           $template->param(WIKINODE => 'Administration');
324             }
325             elsif ($action eq 'index') {
326 0           opendir DIR, 'data/content/';
327 0           my @pages;
328 0           for my $f (sort { lc($a) cmp lc($b) } readdir(DIR)) {
  0            
329 0 0         unless ($f=~/^\./) {
330 0           my $meta = Quiki::Meta::get($f);
331 0           push @pages,
332             { URL => "$self->{SCRIPT_NAME}?node=$f",
333             NAME => $f,
334             AUTHOR => $meta->{last_update_by},
335             DATE => $meta->{last_updated_in},
336             GRAVATAR => Quiki::Users->gravatar($meta->{last_update_by}),
337             }
338             }
339             }
340 0           closedir DIR;
341 0           $template->param(PAGES=>\@pages);
342             }
343             elsif ($action eq 'diff') {
344 0   0       my $source = param('source') || 1;
345 0   0       my $target = param('target') || 1;
346 0           $template->param(CONTENT => Quiki::Pages->calc_diff($self,$node,$source,$target));
347             }
348             else {
349 0           $template->param(CONTENT => Quiki::Formatter::HTML::format($self, $content));
350             }
351              
352              
353             # handle meta data
354 0 0 0       if ($action eq 'save' or $action eq 'rollback') {
355 0           $self->{meta}{last_update_by} = $self->{session}->param('username');
356 0           $self->{meta}{last_updated_in} = `date`; # XXX -- more legible?
357 0           chomp $self->{meta}->{last_updated_in};
358             }
359              
360 0 0         unless ($action eq 'edit') {
361 0           my $L_META;
362 0 0         if ($self->{meta}{last_update_by}) {
363 0           my $url = Quiki::Users->gravatar($self->{meta}{last_update_by});
364 0           $L_META = img({-src => $url, -width => '24', -style => 'vertical-align: middle'});
365 0   0       $L_META .= sprintf(" Last edited by %s, in %s",
366             $self->{meta}{last_update_by},
367             $self->{meta}{last_updated_in} || "");
368             }
369             else {
370 0           $L_META = "";
371             }
372 0   0       my $R_META = sprintf("Revision: %s", $self->{rev} || "");
373              
374 0           $template->param(L_META=>$L_META);
375 0           $template->param(R_META=>$R_META);
376             }
377              
378 0 0         if ($self->{session}->param('msg')) {
379 0           $template->param(MSG => $self->{session}->param('msg'));
380 0           $self->_msg('');
381             }
382              
383             # save meta data
384 0           Quiki::Meta::set($node, $self->{meta});
385 0           $template->output(print_to => \*STDOUT);
386             }
387              
388              
389              
390             =head1 QUIKI CONFIGURATION FILE
391              
392             After a Quiki wiki is deployed with C<< quiki_create >> a
393             C<< quiki.conf >> file can be edited to configure Quiki behavior. While
394             later versions might offer a web interface to configure them, at
395             present you need to use a text editor and change the file. Note that
396             it is a Perl file. Therefore it should parse correctly by Perl. You
397             can check it using C<< perl -c quiki.conf >>.
398              
399             Quiki configuration file supports the following keys:
400              
401             =over 4
402              
403             =item name
404              
405             The display name for your wiki
406              
407             =item theme
408              
409             The theme to be used. Note that at the time we are writing this only
410             the C<< default >> theme exists
411              
412             =item index
413              
414             The name of the main quiki page. It defaults to C<< index >>.
415              
416             =item opensite
417              
418             This is a boolean value. By default it is true (1), meaning the
419             register button will be available to everybody visiting your
420             Quiki. Turn it off setting it to false (0).
421              
422             At the current moment there isn't any other way to register. Therefore
423             you should turn registering off only after all users have an account,
424             or you need to turn it off manually everytime a new user has to
425             register.
426              
427             =back
428              
429              
430             =head1 QUIKI SYNTAX
431              
432             Quiki wiki syntax is very similar to other wiki, and especially
433             similar with dokuwiki syntax.
434              
435             =over 4
436              
437             =item *
438              
439             To force a paragraph give a blank line;
440              
441             =item *
442              
443             To refer to another node use: C<[[NodeName]]> or C<[[NodeName|Node Description]]>;
444              
445             =item *
446              
447             To link the Internet use just the URL and it should be highlighted
448              
449             =item *
450              
451             You can also create named links with: C<[[URL|URL Description]]>
452              
453             =item * Basic formatting:
454              
455             =over 4
456              
457             =item *
458              
459             Bolds: C<**bold**>;
460              
461             =item *
462              
463             Italics: C;
464              
465             =item *
466              
467             Underlines: C<__underline__>;
468              
469             =item *
470              
471             Typewriter: C<< ''typewriter'' >>;
472              
473             =back
474              
475             =item *
476              
477             Six levels of headings:
478              
479             =over 4
480              
481             =item *
482              
483             Stronger: C<====== title ======>
484              
485             =item *
486              
487             Weaker: C<= title =>
488              
489             =back
490              
491             =item *
492              
493             Hard rules are obtained with ten or more dashes: C<--------------->
494              
495             =item *
496              
497             Code/verbatim zones are blocks with all lines indented three spaces.
498              
499             =item * Lists:
500              
501             =over 4
502              
503             =item *
504              
505             Ordered lists as a dash C<->
506              
507             =item *
508              
509             Unordered lists as an asterisk C<*>
510              
511             =item *
512              
513             Each item with two spaces before the mark
514              
515             =item *
516              
517             Deeper levels have multiples of two spaces indentation
518              
519             =back
520              
521             =item *
522              
523             Tables:
524              
525             =over 4
526              
527             =item *
528              
529             Table headers separated by a carret character ^. Note that no space should exist in the beginning of the line.
530              
531             =item *
532              
533             Table rows separated by a pipe character |. Note that no space should exist in the beginning of the line.
534              
535             =item *
536              
537             Each cell (not header) will be formatted accordingly with the ascii alignment:
538              
539             =over 4
540              
541             =item *
542              
543             put the content at the left without spaces, to get left alignment: C<< |foo | >>
544              
545             =item *
546              
547             put the content at the right without spaces, to get right alignment: C<< | foo| >>
548              
549             =item *
550              
551             put the content at the center, with spaces both sides, to get center alignment: C<< | foo | >>
552              
553             =back
554              
555             =back
556              
557             =back
558              
559             =head1 QUIKI DEPLOYMENT
560              
561             =over 4
562              
563             =item 1
564              
565             Install the Quiki Perl module
566              
567             $ cpan Quiki
568              
569             =item 2
570              
571             Use the quiki_create Perl script
572              
573             $ mkdir /var/www/html/myquiki
574             $ quiki_create /var/www/html/myquiki
575              
576             =item 3
577              
578             Configure your Apache accordingly.
579              
580             Sample VirtualHost for Apache2:
581              
582            
583             ServerName quiki.server.com
584             DocumentRoot /var/www/html/myquiki
585             ServerAdmin admin@quiki.server.com
586             DirectoryIndex index.html
587            
588            
589             Options +ExecCGI
590             AddHandler cgi-script .cgi
591            
592            
593              
594             =back
595              
596             =head1 AUTHOR
597              
598             =over 4
599              
600             =item * Alberto Simoes, C<< >>
601              
602             =item * Nuno Carvalho, C<< >>
603              
604             =back
605              
606             =head1 BUGS
607              
608             Please report any bugs or feature requests to C
609             rt.cpan.org>, or through the web interface at
610             L. I will be
611             notified, and then you'll automatically be notified of progress on
612             your bug as I make changes.
613              
614             =head1 SUPPORT
615              
616             You can find documentation for this module with the perldoc command.
617              
618             perldoc Quiki
619              
620             You can also look for information at:
621              
622             =over 4
623              
624             =item * RT: CPAN's request tracker
625              
626             L
627              
628             =item * AnnoCPAN: Annotated CPAN documentation
629              
630             L
631              
632             =item * CPAN Ratings
633              
634             L
635              
636             =item * Search CPAN
637              
638             L
639              
640             =back
641              
642              
643             =head1 ACKNOWLEDGEMENTS
644              
645             Thank you Luis 'Houser' Fernandes C<< >> for the
646             default theme layout design.
647              
648             =head1 COPYRIGHT & LICENSE
649              
650             Copyright 2009-2010 Alberto Simoes and Nuno Carvalho.
651              
652             This program is free software; you can redistribute it and/or modify it
653             under the terms of either: the GNU General Public License as published
654             by the Free Software Foundation; or the Artistic License.
655              
656             See http://dev.perl.org/licenses/ for more information.
657              
658              
659             =cut
660              
661             42; # End of Quiki
662