From c6aa29a02183aebf96c9151ee62622d6f358af88 Mon Sep 17 00:00:00 2001
From: hahn <axel.hahn@iml.unibe.ch>
Date: Tue, 12 Nov 2013 08:30:55 +0100
Subject: [PATCH] - free filter text in overview is case insensitive - project
 settings - file prefix is changeble only if no build was done - fixed:
 overview - show same version in several phases with a short message only

---
 public_html/deployment/act_accept.php         |  228 +-
 .../deployment/classes/formgen.class.php      |  593 +--
 .../deployment/classes/project.class.php      | 3756 +++++++++--------
 .../deployment/classes/projectlist.class.php  |  333 +-
 4 files changed, 2467 insertions(+), 2443 deletions(-)

diff --git a/public_html/deployment/act_accept.php b/public_html/deployment/act_accept.php
index 01f4f090..aa4684e5 100644
--- a/public_html/deployment/act_accept.php
+++ b/public_html/deployment/act_accept.php
@@ -1,114 +1,114 @@
-<?php
-/* ######################################################################
-
-  IML DEPLOYMENT
-
-  webgui - accept a phase to rollout it to the next phase
-
-  ---------------------------------------------------------------------
-  2013-11-08  Axel <axel.hahn@iml.unibe.ch>
-  ###################################################################### */
-
-require_once("./config/inc_projects_config.php");
-require_once("./classes/project.class.php");
-
-
-// --- Checks
-$oPrj=new project($aParams["prj"]);
-
-if (array_key_exists("par3", $aParams)){
-    $sPhase=$aParams["par3"];
-}
-
-    
-$sOut='';
-if (array_key_exists("confirm", $aParams)) {
-        $sOut.=$oPrj->accept($sPhase);
-} else {
-    
-    if (!$sPhase){
-        $sOut.='ERROR: no Phase.<br>';
-    } else {        
-        if (!$oPrj->canAcceptPhase($sPhase)){
-            $sOut.="ERROR: the phase $sPhase cannot be accepted.";
-        } else {
-            $aPhaseData=$oPrj->getPhaseInfos($sPhase);
-            $aConfig=$oPrj->getConfig();
-            $sUrl=$aConfig["phases"][$sPhase]["url"];
-            
-            $sNext=$oPrj->getNextPhase($sPhase);
-            $aPhaseData2=$oPrj->getPhaseInfos($sNext);
-
-            $sOut.='
-                   <p>
-                        URL: <a href="'.$sUrl.'">'.$sUrl.'</a><br>
-                        Die Software wurde erfolgreich getestet und soll auf die n&auml;chste
-                        Phase <span class="'.$sNext.'">'.$sNext.'</span> ausgerollt werden?<br>
-                   </p>';
-            if (
-                    array_key_exists("revision", $aPhaseData2["onhold"])
-                    && $aPhaseData2["onhold"]["revision"]==$aPhaseData["deployed"]["revision"]
-                ){
-                $sOut.=getBox("warning", "In der Queue von [$sNext] ist die Version von [$sPhase] bereits vorhanden!");
-            }
-            if (
-                    array_key_exists("revision", $aPhaseData2["ready4deployment"])
-                    && $aPhaseData2["ready4deployment"]["revision"]==$aPhaseData["deployed"]["revision"]
-                ){
-                $sOut.=getBox("warning", "Im Repo von [$sNext] ist die Version von [$sPhase] bereits vorhanden!");
-            }
-            $sOut.='
-                   <table>
-                    <thead>
-                        <tr>
-                            <th class="'.$sPhase.'">'.$sPhase.'</th>
-                            <th> </th>
-                            <th class="'.$sNext.'" colspan="2">'.$sNext.'</th>
-                        </tr>
-                    </thead>
-                    <tbody>
-                        <tr>
-                            <td class="'.$sPhase.'">
-                                auf dem Server ist installiert:<br>
-                                '.$oPrj->renderPhaseDetail($sPhase, "deployed", false).'
-                            </td>
-                            <td style="vertical-align: middle;">
-                                <img src="/deployment/images/nuvola64x64/apps/noatun.png">
-                            </td>
-                            <td class="'.$sNext.'">
-                                in der Queue:<br>
-                                '.$oPrj->renderPhaseDetail($sNext, "onhold", false).'
-                            </td>
-                            <td class="'.$sNext.'">
-                                im Repo:<br>
-                                '.$oPrj->renderPhaseDetail($sNext, "ready4deployment", false).'
-                            </td>
-                        </tr>
-                        </tbody>
-                   </table>
-                   <br>
-            ';
-            
-            
-            
-            // Eingabe Kommentare zum Deployment
-            $sOut.='
-
-                 <form action="?" method="post" enctype="multipart/form-data">
-                    <input type="hidden" name="confirm" value="1">
-                    <fieldset>
-                        <button type="submit" class="btn btn-primary btn-large" >Accept ['.$sPhase.'] and put to ['.$sNext.']</button>
-                    </fieldset>
-                 </form>
-                 ';
-        }
-    }
-}
-
-    
-$sOut.='<hr>'.aPrjHome();
-
-
-// -- Ausgabe
-$sPhpOut=$sOut;
-?>
+<?php
+/* ######################################################################
+
+  IML DEPLOYMENT
+
+  webgui - accept a phase to rollout it to the next phase
+
+  ---------------------------------------------------------------------
+  2013-11-08  Axel <axel.hahn@iml.unibe.ch>
+  ###################################################################### */
+
+require_once("./config/inc_projects_config.php");
+require_once("./classes/project.class.php");
+
+
+// --- Checks
+$oPrj=new project($aParams["prj"]);
+
+if (array_key_exists("par3", $aParams)){
+    $sPhase=$aParams["par3"];
+}
+
+    
+$sOut='';
+if (array_key_exists("confirm", $aParams)) {
+        $sOut.=$oPrj->accept($sPhase);
+} else {
+    
+    if (!$sPhase){
+        $sOut.='ERROR: no Phase.<br>';
+    } else {        
+        if (!$oPrj->canAcceptPhase($sPhase)){
+            $sOut.="ERROR: the phase $sPhase cannot be accepted.";
+        } else {
+            $aPhaseData=$oPrj->getPhaseInfos($sPhase);
+            $aConfig=$oPrj->getConfig();
+            $sUrl=$aConfig["phases"][$sPhase]["url"];
+            
+            $sNext=$oPrj->getNextPhase($sPhase);
+            $aPhaseData2=$oPrj->getPhaseInfos($sNext);
+
+            $sOut.='
+                   <p>
+                        URL: <a href="'.$sUrl.'">'.$sUrl.'</a><br>
+                        Die Software wurde erfolgreich <span class="'.$sPhase.'">'.$sPhase.'</span> getestet und soll auf die n&auml;chste
+                        Phase <span class="'.$sNext.'">'.$sNext.'</span> ausgerollt werden?<br>
+                   </p>';
+            if (
+                    array_key_exists("revision", $aPhaseData2["onhold"])
+                    && $aPhaseData2["onhold"]["revision"]==$aPhaseData["deployed"]["revision"]
+                ){
+                $sOut.=getBox("warning", "In der Queue von [$sNext] ist die Version von [$sPhase] bereits vorhanden!");
+            }
+            if (
+                    array_key_exists("revision", $aPhaseData2["ready4deployment"])
+                    && $aPhaseData2["ready4deployment"]["revision"]==$aPhaseData["deployed"]["revision"]
+                ){
+                $sOut.=getBox("warning", "Im Repo von [$sNext] ist die Version von [$sPhase] bereits vorhanden!");
+            }
+            $sOut.='
+                   <table>
+                    <thead>
+                        <tr>
+                            <th class="'.$sPhase.'">'.$sPhase.'</th>
+                            <th> </th>
+                            <th class="'.$sNext.'" colspan="2">'.$sNext.'</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        <tr>
+                            <td class="'.$sPhase.'">
+                                auf dem Server ist installiert:<br>
+                                '.$oPrj->renderPhaseDetail($sPhase, "deployed", false).'
+                            </td>
+                            <td style="vertical-align: middle;">
+                                <img src="/deployment/images/nuvola64x64/apps/noatun.png">
+                            </td>
+                            <td class="'.$sNext.'">
+                                in der Queue:<br>
+                                '.$oPrj->renderPhaseDetail($sNext, "onhold", false).'
+                            </td>
+                            <td class="'.$sNext.'">
+                                im Repo:<br>
+                                '.$oPrj->renderPhaseDetail($sNext, "ready4deployment", false).'
+                            </td>
+                        </tr>
+                        </tbody>
+                   </table>
+                   <br>
+            ';
+            
+            
+            
+            // Eingabe Kommentare zum Deployment
+            $sOut.='
+
+                 <form action="?" method="post" enctype="multipart/form-data">
+                    <input type="hidden" name="confirm" value="1">
+                    <fieldset>
+                        <button type="submit" class="btn btn-primary btn-large" >Accept ['.$sPhase.'] and put to ['.$sNext.']</button>
+                    </fieldset>
+                 </form>
+                 ';
+        }
+    }
+}
+
+    
+$sOut.='<hr>'.aPrjHome();
+
+
+// -- Ausgabe
+$sPhpOut=$sOut;
+?>
diff --git a/public_html/deployment/classes/formgen.class.php b/public_html/deployment/classes/formgen.class.php
index bff94d48..e99a2663 100644
--- a/public_html/deployment/classes/formgen.class.php
+++ b/public_html/deployment/classes/formgen.class.php
@@ -1,297 +1,298 @@
-<?php
-
-/* ######################################################################
-
-  IML DEPLOYMENT
-
-  class formgen - (copied and improved from simap prototype project)
-  It generates Form elements. This class does what I need it is not
-  feature complete.
-
-  ---------------------------------------------------------------------
-  2013-11-08  Axel <axel.hahn@iml.unibe.ch>
-  ###################################################################### */
-
-class formgen {
-
-    var $aForm = array();
-    var $sRequired = ' <span title="Eingabe ist erforderlich"><span style="color:#c00;">*</span></span>';
-
-    /**
-     * constructor
-     * @param array $aNewFormData
-     * @return boolean
-     */
-    public function __construct($aNewFormData = array()) {
-        if (count($aNewFormData))
-            return $this->setFormarray($aNewFormData);
-        return true;
-    }
-
-    /**
-     * set a new array
-     * @param array $aNewFormData
-     * @return boolean
-     */
-    public function setFormarray($aNewFormData = array()) {
-        if (!is_array($aNewFormData) || !count($aNewFormData)) {
-            return false;
-        }
-        return $this->aForm = $aNewFormData;
-    }
-
-    /**
-     * render a complete form
-     * @param string $sFormId
-     * @return string html output
-     */
-    public function renderHtml($sFormId) {
-        $sReturn = false;
-        if (!array_key_exists($sFormId, $this->aForm)) {
-            die("ERROR: " . __CLASS__ . ":" . __FUNCTION__ . " - form id " . $sFormId . " does not exist.");
-        }
-        $sReturn.='<form ';
-        if (array_key_exists("meta", $this->aForm[$sFormId])) {
-            foreach (array("method", "action", "target", "accept-charset") as $sAttr) {
-                if (array_key_exists($sAttr, $this->aForm[$sFormId]["meta"])) {
-                    $sReturn.=$sAttr . '="' . $this->aForm[$sFormId]["meta"][$sAttr] . '" ';
-                }
-            }
-        }
-        $sReturn.='>';
-        foreach ($this->aForm[$sFormId]["form"] as $elementKey => $elementData) {
-            $sReturn.=$this->renderHtmlElement($elementKey, $elementData);
-        }
-        $sReturn.='</form>';
-
-        return $sReturn;
-    }
-
-    /**
-     * add html attributes if they exist
-     * @param array $aAttributes  list of attributes to search for
-     * @param array $elementData  array of form element
-     * @return string
-     */
-    private function _addHtmlAtrributes($aAttributes, $elementData) {
-        $sReturn = false;
-        foreach ($aAttributes as $sAtrr) {
-            if (array_key_exists($sAtrr, $elementData) && $elementData[$sAtrr]) {
-                if ($sReturn)
-                    $sReturn.=' ';
-                $sReturn.=$sAtrr . '="' . $elementData[$sAtrr] . '"';
-            }
-        }
-        return $sReturn;
-    }
-
-    private function _addLabel($sLabel, $sFor, $sClass = false) {
-        $sReturn = false;
-        $sReturn = '<label for="' . $sFor . '"';
-        if ($sClass)
-            $sReturn.=' class="' . $sClass . '"';
-        $sReturn.='>' . $sLabel . '</label>';
-        $sReturn.="\n";
-        return $sReturn;
-    }
-
-    private function _checkReqiredKeys($aArray, $aRequiredKeys, $sLabel = false) {
-        $bReturn = true;
-        foreach ($aRequiredKeys as $sKey) {
-            if (!array_key_exists($sKey, $aArray)) {
-                die("ERROR: $sLabel<br>Missing key \"$sKey\" in the array of a form element:<pre>" . print_r($aArray, true) . "</pre>");
-                $bReturn = false;
-            }
-        }
-        return $bReturn;
-    }
-
-    /**
-     * render a single form element
-     * @param string $sId          id of a form element
-     * @param array  $elementData  array of form element
-     * @return string html output
-     */
-    public function renderHtmlElement($sId, $elementData) {
-        $sReturn = false;
-        $aAllowedHtmlAttributes = array();
-        $sDefaultAttributes = "class,onclick,onmouseover,onmouseout,title";
-
-        if (!array_key_exists("type", $elementData)) {
-            print_r($elementData);
-            die("ERROR: " . __CLASS__ . ":" . __FUNCTION__ . " - key &quot;type&quot; does not exist.");
-        }
-
-        $sFormElement = false;
-        $sLabelText = '';
-        $sLabelElement = false;
-
-        $sHtmlDefault = '';
-        $sHtmlTable = '';
-
-        if (array_key_exists("label", $elementData)) {
-            $sLabelText = $elementData["label"];
-            $sLabelText.=(array_key_exists("required", $elementData) && $elementData["required"]) ? $this->sRequired : '';
-        }
-
-        switch ($elementData["type"]) {
-            case "button":
-                $this->_checkReqiredKeys($elementData, array("value"));
-                $sFormElement.='    <button id="' . $sId . '" ';
-                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,checked,name"), $elementData);
-                $sFormElement.='>' . $elementData["value"] . '</button>';
-                $sFormElement.="\n";
-
-                $sHtmlDefault = $sFormElement;
-                break;
-
-            case "checkbox":
-                $this->_checkReqiredKeys($elementData, array("name"));
-                foreach ($elementData["options"] as $idOption => $aOptionData) {
-                    $sFormElement.="\n";
-                    $s = preg_replace('/\W/iu', '', $sId . $idOption);
-                    $sOptionId = preg_replace('/[äöüß]/i', '', $s);
-                    $sFormElement.='    <input type="checkbox" id="' . $sOptionId . '" value="' . $idOption . '" ';
-                    $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,checked"), $aOptionData);
-                    $sFormElement.=' name="' . $elementData["name"] . '[]"';
-                    $sFormElement.='/><label for="' . $sOptionId . '">' . $aOptionData["label"] . '</label>';
-                }
-                $sFormElement.="\n";
-                $sLabelElement.='<span class="help-block">' . $sLabelText . '</span>';
-                $sLabelElement.="\n";
-
-                $sHtmlDefault = $sLabelElement . $sFormElement;
-                $sHtmlTable = '<td>' . $sLabelText . '</td><td>' . $sFormElement . '</td>';
-                break;
-
-            case "hidden":
-                $this->_checkReqiredKeys($elementData, array("value"));
-                $sFormElement.='    <input type="hidden" id="' . $sId . '" ';
-                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "name,value"), $elementData);
-                $sFormElement.=" />";
-                $sFormElement.="\n";
-
-                $sHtmlDefault = $sFormElement . "\n";
-                break;
-
-            case "markup":
-                if (array_key_exists("value", $elementData))
-                    $sHtmlDefault = $elementData["value"] . "\n";
-                break;
-
-            case "radio":
-                $this->_checkReqiredKeys($elementData, array("name"));
-                foreach ($elementData["options"] as $idOption => $aOptionData) {
-                    $sFormElement.="\n";
-                    $s = preg_replace('/\W/iu', '', $sId . $idOption);
-                    $sOptionId = preg_replace('/[äöüß]/i', '', $s);
-                    $sFormElement.='    <input type="radio" id="' . $sOptionId . '" value="' . $idOption . '" ';
-                    $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,checked"), $aOptionData);
-                    $sFormElement.=" " . $this->_addHtmlAtrributes(explode(",", "name"), $elementData);
-                    $sFormElement.='/><label for="' . $sOptionId . '">' . $aOptionData["label"] . '</label>';
-                }
-                $sFormElement.="\n";
-
-                if ($sLabelText) {
-                    $sLabelElement.='<span class="help-block">' . $sLabelText . '</span>' . "\n";
-                }
-
-                $sHtmlTable = '<td>' . $sLabelText . '</td><td>' . $sFormElement . '</td>';
-                $sHtmlDefault = $sLabelElement . $sFormElement;
-
-                // $sReturn.=$this->_addLabel($sFormElement,$sId,"checkbox");
-                break;
-
-            case "select":
-                // HINWEIS optgroups werden nicht unterstuezt - nur einfache Listen
-                $this->_checkReqiredKeys($elementData, array("name"));
-                $sFormElement.='<select id="' . $sId . '" ';
-                ;
-                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,name"), $elementData);
-                $sFormElement.=">\n";
-                foreach ($elementData["options"] as $idOption => $aOptionData) {
-                    $s = preg_replace('/\W/iu', '', $sId . $idOption);
-                    $sOptionId = preg_replace('/[äöüß]/i', '', $s);
-                    $sFormElement.='    <option value="' . $idOption . '" ';
-                    $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes"), $aOptionData);
-                    $sFormElement.='>' . $aOptionData["label"] . '</option>' . "\n";
-                }
-                $sFormElement.="</select>\n";
-
-                if ($sLabelText) {
-                    $sLabelElement.='<span class="help-block">' . $sLabelText . '</span>' . "\n";
-                }
-
-                $sHtmlTable = '<td>' . $sLabelText . '</td><td>' . $sFormElement . '</td>';
-                $sHtmlDefault = $sLabelElement . $sFormElement;
-
-                // $sReturn.=$this->_addLabel($sFormElement,$sId,"checkbox");
-                break;
-
-            case "submit":
-                $this->_checkReqiredKeys($elementData, array("value"));
-                $sFormElement.='    <button id="' . $sId . '" class="btn btn-large btn-primary" type="submit" ';
-                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes"), $elementData);
-                $sFormElement.='>' . $elementData["value"] . '</button>';
-                $sFormElement.="\n";
-
-                $sHtmlDefault = $sFormElement;
-                break;
-
-            case "text":
-                $this->_checkReqiredKeys($elementData, array("name"));
-                $sFormElement.='    <input type="text" id="' . $sId . '" ';
-                $aAllowedHtmlAttributes["text"] = explode(",", "");
-                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,name,disabled,onkeyup,onkeydown,onchange,pattern,placeholder,required,size,value"), $elementData);
-                // $sFormElement.=$this->_addHtmlAtrributes(array("name", "value", "size", "placeholder", "required"), $elementData);
-                // IE: Return abfangen lassen
-                $sFormElement.=' onkeypress="return checkKey(event);"';
-                $sFormElement.=' />';
-                $sFormElement.="\n";
-
-                $sLabelElement = $this->_addLabel($sLabelText, $sId, "control-label");
-
-                $sHtmlDefault = $sLabelElement . '<div class="controls">' . "\n" . $sFormElement . '</div>' . "\n";
-                $sHtmlTable = '<td>' . $sLabelText . '</td><td>' . $sFormElement . '</td>';
-
-                break;
-
-            case "textarea":
-                $this->_checkReqiredKeys($elementData, array("name"));
-                $sFormElement.='    <textarea id="' . $sId . '" ';
-                $aAllowedHtmlAttributes["text"] = explode(",", "");
-                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,name,onkeyup,onkeydown,onchange,placeholder,required,cols,rows"), $elementData);
-                // $sFormElement.=$this->_addHtmlAtrributes(array("name", "value", "size", "placeholder", "required"), $elementData);
-                $sFormElement.='></textarea>';
-                $sFormElement.="\n";
-
-                $sLabelElement = $this->_addLabel($sLabelText, $sId, "control-label");
-
-                $sHtmlDefault = $sLabelElement . '<div class="controls">' . "\n" . $sFormElement . '</div>' . "\n";
-                $sHtmlTable = '<td>' . $sLabelElement . '</td><td>' . $sFormElement . '</td>';
-
-                break;
-
-            default:
-                die("ERROR: " . __CLASS__ . ":" . __FUNCTION__ . " - formelement type " . $elementData["type"] . " ist not supported (yet).");
-                break;
-        }
-
-        // Default or table mode?
-        if (array_key_exists("mode", $elementData) && $elementData["mode"] == 'table' && $sHtmlTable) {
-            $sHtmlDefault = $sHtmlTable;
-        } else {
-            if ($elementData["type"] != "button" && $elementData["type"] != "fieldset" && $elementData["type"] != "markup") {
-                $sHtmlDefault = "<fieldset>" . $sHtmlDefault . "</fieldset>\n";
-            }
-        }
-
-        $sReturn.="<!-- " . $elementData["type"] . " -->\n";
-        $sReturn.=$sHtmlDefault . "\n";
-
-        return $sReturn;
-    }
-
-}
-
+<?php
+
+/* ######################################################################
+
+  IML DEPLOYMENT
+
+  class formgen - (copied and improved from simap prototype project)
+  It generates Form elements. This class does what I need it is not
+  feature complete.
+
+  ---------------------------------------------------------------------
+  2013-11-08  Axel <axel.hahn@iml.unibe.ch>
+  ###################################################################### */
+
+class formgen {
+
+    var $aForm = array();
+    var $sRequired = ' <span title="Eingabe ist erforderlich"><span style="color:#c00;">*</span></span>';
+
+    /**
+     * constructor
+     * @param array $aNewFormData
+     * @return boolean
+     */
+    public function __construct($aNewFormData = array()) {
+        if (count($aNewFormData))
+            return $this->setFormarray($aNewFormData);
+        return true;
+    }
+
+    /**
+     * set a new array
+     * @param array $aNewFormData
+     * @return boolean
+     */
+    public function setFormarray($aNewFormData = array()) {
+        if (!is_array($aNewFormData) || !count($aNewFormData)) {
+            return false;
+        }
+        return $this->aForm = $aNewFormData;
+    }
+
+    /**
+     * render a complete form
+     * @param string $sFormId
+     * @return string html output
+     */
+    public function renderHtml($sFormId) {
+        $sReturn = false;
+        if (!array_key_exists($sFormId, $this->aForm)) {
+            die("ERROR: " . __CLASS__ . ":" . __FUNCTION__ . " - form id " . $sFormId . " does not exist.");
+        }
+        $sReturn.='<form ';
+        if (array_key_exists("meta", $this->aForm[$sFormId])) {
+            foreach (array("method", "action", "target", "accept-charset") as $sAttr) {
+                if (array_key_exists($sAttr, $this->aForm[$sFormId]["meta"])) {
+                    $sReturn.=$sAttr . '="' . $this->aForm[$sFormId]["meta"][$sAttr] . '" ';
+                }
+            }
+        }
+        $sReturn.='>';
+        foreach ($this->aForm[$sFormId]["form"] as $elementKey => $elementData) {
+            $sReturn.=$this->renderHtmlElement($elementKey, $elementData);
+        }
+        $sReturn.='</form>';
+
+        return $sReturn;
+    }
+
+    /**
+     * add html attributes if they exist
+     * @param array $aAttributes  list of attributes to search for
+     * @param array $elementData  array of form element
+     * @return string
+     */
+    private function _addHtmlAtrributes($aAttributes, $elementData) {
+        $sReturn = false;
+        foreach ($aAttributes as $sAtrr) {
+            if (array_key_exists($sAtrr, $elementData) && $elementData[$sAtrr]) {
+                if ($sReturn)
+                    $sReturn.=' ';
+                $sReturn.=$sAtrr . '="' . $elementData[$sAtrr] . '"';
+            }
+        }
+        return $sReturn;
+    }
+
+    private function _addLabel($sLabel, $sFor, $sClass = false) {
+        $sReturn = false;
+        $sReturn = '<label for="' . $sFor . '"';
+        if ($sClass)
+            $sReturn.=' class="' . $sClass . '"';
+        $sReturn.='>' . $sLabel . '</label>';
+        $sReturn.="\n";
+        return $sReturn;
+    }
+
+    private function _checkReqiredKeys($aArray, $aRequiredKeys, $sLabel = false) {
+        $bReturn = true;
+        foreach ($aRequiredKeys as $sKey) {
+            if (!array_key_exists($sKey, $aArray)) {
+                die("ERROR: $sLabel<br>Missing key \"$sKey\" in the array of a form element:<pre>" . print_r($aArray, true) . "</pre>");
+                $bReturn = false;
+            }
+        }
+        return $bReturn;
+    }
+
+    /**
+     * render a single form element
+     * @param string $sId          id of a form element
+     * @param array  $elementData  array of form element
+     * @return string html output
+     */
+    public function renderHtmlElement($sId, $elementData) {
+        $sReturn = false;
+        $aAllowedHtmlAttributes = array();
+        $sDefaultAttributes = "class,onclick,onmouseover,onmouseout,title";
+
+        if (!array_key_exists("type", $elementData)) {
+            print_r($elementData);
+            die("ERROR: " . __CLASS__ . ":" . __FUNCTION__ . " - key &quot;type&quot; does not exist.");
+        }
+
+        $sFormElement = false;
+        $sLabelText = '';
+        $sLabelElement = false;
+
+        $sHtmlDefault = '';
+        $sHtmlTable = '';
+
+        if (array_key_exists("label", $elementData)) {
+            $sLabelText = $elementData["label"];
+            $sLabelText.=(array_key_exists("required", $elementData) && $elementData["required"]) ? $this->sRequired : '';
+        }
+
+        switch ($elementData["type"]) {
+            case "button":
+                $this->_checkReqiredKeys($elementData, array("value"));
+                $sFormElement.='    <button id="' . $sId . '" ';
+                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,checked,name"), $elementData);
+                $sFormElement.='>' . $elementData["value"] . '</button>';
+                $sFormElement.="\n";
+
+                $sHtmlDefault = $sFormElement;
+                break;
+
+            case "checkbox":
+                $this->_checkReqiredKeys($elementData, array("name"));
+                foreach ($elementData["options"] as $idOption => $aOptionData) {
+                    $sFormElement.="\n";
+                    $s = preg_replace('/\W/iu', '', $sId . $idOption);
+                    $sOptionId = preg_replace('/[äöüß]/i', '', $s);
+                    $sFormElement.='    <input type="checkbox" id="' . $sOptionId . '" value="' . $idOption . '" ';
+                    $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,checked"), $aOptionData);
+                    $sFormElement.=' name="' . $elementData["name"] . '[]"';
+                    $sFormElement.='/><label for="' . $sOptionId . '">' . $aOptionData["label"] . '</label>';
+                }
+                $sFormElement.="\n";
+                $sLabelElement.='<span class="help-block">' . $sLabelText . '</span>';
+                $sLabelElement.="\n";
+
+                $sHtmlDefault = $sLabelElement . $sFormElement;
+                $sHtmlTable = '<td>' . $sLabelText . '</td><td>' . $sFormElement . '</td>';
+                break;
+
+            case "hidden":
+                $this->_checkReqiredKeys($elementData, array("value"));
+                $sFormElement.='    <input type="hidden" id="' . $sId . '" ';
+                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "name,value"), $elementData);
+                $sFormElement.=" />";
+                $sFormElement.="\n";
+
+                $sHtmlDefault = $sFormElement . "\n";
+                break;
+
+            case "markup":
+                if (array_key_exists("value", $elementData))
+                    $sHtmlDefault = $elementData["value"] . "\n";
+                break;
+
+            case "radio":
+                $this->_checkReqiredKeys($elementData, array("name"));
+                foreach ($elementData["options"] as $idOption => $aOptionData) {
+                    $sFormElement.="\n";
+                    $s = preg_replace('/\W/iu', '', $sId . $idOption);
+                    $sOptionId = preg_replace('/[äöüß]/i', '', $s);
+                    $sFormElement.='    <input type="radio" id="' . $sOptionId . '" value="' . $idOption . '" ';
+                    $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,checked"), $aOptionData);
+                    $sFormElement.=" " . $this->_addHtmlAtrributes(explode(",", "name"), $elementData);
+                    $sFormElement.='/><label for="' . $sOptionId . '">' . $aOptionData["label"] . '</label>';
+                }
+                $sFormElement.="\n";
+
+                if ($sLabelText) {
+                    $sLabelElement.='<span class="help-block">' . $sLabelText . '</span>' . "\n";
+                }
+
+                $sHtmlTable = '<td>' . $sLabelText . '</td><td>' . $sFormElement . '</td>';
+                $sHtmlDefault = $sLabelElement . $sFormElement;
+
+                // $sReturn.=$this->_addLabel($sFormElement,$sId,"checkbox");
+                break;
+
+            case "select":
+                // HINWEIS optgroups werden nicht unterstuezt - nur einfache Listen
+                $this->_checkReqiredKeys($elementData, array("name"));
+                $sFormElement.='<select id="' . $sId . '" ';
+                ;
+                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,name"), $elementData);
+                $sFormElement.=">\n";
+                foreach ($elementData["options"] as $idOption => $aOptionData) {
+                    $s = preg_replace('/\W/iu', '', $sId . $idOption);
+                    $sOptionId = preg_replace('/[äöüß]/i', '', $s);
+                    $sFormElement.='    <option value="' . $idOption . '" ';
+                    $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes"), $aOptionData);
+                    $sFormElement.='>' . $aOptionData["label"] . '</option>' . "\n";
+                }
+                $sFormElement.="</select>\n";
+
+                if ($sLabelText) {
+                    // $sLabelElement.='<span class="help-block">' . $sLabelText . '</span>' . "\n";
+                    $sLabelElement = $this->_addLabel($sLabelText, $sId, "control-label");
+                }
+
+                $sHtmlTable = '<td>' . $sLabelText . '</td><td>' . $sFormElement . '</td>';
+                $sHtmlDefault = $sLabelElement . $sFormElement;
+
+                // $sReturn.=$this->_addLabel($sFormElement,$sId,"checkbox");
+                break;
+
+            case "submit":
+                $this->_checkReqiredKeys($elementData, array("value"));
+                $sFormElement.='    <button id="' . $sId . '" class="btn btn-large btn-primary" type="submit" ';
+                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes"), $elementData);
+                $sFormElement.='>' . $elementData["value"] . '</button>';
+                $sFormElement.="\n";
+
+                $sHtmlDefault = $sFormElement;
+                break;
+
+            case "text":
+                $this->_checkReqiredKeys($elementData, array("name"));
+                $sFormElement.='    <input type="text" id="' . $sId . '" ';
+                $aAllowedHtmlAttributes["text"] = explode(",", "");
+                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,name,disabled,onkeyup,onkeydown,onchange,pattern,placeholder,required,size,value"), $elementData);
+                // $sFormElement.=$this->_addHtmlAtrributes(array("name", "value", "size", "placeholder", "required"), $elementData);
+                // IE: Return abfangen lassen
+                $sFormElement.=' onkeypress="return checkKey(event);"';
+                $sFormElement.=' />';
+                $sFormElement.="\n";
+
+                $sLabelElement = $this->_addLabel($sLabelText, $sId, "control-label");
+
+                $sHtmlDefault = $sLabelElement . '<div class="controls">' . "\n" . $sFormElement . '</div>' . "\n";
+                $sHtmlTable = '<td>' . $sLabelText . '</td><td>' . $sFormElement . '</td>';
+
+                break;
+
+            case "textarea":
+                $this->_checkReqiredKeys($elementData, array("name"));
+                $sFormElement.='    <textarea id="' . $sId . '" ';
+                $aAllowedHtmlAttributes["text"] = explode(",", "");
+                $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,name,onkeyup,onkeydown,onchange,placeholder,required,cols,rows"), $elementData);
+                // $sFormElement.=$this->_addHtmlAtrributes(array("name", "value", "size", "placeholder", "required"), $elementData);
+                $sFormElement.='></textarea>';
+                $sFormElement.="\n";
+
+                $sLabelElement = $this->_addLabel($sLabelText, $sId, "control-label");
+
+                $sHtmlDefault = $sLabelElement . '<div class="controls">' . "\n" . $sFormElement . '</div>' . "\n";
+                $sHtmlTable = '<td>' . $sLabelElement . '</td><td>' . $sFormElement . '</td>';
+
+                break;
+
+            default:
+                die("ERROR: " . __CLASS__ . ":" . __FUNCTION__ . " - formelement type " . $elementData["type"] . " ist not supported (yet).");
+                break;
+        }
+
+        // Default or table mode?
+        if (array_key_exists("mode", $elementData) && $elementData["mode"] == 'table' && $sHtmlTable) {
+            $sHtmlDefault = $sHtmlTable;
+        } else {
+            if ($elementData["type"] != "button" && $elementData["type"] != "fieldset" && $elementData["type"] != "markup") {
+                $sHtmlDefault = "<fieldset>" . $sHtmlDefault . "</fieldset>\n";
+            }
+        }
+
+        $sReturn.="<!-- " . $elementData["type"] . " -->\n";
+        $sReturn.=$sHtmlDefault . "\n";
+
+        return $sReturn;
+    }
+
+}
+
 ?>
