File Coverage

blib/lib/Rubric/WebApp.pm
Criterion Covered Total %
statement 259 378 68.5
branch 43 132 32.5
condition 28 94 29.7
subroutine 47 63 74.6
pod 41 41 100.0
total 418 708 59.0


line stmt bran cond sub pod time code
1 3     3   32373 use strict;
  3         6  
  3         62  
2 3     3   12 use warnings;
  3         3  
  3         118  
3             # ABSTRACT: the web interface to Rubric
4              
5             #pod =head1 SYNOPSIS
6             #pod
7             #pod use strict;
8             #pod use warnings;
9             #pod use Rubric::WebApp;
10             #pod Rubric::WebApp->new->run();
11             #pod
12             #pod It's a CGI::Application!
13             #pod
14             #pod =head1 DESCRIPTION
15             #pod
16             #pod Rubric::WebApp provides a CGI-based interface to Rubric data. It's built on
17             #pod top of CGI::Application, which does most of the boring work. This module's
18             #pod code sets up the dispatch tables and implements the responses to various
19             #pod queries.
20             #pod
21             #pod =head1 REQUESTS DISPATCH
22             #pod
23             #pod Requests are I<mostly> path-based, though some involve form submission. The
24             #pod basic dispatch table looks something like this:
25             #pod
26             #pod request | description | method called
27             #pod -------------+-------------------------------------------+--------------
28             #pod /login | log in to a user account | login
29             #pod /logout | log out | logout
30             #pod /preferences | view or change account settings | preferences
31             #pod /newuser | create a new user account | newuser
32             #pod /verify | verify a pending user account | verify
33             #pod /link | view the details of entries for a link | link
34             #pod /post | post or edit an entry (must be logged in) | post
35             #pod /edit | edit an entry (must be logged in) | edit
36             #pod /delete | delete an entry (must be logged in) | delete
37             #pod /entry/ENTRY | find and display the identified entry | entry
38             #pod /entries/Q | find and display results for query Q | entries
39             #pod /~USER/TAGS | see a user's entries for given tags | (in flux)
40             #pod /doc/PAGE | view the named page in the documentation | doc
41             #pod /style/PAGE | get the named style sheet | style
42             #pod
43             #pod If the system is private and no user is logged in, the default action is to
44             #pod display a login screen. If the system is public, or a user is logged in, the
45             #pod default action is to display entries.
46             #pod
47             #pod =cut
48              
49             use CGI::Application 3;
50 3     3   1475 use base qw(CGI::Application);
  3         15135  
  3         68  
51 3     3   16 use CGI::Carp qw(fatalsToBrowser);
  3         5  
  3         216  
52 3     3   989  
  3         5640  
  3         15  
53             use Digest::MD5 qw(md5_hex);
54 3     3   211 use Encode 2 qw(decode_utf8);
  3         6  
  3         109  
55 3     3   14  
  3         32  
  3         86  
56             use HTML::TagCloud;
57 3     3   951 use DateTime;
  3         3431  
  3         61  
58 3     3   1870  
  3         1007697  
  3         106  
59             use Email::Address;
60 3     3   1264 use Email::Sender::Simple qw(sendmail);
  3         14190  
  3         116  
61 3     3   953  
  3         207859  
  3         19  
62             use Rubric::Config;
63 3     3   804 use Rubric::Entry;
  3         6  
  3         28  
64 3     3   15 use Rubric::Renderer;
  3         4  
  3         22  
65 3     3   65 use Rubric::WebApp::URI;
  3         6  
  3         65  
66 3     3   999 use Rubric::WebApp::Session;
  3         6  
  3         65  
67 3     3   902  
  3         10  
  3         18  
68             use String::Truncate qw(elide);
69 3     3   2448  
  3         4375  
  3         12  
