diff --git a/config/lang/de.json b/config/lang/de.json
index e6887784259519552b4f57e76df15d2742616cec..c5eea5d513cbfb2aaed0efb04c053ecfc41d1a2b 100644
--- a/config/lang/de.json
+++ b/config/lang/de.json
@@ -222,10 +222,10 @@
     "creating-directory": "Lege das Verzeichnis %s an.",
     "creating-file": "Lege Datei %s an.",
     "defaultbranch": "[default: Master oder Trunk]",
+    "defaults-all-phases": "Default-Werte (für alle Phasen)",
     "deploy": "Deploy",
     "deploy-hint": "Deploy der Queue von Phase [%s]",
     "deploy-impossible": "Deploy der Queue von Phase [%s] ist nicht möglich.",
-    "deploy-settings": "Deployment - Einstellungen",
     "deploytimes": "Deployment Zeitpunkte",
     "deploytimes-immediately": "Ein Archiv in der Queue wird sofort ins Repo gestellt.",
     "deploymethod": "Deployment-Methode",
@@ -237,6 +237,7 @@
     "dir-archive": "Archiv-Verzeichnis",
     "dir-cache": "Cache-Verzeichnis",
     "dir-builds": "Build-Verzeichnis",
+    "edit": "Bearbeiten",
     "empty": "[leer]",
     "error": "FEHLER",
     "error-no-phase": "Es wurde keine Phase mitgegeben.",
@@ -245,8 +246,18 @@
     "fileprefix": "File Prefix",
     "fileprefix-label": "File-Prefix <span class=\"error\"><br>Nach dem ersten Build nicht mehr änderbar!</span>",
     "finished": "Beendet",
+    "foreman-error-missing-template": "In Foreman wurde das Templatefile [%s] definiert, aber dieses existiert nicht im Build.",
+    "foreman-error-no-replacement-for-id": "In Foreman wurde keine noch Ersetzung f&uuml;r [%s] definiert.",
+    "foreman-error-no-target": "In Foreman wurde keine Ziel-Datei definiert",
+    "foreman-error-template-unknown": "Das Template wurde in Foreman nicht aufgenommen",
+    "foreman-error-replacement-unknown": "Das Replacement [%s] in Foreman existiert im Template nicht.",
     "foreman-hostgroup": "Foreman Hostgruppe",
+    "foreman-hostgroup-override": "Foreman Hostgruppe (override)",
+    "foreman-hostgroup-id": "Foreman Hostgruppe (%s)",
+    "foreman-no-host": "Es wurde kein Host in Foreman konfiguriert. Es werden die Platzhalter in Templates erkannt, aber nicht deren Ziel und Ersetzungen.",
+    "foreman-targetfile": "Name der Zieldatei",
     "gotop": "Seitenanfang",
+    "host": "Host",
     "hosts": "Hosts",
     "hostname4puppet": "Hostname f&uuml;r Puppet Agent",
     "inactive": "inaktiv",
@@ -275,7 +286,6 @@
     "replacement-fields-not-found": "Es wurden keine Platzhalter im Format <em>@replace[&quot;Name&quot;]</em> im Template gefunden.",
     "replacement-targetfile": "Ziel-Datei",
     "replacements": "Ersetzungen mit Template-Dateien",
-    "replacements-info": "Beim Build-Prozess gefundene Templates werden aufgelistet und darin vorhandene Platzhalter erkannt. Die Felder sind ausschliesslich für die neue Infrastruktur relevant.",
     "repositoryinfos": "Quell-Repository",
     "repository-access-browser": "Browserzugriff auf das Repo",
     "repository-auth": "Authentifizierung/ Dateiname zum SSH-Private-Key",
diff --git a/config/lang/en.json b/config/lang/en.json
index 5f2a7ea74bbe806b1d57a1f48e59393c7d7ced9a..223cb552088e7e981d06f612c5b632bf746d943a 100644
--- a/config/lang/en.json
+++ b/config/lang/en.json
@@ -224,10 +224,10 @@
     "creating-directory": "Creating directory %s",
     "creating-file": "Creating file %s",
     "defaultbranch": "[default: master or trunk]",
+    "defaults-all-phases": "Default values (for all phases)",
     "deploy": "Deploy",
     "deploy-hint": "Deploy queue of phase [%s]",
     "deploy-impossible": "Deploy queue of phase [%s] is not possible.",
-    "deploy-settings": "Deployment - settings",
     "deploymethod": "Deployment method",
     "deploymethod-none": "None. Do not trigger any action.",
     "deploymethod-puppet": "Run Puppet on target host(s)",
@@ -239,6 +239,7 @@
     "dir-archive": "Archive directory",
     "dir-cache": "Cache directory",
     "dir-builds": "Build directory",
+    "edit": "Edit",
     "empty": "[empty]",
     "error": "ERROR",
     "error-no-phase": "There no phase was given as parameter.",
@@ -247,8 +248,18 @@
     "fileprefix": "File prefix",
     "fileprefix-label": "File prefix <span class=\"error\"><br>It cannot be changed after the first build!</span>",
     "finished": "Fisnished",
+    "foreman-error-missing-template": "In Foreman the templatefile [%s] was defined but it does not exist in the build.",
+    "foreman-error-no-replacement-for-id": "There is no replacement for [%s] in Foreman.",
+    "foreman-error-no-target": "No target file was set in Foreman",
+    "foreman-error-template-unknown": "The template was not added in Foreman.",
+    "foreman-error-replacement-unknown": "The replacement [%s] from Foreman has no plceholder in the template file.",
     "foreman-hostgroup": "Foreman hostgroup",
+    "foreman-hostgroup-override": "Foreman Hostgroup (override)",
+    "foreman-hostgroup-id": "Foreman Hostgroup (%s)",
+    "foreman-no-host": "No host was configured in Foreman.",
+    "foreman-targetfile": "Target file",
     "gotop": "top",
+    "host": "Host",
     "hosts": "Hosts",
     "hostname4puppet": "Hostname to start puppet agent",
     "inactive": "inactive",
@@ -277,7 +288,6 @@
     "replacement-fields-not-found": "No placeholder in the syntax <em>@replace[&quot;name&quot;]</em> were found in the template.",
     "replacement-targetfile": "Target file",
     "replacements": "Replacements in template files",
-    "replacements-info": "List of detected templates with its placeholders.",
     "repositoryinfos": "Sourcecode Repository",
     "repository-access-browser": "Browser access to sources",
     "repository-auth": "Authentication/ filename of ssh private key",
