File Coverage

blib/lib/CGI/Application/Plugin/PageLookup/Menu.pm
Criterion Covered Total %
statement 92 94 97.8
branch 14 22 63.6
condition 1 3 33.3
subroutine 9 9 100.0
pod 3 3 100.0
total 119 131 90.8


line stmt bran cond sub pod time code
1             package CGI::Application::Plugin::PageLookup::Menu;
2              
3 6     6   1199 use warnings;
  6         11  
  6         207  
4 6     6   34 use strict;
  6         14  
  6         210  
5 6     6   35 use Carp;
  6         8  
  6         7891  
6              
7             =head1 NAME
8              
9             CGI::Application::Plugin::PageLookup::Menu - Support for consistent menus across a multilingual website
10              
11             =head1 VERSION
12              
13             Version 1.8
14              
15             =cut
16              
17             our $VERSION = '1.8';
18              
19             =head1 DESCRIPTION
20              
21             The L module can be used to create a database driven menu
22             and similarly data driven site map page. However the Loop module can only translate into other languages
23             if the URLs are kept the same apart from a language identifier. This means that the website
24             would have search engine friendly in only one language. The L module
25             could be used to create a static menu and site map that is automatically translated into various languages
26             with search engine friendly URLs. However they cannot be combined as you cannot pass through first the Loop and then the Href.
27             What this module offers is a specialised variant of the Loop smart object that does combine these features.
28             This module depends on L.
29              
30             =head1 SYNOPSIS
31              
32             In the template you might define a menu as follows (with some CSS and javascript to make it look nice):
33              
34            
35            
36            
  • 37             ">
    38            
    39            
    40            
    41            
  • 42             ">
    43            
    44            
    45            
    46            
  • 47             ">
    48            
    49            
    50            
    51            
    52            
    53            
    54            
    55            
    56            
    57            
    58            
    59              
    60             and the intention is that this should be the same on all English pages, the same on all Vietnamese pages etc etc.
    61             You must register the "menu" parameter as a CGI::Application::Plugin::PageLookup::Menu object as follows:
    62              
    63             use CGI::Application;
    64             use CGI::Application::Plugin::PageLookup qw(:all);
    65             use CGI::Application::Plugin::PageLookup::Menu;
    66             use HTML::Template::Pluggable;
    67             use HTML::Template::Plugin::Dot;
    68              
    69             sub cgiapp_init {
    70             my $self = shift;
    71              
    72             # pagelookup depends CGI::Application::DBH;
    73             $self->dbh_config(......); # whatever arguments are appropriate
    74              
    75             $self->html_tmpl_class('HTML::Template::Pluggable');
    76              
    77             $self->pagelookup_config(
    78              
    79             # load smart dot-notation objects
    80             objects =>
    81             {
    82             # Register the 'values' parameter
    83             menu => 'CGI::Application::Plugin::PageLookup::Menu',
    84             },
    85              
    86             );
    87             }
    88              
    89             =head1 NOTES
    90              
    91             =over
    92              
    93             =item
    94              
    95             This module requires no extra table but it does depend on the 'lineage' and 'rank' columns in the
    96             cgiapp_strcuture table. These columns work the same way as they do in the cgiapp_loops table.
    97             That is the items are ordered according to the rank column and the lineage column is a comma separated
    98             list indicating the ranks of the parent menu items.
    99              
    100             =item
    101              
    102             The module can be used to get data either for menus or human readable sitemaps.
    103              
    104             =item
    105              
    106             One value that will always be returned is the 'pageId' column which can be translated into a URL as dictated
    107             by the website policy. However due to capitalisation issues, you must either call it 'pageid' in the template
    108             or specify 'case_sensitive => 1' somewhere in the template infrastructure.
    109              
    110             =item
    111              
    112             Use of this module for creating menus and sitemaps rather than the Loop module also means you may
    113             not need to set 'globalvars => 1' in the template infrastructure.
    114              
    115             =item
    116              
    117             You can specify additional columns from the cgiapp_pages table to be included the parameters. These could include
    118             a title, may be some javascript etc. These columns are not specified in the core database spec.
    119              
    120             =item
    121              
    122             In the synopsis all parameters below the headline structure call were shown as being "this dot something". In accordance
    123             with L this can be changed by using ":" notation. This has not actually been tested yet.
    124             Nor have we tried testing varying the arguments at different levels of the menu structure.
    125              
    126             =back
    127              
    128             =head1 FUNCTIONS
    129              
    130             =head2 new
    131              
    132             A constructor following the requirements set out in L.
    133              
    134             =cut
    135              
    136             sub new {
    137 14     14 1 24 my $class = shift;
    138 14         31 my $self = {};
    139 14         46 $self->{cgiapp} = shift;
    140 14         31 $self->{page_id} = shift;
    141 14         33 $self->{template} = shift;
    142 14         36 $self->{name} = shift;
    143 14         31 my %args = @_;
    144 14         35 $self->{config} = \%args;
    145              
    146 14         53 bless $self, $class;
    147 14         57 return $self;
    148             }
    149              
    150             =head2 structure
    151              
    152             This function is specified in the template where additional columns are specified.
    153             If no arguments are specified only the 'pageId' column is returned for each menu item.
    154             Additional arguments should be specified either as a single comma separated string (deprecated)
    155             or as multiple arguments.
    156              
    157             =cut
    158              
    159             sub structure {
    160 10     10 1 10128 my $self = shift;
    161 10         30 my @params = @_;
    162 10         32 my $template = "$self->{name}.structure('";
    163 10 100       36 if (scalar(@params) == 1) {
    164             # legacy case
    165 4         12 $template .= "$params[0]')";
    166 4         16 @params = split /,/, $params[0];
    167             }
    168             else {
    169 6         15 $template .= join "','", @params;
    170 6         13 $template .= "')";
    171             }
    172 10         44 return $self->__structure(\@params, "", [$template]);
    173             }
    174              
    175             sub __structure {
    176 128     128   173 my $self = shift;
    177 128 50       140 my @params = @{shift || []};
      128         499  
    178              
    179             # $dlineage are the "breadcrumbs" required to navigate our way through the database
    180             # and corresponds to the 'lineage' column on the cgiapp_structure table.
    181 128         203 my $dlineage = shift;
    182 128 50       264 croak "database lineage missing" unless defined $dlineage;
    183              
    184             # $tlineage are the "breadcrumbs" required to navigate our way through the HTML::Template structure.
    185             # It corresponds to the ARRAY ref used in $template->query(loop=> [....]).
    186 128         154 my $tlineage = shift;
    187 128 50       253 croak "template lineage missing" unless defined $tlineage;
    188              
    189 128         193 my $prefix = $self->{cgiapp}->pagelookup_prefix(%{$self->{config}});
      128         585  
    190 128         237 my $page_id = $self->{page_id};
    191 128         487 my $dbh = $self->{cgiapp}->dbh;
    192              
    193             # This is what we actually want to return
    194 128         6159 my @loop;
    195              
    196 128 100       350 $self->{work_to_be_done} = [] unless exists $self->{work_to_be_done};
    197              
    198             # generate SQL: get menu structure but optionally pull extra columns from cgiapp_pages
    199 128         137 my @params_sql;
    200 128         229 foreach my $p (@params) {
    201 236         679 push @params_sql, ", p2.$p";
    202             }
    203 128         293 my $param_sql = join "", @params_sql;
    204 128         469 my $sql = "SELECT s.rank, p2.pageId $param_sql FROM ${prefix}structure s, ${prefix}pages p2, ${prefix}pages p1 WHERE p1.lang = p2.lang AND s.internalId = p2.internalId AND p1.pageId = '$page_id' AND s.lineage = '$dlineage' AND s.priority IS NOT NULL ORDER BY s.rank ASC";
    205              
    206             # First one pass over the loop
    207 128   33     533 my $sth = $dbh->prepare($sql) || croak $dbh->errstr;
    208 128 50       37733 $sth->execute || croak $dbh->errstr;
    209 128         2885 while(my $hash_ref = $sth->fetchrow_hashref) {
    210              
    211 134         308 my $current_rank = delete $hash_ref->{rank};
    212              
    213             # Now we need to add in any loop variables
    214 134         368 $self->__populate_lower_loops($dlineage, $tlineage, $hash_ref, $current_rank, \@params);
    215              
    216             # We are finally ready to get this structure out of the door
    217 134         3114 push @loop, $hash_ref;
    218              
    219             }
    220 128 50       721 croak $sth->errstr if $sth->err;
    221 128         342 $sth->finish;
    222              
    223             # Now go back over the remaining work
    224 128         147 while(@{$self->{work_to_be_done}}) {
      242         671  
    225 114         132 my $work = shift @{$self->{work_to_be_done}};
      114         209  
    226 114         226 &$work();
    227             }
    228              
    229 128         1959 return \@loop;
    230             }
    231              
    232             =head2 __populate_lower_loops
    233              
    234             A private function that does what is says.
    235              
    236             =cut
    237              
    238             sub __populate_lower_loops {
    239 134     134   184 my $self = shift;
    240 134         163 my $dlineage = shift;
    241 134         137 my $tlineage = shift;
    242 134         152 my $current_row = shift;
    243 134         135 my $current_rank = shift;
    244 134         141 my $param = shift;
    245 134         186 my $comma = ',';
    246 134         412 my $new_dlineage = join $comma , (split /,/, $dlineage), $current_rank;
    247 134         311 my @new_tlineage = @$tlineage;
    248 134         501 my @new_vars = $self->{template}->query(loop=>\@new_tlineage);
    249 134         6725 foreach my $var (@new_vars) {
    250              
    251             # exclude anything that is not a loop
    252 490 100       13476 next if $self->{template}->query(name=>[@new_tlineage, $var]) eq 'VAR';
    253              
    254             # extract new loop name (following mechanics in HTML::Template::Plugin::Dot)
    255 114         4794 my ($one, $the_rest) = split /\./, $var, 2;
    256 114         169 my $loopmap_name = 'this';
    257 114 50       308 $loopmap_name = $1 if $the_rest =~ s/\s*:\s*([_a-z]\w*)\s*$//;
    258 114 50       421 croak "can only handle structure: $the_rest" unless $the_rest =~ /^structure/;
    259              
    260             # Okay we have set up the structure but let's finish the current SQL
    261             # before populating this one
    262 114         295 my $new_loop = [];
    263 114         201 $current_row->{structure} = $new_loop;
    264 114         212 my $new_tlineage = [@new_tlineage, $var];
    265 114         836 push @{$self->{work_to_be_done}}, sub {
    266 114     114   142 push @$new_loop, @{$self->__structure($param, $new_dlineage, $new_tlineage)};
      114         425  
    267 114         129 };
    268             }
    269 134         4324 return;
    270             }
    271              
    272             =head2 slice
    273              
    274             This function is a variant of the C<< structure >> function, which allows one to specify a part of the menu.
    275             The first argument is the database lineage which is a string consisting of comma separated numbers. The other arguments
    276             are as described under C<< structure >>. The slice function can only be used in the topmost TMPL_LOOP of a template.
    277              
    278             =cut
    279              
    280             sub slice {
    281 4     4 1 4622 my $self = shift;
    282 4         197 my $dlineage = shift;
    283 4         17 my $template = "$self->{name}.slice('$dlineage','";
    284 4         11 my @params = @_;
    285 4 50       14 if (scalar(@params) == 1) {
    286             # legacy case
    287 0         0 $template .= "$params[0]')";
    288 0         0 @params = split /,/, $params[0];
    289             }
    290             else {
    291 4         11 $template .= join "','", @params;
    292 4         9 $template .= "')";
    293             }
    294 4         31 return $self->__structure(\@params, $dlineage, [$template]);
    295             }
    296              
    297             =head1 AUTHOR
    298              
    299             Nicholas Bamber, C<< >>
    300              
    301             =head1 BUGS
    302              
    303             Please report any bugs or feature requests to C, or through
    304             the web interface at L. I will be notified, and then you'll
    305             automatically be notified of progress on your bug as I make changes.
    306              
    307             =head1 SUPPORT
    308              
    309             You can find documentation for this module with the perldoc command.
    310              
    311             perldoc CGI::Application::Plugin::PageLookup::Menu
    312              
    313              
    314             You can also look for information at:
    315              
    316             =over 4
    317              
    318             =item * RT: CPAN's request tracker
    319              
    320             L
    321              
    322             =item * AnnoCPAN: Annotated CPAN documentation
    323              
    324             L
    325              
    326             =item * CPAN Ratings
    327              
    328             L
    329              
    330             =item * Search CPAN
    331              
    332             L
    333              
    334             =back
    335              
    336              
    337             =head1 ACKNOWLEDGEMENTS
    338              
    339              
    340             =head1 COPYRIGHT & LICENSE
    341              
    342             Copyright 2009 Nicholas Bamber.
    343              
    344             This program is free software; you can redistribute it and/or modify it
    345             under the terms of either: the GNU General Public License as published
    346             by the Free Software Foundation; or the Artistic License.
    347              
    348             See http://dev.perl.org/licenses/ for more information.
    349              
    350              
    351             =cut
    352              
    353             1; # End of CGI::Application::Plugin::PageLookup::Menu