70             #pod =head1 METHODS
71             #pod
72             #pod =head2 redirect($uri, $message)
73             #pod
74             #pod This method simplifies redirection; it redirects to the given URI, printing the
75             #pod given message as the body of the HTTP response.
76             #pod
77             #pod =cut
78              
79             my ($self, $uri, $message) = @_;
80              
81 5     5 1 16 $self->header_type('redirect');
82             $self->header_props(-url=> $uri);
83 5         61 return $message;
84 5         148 }
85 5         213  
86             #pod =head2 redirect_root($message)
87             #pod
88             #pod This is shorthand to redirect to the Rubric's root URI. It calls C<redirect>.
89             #pod
90             #pod =cut
91              
92             my ($self, $message) = @_;
93              
94             return $self->redirect(Rubric::Config->uri_root, $message);
95 2     2 1 8 }
96              
97 2         11 #pod =head2 cgiapp_init
98             #pod
99             #pod This method is called during CGI::Application's initialization. It sets up the
100             #pod session configuration.
101             #pod
102             #pod =cut
103              
104             my ($self) = @_;
105              
106             my $login_class = Rubric::Config->login_class;
107              
108 48     48 1 1001 ## no critic (StringyEval)
109             eval("require $login_class; 1") or die;
110 48         243 ## use critic
111             $login_class->check_for_login($self);
112             }
113 48 50       2413  
114             #pod =head2 cgiapp_prerun
115 48         423 #pod
116             #pod This method is called before the selected runmode. It checks for a login,
117             #pod checks for updates to result-set paging, and starts processing the request
118             #pod path.
119             #pod
120             #pod =cut
121              
122             my ($self) = @_;
123              
124             $self->check_pager_data;
125              
126             my @path = split '/', $self->query->path_info;
127 48     48 1 4556 $self->param(path => [ @path[ 2 .. $#path ] ]);
128             }
129 48         257  
130             #pod =head2 next_path_part
131 48         1827 #pod
132 48         957 #pod This method shifts the next item off of the request path and returns it.
133             #pod
134             #pod =cut
135              
136             my ($self) = @_;
137             shift @{$self->param('path')};
138             }
139              
140             #pod =head2 check_pager_data
141             #pod
142 80     80 1 12280 #pod This method is called by C<cgiapp_init>, and sets up parameters used for paging
143 80         110 #pod entry listings. The following parameters are used:
  80         173  
144             #pod
145             #pod per_page - how many items per page; default 25, maximum 100; stored in session;
146             #pod page - which page to display; default 1
147             #pod
148             #pod =cut
149              
150             my ($self) = @_;
151              
152             $self->session->param('per_page', int(
153             $self->query->param('per_page')
154             || $self->session->param('per_page')
155             || Rubric::Config->default_page_size
156             ));
157 48     48 1 110  
158             $self->session->param('per_page', Rubric::Config->max_page_size)
159 48   66     139 if $self->session->param('per_page') > Rubric::Config->max_page_size;
160              
161             $self->param('per_page', $self->session->param('per_page'));
162             $self->param('page', int(($self->query->param('page') || 1)));
163             }
164              
165 48 50       178 #pod =head2 template($template, \%variables)
166             #pod
167             #pod This method is used to render a template with both provided and default
168 48         169 #pod variables.
169 48   50     700 #pod
170             #pod Templates are rendered by calling the C<process> method on the template
171             #pod renderer, which is retrieved by calling the C<renderer> method on the WebApp.
172             #pod
173             #pod The following variables are passed by default:
174             #pod
175             #pod current_user - the currently logged-in user (a Rubric::User object)
176             #pod per_page - entries per page (see check_pager_data)
177             #pod page - which page (see check_pager_data)
178             #pod
179             #pod =cut
180              
181             my ($self, $template, $stash) = @_;
182             $stash ||= {};
183             $stash->{current_user} = $self->param('current_user');
184             $stash->{per_page} = $self->param('per_page');
185             $stash->{page} = $self->param('page');
186              
187             my $type = $self->query->param('format');
188             $type = 'html' unless $type and $type =~ /^[\pL\d_]+$/;
189 42     42 1 6647  
190 42   100     114 my ($content_type, $output) =
191 42         121 Rubric::Renderer->process($template, $type, $stash);
192 42         524  
193 42         468 $self->header_add(-type => $content_type);
194             return $output;
195 42         478 }
196 42 100 66     970  
197             #pod =head2 setup
198 42         396 #pod
199             #pod This method, called by CGI::Application's initialization process, sets up
200             #pod the dispatch table for requests, as described above.
201 42         262 #pod
202 42         2580 #pod =cut
203              
204             my ($self) = @_;
205              
206             $self->mode_param(path_info => 1);
207              
208             $self->start_mode('login');
209             $self->run_modes([ qw(style doc login newuser reset_password verify) ]);
210              
211             if ($self->param('current_user') or not Rubric::Config->private_system) {
212             $self->start_mode('entries');
213 48     48 1 1859 $self->run_modes([
214             qw(delete edit entries entry link logout post preferences tag_cloud calendar)
215 48         244 ]);
216             }
217 48         5173  
218 48         658 $self->run_modes(AUTOLOAD => '_default_handler');
219             }
220 48 50 66     1543  
221 48         2091 my ($self, $runmode) = @_;
222 48         511 if (substr($runmode, 0, 1) eq '~') {
223             return $self->_entries_shortcut(substr($runmode, 1));
224             }
225             $self->redirect_root;
226             }
227 48         2653  
228             my ($self, $user) = @_;
229             my $path = $self->param('path');
230              
231 5     5   378 # If there the number of elements in the path is odd, the first one is tags;
232 5 100       24 # otherwise, it's a normal query; this may or may not be safe, in the end. I
233 4         19 # guess we'll find out. -- rjbs, 2006-02-20
234             unshift @$path, 'tags' if @$path % 2;
235 1         5 unshift @$path, 'user', $user;
236              
237             $self->entries;
238             }
239 4     4   22  
240 4         15 #pod =head2 entries
241             #pod
242             #pod This passes off responsibility to the class named in the C<entries_query_class>
243             #pod configuration option. This option defaults to Rubric::WebApp::Entries.
244             #pod
245 4 100       114 #pod =cut
246 4         13  
247             my ($self) = @_;
248 4         14  
249             my $entries_class = Rubric::Config->entries_query_class;
250             ## no critic (StringyEval)
251             die $@ unless eval "require $entries_class";
252             ## use critic
253             $entries_class->entries($self);
254             }
255              
256             #pod =head2 entry
257             #pod
258             #pod This displays the single requested entry.
259 30     30 1 1897 #pod
260             #pod =cut
261 30         177  
262             my ($self) = @_;
263 30 50       1797  
264             my $entry = $self->get_entry;
265 30         194  
266             return $self->template('no_entry', { reason => 'missing' }) unless $entry;
267              
268             return $self->template('no_entry', { reason => 'access' })
269             if grep { $_ eq Rubric::Config->private_tag } $entry->tags
270             and (not $self->param('current_user')
271             or $entry->user ne $self->param('current_user'));
272              
273             $self->template('entry_long' => {
274             entry => $self->param('entry'),
275 2     2 1 175 self_url => $self->query->self_url(),
276             # FIX ME: hack to put the title of the entry in the <title> tag
277 2         8 query_description => $entry->title,
278             long_form => 1
279 2 50       43 });
280             }
281              
282 2 0 0     181 #pod =head2 get_entry
  2   33     4603  
