The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Labyrinth::Plugin::Menus;

use warnings;
use strict;

my $VERSION = '5.18';

=head1 NAME

Labyrinth::Plugin::Menus - Plugin Menus handler for Labyrinth

=head1 DESCRIPTION

Contains all the menu handling functionality for the Labyrinth
framework.

=cut

# menu array
# 0 = ?
# 1 = selected=1, unselected=0
# 2 = title
# 3 = href
# 4 = access
# 5 = text
# 6.. = images

# -------------------------------------
# Library Modules

use base qw(Labyrinth::Plugin::Base);

use Labyrinth::Audit;
use Labyrinth::Globals;
use Labyrinth::DBUtils;
use Labyrinth::Media;
use Labyrinth::MLUtils;
use Labyrinth::Session;
use Labyrinth::Support;
use Labyrinth::Variables;

# -------------------------------------
# Constants

use constant    MaxMenuWidth     => 400;
use constant    MaxMenuHeight    => 400;

# -------------------------------------
# Variables

# type: 0 = optional, 1 = mandatory
# html: 0 = none, 1 = text, 2 = textarea

my %fields = (
    menuid      => { type => 0, html => 0 },
    name        => { type => 1, html => 1 },
    realmid     => { type => 1, html => 0 },
    typeid      => { type => 1, html => 0 },
    title       => { type => 0, html => 1 },
    parentid    => { type => 0, html => 0 },
);

my (@mandatory,@allfields);
for(keys %fields) {
    push @mandatory, $_     if($fields{$_}->{type});
    push @allfields, $_;
}

my @savefields  = qw(name title typeid realmid parentid);
my $INDEXKEY    = 'menuid';
my $ALLSQL      = 'AllMenus';
my $SAVESQL     = 'SaveMenu';
my $ADDSQL      = 'AddMenu';
my $GETSQL      = 'GetMenuByID';
my $DELETESQL   = 'DeleteMenu';
my $LEVEL       = ADMIN;

my %adddata = (
    menuid      => 0,
    realmid     => 0,
    type        => 0,
    title       => '',
    name        => '',
);


# -------------------------------------
# The Subs

=head1 PUBLIC INTERFACE METHODS

=head2 Default Methods

=over 4

=item LoadMenus

Loads all the menus used within the system, and stores them within the 'menu'
template variable hash, using the menuid as the key to each.

=back

=cut

