File Coverage

blib/lib/ClearPress/view.pm
Criterion Covered Total %
statement 361 426 84.7
branch 114 172 66.2
condition 79 123 64.2
subroutine 55 61 90.1
pod 34 34 100.0
total 643 816 78.8


line stmt bran cond sub pod time code
1             # -*- mode: cperl; tab-width: 8; indent-tabs-mode: nil; basic-offset: 2 -*-
2             # vim:ts=8:sw=2:et:sta:sts=2
3             #########
4             # Author: rmp
5             # Created: 2007-03-28
6             #
7             package ClearPress::view;
8 5     5   48546 use strict;
  5         16  
  5         224  
9 5     5   36 use warnings;
  5         11  
  5         191  
10 5     5   35 use base qw(Class::Accessor);
  5         12  
  5         489  
11 5     5   2674 use Template;
  5         96573  
  5         230  
12 5     5   2190 use Template::Filters;
  5         27557  
  5         196  
13 5     5   56 use ClearPress::util;
  5         15  
  5         72  
14 5     5   209 use Carp;
  5         14  
  5         379  
15 5     5   39 use English qw(-no_match_vars);
  5         16  
  5         46  
16 5     5   2272 use POSIX qw(strftime);
  5         15  
  5         47  
17 5     5   2723 use HTML::Entities qw(encode_entities_numeric);
  5         45253  
  5         546  
18 5     5   2628 use HTTP::Headers;
  5         53674  
  5         287  
19 5     5   2221 use HTTP::Status qw(:constants);
  5         27646  
  5         2509  
20 5     5   611 use XML::Simple qw(XMLin);
  5         9728  
  5         90  
21 5     5   3134 use utf8;
  5         90  
  5         36  
22 5     5   2781 use ClearPress::Localize;
  5         27  
  5         248  
23 5     5   2093 use MIME::Base64 qw(encode_base64);
  5         3732  
  5         395  
24 5     5   684 use JSON;
  5         12094  
  5         64  
25 5     5   1212 use Readonly;
  5         19  
  5         33078  