283             #pod
284             #pod This method gets the next part of the path, assumes it to be a Rubric::Entry
285             #pod id, and puts the corresponding entry in the "entry" parameter.
286 2         12 #pod
287             #pod =cut
288              
289             my ($self) = @_;
290              
291             my $entry = Rubric::Entry->retrieve($self->next_path_part);
292             $self->param(entry => $entry);
293             }
294              
295             #pod =head2 link
296             #pod
297             #pod This runmode displays entries that point to a given link, identified either by
298             #pod URI or MD5 sum.
299             #pod
300             #pod =cut
301              
302             my ($self) = @_;
303 4     4 1 10 return $self->redirect_root("...no such link") unless $self->get_link;
304             $self->display_entries;
305 4         14 }
306 4         5685  
307             #pod =head2 get_link
308             #pod
309             #pod This method look for a C<uri> or, failing that, C<url> query parameter. If
310             #pod found, it finds a Rubric::Link for that URI and puts it in the "link"
311             #pod parameter.
312             #pod
313             #pod =cut
314              
315             my ($self) = @_;
316             my %search;
317 0     0 1 0 $search{md5} = $self->query->param('md5');
318 0 0       0 $search{uri} = $self->query->param('uri') || $self->query->param('url');
319 0         0 for (qw(md5sum uri)) {
320             delete $search{$_} unless $search{$_};
321             }
322             return unless %search;
323             return unless my ($link) = Rubric::Link->search(\%search);
324             $self->param('link', $link);
325             }
326              
327             #pod =head2 tag_cloud
328             #pod
329             #pod =cut
330              
331 0     0 1 0 my ($self, $options) = @_;
332 0         0  
333 0         0 my $tags = Rubric::DBI->db_Main->selectall_arrayref(
334 0   0     0 "SELECT tag, count(*)
335 0         0 FROM entrytags
336 0 0       0 WHERE tag not like '@%'
337             GROUP BY tag
338 0 0       0 ORDER BY tag");
339 0 0       0  
340 0         0 my $cloud = HTML::TagCloud->new();
341             foreach my $tag (@$tags) {
342             my $href = Rubric::WebApp::URI->entries({tags => [ $tag->[0] ]});
343             $cloud->add($tag->[0], $href, $tag->[1]);
344             }
345              
346             return $self->template('tag_cloud' => {
347             cloud => $cloud,
348 1     1 1 66 query_description => 'All Tags',
349             });
350 1         9  
351             }
352              
353             #pod =head2 calendar
354             #pod
355             #pod =cut
356              
357 1         390 my ($self, $options) = @_;
358 1         32 my $path = $self->param('path');
359 6         67  
360 6         21 require HTML::CalendarMonth;
361              
362             my $year = shift @$path;
363 1         21 my $month = shift @$path;
364              
365             if (not ($year or $month)) {
366             ($month, $year) = (localtime)[4,5];
367             $month++;
368             $year += 1900;
369             }
370             my $calendar = HTML::CalendarMonth->new(
371             month => $month,
372             year => $year,
373             full_days => 1
374             );
375 1     1 1 87 $calendar->item($calendar->year, $calendar->month)->attr(
376 1         5 style => 'background-color: #EEEEEE'
377             );
378 1         538 $calendar->attr(class => 'calendar');
379             $calendar->alldays->attr(class => 'day');
380 1         26105 my $num_span = HTML::Element->new('span', class => 'day_indicator');
381 1         3 $calendar->alldays->attr(class => 'day');
382             $calendar->alldays->wrap_content($num_span);
383 1 50 33     15 $calendar->allheaders->attr(class => 'headers');
384 1         34  
385 1         3 my $start = DateTime->new(
386 1         3 year => $year,
387             month => $month,
388 1         5 day => 1,
389             hour => 0,
390             minute => 0,
391             second => 0,
392             nanosecond => 0,
393 1         44622 time_zone => '-1700'
394             )->epoch;
395              
396 1         729 my $end = DateTime->new(
397 1         23 year => $year,
398 1         1048 month => $month,
399 1         35 day => $calendar->lastday,
400 1         847 hour => 23,
401 1         11741 minute => 59,
402             second => 59,
403 1         2773 nanosecond => 0,
404             time_zone => '-1700'
405             )->epoch;
406              
407             my $entries = Rubric::Entry->retrieve_from_sql(qq{
408             WHERE id NOT IN (SELECT entry FROM entrytags WHERE tag = '\@private')
409             AND created > '$start'
410             AND created < '$end'
411             ORDER BY created}
412             );
413              
414 1         1677 while (my $entry = $entries->next) {
415             my ($day) = $entry->created->day_of_month;
416             my $a = HTML::Element->new('a');
417             my $div = HTML::Element->new('div');
418             my $title = $entry->title;
419             $a->attr(title => $title);
420             $a->attr(href => Rubric::WebApp::URI->entry($entry));
421             $title = elide($title, 18);
422             $a->push_content($title);
423             $div->push_content($a);
424             $calendar->item($day)->push_content($div);
425 1         467 }
426              
427             my $prev_month = $month;
428             my $prev_year = $year;
429             $prev_month --;
430             if (not $prev_month) {
431             $prev_month = 12;
432 1         977 $prev_year--;
433 6         3760 }
434 6         643  
435 6         124 my $next_month = $month;
436 6         90 my $next_year = $year;
437 6         475 $next_month++;
438 6         90 if ($next_month > 12) {
439 6         536 $next_month = 1;
440 6         142 $next_year++;
441 6         70 }
442 6         73  
443             return $self->template('calendar' => {
444             calendar => $calendar,
445 1         318 prev_link => {
446 1         3 month => sprintf("%02d", $prev_month),
447 1         3 year => $prev_year,
448 1 50       4 },
449 0         0 next_link => {
450 0         0 month => sprintf("%02d", $next_month),
451             year => $next_year,
452             },
453 1         2 query_description => 'Calendar',
454 1         2 });
455 1         2  
456 1 50       4 }
457 0         0  
458 0         0 #pod =head2 login
459             #pod
460             #pod If the user is logged in, this request is immediately redirected to the root of
461 1         15 #pod the Rubric site. Otherwise, a login form is provided.
462             #pod
463             #pod =cut
464              
465             my ($self) = @_;
466              
467             if ($self->param('current_user')) {
468             my $goto = $self->query->param('then_goto') || Rubric::Config->uri_root;
469             return $self->redirect($goto, "Logged in...");
470             }
471              
472             my $note;
473             if ($self->get_current_runmode ne 'login') {
474             $note = "You must log in to use this feature.";
475             $self->query->param('then_goto', $self->query->self_url);
476             }
477              
478             if (Rubric::Config->secure_login) {
479             if ($self->query->env->{'psgi.url_scheme'} ne 'https') {
480             my $goto = Rubric::WebApp::URI->login;
481             return $self->redirect($goto, "Logged in...");
482             }
483             }
484 5     5 1 357  
485             $self->template('login' => {
486 5 100       16 note => $note,
487 2   33     249 then_goto => scalar $self->query->param('then_goto'),
488 2         12 user => scalar $self->query->param('user'),
489             user_pending => scalar $self->param('user_pending')
490             });
491 3         41 }
492 3 50       24  
493 0         0 #pod =head2 logout
494 0         0 #pod
495             #pod This run mode unsets the "current_user" parameter in the session and the WebApp
496             #pod object, then redirects the user to the root of the Rubric site.
497 3 50       30 #pod
498 0 0       0 #pod =cut
499 0         0  
500 0         0 my ($self) = @_;
501             $self->session->clear('current_user');
502             $self->param('current_user', undef);
503              
504 3         24 return $self->redirect_root("Logged out...");
505             }
506              
507             #pod =head2 reset_password
508             #pod
509             #pod This run mode allows a user to request that his password be reset and emailled
510             #pod to him.
511             #pod
512             #pod =cut
513              
514             my ($self) = @_;
515             my $user = $self->get_user
516             || $self->query->param('user')
517             && Rubric::User->retrieve($self->query->param('user'));
518             my $reset_code = $self->get_reset_code;
519              
520 1     1 1 72 return $self->template("reset_login") unless $user;
521 1         4  
522 1         4 return $self->setup_reset_code($user) unless $reset_code;
523              
524 1         39 if (my $password = $user->reset_password($reset_code)) {
525             $self->template("reset", { password => $password });
526             } else {
527             return $self->template("reset_error");
528             }
529              
530             }
531              
532             #pod =head2 setup_reset_code
533             #pod
534             #pod This routine gets a reset code for the user and emails it to him.
535 0     0 1 0 #pod
536 0   0     0 #pod =cut
537              
538             my ($self, $user) = @_;
539 0         0  
540             my $reset_code = $user->randomize_reset_code;
541 0 0       0  
542             $self->send_reset_email_to($user, $reset_code);
543 0 0       0 $self->template("reset_sent");
544             }
545 0 0       0  
546 0         0 #pod =head2 preferences
547             #pod
548 0         0 #pod This method displays account information for the current user. Some account
549             #pod settings may be changed.
550             #pod
551             #pod =cut
552              
553             my ($self) = @_;
554              
555             return $self->login unless $self->param('current_user');
556              
557             return $self->template("preferences")
558             unless my %prefs = $self->_get_prefs_form;
559              
560 0     0 1 0 if (my %errors = $self->validate_prefs(\%prefs)) {
561             return $self->template("preferences", { %prefs, %errors } );
562 0         0 }
563              
564 0         0 $self->update_user(\%prefs);
565 0         0 }
566              
567             #pod =head2 update_user(\%prefs)
568             #pod
569             #pod This method will update the current user object with the changes in C<%prefs>,
570             #pod which is passed by the C<preferences> method.
571             #pod
572             #pod =cut
573              
574             my ($self, $prefs) = @_;
575             for ($self->param('current_user')) {
576 0     0 1 0 $_->password(md5_hex($prefs->{password_1})) if $prefs->{password_1};
577             $_->email($prefs->{email});
578 0 0       0 $_->update;
579             }
580 0 0       0 $self->redirect_root('updated');
581             }
582              
583 0 0       0 my ($self) = @_;
584 0         0  
585             my %form;
586             for (qw(password password_1 password_2 email)) {
587 0         0 $form{$_} = $self->query->param($_) if $self->query->param($_);
588             }
589             return %form;
590             }
591              
592             #pod =head2 validate_prefs(\%prefs)
593             #pod
594             #pod Given a set of preference updates from a form submission, this method validates
595             #pod them and returns a description of the validation results. This method will
596             #pod probably be redesigned (possibly with Data::FormValidator) in the future.
597             #pod Don't count on its interface.
598 0     0 1 0 #pod
599 0         0 #pod =cut
600 0 0       0  
601 0         0 #pod =begin future
602 0         0 #pod
603             #pod sub validate_prefs {
604 0         0 #pod my ($self, $prefs) = @_;
605             #pod require Data::FormValidator;
606             #pod
607             #pod my $profile = {
608 0     0   0 #pod required => [qw(password)],
609             #pod optional => [qw(password_1 password_2 email)],
610 0         0 #pod constraints => {
611 0         0 #pod email => 'email',
612 0 0       0 #pod password_1 => {
613             #pod params => [qw(password_1 password_2)],
614 0         0 #pod constraint => sub { $_[0] eq $_[1] },
615             #pod }
616             #pod },
617             #pod dependency_groups => { new_password => [qw(password_1 password_2)] }
618             #pod };
619             #pod
620             #pod my $results = Data::FormValidator->check($prefs, $profile);
621             #pod }
622             #pod
623             #pod =end future
624             #pod
625             #pod =cut
626              
627             my ($self, $prefs) = @_;
628             my %errors;
629              
630             if (not $prefs->{email}) {
631             $errors{email_missing} = 1;
632             } elsif ($prefs->{email} and $prefs->{email} !~ $Email::Address::addr_spec) {
633             undef $prefs->{email};
634             $errors{email_invalid} = 1;
635             }
636              
637             if (
638             $prefs->{password_1} and $prefs->{password_2}
639             and $prefs->{password_1} ne $prefs->{password_2}
640             ) {
641             undef $prefs->{password_1};
642             undef $prefs->{password_2};
643             $errors{password_mismatch} = 1;
644             }
645              
646             unless ($prefs->{password}) {
647             $errors{password_missing} = 1;
648             } elsif (
649             md5_hex($prefs->{password}) ne $self->param('current_user')->password
650             ) {
651             $errors{password_wrong} = 1;
652             }
653 0     0 1 0  
654 0         0 return %errors;
655             }
656 0 0 0     0  
    0          