sub LoadMenus {
    # get menu list for current realm
    my @rows = $dbi->GetQuery('hash','GetMenus',RealmID($tvars{realm}));

    $tvars{menus} = undef;  # in case we're reloading
    my $request = $ENV{REQUEST_URI};
    my $script = $settings{script};
    my (%match,%tree);
#    LogDebug("script=[$script]");

    # for each menu get option list
    for my $row (@rows) {
        $tvars{menus}->{$row->{menuid}}->{name}     = $row->{name};
        $tvars{menus}->{$row->{menuid}}->{title}    = $row->{title};
        $tvars{menus}->{$row->{menuid}}->{typeid}   = $row->{typeid};
        $tvars{menus}->{$row->{menuid}}->{parentid} = $row->{parentid};
        my @opts = $dbi->GetQuery('hash','GetOptions',$row->{menuid});
        for my $opt (@opts) {
            my @images;
            if($row->{typeid} > 1) {
                my @rs = $dbi->GetQuery('hash','GetOptImages',$opt->{optionid});
                @images = map {$_->{'link'}} @rs;
            }
#LogDebug("request=$request, opt=$opt->{href}");
#LogDebug("section=$tvars{section}, opt=$opt->{section}");
            # try and find the selected option
            my $inx = $tvars{menus}->{$row->{menuid}}->{data} ? scalar(@{$tvars{menus}->{$row->{menuid}}->{data}}) : 0;
            $match{request} = [$row->{menuid},$inx,$opt->{optionid}]    if($request && $request eq $opt->{href});
            $match{section} = [$row->{menuid},$inx,$opt->{optionid}]    if($opt->{section} && $tvars{section} eq $opt->{section});
            $match{options} = [$row->{menuid},$inx,$opt->{optionid}]    if($request && $request =~ /$opt->{href}/);
            $match{hideout} = [$row->{menuid},$inx,$opt->{optionid}]    if($opt->{section} && ($opt->{section} eq 'home' || $opt->{href} eq '/'));
            $match{default} = [$row->{menuid},$inx,$opt->{optionid}]    if(!$match{default});
            $opt->{href} =~ s!^\?!$script\?!;                       # all query only links are local
#            $opt->{href} =~ s!^/$script!$tvars{cgipath}/$script!;  # all script links are local
            push @{$tvars{menus}->{$row->{menuid}}->{data}},
                [   0,0,
                    $opt->{text},
                    $opt->{href},
                    ($opt->{accessid}||0),
                    $opt->{name},
                    @images
                ];
            $tree{$opt->{optionid}} = { menuid => $row->{menuid}, index => scalar(@{$tvars{menus}->{$row->{menuid}}->{data}}) - 1, parent => $row->{parentid} };
        }
    }

#LogDebug("match{$_}=$match{$_}[0]/$match{$_}[2]")    for(qw(request section options hideout default));

                             my $match = 'default';
       if($match{request}[0]) { $match = 'request'; }
    elsif($match{section}[0]) { $match = 'section'; }
    elsif($match{options}[0]) { $match = 'options'; }
    elsif($match{hideout}[0]) { $match = 'hideout'; }
    UpdateSession(optionid => $match{$match}->[2]);

    # previous/next trail
    $tvars{trail2}{prev} = _trail2($match{$match},-1);
    $tvars{trail2}{this} = _trail2($match{$match}, 0);
    $tvars{trail2}{next} = _trail2($match{$match}, 1);
    $tvars{trail1} = undef;

    # breadcrumbs trail
    my $menu = 0;
    my $opt = $match{$match}->[2];
    $tvars{menus}->{$tree{$opt}{menuid}}->{data}->[$tree{$opt}{index}][1] = 1;  # option has been selected

#    use Data::Dumper;
#    LogDebug("opt=$opt");
#    LogDebug("tree=".Dumper(\%tree));

    for(keys %tree) {
        next    unless(defined $opt && defined $tree{$_} && defined $tree{$_}->{parent});
        $menu = $tree{$_}->{menuid} if($opt == $tree{$_}->{parent});
    }

    while($opt) {
        $tvars{menus}->{$tree{$opt}{menuid}}->{data}->[$tree{$opt}{index}][0] = $menu;  # submenu has been selected
        push @{$tvars{trail1}},
        {
            text => $tvars{menus}->{$tree{$opt}{menuid}}->{data}->[$tree{$opt}{index}][2],
            href => $tvars{menus}->{$tree{$opt}{menuid}}->{data}->[$tree{$opt}{index}][3]
        };
        $menu = $tree{$opt}{menuid};
        $opt  = $tree{$opt}{parent};
    }
}

sub _trail2 {
    my ($hash,$diff) = @_;
    return  unless(defined $hash && @$hash);

    my %hash;
    my $opt = $hash->[1] + $diff;
    if($opt >= 0 && $opt < scalar(@{$tvars{menus}->{$hash->[0]}->{data}})) {
        $hash{text} = $tvars{menus}->{$hash->[0]}->{data}->[$opt][2];
        $hash{href} = $tvars{menus}->{$hash->[0]}->{data}->[$opt][3];
    }
    return \%hash;
}

