line
stmt
bran
cond
sub
pod
time
code
1
2
package CGI::Application::Plugin::FormState;
3
4
8
8
366177
use warnings;
8
19
8
262
5
8
8
41
use strict;
8
14
8
353
6
7
8
8
41
use CGI::Application;
8
17
8
138
8
8
8
6879
use CGI::Session::ID::md5;
8
2547
8
189
9
8
8
884
use CGI::Application::Plugin::Session;
8
7152
8
54
10
8
8
567
use vars qw(@ISA @EXPORT);
8
17
8
365
11
12
13
8
8
37
use Carp;
8
11
8
504
14
8
8
37
use Scalar::Util qw(weaken isweak);
8
13
8
916
15
16
8
8
40
use Exporter;
8
19
8
9844
17
@ISA = qw(Exporter);
18
@EXPORT = qw(form_state);
19
20
our $CGIAPP_Namespace = '__CAP_FORM_STATE';
21
my $Default_Expires = '2d';
22
my $Default_Storage_Name = 'cap_form_state';
23
24
sub import {
25
15
15
5985
my $caller = scalar(caller);
26
15
50
223
if ($caller->can('add_callback')) {
27
15
70
$caller->add_callback('load_tmpl', \&_add_form_state_id_to_tmpl);
28
}
29
else {
30
0
0
croak "CAP::FormState: Calling package ($caller) is not a CGI::Application module so cannot install load_tmpl hooks. If you are using \@ISA instead of 'use base', make sure it is in a BEGIN { } block, and make sure these statements appear before the plugin is loaded";
31
}
32
15
779
goto &Exporter::import;
33
}
34
35
=head1 NAME
36
37
CGI::Application::Plugin::FormState - Store Form State without Hidden Fields
38
39
=head1 VERSION
40
41
Version 0.12
42
43
=cut
44
45
our $VERSION = '0.12';
46
47
=head1 SYNOPSIS
48
49
FormState is just a temporary stash that you can use for storing and
50
retrieving private parameters in your multi-page form.
51
52
use CGI::Application::Plugin::FormState;
53
54
my $form = <
55
56
57
58
...
59
60
EOF
61
62
sub form_display_runmode {
63
my $self = shift;
64
65
# Store some parameters
66
$self->form_state->param('name' => 'Road Runner');
67
$self->form_state->param('occupation' => 'Having Fun');
68
69
my $t = $self->load_tmpl(scalarref => \$form);
70
return $t->output;
71
72
}
73
74
sub form_process_runmode {
75
my $self = shift;
76
77
# Retrieve some parameters
78
print $self->form_state->param('name'); # 'Road Runner'
79
print $self->form_state->param('occupation'); # 'Having Fun'
80
}
81
82
83
=head1 EXAMPLE
84
85
This is a more complete example, using L.
86
87
use CGI::Application::Plugin::Session;
88
use CGI::Application::Plugin::FormState;
89
use CGI::Application::Plugin::ValidateRM;
90
91
my $form = <
92
93
94
95
...
96
97
EOF
98
99
sub my_form_display {
100
my $self = shift;
101
my $errs = shift;
102
my $t = $self->load_tmpl(scalarref => \$form);
103
104
# Stash some data into it
105
$self->form_state->param('name' => 'Wile E. Coyote');
106
$self->form_state->param('occupation' => 'Mining Engineer');
107
108
# Normal ValidateRM error handling
109
$t->param($errs) if $errs;
110
return $t->output;
111
}
112
113
sub my_form_process {
114
my $self;
115
116
# Normal ValidateRM validation
117
my ($results, $err_page) = $self->check_rm('my_form_display','_my_form_profile');
118
return $err_page if $err_page;
119
120
# The data from the submitted form
121
my $params = $self->dfv_results;
122
123
$params->{'name'} = $self->form_state->param('name'); # 'Wile E. Coyote'
124
$params->{'occupation'} = $self->form_state->param('occupation'); # 'Mining Engineer'
125
126
127
# Now do something interesting with $params
128
# ...
129
130
131
my $t = $self->load_tmpl('success.html');
132
return $t->output;
133
}
134
135
# Standard ValiateRM profile
136
sub _my_form_profile {
137
return {
138
required => 'email',
139
msgs => {
140
any_errors => 'some_errors',
141
prefix => 'err_',
142
},
143
};
144
}
145
146
147
=head1 DESCRIPTION
148
149
C provides a temporary storage area
150
within the user's session for storing form-related data.
151
152
The main use of this is for multi-page forms. Instead of using hidden
153
fields to store data related to the form, you store and retrieve values
154
from the form state.
155
156
In the first instance of your app:
157
158
$self->form_state->param('some_name' => 'some_value');
159
$self->form_state->param('some_other_name' => 'some_other_value');
160
161
And later, in a different instance of your app:
162
163
$val1 = $self->form_state->param('some_name');
164
$val2 = $self->form_state->param('some_other_name');
165
166
To connect the first instance and the second, you put a single hidden
167
field in your template:
168
169
170
171
You don't have to worry about creating the template param
172
C; it is added automatically to your template
173
parameters via the C hook.
174
175
If you want to use a parameter other than C you can do
176
so via the C parameter to Cconfig>.
177
178
If you're skeptical about whether all this abstraction is a good idea,
179
see L<"MOTIVATION">, below.
180
181
=head1 PRESERVING FORM STATE ACROSS REDIRECTS
182
183
You can include the form_state hash in a link:
184
185
my $link = '/app.cgi?rm=list&cap_form_state=' . $self->form_state->id;
186
187
If you use L, you can easily create redirect this way:
188
189
$self->redirect('/app.cgi?rm=list&cap_form_state=' . $self->form_state->id);
190
191
If you also use L
192
it is as simple as:
193
194
$self->redirect($self->link('/app.cgi', 'rm' => 'list', 'cap_form_state' => $self->form_state->id));
195
196
Or, in the case of a link to the currently running app:
197
198
$self->redirect($self->self_link('rm' => 'list', 'cap_form_state' => $self->form_state->id));
199
200
201
=head1 IMPLEMENTATION
202
203
When you call C<< $self->form_state >> for the first time, a top-level
204
key is created in the user's session. This key contains a random,
205
hard-to-guess element. It might look something like:
206
207
form_state_cap_form_state_84eb13cfed01764d9c401219faa56d53
208
209
All data you place in the form state with C is stored in the
210
user's session under this key.
211
212
You pass the name of this key on to the next instance of your
213
application by means of a hidden field in your form:
214
215
216
217
You manually put this hidden field in your template. The template
218
parameter C is automatically added to your template
219
parameters via the C hook. It contains the random,
220
hard-to-guess portion (e.g. C<84eb13cfed01764d9c401219faa56d53>). When
221
the template is filled, the hidden field will look something like this:
222
223
224
225
Since all values are stored on the server in the user's session, the
226
user can't tamper with any of them.
227
228
To keep old form_data from cluttering up the user's session, the system
229
uses L's C feature to expire old form state keys
230
after a reasonable amount of time has passed (2 days by default).
231
232
You can manually delete a form state storage by calling:
233
234
$self->form_state->delete;
235
236
=cut
237
238
sub _new {
239
9
9
22
my ($class, $webapp) = @_;
240
241
9
68
my $self = {
242
'__CGIAPP_OBJ' => $webapp,
243
'__STORAGE_NAME' => undef,
244
'__STORAGE_HASH' => undef,
245
'__STORAGE_KEY' => undef,
246
'__EXPIRES' => undef,
247
'__CONFIGURED' => undef,
248
};
249
250
# Force reference to CGI::Application object to be weak to avoid
251
# circular references
252
9
51
weaken($self->{'__CGIAPP_OBJ'});
253
254
9
50
return bless $self, $class;
255
}
256
257
sub form_state {
258
109
109
0
680493
my ($self) = @_;
259
260
# It's possible that in the future we will allow named configs, e.g.
261
# $self->form_state('foo')->param('bar);
262
263
109
100
386
if (not exists $self->{$CGIAPP_Namespace}->{'__DEFAULT_CONFIG'}) {
264
9
91
$self->{$CGIAPP_Namespace}->{'__DEFAULT_CONFIG'} = __PACKAGE__->_new($self);
265
}
266
109
502
return $self->{$CGIAPP_Namespace}->{'__DEFAULT_CONFIG'};
267
}
268
269
=head1 METHODS
270
271
=over 4
272
273
=item config(%options)
274
275
Sets defaults for the plugin.
276
277
B
278
279
$self->form_state->config('name' => 'storage_names', 'expires' => '3d')
280
281
The following options are allowed:
282
283
=over 4
284
285
=item name
286
287
Sets the name of the default form state storage. This name is used for
288
the key in the user's session, for the name of hidden form field, and
289
the template parameter used to fill the hidden form field. So if you
290
set the C to C:
291
292
$self->form_state_config('name' => 'foo');
293
294
then the hidden field in your template should look like this:
295
296
297
298
and the key in the user's session would look something like this:
299
300
form_state_foo_84eb13cfed01764d9c401219faa56d53
301
302
=item expires
303
304
Indicates when form state storage keys should expire and disappear from
305
the user's session. Uses the same format as L's
306
C. Defaults to 2 days (C<'2d'>). To cancel expiration and make
307
the form state last as long as the user's session does, use:
308
309
$self->form_state_config('expires' => 0);
310
311
=back
312
313
=cut
314
315
my %Installed_Callback;
316
317
sub config {
318
13
13
1
30
my $self = shift;
319
13
36
my %args = @_;
320
13
100
65
my $storage_name = delete $args{'name'} if exists $args{'name'};
321
13
100
58
my $expires = delete $args{'expires'} if exists $args{'expires'};
322
323
13
100
44
if (keys %args) {
324
2
476
croak "CAP::FormState: unknown configuration keys: " . (join ', ', keys %args);
325
}
326
327
11
66
130
$self->{'__STORAGE_NAME'} ||= $storage_name || $Default_Storage_Name;
66
328
11
66
118
$self->{'__EXPIRES'} ||= $expires || $Default_Expires;
66
329
330
11
39
$self->{'__CONFIGURED'} = 1;
331
332
11
76
$self->_initialize;
333
}
334
335
336
# Deprecated method. Wrapper around 'config'
337
sub init {
338
3
3
0
6
my $self = shift;
339
3
8
my $storage_name = shift;
340
3
11
my %args = @_;
341
3
14
$self->config('name' => $storage_name, %args);
342
}
343
344
sub _initialize {
345
11
11
20
my $self = shift;
346
347
11
56
my $webapp = $self->{__CGIAPP_OBJ};
348
11
41
my $expires = $self->{__EXPIRES};
349
350
11
21
my $storage_name = $self->{__STORAGE_NAME};
351
352
11
66
47
my $storage_hash = $webapp->query->param($storage_name)
353
|| $webapp->query->url_param($storage_name);
354
11
3558
my $storage_key;
355
356
my $already_exists;
357
358
11
100
39
if ($storage_hash) {
359
# restore existing session
360
2
7
$storage_key = 'form_state_' . $storage_name . '_' . $storage_hash;
361
2
50
11
if (ref $webapp->session->param($storage_key) eq 'HASH') {
362
2
46
$already_exists = 1;
363
}
364
}
365
366
11
100
37
unless ($already_exists) {
367
# create new session
368
369
9
49
$storage_hash = CGI::Session::ID::md5->generate_id;
370
371
9
243
$storage_key = 'form_state_' . $storage_name . '_' . $storage_hash;
372
9
40
$webapp->session->param($storage_key => {});
373
9
318
$webapp->query->param($storage_name => $storage_hash);
374
375
}
376
377
# reset expiry date on key
378
11
598
$webapp->session->expire($storage_key, $expires);
379
380
11
502
$self->{'__STORAGE_NAME'} = $storage_name;
381
11
22
$self->{'__STORAGE_HASH'} = $storage_hash;
382
11
21
$self->{'__STORAGE_KEY'} = $storage_key;
383
11
22
$self->{'__EXPIRES'} = $expires;
384
385
11
100
37
return 1 if $already_exists;
386
9
32
return;
387
}
388
389
=item param
390
391
Read and set values in the form state storage. It acts like the
392
C method typically does in modules such as L,
393
L, L, C etc.
394
395
# set a value
396
$self->form_state->param('some_name' => 'some_value');
397
398
# retrieve a value
399
my $val = $self->form_state->param('some_name');
400
401
# set multiple values
402
$self->form_state->param(
403
'some_name' => 'some_value',
404
'some_other_name' => 'some_other_value',
405
);
406
407
# retrive the names of all the keys
408
my @keys = $self->form_state->param;
409
410
=cut
411
412
sub param {
413
65
65
1
86
my $self = shift;
414
415
65
100
161
if (!$self->{'__CONFIGURED'}) {
416
2
14
$self->config;
417
}
418
65
86
my $storage_key = $self->{'__STORAGE_KEY'};
419
420
65
80
my $webapp = $self->{__CGIAPP_OBJ};
421
65
186
my $store = $webapp->session->param($storage_key);
422
423
65
100
1293
if (@_) {
424
45
52
my $param;
425
45
100
135
if (ref $_[0] eq 'HASH') {
100
426
4
9
$param = shift;
427
}
428
elsif (@_ == 1) {
429
27
140
return $store->{$_[0]};
430
}
431
else {
432
14
80
$param = { @_ };
433
}
434
18
87
$store->{$_} = $param->{$_} for keys %$param;
435
436
# A param has been changed, so we touch the session
437
18
56
$webapp->session->param($storage_key => $store);
438
}
439
else {
440
20
103
return keys %$store;
441
}
442
}
443
444
=item clear_params
445
446
Clear all of the values in the form state storage:
447
448
$self->form_state->param('name' => 'Road Runner');
449
$self->form_state->clear_params;
450
print $self->form_state->param('name'); # undef
451
452
453
=cut
454
455
sub clear_params {
456
4
4
1
13
my $self = shift;
457
458
4
50
19
if (!$self->{'__CONFIGURED'}) {
459
0
0
$self->config;
460
}
461
4
8
my $storage_key = $self->{'__STORAGE_KEY'};
462
463
4
9
my $webapp = $self->{__CGIAPP_OBJ};
464
4
16
$webapp->session->param($storage_key => {});
465
}
466
467
=item delete
468
469
Deletes the form_state storage from the user's session.
470
471
=cut
472
473
sub delete {
474
2
2
1
4
my $self = shift;
475
476
# No need to call $self->config and create the session key since we're
477
# just going to delete it.
478
#
479
# However, it's very important not to call session->clear without a key;
480
# if we do, we'll delete all params in the user's session!
481
482
2
100
13
if ($self->{'__STORAGE_KEY'}) {
483
1
7
$self->{__CGIAPP_OBJ}->session->clear($self->{'__STORAGE_KEY'});
484
}
485
}
486
487
488
=item id
489
490
Returns the current value of the storage param - the "hard to guess"
491
portion of the session key.
492
493
my $id = $self->form_state->id;
494
495
=cut
496
497
sub id {
498
14
14
1
25
my $self = shift;
499
500
14
100
63
if (!$self->{'__CONFIGURED'}) {
501
1
5
$self->config;
502
}
503
14
47
$self->{'__STORAGE_HASH'};
504
}
505
506
=item name
507
508
Returns the current name being used for storage.
509
Defaults to C.
510
511
my $name = $self->form_state->name;
512
513
=cut
514
515
sub name {
516
9
9
1
17
my $self = shift;
517
518
9
100
40
if (!$self->{'__CONFIGURED'}) {
519
1
4
$self->config;
520
}
521
9
36
$self->{'__STORAGE_NAME'};
522
}
523
524
=item session_key
525
526
Returns the full key used for storage in the user's session.
527
528
my $key = $self->form_state->session_key;
529
530
# Get the full form state hash
531
my $data = $self->session->param($key);
532
533
The following can be used to debug the form_state data:
534
535
use Data::Dumper;
536
print STDERR Dumper $self->session->param($self->form_state->session_key);
537
538
=back
539
540
=cut
541
542
sub session_key {
543
5
5
1
12
my $self = shift;
544
545
5
100
24
if (!$self->{'__CONFIGURED'}) {
546
1
4
$self->config;
547
}
548
5
32
$self->{'__STORAGE_KEY'};
549
}
550
551
# add the storage id to the template params
552
sub _add_form_state_id_to_tmpl {
553
4
4
222
my ($self, $ht_params, $tmpl_params, $tmpl_file) = @_;
554
555
4
14
my $storage_hash = $self->form_state->id;
556
4
14
my $storage_name = $self->form_state->name;
557
558
4
19
$tmpl_params->{$storage_name} = $storage_hash;
559
}
560
561
=head1 MOTIVATION
562
563
=head2 Why not just use hidden fields?
564
565
Hidden fields are not secure. The end user could save a local copy of
566
your form, change the hidden fields and tamper with your app's form
567
state.
568
569
=head2 Why not just use the user's session?
570
571
With C the data is associated with
572
a particular instance of a form, not with the user. If the user gives
573
up halfway through your multi-page form, you don't want their session to
574
be cluttered up with the incomplete form state data.
575
576
If a user opens up your application in two browser windows (both sharing
577
the same user session), each window should have it's own independent
578
form state.
579
580
For instance, in an email application the user might have one window
581
open for the inbox and another open for the outbox. If you store the
582
value of C<"current_mailbox"> in the user's session, then one of these
583
windows will go to the wrong mailbox.
584
585
Finally, the user's session probably sticks around longer than the form
586
state should.
587
588
=head1 AUTHOR
589
590
Michael Graham, C<< >>
591
592
=head1 BUGS
593
594
Please report any bugs or feature requests to
595
C, or through the web interface at
596
L. I will be notified, and then you'll automatically
597
be notified of progress on your bug as I make changes.
598
599
=head1 ACKNOWLEDGEMENTS
600
601
Thanks to Richard Dice and Cees Hek for helping me sort out the issues
602
with this approach.
603
604
The informative error message text used for when this module is loaded
605
before your app actually C<@ISA> C object was stolen from
606
Cees's L module.
607
608
=head1 COPYRIGHT & LICENSE
609
610
Copyright 2005 Michael Graham, All Rights Reserved.
611
612
This program is free software; you can redistribute it and/or modify it
613
under the same terms as Perl itself.
614
615
=cut
616
617
1;