8 use SL::MoreCommon qw(uri_encode);
12 $yaml_xs = eval { require YAML::XS };
18 my ($package, $domain) = @_;
20 if (!$menu_cache{$domain}) {
21 my $path = File::Spec->catdir('menus', $domain);
23 opendir my $dir, $path or die "can't open $path: $!";
24 my @files = sort grep -f "$path/$_", grep /\.yaml$/, readdir $dir;
29 for my $file (@files) {
32 $data = YAML::XS::LoadFile(File::Spec->catfile($path, $file));
34 $data = YAML::LoadFile(File::Spec->catfile($path, $file));
36 _merge($nodes, $nodes_by_id, $data);
42 by_id => $nodes_by_id,
47 $menu_cache{$domain} = $self;
49 $menu_cache{$domain}->clear_access;
52 $menu_cache{$domain}->set_access;
54 return $menu_cache{$domain};
58 my ($nodes, $by_id, $data) = @_;
60 die 'not an array ref' unless $data && 'ARRAY' eq ref $data; # TODO check this sooner, to get better diag to user
62 for my $node (@$data) {
65 my $merge_to = $by_id->{$id};
69 $by_id->{$id} = $node;
73 # TODO make this a real recursive merge
74 # TODO add support for arrays
76 # merge keys except params
77 for my $key (keys %$node) {
78 if (ref $node->{$key}) {
79 if ('HASH' eq ref $node->{$key}) {
80 $merge_to->{$key} = {} if !exists $merge_to->{$key} || 'HASH' ne ref $merge_to->{$key};
81 for (keys %{ $node->{params} }) {
82 $merge_to->{$key}{$_} = $node->{params}{$_};
85 die "unsupported structure @{[ ref $node->{$key} ]}";
88 $merge_to->{$key} = $node->{$key};
97 # first, some sanity check. are all parents valid ids or empty?
98 for my $node ($self->nodes) {
99 next if !exists $node->{parent} || !$node->{parent} || $self->{by_id}->{$node->{id}};
100 die "menu: node $node->{id} has non-existent parent $node->{parent}";
104 # order them by parent
105 for my $node ($self->nodes) {
106 push @{ $by_parent{ $node->{parent} } //= [] }, $node;
110 $self->{by_id}{''} = $tree;
113 for (keys %by_parent) {
114 my $parent = $self->{by_id}{$_};
115 $parent->{children} = [ sort { $a->{order} <=> $b->{order} } @{ $by_parent{$_} } ];
118 _set_level_rec($tree->{children}, 0);
120 $self->{tree} = $tree->{children};
124 my ($ary_ref, $level) = @_;
127 $_->{level} = $level;
128 _set_level_rec($_->{children}, $level + 1) if $_->{children};
137 my ($self, $all) = @_;
139 _tree_walk_rec($self->{tree}, $all);
143 my ($ary_ref, $all) = @_;
144 map { $_->{children} ? ($_, _tree_walk_rec($_->{children}, $all)) : ($_) } grep { $all || $_->{visible} } @$ary_ref;
147 sub parse_access_string {
148 my ($self, $node) = @_;
153 push @stack, $cur_ary;
155 my $access = $node->{access};
157 while ($access =~ m/^([a-z_\/]+|\||\&|\(|\)|\s+)/) {
159 substr($access, 0, length($1)) = "";
161 next if ($token =~ /\s/);
164 my $new_cur_ary = [];
165 push @stack, $new_cur_ary;
166 push @{$cur_ary}, $new_cur_ary;
167 $cur_ary = $new_cur_ary;
169 } elsif ($token eq ")") {
172 die "Error while parsing menu entry $node->{id}: missing '('";
174 $cur_ary = $stack[-1];
176 } elsif (($token eq "|") || ($token eq "&")) {
177 push @{$cur_ary}, $token;
180 if ($token =~ m{^ client / (.*) }x) {
181 push @{$cur_ary}, $self->parse_instance_conf_string($1);
183 push @{$cur_ary}, $::auth->check_right($::myconfig{login}, $token, 1);
189 die "Error while parsing menu entry $node->{id}: unrecognized token at the start of '$access'\n";
192 if (1 < scalar @stack) {
193 die "Error while parsing menu entry $node->{id}: Missing ')'\n";
196 return SL::Auth::evaluate_rights_ary($stack[0]);
200 my ($self, $node) = @_;
202 return undef if !$node->{href} && !$node->{module} && !$node->{params};
204 my $href = $node->{href} || $node->{module} || 'controller.pl';
207 while (my ($key, $value) = each %{ $node->{params} }) {
208 push @tokens, uri_encode($key, 1) . "=" . uri_encode($value, 1);
211 return join '?', $href, grep $_, join '&', @tokens;
215 $::locale->text($_[1]{name})
218 sub parse_instance_conf_string {
219 my ($self, $setting) = @_;
220 return $::instance_conf->data->{$setting};
225 for my $node ($self->tree_walk("all")) {
226 delete $node->{visible};
227 delete $node->{visible_children};
233 # 1. evaluate access for all
234 # 2. if a menu has no visible children, its not visible either
236 for my $node (reverse $self->tree_walk("all")) {
237 $node->{visible} = $node->{access} ? $self->parse_access_string($node)
238 : !$node->{children} ? 1
239 : $node->{visible_children} ? 1
241 if ($node->{visible} && $node->{parent}) {
242 $self->{by_id}{ $node->{parent} }{visible_children} = 1;