sub _LoadMenus {
    # get menu list for current realm
    my @rows = $dbi->GetQuery('hash','GetMenus',RealmID($tvars{realm}));
    $tvars{menus} = undef;  # in case we're reloading
    my $request = $ENV{REQUEST_URI};
    my $script = $settings{script};
    my (%tree,$last,$href);
#    LogDebug("script=[$script]");

    # for each menu get option list
    for my $row (@rows) {
        $tvars{menus}->{$row->{menuid}}->{name}     = $row->{name};
        $tvars{menus}->{$row->{menuid}}->{title}    = $row->{title};
        $tvars{menus}->{$row->{menuid}}->{typeid}   = $row->{typeid};
        $tvars{menus}->{$row->{menuid}}->{parentid} = $row->{parentid};
        $last = '';
        $href = '';
        my @opts = $dbi->GetQuery('hash','GetOptions',$row->{menuid});
        for my $opt (@opts) {
            my @images;
            if($row->{typeid} > 1) {
                my @rs = $dbi->GetQuery('hash','GetOptImages',$opt->{optionid});
                @images = map {$_->{'link'}} @rs;
            }
            UpdateSession(optionid => $opt->{optionid})   if($request && $request =~ /$opt->{href}/);
            $opt->{href} =~ s!^\?!$script\?!;                       # all query only links are local
#            $opt->{href} =~ s!^/$script!$tvars{cgipath}/$script!;   # all script links are local

            # establish the current level trail
            if($tvars{trail2} && !$tvars{trail2}{'next'}) {
                $tvars{trail2}{'next'} = {text => $opt->{text}, href => $opt->{href}};
            }
            if($opt->{optionid} == $tvars{user}{option}) {
                $tvars{trail2} = {  'prev' => {text => $last, href => $href},
                                    'this' => {text => $opt->{text}}};
            } else {
                $last = $opt->{text};
                $href = $opt->{href};
            }
            push @{$tvars{menus}->{$row->{menuid}}->{data}}, [0,0,$opt->{text},$opt->{href},($opt->{accessid}||0),@images];

            $tree{$opt->{optionid}} = {
                minx => $row->{menuid},
                oinx => (scalar(@{$tvars{menus}->{$row->{menuid}}->{data}}) - 1),
                opar => $row->{parentid}
            };
        }
    }
#    use Data::Dumper;
#    LogDebug("LoadMenus: tree=".Dumper(\%tree));
    # now establish the main trail
    if($tvars{user}{option}) {
        my $option = $tvars{user}{option};
        for my $opt (keys %tree) {
            if($tree{$opt}->{opar} == $option) {
                $tvars{menus}{$tree{$option}->{minx}}{data}[$tree{$option}->{oinx}][0] = $tree{$opt}->{minx};
                LogDebug("LoadMenus: option=$option, opt=$opt, minx=$tree{$option}->{minx}, oinx=$tree{$option}->{oinx}, minx=$tree{$opt}->{minx}");
                                                                                # menu has a sub menu
                last;
            }
        }

        while($option) {
            my $minx = $tree{$option}->{minx};
            my $oinx = $tree{$option}->{oinx};
            if($minx && $oinx) {
                $tvars{menus}{$minx}{data}[$oinx][1] = 1;                        # menu has been selected
                unshift @{$tvars{trail1}},                                      # breadcrumbs trail
                            {text => $tvars{menus}{$minx}{data}[$oinx][2],
                             href => $tvars{menus}{$minx}{data}[$oinx][3]};
            }
            $option = $tree{$option}->{opar};
            next    unless($option);
            my $pinx = $tree{$option}->{minx};
               $oinx = $tree{$option}->{oinx};
            $tvars{menus}{$pinx}{data}[$oinx][0] = $minx;                       # menu has this sub menu
        }
    }
}

=head1 ADMIN INTERFACE METHODS

=head2 Menu Methods

=over

=item Admin

List current menus.

=item Add

Add a new menu.

=item Edit

Edit a given menu.

=item Save

Save the given menu.

=item Delete

Delete the given menu.

=item DeleteOptions

Delete the specified option(s) of a given menu.

=item TypeSelect

Provide a drop down list of menu option types.

=item TypeName

Provide the name of the given option type.

=item ParentSelect

Provides a drop down in order to enable multiple levels of menus and options.

=item CheckImages

Stores the image for a menu option state.

=back

=cut