657 0         0 #pod =head2 newuser
658             #pod
659 0         0 #pod If the proper form information is present, this runmode creates a new user
660 0         0 #pod account. If not, it presents a form.
661             #pod
662             #pod If a user is already logged in, the user is redirected to the root of the
663 0 0 0     0 #pod Rubric.
      0        
664             #pod
665             #pod =cut
666              
667 0         0 my ($self) = @_;
668 0         0  
669 0         0 return $self->redirect_root("registration is closed...")
670             if Rubric::Config->registration_closed;
671              
672 0 0 0     0 return $self->redirect_root("Already logged in...")
673 0         0 if $self->param('current_user');
674              
675             my %newuser;
676             $newuser{$_} = $self->query->param($_)
677             for qw(username password_1 password_2 email);
678              
679             my %errors = $self->validate_newuser_form(\%newuser);
680 0         0 if (%errors) {
681             $self->template('newuser' => { %newuser, %errors });
682             } else {
683             $self->create_newuser(%newuser);
684             }
685             }
686              
687             #pod =head2 validate_newuser_form(\%newuser)
688             #pod
689             #pod Given a set of user data from a form submission, this method validates them and
690             #pod returns a description of the validation results. This method will probably be
691             #pod redesigned (possibly with Data::FormValidator) in the future. Don't count on
692             #pod its interface.
693             #pod
694 1     1 1 74 #pod =cut
695              
696 1 50       5 my ($self, $newuser) = @_;
697             my %errors;
698              
699 1 50       5 if ($newuser->{username} and $newuser->{username} !~ /^[\pL\d_.]+$/) {
700             undef $newuser->{username};
701             $errors{username_invalid} = 1;
702 1         22 } elsif (Rubric::User->retrieve($newuser->{username})) {
703             undef $newuser->{username};
704 1         6 $errors{username_taken} = 1;
705             }
706 1         95  
707 1 50       3 unless ($newuser->{email}) {
708 1         4 $errors{email_missing} = 1;
709             } elsif ($newuser->{email} and $newuser->{email} !~ $Email::Address::addr_spec) {
710 0         0 undef $newuser->{email};
711             $errors{email_invalid} = 1;
712             }
713              
714             if (
715             $newuser->{password_1} and $newuser->{password_2}
716             and $newuser->{password_1} ne $newuser->{password_2}
717             ) {
718             undef $newuser->{password_1};
719             undef $newuser->{password_2};
720             $errors{password_mismatch} = 1;
721             }
722             return %errors;
723             }
724 1     1 1 3  
725 1         3 #pod =head2 create_newuser(\%newuser)
726             #pod
727 1 50 33     31 #pod This method creates a new user account from the given description. It sends
    50          
