Menüstruktur auf YAML geändert
authorSven Schöling <s.schoeling@linet-services.de>
Wed, 15 Jul 2015 15:54:07 +0000 (17:54 +0200)
committerSven Schöling <s.schoeling@linet-services.de>
Mon, 20 Jul 2015 14:37:26 +0000 (16:37 +0200)
13 files changed:
SL/Layout/Admin.pm
SL/Layout/Base.pm
SL/Layout/CssMenu.pm
SL/Layout/Javascript.pm
SL/Layout/MenuLeft.pm
SL/Menu.pm
menus/admin/00-admin.yaml [new file with mode: 0644]
menus/user/00-erp.yaml [new file with mode: 0644]
menus/user/10-crm.yaml [new file with mode: 0644]
scripts/locales.pl
scripts/migrate_menu.pl
templates/webpages/menu/menunew.html
templates/webpages/menu/menuv3.html

index e10ed03..03832b5 100644 (file)
@@ -11,7 +11,7 @@ use SL::Layout::CssMenu;
 sub init_sub_layouts {
   [
     SL::Layout::None->new,
-    SL::Layout::CssMenu->new(menu => Menu->new('menus/admin.ini')),
+    SL::Layout::CssMenu->new(menu => SL::Menu->new('admin')),
   ]
 }
 