sub Admin {
    return  unless(AccessUser($LEVEL));

    if($cgiparams{doaction}) {
           if($cgiparams{doaction} eq 'Delete' ) { Delete();  }
    }

    my @rows = $dbi->GetQuery('hash',$ALLSQL);
    for (@rows) {
        $_->{type} = TypeName($_->{typeid});
        $_->{realm} = RealmName($_->{realmid});
    }
    $tvars{data} = \@rows   if(@rows);
}

sub Add {
    return  unless AccessUser($LEVEL);
    $tvars{data} = \%adddata;
    $tvars{data}->{ddtypes}  = TypeSelect();
    $tvars{data}->{ddrealms} = RealmSelect();
    $tvars{data}->{ddparent} = ParentSelect();
}

sub Edit {
    return  unless AccessUser($LEVEL);
    return  unless AuthorCheck($GETSQL,$INDEXKEY,$LEVEL);

    my $script = $settings{script};

    my @opts = $dbi->GetQuery('hash','GetOptions',$tvars{data}->{menuid});
    for my $opt (@opts) {
        my @images;
        if($tvars{data}->{typeid} > 1) {
            my @rs = $dbi->GetQuery('hash','GetOptImages',$opt->{optionid});
            for(@rs) {
                $opt->{'image' . $_->{typeid}} = $_->{'link'};
                $opt->{'imageid' . $_->{typeid}} = $_->{'imageid'};
            }
            @images = map {$_->{'link'}} @rs;
        }
        $opt->{ddaccess} = AccessSelect($opt->{accessid},'ACCESS'.$opt->{optionid});

        my $href = $opt->{href};
        $href =~ s!^\?!$script\?!;                       # all query only links are local
        push @{$tvars{preview}->{data}}, [0,0,$opt->{text},$href,($opt->{accessid}||0),$opt->{name},@images];
    }
    $tvars{data}->{options}  = \@opts   if(@opts);
    $tvars{data}->{ddtypes}  = TypeSelect($tvars{data}->{typeid});
    $tvars{data}->{ddrealms} = RealmSelect($tvars{data}->{realmid});
    $tvars{data}->{ddparent} = ParentSelect($tvars{data}->{parentid},$tvars{data}->{menuid});

    $tvars{preview}->{$_} = $tvars{data}->{$_}    for(qw(title typeid parentid));
}

sub Save {
    return  unless AccessUser($LEVEL);
    return  unless AuthorCheck($GETSQL,$INDEXKEY,$LEVEL);

    for(keys %fields) {
           if($fields{$_}->{html} == 1) { $cgiparams{$_} = CleanHTML($cgiparams{$_}) }
        elsif($fields{$_}->{html} == 2) { $cgiparams{$_} = CleanTags($cgiparams{$_}) }
        elsif($fields{$_}->{html} == 3) { $cgiparams{$_} = CleanLink($cgiparams{$_}) }
    }

    return  if FieldCheck(\@allfields,\@mandatory);

    my @fields = map {$tvars{data}->{$_}} @savefields;
    if($cgiparams{$INDEXKEY}) {
        $dbi->DoQuery($SAVESQL,@fields,$cgiparams{$INDEXKEY});
    } else {
        $cgiparams{$INDEXKEY} = $dbi->IDQuery($ADDSQL,@fields);
    }

    # delete option if requested
    if($cgiparams{'doaction'}) {
        DeleteOptions()    if($cgiparams{'doaction'} eq 'DeleteOption');
    }

    # save options
    my $order = 1;
    my @opts = $dbi->GetQuery('hash','GetOptions',$tvars{data}->{menuid});
    for my $opt (@opts) {
        my @fields = (
            $cgiparams{"ORDER"    . $opt->{optionid}},          # menu order
            $cgiparams{"NAME"     . $opt->{optionid}},          # CSS code name
            $cgiparams{"SECT"     . $opt->{optionid}},          # section name
            $cgiparams{"TEXT"     . $opt->{optionid}},          # menu text
            $cgiparams{"HREF"     . $opt->{optionid}},          # menu link
            ($cgiparams{"ACCESS"  . $opt->{optionid}} || 0),    # access level
        );
        $dbi->DoQuery('SaveOption',@fields,$opt->{optionid});
        $order = $opt->{orderno} + 1;

        my @rs = $dbi->GetQuery('hash','GetOptImages',$opt->{optionid});
        my %images = map {$_->{typeid} => $_->{imageid}} @rs;
        CheckImages($images{1},'IMAGEFILE',$opt->{optionid},1)  if($tvars{data}->{typeid} > 1);
        CheckImages($images{2},'ROLLOVER' ,$opt->{optionid},2)  if($tvars{data}->{typeid} > 2);
        CheckImages($images{3},'SELECTED' ,$opt->{optionid},3)  if($tvars{data}->{typeid} > 3);
    }

    # add option if requested
    if($cgiparams{'doaction'}) {
        $dbi->DoQuery('AddOption',$tvars{data}->{menuid},$order,0)  if($cgiparams{'doaction'} eq 'AddOption');
    }

    $tvars{thanks} = 1;
}

