File Coverage

blib/lib/ClearPress/view.pm
Criterion Covered Total %
statement 361 427 84.5
branch 114 172 66.2
condition 79 123 64.2
subroutine 55 61 90.1
pod 34 34 100.0
total 643 817 78.7


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   27447 use strict;
  5         9  
  5         137  
9 5     5   22 use warnings;
  5         34  
  5         119  
10 5     5   23 use base qw(Class::Accessor);
  5         7  
  5         316  
11 5     5   1208 use Template;
  5         60172  
  5         147  
12 5     5   1710 use Template::Filters;
  5         17414  
  5         137  
13 5     5   34 use ClearPress::util;
  5         8  
  5         57  
14 5     5   147 use Carp;
  5         9  
  5         285  
15 5     5   31 use English qw(-no_match_vars);
  5         10  
  5         46  
16 5     5   1733 use POSIX qw(strftime);
  5         8  
  5         37  
17 5     5   1764 use HTML::Entities qw(encode_entities_numeric);
  5         25085  
  5         372  
18 5     5   1813 use HTTP::Headers;
  5         31049  
  5         189  
19 5     5   1579 use HTTP::Status qw(:constants);
  5         17435  
  5         1770  
20 5     5   586 use XML::Simple qw(XMLin);
  5         7207  
  5         45  
21 5     5   2214 use utf8;
  5         67  
  5         26  
22 5     5   2063 use ClearPress::Localize;
  5         18  
  5         180  
23 5     5   1495 use MIME::Base64 qw(encode_base64);
  5         2800  
  5         281  
24 5     5   387 use JSON;
  5         7104  
  5         45  
25 5     5   708 use Readonly;
  5         10  
  5         19974  