index a8bf139..e39502f 100644 (file)
@@ -29,9 +29,7 @@ sub new {
 }
 
 sub init_menu {
-  my @menu_files = qw(menus/erp.ini);
-  unshift @menu_files, 'menus/crm.ini' if $::instance_conf->crm_installed;
-  Menu->new(@menu_files);
+  SL::Menu->new('user');
 }
 
 sub init_auto_reload_resources_param {
index c26fad7..d939908 100644 (file)
@@ -3,114 +3,12 @@ package SL::Layout::CssMenu;
 use strict;
 use parent qw(SL::Layout::Base);
 
-use URI;
-
-sub print_menu {
-  my ($self, $parent, $depth) = @_;
-
-  my $html;
-
-  die if ($depth * 1 > 5);
-
-  my @menuorder;
-  my $menu = $self->menu;
-
-  @menuorder = $menu->access_control(\%::myconfig, $parent);
-
-  $parent .= "--" if ($parent);
-
-  foreach my $item (@menuorder) {
-    substr($item, 0, length($parent)) = "";
-    next if (($item eq "") || ($item =~ /--/));
-
-    my $menu_item = $menu->{"${parent}${item}"};
-    my $menu_title = $::locale->text($item);
-    my $menu_text = $menu_title;
-
-    if ($menu_item->{"submenu"} || !defined($menu_item->{"module"}) && !defined($menu_item->{href})) {
-
-      my $h = $self->print_menu("${parent}${item}", $depth * 1 + 1)."\n";
-      if (!$parent) {
-        $html .= qq|<ul><li><h2>${menu_text}</h2><ul>${h}</ul></li></ul>\n|;
-      } else {
-        $html .= qq|<li><div class="x">${menu_text}</div><ul>${h}</ul></li>\n|;
-      }
-    } else {
-      if ($self->{sub_class} && $depth > 1) {
-        $html .= qq|<li class='sub'>|;
-      } else {
-        $html .= qq|<li>|;
-      }
-      $html .= $self->menuitem_v3("${parent}$item", { "title" => $menu_title });
-      $html .= qq|${menu_text}</a></li>\n|;
-    }
-  }
-
-  return $html;
-}
-
-sub menuitem_v3 {
-  $main::lxdebug->enter_sub();
-
-  my ($self, $item, $other) = @_;
-  my $menuitem = $self->menu->{$item};
-
-  my $action = "section_menu";
-  my $module;
-
-  if ($menuitem->{module}) {
-    $module = $menuitem->{module};
-  }
-  if ($menuitem->{action}) {
-    $action = $menuitem->{action};
-  }
-
-  my $level  = $::form->escape($item);
-
-  my @vars;
-  my $target = $menuitem->{target} ? qq| target="| . $::form->escape($menuitem->{target}) . '"' : '';
-  my $str    = qq|<a${target} href="|;
-
-  if ($menuitem->{href}) {
-    $main::lxdebug->leave_sub();
-    return $str . $menuitem->{href} . '">';
-  }
-
-  $str .= qq|$module?action=| . $::form->escape($action);
-
-  map { delete $menuitem->{$_} } qw(module action target href);
-
-  # add other params
-  foreach my $key (keys %{ $menuitem }) {
-    $str .= "&amp;" . $::form->escape($key, 1) . "=";
-    my ($value, $conf) = split(/=/, $menuitem->{$key}, 2);
-    $value = $::myconfig{$value} . "/$conf" if ($conf);
-    $str .= $::form->escape($value, 1);
-  }
-
-  $str .= '"';
-
-  if ($other) {
-    foreach my $key (keys(%{$other})) {
-      $str .= qq| ${key}="| . $::form->quote($other->{$key}) . qq|"|;
-    }
-  }
-
-  $str .= ">";
-
-  $main::lxdebug->leave_sub();
-
-  return $str;
-}
-
 sub use_stylesheet {
   qw(icons16.css frame_header/header.css),
 }
 
 sub pre_content {
-  $_[0]->presenter->render('menu/menuv3',
-    menu           => $_[0]->print_menu,
-  );
+  $_[0]->presenter->render('menu/menuv3', menu => $_[0]->menu);
 }
 
 1;
index 26539de..366e0d7 100644 (file)
@@ -21,9 +21,29 @@ sub use_javascript {
   $self->SUPER::use_javascript(@_);
 }
 
+sub javascripts_inline {
+  $_[0]->SUPER::javascripts_inline,
+<<'EOJS'
+  DHTMLSuite.createStandardObjects();
+  DHTMLSuite.configObj.setImagePath('image/dhtmlsuite/');
+  var menu_model = new DHTMLSuite.menuModel();
+  menu_model.addItemsFromMarkup('main_menu_model');
+  menu_model.init();
+  var menu_bar = new DHTMLSuite.menuBar();
+  menu_bar.addMenuItems(menu_model);
+  menu_bar.setTarget('main_menu_div');
+  menu_bar.init();
+EOJS
+}
+
 sub pre_content {
   $_[0]->SUPER::pre_content .
-  &display
+  $_[0]->presenter->render("menu/menunew",
+    force_ul_width  => 1,
+    menu            => $_[0]->menu,
+    icon_path       => sub { my $img = "image/icons/16x16/$_[0].png"; -f $img ? $img : () },
+    max_width       => sub { 10 * max map { length $::locale->text($_->{name}) } @{ $_[0]{children} || [] } },
+  );
 }
 
 sub start_content {
@@ -45,83 +65,4 @@ sub stylesheets {
   $_[0]->SUPER::stylesheets;
 }
 
-sub display {
-  my ($self) = @_;
-
-  $self->presenter->render("menu/menunew",
-    force_ul_width  => 1,
-    menu_items      => $self->acc_menu,
-  );
-}
-
-sub acc_menu {
-  my ($self) = @_;
-
-  my $menu      = $self->menu;
-
-  my $all_items = [];
-  $self->create_menu($menu, $all_items);
-
-  my $item = { 'subitems' => $all_items };
-  calculate_width($item);
-
-  return $all_items;
-}
-
-sub calculate_width {
-  my $item           = shift;
-
-  $item->{max_width} = max map { length $_->{title} } @{ $item->{subitems} };
-
-  foreach my $subitem (@{ $item->{subitems} }) {
-    calculate_width($subitem) if ($subitem->{subitems});
-  }
-}
-
-sub create_menu {
-  my ($self, $menu, $all_items, $parent, $depth) = @_;
-  my $html;
-
-  my $form     = $main::form;
-  my %myconfig = %main::myconfig;
-
-  $depth ||= 0;
-
-  die if ($depth * 1 > 5);
-
-  my @menuorder  = $menu->access_control(\%myconfig, $parent);
-  $parent       .= "--" if ($parent);
-  $parent      ||= '';
-
-  foreach my $name (@menuorder) {
-    substr($name, 0, length($parent), "");
-    next if (($name eq "") || ($name =~ /--/));
-
-    my $menu_item = $menu->{"${parent}${name}"};
-    my $item      = { 'title' => $::locale->text($name) };
-    push @{ $all_items }, $item;
-
-    if ($menu_item->{submenu} || (!defined($menu_item->{module}) && !defined($menu_item->{href}))) {
-      $item->{subitems} = [];
-      $item->{image} = _icon_path($menu_item->{ICON});
-      $self->create_menu($menu, $item->{subitems}, "${parent}${name}", $depth * 1 + 1);
-
-    } else {
-      $item->{image} = _icon_path("${parent}${name}.png");
-      $menu->menuitem_new("${parent}${name}", $item);
-    }
-  }
-}
-
-sub _icon_path {
-  my ($label, $size) = @_;
-
-  $size ||= 16;
-
-  my $img = "image/icons/${size}x${size}/$label.png";
-
-  return unless -f $img;
-  return $img;
-}
-
 1;
index 4c1901c..17ea51a 100644 (file)
@@ -40,46 +40,28 @@ sub end_content {
 
 sub section_menu {
   my ($menu) = @_;
-  my @menuorder = @{ $menu->{ORDER} };
   my @items;
   my @id_stack = (-1);
 
-  for my $item (@menuorder) {
-    my $menuitem   = $menu->{$item};
-    my $olabel     = apply { s/.*--// } $item;
-    my $ml         = apply { s/--.*// } $item;
-    my $icon_class = apply { $_ = lc $_; s/[^a-z0-9_-]/-/g } $menuitem->{ICON};
-    my $level      = (0 + $item =~ s/--/--/g);
-    my $spacer     = "s" . $level;
+  for my $node ($menu->tree_walk) {
+    my $level      = $node->{level};
 
     # do id stack
     push @id_stack, -1 if    $level > $#id_stack;
     pop @id_stack      while $level < $#id_stack;
     $id_stack[-1]++;
 
-    my $label         = $::locale->text($olabel);
+    my $label = $::locale->text($node->{name});
+    my $href  = $menu->href_for_node($node);
 
-    $menuitem->{module} ||= $::form->{script};
-    $menuitem->{action} ||= "section_menu";
-    $menuitem->{href}   ||= "$menuitem->{module}?action=$menuitem->{action}";
+    my @common_args  = ($label, "s" . $level, join '_', @id_stack);
 
-    # add other params
-    foreach my $key (keys %$menuitem) {
-      next if $key =~ /target|module|action|href|ICON/;
-      $menuitem->{href} .= "&" . $::form->escape($key, 1) . "=";
-      my ($value, $conf) = split(/=/, $menuitem->{$key}, 2);
-      $value = $::myconfig{$value} . "/$conf" if ($conf);
-      $menuitem->{href} .= $::form->escape($value, 1);
-    }
-
-    my @common_args  = ($label, $spacer, join '_', @id_stack);
-
-    if ($spacer eq 's0') { # toplevel
-      push @items, [ @common_args, "icon24 $icon_class", 'm' ];
-    } elsif ($menuitem->{submenu}) {
+    if (!$node->{parent}) { # toplevel
+      push @items, [ @common_args, "icon24 $node->{icon}", 'm' ];
+    } elsif ($node->{children}) {
       push @items, [ @common_args, "icon16 submenu", 'sm' ];
-    } elsif ($menuitem->{module}) {
-      push @items, [ @common_args, "icon16 $icon_class", 'i', $menuitem->{href}, $menuitem->{target} ];
+    } else {
+      push @items, [ @common_args, "icon16 $node->{icon}", 'i', $href, $node->{target} ];
     }
   }
 
index 8737016..534a05e 100644 (file)
-#=====================================================================
-# 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;
 
 use strict;
 
 use SL::Auth;
-use SL::Inifile;
-
-our @ISA = qw(Inifile);
-
+use YAML::XS ();
+use File::Spec;
+use SL::MoreCommon qw(uri_encode);
 
 sub new {
-  $main::lxdebug->enter_sub();
+  my ($package, $domain) = @_;
 
-  my ($package, @menufiles) = @_;
+  my $path = File::Spec->catdir('menus', $domain);
 
-  my $self = $package->SUPER::new($menufiles[0]);
+  opendir my $dir, $path or die "can't open $path: $!";
+  my @files = sort grep -f "$path/$_", readdir $dir;
+  close $dir;
 
-  for (@menufiles[1..$#menufiles]) {
-    my $inifile = Inifile->new($_);
-    push @{ $self->{ORDER} }, @{ delete $inifile->{ORDER} };
-    $self->{$_} = $inifile->{$_} for keys %$inifile;
+  my $nodes = [];
+  my $nodes_by_id = {};
+  for my $file (@files) {
+    my $data = YAML::XS::LoadFile(File::Spec->catfile($path, $file));
+    _merge($nodes, $nodes_by_id, $data);
   }
 
-  $self->set_access;
 
-  $main::lxdebug->leave_sub();
+  my $self = bless {
+    nodes => $nodes,
+    by_id => $nodes_by_id,
+  }, $package;
+
+  $self->build_tree;
+  $self->set_access;
 
   return $self;
 }
 
-sub menuitem_new {
-  $main::lxdebug->enter_sub(LXDebug::DEBUG2());
+sub _merge {
+  my ($nodes, $by_id, $data) = @_;
 
-  my ($self, $name, $item) = @_;
+  die 'not an array ref' unless $data && 'ARRAY' eq ref $data; # TODO check this sooner, to get better diag to user
 
-  my $module      = $self->{$name}->{module} || $::form->{script};
-  my $action      = $self->{$name}->{action};
+  for my $node (@$data) {
+    my $id = $node->{id};
 
-  $item->{target} = $self->{$name}->{target} || "main_window";
-  $item->{href}   = $self->{$name}->{href}   || "${module}?action=" . $::form->escape($action);
+    my $merge_to = $by_id->{$id};
 
-  my @vars = qw(module target href);
-  push @vars, 'action' unless ($self->{$name}->{href});
+    if (!$merge_to) {
+      push @$nodes, $node;
+      $by_id->{$id} = $node;
+      next;
+    }
 
-  map { delete $self->{$name}{$_} } @vars;
+    # 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};
+      }
+    }
+  }
+}
 
-  # add other params
-  foreach my $key (keys %{ $self->{$name} }) {
-    my ($value, $conf)  = split(m/=/, $self->{$name}->{$key}, 2);
-    $value              = $::myconfig->{$value} . "/$conf" if ($conf);
-    $item->{href}      .= "&" . $::form->escape($key) . "=" . $::form->escape($value);
+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-existant parent $node->{parent}";
   }
 
-  $main::lxdebug->leave_sub(LXDebug::DEBUG2());
-}
+  my %by_parent;
+  # order them by parent
+  for my $node ($self->nodes) {
+    push @{ $by_parent{ $node->{parent} } //= [] }, $node;
+  }
 
-sub access_control {
-  $main::lxdebug->enter_sub(2);
+  my $tree = { };
+  $self->{by_id}{''} = $tree;
 
-  my ($self, $myconfig, $menulevel) = @_;
 
-  my @menu = ();
+  for (keys %by_parent) {
+    my $parent = $self->{by_id}{$_};
+    $parent->{children} =  [ sort { $a->{order} <=> $b->{order} } @{ $by_parent{$_} } ];
+  }
+
+  _set_level_rec($tree->{children}, 0);
+
+  $self->{tree} = $tree->{children};
+}
+
+sub _set_level_rec {
+  my ($ary_ref, $level) = @_;
 
-  if (!$menulevel) {
-    @menu = grep { !/--/ } @{ $self->{ORDER} };
-  } else {
-    @menu = grep { /^${menulevel}--/ } @{ $self->{ORDER} };
+  for (@$ary_ref) {
+    $_->{level} = $level;
+    _set_level_rec($_->{children}, $level + 1) if $_->{children};
   }
+}
 
-  $main::lxdebug->leave_sub(2);
+sub nodes {
+  @{ $_[0]{nodes} }
+}
+
+sub tree_walk {
+  my ($self, $all) = @_;
 
-  return @menu;
+  _tree_walk_rec($self->{tree}, $all);
 }
 
-sub parse_access_string {
-  my $self   = shift;
-  my $key    = shift;
-  my $access = shift;
+sub _tree_walk_rec {
+  my ($ary_ref, $all) = @_;
+  map { $_->{children} ? ($_, _tree_walk_rec($_->{children}, $all)) : ($_) } grep { $all || $_->{visible} } @$ary_ref;
+}
 
-  my $form        =  $main::form;
-  my $auth        =  $main::auth;
-  my $myconfig    = \%main::myconfig;
+sub parse_access_string {
+  my ($self, $node) = @_;
 
   my @stack;
   my $cur_ary = [];
 
   push @stack, $cur_ary;
 
-  while ($access =~ m/^([a-z_]+|\||\&|\(|\)|\s+)/) {
+  my $access = $node->{access};
+
+  while ($access =~ m/^([a-z_\/]+|\||\&|\(|\)|\s+)/) {
     my $token = $1;
     substr($access, 0, length($1)) = "";
 
@@ -135,7 +150,7 @@ sub parse_access_string {
     } elsif ($token eq ")") {
       pop @stack;
       if (!@stack) {
-        $form->error("Error in menu.ini for entry ${key}: missing '('");
+        die "Error in menu.ini for entry $node->{id}: missing '('";
       }
       $cur_ary = $stack[-1];
 
@@ -143,77 +158,62 @@ sub parse_access_string {
       push @{$cur_ary}, $token;
 
     } else {
-      push @{$cur_ary}, $auth->check_right($::myconfig{login}, $token, 1);
+      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);
+      }
     }
   }
 
   if ($access) {
-    $form->error("Error in menu.ini for entry ${key}: unrecognized token at the start of '$access'\n");
+    die "Error in menu.ini for entry $node->{id}: unrecognized token at the start of '$access'\n";
   }
 
   if (1 < scalar @stack) {
-    $main::form->error("Error in menu.ini for entry ${key}: Missing ')'\n");
+    die "Error in menu.ini for entry $node->{id}: Missing ')'\n";
   }
 
   return SL::Auth::evaluate_rights_ary($stack[0]);
 }
 
-sub parse_instance_conf_string {
-  my ($self, $setting) = @_;
-  return $::instance_conf->data->{$setting};
-}
-
-sub set_access {
-  my $self = shift;
-
-  my $key;
-
-  foreach $key (@{ $self->{ORDER} }) {
-    my $entry = $self->{$key};
-
-    $entry->{GRANTED}              = $entry->{ACCESS} ? $self->parse_access_string($key, $entry->{ACCESS}) : 1;
-    $entry->{GRANTED}            &&= $self->parse_instance_conf_string($entry->{INSTANCE_CONF}) if $entry->{INSTANCE_CONF};
-    $entry->{IS_MENU}              = $entry->{submenu} || ($key !~ m/--/);
-    $entry->{NUM_VISIBLE_CHILDREN} = 0;
-
-    if ($key =~ m/--/) {
-      my $parent = $key;
-      substr($parent, rindex($parent, '--')) = '';
-      $entry->{GRANTED} &&= $self->{$parent}->{GRANTED};
-    }
-
-    $entry->{VISIBLE} = $entry->{GRANTED};
-  }
-
-  foreach $key (reverse @{ $self->{ORDER} }) {
-    my $entry = $self->{$key};
+sub href_for_node {
+  my ($self, $node) = @_;
 
-    if ($entry->{IS_MENU}) {
-      $entry->{VISIBLE} &&= $entry->{NUM_VISIBLE_CHILDREN} > 0;
-    }
+  return undef if !$node->{href} && !$node->{module} && !$node->{params};
 
-    next if (($key !~ m/--/) || !$entry->{VISIBLE});
+  my $href = $node->{href} || $node->{module} || 'controller.pl';
+  my @tokens;
 
-    my $parent = $key;
-    substr($parent, rindex($parent, '--')) = '';
-    $self->{$parent}->{NUM_VISIBLE_CHILDREN}++;
+  while (my ($key, $value) = each %{ $node->{params} }) {
+    push @tokens, uri_encode($key, 1) . "=" . uri_encode($value, 1);
   }
 
-#   $self->dump_visible();
+  return join '?', $href, grep $_, join '&', @tokens;
+}
 
-  $self->{ORDER} = [ grep { $self->{$_}->{VISIBLE} } @{ $self->{ORDER} } ];
+sub name_for_node {
+  $::locale->text($_[1]{name})
+}
 
-  { no strict 'refs';
-  # ToDO: fix this. nuke and pave algorithm without type checking screams for problems.
-  map { delete @{$self->{$_}}{qw(GRANTED IS_MENU NUM_VISIBLE_CHILDREN VISIBLE ACCESS)} if ($_ ne 'ORDER') } keys %{ $self };
-  }
+sub parse_instance_conf_string {
+  my ($self, $setting) = @_;
+  return $::instance_conf->data->{$setting};
 }
 
-sub dump_visible {
-  my $self = shift;
-  foreach my $key (@{ $self->{ORDER} }) {
-    my $entry = $self->{$key};
-    $main::lxdebug->message(0, "$entry->{GRANTED} $entry->{VISIBLE} $entry->{NUM_VISIBLE_CHILDREN} $key");
+sub set_access {
+  my ($self) = @_;
+  # 1. evaluate access for all
+  # 2. if a menu has no visible children, its not visible either
+
+  for my $node (reverse $self->tree_walk("all")) {
+    $node->{visible} = $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;
+    }
   }
 }
 
diff --git a/menus/admin/00-admin.yaml b/menus/admin/00-admin.yaml
new file mode 100644 (file)
index 0000000..8685027
--- /dev/null
@@ -0,0 +1,92 @@
+---
+- id: users_clients_and_user_groups
+  name: Users, Clients and User Groups
+  order: 100
+- parent: users_clients_and_user_groups
+  id: users_clients_and_user_groups_list_users_clients_and_user_groups
+  name: List Users, Clients and User Groups
+  order: 100
+  params: 
+    action: Admin/show
+- parent: users_clients_and_user_groups
+  id: users_clients_and_user_groups_add_user
+  name: Add User
+  order: 200
+  params: 
+    action: Admin/new_user
+- parent: users_clients_and_user_groups
+  id: users_clients_and_user_groups_add_client
+  name: Add Client
+  order: 300
+  params: 
+    action: Admin/new_client
+- parent: users_clients_and_user_groups
+  id: users_clients_and_user_groups_add_user_group
+  name: Add User Group
+  order: 400
+  params: 
+    action: Admin/new_group
+- id: database_management
+  name: Database Management
+  order: 200
+- parent: database_management
+  id: database_management_create_dataset
+  name: Create Dataset
+  order: 100
+  params: 
+    action: Admin/create_dataset_login
+- parent: database_management
+  id: database_management_delete_dataset
+  name: Delete Dataset
+  order: 200
+  params: 
+    action: Admin/delete_dataset_login
+- id: printer_management
+  name: Printer Management
+  order: 300
+- parent: printer_management
+  id: printer_management_list_printers
+  name: List Printers
+  order: 100
+  params: 
+    action: Admin/list_printers
+- parent: printer_management
+  id: printer_management_add_printer
+  name: Add Printer
+  order: 200
+  params: 
+    action: Admin/new_printer
+- id: system
+  name: System
+  icon: system
+  order: 400
+- parent: system
+  id: system_lock_and_unlock_installation
+  name: Lock and unlock installation
+  order: 100
+  params: 
+    action: Admin/show_lock
+- parent: system
+  id: system_documentation_in_german_
+  name: Documentation (in German)
+  order: 200
+  href: doc/kivitendo-Dokumentation.pdf
+  target: _blank
+- parent: system
+  id: system_kivitendo_website_external_
+  name: kivitendo website (external)
+  order: 300
+  href: http://www.kivitendo.de/
+  target: _blank
+- parent: system
+  id: system_to_user_login
+  name: To user login
+  order: 400
+  params: 
+    action: LoginScreen/user_login
+- parent: system
+  id: system_logout
+  name: Logout
+  order: 500
+  params: 
+    action: Admin/logout
diff --git a/menus/user/00-erp.yaml b/menus/user/00-erp.yaml
new file mode 100644 (file)
index 0000000..1a44d0f
--- /dev/null
@@ -0,0 +1,1388 @@
+---
+- id: master_data
+  name: Master Data
+  icon: master_data
+  order: 100
+- parent: master_data
+  id: master_data_add_customer
+  name: Add Customer
+  icon: customer_add
+  order: 100
+  access: customer_vendor_edit
+  params: 
+    action: CustomerVendor/add
+    db: customer
+- parent: master_data
+  id: master_data_add_vendor
+  name: Add Vendor
+  icon: vendor_add
+  order: 200
+  access: customer_vendor_edit
+  params: 
+    action: CustomerVendor/add
+    db: vendor
+- parent: master_data
+  id: master_data_add_part
+  name: Add Part
+  icon: part_add
+  order: 300
+  access: part_service_assembly_edit
+  module: ic.pl
+  params: 
+    action: add
+    item: part
+- parent: master_data
+  id: master_data_add_service
+  name: Add Service
+  icon: service_add
+  order: 400
+  access: part_service_assembly_edit
+  module: ic.pl
+  params: 
+    action: add
+    item: service
+- parent: master_data
+  id: master_data_add_assembly
+  name: Add Assembly
+  icon: assembly_add
+  order: 500
+  access: part_service_assembly_edit
+  module: ic.pl
+  params: 
+    action: add
+    item: assembly
+- parent: master_data
+  id: master_data_add_project
+  name: Add Project
+  icon: project_add
+  order: 600
+  access: project_edit
+  params: 
+    action: Project/new
+- parent: master_data
+  id: master_data_add_requirement_spec_template
+  name: Add Requirement Spec Template
+  order: 700
+  access: requirement_spec_edit
+  params: 
+    action: RequirementSpec/new
+    is_template: 1
+- parent: master_data
+  id: master_data_update_prices
+  name: Update Prices
+  icon: prices_update
+  order: 800
+  access: part_service_assembly_edit
+  module: ic.pl
+  params: 
+    action: search_update_prices
+- parent: master_data
+  id: master_data_price_rules
+  name: Price Rules
+  order: 900
+  access: part_service_assembly_edit
+  params: 
+    action: PriceRule/list
+    filter.obsolete: 0
+- parent: master_data
+  id: master_data_reports
+  name: Reports
+  icon: master_data_report
+  order: 1000
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: master_data_reports
+  id: master_data_reports_customers
+  name: Customers
+  icon: customer_report
+  order: 100
+  access: customer_vendor_edit
+  params: 
+    action: CustomerVendor/search
+    db: customer
+- parent: master_data_reports
+  id: master_data_reports_vendors
+  name: Vendors
+  icon: vendor_report
+  order: 200
+  access: customer_vendor_edit
+  params: 
+    action: CustomerVendor/search
+    db: vendor
+- parent: master_data_reports
+  id: master_data_reports_contacts
+  name: Contacts
+  order: 300
+  access: customer_vendor_edit
+  params: 
+    action: CustomerVendor/search_contact
+    db: customer
+- parent: master_data_reports
+  id: master_data_reports_parts
+  name: Parts
+  icon: part_report
+  order: 400
+  access: part_service_assembly_details
+  module: ic.pl
+  params: 
+    action: search
+    searchitems: part
+- parent: master_data_reports
+  id: master_data_reports_services
+  name: Services
+  icon: service_report
+  order: 500
+  access: part_service_assembly_details
+  module: ic.pl
+  params: 
+    action: search
+    searchitems: service
+- parent: master_data_reports
+  id: master_data_reports_assemblies
+  name: Assemblies
+  icon: assembly_report
+  order: 600
+  access: part_service_assembly_details
+  module: ic.pl
+  params: 
+    action: search
+    searchitems: assembly
+- parent: master_data_reports
+  id: master_data_reports_projects
+  name: Projects
+  icon: project_report
+  order: 700
+  access: project_edit
+  params: 
+    action: Project/list
+    filter.active: active
+    filter.valid: valid
+- parent: master_data_reports
+  id: master_data_reports_requirement_spec_templates
+  name: Requirement Spec Templates
+  order: 800
+  access: requirement_spec_edit
+  params: 
+    action: RequirementSpec/list
+    is_template: 1
+- id: ar
+  name: AR
+  icon: ar
+  order: 200
+- parent: ar
+  id: ar_add_requirement_spec
+  name: Add Requirement Spec
+  order: 100
+  access: requirement_spec_edit
+  params: 
+    action: RequirementSpec/new
+- parent: ar
+  id: ar_add_quotation
+  name: Add Quotation
+  icon: quotation_add
+  order: 200
+  access: sales_quotation_edit
+  module: oe.pl
+  params: 
+    action: add
+    type: sales_quotation
+- parent: ar
+  id: ar_add_sales_order
+  name: Add Sales Order
+  icon: sales_order_add
+  order: 300
+  access: sales_order_edit
+  module: oe.pl
+  params: 
+    action: add
+    type: sales_order
+- parent: ar
+  id: ar_add_delivery_order
+  name: Add Delivery Order
+  icon: delivery_order_add
+  order: 400
+  access: sales_delivery_order_edit
+  module: do.pl
+  params: 
+    action: add
+    type: sales_delivery_order
+- parent: ar
+  id: ar_add_sales_invoice
+  name: Add Sales Invoice
+  icon: sales_invoice_add
+  order: 500
+  access: invoice_edit
+  module: is.pl
+  params: 
+    action: add
+    type: invoice
+- parent: ar
+  id: ar_add_credit_note
+  name: Add Credit Note
+  icon: credit_note_add
+  order: 600
+  access: invoice_edit
+  module: is.pl
+  params: 
+    action: add
+    type: credit_note
+- parent: ar
+  id: ar_add_dunning
+  name: Add Dunning
+  icon: dunning_add
+  order: 700
+  access: dunning_edit
+  module: dn.pl
+  params: 
+    action: add
+- parent: ar
+  id: ar_add_letter
+  name: Add Letter
+  order: 800
+  access: sales_letter_edit
+  module: letter.pl
+  params: 
+    action: add
+- parent: ar
+  id: ar_reports
+  name: Reports
+  icon: ar_report
+  order: 900
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: ar_reports
+  id: ar_reports_requirement_specs
+  name: Requirement Specs
+  order: 100
+  access: requirement_spec_edit
+  params: 
+    action: RequirementSpec/list
+- parent: ar_reports
+  id: ar_reports_quotations
+  name: Quotations
+  icon: report_quotations
+  order: 200
+  access: sales_quotation_edit
+  module: oe.pl
+  params: 
+    action: search
+    type: sales_quotation
+- parent: ar_reports
+  id: ar_reports_sales_orders
+  name: Sales Orders
+  icon: report_sales_orders
+  order: 300
+  access: sales_order_edit
+  module: oe.pl
+  params: 
+    action: search
+    type: sales_order
+- parent: ar_reports
+  id: ar_reports_delivery_orders
+  name: Delivery Orders
+  icon: delivery_order_report
+  order: 400
+  access: sales_delivery_order_edit
+  module: do.pl
+  params: 
+    action: search
+    type: sales_delivery_order
+- parent: ar_reports
+  id: ar_reports_invoices_credit_notes_ar_transactions
+  name: Invoices, Credit Notes & AR Transactions
+  icon: invoices_report
+  order: 500
+  access: invoice_edit
+  module: ar.pl
+  params: 
+    action: search
+    nextsub: ar_transactions
+- parent: ar_reports
+  id: ar_reports_sales_report
+  name: Sales Report
+  order: 600
+  access: invoice_edit
+  module: vk.pl
+  params: 
+    action: search_invoice
+    nextsub: invoice_transactions
+- parent: ar_reports
+  id: ar_reports_dunnings
+  name: Dunnings
+  icon: dunnings_report
+  order: 700
+  access: dunning_edit
+  module: dn.pl
+  params: 
+    action: search
+- parent: ar_reports
+  id: ar_reports_delivery_plan
+  name: Delivery Plan
+  order: 800
+  access: delivery_plan
+  params: 
+    action: DeliveryPlan/list
+    vc: customer
+    mode: delivery_plan
+- parent: ar_reports
+  id: ar_reports_delivery_value_report
+  name: Delivery Value Report
+  order: 900
+  access: delivery_value_report
+  params: 
+    action: DeliveryPlan/list
+    mode: delivery_value_report
+    vc: customer
+- parent: ar_reports
+  id: ar_reports_financial_controlling
+  name: Financial Controlling
+  order: 1000
+  access: sales_order_edit
+  params: 
+    action: FinancialControllingReport/list
+- parent: ar_reports
+  id: ar_reports_letters
+  name: Letters
+  order: 1100
+  access: sales_letter_report
+  module: letter.pl
+  params: 
+    action: search
+- id: ap
+  name: AP
+  icon: ap
+  order: 300
+- parent: ap
+  id: ap_add_rfq
+  name: Add RFQ
+  icon: rfq_add
+  order: 100
+  access: request_quotation_edit
+  module: oe.pl
+  params: 
+    action: add
+    type: request_quotation
+- parent: ap
+  id: ap_add_purchase_order
+  name: Add Purchase Order
+  icon: purchase_order_add
+  order: 200
+  access: purchase_order_edit
+  module: oe.pl
+  params: 
+    action: add
+    type: purchase_order
+- parent: ap
+  id: ap_add_delivery_note
+  name: Add Delivery Note
+  order: 300
+  access: client/allow_new_purchase_delivery_order & purchase_delivery_order_edit
+  module: do.pl
+  params: 
+    action: add
+    type: purchase_delivery_order
+- parent: ap
+  id: ap_add_vendor_invoice
+  name: Add Vendor Invoice
+  order: 400
+  access: client/allow_new_purchase_invoice & vendor_invoice_edit
+  module: ir.pl
+  params: 
+    action: add
+    type: invoice
+- parent: ap
+  id: ap_reports
+  name: Reports
+  icon: ap_report
+  order: 500
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: ap_reports
+  id: ap_reports_rfqs
+  name: RFQs
+  icon: rfq_report
+  order: 100
+  access: request_quotation_edit
+  module: oe.pl
+  params: 
+    action: search
+    type: request_quotation
+- parent: ap_reports
+  id: ap_reports_purchase_orders
+  name: Purchase Orders
+  icon: purchase_order_report
+  order: 200
+  access: purchase_order_edit
+  module: oe.pl
+  params: 
+    action: search
+    type: purchase_order
+- parent: ap_reports
+  id: ap_reports_delivery_orders
+  name: Delivery Orders
+  order: 300
+  access: purchase_delivery_order_edit
+  module: do.pl
+  params: 
+    action: search
+    type: purchase_delivery_order
+- parent: ap_reports
+  id: ap_reports_vendor_invoices_ap_transactions
+  name: Vendor Invoices & AP Transactions
+  order: 400
+  access: vendor_invoice_edit
+  module: ap.pl
+  params: 
+    action: search
+    nextsub: ap_transactions
+- parent: ap_reports
+  id: ap_reports_delivery_plan
+  name: Delivery Plan
+  order: 500
+  access: delivery_plan
+  params: 
+    action: DeliveryPlan/list
+    mode: delivery_plan
+    vc: vendor
+- parent: ap_reports
+  id: ap_reports_delivery_value_report
+  name: Delivery Value Report
+  order: 600
+  access: delivery_value_report
+  params: 
+    action: DeliveryPlan/list
+    vc: vendor
+    mode: delivery_value_report
+- id: warehouse
+  name: Warehouse
+  icon: warehouse
+  order: 400
+- parent: warehouse
+  id: warehouse_stock
+  name: Stock
+  order: 100
+  access: warehouse_management
+  params: 
+    action: Inventory/stock_in
+- parent: warehouse
+  id: warehouse_produce_assembly
+  name: Produce Assembly
+  icon: assembly_produce
+  order: 200
+  access: warehouse_management
+  module: wh.pl
+  params: 
+    action: transfer_warehouse_selection
+    trans_type: assembly
+- parent: warehouse
+  id: warehouse_transfer
+  name: Transfer
+  order: 300
+  access: warehouse_management
+  module: wh.pl
+  params: 
+    action: transfer_warehouse_selection
+    trans_type: transfer
+- parent: warehouse
+  id: warehouse_removal
+  name: Removal
+  order: 400
+  access: warehouse_management
+  module: wh.pl
+  params: 
+    action: transfer_warehouse_selection
+    trans_type: removal
+- parent: warehouse
+  id: warehouse_reports
+  name: Reports
+  order: 500
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: warehouse_reports
+  id: warehouse_reports_warehouse_content
+  name: Warehouse content
+  order: 100
+  access: warehouse_contents | warehouse_management
+  module: wh.pl
+  params: 
+    action: report
+- parent: warehouse_reports
+  id: warehouse_reports_whjournal
+  name: WHJournal
+  order: 200
+  access: warehouse_management
+  module: wh.pl
+  params: 
+    action: journal
+- id: general_ledger
+  name: General Ledger
+  icon: gl
+  order: 500
+- parent: general_ledger
+  id: general_ledger_add_transaction
+  name: Add Transaction
+  icon: transaction_add
+  order: 100
+  access: general_ledger
+  module: gl.pl
+  params: 
+    action: add
+- parent: general_ledger
+  id: general_ledger_add_ar_transaction
+  name: Add AR Transaction
+  icon: ar_transaction_add
+  order: 200
+  access: general_ledger
+  module: ar.pl
+  params: 
+    action: add
+- parent: general_ledger
+  id: general_ledger_add_ap_transaction
+  name: Add AP Transaction
+  icon: ap_transaction_add
+  order: 300
+  access: general_ledger
+  module: ap.pl
+  params: 
+    action: add
+- parent: general_ledger
+  id: general_ledger_datev_export_assistent
+  name: DATEV - Export Assistent
+  icon: datev
+  order: 400
+  access: datev_export
+  module: datev.pl
+  params: 
+    action: export
+- parent: general_ledger
+  id: general_ledger_reports
+  name: Reports
+  icon: gl_report
+  order: 500
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: general_ledger_reports
+  id: general_ledger_reports_ar_aging
+  name: AR Aging
+  icon: ar_aging
+  order: 100
+  access: general_ledger
+  module: rp.pl
+  params: 
+    action: report
+    report: ar_aging
+- parent: general_ledger_reports
+  id: general_ledger_reports_ap_aging
+  name: AP Aging
+  icon: ap_aging
+  order: 200
+  access: general_ledger
+  module: rp.pl
+  params: 
+    action: report
+    report: ap_aging
+- parent: general_ledger_reports
+  id: general_ledger_reports_journal
+  name: Journal
+  icon: journal
+  order: 300
+  access: general_ledger
+  module: gl.pl
+  params: 
+    action: search
+- id: cash
+  name: Cash
+  icon: cash
+  order: 600
+- parent: cash
+  id: cash_receipt
+  name: Receipt
+  icon: receipt
+  order: 100
+  access: cash
+  module: cp.pl
+  params: 
+    action: payment
+    vc: customer
+    type: receipt
+- parent: cash
+  id: cash_payment
+  name: Payment
+  icon: payment
+  order: 200
+  access: cash
+  module: cp.pl
+  params: 
+    action: payment
+    vc: vendor
+    type: check
+- parent: cash
+  id: cash_bank_collection_via_sepa
+  name: Bank collection via SEPA
+  order: 300
+  access: cash
+  module: sepa.pl
+  params: 
+    action: bank_transfer_add
+    vc: customer
+- parent: cash
+  id: cash_bank_transfer_via_sepa
+  name: Bank transfer via SEPA
+  order: 400
+  access: cash
+  module: sepa.pl
+  params: 
+    action: bank_transfer_add
+    vc: vendor
+- parent: cash
+  id: cash_bank_import
+  name: Bank Import
+  order: 500
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: cash_bank_import
+  id: cash_bank_import_csv
+  name: CSV
+  order: 100
+  access: bank_transaction
+  params: 
+    action: CsvImport/new
+    profile.type: bank_transactions
+- parent: cash_bank_import
+  id: cash_bank_import_mt940
+  name: MT940
+  order: 200
+  access: bank_transaction
+  params: 
+    action: BankImport/upload_mt940
+- parent: cash
+  id: cash_bank_transactions_mt940
+  name: Bank transactions MT940
+  order: 600
+  access: bank_transaction
+  params: 
+    action: BankTransaction/search
+- parent: cash
+  id: cash_reconciliation_with_bank
+  name: Reconciliation with bank
+  order: 700
+  access: bank_transaction
+  params: 
+    action: Reconciliation/search
+    next_sub: Reconciliation/reconciliation
+- parent: cash
+  id: cash_reconciliation
+  name: Reconciliation
+  icon: reconcilliation
+  order: 800
+  access: cash
+  module: rc.pl
+  params: 
+    action: reconciliation
+- parent: cash
+  id: cash_reports
+  name: Reports
+  icon: cash_report
+  order: 900
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: cash_reports
+  id: cash_reports_receipts
+  name: Receipts
+  icon: receipt_report
+  order: 100
+  access: cash
+  module: rp.pl
+  params: 
+    action: report
+    report: receipts
+- parent: cash_reports
+  id: cash_reports_payments
+  name: Payments
+  icon: payment_report
+  order: 200
+  access: cash
+  module: rp.pl
+  params: 
+    action: report
+    report: payments
+- parent: cash_reports
+  id: cash_reports_bank_collections_via_sepa
+  name: Bank collections via SEPA
+  order: 300
+  access: cash
+  module: sepa.pl
+  params: 
+    action: bank_transfer_search
+    vc: customer
+- parent: cash_reports
+  id: cash_reports_bank_transfers_via_sepa
+  name: Bank transfers via SEPA
+  order: 400
+  access: cash
+  module: sepa.pl
+  params: 
+    action: bank_transfer_search
+    vc: vendor
+- parent: cash_reports
+  id: cash_reports_bank_transactions
+  name: Bank transactions
+  order: 500
+  access: bank_transaction
+  params: 
+    action: BankTransaction/list_all
+- id: reports
+  name: Reports
+  icon: report
+  order: 700
+- parent: reports
+  id: reports_chart_of_accounts
+  name: Chart of Accounts
+  icon: chart_of_accounts
+  order: 100
+  access: report
+  module: ca.pl
+  params: 
+    action: chart_of_accounts
+- parent: reports
+  id: reports_trial_balance
+  name: Trial Balance
+  order: 200
+  access: report
+  module: rp.pl
+  params: 
+    action: report
+    report: trial_balance
+- parent: reports
+  id: reports_income_statement
+  name: Income Statement
+  icon: income_statement
+  order: 300
+  access: report
+  module: rp.pl
+  params: 
+    action: report
+    report: income_statement
+- parent: reports
+  id: reports_bwa
+  name: BWA
+  order: 400
+  access: report
+  module: rp.pl
+  params: 
+    action: report
+    report: bwa
+- parent: reports
+  id: reports_balance_sheet
+  name: Balance Sheet
+  icon: balance_sheet
+  order: 500
+  access: report
+  module: rp.pl
+  params: 
+    action: report
+    report: balance_sheet
+- parent: reports
+  id: reports_ustva
+  name: UStVa
+  icon: ustva
+  order: 600
+  access: advance_turnover_tax_return
+  module: ustva.pl
+  params: 
+    action: report
+- parent: reports
+  id: reports_projecttransactions
+  name: Projecttransactions
+  order: 700
+  access: report
+  module: rp.pl
+  params: 
+    action: report
+    report: projects
+- parent: reports
+  id: reports_financial_overview
+  name: Financial Overview
+  order: 800
+  access: report
+  params: 
+    action: FinancialOverview/list
+- parent: reports
+  id: reports_liquidity_projection
+  name: Liquidity projection
+  order: 900
+  access: report
+  params: 
+    action: LiquidityProjection/show
+- id: batch_printing
+  name: Batch Printing
+  icon: printing
+  order: 800
+  access: batch_printing
+- parent: batch_printing
+  id: batch_printing_sales_invoices
+  name: Sales Invoices
+  icon: sales_invoice_printing
+  order: 100
+  access: invoice_edit
+  module: bp.pl
+  params: 
+    action: search
+    vc: customer
+    type: invoice
+- parent: batch_printing
+  id: batch_printing_sales_orders
+  name: Sales Orders
+  icon: sales_order_printing
+  order: 200
+  access: sales_order_edit
+  module: bp.pl
+  params: 
+    action: search
+    type: sales_order
+    vc: customer
+- parent: batch_printing
+  id: batch_printing_quotations
+  name: Quotations
+  icon: quotation_printing
+  order: 300
+  access: sales_quotation_edit
+  module: bp.pl
+  params: 
+    action: search
+    vc: customer
+    type: sales_quotation
+- parent: batch_printing
+  id: batch_printing_packing_lists
+  name: Packing Lists
+  icon: package_lists
+  order: 400
+  access: invoice_edit | sales_order_edit
+  module: bp.pl
+  params: 
+    action: search
+    type: packing_list
+    vc: customer
+- parent: batch_printing
+  id: batch_printing_purchase_orders
+  name: Purchase Orders
+  icon: purchase_order_printing
+  order: 500
+  access: purchase_order_edit
+  module: bp.pl
+  params: 
+    action: search
+    type: purchase_order
+    vc: vendor
+- parent: batch_printing
+  id: batch_printing_rfqs
+  name: RFQs
+  icon: rfq_printing
+  order: 600
+  access: request_quotation_edit
+  module: bp.pl
+  params: 
+    action: search
+    vc: vendor
+    type: request_quotation
+- parent: batch_printing
+  id: batch_printing_checks
+  name: Checks
+  order: 700
+  access: cash
+  module: bp.pl
+  params: 
+    action: search
+    type: check
+    vc: vendor
+- parent: batch_printing
+  id: batch_printing_receipts
+  name: Receipts
+  icon: receipt_printing
+  order: 800
+  access: cash
+  module: bp.pl
+  params: 
+    action: search
+    vc: customer
+    type: receipt
+- id: productivity
+  name: Productivity
+  icon: productivity
+  order: 900
+  access: productivity
+- parent: productivity
+  id: productivity_show_todo_list
+  name: Show TODO list
+  order: 100
+  module: todo.pl
+  params: 
+    action: show_todo_list
+- parent: productivity
+  id: productivity_add_follow_up
+  name: Add Follow-Up
+  order: 200
+  module: fu.pl
+  params: 
+    action: add
+- parent: productivity
+  id: productivity_edit_access_rights
+  name: Edit Access Rights
+  order: 300
+  module: fu.pl
+  params: 
+    action: edit_access_rights
+- parent: productivity
+  id: productivity_reports
+  name: Reports
+  order: 400
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: productivity_reports
+  id: productivity_reports_follow_ups
+  name: Follow-Ups
+  order: 100
+  module: fu.pl
+  params: 
+    action: search
+- id: system
+  name: System
+  icon: system
+  order: 1000
+  access: config
+- parent: system
+  id: system_client_configuration
+  name: Client Configuration
+  order: 100
+  access: admin
+  params: 
+    action: ClientConfig/edit
+- parent: system
+  id: system_ustva_einstellungen
+  name: UStVa Einstellungen
+  order: 200
+  module: ustva.pl
+  params: 
+    action: config_step1
+- parent: system
+  id: system_edit_dunning
+  name: Edit Dunning
+  order: 300
+  module: dn.pl
+  params: 
+    action: edit_config
+- parent: system
+  id: system_chart_of_accounts
+  name: Chart of Accounts
+  order: 400
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: system_chart_of_accounts
+  id: system_chart_of_accounts_add_account
+  name: Add Account
+  order: 100
+  module: am.pl
+  params: 
+    action: add_account
+- parent: system_chart_of_accounts
+  id: system_chart_of_accounts_list_accounts
+  name: List Accounts
+  order: 200
+  module: am.pl
+  params: 
+    action: list_account
+- parent: system
+  id: system_buchungsgruppen
+  name: Buchungsgruppen
+  order: 500
+  params: 
+    action: Buchungsgruppen/list
+- parent: system
+  id: system_taxzones
+  name: Taxzones
+  order: 600
+  params: 
+    action: Taxzones/list
+- parent: system
+  id: system_taxes
+  name: Taxes
+  order: 700
+  module: am.pl
+  params: 
+    action: list_tax
+- parent: system
+  id: system_bank_accounts
+  name: Bank accounts
+  order: 800
+  params: 
+    action: BankAccount/list
+- parent: system
+  id: system_groups
+  name: Groups
+  order: 900
+  module: pe.pl
+  params: 
+    action: search
+    type: partsgroup
+- parent: system
+  id: system_pricegroups
+  name: Pricegroups
+  order: 1000
+  module: pe.pl
+  params: 
+    action: search
+    type: pricegroup
+- parent: system
+  id: system_edit_units
+  name: Edit units
+  order: 1100
+  module: am.pl
+  params: 
+    action: edit_units
+- parent: system
+  id: system_price_factors
+  name: Price Factors
+  order: 1200
+  module: am.pl
+  params: 
+    action: list_price_factors
+- parent: system
+  id: system_departments
+  name: Departments
+  order: 1300
+  params: 
+    action: Department/list
+- parent: system
+  id: system_types_of_business
+  name: Types of Business
+  order: 1400
+  params: 
+    action: Business/list
+- parent: system
+  id: system_leads
+  name: Leads
+  order: 1500
+  module: am.pl
+  params: 
+    action: list_lead
+- parent: system
+  id: system_project_types
+  name: Project Types
+  order: 1600
+  params: 
+    action: ProjectType/list
+- parent: system
+  id: system_project_status
+  name: Project Status
+  order: 1700
+  params: 
+    action: ProjectStatus/list
+- parent: system
+  id: system_requirement_specs
+  name: Requirement specs
+  order: 1800
+  module: menu.pl
+  target: acc_menu
+  params: 
+    action: acc_menu
+- parent: system_requirement_specs
+  id: system_requirement_specs_pre_defined_texts
+  name: Pre-defined Texts
+  order: 100
+  params: 
+    action: RequirementSpecPredefinedText/list
+- parent: system_requirement_specs
+  id: system_requirement_specs_requirement_spec_types
+  name: Requirement Spec Types
+  order: 200
+  params: 
+    action: RequirementSpecType/list
+- parent: system_requirement_specs
+  id: system_requirement_specs_requirement_spec_statuses
+  name: Requirement Spec Statuses
+  order: 300
+  params: 
+    action: RequirementSpecStatus/list
+- parent: system_requirement_specs
+  id: system_requirement_specs_complexities
+  name: Complexities
+  order: 400
+  params: 
+    action: RequirementSpecComplexity/list
+- parent: system_requirement_specs
+  id: system_requirement_specs_risks
+  name: Risks
+  order: 500
+  params: 
+    action: RequirementSpecRisk/list
+- parent: system_requirement_specs
+  id: system_requirement_specs_acceptance_statuses
+  name: Acceptance Statuses
+  order: 600
+  params: 
+    action: RequirementSpecAcceptanceStatus/list
+- parent: system
+  id: system_languages_and_translations
+  name: Languages and translations
+  order: 1900
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: system_languages_and_translations
+  id: system_languages_and_translations_add_language
+  name: Add Language
+  order: 100
+  module: am.pl
+  params: 
+    action: add_language
+- parent: system_languages_and_translations
+  id: system_languages_and_translations_list_languages
+  name: List Languages
+  order: 200
+  module: am.pl
+  params: 
+    action: list_language
+- parent: system_languages_and_translations
+  id: system_languages_and_translations_greetings
+  name: Greetings
+  order: 300
+  module: generictranslations.pl
+  params: 
+    action: edit_greetings
+- parent: system_languages_and_translations
+  id: system_languages_and_translations_sepa_strings
+  name: SEPA strings
+  order: 400
+  module: generictranslations.pl
+  params: 
+    action: edit_sepa_strings
+- parent: system
+  id: system_payment_terms
+  name: Payment Terms
+  order: 2000
+  params: 
+    action: PaymentTerm/list
+- parent: system
+  id: system_delivery_terms
+  name: Delivery Terms
+  order: 2100
+  params: 
+    action: DeliveryTerm/list
+- parent: system
+  id: system_manage_custom_variables
+  name: Manage Custom Variables
+  order: 2200
+  params: 
+    action: CustomVariableConfig/list
+- parent: system
+  id: system_warehouses
+  name: Warehouses
+  order: 2300
+  module: am.pl
+  params: 
+    action: list_warehouses
+- parent: system
+  id: system_import_csv
+  name: Import CSV
+  order: 2400
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: system_import_csv
+  id: system_import_csv_customers_and_vendors
+  name: Customers and vendors
+  order: 100
+  params: 
+    action: CsvImport/new
+    profile.type: customers_vendors
+- parent: system_import_csv
+  id: system_import_csv_contacts
+  name: Contacts
+  order: 200
+  params: 
+    action: CsvImport/new
+    profile.type: contacts
+- parent: system_import_csv
+  id: system_import_csv_shipto
+  name: Shipto
+  order: 300
+  params: 
+    action: CsvImport/new
+    profile.type: addresses
+- parent: system_import_csv
+  id: system_import_csv_parts
+  name: Parts
+  order: 400
+  params: 
+    action: CsvImport/new
+    profile.type: parts
+- parent: system_import_csv
+  id: system_import_csv_inventories
+  name: Inventories
+  order: 500
+  params: 
+    action: CsvImport/new
+    profile.type: inventories
+- parent: system_import_csv
+  id: system_import_csv_projects
+  name: Projects
+  order: 600
+  params: 
+    action: CsvImport/new
+    profile.type: projects
+- parent: system_import_csv
+  id: system_import_csv_orders
+  name: Orders
+  order: 700
+  params: 
+    action: CsvImport/new
+    profile.type: orders
+- parent: system
+  id: system_templates
+  name: Templates
+  order: 2500
+  access: admin
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: system_templates
+  id: system_templates_html_templates
+  name: HTML Templates
+  order: 100
+  module: amtemplates.pl
+  params: 
+    action: display_template_form
+    type: templates
+    format: html
+- parent: system_templates
+  id: system_templates_latex_templates
+  name: LaTeX Templates
+  order: 200
+  module: amtemplates.pl
+  params: 
+    action: display_template_form
+    format: tex
+    type: templates
+- parent: system_templates
+  id: system_templates_stylesheet
+  name: Stylesheet
+  order: 300
+  module: amtemplates.pl
+  params: 
+    action: display_template_form
+    type: stylesheet
+- parent: system
+  id: system_general_ledger_corrections
+  name: General Ledger Corrections
+  order: 2600
+  module: acctranscorrections.pl
+  params: 
+    action: analyze_filter
+- parent: system
+  id: system_background_jobs_and_task_server
+  name: Background jobs and task server
+  order: 2700
+  access: admin
+  module: menu.pl
+  params: 
+    action: acc_menu
+- parent: system_background_jobs_and_task_server
+  id: system_background_jobs_and_task_server_list_current_background_jobs
+  name: List current background jobs
+  order: 100
+  params: 
+    action: BackgroundJob/list
+- parent: system_background_jobs_and_task_server
+  id: system_background_jobs_and_task_server_background_job_history
+  name: Background job history
+  order: 200
+  params: 
+    action: BackgroundJobHistory/list
+- parent: system_background_jobs_and_task_server
+  id: system_background_jobs_and_task_server_task_server_control
+  name: Task server control
+  order: 300
+  params: 
+    action: TaskServer/show
+- parent: system
+  id: system_audit_control
+  name: Audit Control
+  order: 2800
+  module: am.pl
+  params: 
+    action: audit_control
+- parent: system
+  id: system_history_search_engine
+  name: History Search Engine
+  order: 2900
+  module: am.pl
+  params: 
+    action: show_history_search
+- parent: system
+  id: system_employees
+  name: Employees
+  order: 3000
+  access: admin
+  params: 
+    action: Employee/list
+- id: program
+  name: Program
+  icon: program
+  order: 1100
+- parent: program
+  id: program_user_preferences
+  name: User Preferences
+  order: 100
+  module: am.pl
+  params: 
+    action: config
+- parent: program
+  id: program_internal_phone_list
+  name: Internal Phone List
+  order: 200
+  params: 
+    action: CTI/list_internal_extensions
+- parent: program
+  id: program_version
+  name: Version
+  icon: version
+  order: 300
+  module: login.pl
+  params: 
+    action: company_logo
+    no_todo_list: 1
+- parent: program
+  id: program_administration_area
+  name: Administration area
+  order: 400
+  access: display_admin_link
+  params: 
+    action: Admin/login
+- parent: program
+  id: program_documentation_in_german_
+  name: Documentation (in German)
+  order: 500
+  href: doc/kivitendo-Dokumentation.pdf
+  target: _blank
+- parent: program
+  id: program_kivitendo_website_external_
+  name: kivitendo website (external)
+  order: 600
+  href: http://www.kivitendo.de/
+  target: _blank
+- parent: program
+  id: program_logout
+  name: Logout
+  icon: logout
+  order: 700
+  params: 
+    action: LoginScreen/logout
diff --git a/menus/user/10-crm.yaml b/menus/user/10-crm.yaml
new file mode 100644 (file)
index 0000000..c093cd9
--- /dev/null
@@ -0,0 +1,254 @@
+---
+- id: crm
+  name: CRM
+  icon: crm
+  order: 50
+- parent: crm
+  id: crm_search
+  name: Search
+  icon: search
+  order: 100
+  access: crm_search
+  module: crm/getData.php
+- parent: crm
+  id: crm_add
+  name: Add
+  order: 200
+- parent: crm_add
+  id: crm_add_customer
+  name: Customer
+  icon: customer
+  order: 100
+  access: crm_new
+  module: crm/firmen3.php
+  params: 
+    Q: C
+- parent: crm_add
+  id: crm_add_vendor
+  name: Vendor
+  icon: vendor
+  order: 200
+  access: crm_new
+  module: crm/firmen3.php
+  params: 
+    Q: V
+- parent: crm_add
+  id: crm_add_person
+  name: Person
+  icon: contact
+  order: 300
+  access: crm_new
+  module: crm/personen3.php
+- parent: crm
+  id: crm_appointments
+  name: Appointments
+  icon: appointment
+  order: 300
+  access: crm_termin
+  module: crm/termin.php
+- parent: crm
+  id: crm_opportunity
+  name: Opportunity
+  icon: opportunity
+  order: 400
+  access: crm_opportunity
+  module: crm/opportunity.php
+- parent: crm
+  id: crm_follow_up
+  name: Follow-Up
+  icon: follow_up
+  order: 500
+  access: crm_follow
+  module: crm/wvl1.php
+- parent: crm
+  id: crm_e_mail
+  name: E-mail
+  icon: email
+  order: 600
+  access: crm_email
+  module: crm/mail.php
+- parent: crm
+  id: crm_knowledge
+  name: Knowledge
+  icon: knowledge
+  order: 700
+  access: crm_knowhow
+  module: crm/wissen.php
+- parent: crm
+  id: crm_memo
+  name: Memo
+  icon: memo
+  order: 800
+  access: crm_notices
+  module: crm/postit.php
+- parent: crm
+  id: crm_documents
+  name: Documents
+  order: 900
+  access: crm_other
+  module: crm/dokument.php
+- parent: crm
+  id: crm_time_tracking
+  name: Time Tracking
+  order: 1000
+  access: crm_service
+  module: crm/timetrack.php
+- parent: crm
+  id: crm_other
+  name: Other
+  order: 1100
+- parent: crm_other
+  id: crm_other_etikett
+  name: Etikett
+  order: 100
+  access: crm_other
+  module: crm/prtetikett.php
+  target: _blank
+- parent: crm_other
+  id: crm_other_dhl
+  name: DHL
+  order: 200
+  access: crm_other
+  module: crm/dhl.php
+- parent: crm_other
+  id: crm_other_ebayimporter
+  name: eBayImporter
+  order: 300
+  access: crm_other
+  module: crm/ebayImporter.php
+- parent: crm_other
+  id: crm_other_catalog
+  name: Catalog
+  order: 400
+  access: crm_other
+  module: crm/katalog.php
+- parent: crm_other
+  id: crm_other_warehouse_list
+  name: Warehouse list
+  order: 500
+  access: crm_other
+  module: crm/inventur.php
+- parent: crm_other
+  id: crm_other_warehouse_correction
+  name: Warehouse correction
+  order: 600
+  access: crm_admin
+  module: crm/inventurlager.php
+- parent: crm_other
+  id: crm_other_partsedit
+  name: Partsedit
+  order: 700
+  access: crm_other
+  module: crm/partsedit.php
+- parent: crm_other
+  id: crm_other_packliste
+  name: Packliste
+  order: 800
+  access: crm_other
+  module: crm/packliste.php
+- parent: crm_other
+  id: crm_other_eur
+  name: EuR
+  order: 900
+  access: crm_other
+  module: crm/eur.php
+- parent: crm_other
+  id: crm_other_zm
+  name: ZM
+  order: 1000
+  access: crm_other
+  module: crm/ustva_zm.php
+- parent: crm
+  id: crm_service
+  name: Service
+  icon: service
+  order: 1200
+- parent: crm_service
+  id: crm_service_service_contract
+  name: Service Contract
+  order: 100
+  access: crm_service
+  module: crm/vertrag1.php
+- parent: crm_service
+  id: crm_service_add_service_contract
+  name: Add Service Contract
+  order: 200
+  access: crm_service
+  module: crm/vertrag3.php
+- parent: crm_service
+  id: crm_service_machine
+  name: Machine
+  order: 300
+  access: crm_service
+  module: crm/maschine1.php
+- parent: crm_service
+  id: crm_service_add_machine
+  name: Add Machine
+  order: 400
+  access: crm_service
+  module: crm/maschine3.php
+- parent: crm
+  id: crm_admin
+  name: Admin
+  icon: admin
+  order: 1300
+- parent: crm_admin
+  id: crm_admin_document_template
+  name: Document Template
+  icon: document_template
+  order: 100
+  access: crm_admin
+  module: crm/dokument1.php
+- parent: crm_admin
+  id: crm_admin_label
+  name: Label
+  icon: label
+  order: 200
+  access: crm_admin
+  module: crm/aufkleber_def.php
+- parent: crm_admin
+  id: crm_admin_appointment_category
+  name: Appointment Category
+  order: 300
+  access: crm_admin
+  module: crm/tcatedit.php
+- parent: crm_admin
+  id: crm_admin_message
+  name: Message
+  icon: message
+  order: 400
+  access: crm_admin
+  module: crm/user3.php
+- parent: crm_admin
+  id: crm_admin_client
+  name: Client
+  order: 500
+  access: crm_adminstatus
+  module: crm/mandant.php
+- parent: crm_admin
+  id: crm_admin_user_groups
+  name: User Groups
+  icon: user_group
+  order: 600
+  access: crm_admin
+  module: crm/user2.php
+- parent: crm_admin
+  id: crm_admin_user
+  name: User
+  icon: user
+  order: 700
+  access: crm_adminuser
+  module: crm/user1.php
+- parent: crm_admin
+  id: crm_admin_dhl
+  name: DHL
+  order: 800
+  access: crm_adminuser
+  module: crm/dhladm.php
+- parent: crm_admin
+  id: crm_admin_status
+  name: Status
+  icon: status
+  order: 900
+  access: crm_adminstatus
+  module: crm/status.php
index b192714..ec9624a 100755 (executable)
@@ -25,6 +25,8 @@ use IO::Dir;
 use List::MoreUtils qw(apply);
 use List::Util qw(first);
 use Pod::Usage;
+use YAML ();
+use YAML::Loader (); # YAML tries to load Y:L at runtime, but can't find it after we chdir'ed
 
 $OUTPUT_AUTOFLUSH = 1;
 
@@ -41,7 +43,7 @@ my $basedir      = "../..";
 my $locales_dir  = ".";
 my $bindir       = "$basedir/bin/mozilla";
 my @progdirs     = ( "$basedir/SL" );
-my @menufiles    = <${basedir}/menus/*.ini>;
+my @menufiles    = <"${basedir}/menus/*/*">;
 my @javascript_dirs = ($basedir .'/js', $basedir .'/templates/webpages');
 my $javascript_output_dir = $basedir .'/js';
 my $submitsearch = qr/type\s*=\s*[\"\']?submit/i;
@@ -98,13 +100,6 @@ push @progfiles, map { m:^(.+)/([^/]+)$:; [ $2, $1 ] } grep { /\.pm$/ } map { fi
 # put customized files into @customfiles
 my %dir_h;
 
-if ($opt_n) {
-  @customfiles = ();
-} else {
-  tie %dir_h, 'IO::Dir', $basedir;
-  push @menufiles, map { "$basedir/$_" } grep { /.*_menu.ini$/ } keys %dir_h;
-}
-
 my @dbplfiles;
 foreach my $sub_dir ("Pg-upgrade2", "Pg-upgrade2-auth") {
   my $dir = "$basedir/sql/$sub_dir";
@@ -520,24 +515,13 @@ sub scanfile {
 sub scanmenu {
   my $file = shift;
 
-  my $fh = new FileHandle;
-  open $fh, '<:encoding(utf8)', $file or die "$! : $file";
-
-  my @a = grep m/^\[/, <$fh>;
-  close($fh);
+  print STDERR "trying to load file $file\n";
+  my $menu = YAML::LoadFile($file);
 
-  # strip []
-  grep { s/(\[|\])//g } @a;
-
-  foreach my $item (@a) {
-    my @b = split /--/, $item;
-    foreach my $string (@b) {
-      chomp $string;
-      $locale{$string}     = 1;
-      $alllocales{$string} = 1;
-    }
+  for my $node (@$menu) {
+    $locale{$node->{name}}     = 1;
+    $alllocales{$node->{name}} = 1;
   }
-
 }
 
 sub unescape_template_string {
@@ -780,7 +764,7 @@ Be more verbose.
 
 =head1 DESCRIPTION
 
-This script collects strings from Perl files, the menu.ini file and
+This script collects strings from Perl files, the menu files and
 HTML templates and puts them into the file "all" for translation.
 
 =cut
index af295c7..a0cdae8 100644 (file)
@@ -105,7 +105,59 @@ sub translate_to_yaml {
   }
 
   open my $out_file, '>:utf8', $new_file or die $!;
-  print $out_file YAML::Dump(\@menu_items);
+  print $out_file yaml_dump(\@menu_items);
+}
+
+sub yaml_dump {
+  my ($ary_ref) = @_;
+  # YAML dumps keys lexically sorted, which isn't what we want.
+  # we want this order:
+  my @order = qw(
+    parent
+    id
+    name
+    icon
+    order
+    access
+    href
+    module
+    target
+    params
+  );
+
+  # ...oh and we want action in params first
+  #
+  # why this? because:
+  # 1. parent is what is used to anchor. one could argue that id should be
+  #    first, but parent is easier for understanding structure.
+  # 2. after parent the logical structure is
+  #    1. id
+  #    2. stuff related to vidual presentation (name/icon)
+  #    3. stuff needed for logical presentaion (order/access)
+  #    4. stuff related to the action after clicking it
+  # 3. without parent and href (the second is pretty rare) the keys are nicely
+  #    ascending in length, which is very easy to parse visually.
+
+  my $yaml = "---\n";
+  for my $node (@$ary_ref) {
+    my $first = 0;
+    for my $key (@order) {
+      next unless exists $node->{$key};
+      $yaml .= ($first++ ? '  ' : '- ') . $key . ": ";
+      if (!ref $node->{$key}) {
+        $yaml .= $node->{$key} . "\n";
+      } else {
+        $yaml .= "\n";
+        for ('action', grep !/^action$/, keys %{ $node->{$key} }) {
+          next unless exists $node->{$key}{$_};
+          $yaml .= "    $_: $node->{$key}{$_}\n";
+        }
+      }
+
+    }
+  }
+
+  $yaml;
 }
 
 while (my ($in, $out) = each(%menu_files)) {
index fe5eb21..70537e1 100644 (file)
@@ -1,33 +1,31 @@
 [%- USE T8 %]
-[% USE HTML %][%- USE LxERP -%]
+[%- USE L %]
+[%- USE HTML %]
+[%- USE LxERP -%]
  <div id="main_menu_div"></div>
-
  [%- SET main_id = '100' %]
  <ul id="main_menu_model"  style='display:none'>
- [%- FOREACH mainitem = menu_items %]
+ [%- FOREACH node = menu.tree %]
+  [% NEXT UNLESS node.visible %]
   [%- SET main_id = main_id + 1 %]
-  <li id="[% main_id %]"[% IF mainitem.image %] itemIcon="[% mainitem.image %]"[% END %]>
-   <a href="[% IF mainitem.href %][% mainitem.href %][% ELSE %]#[% END %]"[% IF mainitem.target && (mainitem.target != 'main_window') %] target="[% HTML.escape(mainitem.target) %]"[% END %]>
-    [%- HTML.escape(mainitem.title) %]
-   </a>
-   [%- IF mainitem.subitems %]
-    <ul[%- IF force_ul_width %] width="[% mainitem.max_width * 10 %]"[% END %]>
+  <li id="[% main_id %]"[% IF icon_path(node.icon) %] itemIcon="[% icon_path(node.icon) %]"[% END %]>
+   [% L.link(menu.href_for_node(node) || '#', menu.name_for_node(node), target=node.target) %]
+   [%- IF node.children %]
+    <ul width="[% max_width(node) %]">
      [%- SET sub1_id = main_id * 100 %]
-     [%- FOREACH sub1item = mainitem.subitems %]
+     [%- FOREACH sub1node = node.children %]
+      [% NEXT UNLESS sub1node.visible %]
       [%- SET sub1_id = sub1_id + 1 %]
-      <li id="[% sub1_id %]"[% IF sub1item.image %] itemIcon="[% sub1item.image %]"[% END %]>
-       <a href="[% IF sub1item.href %][% sub1item.href %][% ELSE %]#[% END %]"[% IF sub1item.target && (sub1item.target != 'main_window') %] target="[% HTML.escape(sub1item.target) %]"[% END %]>
-        [%- HTML.escape(sub1item.title) %]
-       </a>
-       [%- IF sub1item.subitems %]
-        <ul[%- IF force_ul_width %] width="[% sub1item.max_width * 10 %]"[% END %]>
+      <li id="[% sub1_id %]"[% IF icon_path(sub1node.icon) %] itemIcon="[% icon_path(sub1node.icon) %]"[% END %]>
+       [% L.link(menu.href_for_node(sub1node) || '#', menu.name_for_node(sub1node), target=sub1node.target) %]
+       [%- IF sub1item.children %]
+        <ul width="[% max_width(sub1node) %]">
          [%- SET sub2_id = sub1_id * 100 %]
-         [%- FOREACH sub2item = sub1item.subitems %]
+         [%- FOREACH sub2node = sub1node.children %]
+          [% NEXT UNLESS sub2node.visible %]
           [%- SET sub2_id = sub2_id + 1 %]
-          <li id="[% sub2_id %]"[% IF sub2item.image %] itemIcon="[% sub2item.image %]"[% END %]>
-           <a href="[% IF sub2item.href %][% sub2item.href %][% ELSE %]#[% END %]"[% IF sub2item.target && (sub2item.target != 'main_window') %] target="[% HTML.escape(sub2item.target) %]"[% END %]>
-            [%- HTML.escape(sub2item.title) %]
-           </a>
+          <li id="[% sub2_id %]"[% IF icon_path(sub2item.icon) %] itemIcon="[% icon_path(sub2item.icon) %]"[% END %]>
+            [% L.link(menu.href_for_node(sub2node) || '#', menu.name_for_node(sub2node), target=sub2node.target) %]
           </li>
          [%- END %]
         </ul>
   </li>
  [%- END %]
  </ul>
-
- <script type="text/javascript">
-  <!--
-
-$(function(){
-  DHTMLSuite.createStandardObjects();
-
-  DHTMLSuite.configObj.setImagePath('image/dhtmlsuite/');
-
-  var menu_model = new DHTMLSuite.menuModel();
-  menu_model.addItemsFromMarkup('main_menu_model');
-  menu_model.init();
-
-  var menu_bar = new DHTMLSuite.menuBar();
-  menu_bar.addMenuItems(menu_model);
-  menu_bar.setTarget('main_menu_div');
-  menu_bar.init();
-});
-
-
-
-function open_url(url, target) {
-
-}
-
-    -->
- </script>
index 648e49a..8d045f8 100644 (file)
@@ -1,2 +1,12 @@
- <div id="menuv3">[% menu %]</div>
- <div style="clear: both;"></div>
+[%- USE L %]
+[%- USE HTML %]
+<div id="menuv3">[% FOREACH node = menu.tree %][% NEXT UNLESS node.visible %]<ul><li>[% PROCESS submenu top=1 %]</li></ul>[% END %]</div>
+<div style="clear: both;"></div>
+[%- %]
+[%- BLOCK submenu %]
+  [%- IF top              %]<h2>[% menu.name_for_node(node) | html %]</h2>
+  [%- ELSIF node.children %]<div class="x">[% menu.name_for_node(node) | html %]</div>
+  [%- ELSE                %][% L.link(menu.href_for_node(node), menu.name_for_node(node), title=menu.name_for_node(node), target=node.target) %]
+  [%- END %]
+  [%- IF node.children %]<ul>[%- FOREACH node = node.children %][% NEXT UNLESS node.visible %]<li>[% PROCESS submenu top=0 %]</li>[% END %]</ul>[%- END %]
+[%- END %]