diff --git a/public_html/deployment/classes/Spyc.php b/public_html/deployment/classes/Spyc.php
new file mode 100644
index 0000000000000000000000000000000000000000..2cf1bbc263db23da335fa1d74c8184493d5f8e5a
--- /dev/null
+++ b/public_html/deployment/classes/Spyc.php
@@ -0,0 +1,1161 @@
+<?php
+/**
+   * Spyc -- A Simple PHP YAML Class
+   * @version 0.6.2
+   * @author Vlad Andersen <vlad.andersen@gmail.com>
+   * @author Chris Wanstrath <chris@ozmm.org>
+   * @link https://github.com/mustangostang/spyc/
+   * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen
+   * @license http://www.opensource.org/licenses/mit-license.php MIT License
+   * @package Spyc
+   */
+
+if (!function_exists('spyc_load')) {
+  /**
+   * Parses YAML to array.
+   * @param string $string YAML string.
+   * @return array
+   */
+  function spyc_load ($string) {
+    return Spyc::YAMLLoadString($string);
+  }
+}
+
+if (!function_exists('spyc_load_file')) {
+  /**
+   * Parses YAML to array.
+   * @param string $file Path to YAML file.
+   * @return array
+   */
+  function spyc_load_file ($file) {
+    return Spyc::YAMLLoad($file);
+  }
+}
+
+if (!function_exists('spyc_dump')) {
+  /**
+   * Dumps array to YAML.
+   * @param array $data Array.
+   * @return string
+   */
+  function spyc_dump ($data) {
+    return Spyc::YAMLDump($data, false, false, true);
+  }
+}
+
+if (!class_exists('Spyc')) {
+
+/**
+   * The Simple PHP YAML Class.
+   *
+   * This class can be used to read a YAML file and convert its contents
+   * into a PHP array.  It currently supports a very limited subsection of
+   * the YAML spec.
+   *
+   * Usage:
+   * <code>
+   *   $Spyc  = new Spyc;
+   *   $array = $Spyc->load($file);
+   * </code>
+   * or:
+   * <code>
+   *   $array = Spyc::YAMLLoad($file);
+   * </code>
+   * or:
+   * <code>
+   *   $array = spyc_load_file($file);
+   * </code>
+   * @package Spyc
+   */
+class Spyc {
+
+  // SETTINGS
+
+  const REMPTY = "\0\0\0\0\0";
+
+  /**
+   * Setting this to true will force YAMLDump to enclose any string value in
+   * quotes.  False by default.
+   *
+   * @var bool
+   */
+  public $setting_dump_force_quotes = false;
+
+  /**
+   * Setting this to true will forse YAMLLoad to use syck_load function when
+   * possible. False by default.
+   * @var bool
+   */
+  public $setting_use_syck_is_possible = false;
+
+
+
+  /**#@+
+  * @access private
+  * @var mixed
+  */
+  private $_dumpIndent;
+  private $_dumpWordWrap;
+  private $_containsGroupAnchor = false;
+  private $_containsGroupAlias = false;
+  private $path;
+  private $result;
+  private $LiteralPlaceHolder = '___YAML_Literal_Block___';
+  private $SavedGroups = array();
+  private $indent;
+  /**
+   * Path modifier that should be applied after adding current element.
+   * @var array
+   */
+  private $delayedPath = array();
+
+  /**#@+
+  * @access public
+  * @var mixed
+  */
+  public $_nodeId;
+
+/**
+ * Load a valid YAML string to Spyc.
+ * @param string $input
+ * @return array
+ */
+  public function load ($input) {
+    return $this->_loadString($input);
+  }
+
+ /**
+ * Load a valid YAML file to Spyc.
+ * @param string $file
+ * @return array
+ */
+  public function loadFile ($file) {
+    return $this->_load($file);
+  }
+
+  /**
+     * Load YAML into a PHP array statically
+     *
+     * The load method, when supplied with a YAML stream (string or file),
+     * will do its best to convert YAML in a file into a PHP array.  Pretty
+     * simple.
+     *  Usage:
+     *  <code>
+     *   $array = Spyc::YAMLLoad('lucky.yaml');
+     *   print_r($array);
+     *  </code>
+     * @access public
+     * @return array
+     * @param string $input Path of YAML file or string containing YAML
+     */
+  public static function YAMLLoad($input) {
+    $Spyc = new Spyc;
+    return $Spyc->_load($input);
+  }
+
+  /**
+     * Load a string of YAML into a PHP array statically
+     *
+     * The load method, when supplied with a YAML string, will do its best
+     * to convert YAML in a string into a PHP array.  Pretty simple.
+     *
+     * Note: use this function if you don't want files from the file system
+     * loaded and processed as YAML.  This is of interest to people concerned
+     * about security whose input is from a string.
+     *
+     *  Usage:
+     *  <code>
+     *   $array = Spyc::YAMLLoadString("---\n0: hello world\n");
+     *   print_r($array);
+     *  </code>
+     * @access public
+     * @return array
+     * @param string $input String containing YAML
+     */
+  public static function YAMLLoadString($input) {
+    $Spyc = new Spyc;
+    return $Spyc->_loadString($input);
+  }
+
+  /**
+     * Dump YAML from PHP array statically
+     *
+     * The dump method, when supplied with an array, will do its best
+     * to convert the array into friendly YAML.  Pretty simple.  Feel free to
+     * save the returned string as nothing.yaml and pass it around.
+     *
+     * Oh, and you can decide how big the indent is and what the wordwrap
+     * for folding is.  Pretty cool -- just pass in 'false' for either if
+     * you want to use the default.
+     *
+     * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
+     * you can turn off wordwrap by passing in 0.
+     *
+     * @access public
+     * @return string
+     * @param array|\stdClass $array PHP array
+     * @param int $indent Pass in false to use the default, which is 2
+     * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
+     * @param bool $no_opening_dashes Do not start YAML file with "---\n"
+     */
+  public static function YAMLDump($array, $indent = false, $wordwrap = false, $no_opening_dashes = false) {
+    $spyc = new Spyc;
+    return $spyc->dump($array, $indent, $wordwrap, $no_opening_dashes);
+  }
+
+
+  /**
+     * Dump PHP array to YAML
+     *
+     * The dump method, when supplied with an array, will do its best
+     * to convert the array into friendly YAML.  Pretty simple.  Feel free to
+     * save the returned string as tasteful.yaml and pass it around.
+     *
+     * Oh, and you can decide how big the indent is and what the wordwrap
+     * for folding is.  Pretty cool -- just pass in 'false' for either if
+     * you want to use the default.
+     *
+     * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
+     * you can turn off wordwrap by passing in 0.
+     *
+     * @access public
+     * @return string
+     * @param array $array PHP array
+     * @param int $indent Pass in false to use the default, which is 2
+     * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
+     */
+  public function dump($array,$indent = false,$wordwrap = false, $no_opening_dashes = false) {
+    // Dumps to some very clean YAML.  We'll have to add some more features
+    // and options soon.  And better support for folding.
+
+    // New features and options.
+    if ($indent === false or !is_numeric($indent)) {
+      $this->_dumpIndent = 2;
+    } else {
+      $this->_dumpIndent = $indent;
+    }
+
+    if ($wordwrap === false or !is_numeric($wordwrap)) {
+      $this->_dumpWordWrap = 40;
+    } else {
+      $this->_dumpWordWrap = $wordwrap;
+    }
+
+    // New YAML document
+    $string = "";
+    if (!$no_opening_dashes) $string = "---\n";
+
+    // Start at the base of the array and move through it.
+    if ($array) {
+      $array = (array)$array;
+      $previous_key = -1;
+      foreach ($array as $key => $value) {
+        if (!isset($first_key)) $first_key = $key;
+        $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array);
+        $previous_key = $key;
+      }
+    }
+    return $string;
+  }
+
+  /**
+     * Attempts to convert a key / value array item to YAML
+     * @access private
+     * @return string
+     * @param $key The name of the key
+     * @param $value The value of the item
+     * @param $indent The indent of the current node
+     */
+  private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) {
+    if(is_object($value)) $value = (array)$value;
+    if (is_array($value)) {
+      if (empty ($value))
+        return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array);
+      // It has children.  What to do?
+      // Make it the right kind of item
+      $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array);
+      // Add the indent
+      $indent += $this->_dumpIndent;
+      // Yamlize the array
+      $string .= $this->_yamlizeArray($value,$indent);
+    } elseif (!is_array($value)) {
+      // It doesn't have children.  Yip.
+      $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array);
+    }
+    return $string;
+  }
+
+  /**
+     * Attempts to convert an array to YAML
+     * @access private
+     * @return string
+     * @param $array The array you want to convert
+     * @param $indent The indent of the current level
+     */
+  private function _yamlizeArray($array,$indent) {
+    if (is_array($array)) {
+      $string = '';
+      $previous_key = -1;
+      foreach ($array as $key => $value) {
+        if (!isset($first_key)) $first_key = $key;
+        $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array);
+        $previous_key = $key;
+      }
+      return $string;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+     * Returns YAML from a key and a value
+     * @access private
+     * @return string
+     * @param $key The name of the key
+     * @param $value The value of the item
+     * @param $indent The indent of the current node
+     */
+  private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) {
+    // do some folding here, for blocks
+    if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false ||
+      strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, '%') !== false || strpos ($value, '  ') !== false ||
+      strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 ||
+      substr ($value, -1, 1) == ':')
+    ) {
+      $value = $this->_doLiteralBlock($value,$indent);
+    } else {
+      $value  = $this->_doFolding($value,$indent);
+    }
+
+    if ($value === array()) $value = '[ ]';
+    if ($value === "") $value = '""';
+    if (self::isTranslationWord($value)) {
+      $value = $this->_doLiteralBlock($value, $indent);
+    }
+    if (trim ($value) != $value)
+       $value = $this->_doLiteralBlock($value,$indent);
+
+    if (is_bool($value)) {
+       $value = $value ? "true" : "false";
+    }
+
+    if ($value === null) $value = 'null';
+    if ($value === "'" . self::REMPTY . "'") $value = null;
+
+    $spaces = str_repeat(' ',$indent);
+
+    //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) {
+    if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) {
+      // It's a sequence
+      $string = $spaces.'- '.$value."\n";
+    } else {
+      // if ($first_key===0)  throw new Exception('Keys are all screwy.  The first one was zero, now it\'s "'. $key .'"');
+      // It's mapped
+      if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; }
+      $string = rtrim ($spaces.$key.': '.$value)."\n";
+    }
+    return $string;
+  }
+
+  /**
+     * Creates a literal block for dumping
+     * @access private
+     * @return string
+     * @param $value
+     * @param $indent int The value of the indent
+     */
+  private function _doLiteralBlock($value,$indent) {
+    if ($value === "\n") return '\n';
+    if (strpos($value, "\n") === false && strpos($value, "'") === false) {
+      return sprintf ("'%s'", $value);
+    }
+    if (strpos($value, "\n") === false && strpos($value, '"') === false) {
+      return sprintf ('"%s"', $value);
+    }
+    $exploded = explode("\n",$value);
+    $newValue = '|';
+    if (isset($exploded[0]) && ($exploded[0] == "|" || $exploded[0] == "|-" || $exploded[0] == ">")) {
+        $newValue = $exploded[0];
+        unset($exploded[0]);
+    }
+    $indent += $this->_dumpIndent;
+    $spaces   = str_repeat(' ',$indent);
+    foreach ($exploded as $line) {
+      $line = trim($line);
+      if (strpos($line, '"') === 0 && strrpos($line, '"') == (strlen($line)-1) || strpos($line, "'") === 0 && strrpos($line, "'") == (strlen($line)-1)) {
+        $line = substr($line, 1, -1);
+      }
+      $newValue .= "\n" . $spaces . ($line);
+    }
+    return $newValue;
+  }
+
+  /**
+     * Folds a string of text, if necessary
+     * @access private
+     * @return string
+     * @param $value The string you wish to fold
+     */
+  private function _doFolding($value,$indent) {
+    // Don't do anything if wordwrap is set to 0
+
+    if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) {
+      $indent += $this->_dumpIndent;
+      $indent = str_repeat(' ',$indent);
+      $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent");
+      $value   = ">\n".$indent.$wrapped;
+    } else {
+      if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY)
+        $value = '"' . $value . '"';
+      if (is_numeric($value) && is_string($value))
+        $value = '"' . $value . '"';
+    }
+
+
+    return $value;
+  }
+
+  private function isTrueWord($value) {
+    $words = self::getTranslations(array('true', 'on', 'yes', 'y'));
+    return in_array($value, $words, true);
+  }
+
+  private function isFalseWord($value) {
+    $words = self::getTranslations(array('false', 'off', 'no', 'n'));
+    return in_array($value, $words, true);
+  }
+
+  private function isNullWord($value) {
+    $words = self::getTranslations(array('null', '~'));
+    return in_array($value, $words, true);
+  }
+
+  private function isTranslationWord($value) {
+    return (
+      self::isTrueWord($value)  ||
+      self::isFalseWord($value) ||
+      self::isNullWord($value)
+    );
+  }
+
+  /**
+     * Coerce a string into a native type
+     * Reference: http://yaml.org/type/bool.html
+     * TODO: Use only words from the YAML spec.
+     * @access private
+     * @param $value The value to coerce
+     */
+  private function coerceValue(&$value) {
+    if (self::isTrueWord($value)) {
+      $value = true;
+    } else if (self::isFalseWord($value)) {
+      $value = false;
+    } else if (self::isNullWord($value)) {
+      $value = null;
+    }
+  }
+
+  /**
+     * Given a set of words, perform the appropriate translations on them to
+     * match the YAML 1.1 specification for type coercing.
+     * @param $words The words to translate
+     * @access private
+     */
+  private static function getTranslations(array $words) {
+    $result = array();
+    foreach ($words as $i) {
+      $result = array_merge($result, array(ucfirst($i), strtoupper($i), strtolower($i)));
+    }
+    return $result;
+  }
+
+// LOADING FUNCTIONS
+
+  private function _load($input) {
+    $Source = $this->loadFromSource($input);
+    return $this->loadWithSource($Source);
+  }
+
+  private function _loadString($input) {
+    $Source = $this->loadFromString($input);
+    return $this->loadWithSource($Source);
+  }
+
+  private function loadWithSource($Source) {
+    if (empty ($Source)) return array();
+    if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) {
+      $array = syck_load (implode ("\n", $Source));
+      return is_array($array) ? $array : array();
+    }
+
+    $this->path = array();
+    $this->result = array();
+
+    $cnt = count($Source);
+    for ($i = 0; $i < $cnt; $i++) {
+      $line = $Source[$i];
+
+      $this->indent = strlen($line) - strlen(ltrim($line));
+      $tempPath = $this->getParentPathByIndent($this->indent);
+      $line = self::stripIndent($line, $this->indent);
+      if (self::isComment($line)) continue;
+      if (self::isEmpty($line)) continue;
+      $this->path = $tempPath;
+
+      $literalBlockStyle = self::startsLiteralBlock($line);
+      if ($literalBlockStyle) {
+        $line = rtrim ($line, $literalBlockStyle . " \n");
+        $literalBlock = '';
+        $line .= ' '.$this->LiteralPlaceHolder;
+        $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1]));
+        while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) {
+          $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent);
+        }
+        $i--;
+      }
+
+      // Strip out comments
+      if (strpos ($line, '#')) {
+          $line = preg_replace('/\s*#([^"\']+)$/','',$line);
+      }
+
+      while (++$i < $cnt && self::greedilyNeedNextLine($line)) {
+        $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t");
+      }
+      $i--;
+
+      $lineArray = $this->_parseLine($line);
+
+      if ($literalBlockStyle)
+        $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock);
+
+      $this->addArray($lineArray, $this->indent);
+
+      foreach ($this->delayedPath as $indent => $delayedPath)
+        $this->path[$indent] = $delayedPath;
+
+      $this->delayedPath = array();
+
+    }
+    return $this->result;
+  }
+
+  private function loadFromSource ($input) {
+    if (!empty($input) && strpos($input, "\n") === false && file_exists($input))
+      $input = file_get_contents($input);
+
+    return $this->loadFromString($input);
+  }
+
+  private function loadFromString ($input) {
+    $lines = explode("\n",$input);
+    foreach ($lines as $k => $_) {
+      $lines[$k] = rtrim ($_, "\r");
+    }
+    return $lines;
+  }
+
+  /**
+     * Parses YAML code and returns an array for a node
+     * @access private
+     * @return array
+     * @param string $line A line from the YAML file
+     */
+  private function _parseLine($line) {
+    if (!$line) return array();
+    $line = trim($line);
+    if (!$line) return array();
+
+    $array = array();
+
+    $group = $this->nodeContainsGroup($line);
+    if ($group) {
+      $this->addGroup($line, $group);
+      $line = $this->stripGroup ($line, $group);
+    }
+
+    if ($this->startsMappedSequence($line))
+      return $this->returnMappedSequence($line);
+
+    if ($this->startsMappedValue($line))
+      return $this->returnMappedValue($line);
+
+    if ($this->isArrayElement($line))
+     return $this->returnArrayElement($line);
+
+    if ($this->isPlainArray($line))
+     return $this->returnPlainArray($line);
+
+
+    return $this->returnKeyValuePair($line);
+
+  }
+
+  /**
+     * Finds the type of the passed value, returns the value as the new type.
+     * @access private
+     * @param string $value
+     * @return mixed
+     */
+  private function _toType($value) {
+    if ($value === '') return "";
+    $first_character = $value[0];
+    $last_character = substr($value, -1, 1);
+
+    $is_quoted = false;
+    do {
+      if (!$value) break;
+      if ($first_character != '"' && $first_character != "'") break;
+      if ($last_character != '"' && $last_character != "'") break;
+      $is_quoted = true;
+    } while (0);
+
+    if ($is_quoted) {
+      $value = str_replace('\n', "\n", $value);
+      if ($first_character == "'")
+        return strtr(substr ($value, 1, -1), array ('\'\'' => '\'', '\\\''=> '\''));
+      return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\\\''=> '\''));
+    }
+
+    if (strpos($value, ' #') !== false && !$is_quoted)
+      $value = preg_replace('/\s+#(.+)$/','',$value);
+
+    if ($first_character == '[' && $last_character == ']') {
+      // Take out strings sequences and mappings
+      $innerValue = trim(substr ($value, 1, -1));
+      if ($innerValue === '') return array();
+      $explode = $this->_inlineEscape($innerValue);
+      // Propagate value array
+      $value  = array();
+      foreach ($explode as $v) {
+        $value[] = $this->_toType($v);
+      }
+      return $value;
+    }
+
+    if (strpos($value,': ')!==false && $first_character != '{') {
+      $array = explode(': ',$value);
+      $key   = trim($array[0]);
+      array_shift($array);
+      $value = trim(implode(': ',$array));
+      $value = $this->_toType($value);
+      return array($key => $value);
+    }
+
+    if ($first_character == '{' && $last_character == '}') {
+      $innerValue = trim(substr ($value, 1, -1));
+      if ($innerValue === '') return array();
+      // Inline Mapping
+      // Take out strings sequences and mappings
+      $explode = $this->_inlineEscape($innerValue);
+      // Propagate value array
+      $array = array();
+      foreach ($explode as $v) {
+        $SubArr = $this->_toType($v);
+        if (empty($SubArr)) continue;
+        if (is_array ($SubArr)) {
+          $array[key($SubArr)] = $SubArr[key($SubArr)]; continue;
+        }
+        $array[] = $SubArr;
+      }
+      return $array;
+    }
+
+    if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') {
+      return null;
+    }
+
+    if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){
+      $intvalue = (int)$value;
+      if ($intvalue != PHP_INT_MAX && $intvalue != ~PHP_INT_MAX)
+        $value = $intvalue;
+      return $value;
+    }
+
+    if ( is_string($value) && preg_match('/^0[xX][0-9a-fA-F]+$/', $value)) {
+      // Hexadecimal value.
+      return hexdec($value);
+    }
+
+    $this->coerceValue($value);
+
+    if (is_numeric($value)) {
+      if ($value === '0') return 0;
+      if (rtrim ($value, 0) === $value)
+        $value = (float)$value;
+      return $value;
+    }
+
+    return $value;
+  }
+
+  /**
+     * Used in inlines to check for more inlines or quoted strings
+     * @access private
+     * @return array
+     */
+  private function _inlineEscape($inline) {
+    // There's gotta be a cleaner way to do this...
+    // While pure sequences seem to be nesting just fine,
+    // pure mappings and mappings with sequences inside can't go very
+    // deep.  This needs to be fixed.
+
+    $seqs = array();
+    $maps = array();
+    $saved_strings = array();
+    $saved_empties = array();
+
+    // Check for empty strings
+    $regex = '/("")|(\'\')/';
+    if (preg_match_all($regex,$inline,$strings)) {
+      $saved_empties = $strings[0];
+      $inline  = preg_replace($regex,'YAMLEmpty',$inline);
+    }
+    unset($regex);
+
+    // Check for strings
+    $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
+    if (preg_match_all($regex,$inline,$strings)) {
+      $saved_strings = $strings[0];
+      $inline  = preg_replace($regex,'YAMLString',$inline);
+    }
+    unset($regex);
+
+    // echo $inline;
+
+    $i = 0;
+    do {
+
+    // Check for sequences
+    while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) {
+      $seqs[] = $matchseqs[0];
+      $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1);
+    }
+
+    // Check for mappings
+    while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) {
+      $maps[] = $matchmaps[0];
+      $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1);
+    }
+
+    if ($i++ >= 10) break;
+
+    } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false);
+
+    $explode = explode(',',$inline);
+    $explode = array_map('trim', $explode);
+    $stringi = 0; $i = 0;
+
+    while (1) {
+
+    // Re-add the sequences
+    if (!empty($seqs)) {
+      foreach ($explode as $key => $value) {
+        if (strpos($value,'YAMLSeq') !== false) {
+          foreach ($seqs as $seqk => $seq) {
+            $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value);
+            $value = $explode[$key];
+          }
+        }
+      }
+    }
+
+    // Re-add the mappings
+    if (!empty($maps)) {
+      foreach ($explode as $key => $value) {
+        if (strpos($value,'YAMLMap') !== false) {
+          foreach ($maps as $mapk => $map) {
+            $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value);
+            $value = $explode[$key];
+          }
+        }
+      }
+    }
+
+
+    // Re-add the strings
+    if (!empty($saved_strings)) {
+      foreach ($explode as $key => $value) {
+        while (strpos($value,'YAMLString') !== false) {
+          $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1);
+          unset($saved_strings[$stringi]);
+          ++$stringi;
+          $value = $explode[$key];
+        }
+      }
+    }
+
+
+    // Re-add the empties
+    if (!empty($saved_empties)) {
+      foreach ($explode as $key => $value) {
+        while (strpos($value,'YAMLEmpty') !== false) {
+          $explode[$key] = preg_replace('/YAMLEmpty/', '', $value, 1);
+          $value = $explode[$key];
+        }
+      }
+    }
+
+    $finished = true;
+    foreach ($explode as $key => $value) {
+      if (strpos($value,'YAMLSeq') !== false) {
+        $finished = false; break;
+      }
+      if (strpos($value,'YAMLMap') !== false) {
+        $finished = false; break;
+      }
+      if (strpos($value,'YAMLString') !== false) {
+        $finished = false; break;
+      }
+      if (strpos($value,'YAMLEmpty') !== false) {
+        $finished = false; break;
+      }
+    }
+    if ($finished) break;
+
+    $i++;
+    if ($i > 10)
+      break; // Prevent infinite loops.
+    }
+
+
+    return $explode;
+  }
+
+  private function literalBlockContinues ($line, $lineIndent) {
+    if (!trim($line)) return true;
+    if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true;
+    return false;
+  }
+
+  private function referenceContentsByAlias ($alias) {
+    do {
+      if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; }
+      $groupPath = $this->SavedGroups[$alias];
+      $value = $this->result;
+      foreach ($groupPath as $k) {
+        $value = $value[$k];
+      }
+    } while (false);
+    return $value;
+  }
+
+  private function addArrayInline ($array, $indent) {
+      $CommonGroupPath = $this->path;
+      if (empty ($array)) return false;
+
+      foreach ($array as $k => $_) {
+        $this->addArray(array($k => $_), $indent);
+        $this->path = $CommonGroupPath;
+      }
+      return true;
+  }
+
+  private function addArray ($incoming_data, $incoming_indent) {
+
+   // print_r ($incoming_data);
+
+    if (count ($incoming_data) > 1)
+      return $this->addArrayInline ($incoming_data, $incoming_indent);
+
+    $key = key ($incoming_data);
+    $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null;
+    if ($key === '__!YAMLZero') $key = '0';
+
+    if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values.
+      if ($key || $key === '' || $key === '0') {
+        $this->result[$key] = $value;
+      } else {
+        $this->result[] = $value; end ($this->result); $key = key ($this->result);
+      }
+      $this->path[$incoming_indent] = $key;
+      return;
+    }
+
+
+
+    $history = array();
+    // Unfolding inner array tree.
+    $history[] = $_arr = $this->result;
+    foreach ($this->path as $k) {
+      $history[] = $_arr = $_arr[$k];
+    }
+
+    if ($this->_containsGroupAlias) {
+      $value = $this->referenceContentsByAlias($this->_containsGroupAlias);
+      $this->_containsGroupAlias = false;
+    }
+
+
+    // Adding string or numeric key to the innermost level or $this->arr.
+    if (is_string($key) && $key == '<<') {
+      if (!is_array ($_arr)) { $_arr = array (); }
+
+      $_arr = array_merge ($_arr, $value);
+    } else if ($key || $key === '' || $key === '0') {
+      if (!is_array ($_arr))
+        $_arr = array ($key=>$value);
+      else
+        $_arr[$key] = $value;
+    } else {
+      if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; }
+      else { $_arr[] = $value; end ($_arr); $key = key ($_arr); }
+    }
+
+    $reverse_path = array_reverse($this->path);
+    $reverse_history = array_reverse ($history);
+    $reverse_history[0] = $_arr;
+    $cnt = count($reverse_history) - 1;
+    for ($i = 0; $i < $cnt; $i++) {
+      $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i];
+    }
+    $this->result = $reverse_history[$cnt];
+
+    $this->path[$incoming_indent] = $key;
+
+    if ($this->_containsGroupAnchor) {
+      $this->SavedGroups[$this->_containsGroupAnchor] = $this->path;
+      if (is_array ($value)) {
+        $k = key ($value);
+        if (!is_int ($k)) {
+          $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k;
+        }
+      }
+      $this->_containsGroupAnchor = false;
+    }
+
+  }
+
+  private static function startsLiteralBlock ($line) {
+    $lastChar = substr (trim($line), -1);
+    if ($lastChar != '>' && $lastChar != '|') return false;
+    if ($lastChar == '|') return $lastChar;
+    // HTML tags should not be counted as literal blocks.
+    if (preg_match ('#<.*?>$#', $line)) return false;
+    return $lastChar;
+  }
+
+  private static function greedilyNeedNextLine($line) {
+    $line = trim ($line);
+    if (!strlen($line)) return false;
+    if (substr ($line, -1, 1) == ']') return false;
+    if ($line[0] == '[') return true;
+    if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true;
+    return false;
+  }
+
+  private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) {
+    $line = self::stripIndent($line, $indent);
+    if ($literalBlockStyle !== '|') {
+        $line = self::stripIndent($line);
+    }
+    $line = rtrim ($line, "\r\n\t ") . "\n";
+    if ($literalBlockStyle == '|') {
+      return $literalBlock . $line;
+    }
+    if (strlen($line) == 0)
+      return rtrim($literalBlock, ' ') . "\n";
+    if ($line == "\n" && $literalBlockStyle == '>') {
+      return rtrim ($literalBlock, " \t") . "\n";
+    }
+    if ($line != "\n")
+      $line = trim ($line, "\r\n ") . " ";
+    return $literalBlock . $line;
+  }
+
+   function revertLiteralPlaceHolder ($lineArray, $literalBlock) {
+     foreach ($lineArray as $k => $_) {
+      if (is_array($_))
+        $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock);
+      else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder)
+	       $lineArray[$k] = rtrim ($literalBlock, " \r\n");
+     }
+     return $lineArray;
+   }
+
+  private static function stripIndent ($line, $indent = -1) {
+    if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line));
+    return substr ($line, $indent);
+  }
+
+  private function getParentPathByIndent ($indent) {
+    if ($indent == 0) return array();
+    $linePath = $this->path;
+    do {
+      end($linePath); $lastIndentInParentPath = key($linePath);
+      if ($indent <= $lastIndentInParentPath) array_pop ($linePath);
+    } while ($indent <= $lastIndentInParentPath);
+    return $linePath;
+  }
+
+
+  private function clearBiggerPathValues ($indent) {
+
+
+    if ($indent == 0) $this->path = array();
+    if (empty ($this->path)) return true;
+
+    foreach ($this->path as $k => $_) {
+      if ($k > $indent) unset ($this->path[$k]);
+    }
+
+    return true;
+  }
+
+
+  private static function isComment ($line) {
+    if (!$line) return false;
+    if ($line[0] == '#') return true;
+    if (trim($line, " \r\n\t") == '---') return true;
+    return false;
+  }
+
+  private static function isEmpty ($line) {
+    return (trim ($line) === '');
+  }
+
+
+  private function isArrayElement ($line) {
+    if (!$line || !is_scalar($line)) return false;
+    if (substr($line, 0, 2) != '- ') return false;
+    if (strlen ($line) > 3)
+      if (substr($line,0,3) == '---') return false;
+
+    return true;
+  }
+
+  private function isHashElement ($line) {
+    return strpos($line, ':');
+  }
+
+  private function isLiteral ($line) {
+    if ($this->isArrayElement($line)) return false;
+    if ($this->isHashElement($line)) return false;
+    return true;
+  }
+
+
+  private static function unquote ($value) {
+    if (!$value) return $value;
+    if (!is_string($value)) return $value;
+    if ($value[0] == '\'') return trim ($value, '\'');
+    if ($value[0] == '"') return trim ($value, '"');
+    return $value;
+  }
+
+  private function startsMappedSequence ($line) {
+    return (substr($line, 0, 2) == '- ' && substr ($line, -1, 1) == ':');
+  }
+
+  private function returnMappedSequence ($line) {
+    $array = array();
+    $key         = self::unquote(trim(substr($line,1,-1)));
+    $array[$key] = array();
+    $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key);
+    return array($array);
+  }
+
+  private function checkKeysInValue($value) {
+    if (strchr('[{"\'', $value[0]) === false) {
+      if (strchr($value, ': ') !== false) {
+          throw new Exception('Too many keys: '.$value);
+      }
+    }
+  }
+
+  private function returnMappedValue ($line) {
+    $this->checkKeysInValue($line);
+    $array = array();
+    $key         = self::unquote (trim(substr($line,0,-1)));
+    $array[$key] = '';
+    return $array;
+  }
+
+  private function startsMappedValue ($line) {
+    return (substr ($line, -1, 1) == ':');
+  }
+
+  private function isPlainArray ($line) {
+    return ($line[0] == '[' && substr ($line, -1, 1) == ']');
+  }
+
+  private function returnPlainArray ($line) {
+    return $this->_toType($line);
+  }
+
+  private function returnKeyValuePair ($line) {
+    $array = array();
+    $key = '';
+    if (strpos ($line, ': ')) {
+      // It's a key/value pair most likely
+      // If the key is in double quotes pull it out
+      if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
+        $value = trim(str_replace($matches[1],'',$line));
+        $key   = $matches[2];
+      } else {
+        // Do some guesswork as to the key and the value
+        $explode = explode(': ', $line);
+        $key     = trim(array_shift($explode));
+        $value   = trim(implode(': ', $explode));
+        $this->checkKeysInValue($value);
+      }
+      // Set the type of the value.  Int, string, etc
+      $value = $this->_toType($value);
+      if ($key === '0') $key = '__!YAMLZero';
+      $array[$key] = $value;
+    } else {
+      $array = array ($line);
+    }
+    return $array;
+
+  }
+
+
+  private function returnArrayElement ($line) {
+     if (strlen($line) <= 1) return array(array()); // Weird %)
+     $array = array();
+     $value   = trim(substr($line,1));
+     $value   = $this->_toType($value);
+     if ($this->isArrayElement($value)) {
+       $value = $this->returnArrayElement($value);
+     }
+     $array[] = $value;
+     return $array;
+  }
+
+
+  private function nodeContainsGroup ($line) {
+    $symbolsForReference = 'A-z0-9_\-';
+    if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-)
+    if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1];
+    if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1];
+    if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1];
+    if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1];
+    if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1];
+    return false;
+
+  }
+
+  private function addGroup ($line, $group) {
+    if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1);
+    if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1);
+    //print_r ($this->path);
+  }
+
+  private function stripGroup ($line, $group) {
+    $line = trim(str_replace($group, '', $line));
+    return $line;
+  }
+}
+}
+
+// Enable use of Spyc from command line
+// The syntax is the following: php Spyc.php spyc.yaml
+
+do {
+  if (PHP_SAPI != 'cli') break;
+  if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break;
+  if (empty ($_SERVER['PHP_SELF']) || FALSE === strpos ($_SERVER['PHP_SELF'], 'Spyc.php') ) break;
+  $file = $argv[1];
+  echo json_encode (spyc_load_file ($file));
+} while (0);
\ No newline at end of file
diff --git a/public_html/deployment/classes/config-replacement.class.php b/public_html/deployment/classes/config-replacement.class.php
index 5415757c16d5b8a3c6ab164bdba47f0087177d62..01da4014896e5480888fd6eefadabec3dd63a8ce 100644
--- a/public_html/deployment/classes/config-replacement.class.php
+++ b/public_html/deployment/classes/config-replacement.class.php
@@ -18,6 +18,8 @@ class configreplacement {
      * @var type 
      */
     protected $_oProject = false;
+    protected $_sPhase = false;
+    protected $_aForemanReplacements = false;
 
     
     /**
@@ -35,18 +37,17 @@ class configreplacement {
 
     /**
      * get an array with a flat list of all templatefiles of a build
-     * @param string $sPhase  name of phase
      * @return boolean|array
      */
-    public function getTemplatefiles($sPhase=false){
-        $aReturn = array();
-        if (!$sPhase){
-            $sPhase=$this->_oProject->getNextPhase(false);
+    public function getTemplatefiles(){
+        if (!$this->_sPhase){
+            return false;
         }
+        $aReturn = array();
 
-        $aBuildfiles=$this->_oProject->getBuildfilesByPlace($sPhase, 'onhold');
+        $aBuildfiles=$this->_oProject->getBuildfilesByPlace($this->_sPhase, 'onhold');
         if (!$aBuildfiles){
-            $aBuildfiles=$this->_oProject->getBuildfilesByPlace($sPhase, 'ready2install');
+            $aBuildfiles=$this->_oProject->getBuildfilesByPlace($this->_sPhase, 'ready2install');
         }
         
         if (!$aBuildfiles || !array_key_exists('types', $aBuildfiles) || !array_key_exists('templates', $aBuildfiles['types'])){
@@ -60,11 +61,13 @@ class configreplacement {
 
     /**
      * get an array with all template files (basename) and its replacement fields
-     * @param string $sPhase  name of phase
      * @return array
      */
-    public function getReplacements($sPhase=false){
-        $aFiles=$this->getTemplatefiles($sPhase);
+    public function getReplacements(){
+        if (!$this->_sPhase){
+            return false;
+        }
+        $aFiles=$this->getTemplatefiles($this->_sPhase);
         if (!$aFiles){
             return false;
         }
@@ -80,11 +83,256 @@ class configreplacement {
         return $aReturn;
     }
     
+    /**
+     * get effective hostgroup id of the current phase
+     * @return integer
+     */
+    public function getForemanHostgroup(){
+        $aPrjConfig=$this->_oProject->getConfig();
+        $iForemanHostgroupDefault = (int) $aPrjConfig['deploy']['foreman']['hostgroup'];
+        $iForemanHostgroup = (int) $aPrjConfig['phases'][$this->_sPhase]['foreman-hostgroup'];
+        return ($iForemanHostgroup===OPTION_DEFAULT) ? $iForemanHostgroupDefault : $iForemanHostgroup;
+    }
+    
+    /**
+     * get replacement definitions from foreman
+     * @global type $aConfig
+     * @return boolean
+     */
+    protected function _getForemanReplacement(){
+        global $aConfig;
+        if (!$this->_sPhase){
+            return false;
+        }
+        
+        // abort if no foreman connection was configured
+        if (!array_key_exists('foreman', $aConfig)) {
+            return false;
+        }
+
+        // return already cached result
+        if (array_key_exists($this->_sPhase, $this->_aForemanReplacements)){
+            return $this->_aForemanReplacements[$this->_sPhase];
+        }
+        
+        // rebuilt 
+        $this->_aForemanReplacements[$this->_sPhase]=false;
+        $iEffectiveHostgroup=$this->getForemanHostgroup();
+
+        // abort if no hostgroup was set
+        if($iEffectiveHostgroup<=0){
+            return false;
+        }
+
+        require_once 'foremanapi.class.php';
+        $oForeman=new ForemanApi($aConfig['foreman']);
+       
+        // get a host of this phase
+        $aHosts=$oForeman->read(array(
+            'request' => array(
+                array('hosts' ),
+            ),
+            'filter' => array(
+                'hostgroup_id' => $iEffectiveHostgroup,
+            ),
+            'response' => array('name', 'id'),
+        ));
+
+        $sHost='';
+        $aHostsOfPhase=array();
+        
+        // HACK: phases are part of the hostname .. but not "live" ... and special handling for demo
+        $aPrjConfig=$this->_oProject->getConfig();
+        $bIsDemo=(array_key_exists('fileprefix', $aPrjConfig)
+                && !strpos($aPrjConfig['fileprefix'], 'demo')===false);
+        $sPhase=$bIsDemo ? 'demo' : $this->_sPhase;
+        foreach($aHosts as $aName){
+            $sName=$aName['name'];
+            if (($sPhase==='live' &&
+                (
+                    strpos($sName, 'preview')===false
+                    && strpos($sName, 'stage')===false
+                    && strpos($sName, 'demo')===false
+                )
+            )
+               || (strpos($sName, $sPhase))
+            ){
+                $sHost=$sHost ? $sHost : $sName;
+                $aHostsOfPhase[]=$sName;
+            }
+        }
+        
+        // get created yaml of this host
+        $sUrl=$aConfig['foreman']['api']."hosts/$sHost/externalNodes?name=$sHost";
+        $aData=$oForeman->makeRequest(array(
+            'url'=>$sUrl,
+            'method'=>'GET',
+        ));
+        
+        // HACK: minify YAML of the host ... because it is too large for SPYC parser
+        $sPart="---\n";
+        $bCopy=false;
+        foreach(explode("\n", $aData['body']) as $sLine){
+            if($bCopy){
+                if (strpos($sLine, '    ')===false){
+                    $bCopy=false;
+                } else {
+                    // remove leading spaces and html entities...
+                    $sNewLine=  html_entity_decode(preg_replace('/^\ \ \ \ /', '', $sLine, 1));
+                    $sNewLine=str_replace('&#39;{', "'{", $sNewLine);
+                    $sNewLine=str_replace('}&#39;', "}'", $sNewLine);
+                    
+                    // fix json errors
+                    $sNewLine=str_replace(', }', " }", $sNewLine);
+                    $sNewLine=str_replace(',}', "}", $sNewLine);
+                    $sPart.=$sNewLine."\n";
+                }
+            }
+            if($sLine==='  iml-deployment-config:'){
+                $bCopy=true;
+            }
+            // echo 'DEBUG: '.($bCopy ? 'COPY':'SKIP').$sLine.'<br>';
+        }
+
+        require_once "spyc.php";
+        $aYaml=spyc_load($sPart);
+
+        $aReturn=array();
+        foreach ($aYaml as $aProject){
+            foreach ($aProject as $sFile=>$aParams){
+                $aReturn[$sFile]=array();
+                if (array_key_exists('target', $aParams)){
+                    $aReturn[$sFile]['target']=$aParams['target'];
+                }
+                if (array_key_exists('replace', $aParams)){
+                    $aReplace=json_decode($aParams['replace'], 1);
+                    $aReturn[$sFile]['replace_source']=$aReplace;
+                    foreach ($aReplace as $sVarname=>$value){
+                        if (is_array($value) && array_key_exists('_'.$this->_sPhase.'_', $value)){
+                            $value=$value['_'.$this->_sPhase.'_'];
+                        }
+                        $aReturn[$sFile]['replace'][$sVarname]=$value;
+                    }
+                }
+            }
+        }
+
+        $this->_aForemanReplacements[$this->_sPhase]=array(
+            'rules'=>$aReturn,
+            'host'=>$sHost,
+            'yaml'=>$sPart,
+            'hostgroup'=>$iEffectiveHostgroup,
+            'hostsall'=>$aHosts,
+            'hostsphase'=>$aHostsOfPhase,
+        );
+        return $this->_aForemanReplacements[$this->_sPhase];
+    }
+
+    /**
+     * get foreman base url ... if foreman was activated in the setup
+     * 
+     * @global array  $aConfig  ci config
+     * @return string
+     */
+    private function _getForemanBaseUrl(){
+        global $aConfig;
+        if (!array_key_exists('foreman', $aConfig) 
+                || !array_key_exists('api', $aConfig['foreman']) 
+                || !$aConfig['foreman']['api']
+        ){
+            return false;
+        }
+        return $aConfig['foreman']['api'];
+    }
+    
+    /**
+     * get html code for links to edit each host of the current phase in foreman
+     * 
+     * @return boolean
+     */
+    public function getForemanlink2Host(){
+        $sForemanurl=$this->_getForemanBaseUrl();
+        if (!$sForemanurl){
+            return false;
+        }
+        $aTmp=$this->_getForemanReplacement();
+        if (!array_key_exists('hostsphase', $aTmp)){
+            return false;
+        }
+        require_once 'htmlguielements.class.php';
+        $oHtml=new htmlguielements();
+        $sReturn='';
+        foreach ($aTmp['hostsphase'] as $sHost){
+            $sReturn.=$oHtml->getLinkButton(array(
+                'href'=>$sForemanurl.'hosts/'.$aTmp['host'],
+                'target'=>'_foreman',
+                'title'=>t('edit'),
+                'icon'=>'host',
+                'label'=>t('host').' '.$aTmp['host'],
+            )).' ';
+        }
+        return $sReturn;
+    }
+
+    /**
+     * get html code for a link to edit hostgroup in foreman
+     * 
+     * @return boolean
+     */
+    public function getForemanlink2Hostgroup(){
+        $iEffectiveHostgroup=$this->getForemanHostgroup();
+        if($iEffectiveHostgroup<=0){
+            return false;
+        }
+        $sForemanurl=$this->_getForemanBaseUrl();
+        if (!$sForemanurl){
+            return false;
+        }
+        require_once 'htmlguielements.class.php';
+        $oHtml=new htmlguielements();
+        return $oHtml->getLinkButton(array(
+            'href'=>$sForemanurl.'hostgroups/'.$iEffectiveHostgroup.'/edit',
+            'target'=>'_foreman',
+            'title'=>t('edit'),
+            'icon'=>'hostgroup',
+            'label'=>sprintf(t('foreman-hostgroup-id'), $iEffectiveHostgroup),
+        ));
+        
+    }
+    
+    /**
+     * get replacements in foreman 
+     * @return type
+     */
+    public function getForemanReplacements(){
+        return $this->_getForemanReplacement();
+    }
+    
     /**
      * switch to a project
      * @param type $sProject
      */
-    public function setProject($sProject){
+    public function setProject($sProject, $sPhase=false){
         $this->_oProject = new project($sProject);
+        $this->_aForemanReplacements=false;
+        $this->setPhase($sPhase);
+        return true;
+    }
+    
+    /**
+     * set a phase of a project
+     * @param string  $sPhase  name of valid phase
+     * @return boolean
+     */
+    public function setPhase($sPhase=false){
+        $this->_sPhase=false;
+        if (!$sPhase){
+            $sPhase=$this->_oProject->getNextPhase(false);
+        }
+        if (!$sPhase || !$this->_oProject->isActivePhase($sPhase)) {
+            return false;
+        }
+        $this->_sPhase=$sPhase;
+        return true;
     }
 }
diff --git a/public_html/deployment/classes/foremanapi.class.php b/public_html/deployment/classes/foremanapi.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..2c2e3e0878f2e38ab52f9d8f19fc4db725e8eb15
--- /dev/null
+++ b/public_html/deployment/classes/foremanapi.class.php
@@ -0,0 +1,508 @@
+<?php
+/**
+ * ForemanApi
+ * 
+ * foreman access to API
+ * 
+ * @example
+ * in project class
+ * $oForeman=new ForemanApi($this->_aConfig['foreman']);
+ * 
+ * // enable debugging
+ * $oForeman->setDebug(1);
+ * 
+ * // self check
+ * $oForeman->selfcheck(); die(__FUNCTION__);
+ *
+ * // read operating systems and get id and title only
+ * $aForemanHostgroups=$oForeman->read(array(
+ *    'request'=>array(
+ *        array('operatingsystems'),
+ *    ),
+ *    'response'=>array(
+ *        'id','title'
+ *   ),
+ * ));
+ * 
+ * // read details for operating systems #4
+ * $aForemanHostgroups=$oForeman->read(array(
+ *    'request'=>array(
+ *        array('operatingsystems', 4),
+ *    ),
+ * ));
+ *
+ * 
+ * $aOptions ... can contain optional subkeys
+ * - request
+ *      [] list of array(keyword [,id])
+ * - filter (array)
+ *      - search (string)
+ *      - page (string)
+ *      - per_page (string)
+ * - response (array)
+ *      - list of keys, i.e. array('id', 'title')
+
+ * @author hahn
+ */
+class ForemanApi {
+
+    protected $_aCfg=array();
+    protected $_bDebug = false;
+
+    protected $_aAllowedUrls=array(
+        'api'=>array(
+            ''=>array(),
+            'architectures'=>array(),
+            'audits'=>array('methods'=>array('GET')),
+            'auth_source_ldaps'=>array(),
+            'bookmarks'=>array(),
+            'common_parameters'=>array(),
+            'compliance'=>array(),
+            'compute_attributes'=>array(),
+            'compute_profiles'=>array(),
+            'compute_resources'=>array(),
+            'config_groups'=>array(),
+            'config_reports'=>array(),
+            'config_templates'=>array(),
+            'dashboard'=>array('methods'=>array('GET')),
+            'domains'=>array(),
+            'environments'=>array(),
+            'fact_values'=>array(),
+            'filters'=>array(),
+            'hosts'=>array(),
+            'hostgroups'=>array(),
+            'job_invocations'=>array(),
+            'job_templates'=>array(),
+            'locations'=>array(),
+            'mail_notifications'=>array(),
+            'media'=>array(),
+            'models'=>array(),
+            'operatingsystems'=>array('methods'=>array('GET')),
+            'orchestration'=>array(),
+            'organizations'=>array(),
+            'permissions'=>array(),
+            'plugins'=>array(),
+            'provisioning_templates'=>array(),
+            'ptables'=>array(),
+            'puppetclasses'=>array(),
+            'realms'=>array(),
+            'remote_execution_features'=>array(),
+            'reports'=>array(),
+            'roles'=>array(),
+            'settings'=>array(),
+            'smart_class_parameters'=>array(),
+            'smart_proxies'=>array(),
+            'smart_variables'=>array(),
+            'statistics'=>array('methods'=>array('GET')),
+            'status'=>array('methods'=>array('GET')),
+            'subnets'=>array(),
+            'template_combinations'=>array(),
+            'template_kinds'=>array('methods'=>array('GET')),
+            'templates'=>array(),
+            'usergroups'=>array(),
+            'users'=>array(),
+            // ...
+        ),
+        'api/v2'=>array(
+            'discovered_hosts'=>array(),
+            'discovery_rules'=>array(),
+        ),
+        'foreman_tasks/api'=>array(
+            'recurring_logics'=>array(),
+            'tasks'=>array(),
+        ),
+    );
+    
+    
+    protected $_aRequest=array();
+    
+    
+    // ----------------------------------------------------------------------
+    // constructor
+    // ----------------------------------------------------------------------
+    
+    
+    public function __construct($aCfg) {
+        if(!is_array($aCfg) || !count($aCfg) || !array_key_exists('api', $aCfg)){
+            die("ERROR: class ".__CLASS__." must be initialized with an array containing api config for foreman.");
+            return false;
+        }
+        $this->_aCfg=$aCfg;
+        
+        return true;
+    }
+    
+    // ----------------------------------------------------------------------
+    // private functions
+    // ----------------------------------------------------------------------
+    /**
+     * add a log messsage
+     * @global object $oLog
+     * @param  string $sMessage  messeage text
+     * @param  string $sLevel    warnlevel of the given message
+     * @return bool
+     */
+    protected function log($sMessage, $sLevel = "info") {
+        global $oCLog;
+        return $oCLog->add(basename(__FILE__) . " class " . __CLASS__ . " - " . $sMessage, $sLevel);
+    }
+    
+    /**
+     * search url prefix in $this->_aAllowedUrls by given key
+     * @param type $sFunction
+     * @return type
+     */
+    protected function _guessPrefixUrl($sFunction=false){
+        $sReturn='';
+        /*
+        if (!$sFunction){
+            $sFunction=$this->_aRequest['request'][0][0];
+        }
+         * 
+         */
+        foreach($this->_aAllowedUrls as $sPrefix=>$aUrls){
+            foreach(array_keys($aUrls) as $sKeyword){
+                if ($sFunction==$sKeyword){
+                    $sReturn=$sPrefix;
+                    break;
+                }
+            }
+        }
+        return $sReturn;
+    }
+
+    /**
+     * generate an url for foreman API request based on option keys
+     * @return string
+     */
+    protected function _generateUrl(){
+        if(!array_key_exists('request', $this->_aRequest)){
+            die('ERROR: missing key [request]');
+        }
+        $sReturn=$this->_aCfg['api'];
+        
+        $sPrefix=$this->_guessPrefixUrl();
+        $sReturn.=$sPrefix.'/';
+        
+        foreach($this->_aRequest['request'] as $aReqItem){
+            if (!array_key_exists($aReqItem[0], $this->_aAllowedUrls[$sPrefix])){
+                echo 'WARNING: wrong item: [' . $aReqItem[0]."]<br>\n";
+            }
+            $sReturn.=$aReqItem[0] ? $aReqItem[0].'/' : '';
+            if(count($aReqItem)>1){
+                $sReturn.=(int)$aReqItem[1].'/';
+            }
+        }
+        return $sReturn;
+    }
+    
+    /**
+     * add parameter for search and paging in an foreman API URL
+     * 
+     * @return string
+     */
+    protected function _generateParams(){
+        if (!array_key_exists('filter', $this->_aRequest)){
+            return '';
+        }
+        $sReturn='?';
+        
+        foreach ($this->_aRequest['filter'] as $sKey=>$value){
+            $sReturn.='&'.$sKey.'='.urlencode($value);
+        }
+        return $sReturn;
+    }
+
+    /**
+     * make an http get request and return the response body
+     * it is called by _makeRequest
+     * $aRequest contains subkeys
+     * - url
+     * - method; one of GET|POST|PUT|DELETE
+     * - postdata; for POST only
+     * 
+     * @param array   $aRequest   arrayurl for Foreman API
+     * @return string
+     */
+    protected function _httpCall($aRequest=false, $iTimeout = 15) {
+        if ($aRequest){
+            $this->_aRequest=$aRequest;
+        }
+        $this->log(__FUNCTION__ . " start <pre>".print_r($this->_aRequest,1)."</pre>");
+        if (!function_exists("curl_init")) {
+            die("ERROR: PHP CURL module is not installed.");
+        }
+        
+        $sApiUser=array_key_exists('user', $this->_aCfg) ? $this->_aCfg['user'] : false;
+        $sApiPassword=array_key_exists('password', $this->_aCfg) ? $this->_aCfg['password'] : false;
+
+        $sFullUrl=$sApiUrl.$this->_aRequest['url'];
+        $this->log(__FUNCTION__ . " ".$this->_aRequest['method']." " . $this->_aRequest['url']);
+        $ch = curl_init($this->_aRequest['url']);
+
+        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->_aRequest['method']);
+        if ($this->_aRequest['method']==='POST'){
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->_aRequest['postdata']);
+        }
+        curl_setopt($ch, CURLOPT_HEADER, 1);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
+        curl_setopt($ch, CURLOPT_TIMEOUT, $iTimeout);
+        curl_setopt($ch, CURLOPT_USERAGENT, 'IML Deployment GUI :: ' . __CLASS__);
+        if ($sApiUser){
+            curl_setopt($ch, CURLOPT_USERPWD, $sApiUser.":".$sApiPassword);
+        }
+
+        $res = curl_exec($ch);
+        $aReturn=array('info'=>curl_getinfo($ch), 'error'=>curl_error($ch));
+        curl_close($ch);
+        $this->log(__FUNCTION__ . " status ".$aReturn['info']['http_code'].' '.$this->_aRequest['method']." $sFullUrl");
+
+        $sHeader=substr($res, 0, $aReturn['info']['header_size']);
+        $aReturn['header']=explode("\n", $sHeader);
+        $aReturn['body']=str_replace($sHeader, "", $res);
+                
+        return $aReturn;
+    }
+
+    /**
+     * write debug infos if enabled
+     * @param string $sMessage
+     * @return boolean
+     */
+    protected function _writeDebug($sMessage){
+        if ($this->_bDebug){
+            echo "DEBUG :: ".__CLASS__." :: $sMessage<br>\n";
+        }
+        return true;
+    }
+
+    // ----------------------------------------------------------------------
+    // public functions :: low level
+    // ----------------------------------------------------------------------
+    
+    /**
+     * make an http(s) request to foreman and scan result for http code and
+     * content in response json; method returns an array with subkeys
+     * - info: curl info array
+     * - error: curl error message
+     * - header: http response headers
+     * - body: http response body
+     * - _json: parsed json data from response body
+     * - _OK: flag if result is OK and complete
+     * - _status: info
+     * 
+     *      * $aRequest contains subkeys
+     * - function --> to extract method and generate url
+     * - method; one of GET|POST|PUT|DELETE
+     * - postdata; for POST only
+
+     * @param array   $aRequest   arrayurl for Foreman API
+     * @return array
+     */
+    public function makeRequest($aRequest=false) {
+        if ($aRequest){
+            $this->_aRequest=$aRequest;
+        }
+        $sStatus='unknown';
+        $bOk=false;
+        
+        
+        // prevent missing data because of paging
+        if ($this->_aRequest['method']==='GET' && !array_key_exists('per_page', $this->_aRequest['filter'])){
+            $this->_aRequest['filter']['per_page']=1000;
+        }
+        // TODO check postdata
+        if ($this->_aRequest['method']==='POST' && (!array_key_exists('postdata',$this->_aRequest) || !count($this->_aRequest['postdata']))){
+            die("ERROR in ".__CLASS__."::".__FUNCTION__.": missing data to make a POST request");
+        }
+        if (!array_key_exists('url', $this->_aRequest)){
+            $this->_aRequest['url']=$this->_generateUrl().$this->_generateParams();
+        }
+
+        // ----- request
+        $this->_writeDebug(__FUNCTION__ . ' start request <pre>'.print_r($this->_aRequest,1).'</pre>');
+        $aReturn=$this->_httpCall();
+        
+        // ----- check result
+        // check status
+        $iStatuscode=$aReturn['info']['http_code'];
+        if ($iStatuscode===0){
+            $sStatus='wrong host';
+        }
+        if ($iStatuscode>=200 && $iStatuscode<400){
+            $sStatus='OK';
+            $bOk=true;
+        }
+        if ($iStatuscode>=400 && $iStatuscode<500){
+            $sStatus='error';
+        }
+        if ($iStatuscode===404){
+            $sStatus='wrong url';
+        }
+        
+        // check result json
+        if($bOk){
+            $aJson=json_decode($aReturn['body'], 1);
+            if (is_array($aJson)){
+                if (array_key_exists('total', $aJson) && $aJson['total'] > $aJson['per_page']){
+                    $bOk=false;
+                    $sStatus='Http OK, but incomplete results (paging)';
+                }
+                $aReturn['_json']=$aJson;
+            } else {
+                $bOk=false;
+                $sStatus='Http OK, but wrong response';
+            }
+        }
+        $aReturn['_OK']=$bOk;
+        $aReturn['_status']=$sStatus;
+        $this->_writeDebug(__FUNCTION__ . ' result of request <pre>'.print_r($aReturn,1).'</pre>');
+
+        return $aReturn;
+    }
+
+    /**
+     * filter output for the response based on values $this->_aRequest['response']
+     * @param array  $aData  response of $this->makeRequest();
+     * @return array
+     */
+    protected function _filterOutput($aData){
+        if(!array_key_exists('response', $this->_aRequest)){
+            return $aData;
+        }
+        $bIsList=array_key_exists('results', $aData['_json']);
+        $aTmp=$bIsList ? $aData['_json']['results'] : array($aData['_json']);
+        if(!count($aTmp)){
+            return array();
+        }
+        $aReturn=array();
+        foreach($aTmp as $aItem){
+            $aReturn[] = array_filter($aItem, function($key) {
+                return array_search($key, $this->_aRequest['response']) !==false;
+                }, ARRAY_FILTER_USE_KEY
+            );
+        }
+        return ($bIsList==1)
+                ? $aReturn 
+                : (count($aReturn) ? $aReturn[0] : array());
+    }
+
+
+    // ----------------------------------------------------------------------
+    // public foreman functions
+    // ----------------------------------------------------------------------
+
+    /**
+     * enable/ disable debugging
+     * @param boolean  $bNewDebugFlag  new value; true|false
+     * @return boolean
+     */
+    public function setDebug($bNewDebugFlag){
+        return $this->_bDebug=$bNewDebugFlag;
+    }
+
+    /**
+     * check for missing config entries
+     * @return type
+     */
+    public function selfcheck() {
+        $sOut='';
+        $sWarning='';
+        $sOut.="<h1>selfcheck</h1>";
+        $aApi=$this->read(array('request'=>array(array(''))));
+        if($aApi['_OK']){
+            foreach($aApi['_json']['links'] as $sKey=>$aCalls){
+                $sOut.="<h2>$sKey</h2><ul>";
+                foreach ($aCalls as $sLabel=>$sUrl){
+                    $sOut.="<li>$sLabel .. $sUrl ";
+                    $aTmp=preg_split('#\/#', $sUrl);
+                    $sDir2=count($aTmp)>2 ? $aTmp[2] : '??';
+                    $sDir3=count($aTmp)>3 ? $aTmp[3] : '??';
+                    $sOut.="..... " 
+                        . ($this->_guessPrefixUrl($sDir2).$this->_guessPrefixUrl($sDir3)
+                            ?'<span style="background:#cfc">OK</span>'
+                            :'<span style="background:#fcc">miss</span>' 
+                        ) . ' ' . $sDir2.', '.$sDir3 . "</li>\n";
+                    if (!($this->_guessPrefixUrl($sDir2).$this->_guessPrefixUrl($sDir3))){
+                        $sWarning.="<li>$sKey - $sLabel - $sUrl</li>";
+                    }
+                }
+                $sOut.="</ul>";
+            }
+        } else {
+            $sOut.='ERROR: unable to connect to foreman or missing permissions.<br>';
+        }
+        if ($sWarning){
+            echo 'WARNINGS:<ol>'.$sWarning.'</ol>';
+        }
+        echo $sOut;
+        return true;
+    }
+    
+    // ----------------------------------------------------------------------
+    // public foreman API CRUD functions
+    // ----------------------------------------------------------------------
+    
+    /**
+     * TODO: create
+     * @param array $aOptions
+     */
+    public function create($aOptions){
+        /*
+        $this->_aRequest=$aOptions;
+        $this->_aRequest['method']='POST';
+        return $this->makeRequest();
+         */
+    }
+    /**
+     * GETTER
+     * $aOptions ... can contain optional subkeys
+     * - request
+     *      [] list of array(keyword [,id])
+     * - filter (array)
+     *      - search (string)
+     *      - page (string)
+     *      - per_page (string)
+     *      - any attribute in the return resultset
+     * - response (array)
+     *      - list of keys, i.e. array('id', 'title')
+     * 
+     * @param array  $aOptions  
+     * @return array
+     */
+    public function read($aOptions){
+        $this->_aRequest=$aOptions;
+        $this->_aRequest['method']='GET';
+        $aData=$this->makeRequest();
+        if (!$aData['_OK']){
+            return false;
+        }
+        return $this->_filterOutput($aData);
+    }
+    
+    /**
+     * TODO
+     * @param type $aOptions
+     */
+    public function update($aOptions){
+        /*
+        $this->_aRequest=$aOptions;
+        $this->_aRequest['method']='PUT';
+        return $this->makeRequest();
+         */
+    }
+    
+    /**
+     * TODO
+     * @param type $aOptions
+     */
+    public function delete($aOptions){
+        /*
+        $this->_aRequest=$aOptions;
+        $this->_aRequest['method']='DELETE';
+        return $this->makeRequest();
+         */
+    }
+    
+}
diff --git a/public_html/deployment/classes/htmlguielements.class.php b/public_html/deployment/classes/htmlguielements.class.php
index 8c9e7afe99eee9f33b78b2b3a41207d762c47acf..35de3547ca79758062f0af3d2fb2f1c7b07790a5 100644
--- a/public_html/deployment/classes/htmlguielements.class.php
+++ b/public_html/deployment/classes/htmlguielements.class.php
@@ -55,10 +55,23 @@ class htmlguielements{
             
         ),
         'icons'=>array(
+            'workflow'=>'fa-angle-double-right',
+            'repository'=>'fa-database',
+            'phase'=>'fa-flag',
+            'package'=>'fa-cubes',
+            'version'=>'fa-tag',
+            'list'=>'fa-list',
+            
             'branch'=>'glyphicon-bookmark',
             'calendar'=>'glyphicon-calendar',
             'comment'=>'glyphicon-comment',
             'revision'=>'glyphicon-tag',
+            
+            'host'=>'fa-hdd-o',
+            'hostgroup'=>'fa-sitemap',
+            'templatefile'=>'fa-file-code-o',
+            'targetfile'=>'fa-file-o',
+            'replace'=>'fa-random',
         ),
     );
     
@@ -94,6 +107,20 @@ class htmlguielements{
         return ($sValue ? ' '.$sAttribute.'="'.$sValue.'"' : '' );
     }
     
+    /**
+     * get html attributes as string from all keys of given hash
+     * 
+     * @param array $aItem
+     * @return string
+     */
+    public function addAllAttributes($aItem){
+        $sReturn='';
+        foreach (array_keys($aItem) as $sKey){
+            $sReturn.=$this->addAttributeFromKey($sKey, $aItem);
+        }
+        return $sReturn;
+    }
+    
     // ----------------------------------------------------------------------
     // low level
     // ----------------------------------------------------------------------
@@ -113,7 +140,7 @@ class htmlguielements{
                 : ( strpos($sLabel, 'fa-')===0 ? 'fa' : '')
                 );
         if(!$sPrefix){
-            return '';
+            return $this->getIconByType($sLabel);
         }
         return '<i'.$this->addAttribute('class', ($sPrefix ? $sPrefix . ' ' : '').$sLabel).'></i> ';
     }
@@ -149,9 +176,7 @@ class htmlguielements{
         }
         
         $sReturn='<a'.$sHref;
-        foreach (array_keys($aItem) as $sKey){
-            $sReturn.=$this->addAttributeFromKey($sKey, $aItem);
-        }
+        $sReturn.=$this->addAllAttributes($aItem);
         $sReturn.='>'
                 .$sLabel
                 .'</a>';
@@ -159,7 +184,9 @@ class htmlguielements{
     }
     
     /**
-     * add default css classes and colors based on $aItem['type']
+     * add default css classes and colors based on $aItem['type'] and the
+     * local default settings in $this->aCfg
+     * 
      * @param array  $aItem
      * @return array 
      */
@@ -269,5 +296,35 @@ class htmlguielements{
                 . '<div class="tab-content">'.$sContent.'</div>'
                 . '</div>';
     }
+
+    /**
+     * get html code for a table
+     * 
+     * @param array $aTabledata  array with subkeys "header" and "body"
+     * @return string
+     */
+    public function getTable($aTabledata) {
+        $sTHead='';
+        $sTBody='';
+        if (array_key_exists('body', $aTabledata)){
+            foreach ($aTabledata['body'] as $aRow){
+                $sTBody.='<tr>';
+                foreach ($aRow as $sItem){
+                    $sTBody.='<td>'.$sItem.'</td>';
+                }
+                $sTBody.='</tr>';
+            }
+        }
+        if (array_key_exists('header', $aTabledata)){
+            foreach ($aTabledata['header'] as $sItem){
+                $sTHead.='<th>'.$sItem.'</th>';
+            }
+        }
+        return '<table class="table" style="width: auto;">'
+            .($sTHead ? '<thead>'.$sTHead.'</thead>' : '')
+            .($sTBody ? '<tbody>'.$sTBody.'</tbody>' : '')
+            .'</table>'
+            ;
+    }
         
 }
diff --git a/public_html/deployment/classes/project.class.php b/public_html/deployment/classes/project.class.php
index 03a6e5d27b5db964d207875dae9c40907b39ec57..e84a6c89c502aadba514e03cb2b91def243365c4 100644
--- a/public_html/deployment/classes/project.class.php
+++ b/public_html/deployment/classes/project.class.php
@@ -1,5 +1,8 @@
 <?php
 
+define("OPTION_DEFAULT", -999);
+define("OPTION_NONE", -1);
+
 require_once 'base.class.php';
 require_once 'htmlguielements.class.php';
 
@@ -2291,6 +2294,8 @@ class project extends base {
     private function _renderHostsData($aData) {
         $sReturn = '';
         if (array_key_exists('_hosts', $aData)) {
+            $oHtml = new htmlguielements();
+            
             // $sReturn.= print_r($aData['_hosts'], 1);
             $sReturn.= '<div class="hosts">'
                     . '<br><strong>' . t('hosts') . ':</strong><br>'
@@ -2302,7 +2307,7 @@ class project extends base {
 
                 $sReturn.= '<div class="host">'
                         . $this->_getChecksumDiv($aHostinfos['_data']['revision'])
-                        . '<i class="fa fa-cube"></i><br>'
+                        . $oHtml->getIcon('host').'<br>'
                         . $sHostname . "<br>($sAge) "
                         . '</div>'
                 ;
@@ -2942,19 +2947,14 @@ class project extends base {
         $sMessages = '';
         require_once ("formgen.class.php");
 
-        require_once("./classes/config-replacement.class.php");
-        $oConfig = new configreplacement();
-        $oConfig->setProject($this->_aConfig["id"]);
-
-
         $aForemanHostgroups = false;
         $iForemanHostgroupDefault = false;
         $sForemanHostgroupDefault = false;
         if (array_key_exists('foreman', $this->_aConfig)) {
             // echo '<pre>' . print_r($this->_aPrjConfig, 1) . '</pre>';
             $iForemanHostgroupDefault = (int) $this->_aPrjConfig['deploy']['foreman']['hostgroup'];
-            require_once('deploy-foreman.class.php');
-            $oForeman = new deployForeman($this->_aConfig['foreman']);
+            require_once('foremanapi.class.php');
+            $oForeman = new ForemanApi($this->_aConfig['foreman']);
             // $oForeman->setDebug(1);
             // $oForeman->selfcheck(); die(__FUNCTION__);
 
@@ -2972,7 +2972,7 @@ class project extends base {
                 'name' => 'deploy[foreman][hostgroup]',
                 'label' => t("foreman-hostgroup"),
                 'options' => array(
-                    '-1' => array(
+                    OPTION_NONE => array(
                         'label' => t('none'),
                     ),
                     '' => array(
@@ -3053,9 +3053,9 @@ class project extends base {
                         'type' => 'markup',
                         'value' => '<div class="tabbable">
                             <ul class="nav nav-tabs">
-                                <li class="active"><a href="#tab1" data-toggle="tab">' . t('setup-metadata') . '</a></li>
-                                <li><a href="#tab2" data-toggle="tab">' . t('build') . '</a></li>
-                                <li><a href="#tab3" data-toggle="tab">' . t('phases') . '</a></li>
+                                <li class="active"><a href="#tab1" data-toggle="tab">' . $oHtml->getIcon('list').t('setup-metadata') . '</a></li>
+                                <li><a href="#tab2" data-toggle="tab">' . $oHtml->getIcon('repository').t('repositoryinfos') . '</a></li>
+                                <li><a href="#tab3" data-toggle="tab">' . $oHtml->getIcon('phase').t('phases') . '</a></li>
                                 <!--
                                 <li><a href="#tab3" data-toggle="tab">' . t('deployment') . '</a></li>
                                 -->
@@ -3165,7 +3165,7 @@ class project extends base {
         if ($aSelectForemanGroups) {
             $aForms["setup"]["form"]['input' . $i++] = array(
                 'type' => 'markup',
-                'value' => '<h3>Default</h3>',
+                'value' => '<strong>'.t("defaults-all-phases").'</strong><br><br>',
             );
             $aForms["setup"]["form"]['input' . $i++] = $aSelectForemanGroups;
             $aForms["setup"]["form"]['input' . $i++] = array(
@@ -3191,13 +3191,13 @@ class project extends base {
                     'name' => 'phases[' . $sPhase . '][foreman-hostgroup]',
                     'label' => t("foreman-hostgroup"),
                     'options' => array(
-                        '-999' => array(
+                        OPTION_DEFAULT => array(
                             'label' => t('default') . ' (' . $sForemanHostgroupDefault . ')',
-                            'selected' => $iForemanHostgroup === -999 ? 'selected' : false,
+                            'selected' => $iForemanHostgroup === OPTION_DEFAULT ? 'selected' : false,
                         ),
-                        '-1' => array(
+                        OPTION_NONE => array(
                             'label' => t('none'),
-                            'selected' => $iForemanHostgroup === -1 ? 'selected' : false,
+                            'selected' => $iForemanHostgroup === OPTION_NONE ? 'selected' : false,
                         ),
                         '' => array(
                             'label' => '- - - - - - - - - - - - - - - - - - - - ',
@@ -3251,10 +3251,6 @@ class project extends base {
                 'value' => ''
                 . '<div id="' . $sDivId4PhaseSettings . '" ' . ($bActivePhase ? '' : ' style="display: none;"') . '">'
             );
-            $aForms["setup"]["form"]['input' . $i++] = array(
-                'type' => 'markup',
-                'value' => '<div style="clear: both"></div><div class="form-group"><h3>' . t("deploy-settings") . '</h3></div>'
-            );
             $aForms["setup"]["form"]['input' . $i++] = array(
                 'type' => 'text',
                 'name' => 'phases[' . $sPhase . '][url]',
@@ -3359,83 +3355,6 @@ class project extends base {
             if ($aSelectForemanGroups) {
                 $aForms["setup"]["form"]['input' . $i++] = $aSelectForemanHostGroup;
             }
-            $aReplacements = $oConfig->getReplacements($sPhase);
-            $sDivIdReplacement = 'divreplacements-' . $sPhase;
-            $aForms["setup"]["form"]['input' . $i++] = array(
-                'type' => 'markup',
-                'value' => '<div style="clear: both; height: 2em;"></div>'
-                . '<div class="form-group">'
-                . ($aReplacements ? '<a class="expandable closed" href="#" onclick="$(\'#' . $sDivIdReplacement . '\').slideToggle(); $(this).toggleClass(\'closed\'); return false;">' : ''
-                )
-                . '<h3>' . t("replacements") . '  (' . ($aReplacements ? count($aReplacements) : 0) . ')</h3>'
-                . ($aReplacements ? '</a>' : '')
-                . t('replacements-info')
-                . '</div>'
-            );
-
-
-            if ($aReplacements) {
-                $aForms["setup"]["form"]['input' . $i++] = array(
-                    'type' => 'markup',
-                    'value' => '<div id="' . $sDivIdReplacement . '" style="display: none;">'
-                );
-                foreach ($aReplacements as $sFile => $aFields) {
-                    $tTplFile = basename($sFile);
-                    $aValues = (array_key_exists("replace", $this->_aPrjConfig["phases"][$sPhase]) && array_key_exists($tTplFile, $this->_aPrjConfig["phases"][$sPhase]["replace"])
-                            ) ? $this->_aPrjConfig["phases"][$sPhase]["replace"][$tTplFile] : false;
-                    $aForms["setup"]["form"]['input' . $i++] = array(
-                        'type' => 'markup',
-                        'value' => '<div class="form-group"><br><h4><i class="fa fa-file-code-o"></i> ' . $tTplFile . '</h4>'
-                        // . '<textarea cols="100" rows="7" >'.file_get_contents($sFile).'</textarea>'
-                        . '</div>'
-                    );
-                    $aForms["setup"]["form"]['input' . $i++] = array(
-                        'type' => 'text',
-                        'name' => 'phases[' . $sPhase . '][replace][' . $tTplFile . '][targetfile]',
-                        'label' => t("replacement-targetfile"),
-                        'value' => $aValues && array_key_exists('targetfile', $aValues) ? $aValues['targetfile'] : '',
-                        // 'required' => 'required',
-                        'validate' => 'isastring',
-                        'size' => 100,
-                        'placeholder' => strip_tags(t("replacement-targetfile")),
-                    );
-                    $aForms["setup"]["form"]['input' . $i++] = array(
-                        'type' => 'markup',
-                        'value' => '<br>' . t("replacement-fields")
-                    );
-                    if (count($aFields)) {
-                        foreach ($aFields as $sField) {
-
-                            $aForms["setup"]["form"]['input' . $i++] = array(
-                                'type' => 'text',
-                                'disabled' => $this->oUser->hasPermission("project-action-setup-edit-replacements") ? '' : 'disabled',
-                                'name' => 'phases[' . $sPhase . '][replace][' . $tTplFile . '][' . $sField . ']',
-                                'label' => $sField,
-                                'value' => $aValues && array_key_exists($sField, $aValues) ? $aValues[$sField] : '',
-                                // 'required' => 'required',
-                                'validate' => 'isastring',
-                                'size' => 100,
-                                'placeholder' => $sField,
-                            );
-                        }
-                    } else {
-                        $aForms["setup"]["form"]['input' . $i++] = array(
-                            'type' => 'markup',
-                            'value' => '<br>' . $oHtml->getBox("error", t("replacement-fields-not-found"))
-                                // . '<pre>'.print_r($aValues, 1).'</pre>'
-                        );
-                    }
-                }
-                $aForms["setup"]["form"]['input' . $i++] = array(
-                    'type' => 'markup',
-                    'value' => '</div>'
-                );
-            } else {
-                $aForms["setup"]["form"]['input' . $i++] = array(
-                    'type' => 'markup',
-                    'value' => '<div class="form-group"><h4>' . t("none") . '</h4></div>'
-                );
-            }
 
             $aForms["setup"]["form"]['input' . $i++] = array(
                 'type' => 'markup',
diff --git a/public_html/deployment/main.css b/public_html/deployment/main.css
index 342dc40172d9fd7b0910a0f25b699b68aef26434..4ba3bc08a7b8c749a02e261b64e967a0e74c6741 100644
--- a/public_html/deployment/main.css
+++ b/public_html/deployment/main.css
@@ -238,6 +238,8 @@ input[type="radio"]:checked+label, input[type="checkbox"]:checked+label{
 .visualprocess .process .title{margin-bottom: 2em; font-weight: bold; font-size: 150%; color:#aaa;}
 .visualprocess .process .details{background: #fff; min-height: 6em;}
 
+/* ----- replacemets with templates ----- */
+span.replace{background:#fea; font-weight: bold;}
 
 /* ----- log table ----- */
 .loglevel-info{}
diff --git a/public_html/deployment/pages/act_build.php b/public_html/deployment/pages/act_build.php
index 9fbeee728a192211fbdbf7502b53fbeaa5eb5ee0..996de19faa9b3eb9f29caa7529950ef6a86ec2ea 100644
--- a/public_html/deployment/pages/act_build.php
+++ b/public_html/deployment/pages/act_build.php
@@ -57,9 +57,9 @@ if (!array_key_exists("confirm", $aParams)) {
         <table>
              <thead>
                  <tr>
-                     <th class="versioncontrol" colspan="2">' . t("versioncontrol") . '</th>
+                     <th class="versioncontrol" colspan="2">' . $oHtml->getIcon('repository').t("versioncontrol") . '<br></th>
                      <th> </th>
-                     <th class="' . $sNext . '" colspan="2">' . $sNext . '</th>
+                     <th class="' . $sNext . '" colspan="2">' . $oHtml->getIcon('phase').$sNext . '</th>
                  </tr>
              </thead>
              <tbody>
diff --git a/public_html/deployment/pages/act_htmltest.php b/public_html/deployment/pages/act_htmltest.php
index 51ac13853360d4c169b5862836f018321b043d94..3188f31ceaee2d260204a4d9c95bd33f22e6ee99 100644
--- a/public_html/deployment/pages/act_htmltest.php
+++ b/public_html/deployment/pages/act_htmltest.php
@@ -96,6 +96,16 @@ $sRows=''
         ),
     )
 );")
+   .  addHtmltestTest("Tabelle", "\$oHtml->getTable(
+    array(
+        'header'=>array('A', 'B'), 
+        'body'=>array(
+            array('Zelle A 1', 'Zelle B 1'),
+            array('Zelle A 2', 'Zelle B 2'),
+            array('Zelle A 3', 'Zelle B 3'),
+        ),
+));"
+)
 ;
 
 // ----------------------------------------------------------------------
diff --git a/public_html/deployment/pages/act_overview.php b/public_html/deployment/pages/act_overview.php
index 48ccb45ac70c8f4ef775d7f6f43a3a013e760f0c..8bff0dd4ec78ddbfade22fbce36ca92fc5e0062b 100644
--- a/public_html/deployment/pages/act_overview.php
+++ b/public_html/deployment/pages/act_overview.php
@@ -47,20 +47,20 @@ if (!array_key_exists("prj", $aParams)) {
                 <br>
         
             <ul class="nav nav-tabs">
-                <li class="active"><a href="#tab1" data-toggle="tab">' . t("way-of-packages") . '</a></li>
-                <li><a href="#tab2" data-toggle="tab">' . t("repositoryinfos") . '</a></li>
-                <li><a href="#tab3" data-toggle="tab">' . t("packages") . '</a></li>
-                <li><a href="#tab4" data-toggle="tab">' . t('phases') . '</a></li>
+                <li class="active"><a href="#tab1" data-toggle="tab">' . $oHtml->getIcon('workflow') . t("way-of-packages") . '</a></li>
+                <li><a href="#tab2" data-toggle="tab">' . $oHtml->getIcon('repository') . t("repositoryinfos") . '</a></li>
+                <li><a href="#tab3" data-toggle="tab">' . $oHtml->getIcon('package') . t("packages") . '</a></li>
+                <li><a href="#tab4" data-toggle="tab">' . $oHtml->getIcon('phase') . t('phases') . '</a></li>
             </ul>
             <br>
             <div class="tab-content">
                 <div class="tab-pane active" id="tab1">
                     '
-                    . '<h3 id="h3visual">' . t("way-of-packages") . '</h3>
+                    . '<h3 id="h3visual">' . $oHtml->getIcon('workflow') . t("way-of-packages") . '</h3>
                     ' . $oPrj->renderVisual() . '
                 </div>
                 <div class="tab-pane" id="tab2">
-                    <h3 id="h3repo">' . t("repositoryinfos") . '</h3>
+                    <h3 id="h3repo">' . $oHtml->getIcon('repository') . t("repositoryinfos") . '</h3>
                         <div style="max-width: 40em;">
                         ' . $oPrj->renderRepoInfo() . '
                         ' . $sListOfBranches . '
@@ -69,13 +69,13 @@ if (!array_key_exists("prj", $aParams)) {
                 </div>
                 
                 <div class="tab-pane" id="tab3">
-                    <h3 id="h3versions">' . t("packages") . '</h3>
+                    <h3 id="h3versions">' . $oHtml->getIcon('package') . t("packages") . '</h3>
                     ' . $oPrj->renderVersionUsage() .'
    
                 </div>
                 <div class="tab-pane" id="tab4">
                     
-                    <h3 id="h3phases">' . t("phases") . '</h3>
+                    <h3 id="h3phases">' . $oHtml->getIcon('phase') . t("phases") . '</h3>
                     '.($oPrj->getActivePhases()
                             ? '<p>' 
                                 . t("page-overview-phase-infos") 
diff --git a/public_html/deployment/pages/act_phase.php b/public_html/deployment/pages/act_phase.php
index c93965e3c38f98e5187a7143348b80cccccb75ac..24d0013dcbe3c697710a12e1068a0052b1ed24b4 100644
--- a/public_html/deployment/pages/act_phase.php
+++ b/public_html/deployment/pages/act_phase.php
@@ -13,6 +13,7 @@
 
 require_once("./classes/project.class.php");
 require_once("./inc_functions.php");
+require_once("./classes/config-replacement.class.php");
 
 
 // --- Checks
@@ -24,11 +25,201 @@ if (array_key_exists("par3", $aParams)) {
 }
 
 if ($sPhase) {
-    $sPhase = $aParams["par3"];
-    // $sFirst = $oPrj->getNextPhase();
+    $aWarnings=array();
+    $sOutReplace='';
+    
+    // ----------------------------------------------------------------------
+    // replacement
+    // ----------------------------------------------------------------------
+    
+    $oConfig = new configreplacement();
+    $oConfig->setProject($aParams["prj"], $sPhase);
+
+    $aReplacements=$oConfig->getReplacements();
+
+    $sOut.='<h3>' . $oHtml->getIcon('replace') . t("replacements") . '</h3>';
+
+    // ----------------------------------------------------------------------
+    // Links to foreman
+    // ----------------------------------------------------------------------
+    $aForeman=$oConfig->getForemanReplacements();
+    $aReplacementsForeman=$aForeman ? $aForeman['rules'] : false;
+    
+    // echo '<pre>$aReplacements = '.print_r($aReplacements, 1) . '</pre>';
+    // echo '<pre>$aForeman = '.print_r($aForeman, 1) . '</pre>';
+
+    $sOut.=($aReplacementsForeman
+            ? $oConfig->getForemanlink2Hostgroup().' '
+                .$oConfig->getForemanlink2Host()
+            : t('foreman-no-host')
+            ).'<br><br>'
+        ;
+
+    // ----------------------------------------------------------------------
+    // Loop over files
+    // ----------------------------------------------------------------------
+
+    if ($aReplacements) {
+        
+        // open all/ close all
+        if(count($aReplacements)>1){
+            $sOutReplace.=$oHtml->getLinkButton(array(
+                'onclick'=>'$(\'.divfileinfos\').slideDown(); $(\'.expandable\').removeClass(\'closed\'); this.blur(); return false;',
+                'icon'=>'fa-chevron-down',
+            ))
+            .$oHtml->getLinkButton(array(
+                'onclick'=>'$(\'.divfileinfos\').slideUp(); $(\'.expandable\').addClass(\'closed\'); this.blur(); return false;',
+                'icon'=>'fa-chevron-up',
+            ))
+            ;
+        }
+        
+        foreach ($aReplacements as $sFile => $aFields) {
+            $tTplFile = basename($sFile);
+            $bFileInForeman = $aReplacementsForeman && array_key_exists($tTplFile, $aReplacementsForeman);
+            $sDivIdFile='div4file-'.md5($sFile);
+
+            $sOutReplace.='<h4>' . 
+                    $oHtml->getLink(array(
+                        'onclick'=>'$(\'#'.$sDivIdFile.'\').slideToggle(); $(this).toggleClass(\'closed\'); return false;',
+                        'class'=>'expandable closed',
+                        'icon'=>'templatefile',
+                        'label'=>$tTplFile,
+                    )) . '</h4>'
+                    . '<div id="'.$sDivIdFile.'" class="divfileinfos" style="display: none;">'
+                    ;
+
+            // --- check: does this file in foreman exist?
+            if (!array_key_exists($tTplFile, $aReplacementsForeman)){
+                if ($aReplacementsForeman){
+                    $sOutReplace.=$oHtml->getBox('error', t('foreman-error-template-unknown'));
+                    $aWarnings[]=$tTplFile.': '.t('foreman-error-template-unknown');
+                }
+            }
+
+
+            // --- check: target file was set?
+            $aTable=array('header'=>array(), 'body'=>array());
+            if ($bFileInForeman){
+                if ($aReplacementsForeman && array_key_exists('target', $aReplacementsForeman[$tTplFile])){
+                    $sTd='<strong>'.$oHtml->getIcon('targetfile') . $aReplacementsForeman[$tTplFile]['target'].'</strong>';
+                } else {
+                    if ($bFileInForeman){
+                        $sTd=t('foreman-error-no-target');
+                        $aWarnings[]=$tTplFile.': '.t('foreman-error-no-target');
+                    } else {
+                        $sTd='-';
+                    }
+                }
+                $aTable['body'][]=array(
+                    $oHtml->getIcon('templatefile') . t('foreman-targetfile'), 
+                    $sTd
+                );
+            }
+
+            // --- loop over all replacement items of template file
+            //     and check if they exist in foreman
+            foreach ($aFields as $sField){
+                if ($aReplacementsForeman && array_key_exists($sField, $aReplacementsForeman[$tTplFile]['replace'])){
+                    $sTd=$aReplacementsForeman[$tTplFile]['replace'][$sField];
+                } else {
+                    if ($bFileInForeman){
+                        $sTd=$oHtml->getBox('error', sprintf(t('foreman-error-no-replacement-for-id'), $sField));
+                        $aWarnings[]=$tTplFile.': '.sprintf(t('foreman-error-no-replacement-for-id'), $sField);
+                    } else {
+                        $sTd='-';
+                    }
+
+                }
+                $aTable['body'][]=array(
+                    $oHtml->getIcon('replace') . $sField, 
+                    $sTd
+                );
+            }
+
+            // --- reverse check ... loop over all replacement items of foreman
+            //     and check if they exist in template
+            if ($aReplacementsForeman){
+                // for testing: create a testvalue
+                // $aReplacementsForeman[$tTplFile]['replace']['testentry']='dummy value';
+                foreach ($aReplacementsForeman[$tTplFile]['replace'] as $sField=>$sValue){
+                    if (array_search($sField, $aFields)===false){
+                        $aTable['body'][]=array(
+                            $oHtml->getIcon('replace') . 
+                            $oHtml->getBox('error', sprintf(t('foreman-error-replacement-unknown'), $sField)),
+                            $sValue
+                        );
+                        $aWarnings[]=$tTplFile.': '. sprintf(t('foreman-error-replacement-unknown'), $sField);
+                    }
+                }
+            }
+
+            // --- filecontent of template
+            $ContentFile='<br>'.$sFile.'<br><br>'
+                    .'<pre style="max-height:35em;">'
+                    . preg_replace('/(@replace\[.*\])/U', '<span class="replace">${1}</span>' , htmlentities(file_get_contents($sFile)))
+                    .'</pre>';
+
+            // --- output with tabs for a template file
+            $sOutReplace.=$oHtml->getNav(
+                array(
+                    'options'=>array('type'=>'tabs'),
+                    'tabs'=>array(
+                        $oHtml->getIcon('list') . t('replacement-fields') => '<br>'.$oHtml->getTable($aTable),
+                        $oHtml->getIcon('templatefile') . $tTplFile => $ContentFile,
+                    )                    
+                )
+            )
+            .'<br></div>';
+        }
+    } else {
+        $sOutReplace.=t('none');
+    }
+    
+    
+    // --- reverse check - do templates in foreman physically exist?
+    // for testing: create a testfile
+    // $aReplacementsForeman['dummytemplate.erb']=array();
+    if($aReplacementsForeman){
+        foreach(array_keys($aReplacementsForeman) as $sForemanTemplate){
+            
+            $bFound=false;
+            foreach (array_keys($aReplacements) as $sFile) {
+                if (basename($sFile)===$sForemanTemplate){
+                    $bFound=true;
+                    continue;
+                }
+            }
+            if (!$bFound){
+                $aWarnings[]=sprintf(t('foreman-error-missing-template'), $sForemanTemplate);
+                $sDivIdFile='div4file-'.md5($sForemanTemplate);
+
+                $sOutReplace.='<h4>' . 
+                        $oHtml->getLink(array(
+                            'onclick'=>'$(\'#'.$sDivIdFile.'\').slideToggle(); $(this).toggleClass(\'closed\'); return false;',
+                            'class'=>'expandable closed',
+                            'icon'=>'fa-warning',
+                            'label'=>$sForemanTemplate,
+                        )) . '</h4>'
+                        . '<div id="'.$sDivIdFile.'" class="divfileinfos" style="display: none;">'
+                        .$oHtml->getBox('error', sprintf(t('foreman-error-missing-template'), $sForemanTemplate))
+                        . '</div>'
+                        ;
+
+            }
+        }
+    }
 
+    if (count($aWarnings)){
+        $sOut.=$oHtml->getBox('warning', '<ul><li>'.implode('<li>', $aWarnings).'</ul>');
+    }
+    $sOut.=$sOutReplace;
+    
+    // ----------------------------------------------------------------------
+    // versions
+    // ----------------------------------------------------------------------
     $sOut.='
-    <h3>' . t("versions") . '</h3>
+    <h3>' . $oHtml->getIcon('version').t("versions") . '</h3>
     <table>
      <thead>
          <tr>
@@ -42,10 +233,15 @@ if ($sPhase) {
         </tr>
      </tbody>
     </table>
-    '
+    ';
+    
+    // ----------------------------------------------------------------------
+    // list of phases
+    // ----------------------------------------------------------------------
+    
     // show all  phases if there are more than one
-    . (count($oPrj->getActivePhases())>1
-        ? '<h3>' . t("phases") . '</h3>' .  $oPrj->renderPhaseInfo()
+    $sOut.=(count($oPrj->getActivePhases())>1
+        ? '<h3>' . $oHtml->getIcon('phase').t("phases") . '</h3>' .  $oPrj->renderPhaseInfo()
         : ''
     )
     ;