8 use SL::MoreCommon qw(uri_encode);
12 $yaml_xs = eval { require YAML::XS };
16 my ($package, $domain) = @_;
18 my $path = File::Spec->catdir('menus', $domain);
20 opendir my $dir, $path or die "can't open $path: $!";
21 my @files = sort grep -f "$path/$_", readdir $dir;
26 for my $file (@files) {
29 $data = YAML::XS::LoadFile(File::Spec->catfile($path, $file));
31 $data = YAML::LoadFile(File::Spec->catfile($path, $file));
33 _merge($nodes, $nodes_by_id, $data);
39 by_id => $nodes_by_id,
49 my ($nodes, $by_id, $data) = @_;
51 die 'not an array ref' unless $data && 'ARRAY' eq ref $data; # TODO check this sooner, to get better diag to user
53 for my $node (@$data) {
56 my $merge_to = $by_id->{$id};
60 $by_id->{$id} = $node;
64 # TODO make this a real recursive merge
65 # TODO add support for arrays
67 # merge keys except params
68 for my $key (keys %$node) {
69 if (ref $node->{$key}) {
70 if ('HASH' eq ref $node->{$key}) {
71 $merge_to->{$key} = {} if !exists $merge_to->{$key} || 'HASH' ne ref $merge_to->{$key};
72 for (keys %{ $node->{params} }) {
73 $merge_to->{$key}{$_} = $node->{params}{$_};
76 die "unsupported structure @{[ ref $node->{$key} ]}";
79 $merge_to->{$key} = $node->{$key};
88 # first, some sanity check. are all parents valid ids or empty?
89 for my $node ($self->nodes) {
90 next if !exists $node->{parent} || !$node->{parent} || $self->{by_id}->{$node->{id}};
91 die "menu: node $node->{id} has non-existant parent $node->{parent}";
95 # order them by parent
96 for my $node ($self->nodes) {
97 push @{ $by_parent{ $node->{parent} } //= [] }, $node;
101 $self->{by_id}{''} = $tree;
104 for (keys %by_parent) {
105 my $parent = $self->{by_id}{$_};
106 $parent->{children} = [ sort { $a->{order} <=> $b->{order} } @{ $by_parent{$_} } ];
109 _set_level_rec($tree->{children}, 0);
111 $self->{tree} = $tree->{children};
115 my ($ary_ref, $level) = @_;
118 $_->{level} = $level;
119 _set_level_rec($_->{children}, $level + 1) if $_->{children};
128 my ($self, $all) = @_;
130 _tree_walk_rec($self->{tree}, $all);
134 my ($ary_ref, $all) = @_;
135 map { $_->{children} ? ($_, _tree_walk_rec($_->{children}, $all)) : ($_) } grep { $all || $_->{visible} } @$ary_ref;
138 sub parse_access_string {
139 my ($self, $node) = @_;
144 push @stack, $cur_ary;
146 my $access = $node->{access};
148 while ($access =~ m/^([a-z_\/]+|\||\&|\(|\)|\s+)/) {
150 substr($access, 0, length($1)) = "";
152 next if ($token =~ /\s/);
155 my $new_cur_ary = [];
156 push @stack, $new_cur_ary;
157 push @{$cur_ary}, $new_cur_ary;
158 $cur_ary = $new_cur_ary;
160 } elsif ($token eq ")") {
163 die "Error while parsing menu entry $node->{id}: missing '('";
165 $cur_ary = $stack[-1];
167 } elsif (($token eq "|") || ($token eq "&")) {
168 push @{$cur_ary}, $token;
171 if ($token =~ m{^ client / (.*) }x) {
172 push @{$cur_ary}, $self->parse_instance_conf_string($1);
174 push @{$cur_ary}, $::auth->check_right($::myconfig{login}, $token, 1);
180 die "Error while parsing menu entry $node->{id}: unrecognized token at the start of '$access'\n";
183 if (1 < scalar @stack) {
184 die "Error while parsing menu entry $node->{id}: Missing ')'\n";
187 return SL::Auth::evaluate_rights_ary($stack[0]);
191 my ($self, $node) = @_;
193 return undef if !$node->{href} && !$node->{module} && !$node->{params};
195 my $href = $node->{href} || $node->{module} || 'controller.pl';
198 while (my ($key, $value) = each %{ $node->{params} }) {
199 push @tokens, uri_encode($key, 1) . "=" . uri_encode($value, 1);
202 return join '?', $href, grep $_, join '&', @tokens;
206 $::locale->text($_[1]{name})
209 sub parse_instance_conf_string {
210 my ($self, $setting) = @_;
211 return $::instance_conf->data->{$setting};
216 # 1. evaluate access for all
217 # 2. if a menu has no visible children, its not visible either
219 for my $node (reverse $self->tree_walk("all")) {
220 $node->{visible} = $node->{access} ? $self->parse_access_string($node)
221 : !$node->{children} ? 1
222 : $node->{visible_children} ? 1
224 if ($node->{visible} && $node->{parent}) {
225 $self->{by_id}{ $node->{parent} }{visible_children} = 1;