File Coverage

blib/lib/meon/Web/Form/TrainingCourse.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::Form::TrainingCourse;
2              
3 2     2   10309335 use meon::Web::Util;
  0            
  0            
4             use meon::Web::TimelineEntry;
5             use meon::Web::XML2Comment;
6             use Path::Class 'dir', 'file';
7             use Email::Sender::Simple qw(sendmail);
8             use Class::Load 'load_class';
9             use Run::Env;
10             use File::MimeInfo 'mimetype';
11              
12             use utf8;
13             use 5.010;
14              
15             use HTML::FormHandler::Moose;
16             extends 'HTML::FormHandler';
17             with 'meon::Web::Role::Form';
18              
19             has 'inputs' => (is => 'rw', isa => 'HashRef', default => sub {{}}, lazy => 1);
20              
21             sub _course_form {
22             my ($self) = @_;
23             my $xpc = meon::Web::env->xpc;
24             my $dom = meon::Web::env->xml;
25             my ($course_form) = $xpc->findnodes('//*[@id="course"]//x:form',$dom);
26             return $course_form;
27             }
28              
29             sub certificate_status {
30             my ($self) = @_;
31             return eval { $self->get_config_text('certificate-status') } // 'on-going';
32             }
33              
34             sub input_enabled {
35             my ($self) = @_;
36             my $cert_status = $self->certificate_status;
37             return 1 if $cert_status eq 'on-going';
38             return;
39             }
40              
41             before 'process' => sub {
42             my ($self) = @_;
43             my $c = $self->c;
44              
45             my $xpc = meon::Web::env->xpc;
46             my $dom = meon::Web::env->xml;
47              
48             my $course_form = $self->_course_form;
49             return unless $course_form;
50              
51             my (@headings) = $xpc->findnodes('.//x:h1|.//x:h2|.//x:h3|.//x:h4',$course_form);
52             my (@inputs) = $xpc->findnodes('.//x:input|.//x:select|.//x:textarea',$course_form);
53             my $step = eval { $self->get_config_text('step') } // 0;
54             my $step_done = eval { $self->get_config_text('step-done') } // 0;
55             my $cert_id = $self->get_config_text('certificate-id');
56             my $cert_status = $self->certificate_status;
57             my $forced_step = $c->req->param('step') // '';
58             if (length($forced_step) && ($forced_step <= $step_done)) {
59             $self->set_config_text('step' => $forced_step + 0);
60             $self->store_config;
61             $self->redirect($c->req->uri_with({step => undef})->absolute);
62             return;
63             }
64              
65             # build-up the content tree
66             foreach my $summary ($xpc->findnodes('//w:training-course-summary',$dom)) {
67             my $div_el = $c->model('ResponseXML')->create_xhtml_element('div');
68             $div_el->setAttribute(class=>'training-course-summary');
69             $summary->appendChild($div_el);
70              
71             my @inputs = $xpc->findnodes('.//x:input|.//x:select|.//x:textarea',$course_form);
72             my %input_hash;
73             foreach my $input (@inputs) {
74             my $input_name = $input->getAttribute('name');
75             my $input_value = eval { $self->get_config_text('user_'.$input_name) } // '';
76              
77             my ($label) = $xpc->findnodes('.//x:*[@for="'.$input->getAttribute('id').'"]',$course_form);
78             $input_name = $label->textContent
79             if $label;
80              
81             my $input_div_el = $c->model('ResponseXML')->create_xhtml_element('div');
82             $input_div_el->setAttribute(class=>'summary-item');
83             $div_el->appendChild($input_div_el);
84             $input_div_el->appendText("\n");
85             my $label_div_el = $c->model('ResponseXML')->create_xhtml_element('div');
86             $label_div_el->setAttribute(class=>'label');
87             $input_div_el->appendChild($label_div_el);
88             $input_div_el->appendText("\n");
89             my $text_pre_el = $c->model('ResponseXML')->create_xhtml_element('pre');
90             $input_div_el->appendChild($text_pre_el);
91             $input_div_el->appendText("\n");
92              
93             $label_div_el->appendText($input_name);
94             $text_pre_el->appendText($input_value);
95             $input_hash{$input_name} = $input_value;
96             }
97             $self->inputs(\%input_hash);
98             }
99              
100             # build-up the content tree
101             foreach my $tree ($xpc->findnodes('//w:training-course-tree',$dom)) {
102             my $div_el = $c->model('ResponseXML')->create_xhtml_element('div');
103             $div_el->setAttribute(class=>'content-tree');
104             $tree->appendChild($div_el);
105              
106             my $container = $div_el;
107             my $cur_level = 0;
108             my $tree_step = 0;
109             foreach my $heading (@headings) {
110             my $level = $heading->nodeName;
111             $level =~ s/[^\d]//g;
112             $level += 0;
113             next unless $level;
114              
115             if ($level > $cur_level) {
116             while ($level != $cur_level) {
117             $container = $container->lastChild // $container;
118             my $ul_el = $container->addNewChild($div_el->namespaceURI => 'ul');
119             $container = $ul_el;
120             $cur_level++
121             }
122             }
123             elsif ($level < $cur_level) {
124             while ($level != $cur_level) {
125             do {
126             $container = $container->parentNode;
127             } while ($container->nodeName ne 'ul');
128             $cur_level--
129             }
130             }
131              
132             my $li_el = $container->addNewChild($div_el->namespaceURI => 'li');
133             if ($tree_step == $step) {
134             my $b_el = $li_el->addNewChild($div_el->namespaceURI => 'b');
135             $b_el->setAttribute(class => 'current-step');
136             $b_el->appendText($heading->textContent);
137             }
138             elsif ($tree_step <= $step_done) {
139             my $a_el = $li_el->addNewChild($div_el->namespaceURI => 'a');
140             $a_el->setAttribute(href => '?step='.$tree_step);
141             $a_el->appendText($heading->textContent);
142             }
143             else {
144             $li_el->appendText($heading->textContent);
145             }
146             $tree_step++;
147             }
148             }
149              
150             # back/forward navigation
151             my $current = $headings[$step];
152             my $next = ($step+1 < @headings ? $headings[$step+1] : undef);
153             my $parent = $current->parentNode;
154              
155             if ($next) {
156             while (my $next_node = $next->nextSibling) {
157             $parent->removeChild($next_node);
158             }
159             $parent->removeChild($next);
160             }
161              
162             if ($step) {
163             my $prev = $headings[$step-1];
164             while (my $prev_node = $current->previousSibling) {
165             $parent->removeChild($prev_node);
166             }
167              
168             my $nav_back_el = $c->model('ResponseXML')->create_element('navigate-back');
169             $nav_back_el->appendText($prev->textContent);
170             $parent->appendChild(
171             $nav_back_el
172             );
173             }
174              
175             if ($next) {
176             my $nav_forward_el = $c->model('ResponseXML')->create_element('navigate-forward');
177             $nav_forward_el->appendText($next->textContent);
178             $parent->appendChild(
179             $nav_forward_el
180             );
181             }
182             elsif ($self->input_enabled) {
183             my $nav_forward_el = $c->model('ResponseXML')->create_element('navigate-finish');
184             $parent->appendChild(
185             $nav_forward_el
186             );
187             }
188              
189             # add dummy element to the top of the step
190             my $form_top_el = $c->model('ResponseXML')->create_element('form-top');
191             $parent->insertBefore(
192             $form_top_el,
193             $current,
194             );
195              
196             # populate input/select/textarea fields
197             foreach my $input (@inputs) {
198             my $input_name = $input->getAttribute('name');
199             my $input_value = eval { $self->get_config_text('user_'.$input_name) } // '';
200              
201             given ($input->nodeName) {
202             when ('select') {
203             my ($option) = $xpc->findnodes('.//x:option[@value="'.$input_value.'"]',$input);
204             $option->setAttribute('selected' => 'selected')
205             if $option;
206             }
207             when ('textarea') {
208             $input->removeChildNodes();
209             $input->appendText($input_value)
210             }
211             default {
212             $input->setAttribute(value => $input_value);
213             }
214             }
215              
216             unless ($self->input_enabled) {
217             $input->setAttribute(disabled => 'disabled');
218             }
219             }
220             };
221              
222             sub submitted {
223             my $self = shift;
224              
225             my $c = $self->c;
226             my $xpc = meon::Web::env->xpc;
227             my $dom = meon::Web::env->xml;
228             my %params = %{$c->req->params};
229             my $back = $params{back};
230             my $forward = $params{forward};
231             my $finish = $params{finish};
232              
233             my $course_form = $self->_course_form;
234             return unless $course_form;
235              
236             my $rcpt_to = $self->get_config_text('rcpt-to');
237             my $cert_id = eval { $self->get_config_text('certificate-id') };
238             my $cert_ver = eval { $self->get_config_text('certificate-version') };
239             my $post_process = eval { $self->get_config_text('post-process') };
240             my $now = DateTime->now(time_zone => 'UTC');
241              
242             # store/check inputs
243             my $all_required_set = 1;
244             my %inputs = map { $_->getAttribute('name') => $_ } $xpc->findnodes('.//x:input|.//x:select|.//x:textarea',$course_form);
245             foreach my $key (keys %params) {
246             next unless my $input = $inputs{$key};
247             my $value = $params{$key} // '';
248             $value =~ s/\r//g;
249             $value = undef if (length($value) == 0);
250             if (!defined($value) && $input->getAttribute('required')) {
251             $all_required_set = 0;
252             $c->session->{form_input_errors}->{$key} = 'Required';
253             }
254             if ($self->input_enabled) {
255             $self->set_config_text('user_'.$key => $value);
256             }
257             }
258              
259             # set correct step
260             if ($back || ($forward && $all_required_set)) {
261             my $step = eval { $self->get_config_text('step') } // 0;
262             my $step_done = eval { $self->get_config_text('step-done') } // 0;
263             $step-- if $back;
264             $step++ if $forward && eval { $xpc->findnodes('.//w:navigate-forward',$course_form)->size };
265             $self->set_config_text('step' => $step);
266             $self->set_config_text('step-done' => $step)
267             if $step_done < $step;
268             }
269             elsif (
270             $finish
271             && $self->input_enabled
272             && $all_required_set
273             && eval { $xpc->findnodes('.//w:navigate-finish',$course_form)->size }
274             ) {
275             my ($email_content) = map { $_->textContent } $xpc->findnodes('//x:*[@class="training-course-summary"]',$dom);
276             $email_content =
277             'from: '.$c->member->email."\n"
278             .'certificate: '.meon::Web::env->xml_file->dir."\n"
279             .'certificate-id: '.$cert_id."\n"
280             .'certificate-version: '.$cert_ver."\n"
281             .$email_content;
282             die 'failed to extract training course summary'
283             unless defined $email_content;
284             my $cert_status = 'submitted';
285              
286             my @attachments;
287             if ($post_process) {
288             load_class($post_process);
289             my ($new_cert_status, $new_email_content, $extras) = $post_process->post_process(
290             member => $c->member,
291             dom => $dom,
292             inputs => $self->inputs,
293             results_text => $email_content,
294             certificate_id => $cert_id,
295             certificate_version => $cert_ver,
296             now => $now,
297             );
298             $extras //= {};
299             $email_content = $new_email_content
300             if $new_email_content;
301             $cert_status = $new_cert_status
302             if $new_cert_status;
303              
304             if (my $attachment = delete $extras->{attachment}) {
305             push(@attachments, (ref $attachment eq 'ARRAY' ? @{$attachment} : $attachment ));
306             };
307             }
308              
309             my @email_headers = (
310             header_str => [
311             From => (
312             meon::Web::env->hostname_config->{'main'}{'support-email'}
313             || meon::Web::Config->get->{main}->{'no-reply-email'}
314             ),
315             To => $rcpt_to,
316             Subject => $cert_id.' finished',
317             ],
318             );
319             my @email_text = (
320             attributes => {
321             content_type => "text/plain",
322             charset => "UTF-8",
323             encoding => "8bit",
324             },
325             body_str => $email_content,
326             );
327             my $email;
328             if (@attachments) {
329             $email = Email::MIME->create(
330             @email_headers,
331             parts => [
332             Email::MIME->create(@email_text),
333             (
334             map {
335             my $filename = file(
336             ref($_) eq 'HASH'
337             ? $_->{filename}
338             : $_
339             );
340             my $basename = $filename->basename;
341             my $content_type = (
342             ref($_) eq 'HASH'
343             ? $_->{content_type}
344             : undef
345             ) // mimetype($basename) // 'application/octet-stream';
346             Email::MIME->create(
347             attributes => {
348             filename => $basename,
349             content_type => $content_type,
350             encoding => "base64",
351             name => $basename,
352             },
353             body => IO::Any->slurp($filename),
354             );
355             } @attachments
356             ),
357             ],
358             );
359             }
360             else {
361             $email = Email::MIME->create(
362             @email_headers,
363             @email_text,
364             );
365             }
366              
367             $self->set_config_text('certificate-status' => $cert_status);
368             $self->set_config_text('certificate-finish-datetime' => $now->strftime('%Y%m%d-%H%M%S'));
369             if (my ($report_pdf) = @attachments) {
370             $self->set_config_text('certificate-report' => $report_pdf->basename);
371             }
372              
373             if (Run::Env->dev) {
374             warn $email->as_string;
375             }
376             else {
377             sendmail($email->as_string);
378             }
379             }
380              
381             $self->store_config;
382              
383             $self->redirect($c->req->uri->absolute);
384             }
385              
386             no HTML::FormHandler::Moose;
387              
388             1;