26              
27             our $VERSION = q[476.4.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;
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 29507 my ($class, $self) = @_;
39 80   100     289 $self ||= {};
40 80         203 bless $self, $class;
41              
42 80         395 my $util = $self->util;
43 80 100       1552 my $username = $util ? $util->username : q[];
44 80         1104 $self->{requestor_username} = $username;
45 80 100       282 $self->{logged_in} = $username?1:0;
46 80         247 $self->{warnings} = [];
47 80         244 $self->{output_buffer} = [];
48 80         205 $self->{output_finished} = 0;
49 80         210 $self->{autoescape} = 1;
50              
51 80   100     327 my $aspect = $self->aspect || q[];
52              
53 80 100 66     1818 $self->{content_type} ||= ($aspect =~ /(?:rss|atom|ajax|xml)$/smx)?'text/xml':q[];
54 80 100 100     462 $self->{content_type} ||= ($aspect =~ /(?:js|json)$/smx)?'application/json':q[];
55 80 50 66     405 $self->{content_type} ||= ($aspect =~ /_svg$/smx)?'image/svg+xml':q[];
56 80 50 66     372 $self->{content_type} ||= ($aspect =~ /_svgz$/smx)?'image/svg+xml':q[];
57 80 100 100     392 $self->{content_type} ||= ($aspect =~ /_png$/smx)?'image/png':q[];
58 80 100 100     489 $self->{content_type} ||= ($aspect =~ /_jpg$/smx)?'image/jpeg':q[];
59 80 50 66     365 $self->{content_type} ||= ($aspect =~ /_txt$/smx)?'text/plain':q[];
60 80 50 66     353 $self->{content_type} ||= ($aspect =~ /_csv$/smx)?'text/csv':q[];
61 80 50 66     365 $self->{content_type} ||= ($aspect =~ /_xls$/smx)?'application/vnd.ms-excel':q[];
62              
63 80         326 $self->setup_filters;
64              
65 80         297 $self->init;
66              
67 80         246 ClearPress::Localize->init($self->locales);
68              
69 80   100     603 $self->{content_type} ||= 'text/html';
70              
71 80   100     493 $self->{charset} ||= 'UTF-8';
72 80   66     407 $self->{headers} ||= HTTP::Headers->new;
73              
74 80         782 return $self;
75             }
76              
77             sub setup_filters {
78 80     80 1 141 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         638 });
90              
91             $self->add_tt_filter('xml_entity', sub {
92 4     4   1160 my $string = shift;
93 4 100       14 if(!defined $string) {
94 1         2 $string = q[];
95             }
96 4         19 return encode_entities_numeric($string),
97 80         440 });
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         421 });
106              
107 80         297 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         1412 }, 1]);
136              
137 80         150 return 1;
138             }
139              
140             sub init {
141 80     80 1 138 return 1;
142             }
143              
144             sub locales {
145 80     80 1 147 my $self = shift;
146 80         265 my $util = $self->util;
147             return {
148 80 100       1215 $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 5 my ($self, $warning) = @_;
155 1         3 push @{$self->{warnings}}, $warning;
  1         6  
156 1         4 return 1;
157             }
158              
159             sub warnings {
160 11     11 1 38 my $self = shift;
161 11         65 return $self->{warnings};
162             }
163              
164             sub _accessor { ## no critic (ProhibitUnusedPrivateSubroutines)
165 2     2   2337 my ($self, $field, $val) = @_;
166 2         315 carp q[_accessor is deprecated. Use __PACKAGE__->mk_accessors(...) instead];
167 2 100       2106 if(defined $val) {
168 1         5 $self->{$field} = $val;
169             }
170 2         14 return $self->{$field};
171             }
172              
173             sub authorised {
174 25     25 1 74 my $self = shift;
175 25   50     95 my $action = $self->action || q[];
176 25   100     339 my $aspect = $self->aspect || q[];
177 25         321 my $util = $self->util;
178 25         278 my $requestor = $util->requestor;
179              
180 25 100       183 if(!$requestor) {
181             #########
182             # If there's no requestor user object then authorisation isn't supported
183             #
184 16         70 return 1;
185             }
186              
187 9 100 100     54 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         9 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     49 if($requestor->can('is_member_of') &&
200             $requestor->is_member_of('admin')) {
201 3         31 return 1;
202             }
203             }
204              
205 4         33 return;
206             }
207              
208             sub template_name {
209 22     22 1 2314 my ($self, @args) = @_;
210              
211 22 100       84 if(scalar @args) {
212 1         3 $self->{template_override} = $args[0];
213             }
214              
215 22 100       92 if(exists $self->{template_override}) {
216 2         6 return $self->{template_override};
217             }
218              
219 20         112 my $name = $self->entity_name;
220 20 100       295 if(!$name) {
221 6         19 ($name) = (ref $self) =~ /view::(.*)$/smx;
222             }
223 20   100     89 $name ||= 'view';
224 20         130 my $method = $self->method_name;
225              
226 20         65 $name =~ s/:+/_/smxg;
227 20 100       70 if(!$method) {
228 1         7 return $name;
229             }
230              
231 19         74 my $util = $self->util;
232 19         243 my $tmp = "${name}/$method";
233 19         90 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       528 if(-e "$path/$tmp.tt2") {
239 1         7 return $tmp;
240             }
241              
242 18         99 return "${name}_$method";
243             }
244              
245             sub method_name {
246 37     37 1 97 my $self = shift;
247 37         128 my $aspect = $self->aspect;
248 37         486 my $action = $self->action;
249 37   100     488 my $method = $aspect || $action;
250 37         142 my $model = $self->model;
251 37         493 my $pk = $model->primary_key;
252              
253 37 100       250 if(!$method) {
254 1         3 return q[];
255             }
256              
257 36 100 100     244 if($pk &&
      66        
258             $method eq 'read' &&
259             !$model->$pk()) {
260 1         10 $method = 'list';
261             }
262              
263 36         128 $method =~ s/__/_/smxg;
264              
265 36         109 return $method;
266             }
267              
268             sub streamed_aspects {
269 30     30 1 116 return [qw(options)];
270             }
271              
272             sub streamed {
273 32     32 1 73 my $self = shift;
274 32         111 my $aspect = $self->aspect;
275              
276 32         403 for my $str_aspect (@{$self->streamed_aspects}) {
  32         115  
277 32 100       127 if($aspect eq $str_aspect) {
278 2         7 return 1;
279             }
280             }
281 30         108 return;
282             }
283              
284             sub render {
285 17     17 1 52 my $self = shift;
286 17         63 my $util = $self->util;
287 17   100     227 my $aspect = $self->aspect || q[];
288 17         272 my $action = $self->action;
289              
290 17 50       225 if(!$util) {
291 0         0 croak q[No util object available];
292             }
293              
294 17         89 my $requestor = $util->requestor;
295              
296 17 50       192 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         96 my $method = $self->method_name;
317 17 50       143 if($method !~ /^(?:add|edit|create|read|update|delete|list|options)/smx) {
318 0         0 croak qq[Illegal method: $method];
319             }
320              
321 17 50       174 if($self->can($method)) {
322 17 50 33     149 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         61 my $streamed = $self->streamed;
331              
332 17 100       62 if($streamed) {
333 1         8 $self->output_flush;
334             }
335              
336 17         89 $self->$method();
337              
338 17 100       81 if($streamed) {
339 1         8 $self->output_end;
340 1         3 return q[];
341             }
342              
343             } else {
344 0         0 croak qq[Unsupported method: $method];
345             }
346              
347 16         104 my $model = $self->model;
348 16         282 my $actions = my $warnings = q[];
349              
350 16 100       74 if($self->decor) {
351 8         59 $actions = $self->actions;
352             eval {
353 8         73 $self->process_template('warnings.tt2', {
354             warnings => $self->warnings,
355             }, \$warnings);
356              
357 8 50       20 } 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         253 my $tmpl = $self->template_name;
369 16         80 my $cfg = $util->config;
370 16         43 my $content = q[];
371              
372 16         118 $self->process_template("$tmpl.tt2", {}, \$content);
373              
374 15   50     166 return $warnings . $actions . $content || q[No data];
375             }
376              
377             sub process_template { ## no critic (Complexity)
378 35     35 1 1456 my ($self, $template, $extra_params, $where_to_ref) = @_;
379 35         142 my $util = $self->util;
380 35         513 my $cfg = $util->config;
381 35         343 my ($entity) = (ref $self) =~ /([^:]+)$/smx;
382 35   50     141 $entity ||= q[];
383 35   50     218 my $script_name = $ENV{SCRIPT_NAME} || q[];
384 35         139 my ($xfh, $xfp) = ($ENV{HTTP_X_FORWARDED_HOST}, $ENV{HTTP_X_FORWARDED_PORT});
385 35   100     208 my $http_host = ($xfh ? $xfh : $ENV{HTTP_HOST}) || q[localhost];
386 35   50     212 my $http_port = ($xfh ? $xfp : $ENV{HTTP_PORT}) || q[];
387 35 50 33     219 my $http_proto = $ENV{HTTP_X_FORWARDED_PROTO} || $ENV{HTTPS}?q[https]:q[http];
388 35 50       273 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         189 $_ => $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         2307 %{$cfg_globals},
414 35 50       1353 %{$extra_params||{}},
  35         461  
415             };
416              
417              
418             my $appname = $util->config->val('application', 'name') ||
419             $util->config->val('application', 'namespace') ||
420 35   33     184 $ENV{SCRIPT_NAME};
421              
422 35   100     1258 $TEMPLATE_CACHE->{$appname} ||= {};
423 35         82 my $template_cache = $TEMPLATE_CACHE->{$appname};
424              
425 35 100       138 if(!$template_cache->{$template}) {
426 22         88 my $path = sprintf q[%s/templates], $util->data_path;
427 22 100       1218 open my $fh, q[<], "$path/$template" or croak qq[Error opening $template];
428 21         153 local $RS = undef;
429 21         399 $template_cache->{$template} = <$fh>;
430 21 50       268 close $fh or croak qq[Error closing $template];
431             }
432              
433 34         111 $template = \$template_cache->{$template};
434              
435 34 50       145 if($where_to_ref) {
436 34 50       167 $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         171729 return 1;
443             }
444              
445             sub _populate_from_cgi {
446 14     14   35 my $self = shift;
447 14         58 my $util = $self->util;
448 14         191 my $model = $self->model;
449 14         191 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         83 $model->read;
456              
457 14         52 my $pk = $model->primary_key;
458              
459 14         73 my @fields = $model->fields;
460 14 50       80 if($pk) {
461             #########
462             # don't leave primary key in field list
463             #
464 14         33 @fields = grep { $_ ne $pk } @fields;
  55         138  
465             }
466              
467             my $params = {
468             map { ## no critic (ProhibitComplexMappings)
469 14         59 my $p = $cgi->param($_);
  17         287  
470 17         446 utf8::decode($p);
471 17         62 $_ => $p;
472             } $cgi->param
473             };
474              
475             #########
476             # parse new-style POST payload
477             # todo: look at PUTDATA as well
478             #
479 14         76 my $postdata = $cgi->param('POSTDATA');
480 14 100       322 if($postdata) {
481 6         23 utf8::decode($postdata);
482             eval {
483 6         93 my $json = JSON->new->utf8;
484             eval {
485 6         66 $params = $json->decode($postdata);
486 5         23 1;
487              
488 6 100       18 } or do {
489 1         6 $params = XMLin($postdata);
490             };
491              
492 6         2563 for my $k (%{$params}) {
  6         27  
493 20 0 33     67 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         48 1;
500              
501 6 50       12 } 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         49 my $xml = $cgi->param('XForms:Model');
513 14 100       238 if($xml) {
514 1         15 utf8::decode($xml);
515 1         6 $params = XMLin($xml);
516 1         50356 for my $k (%{$params}) {
  1         4  
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         33 for my $field (@fields) {
526 41 100       181 if(!exists $params->{$field}) {
527 28         49 next;
528             }
529 13         29 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       55 if($self->autoescape) {
536 13         228 $v = $cgi->escapeHTML($v);
537             }
538              
539 13         1288 $model->$field($v);
540             }
541              
542 14         229 return 1;
543             }
544              
545             sub add {
546 1     1 1 10 my $self = shift;
547 1         6 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 13 return 1;
561             }
562              
563             sub read { ## no critic (homonym)
564 3     3 1 558 return 1;
565             }
566              
567             sub delete { ## no critic (homonym)
568 2     2 1 517 my $self = shift;
569 2         11 my $model = $self->model;
570              
571 2 50       38 $model->delete or croak qq[Failed to delete entity: $EVAL_ERROR];
572              
573 2         19 return 1;
574             }
575              
576             sub update {
577 7     7 1 566 my $self = shift;
578 7         31 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         113 $self->_populate_from_cgi;
585              
586 7 50       48 $model->update or croak qq[Failed to update entity: $EVAL_ERROR];
587 7         35 return 1;
588             }
589              
590             sub create {
591 6     6 1 605 my $self = shift;
592 6         28 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         115 $self->_populate_from_cgi;
599              
600 6 50       34 $model->create or croak qq[Failed to create entity: $EVAL_ERROR];
601              
602 6         35 return 1;
603             }
604              
605             sub add_tt_filter {
606 320     320 1 779 my ($self, $name, $code) = @_;
607              
608 320 50 33     1208 if(!$name || !$code) {
609 0         0 return;
610             }
611              
612 320         753 $self->tt_filters->{$name} = $code;
613              
614 320         599 return 1;
615             }
616              
617             sub tt_filters {
618 331     331 1 579 my $self = shift;
619              
620 331 100       790 if(!$self->{tt_filters}) {
621 80         218 $self->{tt_filters} = {};
622             }
623              
624 331         965 return $self->{tt_filters};
625             }
626              
627             sub tt_opts {
628 9     9 1 26 return {};
629             }
630              
631             sub tt {
632 36     36 1 111 my ($self, $tt) = @_;
633 36         145 my $util = $self->util;
634              
635 36 50       497 if($tt) {
636 0         0 $util->{tt} = $tt;
637             }
638              
639 36 100       147 if(!$util->{tt}) {
640 9         37 my $filters = Template::Filters->new({
641             FILTERS => $self->tt_filters,
642             });
643 9         529 my $opts = $self->tt_opts;
644 9   33     39 my $ns = $util->config->val('application', 'namespace') ||
645             $util->config->val('application', 'name');
646 9 50       275 my $plugin_base = $ns ? q[ClearPress::Template::Plugin] : sprintf q[%s::plugin], $ns;
647 9         38 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         140 while (my ($k, $v) = each %{$defaults}) {
  63         200  
657 54 50       126 if(!exists $opts->{$k}) {
658 54         123 $opts->{$k} = $v;
659             }
660             }
661              
662 9 50       77 $util->{tt} = Template->new($opts) or croak $Template::ERROR;
663             }
664 36         72914 return $util->{tt};
665             }
666              
667             sub decor {
668 49     49 1 168 my $self = shift;
669 49   100     204 my $aspect = $self->aspect || q[];
670              
671 49         847 for my $ending (qw(rss atom ajax xml
672             json js _png _jpg _svg _svgz
673             _txt _csv _xls)) {
674 401 100       1097 if((substr $aspect, -length $ending, length $ending) eq $ending) {
675 26         145 return 0;
676             }
677             }
678 23         95 return 1;
679             }
680              
681             sub output_flush {
682 19     19 1 51 my ($self) = @_;
683 19 100       82 $DEBUG_OUTPUT and carp "output_flush: @{[scalar @{$self->{output_buffer}}]} blobs in queue";
  1         4  
  1         122  
684              
685             eval {
686 19 50       35 print grep { $_ } @{$self->{output_buffer}} or croak $ERRNO;
  57         308  
  19         67  
687 19         478 1;
688              
689 19 50       227 } 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         100 $self->output_reset;
697 19         52 return 1;
698             }
699              
700             sub output_prepend {
701 15     15 1 1580 my ($self, @args) = @_;
702 15 100       62 if(!$self->output_finished) {
703 14 50 33     145 if(scalar @args == 2 && $args[1] eq "\n" && !$args[0]) {
      33        
704 0         0 return;
705             }
706 14         38 unshift @{$self->{output_buffer}}, grep { $_ } @args; # don't push undef or ""
  14         51  
  28         96  
707 14 50       56 $DEBUG_OUTPUT and carp "output_prepend prepended (@{[scalar @args]} blobs)";
  0         0  
708             }
709 15         51 return 1;
710             }
711              
712             sub output_buffer {
713 33     33 1 2149 my ($self, @args) = @_;
714 33 100       136 if(!$self->output_finished) {
715 31 50 66     142 if(scalar @args == 2 && $args[1] eq "\n" && !$args[0]) {
      66        
716 0         0 return;
717             }
718              
719 31         66 push @{$self->{output_buffer}}, grep { $_ } @args; # don't push undef or ""
  31         93  
  32         112  
720 31 100       153 $DEBUG_OUTPUT and carp "output_buffer added (@{[scalar @args]} blobs)";
  2         254  
721             }
722 33         1422 return 1;
723             }
724              
725             sub output_finished {
726 66     66 1 162 my ($self, $val) = @_;
727 66 100       188 if(defined $val) {
728 18         43 $self->{output_finished} = $val;
729 18 100       155 $DEBUG_OUTPUT and carp "output_finished = $val";
730             }
731 66         330 return $self->{output_finished};
732             }
733              
734             sub output_end {
735 18     18 1 53 my $self = shift;
736 18 100       169 $DEBUG_OUTPUT and carp "output_end: $self";
737 18         136 $self->output_finished(1);
738 18         77 return $self->output_flush;
739             }
740              
741             sub output_reset {
742 23     23 1 77 my $self = shift;
743 23         123 $self->{output_buffer} = [];
744 23 100       475 $DEBUG_OUTPUT and carp 'output_reset';
745 23         200 return;
746             }
747              
748             sub actions {
749 9     9 1 27 my $self = shift;
750 9         26 my $content = q[];
751              
752 9         65 $self->process_template('actions.tt2', {}, \$content);
753 9         40 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           Readonly::Scalar my $OVERFLOW => 1024;
771 0 0         if(length $self->headers->as_string > $OVERFLOW) { # fudge for apparent buffer overflow with apache+mod_perl (ParseHeaders related?)
772 0           carp q[warning: header block looks long];
773 0           $self->headers->remove_header('Location');
774 0           $self->headers->header('Status', HTTP_OK);
775             }
776             }
777              
778 0           $self->output_buffer($self->headers->as_string, "\n");
779 0           $self->decorator->meta_refresh(qq[0;URL='$url']);
780              
781             #########
782             # clean everything up and terminate
783             #
784 0           $self->output_flush();
785 0           $self->headers->clear();
786              
787             ########
788             # Note: This ought to correspond to content-type, but doesn't!
789             #
790             return <<"EOT"
791            

This document has moved here.

792            
793             EOT
794 0           }
795              
796             #########
797             # automated method generation for core CRUD+ view methods
798             #
799             BEGIN {
800 5     5   47 no strict 'refs'; ## no critic (ProhibitNoStrict)
  5         14  
  5         478  
801 5     5   23 for my $ext (qw(xml ajax json csv)) {
802 20         39 for my $method (qw(create list read update delete)) {
803 100         236 my $ns = sprintf q[%s_%s], $method, $ext;
804 100     24   296 *{$ns} = sub { my $self = shift; return $self->$method; };
  100         517  
  24         7066  
  24         86  
805             }
806             }
807             }
808              
809             1;
810             __END__