File Coverage

blib/lib/Dancer2/Plugin/Interchange6/Routes.pm
Criterion Covered Total %
statement 75 75 100.0
branch 22 24 91.6
condition 3 3 100.0
subroutine 7 7 100.0
pod 1 1 100.0
total 108 110 98.1


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::Interchange6::Routes;
2              
3 2     2   6925 use Dancer2::Plugin;
  2         3  
  2         11  
4 2     2   5089 use Dancer2::Plugin::Interchange6::Routes::Account;
  2         4  
  2         52  
5 2     2   700 use Dancer2::Plugin::Interchange6::Routes::Cart;
  2         3  
  2         53  
6 2     2   654 use Dancer2::Plugin::Interchange6::Routes::Checkout;
  2         3  
  2         1854  
7              
8             =head1 NAME
9              
10             Dancer2::Plugin::Interchange6::Routes - Routes for Interchange6 Shop Machine
11              
12             =head2 shop_setup_routes
13              
14             The following routes are provided by this plugin.
15              
16             Active routes are automatically installed by the C<shop_setup_routes> keyword:
17              
18             =over 4
19              
20             =item cart (C</cart>)
21              
22             Route for displaying and updating the cart.
23              
24             =item checkout (C</checkout>)
25              
26             Route for the checkout process (not B<active> and not recommended).
27              
28             =item login (C</login>)
29              
30             Login route.
31              
32             =item logout (C</logout>)
33              
34             Logout route.
35              
36             =item navigation
37              
38             Route for displaying navigation pages, for example
39             categories and menus.
40              
41             The number of products shown on the navigation page can
42             be configured with the C<records> option:
43              
44             plugins:
45             Interchange6::Routes:
46             navigation:
47             records: 20
48              
49             =item product
50              
51             Route for displaying products.
52              
53             =back
54              
55             =head2 CONFIGURATION
56              
57             The template for each route type can be configured:
58              
59             plugins:
60             Interchange6::Routes:
61             account:
62             login:
63             template: login
64             uri: login
65             success_uri:
66             logout:
67             template: logout
68             uri: logout
69             cart:
70             template: cart
71             uri: cart
72             active: 1
73             checkout:
74             template: checkout
75             uri: checkout
76             active: 0
77             navigation:
78             template: listing
79             records: 0
80             product:
81             template: product
82              
83             This sample configuration shows the current defaults.
84              
85             =head2 HOOKS
86              
87             The following hooks are available to manipulate the values
88             passed to the templates:
89              
90             =over 4
91              
92             =item before_product_display
93              
94             The hook sub receives a hash reference, where the Product object
95             is the value of the C<product> key.
96              
97             =item before_cart_display
98              
99             =item before_checkout_display
100              
101             =item before_navigation_search
102              
103             This hook is called if a navigation uri is requested and before product search
104             queries are generated.
105              
106             The hook sub receives the navigation data as hash reference:
107              
108             =over 4
109              
110             =item navigation
111              
112             Navigation object.
113              
114             =item page
115              
116             Page number found at end of URI or 1 if no page number found.
117              
118             =item template
119              
120             Name of template.
121              
122             =back
123              
124             The navigation hash reference can be modified inside the hook and all changes
125             will be visible to the navigation route (and also the template) after the hook
126             returns.
127              
128             =item before_navigation_display
129              
130             The hook sub receives the navigation data as hash reference:
131              
132             =over 4
133              
134             =item navigation
135              
136             Navigation object.
137              
138             =item products
139              
140             Product listing for this navigation item. The product listing is generated
141             using L<Interchange6::Schema::Result::Product/listing>.
142              
143             =item pager
144              
145             L<Data::Page> object for L</products>.
146              
147             To get the full count of products call C<total_entries> on the Data::Page
148             object.
149              
150             =item template
151              
152             Name of template. In order to use another template, change
153             the value in the hashref.
154              
155             hook 'before_navigation_display' => sub {
156             my $navigation_data = shift;
157              
158             if ($navigation_data->{navigation}->uri =~ /^admin/) {
159             $navigation_data->{template} = 'admin_listing';
160             }
161             };
162              
163             =back
164              
165             =item before_login_display
166              
167             =back
168              
169             =head3 EXAMPLES
170              
171             Disable parts of layout on the login view:
172              
173             hook 'before_login_display' => sub {
174             my $tokens = shift;
175              
176             $tokens->{layout_noleft} = 1;
177             $tokens->{layout_noright} = 1;
178             };
179              
180             =cut
181              
182             =head1 DANCER HOOKS
183              
184             The following standard L<Dancer2> hooks are used:
185              
186             =head2 before
187              
188             Set L<Interchange6::Schema/current_user> for the default schema
189             to L<Dancer2::Plugin::Auth::Extensible/logged_in_user> or C<undef>.
190              
191             =cut
192              
193             plugin_hooks(
194             qw/before_product_display before_navigation_search
195             before_navigation_display/
196             );
197              
198             # config attributes
199              
200             has login_template => (
201             is => 'ro',
202             from_config => 'account.login.template',
203             default => sub { 'login' },
204             );
205              
206             has login_uri => (
207             is => 'ro',
208             from_config => 'account.login.uri',
209             default => sub { 'login' },
210             );
211              
212             has login_success_uri => (
213             is => 'ro',
214             from_config => 'account.login.success_uri',
215             default => sub { '' },
216             );
217              
218             has logout_template => (
219             is => 'ro',
220             from_config => 'account.logout.template',
221             default => sub { 'logout' },
222             );
223              
224             has logout_uri => (
225             is => 'ro',
226             from_config => 'account.logout.uri',
227             default => sub { 'logout' },
228             );
229              
230             has cart_template => (
231             is => 'ro',
232             from_config => 'cart.template',
233             default => sub { 'cart' },
234             );
235              
236             has cart_uri => (
237             is => 'ro',
238             from_config => 'cart.uri',
239             default => sub { 'cart' },
240             );
241              
242             has cart_active => (
243             is => 'ro',
244             from_config => 'cart.active',
245             default => sub { 1 },
246             );
247              
248             has checkout_template => (
249             is => 'ro',
250             from_config => 'checkout.template',
251             default => sub { 'checkout' },
252             );
253              
254             has checkout_uri => (
255             is => 'ro',
256             from_config => 'checkout.uri',
257             default => sub { 'checkout' },
258             );
259              
260             has checkout_active => (
261             is => 'ro',
262             from_config => 'checkout.active',
263             default => sub { 0 },
264             );
265              
266             has navigation_template => (
267             is => 'ro',
268             from_config => 'navigation.template',
269             default => sub { 'listing' },
270             );
271              
272             has navigation_records => (
273             is => 'ro',
274             from_config => 'navigation.records',
275             default => sub { 0 },
276             );
277              
278             has product_template => (
279             is => 'ro',
280             from_config => 'product.template',
281             default => sub { 'product' },
282             );
283              
284             # plugins we use
285              
286             has plugin_auth_extensible => (
287             is => 'ro',
288             lazy => 1,
289             default => sub {
290             $_[0]->app->with_plugin('Dancer2::Plugin::Auth::Extensible');
291             },
292             handles => [ 'logged_in_user', ],
293             );
294              
295             has plugin_interchange6 => (
296             is => 'ro',
297             lazy => 1,
298             default => sub {
299             $_[0]->app->with_plugin('Dancer2::Plugin::Interchange6');
300             },
301             handles => [
302             'shop_address', 'shop_attribute',
303             'shop_cart', 'shop_charge',
304             'shop_country', 'shop_message',
305             'shop_navigation', 'shop_order',
306             'shop_product', 'shop_redirect',
307             'shop_schema', 'shop_state',
308             'shop_user',
309             ],
310             );
311              
312             # other attributes
313              
314             has object_autodetect => (
315             is => 'ro',
316             lazy => 1,
317             default =>
318             sub { $_[0]->app->config->{template} eq 'template_flute' ? 1 : 0 },
319             );
320              
321             # keywords
322              
323             plugin_keywords 'shop_setup_routes';
324              
325             sub shop_setup_routes {
326 2     2 1 570703 my $plugin = shift;
327 2         20 my $app = $plugin->app;
328              
329 2         4 my $sub;
330              
331             # display warnings
332 2         7 $plugin->_config_warnings;
333              
334             # account routes
335 2         438 my $account_routes =
336             Dancer2::Plugin::Interchange6::Routes::Account::account_routes($plugin);
337              
338             $app->add_route(
339             method => 'get',
340             regexp => '/' . $plugin->login_uri,
341             code => $account_routes->{login}->{get},
342 2         10 );
343              
344             $app->add_route(
345             method => 'post',
346             regexp => '/' . $plugin->login_uri,
347             code => $account_routes->{login}->{post},
348 2         363 );
349              
350 2         318 foreach my $method ( 'get', 'post' ) {
351             $app->add_route(
352             method => $method,
353             regexp => '/' . $plugin->logout_uri,
354             code => $account_routes->{logout}->{any},
355 4         359 );
356             }
357              
358 2 100       319 if ( $plugin->cart_active ) {
359              
360             # routes for cart
361 1         6 my $cart_sub =
362             Dancer2::Plugin::Interchange6::Routes::Cart::cart_route($plugin);
363              
364 1         3 foreach my $method ( 'get', 'post' ) {
365 2         217 $app->add_route(
366             method => $method,
367             regexp => '/' . $plugin->cart_uri,
368             code => $cart_sub,
369             );
370             }
371             }
372              
373 2 100       521 if ( $plugin->checkout_active ) {
374              
375             # routes for checkout
376 1         366 my $checkout_sub =
377             Dancer2::Plugin::Interchange6::Routes::Checkout::checkout_route(
378             $plugin);
379              
380 1         4 foreach my $method ( 'get', 'post' ) {
381 2         208 $app->add_route(
382             method => $method,
383             regexp => '/' . $plugin->checkout_uri,
384             code => $checkout_sub,
385             );
386             }
387             }
388              
389             # fallback route for flypage and navigation
390             $app->add_route(
391             method => 'get',
392             regexp => qr{/(?<path>.+)},
393             code => sub {
394 22     22   4262 my $app = shift;
395 22         127 my $path = $app->request->captures->{'path'};
396              
397 22         761 my $schema = $plugin->shop_schema;
398              
399             # check for a matching product by uri
400 22         1437 my $product =
401             $plugin->shop_product->single( { uri => $path, active => 1 } );
402              
403 22 100       58115 if ( !$product ) {
404              
405             # check for a matching product by sku
406 15         669 $product = $plugin->shop_product->single(
407             { sku => $path, active => 1 } );
408              
409 15 100 100     37269 if ( $product && $product->uri ) {
410              
411             # permanent redirect to specific URL
412 3         214 $app->log( "debug",
413             "Redirecting permanently to product uri ",
414             $product->uri, " for $path." );
415              
416 3         1693 return $app->redirect(
417             $app->request->uri_for( $product->uri ), 301 );
418             }
419             }
420              
421 19 100       545 if ($product) {
422              
423             # flypage
424 8         26 my $tokens = { product => $product };
425              
426 8         168 $plugin->execute_plugin_hook( 'before_product_display',
427             $tokens );
428              
429 8         6473 my $output =
430             $app->template( $plugin->product_template, $tokens );
431              
432             # temporary way to erase cart errors from missing variants
433 8         8254 $app->session->write( shop_cart_error => undef );
434              
435 8         500 return $output;
436             }
437              
438             # check for page number
439 11         22 my $page;
440              
441 11 100       74 if ( $path =~ s%/([1-9][0-9]*)$%% ) {
442 1         3 $page = $1;
443             }
444             else {
445 10         20 $page = 1;
446             }
447              
448             # first check for navigation item
449 11         226 my $nav =
450             $plugin->shop_navigation->single( { uri => $path, active => 1 } );
451              
452 11 100       23112 if ( defined $nav ) {
453              
454             # navigation item found
455              
456             # retrieve navigation attribute for template
457 6         258 my $template = $plugin->navigation_template;
458              
459 6 100       99 if ( my $attr_value = $nav->find_attribute_value('template') ) {
460 5         76709 $app->log( "debug",
461             "Change template name from $template"
462             . " to $attr_value due to navigation attribute." );
463 5         2669 $template = $attr_value;
464             }
465              
466 6         7744 my $tokens = {
467             navigation => $nav,
468             page => $page,
469             template => $template
470             };
471              
472 6         118 $plugin->execute_plugin_hook( 'before_navigation_search',
473             $tokens );
474              
475             # Find product listing for this nav for active products only.
476             # In order_by me refers to navigation_products.
477              
478             my $products =
479             $tokens->{navigation}
480 6         4181 ->navigation_products->search_related('product')
481             ->active->listing->order_by('!me.priority,!product.priority');
482              
483 6 50       333348 if ( defined $plugin->navigation_records ) {
484              
485             # records per page is set in configuration so page the
486             # result set
487              
488             $products = $products->rows( $plugin->navigation_records )
489 6         3516 ->page( $tokens->{page} );
490             }
491              
492             # get a pager
493              
494 6         3258 $tokens->{pager} = $products->pager;
495              
496             # can template autodetect objects?
497              
498 6 50       7408 if ( !$plugin->object_autodetect ) {
499 6         173 $products = [ $products->all ];
500             }
501              
502 6         99595 $tokens->{products} = $products;
503              
504 6         586 $plugin->execute_plugin_hook( 'before_navigation_display',
505             $tokens );
506              
507 6         43335 return $app->template( $tokens->{template}, $tokens );
508             }
509              
510             # check for uri redirect record
511 5         208 my ( $redirect, $status_code ) = $plugin->shop_redirect($path);
512              
513 5 100       21030 if ($redirect) {
514              
515             # redirect to specific URL
516 2         47 $app->log( "debug",
517             "UriRedirect record found redirecting uri"
518             . " $redirect to $path with status code $status_code" );
519              
520 2         1121 return $app->redirect( $app->request->uri_for($redirect),
521             $status_code );
522             }
523              
524             # display not_found page
525 3         126 $app->response->status('not_found');
526 3         315 $app->forward(404);
527             },
528 2         524 );
529             }
530              
531             sub _config_warnings {
532 2     2   3 my $plugin = shift;
533              
534 2 100       10 if ( $plugin->navigation_records == 0 ) {
535 1         42 warn __PACKAGE__, ": Maximum number of navigation records is zero.\n";
536             }
537             }
538              
539             1;