\ No newline at end of file
diff --git a/public_html/deployment/classes/project.class.php b/public_html/deployment/classes/project.class.php
index 882359f0..4ed2b8c3 100644
--- a/public_html/deployment/classes/project.class.php
+++ b/public_html/deployment/classes/project.class.php
@@ -1,1869 +1,1889 @@
-<?php
-/* ######################################################################
-
-  IML DEPLOYMENT
-
-  class project for all actions for single project
-    * actions
-    * rendering html
-
-  ---------------------------------------------------------------------
-  2013-11-08  Axel <axel.hahn@iml.unibe.ch>
-  ###################################################################### */
-
-/**
- * class for single project
- */
-class project {
-    // ----------------------------------------------------------------------
-    // CONFIG
-    // ----------------------------------------------------------------------
-
-    /**
-     * config file
-     * @var string
-     */
-    private $_sCfgfile = "../config/inc_projects_config.php";
-
-    /**
-     * directory for project configs
-     * @var string
-     */
-    private $_sCfgdir = "../config/projects";
-
-    /**
-     * configuration ($aConfig in the config file)
-     * @var array
-     */
-    private $_aConfig = array();
-
-    /**
-     * configuration of the project (= $aProjects[ID] in the config file)
-     * @var array
-     */
-    private $_aPrjConfig = array();
-
-    /**
-     * version infos of all phases
-     * @var type 
-     */
-    private $_aData = array();
-
-    /**
-     * existing versions in the archive dir
-     * @var type 
-     */
-    private $_aVersions = array();
-
-    /**
-     * places of version infos in each deployment phase
-     * @var type 
-     */
-    private $_aPlaces = array(
-        "onhold" => "Queue",
-        "ready4deployment" => "Puppet",
-        "deployed" => "Installiert",
-    );
-    private $_iRcAll = 0;
-
-    // ----------------------------------------------------------------------
-    // constructor
-    // ----------------------------------------------------------------------
-
-    /**
-     * constructor
-     * @param string $sId  id of the project
-     */
-    public function __construct($sId = false) {
-        $this->_readConfig();
-        if ($sId)
-            $this->setProjectById($sId);
-    }
-
-    // ----------------------------------------------------------------------
-    // private functions
-    // ----------------------------------------------------------------------
-
-    /**
-     * read default config file
-     * @return boolean
-     */
-    private function _readConfig() {
-        require(__dir__ . '/' . $this->_sCfgfile);
-        $this->_aConfig = $aConfig;
-        return true;
-    }
-
-    /**
-     * validate config data
-     * @return boolean
-     */
-    private function _verifyConfig() {
-        if (!count($this->_aPrjConfig))
-            die("ERROR::CONFIG: no config was for found the project. check $aProjects in config file.");
-
-        if (!array_key_exists("packageDir", $this->_aConfig))
-            die("ERROR::CONFIG: packagedir is not defined.");
-        if (!$this->_aConfig["packageDir"])
-            die("ERROR::CONFIG: packagedir is not set.");
-        if (!file_exists($this->_aConfig["packageDir"]))
-            die("ERROR::CONFIG: packagedir does not exist: &quot;" . $this->_aConfig['packageDir'] . "&quot;.");
-
-        if (!array_key_exists("archiveDir", $this->_aConfig))
-            die("ERROR::CONFIG: key &quot;archiveDir&quot; was not found in config.");
-        if (!$this->_aConfig["archiveDir"])
-            die("ERROR::CONFIG: archiveDir is not set.");
-        if (!file_exists($this->_aConfig["archiveDir"]))
-            die("ERROR::CONFIG: archiveDir does not exist: &quot;" . $this->_aConfig['archiveDir'] . "&quot;.");
-
-        foreach (array("fileprefix", "build", "phases") as $sKey) {
-            if (!array_key_exists($sKey, $this->_aPrjConfig))
-                die("ERROR::CONFIG: key &quot;$sKey&quot; was not found in config.<br><pre>" . print_r($this->_aPrjConfig, true) . "</pre>");
-        }
-
-        // TODO: verify ausbauen
-        return true;
-    }
-
-    /**
-     * execute a commandline; returns a string of output of timestamp, command, output and returncode
-     * @param string $sCommand
-     * @return string
-     */
-    private function _execAndSend($sCommand) {
-        $sReturn = '';
-        $bUseHtml = $_SERVER ? true : false;
-
-        ob_implicit_flush(true);
-        // ob_end_flush();
-        $descriptorspec = array(
-            0 => array("pipe", "r"), // stdin is a pipe that the child will read from
-            1 => array("pipe", "w"), // stdout is a pipe that the child will write to
-            2 => array("pipe", "w")    // stderr is a pipe that the child will write to
-        );
-        flush();
-        $process = proc_open($sCommand, $descriptorspec, $pipes, realpath('./'), array());
-
-        $sReturn.="[" . date("H:i:s d.m.Y") . "] ";
-        $sReturn.=$bUseHtml ? "<strong>$sCommand</strong>" : "$sCommand";
-        $sReturn.=$bUseHtml ? "<br>" : "\n";
-
-        $sErrors = false;
-        if (is_resource($process)) {
-            while ($s = fgets($pipes[1])) {
-                $sReturn.=$s;
-                flush();
-            }
-            while ($s = fgets($pipes[2])) {
-                $sErrors.=$s;
-                flush();
-            }
-        }
-        if ($sErrors)
-            $sReturn.="STDERR:\n" . $sErrors;
-        $oStatus = proc_get_status($process);
-        $iRc = $oStatus['exitcode'];
-        $this->_iRcAll += $iRc;
-        $sReturn.="[" . date("H:i:s d.m.Y") . "] exitcode " . $iRc;
-        if ($bUseHtml) {
-            if ($iRc == 0) {
-                $sReturn = '<pre class="cli">' . $sReturn;
-            } else {
-                $sReturn = '<pre class="cli error">' . $sReturn;
-            }
-            $sReturn.='</pre>';
-        }
-
-        flush();
-        return $sReturn;
-    }
-
-    // ----------------------------------------------------------------------
-    // GETTER
-    // ----------------------------------------------------------------------
-
-    private function _getConfigFile($sId) {
-        if (!$sId)
-            die("_getConfigFile requires an ID");
-        return __dir__ . '/' . $this->_sCfgdir . '/' . $sId . ".json";
-    }
-
-    /**
-     * get a full ath for temp directory (for a build)
-     * @return string
-     */
-    private function _getTempDir() {
-        return $s = $this->_getBuildDir() . '/' . $this->_aPrjConfig["fileprefix"] . "_" . date("Ymd-His");
-    }
-
-    /**
-     * get full path where the project builds are (a build setes a subdir)
-     * @return string
-     */
-    private function _getBuildDir() {
-        return $this->_aConfig['buildDir'] . '/' . $this->_aConfig["id"];
-    }
-
-    /**
-     * get full path where the project default files are
-     * @return type
-     */
-    private function _getDefaultsDir() {
-        $s = $this->_aConfig['buildDefaultsDir'] . '/' . $this->_aConfig["id"];
-        return file_exists($s) ? $s : false;
-    }
-
-    /**
-     * get directory for infofile and package (without extension)
-     * @param string $sPhase  one of preview|stage|live ...
-     * @param string $sPlace  one of onhold|ready4deployment|deployed
-     * @return string
-     */
-    private function _getFileBase($sPhase, $sPlace) {
-        if (!array_key_exists($sPhase, $this->_aConfig["phases"])) {
-            die("ERROR: _getFileBase - this phase does not exist: $sPhase.");
-        }
-        if (!array_key_exists($sPlace, $this->_aPlaces)) {
-            die("ERROR: _getFileBase - this place does not exist: $sPhase.");
-        }
-
-
-        // local file for onhold|ready4deployment
-        $sBase = $this->_aConfig['packageDir'] . "/" . $sPhase . "/" . $this->_aPrjConfig["fileprefix"];
-
-        if ($sPlace == "onhold")
-            $sBase.="_onhold";
-        // $sBase .= "/" . $this->_aPrjConfig["fileprefix"];
-        // url for deployed
-        if ($sPlace == "deployed") {
-            if ($this->isActivePhase($sPhase) && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) {
-                $sBase = $this->_aPrjConfig["phases"][$sPhase]["url"] . $this->_aPrjConfig["fileprefix"];
-            } else {
-                $sBase = '';
-            }
-        }
-
-        return $sBase;
-    }
-
-    /**
-     * get filename for info file
-     * @param string $sPhase  one of preview|stage|live ...
-     * @param string $sPlace  one of onhold|ready4deployment|deployed
-     * @return string
-     */
-    private function _getInfofile($sPhase, $sPlace) {
-        $sBase = $this->_getFileBase($sPhase, $sPlace);
-        return $sBase ? $sBase . '/' . $this->_aPrjConfig["fileprefix"] . '.json' : false;
-    }
-
-    /**
-     * get filename for package file
-     * @param string $sPhase  one of preview|stage|live ...
-     * @param string $sPlace  one of onhold|ready4deployment|deployed
-     * @return string
-     */
-    private function _getPackagefile($sPhase, $sPlace) {
-        $sBase = $this->_getFileBase($sPhase, $sPlace);
-        return $sBase ? $sBase . '/' . $this->_aPrjConfig["fileprefix"] . '.tgz' : false;
-    }
-
-    /**
-     * get full path of a packed project archive
-     * @param type $sTimestamp
-     * @return type
-     */
-    private function _getArchiveDir($sTimestamp) {
-        if (!$sTimestamp) {
-            die("ERROR: getArchiveDir timestamp is required");
-        }
-        return $this->_getProjectArchiveDir() . '/' . $sTimestamp;
-    }
-
-    /**
-     * get the directory for archive files of this project
-     * @return string
-     */
-    public function _getProjectArchiveDir() {
-        return $this->_aConfig["archiveDir"] . '/' . $this->_aConfig["id"];
-    }
-
-    /**
-     * get all existing versions in archive and its usage
-     * versions are array keys; where they are used is written in values
-     * @return array
-     */
-    public function getVersions() {
-
-        // --- read all file entries
-        $aReturn = array();
-        $sDir = $this->_getProjectArchiveDir();
-        if (is_dir($sDir)) {
-            foreach (scandir($sDir) as $sEntry) {
-                if (is_dir($sDir . '/' . $sEntry) && $sEntry != '.' && $sEntry != '..')
-                    $aReturn[$sEntry] = false;
-            }
-        }
-        $this->_aVersions = $aReturn;
-
-        // --- check version in all phases 
-        $this->getAllPhaseInfos();
-        foreach ($this->_aData["phases"] as $sPhase => $aData) {
-            foreach (array_keys($this->_aPlaces) as $sPlace) {
-                if (array_key_exists($sPlace, $aData) && array_key_exists("version", $aData[$sPlace])) {
-                    $this->_aVersions[$aData[$sPlace]["version"]][] = array('phase' => $sPhase, 'place' => $sPlace);
-                }
-            }
-        }
-        ksort($this->_aVersions);
-        return $this->_aVersions;
-    }
-
-    /**
-     * recursive delete 
-     * @param type $dir
-     * @return type
-     */
-    private function _rmdir($dir) {
-        foreach (scandir($dir) as $sEntry) {
-            if (is_dir($dir . '/' . $sEntry) && $sEntry != '.' && $sEntry != '..') {
-                $this->_rmdir($dir . '/' . $sEntry);
-            } elseif (is_file($dir . '/' . $sEntry) || is_link($dir . '/' . $sEntry))
-                unlink($dir . '/' . $sEntry);
-        }
-        return rmdir($dir);
-    }
-
-    /**
-     * cleanup of archive directory; it returns the list of deleted
-     * directories as array
-     * @return array
-     */
-    public function cleanupArchive() {
-        $aUnused = array();
-        $sDir = $this->_getProjectArchiveDir();
-        $this->getVersions();
-
-        $aDelete = array();
-        // find unused versions
-        foreach ($this->_aVersions as $sVersion => $aUsage) {
-            if (!$aUsage || count($aUsage) == 0) {
-                $aUnused[] = $sVersion;
-            }
-        }
-
-        // keep a few
-        while (count($aUnused) > $this->_aConfig["versionsToKeep"]) {
-            $sVersion = array_shift($aUnused);
-            $sDir2 = $sDir . '/' . $sVersion;
-            if ($this->_rmdir($sDir2)) {
-                $aDelete[] = $sDir2;
-            } else {
-                echo "Warning: unable to delete Archive $sDir2<br>";
-            };
-        }
-
-        // rescan versions
-        if (count($aDelete)) {
-            $this->getVersions();
-        }
-
-        return $aDelete;
-    }
-
-    /**
-     * cleanup of archive directory; it returns the list of deleted
-     * directories as array
-     * @return array
-     */
-    public function cleanupBuilds() {
-        $sDir = $this->_getBuildDir();
-        $aDirlist = array();
-        $aDelete = array();
-        if (is_dir($sDir)) {
-            foreach (scandir($sDir) as $sEntry) {
-                if (is_dir($sDir . '/' . $sEntry) && $sEntry != '.' && $sEntry != '..')
-                    $aDirlist[] = $sEntry;
-            }
-        }
-
-        // keep a few
-        while (count($aDirlist) >= $this->_aConfig["builtsToKeep"]) {
-            $sVersion = array_shift($aDirlist);
-            $sDir2 = $sDir . '/' . $sVersion;
-            if ($this->_rmdir($sDir2)) {
-                $aDelete[] = $sDir2;
-            } else {
-                echo "Warning: unable to delete Build $sDir2<br>";
-            };
-        }
-
-        return $aDelete;
-    }
-
-    /**
-     * get conmplete config of the project
-     * @return array
-     */
-    public function getConfig() {
-        return $this->_aPrjConfig;
-    }
-
-    /**
-     * get name/ label of the project
-     * @return string
-     */
-    public function getLabel() {
-        return $this->_aPrjConfig["label"];
-    }
-
-    /**
-     * get description of the project
-     * @return string
-     */
-    public function getDescription() {
-        return $this->_aPrjConfig["description"];
-    }
-
-    /**
-     * get deploy and queue infos for all phases
-     * @return type
-     */
-    public function getAllPhaseInfos() {
-        if (!array_key_exists("phases", $this->_aData))
-            $this->_aData["phases"] = array();
-
-        foreach (array_keys($this->_aConfig["phases"]) as $sPhase) {
-            if (!array_key_exists($sPhase, $this->_aData["phases"])) {
-                $this->getPhaseInfos($sPhase);
-            }
-        }
-        return $this->_aData["phases"];
-    }
-
-    /**
-     * get statusinfos of a named phase
-     * @param string $sPhase name of the phase; one of preview|stage|live
-     * @return array
-     */
-    public function getPhaseInfos($sPhase) {
-        if (!$sPhase) {
-            die("ERROR: project->getPhaseInfos - parameter for phase is required");
-        }
-        if (!array_key_exists("phases", $this->_aData))
-            $this->_aData["phases"] = array();
-
-        if (!array_key_exists($sPhase, $this->_aData["phases"])) {
-
-            $this->_aData["phases"][$sPhase] = array();
-            $aTmp = array();
-
-            // a blocked package is waiting for deployment timeslot?
-            $sKey = "onhold";
-            $sJsonfile = $this->_getInfofile($sPhase, $sKey);
-            $aTmp[$sKey] = array();
-            if (file_exists($sJsonfile)) {
-                $aJson = json_decode(file_get_contents($sJsonfile), true);
-                if (array_key_exists("version", $aJson)) {
-                    $aTmp[$sKey] = $aJson;
-                    $aTmp[$sKey]["infofile"] = $sJsonfile;
-                    $aTmp[$sKey]["ok"] = 1;
-                } else {
-                    $aTmp[$sKey]["error"] = "info file $sJsonfile exists but is corrupt (no version).<pre>" . print_r($aJson, true) . "</pre>";
-                }
-            } else {
-                $aTmp[$sKey]["info"] = "No package is waiting in the queue.";
-                $aTmp[$sKey]["ok"] = 1;
-            }
-
-            // package for puppet
-            $sKey = "ready4deployment";
-            $sJsonfile = $this->_getInfofile($sPhase, $sKey);
-            $aTmp[$sKey] = array();
-            if (file_exists($sJsonfile)) {
-                $sPkgfile = $this->_getPackagefile($sPhase, $sKey);
-                if (file_exists($sPkgfile)) {
-                    $aJson = json_decode(file_get_contents($sJsonfile), true);
-                    if (is_array($aJson) && array_key_exists("version", $aJson)) {
-                        $aTmp[$sKey] = $aJson;
-                        $aTmp[$sKey]["infofile"] = $sJsonfile;
-                        $aTmp[$sKey]["packagefile"] = $sPkgfile;
-                        $aTmp[$sKey]["ok"] = 1;
-                    } else {
-                        $aTmp[$sKey]["error"] = "info file $sJsonfile exists but is corrupt (no version).<pre>" . print_r($aJson, true) . "</pre>";
-                    }
-                } else {
-                    $aTmp[$sKey]["error"] = "package file was not found: $sPkgfile";
-                }
-            } else {
-                $aTmp[$sKey]["error"] = "info file was not found: $sJsonfile";
-            }
-
-            // published data
-            $sKey = "deployed";
-            $sJsonfile = $this->_getInfofile($sPhase, $sKey);
-            $aTmp[$sKey] = array();
-            if ($this->isActivePhase($sPhase)) {
-                $sJsonUrl = $this->_aPrjConfig["phases"][$sPhase]["url"] . $this->_aPrjConfig["fileprefix"] . ".json";
-                $sJsonData = @file_get_contents($sJsonUrl);
-                if ($sJsonData) {
-                    $aJson = json_decode($sJsonData, true);
-                    if (array_key_exists("version", $aJson)) {
-                        $aTmp[$sKey] = $aJson;
-                        $aTmp[$sKey]["infofile"] = $sJsonUrl;
-                        $aTmp[$sKey]["ok"] = 1;
-                    } else {
-                        $aTmp[$sKey]["error"] = "json url was readable $sJsonUrl but is corrupt (no version).<pre>" . print_r($aJson, true) . "</pre>";
-                    }
-                } else {
-                    $aTmp[$sKey]["error"] = "json url not readable $sJsonUrl";
-                }
-            } else {
-                $aTmp[$sKey]["warning"] = "this phase is not active or has no url";
-            }
-
-            $this->_aData["phases"][$sPhase] = $aTmp;
-        }
-        return $this->_aData["phases"][$sPhase];
-    }
-
-    /**
-     * get a list of all existing projects as a flat array
-     * <code>
-     * print_r($oPrj->getProjects());
-     * </code>
-     * returns<br>
-     * Array ( [0] => project1 [1] => project2 ) 
-     * @return array
-     */
-    public function getProjects() {
-        $aReturn = array();
-        foreach (glob(__dir__ . '/' . $this->_sCfgdir . "/*.json") as $filename) {
-            $aReturn[] = str_replace(".json", "", basename($filename));
-        }
-        sort($aReturn);
-        return $aReturn;
-    }
-
-    /**
-     * check if the given phase is active for this project
-     * @param type $sPhase
-     * @return type
-     */
-    public function isActivePhase($sPhase) {
-        return (
-                array_key_exists($sPhase, $this->_aPrjConfig["phases"]) && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase]) && $this->_aPrjConfig["phases"][$sPhase]["url"]
-                );
-    }
-
-    /**
-     * return array of all (active and inactive) phases
-     * @return type
-     */
-    public function getPhases() {
-        return $this->_aConfig["phases"];
-    }
-
-    /**
-     * get a flat array with active phases of the project
-     * @return array
-     */
-    public function getActivePhases() {
-        $aReturn = array();
-        foreach (array_keys($this->_aConfig["phases"]) as $s) {
-            if ($this->isActivePhase($s)) {
-                $aReturn[] = $s;
-            }
-        }
-        return $aReturn;
-    }
-
-    /**
-     * find the next active phase of a project
-     * @param string $sPhase current phase; if empty the function sends back the first phase
-     */
-    public function getNextPhase($sPhase = false) {
-        if ($sPhase) {
-            if (!array_key_exists($sPhase, $this->_aConfig["phases"])) {
-                die("ERROR: this phase does not exist: $sPhase.");
-            }
-        }
-
-        $sNextPhase = false;
-        $bUseNextPhase = $sPhase ? false : true;
-        foreach (array_keys($this->_aConfig["phases"]) as $s) {
-            if ($bUseNextPhase) {
-                if ($this->isActivePhase($s)) {
-                    $sNextPhase = $s;
-                    $bUseNextPhase = false;
-                    continue;
-                }
-            }
-            if ($sPhase == $s) {
-                $bUseNextPhase = true;
-            }
-        }
-
-        return $sNextPhase;
-    }
-
-    /**
-     * check: is the deployment to the next phase enabled for this phase?
-     * @param type $sPhase
-     */
-    public function canAcceptPhase($sPhase = false) {
-
-        if (!$sPhase) {
-            $sNext = $this->getNextPhase($sPhase);
-            return $sNext > '';
-        }
-
-
-        if (!array_key_exists($sPhase, $this->_aConfig["phases"])) {
-            die("ERROR: in canAcceptPhase this phase does not exist: $sPhase.");
-        }
-        if (!$this->isActivePhase($sPhase)) {
-            // die("ERROR: the phase $sPhase is not active in this project.");
-            return false;
-        }
-        $sNext = $this->getNextPhase($sPhase);
-        if (!$sNext)
-            return false;
-
-        // ensure that _aData is filled
-        $this->getPhaseInfos($sPhase);
-
-        // array key "ok" must be in the ready4deployment and deployed info
-        if (
-                array_key_exists($sPhase, $this->_aData["phases"]) && array_key_exists("onhold", $this->_aData["phases"][$sPhase]) && array_key_exists("ready4deployment", $this->_aData["phases"][$sPhase]) && array_key_exists("deployed", $this->_aData["phases"][$sPhase]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["onhold"]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["ready4deployment"]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["deployed"])
-        )
-            return true;
-
-        return false;
-    }
-
-    /**
-     * get current revision from remote repo
-     * @return string
-     */
-    public function getRepoRevision($bRefresh = false) {
-        $sReturn = "";
-        if (
-                array_key_exists("source", $this->_aData["phases"]) && $this->_aData["phases"]["source"] && $bRefresh == false
-        )
-            return $this->_aData["phases"]["source"];
-        switch ($this->_aPrjConfig["build"]["type"]) {
-            case "git":
-                $sKeyfile = dirname(dirname(__file__)) . "/" . $this->_aPrjConfig["build"]["keyfile"];
-                $sWrapper = dirname(dirname(dirname(dirname(__file__)))) . "/shellscripts/gitsshwrapper.sh";
-                $sGitCmd = "git ls-remote --heads " . $this->_aPrjConfig["build"]["ssh"] . "  master | awk '{ print $1 }'";
-                $sRevision = shell_exec("export GIT_SSH=$sWrapper ; export PKEY=$sKeyfile; $sGitCmd");
-                $sReturn = trim($sRevision);
-                /*
-                  $sGitCmd="export GIT_SSH=$sWrapper ; ";
-                  $sGitCmd.="export PKEY=$sKeyfile ; ";
-                  $sGitCmd.="export testdir=/tmp/test_\$\$ ; set -vx ; ";
-                  $sGitCmd.="mkdir \$testdir && cd \$testdir && ";
-                  $sGitCmd.="git init && ";
-                  $sGitCmd.="git remote add origin " . $this->_aPrjConfig["build"]["ssh"] . " && ";
-                  // $sGitCmd.="git clean -f && ";
-                  // $sGitCmd.="git pull ; ";
-                  // $sGitCmd.="sleep 1 ; ";
-                  $sGitCmd.="git log -1 --format=fuller origin/master remote; ";
-                  // $sGitCmd.="cd /tmp ; rm -rf \$testdir ";
-                  $sReturn.= $this->_execAndSend($sGitCmd);
-                 */
-                $this->_aData["phases"]["source"] = $sReturn;
-                break;
-            default:
-                die("getRepoRevision(): Build Type not supported: " . $this->_aPrjConfig["build"]["type"] . $sReturn);
-        }
-        return $sReturn;
-    }
-
-    // ----------------------------------------------------------------------
-    // SETTER
-    // ----------------------------------------------------------------------
-
-    /**
-     * apply a config
-     * @param array $aConfig
-     * @return boolean
-     */
-    public function setProjectById_OLD($sId) {
-        $this->_aPrjConfig = array();
-        require(__dir__ . '/' . $this->_sCfgfile);
-        if (!array_key_exists("$sId", $aProjects)) {
-            die("ERROR: a project with ID $sId does not exist.");
-        }
-        $this->_aPrjConfig = $aProjects[$sId];
-        $this->_aConfig["id"] = $sId;
-        $this->_verifyConfig();
-        return true;
-    }
-
-    /**
-     * apply a config
-     * @param array $aConfig
-     * @return boolean
-     */
-    public function setProjectById($sId) {
-        $this->_aPrjConfig = array();
-        require(__dir__ . '/' . $this->_sCfgfile);
-        $this->_aConfig["id"] = $sId;
-
-
-        /*
-          if (!array_key_exists("$sId", $aProjects)) {
-          die("ERROR: a project with ID $sId does not exist.");
-          }
-          $this->_aPrjConfig = $aProjects[$sId];
-          if (file_exists($this->_getConfigFile($sId))) {
-          die("ERROR: a project with ID $sId does not exist.");
-          }
-         */
-
-        $this->_aPrjConfig = json_decode(file_get_contents($this->_getConfigFile($sId)), true);
-        // $aData=json_decode(file_get_contents($this->_getConfigFile($sId)), true);
-        // echo "<pre>" . print_r($aData, true) . "</pre>";
-
-        $this->_verifyConfig();
-        return true;
-    }
-
-    // ----------------------------------------------------------------------
-    // ACTIONS
-    // ----------------------------------------------------------------------
-
-    /**
-     * get html code of a div around a message
-     * @param string $sWarnlevel one of error|success|info|warning to get a colored box
-     * @param string $sMessage   message txt
-     * @return string
-     */
-    public function getBox($sWarnlevel, $sMessage) {
-        $aCfg = array(
-            "error" => array("class" => "alert alert-error", "prefix" => "ERROR :-("),
-            "success" => array("class" => "alert alert-success", "prefix" => "SUCCESS :-)"),
-            "info" => array("class" => "alert alert-info", "prefix" => "INFO"),
-            "warning" => array("class" => "alert alert-block", "prefix" => "WARNING"),
-        );
-        $sClass = "";
-        $sPrefix = "";
-        if (array_key_exists($sWarnlevel, $aCfg)) {
-            $sClass = $aCfg[$sWarnlevel]["class"];
-            $sPrefix = $aCfg[$sWarnlevel]["prefix"];
-            $sMessage = '<strong>' . $aCfg[$sWarnlevel]["prefix"] . '</strong> ' . $sMessage;
-        }
-        return '
-            <div class="' . $sClass . '">
-                ' . $sMessage . '
-            </div>';
-    }
-
-    /**
-     * Build a new package for the deployment. It will be put to the queue
-     * of the first active phase (i.e. preview).
-     * If there is no deployment time range it will be deployed too.
-     * @global type $aParams
-     * @return boolean|string
-     */
-    public function build() {
-        global $aParams;
-        $sReturn = false;
-
-        $this->_iRcAll = 0;
-        // return $this->_execAndSend("bash --login -c 'ruby --version' " . $sTempDir);
-
-        $sReturn = "<h2>Build " . $this->getLabel() . "</h2>";
-
-        // --------------------------------------------------
-        // create workdir
-        // --------------------------------------------------
-        $aDirs = $this->cleanupBuilds();
-        if (count($aDirs))
-            $sReturn.='<h3>Cleanup older failed builds</h3><pre>' . print_r($aDirs, true) . '</pre>';
-
-        $sTempDir = $this->_getTempDir();
-        $sFirstLevel = $this->getNextPhase();
-        if (!$sFirstLevel)
-            return false;
-
-        $sReturn.="<h3>Create a temporary build dir</h3>";
-        if (!file_exists($sTempDir)) {
-            $sReturn.=$this->_execAndSend("mkdir -p " . $sTempDir);
-        }
-        $sReturn.=$this->_execAndSend("ls -ld " . $sTempDir);
-        if (!file_exists($sTempDir)) {
-            return $this->getBox("error", "$sTempDir was not created." . $sReturn);
-        }
-
-        // --------------------------------------------------
-        // checkout
-        // --------------------------------------------------
-        switch ($this->_aPrjConfig["build"]["type"]) {
-            case "git":
-
-                $sReturn.="<h3>Checkout a GIT project</h3>";
-                // $sReturn.=$this->_execAndSend("find " . $this->_aConfig["workDir"]);
-                // SKIP $sReturn.=$this->_execAndSend("cd $sTempDir && git init");
-
-                $sKeyfile = dirname(dirname(__file__)) . "/" . $this->_aPrjConfig["build"]["keyfile"];
-                $sWrapper = dirname(dirname(dirname(dirname(__file__)))) . "/shellscripts/gitsshwrapper.sh";
-                // $sReturn.=$this->_execAndSend("cd $sTempDir && export GIT_SSH=$sWrapper ; export PKEY=$sKeyfile; git pull " . $this->_aPrjConfig["build"]["ssh"]);
-                $sReturn.=$this->_execAndSend("export GIT_SSH=$sWrapper ; export PKEY=$sKeyfile; git clone " . $this->_aPrjConfig["build"]["ssh"] . " $sTempDir ");
-
-                // $sVersion=$this->_execAndSend("cd $sTempDir && export GIT_SSH=$sWrapper ; export PKEY=$sKeyfile; git pull " . $this->_aPrjConfig["build"]["ssh"]);
-                // control: directory listing after checkout
-                $sReturn.=$this->_execAndSend("ls -lisa $sTempDir");
-
-                // fetch version infos
-
-                $sRevision = shell_exec("cd $sTempDir && git log -n 1 --pretty=format:\"%H\"");
-                $sCommitMsg = shell_exec("cd $sTempDir && git log -1");
-                $sCommitMsg = str_replace("\n", "<br>", $sCommitMsg);
-                $sCommitMsg = str_replace('"', "&quot;", $sCommitMsg);
-                // $sCommitMsg = str_replace('<', "&lt;", $sCommitMsg);
-                // $sCommitMsg = str_replace('>', "&gt;", $sCommitMsg);
-                $sReturn.=$this->getBox("info", $sCommitMsg);
-
-                break;
-
-            default:
-                return $this->getBox("error", "Build Type not supported: " . $this->_aPrjConfig["build"]["type"] . $sReturn);
-        }
-        if (!$this->_iRcAll == 0) {
-            return $this->getBox("error", "checkout failed.</h3>One of the commands failed (see above).<br>You can ask the sysadmins and analyze with them the created temp directory &quot;' . $sTempDir . '&quot;." . $sReturn);
-        }
-        $sReturn.=$this->getBox("success", "Checkout OK!");
-
-
-        // --------------------------------------------------
-        // copy default structure
-        // --------------------------------------------------
-        $sReturn.="<h3>get default data</h3>";
-        if ($this->_getDefaultsDir()) {
-            $sReturn.='get data from ' . $this->_getDefaultsDir() . '<br>';
-            $sReturn.=$this->_execAndSend("find " . $this->_getDefaultsDir());
-            $sReturn.=$this->_execAndSend("rsync -r " . $this->_getDefaultsDir() . "/* $sTempDir");
-            // $sReturn.=$this->_execAndSend("find $sTempDir");
-        } else {
-            $sReturn.='No defaults ... starting with empty directory.<br>';
-        }
-
-        // --------------------------------------------------
-        // execute hook
-        // --------------------------------------------------
-        $sHookfile = $this->_aConfig['hooks']['build-aftercheckout'];
-        $sReturn.='<h3>Execute Hook ' . $sHookfile . '</h3>';
-        if (file_exists($sTempDir . '/' . $sHookfile)) {
-            $sReturn.=$this->_execAndSend('cd ' . $sTempDir . ' && chmod 755 hooks/on*');
-            $sReturn.=$this->_execAndSend('bash --login -c \'' . $sTempDir . '/' . $sHookfile . '\'');
-            if (!$this->_iRcAll == 0) {
-                return $this->getBox("error", "executing hook failed. One of the commands failed.<br>You can ask the sysadmins and analyze with them the created temp directory &quot;' . $sTempDir . '&quot;." . $sReturn);
-            }
-        } else {
-            $sReturn.='SKIP. Hook was not found.<br>';
-        }
-
-
-        // --------------------------------------------------
-        // TODO: cleanup .git, .svn, ...?
-        // wenn es kein .git gibt, bricht er ab...
-        // --------------------------------------------------
-        $sReturn.="<h3>cleanup project</h3>";
-        $sReturn.=$this->_execAndSend("cd $sTempDir && rm -rf .git");
-        // $sReturn.=$this->_execAndSend("cd $sTempDir && rm -rf .svn");
-        // $sReturn.=$this->_execAndSend("find  $sTempDir -type d -name '.svn' -exec rm -rf {} \;");
-        // public_html must exist
-        $sReturn.="<h3>check public and public_html</h3>";
-
-        $sWebroot = false;
-        $sWebroot1 = $sTempDir . '/public_html';
-        $sWebroot2 = $sTempDir . '/public';
-        if (file_exists($sWebroot1)) {
-            $sWebroot = $sWebroot1;
-        }
-        if (file_exists($sWebroot2)) {
-            $sWebroot = $sWebroot2;
-        }
-
-        if (!$sWebroot) {
-            return $this->getBox("error", "a subdir &quot;public_html&quot; or &quot;public&quot; does not exist." . $sReturn);
-        }
-
-        if (!$this->_iRcAll == 0) {
-            return $this->getBox("error", "build failed - working directory has errors and is not ready to create package.<br>You can ask the sysadmins and analyze with them the created temp directory &quot;$sTempDir&quot;" . $sReturn);
-        }
-        $sReturn.=$this->getBox("success", "preparations ok - directory is ready for packaging now.");
-
-        // --------------------------------------------------
-        // create package
-        // --------------------------------------------------
-        $sReturn.='<h3>Create package</h3>';
-
-        // generate info file
-        $sTs = date("Y-m-d H:i:s");
-        $sTs2 = date("Ymd_His");
-        $sInfoFileWebroot = $sWebroot . '/' . basename($this->_getInfofile($sFirstLevel, "deployed"));
-        $sInfoFileArchiv = $this->_getArchiveDir($sTs2) . '/' . basename($this->_getInfofile($sFirstLevel, "deployed"));
-        $sPackageFileArchiv = $this->_getArchiveDir($sTs2) . '/' . basename($this->_getPackagefile($sFirstLevel, "deployed"));
-
-        $sInfos = '{
-            "date": "' . $sTs . '",
-            "version": "' . $sTs2 . '",
-            "revision": "' . $sRevision . '",
-            "message": "' . $sCommitMsg . '"
-        }';
-        /*
-          "user": "' . $aParams["inputUser"] . '",
-          "remark": "' . $aParams["inputComment"] . '"
-         */
-
-        $sReturn.="writing info file into webroot...<br>";
-        file_put_contents($sInfoFileWebroot, $sInfos);
-        $sReturn.=$this->_execAndSend("ls -l $sInfoFileWebroot");
-
-        if (!file_exists(dirname($sPackageFileArchiv))) {
-            $sReturn.="* create " . dirname($sPackageFileArchiv) . "<br>";
-            mkdir(dirname($sPackageFileArchiv), 0775, true);
-        }
-        $sReturn.=$this->_execAndSend("ls -ld " . dirname($sPackageFileArchiv));
-        if (!file_exists(dirname($sPackageFileArchiv))) {
-            return $this->getBox("error", "directory was not created: " . dirname($sPackageFileArchiv) . $sReturn);
-            return $sReturn;
-        }
-
-        // create tgz archive
-        $sReturn.="create archive $sPackageFileArchiv<br>";
-        $sReturn.=$this->_execAndSend("cd $sTempDir && tar -czf $sPackageFileArchiv .");
-
-        // write info file (.json)
-        $sReturn.="writing info file into archive...<br>";
-        file_put_contents($sInfoFileArchiv, $sInfos);
-
-        // copy template files
-        if (file_exists($sTempDir . '/hooks/templates/')) {
-            $sReturn.="put templatesfile into archive...<br>";
-            $sReturn.=$this->_execAndSend("cp $sTempDir/hooks/templates/* " . $this->_getArchiveDir($sTs2));
-        } else {
-            $sReturn.="SKIP put templatesfile into archive - $sTempDir/hooks/templates/ does not exist.<br>";
-        }
-
-        $sReturn.="<br>Created Archive files:<br>";
-        $sReturn.=$this->_execAndSend("ls -l " . $this->_getArchiveDir($sTs2));
-
-        if (!$this->_iRcAll == 0) {
-            return $this->getBox("error", 'creation failed One of the commands failed (see below).<br>You can ask the sysadmins and analyze with them the created temp directory &quot;' . $sTempDir . '&quot;.' . $sReturn);
-        }
-
-
-        $sReturn.="<h3>Cleanup</h3>";
-        $sReturn.="<h3>cleanup $sTempDir</h3>";
-        $sReturn.=$this->_execAndSend("rm -rf $sTempDir");
-        $sReturn.="<h3>cleanup Archive</h3>removing the oldest unused packages ...";
-        $sReturn.='<pre>' . print_r($this->cleanupArchive(), true) . '</pre>';
-
-        $sReturn.=$this->getBox("success", "Build finished successfully.");
-
-        // TODO: force synch archive to puppet master
-
-        $sReturn.=$this->queue($sFirstLevel, $sTs2);
-
-
-
-        return $sReturn;
-    }
-
-    /**
-     * put a packaged version into the queue of a specified phase
-     * @param string $sPhase    name of the phase
-     * @param string $sVersion  version 
-     * @return string
-     */
-    public function queue($sPhase, $sVersion) {
-        $sReturn = "<h2>Queue " . $this->getLabel() . " to $sPhase</h2>";
-
-        if (!$this->isActivePhase($sPhase)) {
-            return $sReturn . $this->getBox("error", 'phase ' . $sPhase . ' is not active.');
-        }
-
-        $sPlace = "onhold";
-
-        $sLinkTarget = $this->_getArchiveDir($sVersion);
-        $sLinkName = $this->_getFileBase($sPhase, $sPlace);
-
-        // --------------------------------------------------
-        // Checks
-        // --------------------------------------------------
-        if (!$sLinkName) {
-            die("ERROR: Queuing failed - sLinkName is empty.");
-        }
-        if (!$sLinkTarget) {
-            die("ERROR: Queuing  failed - sLinkTarget is empty.");
-        }
-        if (!file_exists($sLinkTarget)) {
-            die("ERROR: Queuing failed - version $sVersion is invalid. The directory $sLinkTarget does not exist.");
-        }
-
-        // --------------------------------------------------
-        // create the new link
-        // --------------------------------------------------
-        $this->_iRcAll = 0;
-        if (file_exists($sLinkName)) {
-            $sReturn.="removing existing version<br>";
-            $sReturn.=$this->_execAndSend("rm -f $sLinkName");
-        }
-        $sReturn.="linking to new version <br>";
-        $sReturn.=$this->_execAndSend("ln -s $sLinkTarget $sLinkName");
-        $sReturn.=$this->_execAndSend("ls -l $sLinkName | fgrep $sLinkTarget");
-
-
-        if (!$this->_iRcAll == 0) {
-            return $this->getBox("error", 'Queuing failed One of the commands failed.' . $sReturn);
-        }
-        $sReturn.=$this->getBox("success", "the version $sVersion was set to place $sPlace");
-        $sReturn.=$this->deploy($sPhase);
-
-        return $sReturn;
-    }
-
-    /**
-     * deploy a queued package - this moves the queue into the repo directory
-     * and will be installed on server within 30 min.
-     * This method checks the deploy times
-     * @param string $sPhase which queue of which phase we want to install in server
-     * @return boolean|string
-     */
-    public function deploy($sPhase) {
-        $sReturn = "<h2>Deploy " . $this->getLabel() . " to $sPhase</h2>";
-
-        if (!$this->isActivePhase($sPhase)) {
-            return $sReturn . $this->getBox("error", 'phase ' . $sPhase . ' is not active.');
-        }
-
-        $sQueueLink = $this->_getFileBase($sPhase, "onhold");
-        $sRepoLink = $this->_getFileBase($sPhase, "ready4deployment");
-
-        if (array_key_exists("deploytimes", $this->_aConfig["phases"][$sPhase])) {
-            // check if the a deploy time is reached
-            $sNow = date("D H:i:s");
-            $sReturn.="<h3>check deployment times</h3>";
-            $sReturn.="check if one of the deployment times is reached and matches time on server <strong>$sNow</strong><br>";
-            $bCanDeploy = false;
-            foreach ($this->_aConfig["phases"][$sPhase]["deploytimes"] as $sRegex) {
-                $sReturn.="... test &quot;$sRegex&quot; ... ";
-                if (preg_match($sRegex, $sNow)) {
-                    $sReturn.="OK";
-                    $bCanDeploy = true;
-                } else {
-                    $sReturn.="no.";
-                }
-                $sReturn.="<br>";
-            }
-            if (!$bCanDeploy) {
-                $sReturn.=$this->getBox("info", "SKIP: Im Moment ist leider kein Deployment-Zeitfenster");
-                return $sReturn;
-            }
-            $sReturn.="OK, deployment time was reached.<br>";
-            // if ()
-        }
-        if (!file_exists($sQueueLink)) {
-            $sReturn.=$this->getBox("info", "SKIP: nothing to do - the current queue is empty ($sQueueLink does not exist).");
-            return $sReturn;
-        }
-
-
-        // --------------------------------------------------
-        // move the queue link to the repo name
-        // --------------------------------------------------
-        $this->_iRcAll = 0;
-        if (file_exists($sRepoLink)) {
-            $sReturn.="removing existing version<br>";
-            $sReturn.=$this->_execAndSend("rm -f $sRepoLink");
-        }
-        $sReturn.="moving queue to repo<br>";
-        $sReturn.=$this->_execAndSend("mv $sQueueLink $sRepoLink");
-
-
-        if (!$this->_iRcAll == 0) {
-            return $this->getBox("error", "Deployment failed - One of the commands failed." . $sReturn);
-        }
-
-        // $sReturn.=$this->_execAndSend("ln -s $sLinkTarget $sLinkName");
-        if (array_key_exists('mirrorPackages', $this->_aConfig) && count($this->_aConfig['mirrorPackages'])) {
-            foreach ($this->_aConfig['mirrorPackages'] as $sLabel => $aTarget) {
-                $sReturn.="synching package - $sLabel<br>";
-                if (array_key_exists('type', $aTarget)) {
-                    $sCmd = false;
-                    // $sSource=$this->_aConfig["packageDir"]."/$sPhase/*";
-                    $sSource = $sRepoLink;
-                    $sTarget = $aTarget['target'] . "/$sPhase";
-                    switch ($aTarget['type']) {
-                        case 'rsync':
-                            $sCmd = "ls -l $sSource 2>/dev/null && /usr/bin/rsync --delete -rLv  $sSource $sTarget";
-                            break;
-                        default:
-                            $sReturn.="SKIP: type " . $aTarget['type'] . " ist not supported (yet) - skipping.\n";
-                            break;
-                    } // switch
-                    if ($sCmd) {
-                        /*
-                          if ($aTarget['runas']) {
-                          $sCmd="su - " . $aTarget['runas'] . " -c \"" . $sCmd . "\"";
-                          }
-                         * 
-                         */
-                        $sReturn.=$this->_execAndSend($sCmd);
-                    }
-                }
-            } // foreach
-        }
-
-        // TODO: run puppet agent on target server(s) - for preview only
-        if (array_key_exists("puppethost", $this->_aPrjConfig["phases"][$sPhase])) {
-            $sReturn.="Run puppet agent.<br>";
-            $sReturn.=$this->_execAndSend("ssh imldeployment@" . $this->_aPrjConfig["phases"][$sPhase]["puppethost"] . " sudo puppet agent -t | fgrep -i Deploy");
-        } else {
-            $sReturn.="SKIP: no puppet host was defined. The deployment was done and will be installed soon (within 30min).<br>";
-        }
-
-        $sReturn.="<br>";
-        $sReturn.=$this->getBox("success", "SUCCESS: deployment was done.");
-        return $sReturn;
-    }
-
-    /**
-     * accept a the installed version in a phase and put this version
-     * to the queue of the next phase.
-     * @param string $sPhase which queue of which phase we want to install in server
-     * @return type
-     */
-    public function accept($sPhase) {
-        $sReturn = "<h2>Accept " . $this->getLabel() . " phase $sPhase</h2>";
-        if (!$this->canAcceptPhase($sPhase)) {
-            return $sReturn . $this->getBox("error", 'phase ' . $sPhase . ' cannot be accepted.');
-        }
-
-        $sReturn.="<h3>Info: Installed on $sPhase</h3>";
-        $aInfos = $this->getPhaseInfos($sPhase);
-        $sVersion = $aInfos["deployed"]["version"];
-        $sNext = $this->getNextPhase($sPhase);
-
-
-        $sReturn.='<pre>' . print_r($aInfos["deployed"], true) . '</pre>';
-        $sReturn.=$this->getBox("info", "package version is [" . $sVersion . "] and will be queued to phase [$sNext].");
-        $sReturn.=$this->queue($sNext, $sVersion);
-
-        return $sReturn;
-    }
-
-    /**
-     * save POSTed data as project config
-     * @return boolean
-     */
-    public function saveConfig($aData = false) {
-        if (!$aData)
-            $aData = $_POST;
-        if (!array_key_exists("id", $aData))
-            return false;
-        // if (!array_key_exists("setupaction", $_POST)) return false;
-
-        $sId = $aData["id"];
-
-        // remove unwanted items
-        foreach (array("setupaction", "prj", "id") as $s) {
-            if (array_key_exists($s, $aData))
-                unset($aData[$s]);
-        }
-
-        // echo "IST <pre>" . print_r($this->_aPrjConfig, true) . "</pre>"; echo "NEU <pre>" . print_r($aData, true) . "</pre>"; die();
-        // make a backup of a working config
-        $sCfgFile = $this->_getConfigFile($sId);
-        $sBakFile = $this->_getConfigFile($sId) . ".ok";
-        copy($sCfgFile, $sBakFile);
-
-        $bReturn = file_put_contents($sCfgFile, json_encode($aData));
-        $this->setProjectById($sId);
-        return $bReturn;
-    }
-
-    /**
-     * create a new project; it returns the error message if it fails and
-     * an empty string if it was successful.
-     * @param string  $sId  id
-     * @return string
-     */
-    public function create($sId) {
-        if (!$sId) {
-            return "Ohne ID kann ich kein Projekt anlegen.";
-        }
-        $s = preg_replace('/[a-z\-\_0-9]*/', "", $sId);
-        if ($s) {
-            return "Die ID $sId enthaelt unerlaubte Zeichen. Erlaubt sind Kleinbuchstaben, Ziffern, Minus, Unterstrich.";
-        }
-        if ($sId == "all") {
-            return "ID $sId ist reserviert.";
-        }
-        if (array_search($sId, $this->getProjects()) !== false) {
-            return "Die ID $sId ist bereits vergeben.";
-        }
-
-        // reset config and create a skeleton
-        $this->_readConfig();
-        $this->_aConfig["id"] = $sId;
-
-        $this->_aPrjConfig = array(
-            "id" => $sId, // for saveConfig
-            "label" => "$sId",
-            "fileprefix" => "$sId",
-            "description" => '',
-            "contact" => '',
-            "build" => array(
-                "type" => "",
-                "ssh" => "",
-                "keyfile" => "",
-                "webaccess" => "",
-            ),
-            "phases" => array(
-                "preview" => array(),
-                "stage" => array(),
-                "live" => array(),
-            ),
-        );
-        $this->_verifyConfig(); // check skeleton
-        $bReturn = $this->saveConfig($this->_aPrjConfig);
-        if (!$bReturn) {
-            return "Das neue Projekt konnte nicht gespeichert werden.";
-        }
-
-        // alles OK - dann leeren String
-        return "";
-    }
-
-    // ----------------------------------------------------------------------
-    // RENDERING
-    // ----------------------------------------------------------------------
-
-    /**
-     * return html code for a div with background color based on a checksum of the given text
-     * @param string $sText text that is used for checksum
-     * @param string $sFormat one of hex|rgba
-     * @param float  $fAlpha  alpha channel for rgba; 0..1
-     * @return string
-     */
-    function getChecksumDiv($sText, $sFormat = "hex", $fAlpha = 1.0) {
-        return '<div style="background: ' . $this->getChecksumColor($sText, $sFormat, $fAlpha) . '; height: 3px;"> </div>';
-    }
-
-    /**
-     * generate css color based on a checksum of the given text
-     * @param string $sText text that is used for checksum
-     * @return string 
-     */
-    function getChecksumColor($sText, $sFormat = "hex", $fAlpha = 1.0) {
-        $sReturn = '';
-        $s = md5($sText);
-        $sRH = substr($s, 0, 2);
-        $sGH = substr($s, 2, 2);
-        $sBH = substr($s, 4, 2);
-        switch ($sFormat) {
-            case "rgba":
-                $sReturn = "rgba(" . hexdec($sRH) . ", " . hexdec($sGH) . ", " . hexdec($sBH) . ", " . $fAlpha . ")";
-                break;
-
-            default:
-                $sReturn = "#$sRH$sGH$sBH";
-                break;
-        }
-        return $sReturn;
-    }
-
-    /**
-     * render html for a colored link to any project action
-     * @param string $sFunction name of the action; one of accept|build|cleanup|deploy|phase
-     * @param string $sPhase    current phase where to place the link
-     * @return string
-     */
-    public function renderLink($sFunction, $sPhase = false) {
-        $sFirst = $this->getNextPhase();
-        $sNext = $this->getNextPhase($sPhase);
-        $aLinkdata = array(
-            'default' => array('icon' => 'icon-forward', 'class' => ''),
-            'accept' => array('icon' => 'icon-forward', 'class' => $sNext,
-                'hint' => 'Accept [' . $sPhase . '] und in die Queue von [' . $sNext . '] stellen.',
-                'label' => 'Accept'
-            ),
-            'build' => array('icon' => 'icon-forward', 'class' => $sFirst,
-                'hint' => 'neues Paket erstellen und in [' . $sFirst . '] stellen.',
-                'label' => 'Build'
-            ),
-            'cleanup' => array('icon' => 'icon-chevron-right', 'class' => ''),
-            'deploy' => array('icon' => 'icon-forward', 'class' => $sPhase,
-                'hint' => 'Deploy der Queue von [' . $sPhase . ']',
-                'label' => 'Deploy'
-            ),
-            'overview' => array('icon' => 'icon-book', 'class' => '',
-                'hint' => 'Projekt-Übersicht [' . $this->getLabel() . ']',
-                'label' => $this->getLabel()
-            ),
-            'phase' => array('icon' => 'icon-chevron-right', 'class' => $sPhase,
-                'hint' => 'Details zur Phase [' . $sPhase . ']',
-                'label' => 'Details'
-            ),
-        );
-        $sClass = $sPhase;
-        $sIconClass = (array_key_exists($sFunction, $aLinkdata)) ? $aLinkdata[$sFunction]['icon'] : $aLinkdata['default']['icon'];
-        $sHint = (
-                array_key_exists($sFunction, $aLinkdata) && array_key_exists("hint", $aLinkdata[$sFunction])
-                ) ? $aLinkdata[$sFunction]['hint'] : "";
-        $sLabel = (
-                array_key_exists($sFunction, $aLinkdata) && array_key_exists("label", $aLinkdata[$sFunction])
-                ) ? $aLinkdata[$sFunction]['label'] : $sFunction;
-        $sClass = (
-                array_key_exists($sFunction, $aLinkdata) && array_key_exists("class", $aLinkdata[$sFunction])
-                ) ? $aLinkdata[$sFunction]['class'] : '';
-
-        $sLink = "/deployment/" . $this->_aConfig["id"] . "/";
-        if ($sFunction != "overview") {
-            $sLink.="$sFunction/";
-        }
-        if ($sPhase) {
-            $sLink.="$sPhase/";
-        }
-
-        return '<a href="' . $sLink . '" title="' . $sHint . '" class="btn ' . $sClass . '"><i class="' . $sIconClass . '"></i> ' . $sLabel . '</a>';
-    }
-
-    /**
-     * render html for project metadata in the overview 
-     * @return string
-     */
-    public function renderProjektInfos() {
-        $aLang = array(
-            'label' => 'Projektname',
-            'description' => 'Beschreibung',
-            'contact' => 'Kontakt',
-        );
-        $sReturn = false;
-        $sReturn.='<table><tbody>';
-        foreach ($aLang as $key => $sLong) {
-            $sReturn.='<tr><td>' . $sLong . ':</td><td>' . $this->_aPrjConfig[$key] . '</td></tr>';
-        }
-        if (array_key_exists("type", $this->_aPrjConfig["build"])) {
-            $sReturn.='<tr><td>Quell-Repository:</td><td><strong>' . $this->_aPrjConfig["build"]["type"] . '</strong><br>';
-            if (array_key_exists("ssh", $this->_aPrjConfig["build"])) {
-                $sReturn.='Zugriff mit SSH-Protokoll auf<br><em>' . $this->_aPrjConfig["build"]["ssh"] . '</em><br>';
-            }
-            $sReturn.='Browserzugriff auf das Repo:<br>';
-            if (array_key_exists("webaccess", $this->_aPrjConfig["build"])) {
-                $sReturn.='<a href="' . $this->_aPrjConfig["build"]["webaccess"] . '">' . $this->_aPrjConfig["build"]["webaccess"] . '</a><br>';
-            } else {
-                $sReturn.='unkbekannt<br>';
-            }
-            $sReturn.='</td></tr>';
-        }
-        $sReturn.='</tbody></table>';
-
-        return $sReturn;
-    }
-
-    /**
-     * render html for the project overview; it shows the defined phases for 
-     * the project as a table
-     * @return type
-     */
-    public function renderPhaseInfo() {
-        $sRow1 = false;
-        $sRow2 = false;
-        foreach ($this->getActivePhases() as $sPhase) {
-            $sRow1.='<th class="' . $sPhase . '">' . $sPhase . '</th>';
-            $sRow2.='<td class="' . $sPhase . '">'
-                    . 'Link: <a href="' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '">' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '</a><br>'
-                    . '<br>Deployment:<br>';
-            if (array_key_exists("deploytimes", $this->_aConfig["phases"][$sPhase])) {
-                $sRow2.=implode("<br>", $this->_aConfig["phases"][$sPhase]["deploytimes"]);
-            } else {
-                $sRow2.='Ein Archiv in der Queue wird sofort ins Repo gestellt.';
-            }
-            $sRow2.='<br>' . $this->renderLink("phase", $sPhase);
-            $sRow2.='</td>';
-        }
-        return '<table><thead><tr>' . $sRow1 . '</tr></thead><tbody><tr>' . $sRow2 . '</tr></tbody></table>';
-    }
-
-    /**
-     * render html for a place of a phase
-     * @param string  $sPhase    phase
-     * @param string  $sPlace    name of the place; one of onhold|ready4deployment|deployed
-     * @param bool    $bActions  draw action links (deploy, accept) on/ off
-     * @param bool    $bLong     use long variant to display infos? 
-     * @return string|boolean
-     */
-    public function renderPhaseDetail($sPhase, $sPlace, $bActions = true, $bLong = true) {
-
-        if (!$sPhase) {
-            return false;
-        }
-        if (!$sPlace) {
-            return false;
-        }
-        if (!$this->isActivePhase($sPhase)) {
-            return false;
-        }
-        if (!array_key_exists($sPlace, $this->_aPlaces)) {
-            return false;
-        }
-
-        $sReturn = false;
-        $aDataPhase = $this->getPhaseInfos($sPhase);
-        $aData = $aDataPhase[$sPlace];
-        if (array_key_exists("ok", $aData) && array_key_exists("version", $aData)) {
-            // TODO: getChecksumDiv anhand der Repo-Versionsnummer - dann kann man beim build auch die Farbe mit dem Repo HEAD vergleichen
-            $sReturn .= '
-                ' . $this->getChecksumDiv($aData["revision"]) . '
-                <i class="icon-calendar"></i> Build vom ' . date("d.m.Y H:i:s", strtotime($aData["date"]));
-            if ($bLong) {
-                $sReturn.='<br><i class="icon-tag"></i> Revision: ' . $aData["revision"] . '<br>
-                    <i class="icon-comment"></i> Commit-Message:<br><pre>' . $aData["message"] . '</pre>';
-                if ($sPlace == "deployed" && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) {
-                    $sReturn.='<i class="icon-globe"></i> URL: <a href="' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '">' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '</a><br>';
-                }
-            } else {
-                $sReturn.=' <a href="#" class="info">Info<span>'
-                        . '<span class="' . $sPhase . ' title">' . $this->getLabel() . " :: $sPhase :: $sPlace</span><br><br>"
-                        . '<i class="icon-tag"></i> Revision: ' . $aData["revision"] . '<br>'
-                        . '<i class="icon-comment"></i> Commit-Message:<pre>' . $aData["message"] . '</pre>';
-                if ($sPlace == "deployed" && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) {
-                    $sReturn.='<i class="icon-globe"></i> URL: ' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '<br>';
-                }
-                $sReturn.='</span></a> ';
-            }
-
-            switch ($sPlace) {
-                case "onhold":
-                    if (array_key_exists($sPhase, $this->_aConfig)) {
-                        $sReturn .= print_r($this->_aConfig[$sPhase], true);
-                        if (array_key_exists("deploytimes", $this->_aConfig[$sPhase])) {
-                            $sReturn .= '<br><i class="icon-time"></i> Deployment:<br>' . implode("<br>", array_values($this->_aPhases[$sPhase]["deploytimes"]));
-                        }
-                        if ($bActions) {
-                            $sReturn .= $this->renderLink("deploy", $sPhase);
-                        }
-                    }
-                    break;
-
-                case "ready4deployment":
-                    break;
-
-                case "deployed":
-                    if ($bActions && $this->canAcceptPhase($sPhase)) {
-                        $sReturn .= $this->renderLink("accept", $sPhase);
-                    }
-                    break;
-                default:
-                    break;
-            }
-        } else {
-            if (array_key_exists("error", $aData)) {
-                $sReturn.= '<div class="error">';
-                // if ($bLong and true){
-                //     $sReturn.= '<i class="icon-exclamation-sign"></i> FEHLER:<br>' . $aData["error"] . '';
-                // } else {
-                $sReturn.= '<a href="#" class="info"><i class="icon-exclamation-sign"></i> FEHLER:<span>' . $aData["error"] . '</span></a>';
-                // }
-                $sReturn.= '</div>';
-            } else if (array_key_exists("warning", $aData)) {
-                $sReturn.= '<div class="warning"><i class="icon-info-sign"></i> WARNUNG:<br>' . $aData["warning"] . '</div>';
-            } else {
-                $sReturn.= '[leer]';
-            }
-        }
-        return $sReturn;
-    }
-
-    /**
-     * render html for a row with td for all places
-     * @param string $sPhase  phase (just needed for coloring)
-     * @return string
-     */
-    public function renderPlacesAsTd($sPhase) {
-        $sRow1 = '';
-        foreach ($this->_aPlaces as $sPlace => $sLabel) {
-            $sRow1.='<td class="' . $sPhase . ' ' . $this->_aConfig["id"] . ' tdphase">' . $sLabel . '</td>';
-        }
-        return $sRow1;
-    }
-
-    /**
-     * render html for a row with td for all places of a phase
-     * @param string $sPhase   phase
-     * @param bool   $bActions draw action links (deploy, accept) on/ off
-     * @param bool   $bLong    use long variant to display infos? 
-     * @return string|boolean
-     */
-    public function renderAllPhaseDetails($sPhase, $bActions = true, $bLong = true) {
-        if (!$sPhase) {
-            return false;
-        }
-        if (!$this->isActivePhase($sPhase)) {
-            return '
-                        <td class="' . $sPhase . ' tdphase ' . $this->_aConfig["id"] . '" colspan="' . count($this->_aPlaces) . '">
-                            <div class="versioninfo center inactive"><i class="icon-ban-circle"></i> inactive</div>
-                        </td>';
-        }
-        $sRow2 = false;
-
-        foreach ($this->_aPlaces as $sPlace => $sLabel) {
-            $sRow2.='<td class="' . $sPhase . ' tdphase td' . $this->_aConfig["id"] . '">' . $this->renderPhaseDetail($sPhase, $sPlace, $bActions, $bLong) . '</td>';
-        }
-        return $sRow2;
-    }
-
-    /**
-     * return html code for the installed version in the repository
-     * @return string
-     */
-    public function renderRepoInfo() {
-        $sReturn = "";
-        switch ($this->_aPrjConfig["build"]["type"]) {
-            case "git":
-
-                $sKeyfile = dirname(dirname(__file__)) . "/" . $this->_aPrjConfig["build"]["keyfile"];
-                $sWrapper = dirname(dirname(dirname(dirname(__file__)))) . "/shellscripts/gitsshwrapper.sh";
-                $sGitCmd = "git ls-remote --heads " . $this->_aPrjConfig["build"]["ssh"] . "  master | awk '{ print $1 }'";
-                $sRevision = shell_exec("export GIT_SSH=$sWrapper ; export PKEY=$sKeyfile; $sGitCmd");
-                $sRevision = $this->getRepoRevision();
-                $sReturn.=$this->getChecksumDiv($sRevision);
-                $sReturn.= '<i class="icon-tag"></i> Revision: ' . $sRevision;
-                $sReturn.="<br>$sGitCmd<br>";
-
-                if (array_key_exists("webaccess", $this->_aPrjConfig["build"])) {
-                    $sReturn.='<br>Web-GUI des Repositories:<br><a href="' . $this->_aPrjConfig["build"]["webaccess"] . '">' . $this->_aPrjConfig["build"]["webaccess"] . '</a><br>';
-                }
-
-                break;
-
-            default:
-                return $this->getBox("error", "Build Type not supported: " . $this->_aPrjConfig["build"]["type"] . $sReturn);
-        }
-        return $sReturn;
-    }
-
-    /**
-     * return html code for a list of all built packages and their usage
-     * @return string
-     */
-    public function renderVersionUsage() {
-        $sReturn = false;
-        $sRowHead1 = false;
-        $sRowHead2 = false;
-        if (!count($this->getVersions())) {
-            return $this->getBox("info", "Für dieses Projekt wurde noch kein Paket erzeugt.");
-        }
-        foreach ($this->getActivePhases() as $sPhase) {
-            $sRowHead1.='<th class="' . $sPhase . '" colspan="' . count($this->_aPlaces) . '">' . $sPhase . '</th>';
-            $sRowHead2.=$this->renderPlacesAsTd($sPhase);
-        }
-        foreach ($this->getVersions() as $sVersion => $aData) {
-            $sReturn.='<tr>';
-            $sReturn.='<td>' . $sVersion . '</td>';
-            foreach ($this->getActivePhases() as $sPhase) {
-
-                foreach (array_keys($this->_aPlaces) as $sPlace) {
-                    $bFound = false;
-                    if (is_array($aData) && count($aData))
-                        foreach ($aData as $i => $aPhaseUsage) {
-                            if ($aPhaseUsage["phase"] == $sPhase && $aPhaseUsage["place"] == $sPlace
-                            )
-                                $bFound = true;
-                        }
-                    if ($bFound) {
-                        $sReturn.='<td class="' . $sPhase . '" style="text-align: center;">X</td>';
-                    } else {
-                        $sReturn.='<td> </td>';
-                    }
-                }
-            }
-            $sReturn.='</tr>';
-        }
-        $sReturn = 'Die nachfolgende Tabelle zeigt die bereits erzeugten Pakete an und wo diese verwendet werden.'
-                . '<table>'
-                . '<thead><tr><td>Version</td>'
-                . $sRowHead1
-                . '</tr><tr><td>'
-                . $sRowHead2
-                . '</tr></thead>'
-                . '<tbody>'
-                . $sReturn
-                . '</tbody>'
-                . '</table>';
-        return $sReturn;
-    }
-
-    /**
-     * Form Item 
-     * @param string $sType    one of text | ...
-     * @param id     $sId      id for form element
-     * @param string $sValue   prefilled form value
-     * @param string $sLabel   label
-     * @return string
-     */
-    private function _renderFormitem($sType, $sId, $sValue, $sLabel = "") {
-        $sReturn = '';
-        switch ($sType) {
-            case "text":
-                $sReturn = '<label for="' . $sId . '">' . $sLabel . '</label><input type="text" id="' . $sId . '" name="' . $sId . '" size="100" value="' . $sValue . '">';
-
-                break;
-
-            default:
-                break;
-        }
-        return $sReturn;
-    }
-
-    /**
-     * return html code for the setup form of an exsiting project
-     * @return string
-     */
-    public function renderProjectSetup() {
-
-        require_once ("formgen.class.php");
-        $i = 0;
-
-
-        $aForms = array(
-            'setup' => array(
-                'meta' => array(
-                    'method' => 'POST',
-                    'action' => '?',
-                ),
-                'validate' => array(),
-                'form' => array(
-                    'input' . $i++ => array(
-                        'type' => 'hidden',
-                        'name' => 'setupaction',
-                        'value' => 'save',
-                    ),
-                    'input' . $i++ => array(
-                        'type' => 'hidden',
-                        'name' => 'id',
-                        'value' => $this->_aConfig["id"],
-                    ),
-                    'input' . $i++ => array(
-                        'type' => 'markup',
-                        'value' => '<div class="tabbable">
-                            <ul class="nav nav-tabs">
-                                <li class="active"><a href="#tab1" data-toggle="tab">allgemeine Metadaten</a></li>
-                                <li><a href="#tab2" data-toggle="tab">Build</a></li>
-                                <li><a href="#tab3" data-toggle="tab">Phasen</a></li>
-                            </ul>
-                            <div class="tab-content">
-                            <div class="tab-pane active" id="tab1">
-                            
-                            ',
-                    ),
-                    'input' . $i++ => array(
-                        'type' => 'text',
-                        'name' => 'label',
-                        'label' => 'Label des Projekts',
-                        'value' => $this->_aPrjConfig["label"],
-                        'required' => 'required',
-                        'validate' => 'isastring',
-                        'size' => 100,
-                        'placeholder' => 'Projekt',
-                    ),
-                    'input' . $i++ => array(
-                        'type' => 'text',
-                        'name' => 'description',
-                        'label' => 'Kurz-Beschreibung',
-                        'value' => $this->_aPrjConfig["description"],
-                        'required' => 'required',
-                        'validate' => 'isastring',
-                        'size' => 100,
-                        'placeholder' => '',
-                    ),
-                    'input' . $i++ => array(
-                        'type' => 'text',
-                        'name' => 'contact',
-                        'label' => 'Ansprechpartner zum Projekt',
-                        'value' => $this->_aPrjConfig["contact"],
-                        'required' => 'required',
-                        'validate' => 'isastring',
-                        'size' => 100,
-                        'placeholder' => '',
-                    ),
-                    // --------------------------------------------------
-                    'input' . $i++ => array(
-                        'type' => 'markup',
-                        'value' => ' </div><div class="tab-pane" id="tab2">
-                            <p>
-                                Einstellungen zum Erstellen eines neuen Builds.
-                                Es werden Zugriffsdaten auf das Repository benötigt.<br>
-                                Der Fileprefix bestimmt einen Teil des Dateinamens
-                                und wird auch in Puppet konfiguriert. Er darf nach 
-                                eingerichtetem Deployment nicht mehr geändert werden.
-                            </p>
-                            ',
-                    ),
-                    'input' . $i++ => array(
-                        'type' => 'text',
-                        'name' => 'build[type]',
-                        'label' => 'Typ',
-                        'value' => $this->_aPrjConfig["build"]["type"],
-                        'required' => 'required',
-                        'validate' => 'isastring',
-                        'size' => 100,
-                        'placeholder' => '',
-                    ),
-                    'input' . $i++ => array(
-                        'type' => 'text',
-                        'name' => 'build[ssh]',
-                        'label' => 'SSH-URL zum Repository',
-                        'value' => $this->_aPrjConfig["build"]["ssh"],
-                        // 'required' => 'required',
-                        'validate' => 'isastring',
-                        'size' => 100,
-                        'placeholder' => '',
-                    ),
-                    'input' . $i++ => array(
-                        'type' => 'text',
-                        'name' => 'build[keyfile]',
-                        'label' => 'Dateiname zum SSH-Private-Key',
-                        'value' => $this->_aPrjConfig["build"]["keyfile"],
-                        // 'required' => 'required',
-                        'validate' => 'isastring',
-                        'size' => 100,
-                        'placeholder' => '',
-                    ),
-                    'input' . $i++ => array(
-                        'type' => 'text',
-                        'name' => 'build[webaccess]',
-                        'label' => 'URL zur Web GUI des Repositorys',
-                        'value' => $this->_aPrjConfig["build"]["webaccess"],
-                        'validate' => 'isastring',
-                        'size' => 100,
-                        'placeholder' => '',
-                    ),
-                    'input' . $i++ => array(
-                        'type' => 'text',
-                        'name' => 'fileprefix',
-                        // 'disabled' => 'disabled',
-                        'label' => 'File-Prefix <span class="error">(nachträglich NICHT mehr ändern!)</span>',
-                        'value' => $this->_aPrjConfig["fileprefix"],
-                        'required' => 'required',
-                        'validate' => 'isastring',
-                        'pattern' => '[a-z0-9\-\_]*',
-                        'size' => 100,
-                        'placeholder' => '',
-                    ),
-                    // --------------------------------------------------
-                    'input' . $i++ => array(
-                        'type' => 'markup',
-                        'value' => ' </div><div class="tab-pane" id="tab3">
-                            <p>
-                                Gib die URLs der jeweiligen Phasen an. Wird keine URL eingetragen, ist die jeweilige Phase nicht aktiv.<br>
-                                Der Puppet-Host ist optional nur für die erste Phase ' . $this->getNextPhase() . ' anzugeben.
-                                Der Sysadmin muss zudem diesen Host in Puppet konfigurieren.
-                            </p>
-                            ',
-                    ),
-                ),
-            ),
-        );
-        foreach (array_keys($this->getPhases()) as $sPhase) {
-            $aForms["setup"]["form"]['input' . $i++] = array(
-                'type' => 'markup',
-                'value' => 'Phase <span class="' . $sPhase . '">' . $sPhase . '</span>',
-            );
-            $sUrl = array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["url"] : "";
-            $aForms["setup"]["form"]['input' . $i++] = array(
-                'type' => 'text',
-                'name' => 'phases[' . $sPhase . '][url]',
-                'label' => 'URL der Webseite',
-                'value' => $sUrl,
-                // 'required' => 'required',
-                'validate' => 'isastring',
-                'size' => 100,
-                'placeholder' => 'http://[phase].[Projekt].iml.unibe.ch/',
-            );
-            $sPuppethost = array_key_exists("puppethost", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["puppethost"] : "";
-            $aForms["setup"]["form"]['input' . $i++] = array(
-                'type' => 'text',
-                'name' => 'phases[' . $sPhase . '][puppethost]',
-                'label' => 'Hostname für puppet agent',
-                'value' => $sPuppethost,
-                // 'required' => 'required',
-                'validate' => 'isastring',
-                'size' => 100,
-                'placeholder' => '',
-            );
-        }
-        $aForms["setup"]["form"]['input' . $i++] = array(
-            'type' => 'markup',
-            'value' => '</div></div><hr>',
-        );
-        $aForms["setup"]["form"]['input' . $i++] = array(
-            'type' => 'submit',
-            'name' => 'btnsave',
-            'label' => 'Speichern',
-            'value' => 'Speichern',
-        );
-
-        $oForm = new formgen($aForms);
-        return $oForm->renderHtml("setup");
-    }
-
-    /**
-     * return html code for the setup form for a new project
-     * @return string
-     */
-    public function renderNewProject() {
-        global $aParams;
-
-        require_once ("formgen.class.php");
-        $i = 0;
-        $sID = array_key_exists("id", $aParams) ? $aParams["id"] : "";
-
-        $aForms = array(
-            'setup' => array(
-                'meta' => array(
-                    'method' => 'POST',
-                    'action' => '?',
-                ),
-                'validate' => array(),
-                'form' => array(
-                    'input' . $i++ => array(
-                        'type' => 'hidden',
-                        'name' => 'setupaction',
-                        'value' => 'create',
-                    ),
-                    'input' . $i++ => array(
-                        'type' => 'text',
-                        'name' => 'id',
-                        'label' => 'ID des neuen Projekts',
-                        'value' => $sID,
-                        'required' => 'required',
-                        'validate' => 'isastring',
-                        'size' => 100,
-                        'pattern' => '[a-z0-9\-\_]*',
-                        'placeholder' => 'Projekt: Kleinbuchstaben a-z, Ziffern, Minus, Unterstrich',
-                    ),
-                ),
-            ),
-        );
-        $aForms["setup"]["form"]['input' . $i++] = array(
-            'type' => 'submit',
-            'name' => 'btnsave',
-            'label' => 'Speichern',
-            'value' => 'Speichern',
-        );
-
-        $oForm = new formgen($aForms);
-        return $oForm->renderHtml("setup");
-    }
-
-}
-
+<?php
+
+/* ######################################################################
+
+  IML DEPLOYMENT
+
+  class project for all actions for single project
+ * actions
+ * rendering html
+
+  ---------------------------------------------------------------------
+  2013-11-08  Axel <axel.hahn@iml.unibe.ch>
+  ###################################################################### */
+
+/**
+ * class for single project
+ */
+class project {
+    // ----------------------------------------------------------------------
+    // CONFIG
+    // ----------------------------------------------------------------------
+
+    /**
+     * config file
+     * @var string
+     */
+    private $_sCfgfile = "../config/inc_projects_config.php";
+
+    /**
+     * directory for project configs
+     * @var string
+     */
+    private $_sCfgdir = "../config/projects";
+
+    /**
+     * configuration ($aConfig in the config file)
+     * @var array
+     */
+    private $_aConfig = array();
+
+    /**
+     * configuration of the project (= $aProjects[ID] in the config file)
+     * @var array
+     */
+    private $_aPrjConfig = array();
+
+    /**
+     * version infos of all phases
+     * @var type 
+     */
+    private $_aData = array();
+
+    /**
+     * existing versions in the archive dir
+     * @var type 
+     */
+    private $_aVersions = array();
+
+    /**
+     * places of version infos in each deployment phase
+     * @var type 
+     */
+    private $_aPlaces = array(
+        "onhold" => "Queue",
+        "ready4deployment" => "Puppet",
+        "deployed" => "Installiert",
+    );
+    private $_iRcAll = 0;
+
+    // ----------------------------------------------------------------------
+    // constructor
+    // ----------------------------------------------------------------------
+
+    /**
+     * constructor
+     * @param string $sId  id of the project
+     */
+    public function __construct($sId = false) {
+        $this->_readConfig();
+        if ($sId)
+            $this->setProjectById($sId);
+    }
+
+    // ----------------------------------------------------------------------
+    // private functions
+    // ----------------------------------------------------------------------
+
+    /**
+     * read default config file
+     * @return boolean
+     */
+    private function _readConfig() {
+        require(__dir__ . '/' . $this->_sCfgfile);
+        $this->_aConfig = $aConfig;
+        return true;
+    }
+
+    /**
+     * validate config data
+     * @return boolean
+     */
+    private function _verifyConfig() {
+        if (!count($this->_aPrjConfig))
+            die("ERROR::CONFIG: no config was for found the project. check $aProjects in config file.");
+
+        if (!array_key_exists("packageDir", $this->_aConfig))
+            die("ERROR::CONFIG: packagedir is not defined.");
+        if (!$this->_aConfig["packageDir"])
+            die("ERROR::CONFIG: packagedir is not set.");
+        if (!file_exists($this->_aConfig["packageDir"]))
+            die("ERROR::CONFIG: packagedir does not exist: &quot;" . $this->_aConfig['packageDir'] . "&quot;.");
+
+        if (!array_key_exists("archiveDir", $this->_aConfig))
+            die("ERROR::CONFIG: key &quot;archiveDir&quot; was not found in config.");
+        if (!$this->_aConfig["archiveDir"])
+            die("ERROR::CONFIG: archiveDir is not set.");
+        if (!file_exists($this->_aConfig["archiveDir"]))
+            die("ERROR::CONFIG: archiveDir does not exist: &quot;" . $this->_aConfig['archiveDir'] . "&quot;.");
+
+        foreach (array("fileprefix", "build", "phases") as $sKey) {
+            if (!array_key_exists($sKey, $this->_aPrjConfig))
+                die("ERROR::CONFIG: key &quot;$sKey&quot; was not found in config.<br><pre>" . print_r($this->_aPrjConfig, true) . "</pre>");
+        }
+
+        // TODO: verify ausbauen
+        return true;
+    }
+
+    /**
+     * execute a commandline; returns a string of output of timestamp, command, output and returncode
+     * @param string $sCommand
+     * @return string
+     */
+    private function _execAndSend($sCommand) {
+        $sReturn = '';
+        $bUseHtml = $_SERVER ? true : false;
+
+        ob_implicit_flush(true);
+        // ob_end_flush();
+        $descriptorspec = array(
+            0 => array("pipe", "r"), // stdin is a pipe that the child will read from
+            1 => array("pipe", "w"), // stdout is a pipe that the child will write to
+            2 => array("pipe", "w")    // stderr is a pipe that the child will write to
+        );
+        flush();
+        $process = proc_open($sCommand, $descriptorspec, $pipes, realpath('./'), array());
+
+        $sReturn.="[" . date("H:i:s d.m.Y") . "] ";
+        $sReturn.=$bUseHtml ? "<strong>$sCommand</strong>" : "$sCommand";
+        $sReturn.=$bUseHtml ? "<br>" : "\n";
+
+        $sErrors = false;
+        if (is_resource($process)) {
+            while ($s = fgets($pipes[1])) {
+                $sReturn.=$s;
+                flush();
+            }
+            while ($s = fgets($pipes[2])) {
+                $sErrors.=$s;
+                flush();
+            }
+        }
+        if ($sErrors)
+            $sReturn.="STDERR:\n" . $sErrors;
+        $oStatus = proc_get_status($process);
+        $iRc = $oStatus['exitcode'];
+        $this->_iRcAll += $iRc;
+        $sReturn.="[" . date("H:i:s d.m.Y") . "] exitcode " . $iRc;
+        if ($bUseHtml) {
+            if ($iRc == 0) {
+                $sReturn = '<pre class="cli">' . $sReturn;
+            } else {
+                $sReturn = '<pre class="cli error">' . $sReturn;
+            }
+            $sReturn.='</pre>';
+        }
+
+        flush();
+        return $sReturn;
+    }
+
+    // ----------------------------------------------------------------------
+    // GETTER
+    // ----------------------------------------------------------------------
+
+    private function _getConfigFile($sId) {
+        if (!$sId)
+            die("_getConfigFile requires an ID");
+        return __dir__ . '/' . $this->_sCfgdir . '/' . $sId . ".json";
+    }
+
+    /**
+     * get a full ath for temp directory (for a build)
+     * @return string
+     */
+    private function _getTempDir() {
+        return $s = $this->_getBuildDir() . '/' . $this->_aPrjConfig["fileprefix"] . "_" . date("Ymd-His");
+    }
+
+    /**
+     * get full path where the project builds are (a build setes a subdir)
+     * @return string
+     */
+    private function _getBuildDir() {
+        return $this->_aConfig['buildDir'] . '/' . $this->_aConfig["id"];
+    }
+
+    /**
+     * get full path where the project default files are
+     * @return type
+     */
+    private function _getDefaultsDir() {
+        $s = $this->_aConfig['buildDefaultsDir'] . '/' . $this->_aConfig["id"];
+        return file_exists($s) ? $s : false;
+    }
+
+    /**
+     * get directory for infofile and package (without extension)
+     * @param string $sPhase  one of preview|stage|live ...
+     * @param string $sPlace  one of onhold|ready4deployment|deployed
+     * @return string
+     */
+    private function _getFileBase($sPhase, $sPlace) {
+        if (!array_key_exists($sPhase, $this->_aConfig["phases"])) {
+            die("ERROR: _getFileBase - this phase does not exist: $sPhase.");
+        }
+        if (!array_key_exists($sPlace, $this->_aPlaces)) {
+            die("ERROR: _getFileBase - this place does not exist: $sPhase.");
+        }
+
+
+        // local file for onhold|ready4deployment
+        $sBase = $this->_aConfig['packageDir'] . "/" . $sPhase . "/" . $this->_aPrjConfig["fileprefix"];
+
+        if ($sPlace == "onhold")
+            $sBase.="_onhold";
+        // $sBase .= "/" . $this->_aPrjConfig["fileprefix"];
+        // url for deployed
+        if ($sPlace == "deployed") {
+            if ($this->isActivePhase($sPhase) && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) {
+                $sBase = $this->_aPrjConfig["phases"][$sPhase]["url"] . $this->_aPrjConfig["fileprefix"];
+            } else {
+                $sBase = '';
+            }
+        }
+
+        return $sBase;
+    }
+
+    /**
+     * get filename for info file
+     * @param string $sPhase  one of preview|stage|live ...
+     * @param string $sPlace  one of onhold|ready4deployment|deployed
+     * @return string
+     */
+    private function _getInfofile($sPhase, $sPlace) {
+        $sBase = $this->_getFileBase($sPhase, $sPlace);
+        return $sBase ? $sBase . '/' . $this->_aPrjConfig["fileprefix"] . '.json' : false;
+    }
+
+    /**
+     * get filename for package file
+     * @param string $sPhase  one of preview|stage|live ...
+     * @param string $sPlace  one of onhold|ready4deployment|deployed
+     * @return string
+     */
+    private function _getPackagefile($sPhase, $sPlace) {
+        $sBase = $this->_getFileBase($sPhase, $sPlace);
+        return $sBase ? $sBase . '/' . $this->_aPrjConfig["fileprefix"] . '.tgz' : false;
+    }
+
+    /**
+     * get full path of a packed project archive
+     * @param type $sTimestamp
+     * @return type
+     */
+    private function _getArchiveDir($sTimestamp) {
+        if (!$sTimestamp) {
+            die("ERROR: getArchiveDir timestamp is required");
+        }
+        return $this->_getProjectArchiveDir() . '/' . $sTimestamp;
+    }
+
+    /**
+     * get the directory for archive files of this project
+     * @return string
+     */
+    public function _getProjectArchiveDir() {
+        return $this->_aConfig["archiveDir"] . '/' . $this->_aConfig["id"];
+    }
+
+    /**
+     * get all existing versions in archive and its usage
+     * versions are array keys; where they are used is written in values
+     * @return array
+     */
+    public function getVersions() {
+
+        // --- read all file entries
+        $aReturn = array();
+        $sDir = $this->_getProjectArchiveDir();
+        if (is_dir($sDir)) {
+            foreach (scandir($sDir) as $sEntry) {
+                if (is_dir($sDir . '/' . $sEntry) && $sEntry != '.' && $sEntry != '..')
+                    $aReturn[$sEntry] = false;
+            }
+        }
+        $this->_aVersions = $aReturn;
+
+        // --- check version in all phases 
+        $this->getAllPhaseInfos();
+        foreach ($this->_aData["phases"] as $sPhase => $aData) {
+            foreach (array_keys($this->_aPlaces) as $sPlace) {
+                if (array_key_exists($sPlace, $aData) && array_key_exists("version", $aData[$sPlace])) {
+                    $this->_aVersions[$aData[$sPlace]["version"]][] = array('phase' => $sPhase, 'place' => $sPlace);
+                }
+            }
+        }
+        ksort($this->_aVersions);
+        return $this->_aVersions;
+    }
+
+    /**
+     * recursive delete 
+     * @param type $dir
+     * @return type
+     */
+    private function _rmdir($dir) {
+        foreach (scandir($dir) as $sEntry) {
+            if (is_dir($dir . '/' . $sEntry) && $sEntry != '.' && $sEntry != '..') {
+                $this->_rmdir($dir . '/' . $sEntry);
+            } elseif (is_file($dir . '/' . $sEntry) || is_link($dir . '/' . $sEntry))
+                unlink($dir . '/' . $sEntry);
+        }
+        return rmdir($dir);
+    }
+
+    /**
+     * cleanup of archive directory; it returns the list of deleted
+     * directories as array
+     * @return array
+     */
+    public function cleanupArchive() {
+        $aUnused = array();
+        $sDir = $this->_getProjectArchiveDir();
+        $this->getVersions();
+
+        $aDelete = array();
+        // find unused versions
+        foreach ($this->_aVersions as $sVersion => $aUsage) {
+            if (!$aUsage || count($aUsage) == 0) {
+                $aUnused[] = $sVersion;
+            }
+        }
+
+        // keep a few
+        while (count($aUnused) > $this->_aConfig["versionsToKeep"]) {
+            $sVersion = array_shift($aUnused);
+            $sDir2 = $sDir . '/' . $sVersion;
+            if ($this->_rmdir($sDir2)) {
+                $aDelete[] = $sDir2;
+            } else {
+                echo "Warning: unable to delete Archive $sDir2<br>";
+            };
+        }
+
+        // rescan versions
+        if (count($aDelete)) {
+            $this->getVersions();
+        }
+
+        return $aDelete;
+    }
+
+    /**
+     * cleanup of archive directory; it returns the list of deleted
+     * directories as array
+     * @return array
+     */
+    public function cleanupBuilds() {
+        $sDir = $this->_getBuildDir();
+        $aDirlist = array();
+        $aDelete = array();
+        if (is_dir($sDir)) {
+            foreach (scandir($sDir) as $sEntry) {
+                if (is_dir($sDir . '/' . $sEntry) && $sEntry != '.' && $sEntry != '..')
+                    $aDirlist[] = $sEntry;
+            }
+        }
+
+        // keep a few
+        while (count($aDirlist) >= $this->_aConfig["builtsToKeep"]) {
+            $sVersion = array_shift($aDirlist);
+            $sDir2 = $sDir . '/' . $sVersion;
+            if ($this->_rmdir($sDir2)) {
+                $aDelete[] = $sDir2;
+            } else {
+                echo "Warning: unable to delete Build $sDir2<br>";
+            };
+        }
+
+        return $aDelete;
+    }
+
+    /**
+     * get conmplete config of the project
+     * @return array
+     */
+    public function getConfig() {
+        return $this->_aPrjConfig;
+    }
+
+    /**
+     * get name/ label of the project
+     * @return string
+     */
+    public function getLabel() {
+        return $this->_aPrjConfig["label"];
+    }
+
+    /**
+     * get description of the project
+     * @return string
+     */
+    public function getDescription() {
+        return $this->_aPrjConfig["description"];
+    }
+
+    /**
+     * get deploy and queue infos for all phases
+     * @return type
+     */
+    public function getAllPhaseInfos() {
+        if (!array_key_exists("phases", $this->_aData))
+            $this->_aData["phases"] = array();
+
+        foreach (array_keys($this->_aConfig["phases"]) as $sPhase) {
+            if (!array_key_exists($sPhase, $this->_aData["phases"])) {
+                $this->getPhaseInfos($sPhase);
+            }
+        }
+        return $this->_aData["phases"];
+    }
+
+    /**
+     * get statusinfos of a named phase
+     * @param string $sPhase name of the phase; one of preview|stage|live
+     * @return array
+     */
+    public function getPhaseInfos($sPhase) {
+        if (!$sPhase) {
+            die("ERROR: project->getPhaseInfos - parameter for phase is required");
+        }
+        if (!array_key_exists("phases", $this->_aData))
+            $this->_aData["phases"] = array();
+
+        if (!array_key_exists($sPhase, $this->_aData["phases"])) {
+
+            $this->_aData["phases"][$sPhase] = array();
+            $aTmp = array();
+
+            // a blocked package is waiting for deployment timeslot?
+            $sKey = "onhold";
+            $sJsonfile = $this->_getInfofile($sPhase, $sKey);
+            $aTmp[$sKey] = array();
+            if (file_exists($sJsonfile)) {
+                $aJson = json_decode(file_get_contents($sJsonfile), true);
+                if (array_key_exists("version", $aJson)) {
+                    $aTmp[$sKey] = $aJson;
+                    $aTmp[$sKey]["infofile"] = $sJsonfile;
+                    $aTmp[$sKey]["ok"] = 1;
+                } else {
+                    $aTmp[$sKey]["error"] = "info file $sJsonfile exists but is corrupt (no version).<pre>" . print_r($aJson, true) . "</pre>";
+                }
+            } else {
+                $aTmp[$sKey]["info"] = "No package is waiting in the queue.";
+                $aTmp[$sKey]["ok"] = 1;
+            }
+
+            // package for puppet
+            $sKey = "ready4deployment";
+            $sJsonfile = $this->_getInfofile($sPhase, $sKey);
+            $aTmp[$sKey] = array();
+            if (file_exists($sJsonfile)) {
+                $sPkgfile = $this->_getPackagefile($sPhase, $sKey);
+                if (file_exists($sPkgfile)) {
+                    $aJson = json_decode(file_get_contents($sJsonfile), true);
+                    if (is_array($aJson) && array_key_exists("version", $aJson)) {
+                        $aTmp[$sKey] = $aJson;
+                        $aTmp[$sKey]["infofile"] = $sJsonfile;
+                        $aTmp[$sKey]["packagefile"] = $sPkgfile;
+                        $aTmp[$sKey]["ok"] = 1;
+                    } else {
+                        $aTmp[$sKey]["error"] = "info file $sJsonfile exists but is corrupt (no version).<pre>" . print_r($aJson, true) . "</pre>";
+                    }
+                } else {
+                    $aTmp[$sKey]["error"] = "package file was not found: $sPkgfile";
+                }
+            } else {
+                $aTmp[$sKey]["error"] = "info file was not found: $sJsonfile";
+            }
+
+            // published data
+            $sKey = "deployed";
+            $sJsonfile = $this->_getInfofile($sPhase, $sKey);
+            $aTmp[$sKey] = array();
+            if ($this->isActivePhase($sPhase)) {
+                $sJsonUrl = $this->_aPrjConfig["phases"][$sPhase]["url"] . $this->_aPrjConfig["fileprefix"] . ".json";
+                $sJsonData = @file_get_contents($sJsonUrl);
+                if ($sJsonData) {
+                    $aJson = json_decode($sJsonData, true);
+                    if (array_key_exists("version", $aJson)) {
+                        $aTmp[$sKey] = $aJson;
+                        $aTmp[$sKey]["infofile"] = $sJsonUrl;
+                        $aTmp[$sKey]["ok"] = 1;
+                    } else {
+                        $aTmp[$sKey]["error"] = "json url was readable $sJsonUrl but is corrupt (no version).<pre>" . print_r($aJson, true) . "</pre>";
+                    }
+                } else {
+                    $aTmp[$sKey]["error"] = "json url not readable $sJsonUrl";
+                }
+            } else {
+                $aTmp[$sKey]["warning"] = "this phase is not active or has no url";
+            }
+
+            $this->_aData["phases"][$sPhase] = $aTmp;
+        }
+        return $this->_aData["phases"][$sPhase];
+    }
+
+    /**
+     * get a list of all existing projects as a flat array
+     * <code>
+     * print_r($oPrj->getProjects());
+     * </code>
+     * returns<br>
+     * Array ( [0] => project1 [1] => project2 ) 
+     * @return array
+     */
+    public function getProjects() {
+        $aReturn = array();
+        foreach (glob(__dir__ . '/' . $this->_sCfgdir . "/*.json") as $filename) {
+            $aReturn[] = str_replace(".json", "", basename($filename));
+        }
+        sort($aReturn);
+        return $aReturn;
+    }
+
+    /**
+     * check if the given phase is active for this project
+     * @param type $sPhase
+     * @return type
+     */
+    public function isActivePhase($sPhase) {
+        return (
+                array_key_exists($sPhase, $this->_aPrjConfig["phases"]) && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase]) && $this->_aPrjConfig["phases"][$sPhase]["url"]
+                );
+    }
+
+    /**
+     * return array of all (active and inactive) phases
+     * @return type
+     */
+    public function getPhases() {
+        return $this->_aConfig["phases"];
+    }
+
+    /**
+     * get a flat array with active phases of the project
+     * @return array
+     */
+    public function getActivePhases() {
+        $aReturn = array();
+        foreach (array_keys($this->_aConfig["phases"]) as $s) {
+            if ($this->isActivePhase($s)) {
+                $aReturn[] = $s;
+            }
+        }
+        return $aReturn;
+    }
+
+    /**
+     * find the next active phase of a project
+     * @param string $sPhase current phase; if empty the function sends back the first phase
+     */
+    public function getNextPhase($sPhase = false) {
+        if ($sPhase) {
+            if (!array_key_exists($sPhase, $this->_aConfig["phases"])) {
+                die("ERROR: this phase does not exist: $sPhase.");
+            }
+        }
+
+        $sNextPhase = false;
+        $bUseNextPhase = $sPhase ? false : true;
+        foreach (array_keys($this->_aConfig["phases"]) as $s) {
+            if ($bUseNextPhase) {
+                if ($this->isActivePhase($s)) {
+                    $sNextPhase = $s;
+                    $bUseNextPhase = false;
+                    continue;
+                }
+            }
+            if ($sPhase == $s) {
+                $bUseNextPhase = true;
+            }
+        }
+
+        return $sNextPhase;
+    }
+
+    /**
+     * check: is the deployment to the next phase enabled for this phase?
+     * @param type $sPhase
+     */
+    public function canAcceptPhase($sPhase = false) {
+
+        if (!$sPhase) {
+            $sNext = $this->getNextPhase($sPhase);
+            return $sNext > '';
+        }
+
+
+        if (!array_key_exists($sPhase, $this->_aConfig["phases"])) {
+            die("ERROR: in canAcceptPhase this phase does not exist: $sPhase.");
+        }
+        if (!$this->isActivePhase($sPhase)) {
+            // die("ERROR: the phase $sPhase is not active in this project.");
+            return false;
+        }
+        $sNext = $this->getNextPhase($sPhase);
+        if (!$sNext)
+            return false;
+
+        // ensure that _aData is filled
+        $this->getPhaseInfos($sPhase);
+
+        // array key "ok" must be in the ready4deployment and deployed info
+        if (
+                array_key_exists($sPhase, $this->_aData["phases"]) && array_key_exists("onhold", $this->_aData["phases"][$sPhase]) && array_key_exists("ready4deployment", $this->_aData["phases"][$sPhase]) && array_key_exists("deployed", $this->_aData["phases"][$sPhase]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["onhold"]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["ready4deployment"]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["deployed"])
+        )
+            return true;
+
+        return false;
+    }
+
+    /**
+     * get current revision from remote repo
+     * @return string
+     */
+    public function getRepoRevision($bRefresh = false) {
+        $sReturn = "";
+        if (
+                array_key_exists("source", $this->_aData["phases"]) && $this->_aData["phases"]["source"] && $bRefresh == false
+        )
+            return $this->_aData["phases"]["source"];
+        switch ($this->_aPrjConfig["build"]["type"]) {
+            case "git":
+                $sKeyfile = dirname(dirname(__file__)) . "/" . $this->_aPrjConfig["build"]["keyfile"];
+                $sWrapper = dirname(dirname(dirname(dirname(__file__)))) . "/shellscripts/gitsshwrapper.sh";
+                $sGitCmd = "git ls-remote --heads " . $this->_aPrjConfig["build"]["ssh"] . "  master | awk '{ print $1 }'";
+                $sRevision = shell_exec("export GIT_SSH=$sWrapper ; export PKEY=$sKeyfile; $sGitCmd");
+                $sReturn = trim($sRevision);
+                /*
+                  $sGitCmd="export GIT_SSH=$sWrapper ; ";
+                  $sGitCmd.="export PKEY=$sKeyfile ; ";
+                  $sGitCmd.="export testdir=/tmp/test_\$\$ ; set -vx ; ";
+                  $sGitCmd.="mkdir \$testdir && cd \$testdir && ";
+                  $sGitCmd.="git init && ";
+                  $sGitCmd.="git remote add origin " . $this->_aPrjConfig["build"]["ssh"] . " && ";
+                  // $sGitCmd.="git clean -f && ";
+                  // $sGitCmd.="git pull ; ";
+                  // $sGitCmd.="sleep 1 ; ";
+                  $sGitCmd.="git log -1 --format=fuller origin/master remote; ";
+                  // $sGitCmd.="cd /tmp ; rm -rf \$testdir ";
+                  $sReturn.= $this->_execAndSend($sGitCmd);
+                 */
+                $this->_aData["phases"]["source"] = $sReturn;
+                break;
+            default:
+                die("getRepoRevision(): Build Type not supported: " . $this->_aPrjConfig["build"]["type"] . $sReturn);
+        }
+        return $sReturn;
+    }
+
+    // ----------------------------------------------------------------------
+    // SETTER
+    // ----------------------------------------------------------------------
+
+    /**
+     * apply a config
+     * @param array $aConfig
+     * @return boolean
+     */
+    public function setProjectById_OLD($sId) {
+        $this->_aPrjConfig = array();
+        require(__dir__ . '/' . $this->_sCfgfile);
+        if (!array_key_exists("$sId", $aProjects)) {
+            die("ERROR: a project with ID $sId does not exist.");
+        }
+        $this->_aPrjConfig = $aProjects[$sId];
+        $this->_aConfig["id"] = $sId;
+        $this->_verifyConfig();
+        return true;
+    }
+
+    /**
+     * apply a config
+     * @param array $aConfig
+     * @return boolean
+     */
+    public function setProjectById($sId) {
+        $this->_aPrjConfig = array();
+        require(__dir__ . '/' . $this->_sCfgfile);
+        $this->_aConfig["id"] = $sId;
+
+
+        /*
+          if (!array_key_exists("$sId", $aProjects)) {
+          die("ERROR: a project with ID $sId does not exist.");
+          }
+          $this->_aPrjConfig = $aProjects[$sId];
+          if (file_exists($this->_getConfigFile($sId))) {
+          die("ERROR: a project with ID $sId does not exist.");
+          }
+         */
+
+        $this->_aPrjConfig = json_decode(file_get_contents($this->_getConfigFile($sId)), true);
+        // $aData=json_decode(file_get_contents($this->_getConfigFile($sId)), true);
+        // echo "<pre>" . print_r($aData, true) . "</pre>";
+
+        $this->_verifyConfig();
+        return true;
+    }
+
+    // ----------------------------------------------------------------------
+    // ACTIONS
+    // ----------------------------------------------------------------------
+
+    /**
+     * get html code of a div around a message
+     * @param string $sWarnlevel one of error|success|info|warning to get a colored box
+     * @param string $sMessage   message txt
+     * @return string
+     */
+    public function getBox($sWarnlevel, $sMessage) {
+        $aCfg = array(
+            "error" => array("class" => "alert alert-error", "prefix" => "ERROR :-("),
+            "success" => array("class" => "alert alert-success", "prefix" => "SUCCESS :-)"),
+            "info" => array("class" => "alert alert-info", "prefix" => "INFO"),
+            "warning" => array("class" => "alert alert-block", "prefix" => "WARNING"),
+        );
+        $sClass = "";
+        $sPrefix = "";
+        if (array_key_exists($sWarnlevel, $aCfg)) {
+            $sClass = $aCfg[$sWarnlevel]["class"];
+            $sPrefix = $aCfg[$sWarnlevel]["prefix"];
+            $sMessage = '<strong>' . $aCfg[$sWarnlevel]["prefix"] . '</strong> ' . $sMessage;
+        }
+        return '
+            <div class="' . $sClass . '">
+                ' . $sMessage . '
+            </div>';
+    }
+
+    /**
+     * Build a new package for the deployment. It will be put to the queue
+     * of the first active phase (i.e. preview).
+     * If there is no deployment time range it will be deployed too.
+     * @global type $aParams
+     * @return boolean|string
+     */
+    public function build() {
+        global $aParams;
+        $sReturn = false;
+
+        $this->_iRcAll = 0;
+        // return $this->_execAndSend("bash --login -c 'ruby --version' " . $sTempDir);
+
+        $sReturn = "<h2>Build " . $this->getLabel() . "</h2>";
+
+        // --------------------------------------------------
+        // create workdir
+        // --------------------------------------------------
+        $aDirs = $this->cleanupBuilds();
+        if (count($aDirs))
+            $sReturn.='<h3>Cleanup older failed builds</h3><pre>' . print_r($aDirs, true) . '</pre>';
+
+        $sTempDir = $this->_getTempDir();
+        $sFirstLevel = $this->getNextPhase();
+        if (!$sFirstLevel)
+            return false;
+
+        $sReturn.="<h3>Create a temporary build dir</h3>";
+        if (!file_exists($sTempDir)) {
+            $sReturn.=$this->_execAndSend("mkdir -p " . $sTempDir);
+        }
+        $sReturn.=$this->_execAndSend("ls -ld " . $sTempDir);
+        if (!file_exists($sTempDir)) {
+            return $this->getBox("error", "$sTempDir was not created." . $sReturn);
+        }
+
+        // --------------------------------------------------
+        // checkout
+        // --------------------------------------------------
+        switch ($this->_aPrjConfig["build"]["type"]) {
+            case "git":
+
+                $sReturn.="<h3>Checkout a GIT project</h3>";
+                // $sReturn.=$this->_execAndSend("find " . $this->_aConfig["workDir"]);
+                // SKIP $sReturn.=$this->_execAndSend("cd $sTempDir && git init");
+
+                $sKeyfile = dirname(dirname(__file__)) . "/" . $this->_aPrjConfig["build"]["keyfile"];
+                $sWrapper = dirname(dirname(dirname(dirname(__file__)))) . "/shellscripts/gitsshwrapper.sh";
+                // $sReturn.=$this->_execAndSend("cd $sTempDir && export GIT_SSH=$sWrapper ; export PKEY=$sKeyfile; git pull " . $this->_aPrjConfig["build"]["ssh"]);
+                $sReturn.=$this->_execAndSend("export GIT_SSH=$sWrapper ; export PKEY=$sKeyfile; git clone " . $this->_aPrjConfig["build"]["ssh"] . " $sTempDir ");
+
+                // $sVersion=$this->_execAndSend("cd $sTempDir && export GIT_SSH=$sWrapper ; export PKEY=$sKeyfile; git pull " . $this->_aPrjConfig["build"]["ssh"]);
+                // control: directory listing after checkout
+                $sReturn.=$this->_execAndSend("ls -lisa $sTempDir");
+
+                // fetch version infos
+
+                $sRevision = shell_exec("cd $sTempDir && git log -n 1 --pretty=format:\"%H\"");
+                $sCommitMsg = shell_exec("cd $sTempDir && git log -1");
+                $sCommitMsg = str_replace("\n", "<br>", $sCommitMsg);
+                $sCommitMsg = str_replace('"', "&quot;", $sCommitMsg);
+                // $sCommitMsg = str_replace('<', "&lt;", $sCommitMsg);
+                // $sCommitMsg = str_replace('>', "&gt;", $sCommitMsg);
+                $sReturn.=$this->getBox("info", $sCommitMsg);
+
+                break;
+
+            default:
+                return $this->getBox("error", "Build Type not supported: " . $this->_aPrjConfig["build"]["type"] . $sReturn);
+        }
+        if (!$this->_iRcAll == 0) {
+            return $this->getBox("error", "checkout failed.</h3>One of the commands failed (see above).<br>You can ask the sysadmins and analyze with them the created temp directory &quot;' . $sTempDir . '&quot;." . $sReturn);
+        }
+        $sReturn.=$this->getBox("success", "Checkout OK!");
+
+
+        // --------------------------------------------------
+        // copy default structure
+        // --------------------------------------------------
+        $sReturn.="<h3>get default data</h3>";
+        if ($this->_getDefaultsDir()) {
+            $sReturn.='get data from ' . $this->_getDefaultsDir() . '<br>';
+            $sReturn.=$this->_execAndSend("find " . $this->_getDefaultsDir() . " | head -15");
+            $sReturn.=$this->_execAndSend("rsync -r " . $this->_getDefaultsDir() . "/* $sTempDir");
+            // $sReturn.=$this->_execAndSend("find $sTempDir");
+        } else {
+            $sReturn.='No defaults ... starting with empty directory.<br>';
+        }
+
+        // --------------------------------------------------
+        // execute hook
+        // --------------------------------------------------
+        $sHookfile = $this->_aConfig['hooks']['build-aftercheckout'];
+        $sReturn.='<h3>Execute Hook ' . $sHookfile . '</h3>';
+        if (file_exists($sTempDir . '/' . $sHookfile)) {
+            $sReturn.=$this->_execAndSend('cd ' . $sTempDir . ' && chmod 755 hooks/on*');
+            $sReturn.=$this->_execAndSend('bash --login -c \'' . $sTempDir . '/' . $sHookfile . '\'');
+            if (!$this->_iRcAll == 0) {
+                return $this->getBox("error", "executing hook failed. One of the commands failed.<br>You can ask the sysadmins and analyze with them the created temp directory &quot;' . $sTempDir . '&quot;." . $sReturn);
+            }
+        } else {
+            $sReturn.='SKIP. Hook was not found.<br>';
+        }
+
+
+        // --------------------------------------------------
+        // TODO: cleanup .git, .svn, ...?
+        // wenn es kein .git gibt, bricht er ab...
+        // --------------------------------------------------
+        $sReturn.="<h3>cleanup project</h3>";
+        $sReturn.=$this->_execAndSend("cd $sTempDir && rm -rf .git");
+        // $sReturn.=$this->_execAndSend("cd $sTempDir && rm -rf .svn");
+        // $sReturn.=$this->_execAndSend("find  $sTempDir -type d -name '.svn' -exec rm -rf {} \;");
+        // public_html must exist
+        $sReturn.="<h3>check public and public_html</h3>";
+
+        $sWebroot = false;
+        $sWebroot1 = $sTempDir . '/public_html';
+        $sWebroot2 = $sTempDir . '/public';
+        if (file_exists($sWebroot1)) {
+            $sWebroot = $sWebroot1;
+        }
+        if (file_exists($sWebroot2)) {
+            $sWebroot = $sWebroot2;
+        }
+
+        if (!$sWebroot) {
+            return $this->getBox("error", "a subdir &quot;public_html&quot; or &quot;public&quot; does not exist." . $sReturn);
+        }
+
+        if (!$this->_iRcAll == 0) {
+            return $this->getBox("error", "build failed - working directory has errors and is not ready to create package.<br>You can ask the sysadmins and analyze with them the created temp directory &quot;$sTempDir&quot;" . $sReturn);
+        }
+        $sReturn.=$this->getBox("success", "preparations ok - directory is ready for packaging now.");
+
+        // --------------------------------------------------
+        // create package
+        // --------------------------------------------------
+        $sReturn.='<h3>Create package</h3>';
+
+        // generate info file
+        $sTs = date("Y-m-d H:i:s");
+        $sTs2 = date("Ymd_His");
+        $sInfoFileWebroot = $sWebroot . '/' . basename($this->_getInfofile($sFirstLevel, "deployed"));
+        $sInfoFileArchiv = $this->_getArchiveDir($sTs2) . '/' . basename($this->_getInfofile($sFirstLevel, "deployed"));
+        $sPackageFileArchiv = $this->_getArchiveDir($sTs2) . '/' . basename($this->_getPackagefile($sFirstLevel, "deployed"));
+
+        $sInfos = '{
+            "date": "' . $sTs . '",
+            "version": "' . $sTs2 . '",
+            "revision": "' . $sRevision . '",
+            "message": "' . $sCommitMsg . '"
+        }';
+        /*
+          "user": "' . $aParams["inputUser"] . '",
+          "remark": "' . $aParams["inputComment"] . '"
+         */
+
+        $sReturn.="writing info file into webroot...<br>";
+        file_put_contents($sInfoFileWebroot, $sInfos);
+        $sReturn.=$this->_execAndSend("ls -l $sInfoFileWebroot");
+
+        if (!file_exists(dirname($sPackageFileArchiv))) {
+            $sReturn.="* create " . dirname($sPackageFileArchiv) . "<br>";
+            mkdir(dirname($sPackageFileArchiv), 0775, true);
+        }
+        $sReturn.=$this->_execAndSend("ls -ld " . dirname($sPackageFileArchiv));
+        if (!file_exists(dirname($sPackageFileArchiv))) {
+            return $this->getBox("error", "directory was not created: " . dirname($sPackageFileArchiv) . $sReturn);
+            return $sReturn;
+        }
+
+        // create tgz archive
+        $sReturn.="create archive $sPackageFileArchiv<br>";
+        $sReturn.=$this->_execAndSend("cd $sTempDir && tar -czf $sPackageFileArchiv .");
+
+        // write info file (.json)
+        $sReturn.="writing info file into archive...<br>";
+        file_put_contents($sInfoFileArchiv, $sInfos);
+
+        // copy template files
+        if (file_exists($sTempDir . '/hooks/templates/')) {
+            $sReturn.="put templatesfile into archive...<br>";
+            $sReturn.=$this->_execAndSend("cp $sTempDir/hooks/templates/* " . $this->_getArchiveDir($sTs2));
+        } else {
+            $sReturn.="SKIP put templatesfile into archive - $sTempDir/hooks/templates/ does not exist.<br>";
+        }
+
+        $sReturn.="<br>Created Archive files:<br>";
+        $sReturn.=$this->_execAndSend("ls -l " . $this->_getArchiveDir($sTs2));
+
+        if (!$this->_iRcAll == 0) {
+            return $this->getBox("error", 'creation failed One of the commands failed (see below).<br>You can ask the sysadmins and analyze with them the created temp directory &quot;' . $sTempDir . '&quot;.' . $sReturn);
+        }
+
+
+        $sReturn.="<h3>Cleanup</h3>";
+        $sReturn.="<h3>cleanup $sTempDir</h3>";
+        $sReturn.=$this->_execAndSend("rm -rf $sTempDir");
+        $sReturn.="<h3>cleanup Archive</h3>removing the oldest unused packages ...";
+        $sReturn.='<pre>' . print_r($this->cleanupArchive(), true) . '</pre>';
+
+        $sReturn.=$this->getBox("success", "Build finished successfully.");
+
+        // TODO: force synch archive to puppet master
+
+        $sReturn.=$this->queue($sFirstLevel, $sTs2);
+
+
+
+        return $sReturn;
+    }
+
+    /**
+     * put a packaged version into the queue of a specified phase
+     * @param string $sPhase    name of the phase
+     * @param string $sVersion  version 
+     * @return string
+     */
+    public function queue($sPhase, $sVersion) {
+        $sReturn = "<h2>Queue " . $this->getLabel() . " to $sPhase</h2>";
+
+        if (!$this->isActivePhase($sPhase)) {
+            return $sReturn . $this->getBox("error", 'phase ' . $sPhase . ' is not active.');
+        }
+
+        $sPlace = "onhold";
+
+        $sLinkTarget = $this->_getArchiveDir($sVersion);
+        $sLinkName = $this->_getFileBase($sPhase, $sPlace);
+
+        // --------------------------------------------------
+        // Checks
+        // --------------------------------------------------
+        if (!$sLinkName) {
+            die("ERROR: Queuing failed - sLinkName is empty.");
+        }
+        if (!$sLinkTarget) {
+            die("ERROR: Queuing  failed - sLinkTarget is empty.");
+        }
+        if (!file_exists($sLinkTarget)) {
+            die("ERROR: Queuing failed - version $sVersion is invalid. The directory $sLinkTarget does not exist.");
+        }
+
+        // --------------------------------------------------
+        // create the new link
+        // --------------------------------------------------
+        $this->_iRcAll = 0;
+        if (file_exists($sLinkName)) {
+            $sReturn.="removing existing version<br>";
+            $sReturn.=$this->_execAndSend("rm -f $sLinkName");
+        }
+        $sReturn.="linking to new version <br>";
+        $sReturn.=$this->_execAndSend("ln -s $sLinkTarget $sLinkName");
+        $sReturn.=$this->_execAndSend("ls -l $sLinkName | fgrep $sLinkTarget");
+
+
+        if (!$this->_iRcAll == 0) {
+            return $this->getBox("error", 'Queuing failed One of the commands failed.' . $sReturn);
+        }
+        $sReturn.=$this->getBox("success", "the version $sVersion was set to place $sPlace");
+        $sReturn.=$this->deploy($sPhase);
+
+        return $sReturn;
+    }
+
+    /**
+     * deploy a queued package - this moves the queue into the repo directory
+     * and will be installed on server within 30 min.
+     * This method checks the deploy times
+     * @param string $sPhase which queue of which phase we want to install in server
+     * @return boolean|string
+     */
+    public function deploy($sPhase) {
+        $sReturn = "<h2>Deploy " . $this->getLabel() . " to $sPhase</h2>";
+
+        if (!$this->isActivePhase($sPhase)) {
+            return $sReturn . $this->getBox("error", 'phase ' . $sPhase . ' is not active.');
+        }
+
+        $sQueueLink = $this->_getFileBase($sPhase, "onhold");
+        $sRepoLink = $this->_getFileBase($sPhase, "ready4deployment");
+
+        if (array_key_exists("deploytimes", $this->_aConfig["phases"][$sPhase])) {
+            // check if the a deploy time is reached
+            $sNow = date("D H:i:s");
+            $sReturn.="<h3>check deployment times</h3>";
+            $sReturn.="check if one of the deployment times is reached and matches time on server <strong>$sNow</strong><br>";
+            $bCanDeploy = false;
+            foreach ($this->_aConfig["phases"][$sPhase]["deploytimes"] as $sRegex) {
+                $sReturn.="... test &quot;$sRegex&quot; ... ";
+                if (preg_match($sRegex, $sNow)) {
+                    $sReturn.="OK";
+                    $bCanDeploy = true;
+                } else {
+                    $sReturn.="no.";
+                }
+                $sReturn.="<br>";
+            }
+            if (!$bCanDeploy) {
+                $sReturn.=$this->getBox("info", "SKIP: Im Moment ist leider kein Deployment-Zeitfenster");
+                return $sReturn;
+            }
+            $sReturn.="OK, deployment time was reached.<br>";
+            // if ()
+        }
+        if (!file_exists($sQueueLink)) {
+            $sReturn.=$this->getBox("info", "SKIP: nothing to do - the current queue is empty ($sQueueLink does not exist).");
+            return $sReturn;
+        }
+
+
+        // --------------------------------------------------
+        // move the queue link to the repo name
+        // --------------------------------------------------
+        $this->_iRcAll = 0;
+        if (file_exists($sRepoLink)) {
+            $sReturn.="removing existing version<br>";
+            $sReturn.=$this->_execAndSend("rm -f $sRepoLink");
+        }
+        $sReturn.="moving queue to repo<br>";
+        $sReturn.=$this->_execAndSend("mv $sQueueLink $sRepoLink");
+
+
+        if (!$this->_iRcAll == 0) {
+            return $this->getBox("error", "Deployment failed - One of the commands failed." . $sReturn);
+        }
+
+        // $sReturn.=$this->_execAndSend("ln -s $sLinkTarget $sLinkName");
+        if (array_key_exists('mirrorPackages', $this->_aConfig) && count($this->_aConfig['mirrorPackages'])) {
+            foreach ($this->_aConfig['mirrorPackages'] as $sLabel => $aTarget) {
+                $sReturn.="synching package - $sLabel<br>";
+                if (array_key_exists('type', $aTarget)) {
+                    $sCmd = false;
+                    // $sSource=$this->_aConfig["packageDir"]."/$sPhase/*";
+                    $sSource = $sRepoLink;
+                    $sTarget = $aTarget['target'] . "/$sPhase";
+                    switch ($aTarget['type']) {
+                        case 'rsync':
+                            $sCmd = "ls -l $sSource 2>/dev/null && /usr/bin/rsync --delete -rLv  $sSource $sTarget";
+                            break;
+                        default:
+                            $sReturn.="SKIP: type " . $aTarget['type'] . " ist not supported (yet) - skipping.\n";
+                            break;
+                    } // switch
+                    if ($sCmd) {
+                        /*
+                          if ($aTarget['runas']) {
+                          $sCmd="su - " . $aTarget['runas'] . " -c \"" . $sCmd . "\"";
+                          }
+                         * 
+                         */
+                        $sReturn.=$this->_execAndSend($sCmd);
+                    }
+                }
+            } // foreach
+        }
+
+        // TODO: run puppet agent on target server(s) - for preview only
+        if (array_key_exists("puppethost", $this->_aPrjConfig["phases"][$sPhase]) && $this->_aPrjConfig["phases"][$sPhase]["puppethost"]
+        ) {
+            $sReturn.="Run puppet agent.<br>";
+            $sReturn.=$this->_execAndSend("ssh imldeployment@" . $this->_aPrjConfig["phases"][$sPhase]["puppethost"] . " sudo puppet agent -t | fgrep -i Deploy");
+        } else {
+            $sReturn.="SKIP: no puppet host was defined. The deployment was done and will be installed soon (within 30min).<br>";
+        }
+
+        $sReturn.="<br>";
+        $sReturn.=$this->getBox("success", "SUCCESS: deployment was done.");
+        return $sReturn;
+    }
+
+    /**
+     * accept a the installed version in a phase and put this version
+     * to the queue of the next phase.
+     * @param string $sPhase which queue of which phase we want to install in server
+     * @return type
+     */
+    public function accept($sPhase) {
+        $sReturn = "<h2>Accept " . $this->getLabel() . " phase $sPhase</h2>";
+        if (!$this->canAcceptPhase($sPhase)) {
+            return $sReturn . $this->getBox("error", 'phase ' . $sPhase . ' cannot be accepted.');
+        }
+
+        $sReturn.="<h3>Info: Installed on $sPhase</h3>";
+        $aInfos = $this->getPhaseInfos($sPhase);
+        $sVersion = $aInfos["deployed"]["version"];
+        $sNext = $this->getNextPhase($sPhase);
+
+
+        $sReturn.='<pre>' . print_r($aInfos["deployed"], true) . '</pre>';
+        $sReturn.=$this->getBox("info", "package version is [" . $sVersion . "] and will be queued to phase [$sNext].");
+        $sReturn.=$this->queue($sNext, $sVersion);
+
+        return $sReturn;
+    }
+
+    /**
+     * save POSTed data as project config
+     * @return boolean
+     */
+    public function saveConfig($aData = false) {
+        if (!$aData)
+            $aData = $_POST;
+        if (!array_key_exists("id", $aData))
+            return false;
+        // if (!array_key_exists("setupaction", $_POST)) return false;
+
+        $sId = $aData["id"];
+
+        // remove unwanted items
+        foreach (array("setupaction", "prj", "id") as $s) {
+            if (array_key_exists($s, $aData))
+                unset($aData[$s]);
+        }
+
+        // echo "IST <pre>" . print_r($this->_aPrjConfig, true) . "</pre>"; echo "NEU <pre>" . print_r($aData, true) . "</pre>"; die();
+        // make a backup of a working config
+        $sCfgFile = $this->_getConfigFile($sId);
+        $sBakFile = $this->_getConfigFile($sId) . ".ok";
+        copy($sCfgFile, $sBakFile);
+
+        $bReturn = file_put_contents($sCfgFile, json_encode($aData));
+        $this->setProjectById($sId);
+        return $bReturn;
+    }
+
+    /**
+     * create a new project; it returns the error message if it fails and
+     * an empty string if it was successful.
+     * @param string  $sId  id
+     * @return string
+     */
+    public function create($sId) {
+        if (!$sId) {
+            return "Ohne ID kann ich kein Projekt anlegen.";
+        }
+        $s = preg_replace('/[a-z\-\_0-9]*/', "", $sId);
+        if ($s) {
+            return "Die ID $sId enthaelt unerlaubte Zeichen. Erlaubt sind Kleinbuchstaben, Ziffern, Minus, Unterstrich.";
+        }
+        if ($sId == "all") {
+            return "ID $sId ist reserviert.";
+        }
+        if (array_search($sId, $this->getProjects()) !== false) {
+            return "Die ID $sId ist bereits vergeben.";
+        }
+
+        // reset config and create a skeleton
+        $this->_readConfig();
+        $this->_aConfig["id"] = $sId;
+
+        $this->_aPrjConfig = array(
+            "id" => $sId, // for saveConfig
+            "label" => "$sId",
+            "fileprefix" => "$sId",
+            "description" => '',
+            "contact" => '',
+            "build" => array(
+                "type" => "",
+                "ssh" => "",
+                "keyfile" => "",
+                "webaccess" => "",
+            ),
+            "phases" => array(
+                "preview" => array(),
+                "stage" => array(),
+                "live" => array(),
+            ),
+        );
+        $this->_verifyConfig(); // check skeleton
+        $bReturn = $this->saveConfig($this->_aPrjConfig);
+        if (!$bReturn) {
+            return "Das neue Projekt konnte nicht gespeichert werden.";
+        }
+
+        // alles OK - dann leeren String
+        return "";
+    }
+
+    // ----------------------------------------------------------------------
+    // RENDERING
+    // ----------------------------------------------------------------------
+
+    /**
+     * return html code for a div with background color based on a checksum of the given text
+     * @param string $sText text that is used for checksum
+     * @param string $sFormat one of hex|rgba
+     * @param float  $fAlpha  alpha channel for rgba; 0..1
+     * @return string
+     */
+    private function _getChecksumDiv($sText, $sFormat = "hex", $fAlpha = 1.0) {
+        return '<div style="background: ' . $this->_getChecksumColor($sText, $sFormat, $fAlpha) . '; height: 3px;"> </div>';
+    }
+
+    /**
+     * generate css color based on a checksum of the given text
+     * @param string $sText text that is used for checksum
+     * @return string 
+     */
+    private function _getChecksumColor($sText, $sFormat = "hex", $fAlpha = 1.0) {
+        $sReturn = '';
+        $s = md5($sText);
+        $sRH = substr($s, 0, 2);
+        $sGH = substr($s, 2, 2);
+        $sBH = substr($s, 4, 2);
+        switch ($sFormat) {
+            case "rgba":
+                $sReturn = "rgba(" . hexdec($sRH) . ", " . hexdec($sGH) . ", " . hexdec($sBH) . ", " . $fAlpha . ")";
+                break;
+
+            default:
+                $sReturn = "#$sRH$sGH$sBH";
+                break;
+        }
+        return $sReturn;
+    }
+
+    /**
+     * get html code for the colored bar on top of each phase detail items
+     * @param string $sPhase
+     * @param string $sPlace
+     * @return string
+     */
+    private function _renderBar($sPhase, $sPlace) {
+        $aDataPhase = $this->getPhaseInfos($sPhase);
+        $aData = $aDataPhase[$sPlace];
+        if (!array_key_exists("revision", $aData)) {
+            return false;
+        }
+        return $this->_getChecksumDiv($aData["revision"]);
+    }
+
+    /**
+     * render html for a colored link to any project action
+     * @param string $sFunction name of the action; one of accept|build|cleanup|deploy|phase
+     * @param string $sPhase    current phase where to place the link
+     * @return string
+     */
+    public function renderLink($sFunction, $sPhase = false) {
+        $sFirst = $this->getNextPhase();
+        $sNext = $this->getNextPhase($sPhase);
+        $aLinkdata = array(
+            'default' => array('icon' => 'icon-forward', 'class' => ''),
+            'accept' => array('icon' => 'icon-forward', 'class' => $sNext,
+                'hint' => 'Accept [' . $sPhase . '] und in die Queue von [' . $sNext . '] stellen.',
+                'label' => 'Accept'
+            ),
+            'build' => array('icon' => 'icon-forward', 'class' => $sFirst,
+                'hint' => 'neues Paket erstellen und in [' . $sFirst . '] stellen.',
+                'label' => 'Build'
+            ),
+            'cleanup' => array('icon' => 'icon-chevron-right', 'class' => ''),
+            'deploy' => array('icon' => 'icon-forward', 'class' => $sPhase,
+                'hint' => 'Deploy der Queue von [' . $sPhase . ']',
+                'label' => 'Deploy'
+            ),
+            'overview' => array('icon' => 'icon-book', 'class' => '',
+                'hint' => 'Projekt-Übersicht [' . $this->getLabel() . ']',
+                'label' => $this->getLabel()
+            ),
+            'phase' => array('icon' => 'icon-chevron-right', 'class' => $sPhase,
+                'hint' => 'Details zur Phase [' . $sPhase . ']',
+                'label' => 'Details'
+            ),
+        );
+        $sClass = $sPhase;
+        $sIconClass = (array_key_exists($sFunction, $aLinkdata)) ? $aLinkdata[$sFunction]['icon'] : $aLinkdata['default']['icon'];
+        $sHint = (
+                array_key_exists($sFunction, $aLinkdata) && array_key_exists("hint", $aLinkdata[$sFunction])
+                ) ? $aLinkdata[$sFunction]['hint'] : "";
+        $sLabel = (
+                array_key_exists($sFunction, $aLinkdata) && array_key_exists("label", $aLinkdata[$sFunction])
+                ) ? $aLinkdata[$sFunction]['label'] : $sFunction;
+        $sClass = (
+                array_key_exists($sFunction, $aLinkdata) && array_key_exists("class", $aLinkdata[$sFunction])
+                ) ? $aLinkdata[$sFunction]['class'] : '';
+
+        $sLink = "/deployment/" . $this->_aConfig["id"] . "/";
+        if ($sFunction != "overview") {
+            $sLink.="$sFunction/";
+        }
+        if ($sPhase) {
+            $sLink.="$sPhase/";
+        }
+
+        return '<a href="' . $sLink . '" title="' . $sHint . '" class="btn ' . $sClass . '"><i class="' . $sIconClass . '"></i> ' . $sLabel . '</a>';
+    }
+
+    /**
+     * render html for project metadata in the overview 
+     * @return string
+     */
+    public function renderProjektInfos() {
+        $aLang = array(
+            'label' => 'Projektname',
+            'description' => 'Beschreibung',
+            'contact' => 'Kontakt',
+        );
+        $sReturn = false;
+        $sReturn.='<table><tbody>';
+        foreach ($aLang as $key => $sLong) {
+            $sReturn.='<tr><td>' . $sLong . ':</td><td>' . $this->_aPrjConfig[$key] . '</td></tr>';
+        }
+        if (array_key_exists("type", $this->_aPrjConfig["build"])) {
+            $sReturn.='<tr><td>Quell-Repository:</td><td><strong>' . $this->_aPrjConfig["build"]["type"] . '</strong><br>';
+            if (array_key_exists("ssh", $this->_aPrjConfig["build"])) {
+                $sReturn.='Zugriff mit SSH-Protokoll auf<br><em>' . $this->_aPrjConfig["build"]["ssh"] . '</em><br>';
+            }
+            $sReturn.='Browserzugriff auf das Repo:<br>';
+            if (array_key_exists("webaccess", $this->_aPrjConfig["build"])) {
+                $sReturn.='<a href="' . $this->_aPrjConfig["build"]["webaccess"] . '">' . $this->_aPrjConfig["build"]["webaccess"] . '</a><br>';
+            } else {
+                $sReturn.='unkbekannt<br>';
+            }
+            $sReturn.='</td></tr>';
+        }
+        $sReturn.='</tbody></table>';
+
+        return $sReturn;
+    }
+
+    /**
+     * render html for the project overview; it shows the defined phases for 
+     * the project as a table
+     * @return type
+     */
+    public function renderPhaseInfo() {
+        $sRow1 = false;
+        $sRow2 = false;
+        foreach ($this->getActivePhases() as $sPhase) {
+            $sRow1.='<th class="' . $sPhase . '">' . $sPhase . '</th>';
+            $sRow2.='<td class="' . $sPhase . '">'
+                    . 'Link: <a href="' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '">' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '</a><br>'
+                    . '<br>Deployment:<br>';
+            if (array_key_exists("deploytimes", $this->_aConfig["phases"][$sPhase])) {
+                $sRow2.=implode("<br>", $this->_aConfig["phases"][$sPhase]["deploytimes"]);
+            } else {
+                $sRow2.='Ein Archiv in der Queue wird sofort ins Repo gestellt.';
+            }
+            $sRow2.='<br>' . $this->renderLink("phase", $sPhase);
+            $sRow2.='</td>';
+        }
+        return '<table><thead><tr>' . $sRow1 . '</tr></thead><tbody><tr>' . $sRow2 . '</tr></tbody></table>';
+    }
+
+    /**
+     * render html for a place of a phase
+     * @param string  $sPhase    phase
+     * @param string  $sPlace    name of the place; one of onhold|ready4deployment|deployed
+     * @param bool    $bActions  draw action links (deploy, accept) on/ off
+     * @param bool    $bLong     use long variant to display infos? 
+     * @return string|boolean
+     */
+    public function renderPhaseDetail($sPhase, $sPlace, $bActions = true, $bLong = true) {
+
+        if (!$sPhase) {
+            return false;
+        }
+        if (!$sPlace) {
+            return false;
+        }
+        if (!$this->isActivePhase($sPhase)) {
+            return false;
+        }
+        if (!array_key_exists($sPlace, $this->_aPlaces)) {
+            return false;
+        }
+
+        $sReturn = false;
+        $aDataPhase = $this->getPhaseInfos($sPhase);
+        $aData = $aDataPhase[$sPlace];
+        if (array_key_exists("ok", $aData) && array_key_exists("version", $aData)) {
+            // TODO: getChecksumDiv anhand der Repo-Versionsnummer - dann kann man beim build auch die Farbe mit dem Repo HEAD vergleichen
+            $sReturn .= '
+                ' . $this->_renderBar($sPhase, $sPlace) . '
+                <i class="icon-calendar"></i> Build vom ' . date("d.m.Y H:i:s", strtotime($aData["date"]));
+            if ($bLong) {
+                $sReturn.='<br><i class="icon-tag"></i> Revision: ' . $aData["revision"] . '<br>
+                    <i class="icon-comment"></i> Commit-Message:<br><pre>' . $aData["message"] . '</pre>';
+                if ($sPlace == "deployed" && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) {
+                    $sReturn.='<i class="icon-globe"></i> URL: <a href="' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '">' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '</a><br>';
+                }
+            } else {
+                $sReturn.=' <a href="#" class="info">Info<span>'
+                        . '<span class="' . $sPhase . ' title">' . $this->getLabel() . " :: $sPhase :: $sPlace</span><br><br>"
+                        . '<i class="icon-tag"></i> Revision: ' . $aData["revision"] . '<br>'
+                        . '<i class="icon-comment"></i> Commit-Message:<pre>' . $aData["message"] . '</pre>';
+                if ($sPlace == "deployed" && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) {
+                    $sReturn.='<i class="icon-globe"></i> URL: ' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '<br>';
+                }
+                $sReturn.='</span></a> ';
+            }
+
+            switch ($sPlace) {
+                case "onhold":
+                    if (array_key_exists($sPhase, $this->_aConfig)) {
+                        $sReturn .= print_r($this->_aConfig[$sPhase], true);
+                        if (array_key_exists("deploytimes", $this->_aConfig[$sPhase])) {
+                            $sReturn .= '<br><i class="icon-time"></i> Deployment:<br>' . implode("<br>", array_values($this->_aPhases[$sPhase]["deploytimes"]));
+                        }
+                        if ($bActions) {
+                            $sReturn .= $this->renderLink("deploy", $sPhase);
+                        }
+                    }
+                    break;
+
+                case "ready4deployment":
+                    break;
+
+                case "deployed":
+                    if ($bActions && $this->canAcceptPhase($sPhase)) {
+                        $sReturn .= $this->renderLink("accept", $sPhase);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        } else {
+            if (array_key_exists("error", $aData)) {
+                $sReturn.= '<div class="error">';
+                // if ($bLong and true){
+                //     $sReturn.= '<i class="icon-exclamation-sign"></i> FEHLER:<br>' . $aData["error"] . '';
+                // } else {
+                $sReturn.= '<a href="#" class="info"><i class="icon-exclamation-sign"></i> FEHLER:<span>' . $aData["error"] . '</span></a>';
+                // }
+                $sReturn.= '</div>';
+            } else if (array_key_exists("warning", $aData)) {
+                $sReturn.= '<div class="warning"><i class="icon-info-sign"></i> WARNUNG:<br>' . $aData["warning"] . '</div>';
+            } else {
+                $sReturn.= '[leer]';
+            }
+        }
+        return $sReturn;
+    }
+
+    /**
+     * render html for a row with td for all places
+     * @param string $sPhase  phase (just needed for coloring)
+     * @return string
+     */
+    public function renderPlacesAsTd($sPhase) {
+        $sRow1 = '';
+        foreach ($this->_aPlaces as $sPlace => $sLabel) {
+            $sRow1.='<td class="' . $sPhase . ' ' . $this->_aConfig["id"] . ' tdphase">' . $sLabel . '</td>';
+        }
+        return $sRow1;
+    }
+
+    /**
+     * render html for a row with td for all places of a phase
+     * @param string $sPhase   phase
+     * @param bool   $bActions draw action links (deploy, accept) on/ off
+     * @param bool   $bLong    use long variant to display infos? 
+     * @return string|boolean
+     */
+    public function renderAllPhaseDetails($sPhase, $bActions = true, $bLong = true) {
+        if (!$sPhase) {
+            return false;
+        }
+        if (!$this->isActivePhase($sPhase)) {
+            return '
+                        <td class="' . $sPhase . ' tdphase ' . $this->_aConfig["id"] . '" colspan="' . count($this->_aPlaces) . '">
+                            <div class="versioninfo center inactive"><i class="icon-ban-circle"></i> inactive</div>
+                        </td>';
+        }
+        $sRow2 = false;
+
+        $aRows = array();
+        $sLastPlace = '';
+        // echo "<pre>" . print_r($this->_aData, true) . "</pre>";
+        foreach (array_keys($this->_aPlaces) as $sPlace) {
+            $aRows[$sPlace] = $this->renderPhaseDetail($sPhase, $sPlace, $bActions, $bLong);
+            if ($sLastPlace && array_key_exists("version", $this->_aData["phases"][$sPhase][$sLastPlace]) && array_key_exists("version", $this->_aData["phases"][$sPhase][$sPlace]) && $this->_aData["phases"][$sPhase][$sLastPlace]["version"] == $this->_aData["phases"][$sPhase][$sPlace]["version"]
+            ) {
+                $aRows[$sLastPlace] = $this->_renderBar($sPhase, $sPlace) . "&raquo;";
+            }
+            $sLastPlace = $sPlace;
+        }
+        foreach (array_keys($this->_aPlaces) as $sPlace) {
+            $sRow2.='<td class="' . $sPhase . ' tdphase td' . $this->_aConfig["id"] . '">' . $aRows[$sPlace] . '</td>';
+        }
+        return $sRow2;
+    }
+
+    /**
+     * return html code for the installed version in the repository
+     * @return string
+     */
+    public function renderRepoInfo() {
+        $sReturn = "";
+        switch ($this->_aPrjConfig["build"]["type"]) {
+            case "git":
+
+                $sKeyfile = dirname(dirname(__file__)) . "/" . $this->_aPrjConfig["build"]["keyfile"];
+                $sWrapper = dirname(dirname(dirname(dirname(__file__)))) . "/shellscripts/gitsshwrapper.sh";
+                $sGitCmd = "git ls-remote --heads " . $this->_aPrjConfig["build"]["ssh"] . "  master | awk '{ print $1 }'";
+                $sRevision = shell_exec("export GIT_SSH=$sWrapper ; export PKEY=$sKeyfile; $sGitCmd");
+                $sRevision = $this->getRepoRevision();
+                $sReturn.=$this->_getChecksumDiv($sRevision);
+                $sReturn.= '<i class="icon-tag"></i> Revision: ' . $sRevision;
+                $sReturn.="<br>$sGitCmd<br>";
+
+                if (array_key_exists("webaccess", $this->_aPrjConfig["build"])) {
+                    $sReturn.='<br>Web-GUI des Repositories:<br><a href="' . $this->_aPrjConfig["build"]["webaccess"] . '">' . $this->_aPrjConfig["build"]["webaccess"] . '</a><br>';
+                }
+
+                break;
+
+            default:
+                return $this->getBox("error", "Build Type not supported: " . $this->_aPrjConfig["build"]["type"] . $sReturn);
+        }
+        return $sReturn;
+    }
+
+    /**
+     * return html code for a list of all built packages and their usage
+     * @return string
+     */
+    public function renderVersionUsage() {
+        $sReturn = false;
+        $sRowHead1 = false;
+        $sRowHead2 = false;
+        if (!count($this->getVersions())) {
+            return $this->getBox("info", "Für dieses Projekt wurde noch kein Paket erzeugt.");
+        }
+        foreach ($this->getActivePhases() as $sPhase) {
+            $sRowHead1.='<th class="' . $sPhase . '" colspan="' . count($this->_aPlaces) . '">' . $sPhase . '</th>';
+            $sRowHead2.=$this->renderPlacesAsTd($sPhase);
+        }
+        foreach ($this->getVersions() as $sVersion => $aData) {
+            $sReturn.='<tr>';
+            $sReturn.='<td>' . $sVersion . '</td>';
+            foreach ($this->getActivePhases() as $sPhase) {
+
+                foreach (array_keys($this->_aPlaces) as $sPlace) {
+                    $bFound = false;
+                    if (is_array($aData) && count($aData))
+                        foreach ($aData as $i => $aPhaseUsage) {
+                            if ($aPhaseUsage["phase"] == $sPhase && $aPhaseUsage["place"] == $sPlace
+                            )
+                                $bFound = true;
+                        }
+                    if ($bFound) {
+                        $sReturn.='<td class="' . $sPhase . '" style="text-align: center;">X</td>';
+                    } else {
+                        $sReturn.='<td> </td>';
+                    }
+                }
+            }
+            $sReturn.='</tr>';
+        }
+        $sReturn = 'Die nachfolgende Tabelle zeigt die bereits erzeugten Pakete an und wo diese verwendet werden.'
+                . '<table>'
+                . '<thead><tr><td>Version</td>'
+                . $sRowHead1
+                . '</tr><tr><td>'
+                . $sRowHead2
+                . '</tr></thead>'
+                . '<tbody>'
+                . $sReturn
+                . '</tbody>'
+                . '</table>';
+        return $sReturn;
+    }
+
+    /**
+     * return html code for the setup form of an exsiting project
+     * @return string
+     */
+    public function renderProjectSetup() {
+
+        require_once ("formgen.class.php");
+        $i = 0;
+
+        $aPrefixItem = count($this->getVersions()) ?
+                array(
+            'type' => 'markup',
+            'value' => '<fieldset>
+                            <label class="control-label">File-Prefix</label>
+                            <div>
+                                <input id="inputprefix" type="hidden" name="fileprefix" value="' . $this->_aPrjConfig["fileprefix"] . '">
+                                ' . $this->_aPrjConfig["fileprefix"] . '
+                            </div>
+                            </fieldset>
+                            ',
+                ) :
+                array(
+            'type' => 'text',
+            'name' => 'fileprefix',
+            // 'disabled' => 'disabled',
+            'label' => 'File-Prefix <span class="error"><br>Nach dem ersten Build nicht mehr änderbar!</span>',
+            'value' => $this->_aPrjConfig["fileprefix"],
+            'required' => 'required',
+            'validate' => 'isastring',
+            'pattern' => '[a-z0-9\-\_]*',
+            'size' => 100,
+            'placeholder' => '',
+        );
+
+
+        $aForms = array(
+            'setup' => array(
+                'meta' => array(
+                    'method' => 'POST',
+                    'action' => '?',
+                ),
+                'validate' => array(),
+                'form' => array(
+                    'input' . $i++ => array(
+                        'type' => 'hidden',
+                        'name' => 'setupaction',
+                        'value' => 'save',
+                    ),
+                    'input' . $i++ => array(
+                        'type' => 'hidden',
+                        'name' => 'id',
+                        'value' => $this->_aConfig["id"],
+                    ),
+                    'input' . $i++ => array(
+                        'type' => 'markup',
+                        'value' => '<div class="tabbable">
+                            <ul class="nav nav-tabs">
+                                <li class="active"><a href="#tab1" data-toggle="tab">allgemeine Metadaten</a></li>
+                                <li><a href="#tab2" data-toggle="tab">Build</a></li>
+                                <li><a href="#tab3" data-toggle="tab">Phasen</a></li>
+                            </ul>
+                            <div class="tab-content">
+                            <div class="tab-pane active" id="tab1">
+                            
+                            ',
+                    ),
+                    'input' . $i++ => array(
+                        'type' => 'text',
+                        'name' => 'label',
+                        'label' => 'Label des Projekts',
+                        'value' => $this->_aPrjConfig["label"],
+                        'required' => 'required',
+                        'validate' => 'isastring',
+                        'size' => 100,
+                        'placeholder' => 'Projekt',
+                    ),
+                    'input' . $i++ => array(
+                        'type' => 'text',
+                        'name' => 'description',
+                        'label' => 'Kurz-Beschreibung',
+                        'value' => $this->_aPrjConfig["description"],
+                        'required' => 'required',
+                        'validate' => 'isastring',
+                        'size' => 100,
+                        'placeholder' => '',
+                    ),
+                    'input' . $i++ => array(
+                        'type' => 'text',
+                        'name' => 'contact',
+                        'label' => 'Ansprechpartner zum Projekt',
+                        'value' => $this->_aPrjConfig["contact"],
+                        'required' => 'required',
+                        'validate' => 'isastring',
+                        'size' => 100,
+                        'placeholder' => '',
+                    ),
+                    // --------------------------------------------------
+                    'input' . $i++ => array(
+                        'type' => 'markup',
+                        'value' => ' </div><div class="tab-pane" id="tab2">
+                            <p>
+                                Einstellungen zum Erstellen eines neuen Builds.
+                                Es werden Zugriffsdaten auf das Repository benötigt.<br>
+                                Der Fileprefix bestimmt einen Teil des Dateinamens
+                                und wird auch in Puppet konfiguriert. Er darf nach 
+                                eingerichtetem Deployment nicht mehr geändert werden.
+                            </p>
+                            ',
+                    ),
+                    'input' . $i++ => array(
+                        'type' => 'text',
+                        'name' => 'build[type]',
+                        'label' => 'Typ',
+                        'value' => $this->_aPrjConfig["build"]["type"],
+                        'required' => 'required',
+                        'validate' => 'isastring',
+                        'size' => 100,
+                        'placeholder' => '',
+                    ),
+                    'input' . $i++ => array(
+                        'type' => 'text',
+                        'name' => 'build[ssh]',
+                        'label' => 'SSH-URL zum Repository',
+                        'value' => $this->_aPrjConfig["build"]["ssh"],
+                        // 'required' => 'required',
+                        'validate' => 'isastring',
+                        'size' => 100,
+                        'placeholder' => '',
+                    ),
+                    'input' . $i++ => array(
+                        'type' => 'text',
+                        'name' => 'build[keyfile]',
+                        'label' => 'Dateiname zum SSH-Private-Key',
+                        'value' => $this->_aPrjConfig["build"]["keyfile"],
+                        // 'required' => 'required',
+                        'validate' => 'isastring',
+                        'size' => 100,
+                        'placeholder' => '',
+                    ),
+                    'input' . $i++ => array(
+                        'type' => 'text',
+                        'name' => 'build[webaccess]',
+                        'label' => 'URL zur Web GUI des Repositorys',
+                        'value' => $this->_aPrjConfig["build"]["webaccess"],
+                        'validate' => 'isastring',
+                        'size' => 100,
+                        'placeholder' => '',
+                    ),
+                    'input' . $i++ => $aPrefixItem,
+                    // --------------------------------------------------
+                    'input' . $i++ => array(
+                        'type' => 'markup',
+                        'value' => ' </div><div class="tab-pane" id="tab3">
+                            <p>
+                                Gib die URLs der jeweiligen Phasen an. Wird keine URL eingetragen, ist die jeweilige Phase nicht aktiv.<br>
+                                Der Puppet-Host ist optional nur für die erste Phase ' . $this->getNextPhase() . ' anzugeben.
+                                Der Sysadmin muss zudem diesen Host in Puppet konfigurieren.
+                            </p>
+                            ',
+                    ),
+                ),
+            ),
+        );
+        foreach (array_keys($this->getPhases()) as $sPhase) {
+            $aForms["setup"]["form"]['input' . $i++] = array(
+                'type' => 'markup',
+                'value' => 'Phase <span class="' . $sPhase . '">' . $sPhase . '</span>',
+            );
+            $sUrl = array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["url"] : "";
+            $aForms["setup"]["form"]['input' . $i++] = array(
+                'type' => 'text',
+                'name' => 'phases[' . $sPhase . '][url]',
+                'label' => 'URL der Webseite',
+                'value' => $sUrl,
+                // 'required' => 'required',
+                'validate' => 'isastring',
+                'size' => 100,
+                'placeholder' => 'http://[phase].[Projekt].iml.unibe.ch/',
+            );
+            $sPuppethost = array_key_exists("puppethost", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["puppethost"] : "";
+            $aForms["setup"]["form"]['input' . $i++] = array(
+                'type' => 'text',
+                'name' => 'phases[' . $sPhase . '][puppethost]',
+                'label' => 'Hostname für puppet agent',
+                'value' => $sPuppethost,
+                // 'required' => 'required',
+                'validate' => 'isastring',
+                'size' => 100,
+                'placeholder' => '',
+            );
+        }
+        $aForms["setup"]["form"]['input' . $i++] = array(
+            'type' => 'markup',
+            'value' => '</div></div><hr>',
+        );
+        $aForms["setup"]["form"]['input' . $i++] = array(
+            'type' => 'submit',
+            'name' => 'btnsave',
+            'label' => 'Speichern',
+            'value' => 'Speichern',
+        );
+
+        $oForm = new formgen($aForms);
+        return $oForm->renderHtml("setup");
+    }
+
+    /**
+     * return html code for the setup form for a new project
+     * @return string
+     */
+    public function renderNewProject() {
+        global $aParams;
+
+        require_once ("formgen.class.php");
+        $i = 0;
+        $sID = array_key_exists("id", $aParams) ? $aParams["id"] : "";
+
+        $aForms = array(
+            'setup' => array(
+                'meta' => array(
+                    'method' => 'POST',
+                    'action' => '?',
+                ),
+                'validate' => array(),
+                'form' => array(
+                    'input' . $i++ => array(
+                        'type' => 'hidden',
+                        'name' => 'setupaction',
+                        'value' => 'create',
+                    ),
+                    'input' . $i++ => array(
+                        'type' => 'text',
+                        'name' => 'id',
+                        'label' => 'ID des neuen Projekts',
+                        'value' => $sID,
+                        'required' => 'required',
+                        'validate' => 'isastring',
+                        'size' => 100,
+                        'pattern' => '[a-z0-9\-\_]*',
+                        'placeholder' => 'Projekt: Kleinbuchstaben a-z, Ziffern, Minus, Unterstrich',
+                    ),
+                ),
+            ),
+        );
+        $aForms["setup"]["form"]['input' . $i++] = array(
+            'type' => 'submit',
+            'name' => 'btnsave',
+            'label' => 'Speichern',
+            'value' => 'Speichern',
+        );
+
+        $oForm = new formgen($aForms);
+        return $oForm->renderHtml("setup");
+    }
+
+}
+
 ?>
\ No newline at end of file
diff --git a/public_html/deployment/classes/projectlist.class.php b/public_html/deployment/classes/projectlist.class.php
index 774d370d..056cd0ba 100644
--- a/public_html/deployment/classes/projectlist.class.php
+++ b/public_html/deployment/classes/projectlist.class.php
@@ -1,166 +1,169 @@
-<?php
-/* ######################################################################
-
-  IML DEPLOYMENT
-
-  class projectlist to render overview page
-
-  ---------------------------------------------------------------------
-  2013-11-08  Axel <axel.hahn@iml.unibe.ch>
-  ###################################################################### */
-
-require_once 'project.class.php';
-
-/**
- * class for project overview
- */
-class projectlist {
-
-    // ----------------------------------------------------------------------
-    // CONFIG
-    // ----------------------------------------------------------------------
-    // ----------------------------------------------------------------------
-    // constructor
-    // ----------------------------------------------------------------------
-
-    /**
-     * constructor
-     * @param array $aProjects array with all projects (overrides config data)
-     */
-    public function __construct($aProjects = false) {
-    }
-
-    // ----------------------------------------------------------------------
-    // private functions
-    // ----------------------------------------------------------------------
-
-
-    // ----------------------------------------------------------------------
-    // GETTER
-    // ----------------------------------------------------------------------
-
-    // ----------------------------------------------------------------------
-    // SETTER
-    // ----------------------------------------------------------------------
-
-    // ----------------------------------------------------------------------
-    // ACTIONS
-    // ----------------------------------------------------------------------
-
-    /**
-     * render html for overview table
-     * @return string
-     */
-    public function renderOverview() {
-        $sOut = '';
-        $oPrj = false;
-        $sTrClass="trproject";
-        $sColClass="tdphase";
-
-        $oPrj1 = new project();
-                
-        $sPrjFilter='';
-        $sPhaseFilter='';
-        $sPrjFilter.='<option value="'.$sTrClass.'">Alle</option>';
-        $sPhaseFilter.='<option value="'.$sColClass.'">Alle</option>';
-        // foreach (array_keys($this->_aPhases) as $sPhase) {
-        foreach (array_keys($oPrj1->getPhases()) as $sPhase) {
-            $sPhaseFilter.='<option value="'.$sPhase.'">'.$sPhase.'</option>';
-        }
-        foreach ($oPrj1->getProjects() as $sPrj) {
-            $oPrj = new project($sPrj);
-            $sPrjFilter.='<option value="'.$sPrj.'">'.$oPrj->getLabel().'</option>';
-
-            $sOutPhases = '';
-
-            // loop over phases ...
-            foreach (array_keys($oPrj->getPhases()) as $sPhase) {
-                $sOutPhases.=$oPrj->renderAllPhaseDetails($sPhase,true,false);
-            }
-
-            // render output
-            $sOut.='
-                <tr class="'.$sPrj.' '.$sTrClass.'">
-                    <td class="prj">
-                        <strong>
-                            '.$oPrj->renderLink("overview").'
-                        </strong><br>
-                        <!-- <a href="/deployment/'.$sPrj.'/" class="btn "><i class=" icon-book"></i> '.$oPrj->getLabel().'</a>-->
-                        
-                        ' . $oPrj->getDescription() . '</td><td class="prj">';
-            if ($oPrj->canAcceptPhase()){
-                $sOut.=$oPrj->renderLink("build");
-                // $sOut.='<a href="/deployment/'.$sPrj.'/build/" class="btn '.$sNext.'"><i class=" icon-forward"></i> Build f&uuml;r ['.$sNext.']</a><br>';
-            }
-            $sOut.='</td>
-                ' . $sOutPhases . '
-                </tr>';
-        }
-        if ($sOut) {
-            $sRowHead1='';
-            $sRowHead2='';
-            
-            foreach (array_keys($oPrj1->getPhases()) as $sPhase){
-                // Anzahl colspan ist hartcodiert :-/
-                $sRowHead1.='<th class="'.$sPhase.' '.$sColClass.'" colspan="3">'.$sPhase.'</th>';
-                $sRowHead2.=$oPrj->renderPlacesAsTd($sPhase);
-            }            
-            $sOut = '
-                <script>
-                    function filterOverviewTable(){
-                        sPrj=$("#prjfilter").val();
-                        sPhase=$("#phasefilter").val();
-                        $(\'.'.$sTrClass.'\').hide(); $(\'.\' + sPrj).show();
-                        $(\'.'.$sColClass.'\').hide(); $(\'.\' + sPhase).show();
-                    }
-                    
-                    function filterTable(){
-                        sSearch=$("#efilter").val();
-                        $(".trproject").each(function() {
-                            sVisible="none";
-                            if (this.innerHTML.search(sSearch)>=0){
-                                sVisible="";
-                            }
-                            $(this).css("display", sVisible);
-                        });
-
-                    }
-                </script>
-                Projekt-Filter: 
-                <select id="prjfilter" onchange="filterOverviewTable(); return false;">
-                    '.$sPrjFilter.'
-                </select>
-                &nbsp;&nbsp;&nbsp;
-                Phasen: 
-                <select id="phasefilter" onchange="filterOverviewTable(); return false;">
-                    '.$sPhaseFilter.'
-                </select>
-                
-                &nbsp;&nbsp;&nbsp;
-                Freitext:
-                <input type="text" id="efilter" name="efilter" 
-                    onchange="filterTable();"
-                    onKeypress="filterTable(); "
-                    onKeyup="filterTable(); "
-                    >
-         
-		<table class="table" id="tbloverview">
-			<thead>
-				<tr>
-					<th class="prj" rowspan="2">Projekt<br><br></th>
-					<th class="prj" rowspan="2"></th>
-                                        '.$sRowHead1.'
-				</tr>
-                                <tr>
-                                        '.$sRowHead2.'
-                                </tr>
-			</thead>
-			<tbody>
-		' . $sOut . '</tbody></table>';
-        }
-        return $sOut;
-    }
-
-}
-
+<?php
+/* ######################################################################
+
+  IML DEPLOYMENT
+
+  class projectlist to render overview page
+
+  ---------------------------------------------------------------------
+  2013-11-08  Axel <axel.hahn@iml.unibe.ch>
+  ###################################################################### */
+
+require_once 'project.class.php';
+
+/**
+ * class for project overview
+ */
+class projectlist {
+
+    // ----------------------------------------------------------------------
+    // CONFIG
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // constructor
+    // ----------------------------------------------------------------------
+
+    /**
+     * constructor
+     * @param array $aProjects array with all projects (overrides config data)
+     */
+    public function __construct($aProjects = false) {
+    }
+
+    // ----------------------------------------------------------------------
+    // private functions
+    // ----------------------------------------------------------------------
+
+
+    // ----------------------------------------------------------------------
+    // GETTER
+    // ----------------------------------------------------------------------
+
+    // ----------------------------------------------------------------------
+    // SETTER
+    // ----------------------------------------------------------------------
+
+    // ----------------------------------------------------------------------
+    // ACTIONS
+    // ----------------------------------------------------------------------
+
+    /**
+     * render html for overview table
+     * @return string
+     */
+    public function renderOverview() {
+        $sOut = '';
+        $oPrj = false;
+        $sTrClass="trproject";
+        $sColClass="tdphase";
+
+        $oPrj1 = new project();
+                
+        $sPrjFilter='';
+        $sPhaseFilter='';
+        $sPrjFilter.='<option value="'.$sTrClass.'">Alle</option>';
+        $sPhaseFilter.='<option value="'.$sColClass.'">Alle</option>';
+        // foreach (array_keys($this->_aPhases) as $sPhase) {
+        foreach (array_keys($oPrj1->getPhases()) as $sPhase) {
+            $sPhaseFilter.='<option value="'.$sPhase.'">'.$sPhase.'</option>';
+        }
+        foreach ($oPrj1->getProjects() as $sPrj) {
+            $oPrj = new project($sPrj);
+            $sPrjFilter.='<option value="'.$sPrj.'">'.$oPrj->getLabel().'</option>';
+
+            $sOutPhases = '';
+
+            // loop over phases ...
+            foreach (array_keys($oPrj->getPhases()) as $sPhase) {
+                $sOutPhases.=$oPrj->renderAllPhaseDetails($sPhase,true,false);
+            }
+
+            // render output
+            $sOut.='
+                <tr class="'.$sPrj.' '.$sTrClass.'">
+                    <td class="prj">
+                        <strong>
+                            '.$oPrj->renderLink("overview").'
+                        </strong><br>
+                        <!-- <a href="/deployment/'.$sPrj.'/" class="btn "><i class=" icon-book"></i> '.$oPrj->getLabel().'</a>-->
+                        
+                        ' . $oPrj->getDescription() . '</td><td class="prj">';
+            if ($oPrj->canAcceptPhase()){
+                $sOut.=$oPrj->renderLink("build");
+                // $sOut.='<a href="/deployment/'.$sPrj.'/build/" class="btn '.$sNext.'"><i class=" icon-forward"></i> Build f&uuml;r ['.$sNext.']</a><br>';
+            }
+            $sOut.='</td>
+                ' . $sOutPhases . '
+                </tr>';
+        }
+        if ($sOut) {
+            $sRowHead1='';
+            $sRowHead2='';
+            
+            foreach (array_keys($oPrj1->getPhases()) as $sPhase){
+                // Anzahl colspan ist hartcodiert :-/
+                $sRowHead1.='<th class="'.$sPhase.' '.$sColClass.'" colspan="3">'.$sPhase.'</th>';
+                $sRowHead2.=$oPrj->renderPlacesAsTd($sPhase);
+            }            
+            $sOut = '
+                <script>
+                
+                    function filterOverviewTable(){
+                        var sPrj=$("#prjfilter").val();
+                        var sPhase=$("#phasefilter").val();
+                        $(\'.'.$sTrClass.'\').hide(); $(\'.\' + sPrj).show();
+                        $(\'.'.$sColClass.'\').hide(); $(\'.\' + sPhase).show();
+                    }
+                    
+                    function filterTable(){
+                        var sSearch=$("#efilter").val();
+                        var Regex = new RegExp(sSearch, "i");
+                        $(".trproject").each(function() {
+                            sVisible="none";
+                            if ( Regex.exec(this.innerHTML)) {
+                                sVisible="";
+                            }
+                            $(this).css("display", sVisible);
+                        });
+
+                    }
+                </script>
+                Freitext-Filter (Regex):
+                <input type="text" id="efilter" name="efilter"
+                    style="width: 150px;"
+                    onchange="filterTable();"
+                    onKeypress="filterTable(); "
+                    onKeyup="filterTable(); "
+                    >
+                &nbsp;&nbsp;&nbsp;
+                Projekt-Filter: 
+                <select id="prjfilter" onchange="filterOverviewTable(); return false;">
+                    '.$sPrjFilter.'
+                </select>
+                &nbsp;&nbsp;&nbsp;
+                Phasen: 
+                <select id="phasefilter" onchange="filterOverviewTable(); return false;">
+                    '.$sPhaseFilter.'
+                </select>
+                
+         
+		<table class="table" id="tbloverview">
+			<thead>
+				<tr>
+					<th class="prj" rowspan="2">Projekt<br><br></th>
+					<th class="prj" rowspan="2"></th>
+                                        '.$sRowHead1.'
+				</tr>
+                                <tr>
+                                        '.$sRowHead2.'
+                                </tr>
+			</thead>
+			<tbody>
+		' . $sOut . '</tbody></table>';
+        }
+        return $sOut;
+    }
+
+}
+
 ?>
\ No newline at end of file
-- 
GitLab