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ä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ä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 "type" 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 "type" 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: "" . $this->_aConfig['packageDir'] . ""."); - - if (!array_key_exists("archiveDir", $this->_aConfig)) - die("ERROR::CONFIG: key "archiveDir" 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: "" . $this->_aConfig['archiveDir'] . ""."); - - foreach (array("fileprefix", "build", "phases") as $sKey) { - if (!array_key_exists($sKey, $this->_aPrjConfig)) - die("ERROR::CONFIG: key "$sKey" 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('"', """, $sCommitMsg); - // $sCommitMsg = str_replace('<', "<", $sCommitMsg); - // $sCommitMsg = str_replace('>', ">", $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 "' . $sTempDir . '"." . $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 "' . $sTempDir . '"." . $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 "public_html" or "public" 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 "$sTempDir"" . $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 "' . $sTempDir . '".' . $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 "$sRegex" ... "; - 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: "" . $this->_aConfig['packageDir'] . ""."); + + if (!array_key_exists("archiveDir", $this->_aConfig)) + die("ERROR::CONFIG: key "archiveDir" 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: "" . $this->_aConfig['archiveDir'] . ""."); + + foreach (array("fileprefix", "build", "phases") as $sKey) { + if (!array_key_exists($sKey, $this->_aPrjConfig)) + die("ERROR::CONFIG: key "$sKey" 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('"', """, $sCommitMsg); + // $sCommitMsg = str_replace('<', "<", $sCommitMsg); + // $sCommitMsg = str_replace('>', ">", $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 "' . $sTempDir . '"." . $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 "' . $sTempDir . '"." . $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 "public_html" or "public" 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 "$sTempDir"" . $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 "' . $sTempDir . '".' . $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 "$sRegex" ... "; + 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) . "»"; + } + $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ü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> - - Phasen: - <select id="phasefilter" onchange="filterOverviewTable(); return false;"> - '.$sPhaseFilter.' - </select> - - - 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ü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(); " + > + + Projekt-Filter: + <select id="prjfilter" onchange="filterOverviewTable(); return false;"> + '.$sPrjFilter.' + </select> + + 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