File Coverage

blib/lib/ClearPress/view.pm
Criterion Covered Total %
statement 362 428 84.5
branch 114 174 65.5
condition 79 123 64.2
subroutine 55 61 90.1
pod 34 34 100.0
total 644 820 78.5


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   30440 use strict;
  5         9  
  5         176  
9 5     5   24 use warnings;
  5         8  
  5         124  
10 5     5   23 use base qw(Class::Accessor);
  5         5  
  5         349  
11 5     5   2083 use Template;
  5         58605  
  5         130  
12 5     5   1649 use Template::Filters;
  5         17437  
  5         158  
13 5     5   42 use ClearPress::util;
  5         13  
  5         54  
14 5     5   149 use Carp;
  5         11  
  5         303  
15 5     5   29 use English qw(-no_match_vars);
  5         10  
  5         37  
16 5     5   1775 use POSIX qw(strftime);
  5         12  
  5         40  
17 5     5   1986 use HTML::Entities qw(encode_entities_numeric);
  5         30871  
  5         421  
18 5     5   1995 use HTTP::Headers;
  5         35528  
  5         222  
19 5     5   1813 use HTTP::Status qw(:constants);
  5         23052  
  5         2188  
20 5     5   526 use XML::Simple qw(XMLin);
  5         6278  
  5         54  
21 5     5   2791 use utf8;
  5         83  
  5         30  
22 5     5   2567 use ClearPress::Localize;
  5         25  
  5         225  
23 5     5   1863 use MIME::Base64 qw(encode_base64);
  5         3596  
  5         353  
24 5     5   553 use JSON;
  5         9770  
  5         50  
25 5     5   937 use Readonly;
  5         14  
  5         27127  
