X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FMenu.pm;h=ac8f7280b4bf51e464c018c5223f9a8824c1cc3f;hb=d679bb5b866e0336fb3df96a28cb58de349c9cb0;hp=a3fa3e5647450ff09a524da3e7f1da0ca134569b;hpb=8c6efb2a9d807818596d7bee4fa9693ab833274c;p=kivitendo-erp.git diff --git a/SL/Menu.pm b/SL/Menu.pm index a3fa3e564..ac8f7280b 100644 --- a/SL/Menu.pm +++ b/SL/Menu.pm @@ -1,169 +1,256 @@ -#===================================================================== -# LX-Office ERP -# Copyright (C) 2004 -# Based on SQL-Ledger Version 2.1.9 -# Web http://www.lx-office.org -# -#===================================================================== -# SQL-Ledger Accounting -# Copyright (C) 2001 -# -# Author: Dieter Simader -# Email: dsimader@sql-ledger.org -# Web: http://www.sql-ledger.org -# -# Contributors: -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -#===================================================================== -# -# routines for menu items -# -#===================================================================== - -package Menu; +package SL::Menu; -sub new { - $main::lxdebug->enter_sub(); +use strict; - my ($type, $menufile, $level) = @_; +use SL::Auth; +use YAML (); +use File::Spec; +use SL::MoreCommon qw(uri_encode); - use SL::Inifile; - my $self = Inifile->new($menufile, $level); +our $yaml_xs; +BEGIN { + $yaml_xs = eval { require YAML::XS }; +} - $main::lxdebug->leave_sub(); +our %menu_cache; - bless $self, $type; -} +sub new { + my ($package, $domain) = @_; + + if (!$menu_cache{$domain}) { + my $path = File::Spec->catdir('menus', $domain); -sub menuitem { - $main::lxdebug->enter_sub(); + opendir my $dir, $path or die "can't open $path: $!"; + my @files = sort grep -f "$path/$_", grep /\.yaml$/, readdir $dir; + close $dir; - my ($self, $myconfig, $form, $item) = @_; + my $nodes = []; + my $nodes_by_id = {}; + for my $file (@files) { + my $data; + if ($yaml_xs) { + $data = YAML::XS::LoadFile(File::Spec->catfile($path, $file)); + } else { + $data = YAML::LoadFile(File::Spec->catfile($path, $file)); + } + _merge($nodes, $nodes_by_id, $data); + } - my $module = $form->{script}; - my $action = "section_menu"; - my $target = ""; - if ($self->{$item}{module}) { - $module = $self->{$item}{module}; + my $self = bless { + nodes => $nodes, + by_id => $nodes_by_id, + }, $package; + + $self->build_tree; + + $menu_cache{$domain} = $self; + } else { + $menu_cache{$domain}->clear_access; } - if ($self->{$item}{action}) { - $action = $self->{$item}{action}; + + $menu_cache{$domain}->set_access; + + return $menu_cache{$domain}; +} + +sub _merge { + my ($nodes, $by_id, $data) = @_; + + die 'not an array ref' unless $data && 'ARRAY' eq ref $data; # TODO check this sooner, to get better diag to user + + for my $node (@$data) { + my $id = $node->{id}; + + my $merge_to = $by_id->{$id}; + + if (!$merge_to) { + push @$nodes, $node; + $by_id->{$id} = $node; + next; + } + + # TODO make this a real recursive merge + # TODO add support for arrays + + # merge keys except params + for my $key (keys %$node) { + if (ref $node->{$key}) { + if ('HASH' eq ref $node->{$key}) { + $merge_to->{$key} = {} if !exists $merge_to->{$key} || 'HASH' ne ref $merge_to->{$key}; + for (keys %{ $node->{params} }) { + $merge_to->{$key}{$_} = $node->{params}{$_}; + } + } else { + die "unsupported structure @{[ ref $node->{$key} ]}"; + } + } else { + $merge_to->{$key} = $node->{$key}; + } + } } - if ($self->{$item}{target}) { - $target = $self->{$item}{target}; +} + +sub build_tree { + my ($self) = @_; + + # first, some sanity check. are all parents valid ids or empty? + for my $node ($self->nodes) { + next if !exists $node->{parent} || !$node->{parent} || $self->{by_id}->{$node->{id}}; + die "menu: node $node->{id} has non-existent parent $node->{parent}"; } - my $level = $form->escape($item); + my %by_parent; + # order them by parent + for my $node ($self->nodes) { + push @{ $by_parent{ $node->{parent} } //= [] }, $node; + } - my $str = - qq|{path}&action=$action&level=$level&login=$form->{login}&password=$form->{password}|; + my $tree = { }; + $self->{by_id}{''} = $tree; - my @vars = qw(module action target href); - if ($self->{$item}{href}) { - $str = qq|{$item}{href}|; - @vars = qw(module target href); + for (keys %by_parent) { + my $parent = $self->{by_id}{$_}; + $parent->{children} = [ sort { $a->{order} <=> $b->{order} } @{ $by_parent{$_} } ]; } - map { delete $self->{$item}{$_} } @vars; + _set_level_rec($tree->{children}, 0); - # add other params - foreach my $key (keys %{ $self->{$item} }) { - $str .= "&" . $form->escape($key, 1) . "="; - ($value, $conf) = split /=/, $self->{$item}{$key}, 2; - $value = $myconfig->{$value} . "/$conf" if ($conf); - $str .= $form->escape($value, 1); - } + $self->{tree} = $tree->{children}; +} + +sub _set_level_rec { + my ($ary_ref, $level) = @_; - if ($target) { - $str .= qq| target=$target|; + for (@$ary_ref) { + $_->{level} = $level; + _set_level_rec($_->{children}, $level + 1) if $_->{children}; } +} - $str .= ">"; +sub nodes { + @{ $_[0]{nodes} } +} - $main::lxdebug->leave_sub(); +sub tree_walk { + my ($self, $all) = @_; - return $str; + _tree_walk_rec($self->{tree}, $all); } -sub menuitemNew { - my ($self, $myconfig, $form, $item) = @_; +sub _tree_walk_rec { + my ($ary_ref, $all) = @_; + map { $_->{children} ? ($_, _tree_walk_rec($_->{children}, $all)) : ($_) } grep { $all || $_->{visible} } @$ary_ref; +} - my $module = $form->{script}; - my $action = "section_menu"; +sub parse_access_string { + my ($self, $node) = @_; - #if ($self->{$item}{module}) { - $module = $self->{$item}{module}; + my @stack; + my $cur_ary = []; - #} - if ($self->{$item}{action}) { - $action = $self->{$item}{action}; - } + push @stack, $cur_ary; - my $level = $form->escape($item); - my $str = - qq|$module?path=$form->{path}&action=$action&level=$level&login=$form->{login}&password=$form->{password}|; - my @vars = qw(module action target href); + my $access = $node->{access}; - if ($self->{$item}{href}) { - $str = qq|$self->{$item}{href}|; - @vars = qw(module target href); - } + while ($access =~ m/^([a-z_\/]+|\||\&|\(|\)|\s+)/) { + my $token = $1; + substr($access, 0, length($1)) = ""; - map { delete $self->{$item}{$_} } @vars; + next if ($token =~ /\s/); - # add other params - foreach my $key (keys %{ $self->{$item} }) { - $str .= "&" . $form->escape($key, 1) . "="; - ($value, $conf) = split /=/, $self->{$item}{$key}, 2; - $value = $myconfig->{$value} . "/$conf" if ($conf); - $str .= $form->escape($value, 1); + if ($token eq "(") { + my $new_cur_ary = []; + push @stack, $new_cur_ary; + push @{$cur_ary}, $new_cur_ary; + $cur_ary = $new_cur_ary; + + } elsif ($token eq ")") { + pop @stack; + if (!@stack) { + die "Error while parsing menu entry $node->{id}: missing '('"; + } + $cur_ary = $stack[-1]; + + } elsif (($token eq "|") || ($token eq "&")) { + push @{$cur_ary}, $token; + + } else { + if ($token =~ m{^ client / (.*) }x) { + push @{$cur_ary}, $self->parse_instance_conf_string($1); + } else { + push @{$cur_ary}, $::auth->check_right($::myconfig{login}, $token, 1); + } + } } - $str .= " "; + if ($access) { + die "Error while parsing menu entry $node->{id}: unrecognized token at the start of '$access'\n"; + } + if (1 < scalar @stack) { + die "Error while parsing menu entry $node->{id}: Missing ')'\n"; + } + + return SL::Auth::evaluate_rights_ary($stack[0]); } -sub access_control { - $main::lxdebug->enter_sub(2); +sub href_for_node { + my ($self, $node) = @_; - my ($self, $myconfig, $menulevel) = @_; + return undef if !$node->{href} && !$node->{module} && !$node->{params}; - my @menu = (); + my $href = $node->{href} || $node->{module} || 'controller.pl'; + my @tokens; - if ($menulevel eq "") { - @menu = grep { !/--/ } @{ $self->{ORDER} }; - } else { - @menu = grep { /^${menulevel}--/ } @{ $self->{ORDER} }; + while (my ($key, $value) = each %{ $node->{params} }) { + push @tokens, uri_encode($key, 1) . "=" . uri_encode($value, 1); } - my @a = split /;/, $myconfig->{acs}; - my $excl = (); + return join '?', $href, grep $_, join '&', @tokens; +} - # remove --AR, --AP from array - grep { ($a, $b) = split /--/; s/--$a$//; } @a; +sub name_for_node { + $::locale->text($_[1]{name}) +} - map { $excl{$_} = 1 } @a; +sub parse_instance_conf_string { + my ($self, $setting) = @_; + return $::instance_conf->data->{$setting}; +} - @a = (); - map { push @a, $_ unless $excl{$_} } (@menu); +sub parse_compare_string { + my ($self, $switch) = @_; + my ($setting, $mode) = split m/=/, $switch, 2; + return $::instance_conf->data->{$setting} eq $mode; +} - $main::lxdebug->leave_sub(2); +sub clear_access { + my ($self) = @_; + for my $node ($self->tree_walk("all")) { + delete $node->{visible}; + delete $node->{visible_children}; + } +} - return @a; +sub set_access { + my ($self) = @_; + # 1. evaluate appearence + # 2. evaluate access for all + # 3. if a menu has no visible children, its not visible either + + for my $node (reverse $self->tree_walk("all")) { + $node->{visible} = $node->{inclusion} ? $self->parse_compare_string($node->{inclusion}) : 1 + && $node->{exclusion} ? !$self->parse_compare_string($node->{exclusion}) : 1 + && $node->{access} ? $self->parse_access_string($node) + : !$node->{children} ? 1 + : $node->{visible_children} ? 1 + : 0; + if ($node->{visible} && $node->{parent}) { + $self->{by_id}{ $node->{parent} }{visible_children} = 1; + } + } } 1;