File Coverage

blib/lib/meon/Web/Controller/Root.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package meon::Web::Controller::Root;
2 2     2   8534300 use Moose;
  0            
  0            
3             use namespace::autoclean;
4             use 5.010;
5              
6             use Path::Class 'file', 'dir';
7             use meon::Web::SPc;
8             use meon::Web::Config;
9             use meon::Web::Util;
10             use meon::Web::env;
11             use XML::LibXML 1.70;
12             use URI::Escape 'uri_escape';
13             use IO::Any;
14             use Class::Load 'load_class';
15             use File::MimeInfo 'mimetype';
16             use Scalar::Util 'blessed';
17             use DateTime::Format::HTTP;
18             use Imager;
19             use URI::Escape 'uri_escape';
20             use List::MoreUtils 'none';
21              
22             use meon::Web::Form::Login;
23             use meon::Web::Form::Delete;
24             use meon::Web::Member;
25             use meon::Web::TimelineEntry;
26              
27              
28             BEGIN { extends 'Catalyst::Controller' }
29              
30             __PACKAGE__->config(namespace => '');
31              
32             sub auto : Private {
33             my ( $self, $c ) = @_;
34              
35             meon::Web::env->clear;
36             meon::Web::env->stash($c->stash);
37              
38             my $uri = $c->req->uri;
39             my $hostname = $uri->host;
40             meon::Web::env->hostname($hostname);
41             my $hostname_dir_name = meon::Web::env->hostname_dir_name;
42             $c->detach('/status_not_found', ['no such domain '.$hostname.' configured'])
43             unless $hostname_dir_name;
44              
45             my $hostname_dir = $c->stash->{hostname_dir} = meon::Web::env->hostname_dir;
46              
47             my $template_file = file($hostname_dir, 'template', 'xsl', 'default.xsl')->stringify;
48             $c->stash->{template} = XML::LibXML->load_xml(location => $template_file);
49              
50             $c->default_auth_store->folder(meon::Web::env->profiles_dir);
51             meon::Web::env->user($c->user);
52              
53             # set cookie domain
54             my $cookie_domain = $hostname;
55             my $config_cookie_domain = meon::Web::env->hostname_config->{'main'}{'cookie-domain'};
56              
57             if ($config_cookie_domain && (substr($hostname,0-length($config_cookie_domain)) eq $config_cookie_domain)) {
58             $cookie_domain = $config_cookie_domain;
59             }
60              
61             $c->_session_plugin_config->{cookie_domain} = $cookie_domain;
62             $c->change_session_expires( 30*24*60*60 )
63             if $c->session->{remember_login};
64              
65             return 1;
66             }
67              
68             sub static : Path('/static') {
69             my ($self, $c) = @_;
70              
71             my $static_file = file(@{$c->static_include_path}, $c->req->path);
72             $c->detach('/status_not_found', [($c->debug ? $static_file : '')])
73             unless -e $static_file;
74              
75             my $mime_type = mimetype($static_file->stringify);
76             $c->res->content_type($mime_type);
77             $c->res->body(IO::Any->read([$static_file]));
78             }
79              
80             sub default :Path {
81             my ( $self, $c ) = @_;
82             $c->forward('resolve_xml', []);
83             }
84              
85             sub resolve_xml : Private {
86             my ( $self, $c ) = @_;
87              
88             my $hostname_dir = $c->stash->{hostname_dir};
89             my $path =
90             delete($c->session->{post_redirect_path})
91             || $c->stash->{path}
92             || $c->req->uri;
93             $path = URI->new($path)
94             unless blessed($path);
95              
96             # replace …/index by …/ in url
97             if ($path =~ m{/index$}) {
98             my $new_uri = $c->req->uri;
99             $new_uri->path(substr($path->path,0,-5));
100             $c->res->redirect($new_uri->absolute);
101             $c->detach;
102             }
103              
104             meon::Web::env->current_path(file($path->path));
105             my $xml_file = file(meon::Web::env->content_dir, $path->path_segments);
106             $xml_file .= 'index' if ($xml_file =~ m{/$});
107             $xml_file .= '.xml';
108              
109             # add trailing slash and redirect when uri points to a folder
110             if ((! -f $xml_file) && (-d substr($xml_file,0,-4))) {
111             my $new_uri = $c->req->uri;
112             $new_uri->path($path->path.'/');
113             $c->res->redirect($new_uri->absolute);
114             $c->detach;
115             }
116              
117             if ((! -f $xml_file) && (-f substr($xml_file,0,-4))) {
118             my $static_file = file(substr($xml_file,0,-4));
119             my $mtime = $static_file->stat->mtime;
120             if (!$c->req->param('t')) {
121             $c->res->redirect($c->req->uri_with({t => $mtime})->absolute);
122             $c->detach;
123             }
124              
125             my $max_age = 365*24*60*60;
126             $c->res->header('Cache-Control' => 'max-age='.$max_age.', private');
127             $c->res->header(
128             'Expires' => DateTime::Format::HTTP->format_datetime(
129             DateTime->now->add(seconds => $max_age)
130             )
131             );
132             $c->res->header(
133             'Last-Modified' => DateTime::Format::HTTP->format_datetime(
134             DateTime->from_epoch(epoch => $mtime)
135             )
136             );
137              
138             my $mime_type = mimetype($static_file->basename);
139             $c->res->content_type($mime_type);
140             $c->res->body($static_file->open('r'));
141             $c->detach;
142             }
143              
144             unless (-e $xml_file) {
145             my $not_found_handler = meon::Web::env->hostname_config->{'main'}{'not-found-handler'};
146             if ($not_found_handler) {
147             load_class($not_found_handler);
148             my $content_dir = meon::Web::env->content_dir;
149             my $relative_path = $xml_file;
150             $relative_path =~ s/^$content_dir//;
151             die 'forbidden' if $path eq $relative_path;
152             $not_found_handler->check($content_dir, $relative_path);
153             }
154             $c->detach('/status_not_found', [($c->debug ? $path.' '.$xml_file : $path)])
155             unless -e $xml_file;
156             }
157              
158             $xml_file = file($xml_file);
159             $c->stash->{xml_file} = $xml_file;
160             meon::Web::env->xml_file($xml_file);
161              
162             my $dom = meon::Web::env->xml;
163             my $xpc = meon::Web::Util->xpc;
164              
165             $c->model('ResponseXML')->dom($dom);
166              
167             $c->model('ResponseXML')->push_new_element('current-path')->appendText($c->req->uri->path);
168             $c->model('ResponseXML')->push_new_element('static-mtime')->appendText(meon::Web::env->static_dir_mtime);
169             $c->model('ResponseXML')->push_new_element('run-env')->appendText(Run::Env->current);
170              
171             # user
172             if ($c->user_exists) {
173             my $user_el = $c->model('ResponseXML')->create_element('user');
174              
175             my $user_el_username = $c->model('ResponseXML')->create_element('username');
176             $user_el_username->appendText($c->user->username);
177             $user_el->appendChild($user_el_username);
178              
179             my @user_roles = $c->user->roles;
180             my $roles_el = $c->model('ResponseXML')->create_element('roles');
181             foreach my $role (@user_roles) {
182             $roles_el->appendChild(
183             $c->model('ResponseXML')->create_element($role)
184             );
185             }
186             $user_el->appendChild($roles_el);
187              
188             my $member = $c->member;
189             my $full_name_el = $c->model('ResponseXML')->create_element('full-name');
190             $full_name_el->appendText($member->get_member_meta('full-name'));
191             $user_el->appendChild($full_name_el);
192              
193             $c->model('ResponseXML')->append_xml($user_el);
194              
195             my @access_roles = map { $_->textContent } $xpc->findnodes('/w:page/w:meta/w:access/w:role',$dom);
196             if (@access_roles && (none { $_ ~~ \@user_roles } @access_roles)) {
197             $c->detach('/status_forbidden', []);
198             }
199             }
200             else {
201             if ($xpc->findnodes('/w:page/w:meta/w:members-only',$dom)) {
202             $c->detach('/login', []);
203             }
204             }
205              
206             # redirect
207             my ($redirect) = $xpc->findnodes('/w:page/w:meta/w:redirect', $dom);
208             if ($redirect) {
209             $redirect = $redirect->textContent;
210             my $redirect_uri = $c->traverse_uri($redirect);
211             $redirect_uri = $redirect_uri->absolute
212             if $redirect_uri->can('absolute');
213             $c->res->redirect($redirect_uri);
214             $c->detach;
215             }
216              
217             # forms
218             if (my ($form_el) = $xpc->findnodes('/w:page/w:meta/w:form',$dom)) {
219             my $skip_form = 0;
220             if ($xpc->findnodes('w:owner-only',$form_el)) {
221             $skip_form = 1;
222             if ($c->user_exists) {
223             my $member = $c->member;
224             my $member_folder = $member->dir;
225              
226             $skip_form = 0
227             if $member_folder->contains($xml_file);
228             }
229             }
230              
231             unless ($skip_form) {
232             my $back_link = delete $c->req->params->{_back_link};
233             if (defined($back_link)) {
234             $c->model('ResponseXML')->push_new_element('back-link')->appendText($back_link);
235             $c->stash->{back_link} = $back_link;
236             }
237             my ($form_class) = 'meon::Web::Form::'.$xpc->findnodes('/w:page/w:meta/w:form/w:process', $dom);
238             load_class($form_class);
239             my $form = $form_class->new(c => $c);
240             my $params = $c->req->body_parameters;
241             foreach my $field ($form->fields) {
242             next if $field->type ne 'Upload';
243             my $field_name = $field->name;
244             $params->{$field_name} = $c->req->upload($field_name)
245             if $c->req->params->{$field_name};
246             }
247             $form->process(params=>$params);
248             $form->submitted
249             if $form->is_valid && $form->can('submitted') && ($c->req->method eq 'POST');
250             $c->model('ResponseXML')->add_xhtml_form(
251             $form->render
252             );
253              
254             if (my $form_input_errors = delete $c->session->{form_input_errors}) {
255             foreach my $input_name (keys %$form_input_errors) {
256             my ($input) = $xpc->findnodes(
257             './/x:input[@name="'.$input_name.'"]'
258             .'|.//x:select[@name="'.$input_name.'"]'
259             .'|.//x:textarea[@name="'.$input_name.'"]'
260             ,$c->model('ResponseXML')->dom
261             );
262             next unless $input;
263             $input->setAttribute('class' => $input->hasAttribute('class') ? $input->getAttribute('class').' error' : 'error');
264             my $span = $input->parentNode->addNewChild($input->namespaceURI, 'span');
265             $span->setAttribute('class' => 'help-inline');
266             $span->appendText($form_input_errors->{$input_name});
267             my $error_class = 'error';
268             my $div = $input->parentNode;
269             if ($div->getAttribute('class') // '' eq 'form-group') {
270             $error_class = 'has-error';
271             }
272             else {
273             $div->parentNode;
274             }
275             $div->setAttribute(
276             'class'
277             => ($div->hasAttribute('class') ? $div->getAttribute('class').' '.$error_class : $error_class)
278             );
279             }
280             }
281              
282             }
283             }
284              
285             # folder listing
286             my (@folder_elements) =
287             $xpc->findnodes('/w:page/w:content//w:dir-listing',$dom);
288             foreach my $folder_el (@folder_elements) {
289             my $folder_name = $folder_el->getAttribute('path');
290             my $reverse = $folder_el->getAttribute('reverse');
291             unless ($folder_name) {
292             $folder_el->appendText('path attribute missing');
293             next;
294             }
295             my $folder_rel = dir(meon::Web::Util->path_fixup($folder_name));
296             my $folder = dir($xml_file->dir, $folder_rel)->absolute;
297             next unless -d $folder;
298             $folder = $folder->resolve;
299             $c->detach('/status_forbidden', [])
300             unless $hostname_dir->contains($folder);
301              
302             my @folders = sort(grep { $_->is_dir } $folder->children(no_hidden => 1));
303             @folders = reverse @folders if $reverse;
304             my @files = sort(grep { not $_->is_dir } $folder->children(no_hidden => 1));
305             @files = reverse @files if $reverse;
306              
307             foreach my $file (@folders) {
308             $file = $file->basename;
309             my $file_el = $c->model('ResponseXML')->create_element('folder');
310             $file_el->setAttribute('href' => join('/', map { uri_escape($_) } $folder_rel->dir_list, $file));
311             $file_el->appendText($file);
312             $folder_el->appendChild($file_el);
313             }
314             foreach my $file (@files) {
315             $file = $file->basename;
316             my $file_el = $c->model('ResponseXML')->create_element('file');
317             $file_el->setAttribute('href' => join('/', map { uri_escape($_) } $folder_rel->dir_list, $file));
318             $file_el->appendText($file);
319             $folder_el->appendChild($file_el);
320             }
321             }
322              
323             # gallery listing
324             my (@galleries) = $xpc->findnodes('/w:page/w:content//w:gallery',$dom);
325             foreach my $gallery (@galleries) {
326             my $gallery_path = $gallery->getAttribute('href');
327             my $max_width = $gallery->getAttribute('thumb-width');
328             my $max_height = $gallery->getAttribute('thumb-height');
329              
330             my $folder_rel = dir(meon::Web::Util->path_fixup($gallery_path));
331             my $folder = dir($xml_file->dir, $folder_rel)->absolute;
332             die 'no pictures in '.$folder unless -d $folder;
333             $folder = $folder->resolve;
334             $c->detach('/status_forbidden', [])
335             unless $hostname_dir->contains($folder);
336              
337             my @files = sort(grep { not $_->is_dir } $folder->children(no_hidden => 1));
338              
339             foreach my $file (@files) {
340             $file = $file->basename;
341             next if $file =~ m/\.xml$/;
342             my $thumb_file = file(map { uri_escape($_) } $folder_rel->dir_list, 'thumb', $file);
343             my $img_file = file(map { uri_escape($_) } $folder_rel->dir_list, $file);
344             my $file_el = $c->model('ResponseXML')->create_element('img');
345             $file_el->setAttribute('src' => $img_file);
346             $file_el->setAttribute('src-thumb' => $thumb_file);
347             $file_el->setAttribute('title' => $file);
348             $file_el->setAttribute('alt' => $file);
349             $gallery->appendChild($file_el);
350              
351             # create thumbnail image
352             $thumb_file = file($xml_file->dir, $thumb_file);
353             unless (-e $thumb_file) {
354             $thumb_file->dir->mkpath
355             unless -e $thumb_file->dir;
356              
357             my $img = Imager->new(file => file($xml_file->dir, $img_file))
358             or die Imager->errstr();
359             if ($img->getwidth > $max_width) {
360             $img = $img->scale(xpixels => $max_width)
361             || die 'failed to scale image - '.$img->errstr;
362             }
363             if ($img->getheight > $max_height) {
364             $img = $img->scale(ypixels => $max_height)
365             || die 'failed to scale image - '.$img->errstr;
366             }
367             $img->write(file => $thumb_file->stringify) || die 'failed to save image - '.$img->errstr;
368             }
369             }
370             }
371              
372             # generate timeline
373             my ($timeline_el) = $xpc->findnodes('/w:page/w:content//w:timeline', $dom);
374             if ($timeline_el) {
375             my $timeline_class = $timeline_el->getAttribute('class') // 'folder';
376             my @entries_files;
377             foreach my $href_entry ($xpc->findnodes('w:timeline-entry[@href]', $timeline_el)) {
378             my $href = $href_entry->getAttribute('href');
379             $timeline_el->removeChild($href_entry);
380             my $path = file(meon::Web::Util->full_path_fixup($href).'.xml');
381             push(@entries_files,$path)
382             if -e $path;
383             }
384             @entries_files = $xml_file->dir->children(no_hidden => 1)
385             if $timeline_class eq 'folder';
386              
387             my @entries =
388             sort { $b->created <=> $a->created }
389             grep { eval { $_->element } }
390             map { meon::Web::TimelineEntry->new(file => $_) }
391             grep { $_->basename ne $xml_file->basename }
392             grep { !$_->is_dir }
393             @entries_files
394             ;
395              
396             foreach my $entry (@entries) {
397             my $entry_el = $entry->element;
398             my $intro = $entry->intro;
399             my $href = $entry->file->resolve;
400             return unless $href;
401             $href = substr($href,0,-4);
402             $href = substr($href,length($c->stash->{hostname_dir}.'/content'));
403             $entry_el->setAttribute('href' => $href);
404             if (defined($intro)) {
405             my $intro_snipped_el = $c->model('ResponseXML')->create_element('intro-snipped');
406             $entry_el->appendChild($intro_snipped_el);
407             $intro_snipped_el->appendText(length($intro) > 78 ? substr($intro,0,78).'…' : $intro);
408             }
409              
410             $timeline_el->appendChild($entry_el);
411             }
412              
413             if (my $older = $self->_older_entries($c)) {
414             my $older_el = $c->model('ResponseXML')->create_element('older');
415             $timeline_el->appendChild($older_el);
416             $older_el->setAttribute('href' => $older);
417             }
418             if (my $newer = $self->_newer_entries($c)) {
419             my $newer_el = $c->model('ResponseXML')->create_element('newer');
420             $timeline_el->appendChild($newer_el);
421             $newer_el->setAttribute('href' => $newer);
422             }
423             }
424              
425             # generate members list
426             my ($members_list_el) = $xpc->findnodes('/w:page/w:content//w:members-list', $dom);
427             if ($members_list_el) {
428             my %members_by_section;
429             my $active_only = $members_list_el->getAttribute('active-only');
430             foreach my $member (sort { $a->section cmp $b->section } meon::Web::env->all_members) {
431             next if ($active_only && !$member->is_active);
432             $member->shred_password;
433             my $sec = $member->section;
434             $members_by_section{$sec} //= [];
435             push(@{$members_by_section{$sec}}, $member);
436             }
437             foreach my $sec (sort keys %members_by_section) {
438             my $sec_el = $c->model('ResponseXML')->create_element('section');
439             $sec_el->setAttribute('name' => $sec);
440             $members_list_el->appendChild($sec_el);
441             foreach my $member (@{$members_by_section{$sec}}) {
442             my $meta = $member->member_meta;
443             $sec_el->appendChild($meta);
444              
445             my $username = $member->username;
446             my $username_el = $c->model('ResponseXML')->create_element('username');
447             $username_el->appendText($username);
448             $meta->appendChild($username_el);
449             my $status = $member->user->status;
450             my $status_el = $c->model('ResponseXML')->create_element('status');
451             $status_el->appendText($status);
452             $meta->appendChild($status_el);
453             }
454             }
455             }
456              
457             # generate exists
458             my (@exists) = (
459             $xpc->findnodes('//w:exists', $dom),
460             $xpc->findnodes('//w:exists', $c->stash->{template}),
461             );
462             foreach my $exist_el (@exists) {
463             my $href = $exist_el->getAttribute('href');
464             my $path = meon::Web::Util->full_path_fixup($href);
465             $exist_el->appendText(-e $path ? 1 : 0);
466             }
467              
468             # handle different templates
469             my ($template_node) = $xpc->findnodes('/w:page/w:meta/w:template', $dom);
470             if ($template_node) {
471             my $template_name = $template_node->textContent;
472             my $template_file = file($hostname_dir, 'template', 'xsl', $template_name.'.xsl')->stringify;
473             $c->detach('/status_not_found', ['no such template '.$template_name])
474             unless -f $template_file;
475             $c->stash->{template} = XML::LibXML->load_xml(location => $template_file);
476             }
477             }
478              
479             sub _older_entries {
480             my ( $self, $c ) = @_;
481             my $dir = $c->stash->{xml_file}->dir;
482             my $cur_dir = $dir->basename;
483             $dir = $dir->parent;
484             while ($cur_dir =~ m/^\d+$/) {
485             my @min_folders =
486             sort
487             grep { $_ < $cur_dir }
488             grep { m/^\d+$/ }
489             map { $_->basename }
490             grep { $_->is_dir }
491             $dir->children(no_hidden => 1)
492             ;
493              
494             if (@min_folders) {
495             # find the last folder of this folder
496             while (@min_folders) {
497             $dir = $dir->subdir(pop(@min_folders));
498             @min_folders =
499             sort
500             grep { m/^\d+$/ }
501             map { $_->basename }
502             grep { $_->is_dir }
503             $dir->children(no_hidden => 1)
504             ;
505             }
506             return $dir->relative($c->stash->{xml_file}->dir).'/';
507             }
508              
509             $cur_dir = $dir->basename;
510             $dir = $dir->parent;
511             }
512             }
513              
514             sub _newer_entries {
515             my ( $self, $c ) = @_;
516             my $dir = $c->stash->{xml_file}->dir;
517             my $cur_dir = $dir->basename;
518             $dir = $dir->parent;
519             while ($cur_dir =~ m/^\d+$/) {
520             my @max_folders =
521             sort
522             grep { $_ > $cur_dir }
523             grep { m/^\d+$/ }
524             map { $_->basename }
525             grep { $_->is_dir }
526             $dir->children(no_hidden => 1)
527             ;
528              
529             if (@max_folders) {
530             # find the first folder of this folder
531             while (@max_folders) {
532             $dir = $dir->subdir(shift(@max_folders));
533             @max_folders =
534             sort
535             grep { m/^\d+$/ }
536             map { $_->basename }
537             grep { $_->is_dir }
538             $dir->children(no_hidden => 1)
539             ;
540             }
541             return $dir->relative($c->stash->{xml_file}->dir).'/';
542             }
543              
544             $cur_dir = $dir->basename;
545             $dir = $dir->parent;
546             }
547             }
548              
549             sub status_forbidden : Private {
550             my ( $self, $c, $message ) = @_;
551              
552             $c->res->status(403);
553              
554             my $xml_file = file(meon::Web::env->content_dir, '403.xml');
555             if (-e $xml_file) {
556             $c->session->{post_redirect_path} = '/403';
557             $self->resolve_xml($c);
558             $c->model('ResponseXML')->push_new_element('error-message')->appendText($message)
559             if $message;
560             }
561             else {
562             $message = '403 - Forbidden: '.$c->req->uri."\n".($message // '');
563             $c->res->content_type('text/plain');
564             $c->res->body($message);
565             }
566             }
567              
568             sub status_not_found : Private {
569             my ( $self, $c, $message ) = @_;
570              
571             $c->res->status(404);
572              
573             my $xml_file = file(meon::Web::env->content_dir, '404.xml');
574             if (-e $xml_file) {
575             $c->session->{post_redirect_path} = '/404';
576             $self->resolve_xml($c);
577             $c->model('ResponseXML')->push_new_element('error-message')->appendText($message)
578             if $message;
579             }
580             else {
581             $message = '404 - Page not found: '.$c->req->uri."\n".($message // '');
582             $c->res->content_type('text/plain');
583             $c->res->body($message);
584             }
585             }
586              
587             sub logout : Local {
588             my ( $self, $c ) = @_;
589              
590             my $username = eval { $c->user->username };
591             $c->delete_session;
592             $c->log->info('logout user '.$username)
593             if $username;
594             return $c->res->redirect($c->uri_for('/'));
595             }
596              
597             sub login : Local {
598             my ( $self, $c ) = @_;
599              
600             my $token = $c->req->param('auth-token');
601             my $username = $c->req->param('username');
602             my $password = $c->req->param('password');
603             my $back_to = $c->req->param('back-to');
604             $c->session->{remember_login} = $c->req->param('remember_login');
605              
606             if ($c->action eq 'logout') {
607             return $c->res->redirect($c->uri_for('/'));
608             }
609             if ($c->user_exists && !$token) {
610             $back_to ||= '/';
611             return $c->res->redirect($c->uri_for($back_to));
612             }
613              
614             my $login_form = meon::Web::Form::Login->new(
615             action => $c->req->uri,
616             );
617              
618             # token authentication
619             if ($token) {
620             my $members_folder = $c->default_auth_store->folder;
621             my $member;
622             if (($token eq 'admin') && $c->user_exists) {
623             my @roles = $c->user->roles;
624             if ('admin' ~~ \@roles) {
625             $member = meon::Web::Member->new(
626             members_folder => $members_folder,
627             username => $username,
628             );
629             }
630             }
631             else {
632             $member = meon::Web::Member->find_by_token(
633             members_folder => $members_folder,
634             token => $token,
635             );
636             if ($member && !$member->is_active) {
637             $member = undef;
638             $login_form->add_form_error('Account not activated or expired.');
639             }
640             }
641              
642             if ($member) {
643             my $username = $member->username;
644             $c->set_authenticated($c->find_user({ username => $username }));
645             $c->log->info('user '.$username.' authenticated via token');
646             $c->change_session_id;
647             $c->session->{old_pw_not_required} = 1;
648             return $c->res->redirect(
649             $c->req->uri_with({
650             'auth-token' => undef,
651             'username' => undef,
652             })->absolute
653             );
654             }
655             else {
656             $login_form->add_form_error('Invalid authentication token.');
657             }
658             }
659             else {
660             $login_form->process(params=>$c->req->params);
661             if ($username =~ m/\@/) {
662             my $members_folder = $c->default_auth_store->folder;
663             my $member = meon::Web::Member->find_by_email(
664             members_folder => $members_folder,
665             email => $username,
666             );
667             $username = $member->user->username
668             if $member;
669             }
670             if ($username && $password && $login_form->is_valid) {
671             if (
672             $c->authenticate({
673             username => $username,
674             password => $password,
675             })
676             ) {
677             $c->log->info('user '.$username.' authenticated');
678             $c->change_session_id;
679             return $c->res->redirect($c->req->uri);
680             }
681             else {
682             $c->log->info('login of user '.$username.' fail');
683             $login_form->field('password')->add_error('authentication failed');
684             }
685             }
686             }
687              
688             $c->stash->{path} = URI->new('/login');
689             $c->forward('resolve_xml', []);
690             $c->model('ResponseXML')->add_xhtml_form(
691             $login_form->render
692             );
693             }
694              
695             sub exception : Path('/exception-test') {
696             die 'here';
697             }
698              
699             sub end : ActionClass('RenderView') {
700             my ($self, $c) = @_;
701              
702             my @errors = @{ $c->error };
703              
704             if (@errors) {
705             $c->response->status(500);
706              
707             my $message = join("\n", @errors);
708             $message ||= 'No output';
709              
710             my $xml_file = file(meon::Web::env->content_dir, '500.xml');
711             if (-e $xml_file) {
712             eval {
713             $c->session->{post_redirect_path} = '/500';
714             $c->forward('resolve_xml', []);
715             $c->model('ResponseXML')->push_new_element('error-message')->appendText($message)
716             if $message;
717             };
718             if ($@) {
719             $c->log->error($@);
720             return;
721             }
722             }
723             else {
724             $message = '500 - Internal server error: '.$c->req->uri."\n".($message // '');
725             $c->res->content_type('text/plain');
726             $c->res->body($message);
727             }
728             }
729              
730             while (my $error = shift(@{$c->error})) {
731             $c->log->error($error);
732             }
733             }
734              
735             __PACKAGE__->meta->make_immutable;
736              
737             1;