26              
27             our $VERSION = q[477.1.4];
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 31310 my ($class, $self) = @_;
39 80   100     373 $self ||= {};
40 80         215 bless $self, $class;
41              
42 80         393 my $util = $self->util;
43 80 100       1709 my $username = $util ? $util->username : q[];
44 80         1136 $self->{requestor_username} = $username;
45 80 100       310 $self->{logged_in} = $username?1:0;
46 80         266 $self->{warnings} = [];
47 80         294 $self->{output_buffer} = [];
48 80         223 $self->{output_finished} = 0;
49 80         243 $self->{autoescape} = 1;
50              
51 80   100     345 my $aspect = $self->aspect || q[];
52              
53 80 100 66     2334 $self->{content_type} ||= ($aspect =~ /(?:rss|atom|ajax|xml)$/smx)?'text/xml':q[];
54 80 100 100     865 $self->{content_type} ||= ($aspect =~ /(?:js|json)$/smx)?'application/json':q[];
55 80 50 66     455 $self->{content_type} ||= ($aspect =~ /_svg$/smx)?'image/svg+xml':q[];
56 80 50 66     545 $self->{content_type} ||= ($aspect =~ /_svgz$/smx)?'image/svg+xml':q[];
57 80 100 100     490 $self->{content_type} ||= ($aspect =~ /_png$/smx)?'image/png':q[];
58 80 100 100     413 $self->{content_type} ||= ($aspect =~ /_jpg$/smx)?'image/jpeg':q[];
59 80 50 66     416 $self->{content_type} ||= ($aspect =~ /_txt$/smx)?'text/plain':q[];
60 80 50 66     387 $self->{content_type} ||= ($aspect =~ /_csv$/smx)?'text/csv':q[];
61 80 50 66     393 $self->{content_type} ||= ($aspect =~ /_xls$/smx)?'application/vnd.ms-excel':q[];
62              
63 80         366 $self->setup_filters;
64              
65 80         342 $self->init;
66              
67 80         310 ClearPress::Localize->init($self->locales);
68              
69 80   100     617 $self->{content_type} ||= 'text/html';
70              
71 80   100     559 $self->{charset} ||= 'UTF-8';
72 80   66     524 $self->{headers} ||= HTTP::Headers->new;
73              
74 80         1204 return $self;
75             }
76              
77             sub setup_filters {
78 80     80 1 177 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         686 });
90              
91             $self->add_tt_filter('xml_entity', sub {
92 4     4   1321 my $string = shift;
93 4 100       16 if(!defined $string) {
94 1         3 $string = q[];
95             }
96 4         18 return encode_entities_numeric($string),
97 80         492 });
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         511 });
106              
107 80         313 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         1726 }, 1]);
136              
137 80         178 return 1;
138             }
139              
140             sub init {
141 80     80 1 159 return 1;
142             }
143              
144             sub locales {
145 80     80 1 178 my $self = shift;
146 80         249 my $util = $self->util;
147             return {
148 80 100       1319 $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 7 my ($self, $warning) = @_;
155 1         4 push @{$self->{warnings}}, $warning;
  1         6  
156 1         5 return 1;
157             }
158              
159             sub warnings {
160 11     11 1 42 my $self = shift;
161 11         75 return $self->{warnings};
162             }
163              
164             sub _accessor { ## no critic (ProhibitUnusedPrivateSubroutines)
165 2     2   2604 my ($self, $field, $val) = @_;
166 2         368 carp q[_accessor is deprecated. Use __PACKAGE__->mk_accessors(...) instead];
167 2 100       227 if(defined $val) {
168 1         6 $self->{$field} = $val;
169             }
170 2         16 return $self->{$field};
171             }
172              
173             sub authorised {
174 25     25 1 89 my $self = shift;
175 25   50     96 my $action = $self->action || q[];
176 25   100     358 my $aspect = $self->aspect || q[];
177 25         335 my $util = $self->util;
178 25         298 my $requestor = $util->requestor;
179              
180 25 100       197 if(!$requestor) {
181             #########
182             # If there's no requestor user object then authorisation isn't supported
183             #
184 16         75 return 1;
185             }
186              
187 9 100 100     66 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         13 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     56 if($requestor->can('is_member_of') &&
200             $requestor->is_member_of('admin')) {
201 3         35 return 1;
202             }
203             }
204              
205 4         38 return;
206             }
207              
208             sub template_name {
209 22     22 1 240 my ($self, @args) = @_;
210              
211 22 100       104 if(scalar @args) {
212 1         4 $self->{template_override} = $args[0];
213             }
214              
215 22 100       117 if(exists $self->{template_override}) {
216 2         7 return $self->{template_override};
217             }
218              
219 20         277 my $name = $self->entity_name;
220 20 100       354 if(!$name) {
221 6         25 ($name) = (ref $self) =~ /view::(.*)$/smx;
222             }
223 20   100     98 $name ||= 'view';
224 20         84 my $method = $self->method_name;
225              
226 20         77 $name =~ s/:+/_/smxg;
227 20 100       84 if(!$method) {
228 1         5 return $name;
229             }
230              
231 19         85 my $util = $self->util;
232 19         297 my $tmp = "${name}/$method";
233 19         209 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       613 if(-e "$path/$tmp.tt2") {
239 1         6 return $tmp;
240             }
241              
242 18         119 return "${name}_$method";
243             }
244              
245             sub method_name {
246 37     37 1 97 my $self = shift;
247 37         145 my $aspect = $self->aspect;
248 37         512 my $action = $self->action;
249 37   100     504 my $method = $aspect || $action;
250 37         157 my $model = $self->model;
251 37         559 my $pk = $model->primary_key;
252              
253 37 100       352 if(!$method) {
254 1         3 return q[];
255             }
256              
257 36 100 100     269 if($pk &&
      66        
258             $method eq 'read' &&
259             !$model->$pk()) {
260 1         9 $method = 'list';
261             }
262              
263 36         130 $method =~ s/__/_/smxg;
264              
265 36         116 return $method;
266             }
267              
268             sub streamed_aspects {
269 30     30 1 148 return [];
270             }
271              
272             sub streamed {
273 32     32 1 78 my $self = shift;
274 32         120 my $aspect = $self->aspect;
275              
276 32         523 for my $str_aspect (@{$self->streamed_aspects}) {
  32         133  
277 2 50       12 if($aspect eq $str_aspect) {
278 2         7 return 1;
279             }
280             }
281 30         115 return;
282             }
283              
284             sub render {
285 17     17 1 63 my $self = shift;
286 17         78 my $util = $self->util;
287 17   100     248 my $aspect = $self->aspect || q[];
288 17         278 my $action = $self->action;
289              
290 17 50       250 if(!$util) {
291 0         0 croak q[No util object available];
292             }
293              
294 17         91 my $requestor = $util->requestor;
295              
296 17 50       271 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         97 my $method = $self->method_name;
317 17 50       148 if($method !~ /^(?:add|edit|create|read|update|delete|list|options)/smx) {
318 0         0 croak qq[Illegal method: $method];
319             }
320              
321 17 50       192 if($self->can($method)) {
322 17 50 33     166 if($action 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         70 my $streamed = $self->streamed;
331              
332 17 100       81 if($streamed) {
333 1         7 $self->output_flush;
334             }
335              
336 17         102 $self->$method();
337              
338 17 100       93 if($streamed) {
339 1         13 $self->output_end;
340 1         4 return q[];
341             }
342              
343             } else {
344 0         0 croak qq[Unsupported method: $method];
345             }
346              
347 16         184 my $model = $self->model;
348 16         322 my $actions = my $warnings = q[];
349              
350 16 100       82 if($self->decor) {
351 8         71 $actions = $self->actions;
352             eval {
353 8         80 $self->process_template('warnings.tt2', {
354             warnings => $self->warnings,
355             }, \$warnings);
356              
357 8 50       26 } 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         170 my $tmpl = $self->template_name;
369 16         99 my $cfg = $util->config;
370 16         50 my $content = q[];
371              
372 16         131 $self->process_template("$tmpl.tt2", {}, \$content);
373              
374 15   50     222 return $warnings . $actions . $content || q[No data];
375             }
376              
377             sub process_template { ## no critic (Complexity)
378 35     35 1 2984 my ($self, $template, $extra_params, $where_to_ref) = @_;
379 35         152 my $util = $self->util;
380 35         569 my $cfg = $util->config;
381 35         389 my ($entity) = (ref $self) =~ /([^:]+)$/smx;
382 35   50     147 $entity ||= q[];
383 35   50     339 my $script_name = $ENV{SCRIPT_NAME} || q[];
384 35         149 my ($xfh, $xfp) = ($ENV{HTTP_X_FORWARDED_HOST}, $ENV{HTTP_X_FORWARDED_PORT});
385 35   100     225 my $http_host = ($xfh ? $xfh : $ENV{HTTP_HOST}) || q[localhost];
386 35   50     231 my $http_port = ($xfh ? $xfp : $ENV{HTTP_PORT}) || q[];
387 35 50 33     262 my $http_proto = $ENV{HTTP_X_FORWARDED_PROTO} || $ENV{HTTPS}?q[https]:q[http];
388 35 50       279 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         226 $_ => $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         2568 %{$cfg_globals},
414 35 50       1649 %{$extra_params||{}},
  35         603  
415             };
416              
417              
418             my $appname = $util->config->val('application', 'name') ||
419             $util->config->val('application', 'namespace') ||
420 35   33     219 $ENV{SCRIPT_NAME};
421              
422 35   100     1487 $TEMPLATE_CACHE->{$appname} ||= {};
423 35         101 my $template_cache = $TEMPLATE_CACHE->{$appname};
424              
425 35 100       161 if(!$template_cache->{$template}) {
426 22         99 my $path = sprintf q[%s/templates], $util->data_path;
427 22 100       1443 open my $fh, q[<], "$path/$template" or croak qq[Error opening $template];
428 21         169 local $RS = undef;
429 21         469 $template_cache->{$template} = <$fh>;
430 21 50       337 close $fh or croak qq[Error closing $template];
431             }
432              
433 34         120 $template = \$template_cache->{$template};
434              
435 34 50       128 if($where_to_ref) {
436 34 50       206 $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         177500 return 1;
443             }
444              
445             sub _populate_from_cgi {
446 14     14   32 my $self = shift;
447 14         47 my $util = $self->util;
448 14         158 my $model = $self->model;
449 14         165 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         91 $model->read;
456              
457 14         53 my $pk = $model->primary_key;
458              
459 14         85 my @fields = $model->fields;
460 14 50       86 if($pk) {
461             #########
462             # don't leave primary key in field list
463             #
464 14         40 @fields = grep { $_ ne $pk } @fields;
  55         184  
465             }
466              
467             my $params = {
468             map { ## no critic (ProhibitComplexMappings)
469 14         125 my $p = $cgi->param($_);
  17         298  
470 17         450 utf8::decode($p);
471 17         173 $_ => $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       383 if($postdata) {
481 6         30 utf8::decode($postdata);
482             eval {
483 6         117 my $json = JSON->new->utf8;
484             eval {
485 6         73 $params = $json->decode($postdata);
486 5         25 1;
487              
488 6 100       19 } or do {
489 1         7 $params = XMLin($postdata);
490             };
491              
492 6         2778 for my $k (%{$params}) {
  6         27  
493 20 0 33     65 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         56 1;
500              
501 6 50       13 } 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         57 my $xml = $cgi->param('XForms:Model');
513 14 100       296 if($xml) {
514 1         4 utf8::decode($xml);
515 1         8 $params = XMLin($xml);
516 1         54076 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         58 for my $field (@fields) {
526 41 100       207 if(!exists $params->{$field}) {
527 28         64 next;
528             }
529 13         35 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       64 if($self->autoescape) {
536 13         215 $v = $cgi->escapeHTML($v);
537             }
538              
539 13         1288 $model->$field($v);
540             }
541              
542 14         139 return 1;
543             }
544              
545             sub add {
546 1     1 1 7 my $self = shift;
547 1         5 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             #########
557             # streamed response
558             #
559 0     0 1 0 return q[];
560             }
561              
562             sub list {
563 5     5 1 13 return 1;
564             }
565              
566             sub read { ## no critic (homonym)
567 3     3 1 510 return 1;
568             }
569              
570             sub delete { ## no critic (homonym)
571 2     2 1 490 my $self = shift;
572 2         7 my $model = $self->model;
573              
574 2 50       24 $model->delete or croak qq[Failed to delete entity: $EVAL_ERROR];
575              
576 2         16 return 1;
577             }
578              
579             sub update {
580 7     7 1 548 my $self = shift;
581 7         28 my $model = $self->model;
582              
583             #########
584             # Populate model object with parameters posted into CGI
585             # by default (in controller.pm) model will only have util & its primary_key.
586             #
587 7         117 $self->_populate_from_cgi;
588              
589 7 50       55 $model->update or croak qq[Failed to update entity: $EVAL_ERROR];
590 7         41 return 1;
591             }
592              
593             sub create {
594 6     6 1 503 my $self = shift;
595 6         23 my $model = $self->model;
596              
597             #########
598             # Populate model object with parameters posted into CGI
599             # by default (in controller.pm) model will only have util & its primary_key.
600             #
601 6         100 $self->_populate_from_cgi;
602              
603 6 50       40 $model->create or croak qq[Failed to create entity: $EVAL_ERROR];
604              
605 6         34 return 1;
606             }
607              
608             sub add_tt_filter {
609 320     320 1 813 my ($self, $name, $code) = @_;
610              
611 320 50 33     1332 if(!$name || !$code) {
612 0         0 return;
613             }
614              
615 320         828 $self->tt_filters->{$name} = $code;
616              
617 320         720 return 1;
618             }
619              
620             sub tt_filters {
621 331     331 1 605 my $self = shift;
622              
623 331 100       880 if(!$self->{tt_filters}) {
624 80         251 $self->{tt_filters} = {};
625             }
626              
627 331         1109 return $self->{tt_filters};
628             }
629              
630             sub tt_opts {
631 9     9 1 31 return {};
632             }
633              
634             sub tt {
635 36     36 1 193 my ($self, $tt) = @_;
636 36         149 my $util = $self->util;
637              
638 36 50       706 if($tt) {
639 0         0 $util->{tt} = $tt;
640             }
641              
642 36 100       160 if(!$util->{tt}) {
643 9         99 my $filters = Template::Filters->new({
644             FILTERS => $self->tt_filters,
645             });
646 9         531 my $opts = $self->tt_opts;
647 9   33     44 my $ns = $util->config->val('application', 'namespace') ||
648             $util->config->val('application', 'name');
649 9 50       320 my $plugin_base = $ns ? q[ClearPress::Template::Plugin] : sprintf q[%s::plugin], $ns;
650 9         42 my $defaults = {
651             PLUGIN_BASE => $plugin_base,
652             RECURSION => 1,
653             INCLUDE_PATH => (sprintf q[%s/templates], $util->data_path),
654             EVAL_PERL => 1,
655             ENCODING => 'utf8',
656             LOAD_FILTERS => [ $filters ],
657             };
658              
659 9         167 while (my ($k, $v) = each %{$defaults}) {
  63         207  
660 54 50       131 if(!exists $opts->{$k}) {
661 54         122 $opts->{$k} = $v;
662             }
663             }
664              
665 9 50       78 $util->{tt} = Template->new($opts) or croak $Template::ERROR;
666             }
667 36         87991 return $util->{tt};
668             }
669              
670             sub decor {
671 49     49 1 186 my $self = shift;
672 49   100     238 my $aspect = $self->aspect || q[];
673              
674 49 50       947 if($self->action eq 'options') {
675 0         0 return 0;
676             }
677 49         1036 for my $ending (qw(rss atom ajax xml
678             json js _png _jpg _svg _svgz
679             _txt _csv _xls)) {
680 401 100       1256 if((substr $aspect, -length $ending, length $ending) eq $ending) {
681 26         238 return 0;
682             }
683             }
684 23         114 return 1;
685             }
686              
687             sub output_flush {
688 19     19 1 59 my ($self) = @_;
689 19 100       72 $DEBUG_OUTPUT and carp "output_flush: @{[scalar @{$self->{output_buffer}}]} blobs in queue";
  1         3  
  1         266  
690              
691             eval {
692 19 50       47 print grep { $_ } @{$self->{output_buffer}} or croak $ERRNO;
  57         357  
  19         80  
693 19         596 1;
694              
695 19 50       2125 } or do {
696             #########
697             # client stopped receiving (e.g. disconnect from lengthy streamed response)
698             #
699 0         0 carp qq[Error flushing output_buffer: $EVAL_ERROR];
700             };
701              
702 19         114 $self->output_reset;
703 19         59 return 1;
704             }
705              
706             sub output_prepend {
707 15     15 1 1940 my ($self, @args) = @_;
708 15 100       65 if(!$self->output_finished) {
709 14 50 33     237 if(scalar @args == 2 && $args[1] eq "\n" && !$args[0]) {
      33        
710 0         0 return;
711             }
712 14         44 unshift @{$self->{output_buffer}}, grep { $_ } @args; # don't push undef or ""
  14         56  
  28         109  
713 14 50       64 $DEBUG_OUTPUT and carp "output_prepend prepended (@{[scalar @args]} blobs)";
  0         0  
714             }
715 15         64 return 1;
716             }
717              
718             sub output_buffer {
719 33     33 1 2567 my ($self, @args) = @_;
720 33 100       275 if(!$self->output_finished) {
721 31 50 66     159 if(scalar @args == 2 && $args[1] eq "\n" && !$args[0]) {
      66        
722 0         0 return;
723             }
724              
725 31         77 push @{$self->{output_buffer}}, grep { $_ } @args; # don't push undef or ""
  31         131  
  32         128  
726 31 100       127 $DEBUG_OUTPUT and carp "output_buffer added (@{[scalar @args]} blobs)";
  2         389  
727             }
728 33         2042 return 1;
729             }
730              
731             sub output_finished {
732 66     66 1 180 my ($self, $val) = @_;
733 66 100       206 if(defined $val) {
734 18         64 $self->{output_finished} = $val;
735 18 100       197 $DEBUG_OUTPUT and carp "output_finished = $val";
736             }
737 66         313 return $self->{output_finished};
738             }
739              
740             sub output_end {
741 18     18 1 61 my $self = shift;
742 18 100       235 $DEBUG_OUTPUT and carp "output_end: $self";
743 18         293 $self->output_finished(1);
744 18         101 return $self->output_flush;
745             }
746              
747             sub output_reset {
748 23     23 1 76 my $self = shift;
749 23         104 $self->{output_buffer} = [];
750 23 100       381 $DEBUG_OUTPUT and carp 'output_reset';
751 23         189 return;
752             }
753              
754             sub actions {
755 9     9 1 32 my $self = shift;
756 9         28 my $content = q[];
757              
758 9         94 $self->process_template('actions.tt2', {}, \$content);
759 9         49 return $content;
760             }
761              
762             sub redirect {
763 0     0 1   my ($self, $url, $status) = @_;
764              
765 0           $self->headers->header('Status', HTTP_FOUND);
766 0           $self->headers->header('Location', $url);
767              
768             #########
769             # - reset all previously output but unflushed content
770             # - push headers down the pipe, and html redirects
771             # - finish up
772             #
773 0           $self->output_reset();
774              
775 0 0         if($TRAP_REDIR_OVERFLOW) {
776 0 0         if(length $self->headers->as_string > $TRAP_REDIR_OVERFLOW) { # fudge for apparent buffer overflow with apache+mod_perl (ParseHeaders related?)
777 0           carp q[warning: header block looks long];
778 0           $self->headers->remove_header('Location');
779 0           $self->headers->header('Status', HTTP_OK);
780             }
781             }
782              
783 0           $self->output_buffer($self->headers->as_string, "\n");
784 0           $self->decorator->meta_refresh(qq[0;URL='$url']);
785              
786             #########
787             # clean everything up and terminate
788             #
789 0           $self->output_flush();
790 0           $self->headers->clear();
791              
792             ########
793             # Warning: This ought to correspond to content-type, but doesn't!
794             #
795             return <<"EOT"
796            

This document has moved here.

797            
798             EOT
799 0           }
800              
801             #########
802             # automated method generation for core CRUD+ view methods
803             #
804             BEGIN {
805 5     5   63 no strict 'refs'; ## no critic (ProhibitNoStrict)
  5         14  
  5         587  
806 5     5   27 for my $ext (qw(xml ajax json csv)) {
807 20         52 for my $method (qw(create list read update delete options)) {
808 120         325 my $ns = sprintf q[%s_%s], $method, $ext;
809 120     24   416 *{$ns} = sub { my $self = shift; return $self->$method; };
  120         797  
  24         6914  
  24         92  
810             }
811             }
812             }
813              
814             1;
815             __END__