sub Delete {
    return  unless AccessUser($LEVEL);
    my @ids = CGIArray('LISTED');
    return  unless @ids;

    # remove menus
    my $ids = join(",",@ids);
    $dbi->DoQuery($DELETESQL,{ids=>$ids});
    my @opts = $dbi->GetQuery('hash','FindOptions',{ids=>$ids});

    # remove options
    $ids = join(",",map {$_->{optionid}} @opts);
    $dbi->DoQuery('DeleteOptions',{ids=>$ids});
    $dbi->DoQuery('DeleteOptImages',{ids=>$ids});
}

sub DeleteOptions {
    my @ids = CGIArray('LISTED');
    return  unless @ids;
    my $ids = join(",",@ids);
    $dbi->DoQuery('DeleteOptions',{ids=>$ids});
    $dbi->DoQuery('DeleteOptImages',{ids=>$ids});
}

my %types = (
    1 => 'text',
    2 => 'image',
    3 => 'rollover',
    4 => 'highlighted',
);
my @types = map {{'id'=>$_,'value'=> $types{$_}}} sort keys %types;

sub TypeSelect {
    my $opt = shift || 0;
    DropDownRows($opt,"typeid",'id','value',@types);
}

sub TypeName {
    my $id = shift || 1;
    return $types{$id};
}

sub ParentSelect {
    my $oinx = shift || 0;
    my $minx = shift || 0;
    my @opts = $dbi->GetQuery('hash','GetAllOptions',$minx);
    my @rows = map {{optionid => $_->{optionid}, text => "$_->{name} - $_->{text}"}} @opts;
    unshift @rows, {optionid => 0, text => 'Select Parent Option'};
    DropDownRows($oinx,"parentid",'optionid','text',@rows);
}

sub CheckImages {
    my ($oldid,$key,$optionid,$typeid) = @_;
    my $param = $key . $optionid;

    return unless($cgiparams{$param});

    my $maximagewidth  = $settings{maxmenuwidth}  || MaxMenuWidth;
    my $maximageheight = $settings{maxmenuheight} || MaxMenuHeight;

#    my $file = CGIFile($key . $optionid);
    my ($imageid) = SaveImageFile(
            param => $key . $optionid,
            stock => 'DRAFT'
        );

    my $sqlkey = ($oldid ? 'SaveOptImage' : 'AddOptImage');
    $dbi->DoQuery($sqlkey,$imageid,$optionid,$typeid);
}

1;

__END__

=head1 SEE ALSO

L<Labyrinth>

=head1 AUTHOR

Barbie, <barbie@missbarbell.co.uk> for
Miss Barbell Productions, L<http://www.missbarbell.co.uk/>

=head1 COPYRIGHT & LICENSE

  Copyright (C) 2002-2014 Barbie for Miss Barbell Productions
  All Rights Reserved.

  This module is free software; you can redistribute it and/or
  modify it under the Artistic License 2.0.

=cut