File Coverage

lib/Dancer/Plugin/Interchange6/Routes.pm
Criterion Covered Total %
statement 91 93 97.8
branch 24 30 80.0
condition 3 3 100.0
subroutine 12 12 100.0
pod n/a
total 130 138 94.2


line stmt bran cond sub pod time code
1             package Dancer::Plugin::Interchange6::Routes;
2              
3 2     2   19761 use Dancer ':syntax';
  2         3  
  2         9  
4 2     2   539 use Dancer::Plugin;
  2         2  
  2         145  
5 2     2   10 use Dancer::Plugin::Interchange6;
  2         2  
  2         132  
6 2     2   697 use Dancer::Plugin::Interchange6::Routes::Account;
  2         14  
  2         51  
7 2     2   749 use Dancer::Plugin::Interchange6::Routes::Cart;
  2         4  
  2         52  
8 2     2   735 use Dancer::Plugin::Interchange6::Routes::Checkout;
  2         4  
  2         49  
9 2     2   9 use Dancer::Plugin::Auth::Extensible;
  2         2  
  2         2232  
10              
11             =head1 NAME
12              
13             Dancer::Plugin::Interchange6::Routes - Routes for Interchange6 Shop Machine
14              
15             =head2 ROUTES
16              
17             The following routes are provided by this plugin.
18              
19             Active routes are automatically installed by the C<shop_setup_routes> keyword:
20              
21             =over 4
22              
23             =item cart (C</cart>)
24              
25             Route for displaying and updating the cart.
26              
27             =item checkout (C</checkout>)
28              
29             Route for the checkout process (not B<active> and not recommended).
30              
31             =item login (C</login>)
32              
33             Login route.
34              
35             =item logout (C</logout>)
36              
37             Logout route.
38              
39             =item navigation
40              
41             Route for displaying navigation pages, for example
42             categories and menus.
43              
44             The number of products shown on the navigation page can
45             be configured with the C<records> option:
46              
47             plugins:
48             Interchange6::Routes:
49             navigation:
50             records: 20
51              
52             =item product
53              
54             Route for displaying products.
55              
56             =back
57              
58             =head2 CONFIGURATION
59              
60             The template for each route type can be configured:
61              
62             plugins:
63             Interchange6::Routes:
64             account:
65             login:
66             template: login
67             uri: login
68             success_uri:
69             logout:
70             template: logout
71             uri: logout
72             cart:
73             template: cart
74             uri: cart
75             active: 1
76             checkout:
77             template: checkout
78             uri: checkout
79             active: 0
80             navigation:
81             template: listing
82             records: 0
83             product:
84             template: product
85              
86             This sample configuration shows the current defaults.
87              
88             =head2 HOOKS
89              
90             The following hooks are available to manipulate the values
91             passed to the templates:
92              
93             =over 4
94              
95             =item before_product_display
96              
97             The hook sub receives a hash reference, where the Product object
98             is the value of the C<product> key.
99              
100             =item before_cart_display
101              
102             =item before_checkout_display
103              
104             =item before_navigation_search
105              
106             This hook is called if a navigation uri is requested and before product search
107             queries are generated.
108              
109             The hook sub receives the navigation data as hash reference:
110              
111             =over 4
112              
113             =item navigation
114              
115             Navigation object.
116              
117             =item page
118              
119             Page number found at end of URI or 1 if no page number found.
120              
121             =item template
122              
123             Name of template.
124              
125             =back
126              
127             The navigation hash reference can be modified inside the hook and all changes
128             will be visible to the navigation route (and also the template) after the hook
129             returns.
130              
131             =item before_navigation_display
132              
133             The hook sub receives the navigation data as hash reference:
134              
135             =over 4
136              
137             =item navigation
138              
139             Navigation object.
140              
141             =item products
142              
143             Product listing for this navigation item. The product listing is generated
144             using L<Interchange6::Schema::Result::Product/listing>.
145              
146             =item pager
147              
148             L<Data::Page> object for L</products>.
149              
150             To get the full count of products call C<total_entries> on the Data::Page
151             object.
152              
153             =item template
154              
155             Name of template. In order to use another template, change
156             the value in the hashref.
157              
158             hook 'before_navigation_display' => sub {
159             my $navigation_data = shift;
160              
161             if ($navigation_data->{navigation}->uri =~ /^admin/) {
162             $navigation_data->{template} = 'admin_listing';
163             }
164             };
165              
166             =back
167              
168             =item before_login_display
169              
170             =back
171              
172             =head3 EXAMPLES
173              
174             Disable parts of layout on the login view:
175              
176             hook 'before_login_display' => sub {
177             my $tokens = shift;
178              
179             $tokens->{layout_noleft} = 1;
180             $tokens->{layout_noright} = 1;
181             };
182              
183             =cut
184              
185             =head1 DANCER HOOKS
186              
187             The following standard L<Dancer> hooks are used:
188              
189             =head2 before
190              
191             Set L<Interchange6::Schema/current_user> for the default schema
192             to L<Dancer::Plugin::Auth::Extensible/logged_in_user> or C<undef>.
193              
194             =cut
195              
196             hook before => sub {
197             shop_schema->set_current_user( logged_in_user || undef );
198             };
199              
200             register shop_setup_routes => sub {
201 1     1   3228 _setup_routes();
202             };
203              
204             register_hook (qw/before_product_display before_navigation_search
205             before_navigation_display/);
206             register_plugin;
207              
208             our $object_autodetect = 0;
209              
210             our %route_defaults = (
211             account => {login => {template => 'login',
212             uri => 'login',
213             success_uri => '',
214             },
215             logout => {template => 'logout',
216             uri => 'logout',
217             },
218             },
219             cart => {template => 'cart',
220             uri => 'cart',
221             active => 1,
222             },
223             checkout => {template => 'checkout',
224             uri => 'checkout',
225             active => 0,
226             },
227             navigation => {template => 'listing',
228             records => 0,
229             },
230             product => {template => 'product'},
231             );
232              
233             sub _setup_routes {
234 1     1   1 my $sub;
235 1         3 my $plugin_config = plugin_setting;
236              
237             # update settings with defaults
238 1         24 my $routes_config = _config_routes($plugin_config, \%route_defaults);
239              
240             # display warnings
241 1         2 _config_warnings($routes_config);
242              
243             # check whether template engine has object autodetect
244 1 50       3 if (config->{template} eq 'template_flute') {
245 0         0 $object_autodetect = 1;
246             }
247              
248             # account routes
249 1         9 my $account_routes = Dancer::Plugin::Interchange6::Routes::Account::account_routes($routes_config);
250              
251             get '/' . $routes_config->{account}->{login}->{uri}
252 1         4 => $account_routes->{login}->{get};
253              
254             post '/' . $routes_config->{account}->{login}->{uri}
255 1         246 => $account_routes->{login}->{post};
256              
257             any ['get', 'post'] => '/' . $routes_config->{account}->{logout}->{uri}
258 1         116 => $account_routes->{logout}->{any};
259              
260 1 50       348 if ($routes_config->{cart}->{active}) {
261             # routes for cart
262 1         3 my $cart_sub = Dancer::Plugin::Interchange6::Routes::Cart::cart_route($routes_config);
263 1         4 get '/' . $routes_config->{cart}->{uri} => $cart_sub;
264 1         246 post '/' . $routes_config->{cart}->{uri} => $cart_sub;
265             }
266              
267 1 50       121 if ($routes_config->{checkout}->{active}) {
268             # routes for checkout
269 1         3 my $checkout_sub = Dancer::Plugin::Interchange6::Routes::Checkout::checkout_route($routes_config);
270 1         4 get '/' . $routes_config->{checkout}->{uri} => $checkout_sub;
271 1         229 post '/' . $routes_config->{checkout}->{uri} => $checkout_sub;
272             }
273              
274             # fallback route for flypage and navigation
275             get qr{/(?<path>.+)} => sub {
276 22     22   138057 my $path = captures->{'path'};
277              
278             # check for a matching product by uri
279 22         2669 my $product = shop_product->single( { uri => $path, active => 1 } );
280              
281 22 100       56192 if (!$product) {
282              
283             # check for a matching product by sku
284 15         423 $product = shop_product->single( { sku => $path, active => 1 } );
285              
286 15 100 100     36196 if ( $product && $product->uri ) {
287              
288             # permanent redirect to specific URL
289 3         216 debug "Redirecting permanently to product uri ",
290             $product->uri,
291             " for $path.";
292 3         218 return redirect( uri_for( $product->uri ), 301 );
293             }
294             }
295              
296 19 100       546 if ($product) {
297              
298             # flypage
299 8         22 my $tokens = { product => $product };
300              
301 8         36 execute_hook( 'before_product_display', $tokens );
302              
303             my $output = template $routes_config->{product}->{template},
304 8         1962 $tokens;
305              
306             # temporary way to erase cart errors from missing variants
307 8         41107 session shop_cart_error => undef;
308              
309 8         50432 return $output;
310             }
311              
312             # check for page number
313 11         19 my $page;
314              
315 11 100       60 if ($path =~ s%/([1-9][0-9]*)$%%) {
316 1         3 $page = $1;
317             }
318             else {
319 10         18 $page = 1;
320             }
321              
322             # first check for navigation item
323 11         43 my $nav = shop_navigation->single( { uri => $path, active => 1 } );
324              
325 11 100       21948 if (defined $nav) {
326              
327             # navigation item found
328              
329             # retrieve navigation attribute for template
330 6         158 my $template = $routes_config->{navigation}->{template};
331              
332 6 100       45 if ( my $attr_value = $nav->find_attribute_value('template') ) {
333 5         69927 debug "Change template name from $template to $attr_value due to navigation attribute.";
334 5         273 $template = $attr_value;
335             }
336              
337 6         7995 my $tokens = {
338             navigation => $nav,
339             page => $page,
340             template => $template
341             };
342              
343 6         22 execute_hook('before_navigation_search', $tokens);
344              
345             # Find product listing for this nav for active products only.
346             # In order_by me refers to navigation_products.
347              
348             my $products =
349             $tokens->{navigation}
350             ->navigation_products
351 6         1198 ->search_related('product')
352             ->active
353             ->listing
354             ->order_by('!me.priority,!product.priority');
355              
356 6 50       316172 if ( defined $routes_config->{navigation}->{records} ) {
357              
358             # records per page is set in configuration so page the
359             # result set
360              
361             $products =
362             $products->rows( $routes_config->{navigation}->{records} )
363 6         3481 ->page( $tokens->{page} );
364             }
365              
366             # get a pager
367              
368 6         3236 $tokens->{pager} = $products->pager;
369              
370             # can template autodetect objects?
371              
372 6 50       6327 if (!$object_autodetect) {
373 6         79 $products = [$products->all];
374             }
375              
376 6         94318 $tokens->{products} = $products;
377              
378 6         369 execute_hook('before_navigation_display', $tokens);
379              
380 6         37162 return template $tokens->{template}, $tokens;
381             }
382              
383             # check for uri redirect record
384 5         135 my ( $redirect, $status_code ) = shop_redirect($path);
385              
386 5 100       19073 if ($redirect) {
387             # redirect to specific URL
388 2         44 debug "UriRedirect record found redirecting uri $redirect"
389             . " to $path with status code $status_code";
390 2         111 return redirect( uri_for($redirect), $status_code );
391             }
392              
393             # display not_found page
394 3         67 status 'not_found';
395 3         91 forward 404;
396 1         132 };
397             }
398              
399             sub _config_routes {
400 5     5   6 my ($settings, $defaults) = @_;
401 5         4 my ($key, $vref, $name, $value, $set_value);
402              
403 5 100       7 unless (ref($defaults)) {
404 2         6 return;
405             }
406              
407 3         10 while (($key, $vref) = each %$defaults) {
408 10 100       14 if (exists $settings->{$key}) {
409             # recurse
410 4         9 _config_routes($settings->{$key}, $defaults->{$key});
411             }
412             else {
413 6         13 $settings->{$key} = $defaults->{$key};
414             }
415             }
416              
417 3         6 return $settings;
418             }
419              
420             sub _config_warnings {
421 1     1   2 my ($settings) = @_;
422              
423 1 50       5 if ($settings->{navigation}->{records} == 0) {
424 0           warning __PACKAGE__, ": Maximum number of navigation records is zero.\n";
425             }
426             }
427              
428             1;