commit 7c54e92fc42214744a82cec3f648d0e044ede238
Author: Moritz Bunkus <m.bunkus@linet.de>
Date:   Mon Jan 12 16:45:06 2015 +0100

    Pflichtenhefte: Ausgabemöglichkeit als HTML
    
    Menüpunkte dafür werden aber nur eingeblendet, sofern auch eine Vorlage
    dafür im Vorlagenverzeichnis existiert.
    
    Die erzeugte HTML-Seite wird in einem neuen Browserfenster angezeigt.

diff --git a/SL/Controller/RequirementSpec.pm b/SL/Controller/RequirementSpec.pm
index d65ce8926..3158cd09a 100644
--- a/SL/Controller/RequirementSpec.pm
+++ b/SL/Controller/RequirementSpec.pm
@@ -21,6 +21,7 @@ use SL::DB::RequirementSpecRisk;
 use SL::DB::RequirementSpecStatus;
 use SL::DB::RequirementSpecType;
 use SL::DB::RequirementSpec;
+use SL::Helper::CreatePDF qw();
 use SL::Helper::Flash;
 use SL::Locale::String;
 use SL::Template::LaTeX;
@@ -29,7 +30,7 @@ use Rose::Object::MakeMethods::Generic
 (
   scalar                  => [ qw(requirement_spec_item visible_item visible_section) ],
   'scalar --get_set_init' => [ qw(requirement_spec customers types statuses complexities risks projects project_types project_statuses default_project_type default_project_status copy_source js
-                                  current_text_block_output_position models time_based_units) ],
+                                  current_text_block_output_position models time_based_units html_template) ],
 );
 
 __PACKAGE__->run_before('setup');
@@ -231,6 +232,27 @@ sub action_create_pdf {
   unlink $result{file_name};
 }
 
+sub action_create_html {
+  my ($self, %params) = @_;
+
+  my $base_name       = $self->requirement_spec->type->template_file_name || 'requirement_spec';
+  my @pictures        = $self->prepare_pictures_for_printing;
+  my $content         = SL::Helper::CreatePDF->create_parsed_file(
+    template      => "${base_name}.html",
+    format        => 'html',
+    template_type => 'HTML',
+    variables     => {
+      SELF        => $self,
+      rspec       => $self->requirement_spec,
+    });
+
+  # $content is now a scalar of bytes, but $self->render() expects a
+  # scalar of characters.
+  $content = Encode::decode('utf-8', $content);
+
+  $self->render(\$content, { layout => 0, process => 0 });
+}
+
 sub action_select_template_to_paste {
   my ($self) = @_;
 
@@ -613,4 +635,11 @@ sub init_models {
   );
 }
 
+sub init_html_template {
+  my ($self)    = @_;
+  my $base_name = $self->requirement_spec->type->template_file_name || 'requirement_spec';
+  my $template  = SL::Helper::CreatePDF->find_template(name => $base_name, extension => 'html');
+  return !!$template;
+}
+
 1;