728 0         0 #pod the user a validation email (if needed) and displays an account creation page.
729 0         0 #pod
730             #pod =cut
731 0         0  
732 0         0 my ($self, %newuser) = @_;
733              
734             my %user = (
735 1 50 0     63 username => $newuser{username},
736 1         4 password => md5_hex($newuser{password_1}),
737             email => $newuser{email},
738             );
739              
740             my $user = Rubric::User->create(\%user);
741              
742 1 0 33     3 unless (Rubric::Config->skip_newuser_verification) {
      0        
743             $user->randomize_verification_code;
744             $self->send_verification_email_to($user);
745             }
746 0         0  
747 0         0 $self->template("account_created");
748 0         0 }
749              
750 1         5 #pod =head2 send_reset_email_to($user)
751             #pod
752             #pod This method sends an email to the given user with a URI to reset his password.
753             #pod
754             #pod =cut
755              
756             my ($self, $user) = @_;
757              
758             my $message = Rubric::Renderer->process(
759             'reset_mail',
760             'txt',
761 0     0 1 0 { user => $user, email_from => Rubric::Config->email_from }
762             );
763              
764             # XXX: This now ignores the smtp_server config.
765             sendmail($message);
766             }
767 0         0  
768             #pod =head2 send_verification_email_to($user)
769 0         0 #pod
770             #pod This method sends a verification email to the given user.
771 0 0       0 #pod
772 0         0 #pod =cut
773 0         0  
774             my ($self, $user) = @_;
775              
776 0         0 my $message = Rubric::Renderer->process(
777             'newuser_mail',
778             'txt',
779             { user => $user, email_from => Rubric::Config->email_from }
780             );
781              
782             # XXX: This now ignores the smtp_server config.
783             sendmail($message);
784             }
785              
786 0     0 1 0 #pod =head2 verify
787             #pod
788 0         0 #pod This runmode attempts to verify a user account. It expects a request to be
789             #pod in the form: C< /verify/username/verification_code >
790             #pod
791             #pod =cut
792              
793             my ($self) = @_;
794              
795 0         0 return $self->redirect_root("Already logged in...")
796             if $self->param('current_user');
797              
798             my $user = $self->get_user;
799             my $code = $self->get_verification_code;
800              
801             return $self->redirect_root("no such user")
802             if defined $user and $user eq '';
803              
804             return $user->verify($code) ? $self->template('verified')
805 0     0 1 0 : $self->redirect_root("BAD USER NO VALIDATION");
806             }
807 0         0  
808             #pod =head2 get_reset_code
809             #pod
810             #pod This gets the next part of the path and puts it in the C<reset_code>
811             #pod parameter.
812             #pod
813             #pod =cut
814 0         0  
815             my ($self) = @_;
816              
817             $self->param(reset_code => $self->next_path_part);
818             }
819              
820             #pod =head2 get_verification_code
821             #pod
822             #pod This gets the next part of the path and puts it in the C<verification_code>
823             #pod parameter.
824             #pod
825 0     0 1 0 #pod =cut
826              
827 0 0       0 my ($self) = @_;
828              
829             $self->param(verification_code => $self->next_path_part);
830 0         0 }
831 0         0  
832             #pod =head2 get_user
833 0 0 0     0 #pod
834             #pod This gets the next part of the path and puts it in the C<user> parameter.
835             #pod
836 0 0       0 #pod =cut
837              
838             my ($self) = @_;
839              
840             $self->param(user => Rubric::User->retrieve($self->next_path_part) || '');
841             }
842              
843             #pod =head2 display_entries
844             #pod
845             #pod This method searches (with Rubric::Entry) for entries matching the requested
846             #pod user and tags. It pages the result (with C<page_entries>) and renders the
847             #pod resulting page with C<render_entries>.
848 0     0 1 0 #pod
849             #pod =cut
850 0         0  
851             my ($self) = @_;
852              
853             return $self->redirect_root("no such user")
854             if defined $self->param('user') and $self->param('user') eq '';
855              
856             $self->param('has_body', scalar $self->query->param('has_body'));
857             $self->param('has_link', scalar $self->query->param('has_link'));
858              
859             my %search = (
860             user => $self->param('user'),
861 0     0 1 0 tags => $self->param('tags'),
862             link => $self->param('link'),
863 0         0 has_body => $self->param('has_body'),
864             has_link => $self->param('has_link'),
865             );
866              
867             my $entries = Rubric::Entry->by_tag(\%search);
868              
869             $self->page_entries($entries)->render_entries;
870             }
871              
872             #pod =head2 page_entries($iterator)
873 0     0 1 0 #pod
874             #pod Given a Class::DBI::Iterator, this method sets up parameters describing the
875 0   0     0 #pod current page. Most importantly, it retrieves an Iterator for the slice of
876             #pod entries representing the current page. The following parameters are set:
877             #pod
878             #pod entries - a Class::DBI::Iterator for the current page's entries
879             #pod count - the number of entries in the entire set
880             #pod pages - the number of pages the set spans
881             #pod
882             #pod =cut
883              
884             my ($self, $iterator) = @_;
885              
886             my $first = $self->param('per_page') * ($self->param('page') - 1);
887 0     0 1 0 my $last = ($self->param('per_page') * $self->param('page')) - 1;
888             my $slice = $iterator->slice($first, $last);
889 0 0 0     0 $self->param('entries', $slice);
890             $self->param('count', $iterator->count);
891              
892 0         0 my $pagecount = int($iterator->count / $self->param('per_page'));
893 0         0 $pagecount++ if $iterator->count % $self->param('per_page');
894             $self->param('pages', $pagecount);
895 0         0  
896             return $self;
897             }
898              
899             #pod =head2 render_entries
900             #pod
901             #pod This method renders a template to display the set of entries set up by
902             #pod C<page_entries>.
903 0         0 #pod
904             #pod =cut
905 0         0  
906             my ($self, $options) = @_;
907             $options ||= {};
908              
909             $self->template('entries' => {
910             count => $self->param('count'),
911             entries => $self->param('entries'),
912             pages => $self->param('pages'),
913             %$options,
914             remove => sub { [ grep { $_ ne $_[0] } @{$_[1]} ] },
915             self_url => $self->query->self_url(),
916             long_form => scalar $self->query->param('long_form'),
917             recent_tags => $self->param('recent_tags'),
918             related_tags => scalar (($options->{user} || 'Rubric::EntryTag')
919             ->related_tags_counted($options->{tags})),
920             query_description => $self->param('query_description'),
921 30     30 1 73 });
922             }
923 30         67  
924 30         741 #pod =head2 edit
925 30         626 #pod
926 30         29980 #pod If the user isn't logged in, it redirects to demand a login. If he is, it
927 30         605 #pod displays a post form, completed with the given entry's data.
928             #pod
929 30         634 #pod =cut
930 30 100       861  
931 30         429 my ($self) = @_;
932              
933 30         491 return $self->template('no_entry', { reason => 'missing' })
934             unless $self->get_entry;
935              
936             return $self->template('no_entry', { reason => 'access' })
937             unless $self->param('entry')->user eq $self->param('current_user');
938              
939             $self->param('existing_entry', $self->param('entry'));
940             $self->param('existing_link', $self->param('entry')->link);
941             return $self->post_form();
942             }
943              
944 30     30 1 81 #pod =head2 post
945 30   50     80 #pod
946             #pod This method wants to be simplified.
947             #pod
948             #pod If the user isn't logged in, it redirects to demand a login. If he is, it
949             #pod checks whether it can create a new entry. If so, it tries to. If not, it
950             #pod displays a form for doing so. If the user already has an entry for the given
951             #pod URI, the existing entry is passed to the form renderer.
952 13     13   217 #pod
  13         166  
  13         34  
