| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Test::WWW::Mechanize::Maypole; | 
| 2 | 1 |  |  | 1 |  | 35263 | use strict; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 31 |  | 
| 3 | 1 |  |  | 1 |  | 6 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 28 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 | 1 |  |  | 1 |  | 802 | use HTTP::Status(); | 
|  | 1 |  |  |  |  | 3918 |  | 
|  | 1 |  |  |  |  | 29 |  | 
| 6 | 1 |  |  | 1 |  | 835 | use HTTP::Headers::Util; | 
|  | 1 |  |  |  |  | 793 |  | 
|  | 1 |  |  |  |  | 93 |  | 
| 7 | 1 |  |  | 1 |  | 762 | use URI; | 
|  | 1 |  |  |  |  | 7797 |  | 
|  | 1 |  |  |  |  | 26 |  | 
| 8 | 1 |  |  | 1 |  | 834 | use UNIVERSAL::require; | 
|  | 1 |  |  |  |  | 1898 |  | 
|  | 1 |  |  |  |  | 13 |  | 
| 9 | 1 |  |  | 1 |  | 1057 | use NEXT; | 
|  | 1 |  |  |  |  | 2324 |  | 
|  | 1 |  |  |  |  | 8 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 1 |  |  | 1 |  | 1109 | use Test::WWW::Mechanize; | 
|  | 1 |  |  |  |  | 235150 |  | 
|  | 1 |  |  |  |  | 23 |  | 
| 12 | 1 |  |  | 1 |  | 1701 | use Class::Data::Inheritable; | 
|  | 1 |  |  |  |  | 353 |  | 
|  | 1 |  |  |  |  | 426 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 | 1 |  |  | 1 |  | 50 | use base qw/ Test::WWW::Mechanize Class::Data::Inheritable /; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 275 |  | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | __PACKAGE__->mk_classdata( '_the_app' ); | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | our $VERSION = '0.23'; | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | sub import | 
| 21 |  |  |  |  |  |  | { | 
| 22 | 1 |  |  | 1 |  | 10 | my ( $class, $app, @db_args ) = @_; | 
| 23 |  |  |  |  |  |  |  | 
| 24 | 1 | 50 |  |  |  | 5 | if ( @db_args ) | 
| 25 |  |  |  |  |  |  | { | 
| 26 | 0 |  |  |  |  | 0 | my $args = join ':', @db_args; | 
| 27 |  |  |  |  |  |  |  | 
| 28 | 0 |  |  |  |  | 0 | eval "package $app; | 
| 29 |  |  |  |  |  |  | sub setup { shift->NEXT::DISTINCT::setup( '$args' ) }";    # qw(@db_args) fails | 
| 30 | 0 | 0 |  |  |  | 0 | die $@ if $@; | 
| 31 |  |  |  |  |  |  | } | 
| 32 |  |  |  |  |  |  |  | 
| 33 | 1 |  |  |  |  | 4 | $class->_the_app( $app ); | 
| 34 |  |  |  |  |  |  |  | 
| 35 | 1 | 50 |  |  |  | 15 | $app->require or die "Couldn't load Maypole app '$app': $@"; | 
| 36 |  |  |  |  |  |  |  | 
| 37 | 0 |  |  |  |  |  | my @exports = qw/ send_output parse_location get_template_root parse_args /; | 
| 38 |  |  |  |  |  |  |  | 
| 39 | 1 |  |  | 1 |  | 6 | no strict 'refs'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 871 |  | 
| 40 | 0 |  |  |  |  |  | *{"$app\::$_"} = \&$_ for @exports; | 
|  | 0 |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | } | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | =head1 NAME | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | Test::WWW::Mechanize::Maypole - Test::WWW::Mechanize for Maypole | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | use Test::WWW::Mechanize::Maypole 'BeerDB'; | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | # or load a test database instead of the one configured in BeerDB.pm: | 
| 52 |  |  |  |  |  |  | # | 
| 53 |  |  |  |  |  |  | # use Test::WWW::Mechanize::Maypole 'BeerDB', 'dbi:SQLite:test-beerdb.db'; | 
| 54 |  |  |  |  |  |  | # use Test::WWW::Mechanize::Maypole 'BeerDB', 'dbi:mysql:beer_d_b', 'dhoworth', 'password'; | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | $ENV{MAYPOLE_TEMPLATES} = 'path/to/templates'; | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | my $mech = Test::WWW::Mechanize::Maypole->new; | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | # | 
| 61 |  |  |  |  |  |  | # basic tests: | 
| 62 |  |  |  |  |  |  | # | 
| 63 |  |  |  |  |  |  | $mech->get_ok( "http://localhost/beerdb/" ); | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | is( $mech->ct, "text/html" ); | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | $mech->content_contains( 'This is the frontpage' ); | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | # | 
| 70 |  |  |  |  |  |  | # logging in and storing cookies: | 
| 71 |  |  |  |  |  |  | # | 
| 72 |  |  |  |  |  |  | $mech->get_ok("http://localhost/beerdb/customer/buybeer"); | 
| 73 |  |  |  |  |  |  | $mech->content_contains( 'Login to BeerDB', 'got login page' ); | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | # specify which form we're interested in | 
| 76 |  |  |  |  |  |  | $mech->form_number(1); # the 1st form | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | # fill in credentials | 
| 79 |  |  |  |  |  |  | $mech->field( 'username' => 'landlord' ); | 
| 80 |  |  |  |  |  |  | $mech->field( 'password' => 'handpump' ); | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | # get a HTTP::Response back | 
| 83 |  |  |  |  |  |  | my $response = $mech->click_button( name => 'submit' ); | 
| 84 |  |  |  |  |  |  | like( $response->content, qr/Shop for beer/, 'got customer/buybeer page'  ); | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | # check our cookies give access to other pages | 
| 87 |  |  |  |  |  |  | $mech->get_ok( "http://localhost/beerdb/customer/edit" ); | 
| 88 |  |  |  |  |  |  | $mech->content_contains( 'Update your details', "got customer account edit page"); | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | # ... see Test::WWW::Mechanize for many more test methods | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | By inheriting from L, this module provides two key benefits | 
| 96 |  |  |  |  |  |  | over using L in test scripts. First, it inherits a plethora of methods | 
| 97 |  |  |  |  |  |  | for testing web content. Second, cookies are handled transparently, allowing | 
| 98 |  |  |  |  |  |  | you to test applications that use cookie-based sessions and authentication. | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | Testing web applications has always been a bit tricky, normally | 
| 101 |  |  |  |  |  |  | starting a web server for your application and making real HTTP | 
| 102 |  |  |  |  |  |  | requests to it. This module allows you to test L web | 
| 103 |  |  |  |  |  |  | applications but does not start a server or issue HTTP | 
| 104 |  |  |  |  |  |  | requests. Instead, it passes the HTTP request parameters directly to | 
| 105 |  |  |  |  |  |  | L. Thus you do not need to use a real hostname: | 
| 106 |  |  |  |  |  |  | "http://localhost/" will do. | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | This makes testing fast and easy. L provides | 
| 109 |  |  |  |  |  |  | functions for common web testing scenarios. For example: | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | $mech->get_ok( $page ); | 
| 112 |  |  |  |  |  |  | $mech->title_is( "Invoice Status", "Make sure we're on the invoice page" ); | 
| 113 |  |  |  |  |  |  | $mech->content_contains( "David Baird", "My name somewhere" ); | 
| 114 |  |  |  |  |  |  | $mech->content_like( qr/(cpan|perl)\.org/, "Link to perl.org or CPAN" ); | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | This module supports cookies automatically. | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | =head1 LOADING | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | To use this module you must pass it the name of the application. | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | Additionally, you can pass an alternate set of database connection parameters, and | 
| 123 |  |  |  |  |  |  | these will override the settings configured in your application. Useful for connecting | 
| 124 |  |  |  |  |  |  | to a test database without having to alter your production code. This won't work if | 
| 125 |  |  |  |  |  |  | your application calls C inside a C block. | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | =head1 CONSTRUCTOR | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | =head2 new | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | Inherited from L, which passes any parameters through to | 
| 132 |  |  |  |  |  |  | L. | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | Note that the name of the Maypole application should be passed to the C | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | use Test::WWW::Mechanize::Maypole 'BeerDB'; | 
| 137 |  |  |  |  |  |  | my $mech = Test::WWW::Mechanize::Maypole->new; | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | =head1 ENVIRONMENT | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | Set C<$ENV{MAYPOLE_TEMPLATES}> to the path where the templates for the application | 
| 142 |  |  |  |  |  |  | can be found. Defaults to C<'.'>. | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | =head1 METHODS | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | Please see the documentation for L. | 
| 147 |  |  |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | =cut | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | sub _make_request | 
| 151 |  |  |  |  |  |  | { | 
| 152 | 0 |  |  | 0 |  |  | my ( $self, $request ) = @_; | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 0 | 0 |  |  |  |  | $self->cookie_jar->add_cookie_header($request) if $self->cookie_jar; | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | # make an HTTP::Response object, to be populated during the handler() call | 
| 157 | 0 |  |  |  |  |  | my $response = HTTP::Response->new; | 
| 158 | 0 |  |  |  |  |  | $response->date( time ); | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | # parse_location() normally takes the url from @ARGV, here we provide $request. | 
| 161 |  |  |  |  |  |  | # $response is taken by send_output | 
| 162 | 0 |  |  |  |  |  | local @ARGV = ( $request, $response ); | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | # handler() calls send_output with no args, so we provide $response via @ARGV | 
| 165 | 0 |  |  |  |  |  | my $status = $self->_the_app->handler; | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | # Translate Maypole codes to HTTP::Status codes. Maypole only has 2 codes, OK (0) | 
| 168 |  |  |  |  |  |  | # and everything else (-1). We'll assume -1 is an error. Note that other codes can | 
| 169 |  |  |  |  |  |  | # be returned by custom application code - we assume anything else is a proper | 
| 170 |  |  |  |  |  |  | # HTTP status | 
| 171 | 0 | 0 |  |  |  |  | if ( defined $status ) | 
| 172 |  |  |  |  |  |  | { | 
| 173 | 0 | 0 |  |  |  |  | $status = 200 if $status == 0; | 
| 174 | 0 | 0 |  |  |  |  | $status = 500 if $status == -1; | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  | else | 
| 177 |  |  |  |  |  |  | { | 
| 178 | 0 |  |  |  |  |  | warn "Undefined response code"; | 
| 179 | 0 |  |  |  |  |  | $status = 500; | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | # $response has now been populated during the handler() call | 
| 183 | 0 |  |  |  |  |  | $response->code( $status ); | 
| 184 | 0 |  |  |  |  |  | $response->message( HTTP::Status::status_message( $status ) ); | 
| 185 |  |  |  |  |  |  |  | 
| 186 | 0 |  |  |  |  |  | $response->header( 'Content-Base', $request->uri ); | 
| 187 | 0 |  |  |  |  |  | $response->request( $request ); | 
| 188 |  |  |  |  |  |  |  | 
| 189 | 0 | 0 |  |  |  |  | $self->cookie_jar->extract_cookies($response) if $self->cookie_jar; | 
| 190 |  |  |  |  |  |  |  | 
| 191 | 0 |  |  |  |  |  | return $response; | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | =head2 Exported methods | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | These methods are exported into the application's namespace, and override methods that would | 
| 198 |  |  |  |  |  |  | otherwise be inherited from Maypole or the Maypole frontend. | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | You will not normally need to use these methods in your test scripts. | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | If you need to replace these methods with custom versions, let me know, and I'll make exporting | 
| 203 |  |  |  |  |  |  | more flexible. | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | =over 4 | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | =item send_output | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | =item parse_location | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | =item parse_args | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | =item get_template_root | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | =back | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | =cut | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | # Called by Maypole::handler(), with no arguments, so $response is placed in @ARGV for | 
| 220 |  |  |  |  |  |  | # retrieval here. This method, and _make_request, are the only places that use the | 
| 221 |  |  |  |  |  |  | # $response object. | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | # Grabs Maypole::Headers and populates the HTTP::Response object. | 
| 224 |  |  |  |  |  |  | sub send_output | 
| 225 |  |  |  |  |  |  | { | 
| 226 | 0 |  |  | 0 | 1 |  | my ( $maypole ) = @_; | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 0 |  |  |  |  |  | my $response = shift @ARGV; | 
| 229 |  |  |  |  |  |  |  | 
| 230 | 0 | 0 |  |  |  |  | $response->content_type( | 
| 231 |  |  |  |  |  |  | $maypole->{content_type} =~ m/^text/ | 
| 232 |  |  |  |  |  |  | ? $maypole->{content_type} . "; charset=" . $maypole->{document_encoding} | 
| 233 |  |  |  |  |  |  | : $maypole->{content_type} | 
| 234 |  |  |  |  |  |  | ); | 
| 235 |  |  |  |  |  |  |  | 
| 236 | 1 |  |  | 1 |  | 7 | $response->content_length( do { use bytes; length $maypole->{output} } ); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 10 |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | # if there are cookies, this is where they get passed on | 
| 239 | 0 |  |  |  |  |  | foreach ($maypole->headers_out->field_names) | 
| 240 |  |  |  |  |  |  | { | 
| 241 | 0 | 0 |  |  |  |  | next if /^Content-(Type|Length)/; | 
| 242 | 0 |  |  |  |  |  | $response->header( $_ => $maypole->headers_out->get($_) ); | 
| 243 |  |  |  |  |  |  | } | 
| 244 |  |  |  |  |  |  |  | 
| 245 | 0 |  |  |  |  |  | $response->content( $maypole->{output} ); | 
| 246 |  |  |  |  |  |  | } | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | # Called by Maypole::handler() with no arguments. | 
| 249 |  |  |  |  |  |  | sub parse_location | 
| 250 |  |  |  |  |  |  | { | 
| 251 | 0 |  |  | 0 | 1 |  | my ( $self ) = @_; | 
| 252 |  |  |  |  |  |  |  | 
| 253 | 0 |  |  |  |  |  | my $request = shift @ARGV; | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | # This is a HTTP::Headers object. | 
| 256 | 0 |  |  |  |  |  | my $headers_in = $request->headers; | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | # Maypole::Headers is a simple subclass of HTTP::Headers | 
| 259 | 0 |  |  |  |  |  | bless $headers_in, 'Maypole::Headers'; | 
| 260 |  |  |  |  |  |  |  | 
| 261 | 0 |  |  |  |  |  | $self->headers_in( $headers_in ); | 
| 262 |  |  |  |  |  |  |  | 
| 263 | 0 |  |  |  |  |  | my $uri = $request->uri; | 
| 264 |  |  |  |  |  |  |  | 
| 265 | 0 |  |  |  |  |  | ( my $uri_base = $self->config->uri_base ) =~ s:/$::; | 
| 266 |  |  |  |  |  |  |  | 
| 267 | 0 |  |  |  |  |  | my $root = URI->new( $uri_base )->path; | 
| 268 |  |  |  |  |  |  |  | 
| 269 | 0 |  |  |  |  |  | $self->{path} = $uri->path; | 
| 270 | 0 |  |  |  |  |  | $self->{path} =~ s:^$root/?::i; | 
| 271 |  |  |  |  |  |  |  | 
| 272 | 0 |  |  |  |  |  | $self->parse_path; | 
| 273 | 0 |  |  |  |  |  | $self->parse_args( $request ); | 
| 274 |  |  |  |  |  |  | } | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | sub parse_args | 
| 277 |  |  |  |  |  |  | { | 
| 278 | 0 |  |  | 0 | 1 |  | my ( $self, $request ) = @_; | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | # this code stolen from Catalyst::Engine::HTTP::Base::prepare_parameters(), | 
| 281 |  |  |  |  |  |  | # with **file uploads removed** | 
| 282 |  |  |  |  |  |  |  | 
| 283 | 0 |  |  |  |  |  | my @params; | 
| 284 |  |  |  |  |  |  |  | 
| 285 | 0 |  |  |  |  |  | push( @params, $request->uri->query_form ); | 
| 286 |  |  |  |  |  |  |  | 
| 287 | 0 | 0 |  |  |  |  | if ( $request->content_type eq 'application/x-www-form-urlencoded' ) | 
| 288 |  |  |  |  |  |  | { | 
| 289 | 0 |  |  |  |  |  | my $uri = URI->new('http:'); | 
| 290 | 0 |  |  |  |  |  | $uri->query( $request->content ); | 
| 291 | 0 |  |  |  |  |  | push( @params, $uri->query_form ); | 
| 292 |  |  |  |  |  |  | } | 
| 293 |  |  |  |  |  |  |  | 
| 294 | 0 | 0 |  |  |  |  | if ( $request->content_type eq 'multipart/form-data' ) | 
| 295 |  |  |  |  |  |  | { | 
| 296 | 0 |  |  |  |  |  | for my $part ( $request->parts ) | 
| 297 |  |  |  |  |  |  | { | 
| 298 | 0 |  |  |  |  |  | my $disposition = $part->header('Content-Disposition'); | 
| 299 | 0 |  |  |  |  |  | my %parameters  = @{ ( HTTP::Headers::Util::split_header_words($disposition) )[0] }; | 
|  | 0 |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  |  | 
| 301 | 0 | 0 |  |  |  |  | die 'File uploads not supported' if $parameters{filename}; | 
| 302 |  |  |  |  |  |  |  | 
| 303 | 0 |  |  |  |  |  | push( @params, $parameters{name}, $part->content ); | 
| 304 |  |  |  |  |  |  | } | 
| 305 |  |  |  |  |  |  | } | 
| 306 |  |  |  |  |  |  |  | 
| 307 | 0 |  |  |  |  |  | my %parameters; | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | # this from Catalyst::Request::param() | 
| 310 | 0 |  |  |  |  |  | while ( my ( $field, $value ) = splice( @params, 0, 2 ) ) | 
| 311 |  |  |  |  |  |  | { | 
| 312 | 0 | 0 |  |  |  |  | next unless defined $field; | 
| 313 |  |  |  |  |  |  |  | 
| 314 | 0 | 0 |  |  |  |  | if ( exists $parameters{$field} ) | 
| 315 |  |  |  |  |  |  | { | 
| 316 | 0 |  |  |  |  |  | for ( $parameters{$field} ) | 
| 317 |  |  |  |  |  |  | { | 
| 318 | 0 | 0 |  |  |  |  | $_ = [$_] unless ref($_) eq 'ARRAY'; | 
| 319 | 0 |  |  |  |  |  | push( @$_, $value ); | 
| 320 |  |  |  |  |  |  | } | 
| 321 |  |  |  |  |  |  | } | 
| 322 |  |  |  |  |  |  | else | 
| 323 |  |  |  |  |  |  | { | 
| 324 | 0 |  |  |  |  |  | $parameters{$field} = $value; | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  | } | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | # back to Maypole... | 
| 329 | 0 |  |  |  |  |  | $self->params( \%parameters ); | 
| 330 | 0 |  |  |  |  |  | $self->query(  \%parameters ); | 
| 331 |  |  |  |  |  |  | } | 
| 332 |  |  |  |  |  |  |  | 
| 333 | 0 | 0 |  | 0 | 1 |  | sub get_template_root { $ENV{MAYPOLE_TEMPLATES} || '.' } | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | 1; | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | __END__ |