File Coverage

blib/lib/Dancer2/Plugin/Interchange6/Routes.pm
Criterion Covered Total %
statement 80 80 100.0
branch 24 26 92.3
condition 5 5 100.0
subroutine 8 8 100.0
pod 1 1 100.0
total 118 120 98.3


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::Interchange6::Routes;
2              
3 2     2   8822 use Dancer2::Plugin;
  2         2  
  2         14  
4 2     2   4767 use Dancer2::Plugin::Interchange6::Routes::Account;
  2         3  
  2         49  
5 2     2   683 use Dancer2::Plugin::Interchange6::Routes::Cart;
  2         4  
  2         67  
6 2     2   734 use Dancer2::Plugin::Interchange6::Routes::Checkout;
  2         4  
  2         1974  
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 546487 my $plugin = shift;
327 2         17 my $app = $plugin->app;
328              
329 2         2 my $sub;
330              
331             $app->add_hook(
332             Dancer2::Core::Hook->new(
333             name => 'before',
334             code => sub {
335              
336             # D2PAE::Provider::DBIC returns logged_in_user as hashref
337             # instead of a proper user result so we have to mess about.
338             # At some point in the future D2PAE will be fixed to allow
339             # user objects to be returned.
340 105   100 105   1142107 my $user = $plugin->logged_in_user || undef;
341 105 100       424159 if ( $user ) {
342             $user = $plugin->shop_user->find(
343             {
344             username => $user->{username}
345             }
346 29         634 );
347             }
348 105         141633 $plugin->shop_schema->set_current_user($user);
349             },
350             )
351 2         44 );
352              
353             # display warnings
354 2         1007 $plugin->_config_warnings;
355              
356             # account routes
357 2         442 my $account_routes =
358             Dancer2::Plugin::Interchange6::Routes::Account::account_routes($plugin);
359              
360             $app->add_route(
361             method => 'get',
362             regexp => '/' . $plugin->login_uri,
363             code => $account_routes->{login}->{get},
364 2         10 );
365              
366             $app->add_route(
367             method => 'post',
368             regexp => '/' . $plugin->login_uri,
369             code => $account_routes->{login}->{post},
370 2         356 );
371              
372 2         316 foreach my $method ( 'get', 'post' ) {
373             $app->add_route(
374             method => $method,
375             regexp => '/' . $plugin->logout_uri,
376             code => $account_routes->{logout}->{any},
377 4         356 );
378             }
379              
380 2 100       317 if ( $plugin->cart_active ) {
381              
382             # routes for cart
383 1         6 my $cart_sub =
384             Dancer2::Plugin::Interchange6::Routes::Cart::cart_route($plugin);
385              
386 1         2 foreach my $method ( 'get', 'post' ) {
387 2         183 $app->add_route(
388             method => $method,
389             regexp => '/' . $plugin->cart_uri,
390             code => $cart_sub,
391             );
392             }
393             }
394              
395 2 100       515 if ( $plugin->checkout_active ) {
396              
397             # routes for checkout
398 1         343 my $checkout_sub =
399             Dancer2::Plugin::Interchange6::Routes::Checkout::checkout_route(
400             $plugin);
401              
402 1         2 foreach my $method ( 'get', 'post' ) {
403 2         180 $app->add_route(
404             method => $method,
405             regexp => '/' . $plugin->checkout_uri,
406             code => $checkout_sub,
407             );
408             }
409             }
410              
411             # fallback route for flypage and navigation
412             $app->add_route(
413             method => 'get',
414             regexp => qr{/(?<path>.+)},
415             code => sub {
416 22     22   3714 my $app = shift;
417 22         108 my $path = $app->request->captures->{'path'};
418              
419 22         521 my $schema = $plugin->shop_schema;
420              
421             # check for a matching product by uri
422 22         1337 my $product =
423             $plugin->shop_product->single( { uri => $path, active => 1 } );
424              
425 22 100       54851 if ( !$product ) {
426              
427             # check for a matching product by sku
428 15         623 $product = $plugin->shop_product->single(
429             { sku => $path, active => 1 } );
430              
431 15 100 100     35828 if ( $product && $product->uri ) {
432              
433             # permanent redirect to specific URL
434 3         215 $app->log( "debug",
435             "Redirecting permanently to product uri ",
436             $product->uri, " for $path." );
437              
438 3         1507 return $app->redirect(
439             $app->request->uri_for( $product->uri ), 301 );
440             }
441             }
442              
443 19 100       505 if ($product) {
444              
445             # flypage
446 8         23 my $tokens = { product => $product };
447              
448 8         187 $plugin->execute_plugin_hook( 'before_product_display',
449             $tokens );
450              
451 8         6344 my $output =
452             $app->template( $plugin->product_template, $tokens );
453              
454             # temporary way to erase cart errors from missing variants
455 8         7948 $app->session->write( shop_cart_error => undef );
456              
457 8         475 return $output;
458             }
459              
460             # check for page number
461 11         17 my $page;
462              
463 11 100       58 if ( $path =~ s%/([1-9][0-9]*)$%% ) {
464 1         3 $page = $1;
465             }
466             else {
467 10         17 $page = 1;
468             }
469              
470             # first check for navigation item
471 11         235 my $nav =
472             $plugin->shop_navigation->single( { uri => $path, active => 1 } );
473              
474 11 100       21656 if ( defined $nav ) {
475              
476             # navigation item found
477              
478             # retrieve navigation attribute for template
479 6         258 my $template = $plugin->navigation_template;
480              
481 6 100       75 if ( my $attr_value = $nav->find_attribute_value('template') ) {
482 5         68361 $app->log( "debug",
483             "Change template name from $template"
484             . " to $attr_value due to navigation attribute." );
485 5         2250 $template = $attr_value;
486             }
487              
488 6         8146 my $tokens = {
489             navigation => $nav,
490             page => $page,
491             template => $template
492             };
493              
494 6         113 $plugin->execute_plugin_hook( 'before_navigation_search',
495             $tokens );
496              
497             # Find product listing for this nav for active products only.
498             # In order_by me refers to navigation_products.
499              
500             my $products =
501             $tokens->{navigation}
502 6         3889 ->navigation_products->search_related('product')
503             ->active->listing->order_by('!me.priority,!product.priority');
504              
505 6 50       312281 if ( defined $plugin->navigation_records ) {
506              
507             # records per page is set in configuration so page the
508             # result set
509              
510             $products = $products->rows( $plugin->navigation_records )
511 6         3151 ->page( $tokens->{page} );
512             }
513              
514             # get a pager
515              
516 6         2396 $tokens->{pager} = $products->pager;
517              
518             # can template autodetect objects?
519              
520 6 50       6371 if ( !$plugin->object_autodetect ) {
521 6         99 $products = [ $products->all ];
522             }
523              
524 6         92957 $tokens->{products} = $products;
525              
526 6         413 $plugin->execute_plugin_hook( 'before_navigation_display',
527             $tokens );
528              
529 6         38761 return $app->template( $tokens->{template}, $tokens );
530             }
531              
532             # check for uri redirect record
533 5         192 my ( $redirect, $status_code ) = $plugin->shop_redirect($path);
534              
535 5 100       18843 if ($redirect) {
536              
537             # redirect to specific URL
538 2         38 $app->log( "debug",
539             "UriRedirect record found redirecting uri"
540             . " $redirect to $path with status code $status_code" );
541              
542 2         906 return $app->redirect( $app->request->uri_for($redirect),
543             $status_code );
544             }
545              
546             # display not_found page
547 3         112 $app->response->status('not_found');
548 3         257 $app->forward(404);
549             },
550 2         562 );
551             }
552              
553             sub _config_warnings {
554 2     2   5 my $plugin = shift;
555              
556 2 100       13 if ( $plugin->navigation_records == 0 ) {
557 1         37 warn __PACKAGE__, ": Maximum number of navigation records is zero.\n";
558             }
559             }
560              
561             1;