953             #pod If a new entry is created, the user is redirected to his entry listing.
954             #pod
955             #pod =cut
956              
957 30   100     99 my ($self) = @_;
958             my (%form, %error);
959              
960             $form{$_} = $self->query->param($_)
961             for qw(entryid uri title description tags body);
962              
963             for (qw(uri title description body tags)) {
964             my $decoded;
965             my $ok = eval {
966             $decoded = decode_utf8($form{$_}, Encode::FB_CROAK | Encode::LEAVE_SRC);
967             1;
968             };
969             $error{$_} = "Invalid characters in $_." unless $ok;
970 1     1 1 61 $form{$_} = $decoded if $ok;
971             }
972 1 50       4  
973             eval { $form{uri} = URI->new($form{uri})->canonical; };
974             $error{uri} = "Invalid URI" if $@;
975 1 50       98  
976             if (
977             $form{uri}
978 1         387 and not $error{uri}
979 1         25 and defined Rubric::Config->allowed_schemes
980 1         126 and not grep { $_ eq $form{uri}->scheme } @{ Rubric::Config->allowed_schemes }
981             ) {
982             $error{uri} = "Invalid URI; valid schemes are: "
983             . "@{ Rubric::Config->allowed_schemes }";
984             }
985              
986             eval { Rubric::Entry->tags_from_string($form{tags}) };
987             $error{tags} = "Tags may only contain letters, numbers, dot, colon, and asterisk." if $@;
988              
989             $error{title} = "You must supply a title." if
990             $self->query->param('submit') and not length $form{title};
991              
992             if ($form{uri} and Rubric::Config->one_entry_per_link) {
993             if (my ($link) = Rubric::Link->search({uri => $form{uri}})) {
994             $self->param(existing_link => $link);
995             if (my ($entry) = $self->param('current_user')->entries(link => $link)) {
996             $self->param(existing_entry => $entry);
997 2     2   7 # why was this a desired error message?
998 2         6 # $error{uri} = "This will replace your current entry for this URI."
999             # if not $form{entryid};
1000             }
1001 2         11 }
1002             }
1003 2         218  
1004 10         11 return (\%form, \%error);
1005 10         13 }
1006 10         109  
1007 10         38 my ($self) = @_;
1008              
1009 10 50       19 return $self->login unless my $user = $self->param('current_user');
1010 10 50       22  
1011             my ($form, $error) = $self->_post_form_contents;
1012              
1013 2         5 return $self->post_form($form, $error)
  2         25  
1014 2 50       646 if not $self->query->param('submit')
1015             or %$error
1016 2 50 66     11 or not my $entry = $self->param('current_user')->quick_entry($form);
      66        
      33        
1017              
1018             my $when_done = $self->query->param('when_done');
1019             my $goto;
1020 0         0  
  0         0  
1021             if ($when_done eq 'close') { return $self->template('close_window') }
1022 0         0 elsif ($when_done eq 'entry') { $goto = Rubric::WebApp::URI->entry($entry) }
1023 0         0 elsif ($when_done eq 'go_back') { $goto = $form->{uri} }
1024             else { $goto = $self->query->param('then_goto') }
1025              
1026 2         14 $goto ||= Rubric::WebApp::URI->entries({user=> $self->param('current_user')});
  2         22  
1027 2 50       117  
1028             $self->redirect( $goto, "Posted..." );
1029             }
1030 2 50 33     9  
1031             #pod =head2 post_form
1032 2 100 66     99 #pod
1033 1 50       13 #pod This method renders a form for the user to create a new entry.
1034 0         0 #pod
1035 0 0       0 #pod =cut
1036 0         0  
1037             my ($self, $form, $error) = @_;
1038              
1039             $self->template( 'post' => {
1040             form => $form,
1041             error => $error,
1042             user => scalar $self->param('current_user'),
1043             existing_entry => scalar $self->param('existing_entry'),
1044 2         901 existing_link => scalar $self->param('existing_link'),
1045             then_goto => scalar $self->query->param('then_goto'),
1046             when_done => scalar $self->query->param('when_done'),
1047             });
1048 2     2 1 169 }
1049              
1050 2 50       10 #pod =head2 delete
1051             #pod
1052 2         218 #pod This method wants to be simplified. It's largely copied from C<post>.
1053             #pod
1054 2 0 33     10 #pod If the user isn't logged in, it redirects to demand a login. If he is, it
      33        
1055             #pod checks whether the user has an entry for the given URI. If so, it's deleted.
1056             #pod
1057             #pod Either way, the user is redirected to his entry listing.
1058             #pod
1059 0         0 #pod =cut
1060 0         0  
1061             my ($self) = @_;
1062 0 0       0  
  0 0       0  
    0          