diff --git a/css/requirement_spec.css b/css/requirement_spec.css
index 123713398..9dbd0d693 100644
--- a/css/requirement_spec.css
+++ b/css/requirement_spec.css
@@ -48,6 +48,7 @@ table.rs_input_field input, table.rs_input_field select {
 .context-menu-item.icon-save   { background-image: url("../image/document-save.png"); }
 .context-menu-item.icon-revert { background-image: url("../image/edit-undo.png"); }
 .context-menu-item.icon-pdf    { background-image: url("../image/application-pdf.png"); }
+.context-menu-item.icon-html   { background-image: url("../image/text-html.png"); }
 .context-menu-item.icon-add-picture { background-image: url("../image/add-picture.png"); }
 .context-menu-item.icon-download    { background-image: url("../image/download.png"); }
 .context-menu-item.icon-renumber    { background-image: url("../image/format-list-ordered.png"); }
diff --git a/image/text-html.png b/image/text-html.png
new file mode 100644
index 000000000..4961d34f6
Binary files /dev/null and b/image/text-html.png differ
diff --git a/js/locale/de.js b/js/locale/de.js
index 4b5d5e36a..874ccc90f 100644
--- a/js/locale/de.js
+++ b/js/locale/de.js
@@ -16,6 +16,7 @@ namespace("kivi").setupLocale({
 "Copy requirement spec":"Pflichtenheft kopieren",
 "Copy template":"Vorlage kopieren",
 "Create":"Anlegen",
+"Create HTML":"HTML erzeugen",
 "Create PDF":"PDF erzeugen",
 "Create a new version":"Eine neue Version anlegen",
 "Create new quotation/order":"Neues Angebot/neuen Auftrag anlegen",
diff --git a/js/requirement_spec.js b/js/requirement_spec.js
index cc2f3323b..9e5b85640 100644
--- a/js/requirement_spec.js
+++ b/js/requirement_spec.js
@@ -151,7 +151,7 @@ ns.initialize_requirement_spec = function(data) {
       plugins: [ "themes", "json_data", "ui", "crrm", "dnd" ]
     });
 
-  ns.create_context_menus(data.is_template);
+  ns.create_context_menus(data);
   $('#requirement_spec_tabs').on("tabsbeforeactivate", ns.tabs_before_activate);
 
   ns.time_based_units = data.time_based_units;
@@ -647,6 +647,11 @@ ns.create_reqspec_pdf = function(key, opt) {
   $.download("controller.pl", data);
 };
 
+ns.create_reqspec_html = function(key, opt) {
+  window.open("controller.pl?action=RequirementSpec/create_html&id=" + encodeURIComponent($('#requirement_spec_id').val()), '_blank');
+  return true;
+};
+
 ns.copy_reqspec = function(key, opt) {
   window.location.href = "controller.pl?action=RequirementSpec/new&copy_source_id=" + encodeURIComponent($('#requirement_spec_id').val());
   return true;
@@ -697,6 +702,11 @@ ns.create_pdf_for_versioned_copy_ajax_call = function(key, opt) {
   return true;
 };
 
+ns.create_html_for_versioned_copy_ajax_call = function(key, opt) {
+  window.open("controller.pl?action=RequirementSpec/create_html&id=" + encodeURIComponent(ns.find_versioned_copy_id(opt.$trigger) || $('#requirement_spec_id').val()), '_blank');
+  return true;
+};
+
 ns.revert_to_versioned_copy_ajax_call = function(key, opt) {
   if (!confirm(kivi.t8('Do you really want to revert to this version?')))
     return true;
@@ -862,8 +872,8 @@ ns.tabs_before_activate = function(event, ui) {
 // ----------------------------- context menus -----------------------------
 // -------------------------------------------------------------------------
 
-ns.create_context_menus = function(is_template) {
-  if (is_template) {
+ns.create_context_menus = function(data) {
+  if (data.is_template) {
     var general_actions = {
         sep98:           "---------"
       , general_actions: { name: kivi.t8('Requirement spec template actions'), className: 'context-menu-heading' }
@@ -887,6 +897,7 @@ ns.create_context_menus = function(is_template) {
         sep98:              "---------"
       , general_actions:    { name: kivi.t8('Requirement spec actions'), className: 'context-menu-heading' }
       , create_pdf:         { name: kivi.t8('Create PDF'),              icon: "pdf",    callback: kivi.requirement_spec.create_reqspec_pdf }
+      , create_html:        { name: kivi.t8('Create HTML'),             icon: "html",   callback: kivi.requirement_spec.create_reqspec_html }
       , create_version:     { name: kivi.t8('Create new version'),      icon: "new",    callback: kivi.requirement_spec.create_version, disabled: kivi.requirement_spec.disable_commands }
       , copy_reqspec:       { name: kivi.t8('Copy requirement spec'),   icon: "copy",   callback: kivi.requirement_spec.copy_reqspec   }
       , delete_reqspec:     { name: kivi.t8('Delete requirement spec'), icon: "delete", callback: kivi.requirement_spec.delete_reqspec }
@@ -896,13 +907,21 @@ ns.create_context_menus = function(is_template) {
       , paste_template:     { name: kivi.t8('Paste template'),     icon: "paste",  callback: kivi.requirement_spec.paste_template }
     };
 
+    var versioned_copy_actions = {
+        heading:             { name: kivi.t8('Version actions'), className: 'context-menu-heading' }
+      , create_version_pdf:  { name: kivi.t8('Create PDF'),        icon: "pdf",    callback: kivi.requirement_spec.create_pdf_for_versioned_copy_ajax_call                                                                      }
+      , create_version_html: { name: kivi.t8('Create HTML'),       icon: "html",   callback: kivi.requirement_spec.create_html_for_versioned_copy_ajax_call                                                                     }
+      , revert_to_version:   { name: kivi.t8('Revert to version'), icon: "revert", callback: kivi.requirement_spec.revert_to_versioned_copy_ajax_call,     disabled: kivi.requirement_spec.disable_versioned_copy_item_commands }
+    };
+
+    if (!data.html_template_exists) {
+      delete general_actions.create_html;
+      delete versioned_copy_actions.create_version_html;
+    }
+
     $.contextMenu({
       selector: '.versioned-copy-context-menu',
-      items:    $.extend({
-          heading:            { name: kivi.t8('Version actions'), className: 'context-menu-heading' }
-        , create_version_pdf: { name: kivi.t8('Create PDF'),        icon: "pdf",    callback: kivi.requirement_spec.create_pdf_for_versioned_copy_ajax_call                                                                      }
-        , revert_to_version:  { name: kivi.t8('Revert to version'), icon: "revert", callback: kivi.requirement_spec.revert_to_versioned_copy_ajax_call,     disabled: kivi.requirement_spec.disable_versioned_copy_item_commands }
-      }, general_actions)
+      items:    $.extend(versioned_copy_actions, general_actions)
     });
 
     $.contextMenu({
diff --git a/locale/de/all b/locale/de/all
index cbe0fa382..e0b5e0de0 100755
--- a/locale/de/all
+++ b/locale/de/all
@@ -586,6 +586,7 @@ $self->{texts} = {
   'Create Chart of Accounts'    => 'Zu verwendender Kontenplan',
   'Create Dataset'              => 'Neue Datenbank anlegen',
   'Create Date'                 => 'Erstelldatum',
+  'Create HTML'                 => 'HTML erzeugen',
   'Create PDF'                  => 'PDF erzeugen',
   'Create a new acceptance status' => 'Einen neuen Abnahmestatus anlegen',
   'Create a new background job' => 'Einen neuen Hintergrund-Job anlegen',
diff --git a/templates/webpages/requirement_spec/show.html b/templates/webpages/requirement_spec/show.html
index c329a4e4d..285dcee15 100644
--- a/templates/webpages/requirement_spec/show.html
+++ b/templates/webpages/requirement_spec/show.html
@@ -92,6 +92,7 @@ $(function() {
       initially_open:          initially_open
     , tree_data:               tree_data
     , is_template:             [% SELF.requirement_spec.is_template ? 'true' : 'false' %]
+    , html_template_exists:    [% SELF.html_template                ? 'true' : 'false' %]
 [% IF SELF.requirement_spec_item %]
     , initially_selected_node: '#fb-[% SELF.requirement_spec_item.id %]'
 [% END %]