26              
27             our $VERSION = q[477.1.2];
28              
29             our $DEBUG_OUTPUT = 0;
30             our $DEBUG_L10N = 0;
31             our $TEMPLATE_CACHE = {};
32             our $LEXICON_CACHE = {};
33             our $TRAP_REDIR_OVERFLOW = 0; # set to non-zero value to cut-off at that many bytes
34              
35             __PACKAGE__->mk_accessors(qw(util model action aspect content_type entity_name autoescape charset decorator headers));
36              
37             sub new { ## no critic (Complexity)
38 80     80 1 50160 my ($class, $self) = @_;
39 80   100     5780 $self ||= {};
40 80         297 bless $self, $class;
41              
42 80         441 my $util = $self->util;
43 80 100       1917 my $username = $util ? $util->username : q[];
44 80         1424 $self->{requestor_username} = $username;
45 80 100       347 $self->{logged_in} = $username?1:0;
46 80         375 $self->{warnings} = [];
47 80         321 $self->{output_buffer} = [];
48 80         289 $self->{output_finished} = 0;
49 80         338 $self->{autoescape} = 1;
50              
51 80   100     417 my $aspect = $self->aspect || q[];
52              
53 80 100 66     2824 $self->{content_type} ||= ($aspect =~ /(?:rss|atom|ajax|xml)$/smx)?'text/xml':q[];
54 80 100 100     3335 $self->{content_type} ||= ($aspect =~ /(?:js|json)$/smx)?'application/json':q[];
55 80 50 66     514 $self->{content_type} ||= ($aspect =~ /_svg$/smx)?'image/svg+xml':q[];
56 80 50 66     653 $self->{content_type} ||= ($aspect =~ /_svgz$/smx)?'image/svg+xml':q[];
57 80 100 100     546 $self->{content_type} ||= ($aspect =~ /_png$/smx)?'image/png':q[];
58 80 100 100     509 $self->{content_type} ||= ($aspect =~ /_jpg$/smx)?'image/jpeg':q[];
59 80 50 66     525 $self->{content_type} ||= ($aspect =~ /_txt$/smx)?'text/plain':q[];
60 80 50 66     537 $self->{content_type} ||= ($aspect =~ /_csv$/smx)?'text/csv':q[];
61 80 50 66     564 $self->{content_type} ||= ($aspect =~ /_xls$/smx)?'application/vnd.ms-excel':q[];
62              
63 80         473 $self->setup_filters;
64              
65 80         447 $self->init;
66              
67 80         449 ClearPress::Localize->init($self->locales);
68              
69 80   100     5459 $self->{content_type} ||= 'text/html';
70              
71 80   100     605 $self->{charset} ||= 'UTF-8';
72 80   66     622 $self->{headers} ||= HTTP::Headers->new;
73              
74 80         1240 return $self;
75             }
76              
77             sub setup_filters {
78 80     80 1 200 my $self = shift;
79             $self->add_tt_filter('js_string', sub {
80 0     0   0 my $string = shift;
81 0 0       0 if(!defined $string) {
82 0         0 $string = q[];
83             }
84 0         0 $string =~ s/\r/\\r/smxg;
85 0         0 $string =~ s/\n/\\n/smxg;
86 0         0 $string =~ s/"/\\"/smxg;
87             # $string =~ s/'/\\'/smxg;
88 0         0 return $string;
89 80         770 });
90              
91             $self->add_tt_filter('xml_entity', sub {
92 4     4   1768 my $string = shift;
93 4 100       18 if(!defined $string) {
94 1         5 $string = q[];
95             }
96 4         25 return encode_entities_numeric($string),
97 80         612 });
98              
99             $self->add_tt_filter('base64', sub {
100 0     0   0 my $string = shift;
101 0 0       0 if(!defined $string) {
102 0         0 $string = q[];
103             }
104 0         0 return encode_base64($string),
105 80         505 });
106              
107 80         382 my $util = $self->util;
108              
109             $self->add_tt_filter('loc', [sub {
110              
111             return sub {
112 0         0 my ($string) = shift;
113              
114             #########
115             # Cache lexicons for
116             # speed. However, loading on-demand
117             # won't generally use shared memory
118             #
119 0         0 my $lang = ClearPress::Localize->lang;
120 0 0 0     0 if($lang && !$LEXICON_CACHE->{$lang}) {
121 0         0 $LEXICON_CACHE->{$lang} = ClearPress::Localize->localizer;
122             }
123              
124 0         0 my $loc = $string;
125             eval {
126 0         0 $loc = $util->{localizers}->{$lang}->maketext($string);
127 0         0 1;
128 0 0       0 } or do {
129 0 0       0 $DEBUG_L10N && carp qq[Could not localize $string to $lang];
130 0         0 1;
131             };
132              
133 0   0     0 return $loc || $string;
134 0     0   0 };
135 80         2073 }, 1]);
136              
137 80         203 return 1;
138             }
139              
140             sub init {
141 80     80 1 171 return 1;
142             }
143              
144             sub locales {
145 80     80 1 191 my $self = shift;
146 80         489 my $util = $self->util;
147             return {
148 80 100       1383 $util ? (q[*] => [Gettext => sprintf q[%s/po/*.po], $util->data_path] ) : (),
149             q[en] => ['Auto'],
150             };
151             }
152              
153             sub add_warning {
154 1     1 1 53 my ($self, $warning) = @_;
155 1         5 push @{$self->{warnings}}, $warning;
  1         6  
156 1         5 return 1;
157             }
158              
159             sub warnings {
160 11     11 1 131 my $self = shift;
161 11         92 return $self->{warnings};
162             }
163              
164             sub _accessor { ## no critic (ProhibitUnusedPrivateSubroutines)
165 2     2   3433 my ($self, $field, $val) = @_;
166 2         396 carp q[_accessor is deprecated. Use __PACKAGE__->mk_accessors(...) instead];
167 2 100       1745 if(defined $val) {
168 1         5 $self->{$field} = $val;
169             }
170 2         17 return $self->{$field};
171             }
172              
173             sub authorised {
174 25     25 1 116 my $self = shift;
175 25   50     156 my $action = $self->action || q[];
176 25   100     489 my $aspect = $self->aspect || q[];
177 25         412 my $util = $self->util;
178 25         363 my $requestor = $util->requestor;
179              
180 25 100       333 if(!$requestor) {
181             #########
182             # If there's no requestor user object then authorisation isn't supported
183             #
184 16         211 return 1;
185             }
186              
187 9 100 100     131 if($action =~ /^list/smx ||
      100        
188             ($action eq 'read' &&
189             $aspect !~ /^(?:add|edit|delete|update|create)/smx)) {
190             #########
191             # by default assume public read access for 'read' actions
192             #
193 2         17 return 1;
194              
195             } else {
196             #########
197             # by default allow only 'admin' group for non-read aspects (add, edit, create, update, delete)
198             #
199 7 100 66     93 if($requestor->can('is_member_of') &&
200             $requestor->is_member_of('admin')) {
201 3         67 return 1;
202             }
203             }
204              
205 4         57 return;
206             }
207              
208             sub template_name {
209 22     22 1 2271 my ($self, @args) = @_;
210              
211 22 100       125 if(scalar @args) {
212 1         5 $self->{template_override} = $args[0];
213             }
214              
215 22 100       158 if(exists $self->{template_override}) {
216 2         14 return $self->{template_override};
217             }
218              
219 20         268 my $name = $self->entity_name;
220 20 100       400 if(!$name) {
221 6         39 ($name) = (ref $self) =~ /view::(.*)$/smx;
222             }
223 20   100     104 $name ||= 'view';
224 20         141 my $method = $self->method_name;
225              
226 20         107 $name =~ s/:+/_/smxg;
227 20 100       86 if(!$method) {
228 1         6 return $name;
229             }
230              
231 19         124 my $util = $self->util;
232 19         353 my $tmp = "${name}/$method";
233 19         204 my $path = sprintf q[%s/templates], $util->data_path;
234              
235             #########
236             # I do not like this stat. I'd prefer a global mode switch in config.
237             #
238 19 100       659 if(-e "$path/$tmp.tt2") {
239 1         14 return $tmp;
240             }
241              
242 18         143 return "${name}_$method";
243             }
244              
245             sub method_name {
246 37     37 1 121 my $self = shift;
247 37         246 my $aspect = $self->aspect;
248 37         619 my $action = $self->action;
249 37   100     578 my $method = $aspect || $action;
250 37         179 my $model = $self->model;
251 37         701 my $pk = $model->primary_key;
252              
253 37 100       291 if(!$method) {
254 1         5 return q[];
255             }
256              
257 36 100 100     408 if($pk &&
      66        
258             $method eq 'read' &&
259             !$model->$pk()) {
260 1         19 $method = 'list';
261             }
262              
263 36         209 $method =~ s/__/_/smxg;
264              
265 36         220 return $method;
266             }
267              
268             sub streamed_aspects {
269 30     30 1 131 return [qw(options)];
270             }
271              
272             sub streamed {
273 32     32 1 85 my $self = shift;
274 32         127 my $aspect = $self->aspect;
275              
276 32         479 for my $str_aspect (@{$self->streamed_aspects}) {
  32         148  
277 32 100       135 if($aspect eq $str_aspect) {
278 2         10 return 1;
279             }
280             }
281 30         110 return;
282             }
283              
284             sub render {
285 17     17 1 60 my $self = shift;
286 17         89 my $util = $self->util;
287 17   100     296 my $aspect = $self->aspect || q[];
288 17         339 my $action = $self->action;
289              
290 17 50       296 if(!$util) {
291 0         0 croak q[No util object available];
292             }
293              
294 17         115 my $requestor = $util->requestor;
295              
296 17 50       268 if(!$self->authorised) {
297             #########
298             # set http forbidden response code
299             #
300 0         0 $self->headers->header('Status', HTTP_FORBIDDEN);
301              
302 0 0       0 if(!$requestor) {
303 0         0 croak q[Authorisation unavailable for this view.];
304             }
305              
306 0         0 my $username = $requestor->username;
307 0 0       0 if(!$username) {
308 0         0 croak q[You are not authorised for this view. You need to log in.];
309             }
310 0         0 croak qq[You ($username) are not authorised for this view];
311             }
312              
313             #########
314             # Figure out and call the appropriate action if available
315             #
316 17         174 my $method = $self->method_name;
317 17 50       162 if($method !~ /^(?:add|edit|create|read|update|delete|list|options)/smx) {
318 0         0 croak qq[Illegal method: $method];
319             }
320              
321 17 50       238 if($self->can($method)) {
322 17 50 33     166 if($aspect eq 'options' ||
323             $aspect =~ /_(?:jpg|png|gif|svg|svgz)/smx) {
324 0         0 return $self->$method();
325             }
326              
327             #########
328             # handle streamed methods
329             #
330 17         74 my $streamed = $self->streamed;
331              
332 17 100       93 if($streamed) {
333 1         14 $self->output_flush;
334             }
335              
336 17         101 $self->$method();
337              
338 17 100       97 if($streamed) {
339 1         18 $self->output_end;
340 1         6 return q[];
341             }
342              
343             } else {
344 0         0 croak qq[Unsupported method: $method];
345             }
346              
347 16         144 my $model = $self->model;
348 16         337 my $actions = my $warnings = q[];
349              
350 16 100       115 if($self->decor) {
351 8         73 $actions = $self->actions;
352             eval {
353 8         143 $self->process_template('warnings.tt2', {
354             warnings => $self->warnings,
355             }, \$warnings);
356              
357 8 50       30 } or do {
358             #########
359             # non-fatal warning - usually warnings.tt2 missing
360             #
361 0         0 carp "Warning: $EVAL_ERROR";
362             };
363             }
364              
365             #########
366             # handle block (non-streamed) methods
367             #
368 16         221 my $tmpl = $self->template_name;
369 16         106 my $cfg = $util->config;
370 16         73 my $content = q[];
371              
372 16         139 $self->process_template("$tmpl.tt2", {}, \$content);
373              
374 15   50     219 return $warnings . $actions . $content || q[No data];
375             }
376              
377             sub process_template { ## no critic (Complexity)
378 35     35 1 3890 my ($self, $template, $extra_params, $where_to_ref) = @_;
379 35         177 my $util = $self->util;
380 35         17909 my $cfg = $util->config;
381 35         419 my ($entity) = (ref $self) =~ /([^:]+)$/smx;
382 35   50     178 $entity ||= q[];
383 35   50     266 my $script_name = $ENV{SCRIPT_NAME} || q[];
384 35         462 my ($xfh, $xfp) = ($ENV{HTTP_X_FORWARDED_HOST}, $ENV{HTTP_X_FORWARDED_PORT});
385 35   100     429 my $http_host = ($xfh ? $xfh : $ENV{HTTP_HOST}) || q[localhost];
386 35   50     345 my $http_port = ($xfh ? $xfp : $ENV{HTTP_PORT}) || q[];
387 35 50 33     302 my $http_proto = $ENV{HTTP_X_FORWARDED_PROTO} || $ENV{HTTPS}?q[https]:q[http];
388 35 50       322 my $href = sprintf q[%s://%s%s%s%s],
    50          
389             $http_proto,
390             $http_host,
391             $http_port?":$http_port":q[],
392             $script_name,
393             ($script_name eq q[/])?q[]:q[/];
394              
395             my $cfg_globals = {
396             (map {
397 35         301 $_ => $cfg->val('globals',$_)
  0         0  
398             } $cfg->Parameters('globals'))
399             };
400              
401             my $params = {
402             requestor => $util->requestor,
403             model => $self->model,
404             view => $self,
405             entity => $entity,
406             SCRIPT_NAME => $script_name,
407             HTTP_HOST => $http_host,
408             HTTP_PORT => $http_port,
409             HTTPS => $http_proto,
410             SCRIPT_HREF => $href,
411             ENTITY_HREF => "$href$entity",
412             now => (strftime '%Y-%m-%dT%H:%M:%S', localtime),
413 35         3072 %{$cfg_globals},
414 35 50       2020 %{$extra_params||{}},
  35         584  
415             };
416              
417              
418             my $appname = $util->config->val('application', 'name') ||
419             $util->config->val('application', 'namespace') ||
420 35   33     232 $ENV{SCRIPT_NAME};
421              
422 35   100     2860 $TEMPLATE_CACHE->{$appname} ||= {};
423 35         102 my $template_cache = $TEMPLATE_CACHE->{$appname};
424              
425 35 100       291 if(!$template_cache->{$template}) {
426 22         113 my $path = sprintf q[%s/templates], $util->data_path;
427 22 100       1566 open my $fh, q[<], "$path/$template" or croak qq[Error opening $template];
428 21         204 local $RS = undef;
429 21         488 $template_cache->{$template} = <$fh>;
430 21 50       391 close $fh or croak qq[Error closing $template];
431             }
432              
433 34         155 $template = \$template_cache->{$template};
434              
435 34 50       137 if($where_to_ref) {
436 34 50       245 $self->tt->process($template, $params, $where_to_ref) or croak $self->tt->error;
437              
438             } else {
439 0 0       0 $self->tt->process($template, $params, $where_to_ref) or croak $self->tt->error;
440             }
441              
442 34         259640 return 1;
443             }
444              
445             sub _populate_from_cgi {
446 14     14   40 my $self = shift;
447 14         66 my $util = $self->util;
448 14         200 my $model = $self->model;
449 14         292 my $cgi = $util->cgi;
450              
451             #########
452             # Populate model object with parameters posted into CGI
453             # by default (in controller.pm) model will only have util & its primary_key.
454             #
455 14         108 $model->read;
456              
457 14         106 my $pk = $model->primary_key;
458              
459 14         111 my @fields = $model->fields;
460 14 50       108 if($pk) {
461             #########
462             # don't leave primary key in field list
463             #
464 14         55 @fields = grep { $_ ne $pk } @fields;
  55         291  
465             }
466              
467             my $params = {
468             map { ## no critic (ProhibitComplexMappings)
469 14         94 my $p = $cgi->param($_);
  17         442  
470 17         830 utf8::decode($p);
471 17         191 $_ => $p;
472             } $cgi->param
473             };
474              
475             #########
476             # parse new-style POST payload
477             # todo: look at PUTDATA as well
478             #
479 14         116 my $postdata = $cgi->param('POSTDATA');
480 14 100       515 if($postdata) {
481 6         65 utf8::decode($postdata);
482             eval {
483 6         134 my $json = JSON->new->utf8;
484             eval {
485 6         140 $params = $json->decode($postdata);
486 5         31 1;
487              
488 6 100       21 } or do {
489 1         12 $params = XMLin($postdata);
490             };
491              
492 6         5189 for my $k (%{$params}) {
  6         34  
493 20 0 33     76 if(ref $params->{$k} &&
      33        
494             ref $params->{$k} eq 'HASH' &&
495 0         0 !scalar keys %{$params->{$k}}) {
496 0         0 delete $params->{$k};
497             }
498             }
499 6         66 1;
500              
501 6 50       15 } or do {
502             #########
503             # Not an XML-formatted POST body. Ignore for now.
504             #
505 0         0 carp q[Got error while parsing POSTDATA: ].$EVAL_ERROR;
506             };
507             }
508              
509             #########
510             # parse old-style XML POST payload
511             #
512 14         71 my $xml = $cgi->param('XForms:Model');
513 14 100       398 if($xml) {
514 1         9 utf8::decode($xml);
515 1         7 $params = XMLin($xml);
516 1         77190 for my $k (%{$params}) {
  1         5  
517 4 0 33     12 if(ref $params->{$k} &&
      33        
518             ref $params->{$k} eq 'HASH' &&
519 0         0 !scalar keys %{$params->{$k}}) {
520 0         0 delete $params->{$k};
521             }
522             }
523             }
524              
525 14         57 for my $field (@fields) {
526 41 100       321 if(!exists $params->{$field}) {
527 28         62 next;
528             }
529 13         47 my $v = $params->{$field};
530              
531             #########
532             # $v here will always be defined
533             # but may be false, e.g. $v=q[] or $v=q[0]
534             #
535 13 50       79 if($self->autoescape) {
536 13         293 $v = $cgi->escapeHTML($v);
537             }
538              
539 13         2269 $model->$field($v);
540             }
541              
542 14         267 return 1;
543             }
544              
545             sub add {
546 1     1 1 49 my $self = shift;
547 1         8 return $self->_populate_from_cgi;
548             }
549              
550             sub edit {
551 0     0 1 0 my $self = shift;
552 0         0 return $self->_populate_from_cgi;
553             }
554              
555             sub options {
556 0     0 1 0 return 1;
557             }
558              
559             sub list {
560 5     5 1 9 return 1;
561             }
562              
563             sub read { ## no critic (homonym)
564 3     3 1 1151 return 1;
565             }
566              
567             sub delete { ## no critic (homonym)
568 2     2 1 1107 my $self = shift;
569 2         11 my $model = $self->model;
570              
571 2 50       39 $model->delete or croak qq[Failed to delete entity: $EVAL_ERROR];
572              
573 2         22 return 1;
574             }
575              
576             sub update {
577 7     7 1 1000 my $self = shift;
578 7         67 my $model = $self->model;
579              
580             #########
581             # Populate model object with parameters posted into CGI
582             # by default (in controller.pm) model will only have util & its primary_key.
583             #
584 7         127 $self->_populate_from_cgi;
585              
586 7 50       60 $model->update or croak qq[Failed to update entity: $EVAL_ERROR];
587 7         73 return 1;
588             }
589              
590             sub create {
591 6     6 1 1018 my $self = shift;
592 6         32 my $model = $self->model;
593              
594             #########
595             # Populate model object with parameters posted into CGI
596             # by default (in controller.pm) model will only have util & its primary_key.
597             #
598 6         128 $self->_populate_from_cgi;
599              
600 6 50       52 $model->create or croak qq[Failed to create entity: $EVAL_ERROR];
601              
602 6         46 return 1;
603             }
604              
605             sub add_tt_filter {
606 320     320 1 1362 my ($self, $name, $code) = @_;
607              
608 320 50 33     1540 if(!$name || !$code) {
609 0         0 return;
610             }
611              
612 320         889 $self->tt_filters->{$name} = $code;
613              
614 320         739 return 1;
615             }
616              
617             sub tt_filters {
618 331     331 1 705 my $self = shift;
619              
620 331 100       941 if(!$self->{tt_filters}) {
621 80         361 $self->{tt_filters} = {};
622             }
623              
624 331         1631 return $self->{tt_filters};
625             }
626              
627             sub tt_opts {
628 9     9 1 30 return {};
629             }
630              
631             sub tt {
632 36     36 1 135 my ($self, $tt) = @_;
633 36         168 my $util = $self->util;
634              
635 36 50       814 if($tt) {
636 0         0 $util->{tt} = $tt;
637             }
638              
639 36 100       182 if(!$util->{tt}) {
640 9         123 my $filters = Template::Filters->new({
641             FILTERS => $self->tt_filters,
642             });
643 9         670 my $opts = $self->tt_opts;
644 9   33     52 my $ns = $util->config->val('application', 'namespace') ||
645             $util->config->val('application', 'name');
646 9 50       371 my $plugin_base = $ns ? q[ClearPress::Template::Plugin] : sprintf q[%s::plugin], $ns;
647 9         51 my $defaults = {
648             PLUGIN_BASE => $plugin_base,
649             RECURSION => 1,
650             INCLUDE_PATH => (sprintf q[%s/templates], $util->data_path),
651             EVAL_PERL => 1,
652             ENCODING => 'utf8',
653             LOAD_FILTERS => [ $filters ],
654             };
655              
656 9         191 while (my ($k, $v) = each %{$defaults}) {
  63         250  
657 54 50       172 if(!exists $opts->{$k}) {
658 54         153 $opts->{$k} = $v;
659             }
660             }
661              
662 9 50       99 $util->{tt} = Template->new($opts) or croak $Template::ERROR;
663             }
664 36         133417 return $util->{tt};
665             }
666              
667             sub decor {
668 49     49 1 171 my $self = shift;
669 49   100     234 my $aspect = $self->aspect || q[];
670              
671 49         2838 for my $ending (qw(rss atom ajax xml
672             json js _png _jpg _svg _svgz
673             _txt _csv _xls)) {
674 401 100       1419 if((substr $aspect, -length $ending, length $ending) eq $ending) {
675 26         205 return 0;
676             }
677             }
678 23         168 return 1;
679             }
680              
681             sub output_flush {
682 19     19 1 66 my ($self) = @_;
683 19 100       76 $DEBUG_OUTPUT and carp "output_flush: @{[scalar @{$self->{output_buffer}}]} blobs in queue";
  1         5  
  1         165  
684              
685             eval {
686 19 50       49 print grep { $_ } @{$self->{output_buffer}} or croak $ERRNO;
  57         18117  
  19         81  
687 19         767 1;
688              
689 19 50       107 } or do {
690             #########
691             # client stopped receiving (e.g. disconnect from lengthy streamed response)
692             #
693 0         0 carp qq[Error flushing output_buffer: $EVAL_ERROR];
694             };
695              
696 19         131 $self->output_reset;
697 19         67 return 1;
698             }
699              
700             sub output_prepend {
701 15     15 1 1862 my ($self, @args) = @_;
702 15 100       62 if(!$self->output_finished) {
703 14 50 33     164 if(scalar @args == 2 && $args[1] eq "\n" && !$args[0]) {
      33        
704 0         0 return;
705             }
706 14         34 unshift @{$self->{output_buffer}}, grep { $_ } @args; # don't push undef or ""
  14         63  
  28         98  
707 14 50       52 $DEBUG_OUTPUT and carp "output_prepend prepended (@{[scalar @args]} blobs)";
  0         0  
708             }
709 15         54 return 1;
710             }
711              
712             sub output_buffer {
713 33     33 1 5884 my ($self, @args) = @_;
714 33 100       180 if(!$self->output_finished) {
715 31 50 66     173 if(scalar @args == 2 && $args[1] eq "\n" && !$args[0]) {
      66        
716 0         0 return;
717             }
718              
719 31         87 push @{$self->{output_buffer}}, grep { $_ } @args; # don't push undef or ""
  31         127  
  32         140  
720 31 100       127 $DEBUG_OUTPUT and carp "output_buffer added (@{[scalar @args]} blobs)";
  2         412  
721             }
722 33         408 return 1;
723             }
724              
725             sub output_finished {
726 66     66 1 260 my ($self, $val) = @_;
727 66 100       217 if(defined $val) {
728 18         56 $self->{output_finished} = $val;
729 18 100       217 $DEBUG_OUTPUT and carp "output_finished = $val";
730             }
731 66         488 return $self->{output_finished};
732             }
733              
734             sub output_end {
735 18     18 1 64 my $self = shift;
736 18 100       234 $DEBUG_OUTPUT and carp "output_end: $self";
737 18         155 $self->output_finished(1);
738 18         103 return $self->output_flush;
739             }
740              
741             sub output_reset {
742 23     23 1 80 my $self = shift;
743 23         102 $self->{output_buffer} = [];
744 23 100       336 $DEBUG_OUTPUT and carp 'output_reset';
745 23         425 return;
746             }
747              
748             sub actions {
749 9     9 1 31 my $self = shift;
750 9         29 my $content = q[];
751              
752 9         86 $self->process_template('actions.tt2', {}, \$content);
753 9         52 return $content;
754             }
755              
756             sub redirect {
757 0     0 1   my ($self, $url, $status) = @_;
758              
759 0           $self->headers->header('Status', HTTP_FOUND);
760 0           $self->headers->header('Location', $url);
761              
762             #########
763             # - reset all previously output but unflushed content
764             # - push headers down the pipe, and html redirects
765             # - finish up
766             #
767 0           $self->output_reset();
768              
769 0 0         if($TRAP_REDIR_OVERFLOW) {
770 0 0         if(length $self->headers->as_string > $TRAP_REDIR_OVERFLOW) { # fudge for apparent buffer overflow with apache+mod_perl (ParseHeaders related?)
771 0           carp q[warning: header block looks long];
772 0           $self->headers->remove_header('Location');
773 0           $self->headers->header('Status', HTTP_OK);
774             }
775             }
776              
777 0           $self->output_buffer($self->headers->as_string, "\n");
778 0           $self->decorator->meta_refresh(qq[0;URL='$url']);
779              
780             #########
781             # clean everything up and terminate
782             #
783 0           $self->output_flush();
784 0           $self->headers->clear();
785              
786             ########
787             # Warning: This ought to correspond to content-type, but doesn't!
788             #
789             return <<"EOT"
790            

This document has moved here.

791            
792             EOT
793 0           }
794              
795             #########
796             # automated method generation for core CRUD+ view methods
797             #
798             BEGIN {
799 5     5   69 no strict 'refs'; ## no critic (ProhibitNoStrict)
  5         19  
  5         744  
800 5     5   29 for my $ext (qw(xml ajax json csv)) {
801 20         59 for my $method (qw(create list read update delete options)) {
802 120         412 my $ns = sprintf q[%s_%s], $method, $ext;
803 120     24   525 *{$ns} = sub { my $self = shift; return $self->$method; };
  120         985  
  24         16590  
  24         118  
804             }
805             }
806             }
807              
808             1;
809             __END__