1063 0         0 return $self->login unless my $user = $self->param('current_user');
1064 0         0  
1065 0         0 return $self->redirect_root("No such entry...")
1066             unless $self->get_entry;
1067 0   0     0  
1068             return $self->redirect_root("Not your entry...")
1069 0         0 unless $self->param('entry')->user eq $user;
1070              
1071             $self->param('entry')->delete;
1072              
1073             my $goto = $self->query->param('then_goto')
1074             || Rubric::WebApp::URI->entries({ username => $user });
1075              
1076             return $self->redirect( $goto, "Deleted..." );
1077             }
1078              
1079 3     3 1 62 #pod =head2 doc
1080             #pod
1081 3         14 #pod This runmode returns a mostly-static document from the template path.
1082             #pod
1083             #pod =cut
1084              
1085             my ($self) = @_;
1086              
1087             $self->get_doc;
1088             my $output = eval { $self->template("docs/" . $self->param('doc_page')); };
1089              
1090             # XXX: this should instead redirect to a 404-page
1091             return $output ? $output : $self->redirect_root("no such document");
1092             }
1093              
1094             #pod =head2 get_doc
1095             #pod
1096             #pod This gets the next part of the path and puts it in the C<doc_page> parameter.
1097             #pod
1098             #pod =cut
1099              
1100             my ($self) = @_;
1101              
1102             my $doc_page = $self->next_path_part;
1103             return $doc_page =~ /^[\pL\d_]+$/ ? $self->param(doc_page => $doc_page)
1104 1     1 1 72 : ();
1105             }
1106 1 50       4  
1107             #pod =head2 style
1108 1 50       100 #pod
1109             #pod This runmode sends the named stylesheet from the CSS path.
1110             #pod
1111 1 50       112 #pod =cut
1112              
1113             my ($self) = @_;
1114 1         454  
1115             my $sheet = $self->next_path_part;
1116 1   33     42690  
1117             my $file = File::Spec->catfile('style', $sheet);
1118              
1119 1         59 $self->header_add(-type => 'text/css');
1120             my $tt = Template->new({
1121             INCLUDE_PATH => [
1122             Rubric::Config->template_path,
1123             File::Spec->catdir(File::ShareDir::dist_dir('Rubric'), 'templates'),
1124             ],
1125             });
1126              
1127             my $output;
1128             $tt->process($file, {}, \$output);
1129 1     1 1 65 return $output;
1130             }
1131 1         4  
1132 1         13 1;
  1         5  
1133              
1134              
1135 1 50       26 =pod
1136              
1137             =encoding UTF-8
1138              
1139             =head1 NAME
1140              
1141             Rubric::WebApp - the web interface to Rubric
1142              
1143             =head1 VERSION
1144              
1145 1     1 1 2 version 0.157
1146              
1147 1         5 =head1 SYNOPSIS
1148 1 50       18  
1149             use strict;
1150             use warnings;
1151             use Rubric::WebApp;
1152             Rubric::WebApp->new->run();
1153              
1154             It's a CGI::Application!
1155              
1156             =head1 DESCRIPTION
1157              
1158             Rubric::WebApp provides a CGI-based interface to Rubric data. It's built on
1159 1     1 1 67 top of CGI::Application, which does most of the boring work. This module's
1160             code sets up the dispatch tables and implements the responses to various
1161 1         4 queries.
1162              
1163 1         25 =head1 PERL VERSION
1164              
1165 1         7 This code is effectively abandonware. Although releases will sometimes be made
1166 1         64 to update contact info or to fix packaging flaws, bug reports will mostly be
1167             ignored. Feature requests are even more likely to be ignored. (If someone
1168             takes up maintenance of this code, they will presumably remove this notice.)
1169             This means that whatever version of perl is currently required is unlikely to
1170             change -- but also that it might change at any new maintainer's whim.
1171              
1172             =head1 REQUESTS DISPATCH
1173 1         914  
1174 1         7 Requests are I<mostly> path-based, though some involve form submission. The
1175 1         1792 basic dispatch table looks something like this:
1176              
1177             request | description | method called
1178             -------------+-------------------------------------------+--------------
1179             /login | log in to a user account | login
1180             /logout | log out | logout
1181             /preferences | view or change account settings | preferences
1182             /newuser | create a new user account | newuser
1183             /verify | verify a pending user account | verify
1184             /link | view the details of entries for a link | link
1185             /post | post or edit an entry (must be logged in) | post
1186             /edit | edit an entry (must be logged in) | edit
1187             /delete | delete an entry (must be logged in) | delete
1188             /entry/ENTRY | find and display the identified entry | entry
1189             /entries/Q | find and display results for query Q | entries
1190             /~USER/TAGS | see a user's entries for given tags | (in flux)
1191             /doc/PAGE | view the named page in the documentation | doc
1192             /style/PAGE | get the named style sheet | style
1193              
1194             If the system is private and no user is logged in, the default action is to
1195             display a login screen. If the system is public, or a user is logged in, the
1196             default action is to display entries.
1197              
1198             =head1 METHODS
1199              
1200             =head2 redirect($uri, $message)
1201              
1202             This method simplifies redirection; it redirects to the given URI, printing the
1203             given message as the body of the HTTP response.
1204              
1205             =head2 redirect_root($message)
1206              
1207             This is shorthand to redirect to the Rubric's root URI. It calls C<redirect>.
1208              
1209             =head2 cgiapp_init
1210              
1211             This method is called during CGI::Application's initialization. It sets up the
1212             session configuration.
1213              
1214             =head2 cgiapp_prerun
1215              
1216             This method is called before the selected runmode. It checks for a login,
1217             checks for updates to result-set paging, and starts processing the request
1218             path.
1219              
1220             =head2 next_path_part
1221              
1222             This method shifts the next item off of the request path and returns it.
1223              
1224             =head2 check_pager_data
1225              
1226             This method is called by C<cgiapp_init>, and sets up parameters used for paging
1227             entry listings. The following parameters are used:
1228              
1229             per_page - how many items per page; default 25, maximum 100; stored in session;
1230             page - which page to display; default 1
1231              
1232             =head2 template($template, \%variables)
1233              
1234             This method is used to render a template with both provided and default
1235             variables.
1236              
1237             Templates are rendered by calling the C<process> method on the template
1238             renderer, which is retrieved by calling the C<renderer> method on the WebApp.
1239              
1240             The following variables are passed by default:
1241              
1242             current_user - the currently logged-in user (a Rubric::User object)
1243             per_page - entries per page (see check_pager_data)
1244             page - which page (see check_pager_data)
1245              
1246             =head2 setup
1247              
1248             This method, called by CGI::Application's initialization process, sets up
1249             the dispatch table for requests, as described above.
1250              
1251             =head2 entries
1252              
1253             This passes off responsibility to the class named in the C<entries_query_class>
1254             configuration option. This option defaults to Rubric::WebApp::Entries.
1255              
1256             =head2 entry
1257              
1258             This displays the single requested entry.
1259              
1260             =head2 get_entry
1261              
1262             This method gets the next part of the path, assumes it to be a Rubric::Entry
1263             id, and puts the corresponding entry in the "entry" parameter.
1264              
1265             =head2 link
1266              
1267             This runmode displays entries that point to a given link, identified either by
1268             URI or MD5 sum.
1269              
1270             =head2 get_link
1271              
1272             This method look for a C<uri> or, failing that, C<url> query parameter. If
1273             found, it finds a Rubric::Link for that URI and puts it in the "link"
1274             parameter.
1275              
1276             =head2 tag_cloud
1277              
1278             =head2 calendar
1279              
1280             =head2 login
1281              
1282             If the user is logged in, this request is immediately redirected to the root of
1283             the Rubric site. Otherwise, a login form is provided.
1284              
1285             =head2 logout
1286              
1287             This run mode unsets the "current_user" parameter in the session and the WebApp
1288             object, then redirects the user to the root of the Rubric site.
1289              
1290             =head2 reset_password
1291              
1292             This run mode allows a user to request that his password be reset and emailled
1293             to him.
1294              
1295             =head2 setup_reset_code
1296              
1297             This routine gets a reset code for the user and emails it to him.
1298              
1299             =head2 preferences
1300              
1301             This method displays account information for the current user. Some account
1302             settings may be changed.
1303              
1304             =head2 update_user(\%prefs)
1305              
1306             This method will update the current user object with the changes in C<%prefs>,
1307             which is passed by the C<preferences> method.
1308              
1309             =head2 validate_prefs(\%prefs)
1310              
1311             Given a set of preference updates from a form submission, this method validates
1312             them and returns a description of the validation results. This method will
1313             probably be redesigned (possibly with Data::FormValidator) in the future.
1314             Don't count on its interface.
1315              
1316             =begin future
1317              
1318             sub validate_prefs {
1319             my ($self, $prefs) = @_;
1320             require Data::FormValidator;
1321              
1322             my $profile = {
1323             required => [qw(password)],
1324             optional => [qw(password_1 password_2 email)],
1325             constraints => {
1326             email => 'email',
1327             password_1 => {
1328             params => [qw(password_1 password_2)],
1329             constraint => sub { $_[0] eq $_[1] },
1330             }
1331             },
1332             dependency_groups => { new_password => [qw(password_1 password_2)] }
1333             };
1334              
1335             my $results = Data::FormValidator->check($prefs, $profile);
1336             }
1337              
1338             =end future
1339              
1340             =head2 newuser
1341              
1342             If the proper form information is present, this runmode creates a new user
1343             account. If not, it presents a form.
1344              
1345             If a user is already logged in, the user is redirected to the root of the
1346             Rubric.
1347              
1348             =head2 validate_newuser_form(\%newuser)
1349              
1350             Given a set of user data from a form submission, this method validates them and
1351             returns a description of the validation results. This method will probably be
1352             redesigned (possibly with Data::FormValidator) in the future. Don't count on
1353             its interface.
1354              
1355             =head2 create_newuser(\%newuser)
1356              
1357             This method creates a new user account from the given description. It sends
1358             the user a validation email (if needed) and displays an account creation page.
1359              
1360             =head2 send_reset_email_to($user)
1361              
1362             This method sends an email to the given user with a URI to reset his password.
1363              
1364             =head2 send_verification_email_to($user)
1365              
1366             This method sends a verification email to the given user.
1367              
1368             =head2 verify
1369              
1370             This runmode attempts to verify a user account. It expects a request to be
1371             in the form: C< /verify/username/verification_code >
1372              
1373             =head2 get_reset_code
1374              
1375             This gets the next part of the path and puts it in the C<reset_code>
1376             parameter.
1377              
1378             =head2 get_verification_code
1379              
1380             This gets the next part of the path and puts it in the C<verification_code>
1381             parameter.
1382              
1383             =head2 get_user
1384              
1385             This gets the next part of the path and puts it in the C<user> parameter.
1386              
1387             =head2 display_entries
1388              
1389             This method searches (with Rubric::Entry) for entries matching the requested
1390             user and tags. It pages the result (with C<page_entries>) and renders the
1391             resulting page with C<render_entries>.
1392              
1393             =head2 page_entries($iterator)
1394              
1395             Given a Class::DBI::Iterator, this method sets up parameters describing the
1396             current page. Most importantly, it retrieves an Iterator for the slice of
1397             entries representing the current page. The following parameters are set:
1398              
1399             entries - a Class::DBI::Iterator for the current page's entries
1400             count - the number of entries in the entire set
1401             pages - the number of pages the set spans
1402              
1403             =head2 render_entries
1404              
1405             This method renders a template to display the set of entries set up by
1406             C<page_entries>.
1407              
1408             =head2 edit
1409              
1410             If the user isn't logged in, it redirects to demand a login. If he is, it
1411             displays a post form, completed with the given entry's data.
1412              
1413             =head2 post
1414              
1415             This method wants to be simplified.
1416              
1417             If the user isn't logged in, it redirects to demand a login. If he is, it
1418             checks whether it can create a new entry. If so, it tries to. If not, it
1419             displays a form for doing so. If the user already has an entry for the given
1420             URI, the existing entry is passed to the form renderer.
1421              
1422             If a new entry is created, the user is redirected to his entry listing.
1423              
1424             =head2 post_form
1425              
1426             This method renders a form for the user to create a new entry.
1427              
1428             =head2 delete
1429              
1430             This method wants to be simplified. It's largely copied from C<post>.
1431              
1432             If the user isn't logged in, it redirects to demand a login. If he is, it
1433             checks whether the user has an entry for the given URI. If so, it's deleted.
1434              
1435             Either way, the user is redirected to his entry listing.
1436              
1437             =head2 doc
1438              
1439             This runmode returns a mostly-static document from the template path.
1440              
1441             =head2 get_doc
1442              
1443             This gets the next part of the path and puts it in the C<doc_page> parameter.
1444              
1445             =head2 style
1446              
1447             This runmode sends the named stylesheet from the CSS path.
1448              
1449             =head1 AUTHOR
1450              
1451             Ricardo SIGNES <rjbs@semiotic.systems>
1452              
1453             =head1 COPYRIGHT AND LICENSE
1454              
1455             This software is copyright (c) 2004 by Ricardo SIGNES.
1456              
1457             This is free software; you can redistribute it and/or modify it under
1458             the same terms as the Perl 5 programming language system itself.
1459              
1460             =cut