<?php

require_once 'project.class.php';
require_once 'htmlguielements.class.php';


/* ######################################################################

  IML DEPLOYMENT

  class project for all actions for single project
  Rendering of web ui

  ---------------------------------------------------------------------
  2013-11-08  Axel <axel.hahn@iml.unibe.ch>
  ###################################################################### */

/**
 * class for single project
 */
// class project {
class projectgui extends project {

    /**
     * constructor
     * @param string $sId  id of the project
    public function __construct($sId = false) {
        parent::__construct($sId);
        $this->_oHtml = new htmlguielements();
    }
    */

    // ----------------------------------------------------------------------
    // private functions
    // ----------------------------------------------------------------------

    /**
     * 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; if false ist returns a gray
     * @param string $sContent   optional: text to show
     * @return string
     */
    private function _getChecksumDiv($sText, $sContent='', $sBarHeight='3px') {
        if ($sText){
            
            // color ranges in decimal values for RGB from ... to
            $iFgStart=60;  $iFgEnd=160;
            $iBgStart=200; $iBgEnd=250;

            $iFgStart=60;  $iFgEnd=160;
            $iBgStart=190; $iBgEnd=250;

            // deivider: 3 digits of md5 will be extracted
            $iFgDivider=16*16*16/($iFgEnd-$iFgStart);
            $iBgDivider=16*16*16/($iBgEnd-$iBgStart);
            
            $sHash=md5($sText);
            $sColor=''
                . 'color: rgba(' 
                . ($iFgStart + round(hexdec(substr($sHash,0,3))/$iFgDivider)) . ','
                . ($iFgStart + round(hexdec(substr($sHash,3,3))/$iFgDivider)) . ','
                . ($iFgStart + round(hexdec(substr($sHash,6,3))/$iFgDivider)) . ','
                . '1'
                . ');'
                . 'background: rgba(' 
                . ($iBgStart + round(hexdec(substr($sHash,0,3))/$iBgDivider)) . ','
                . ($iBgStart + round(hexdec(substr($sHash,3,3))/$iBgDivider)) . ','
                . ($iBgStart + round(hexdec(substr($sHash,6,3))/$iBgDivider)) . ','
                . '1'
                . ');'
                ;
        } else {
            $sColor = "color: #888; background: #ccc;";
        }
        return '<div style="' . $sColor . ' border-top: '.$sBarHeight.' solid;">'.($sContent ? $sContent : ' ').'</div>';
    }


    /**
     * get html code for the colored bar on top of each phase detail items
     * @param string $sPhase  phase of a project
     * @param string $sPlace  place in the given phase
     * @return string
     */
    private function _renderBar($sPhase, $sPlace, $sBarHeight='3px') {
        $aDataPhase = $this->getPhaseInfos($sPhase);
        $aData = $aDataPhase[$sPlace];
        if (!array_key_exists("revision", $aData)) {
            return false;
        }
        return $this->_getChecksumDiv($aData["revision"], '', $sBarHeight);
    }

    private function _renderHostsData($aData) {
        $sReturn = '';
        if (array_key_exists('_hosts', $aData)) {
            
            // $sReturn.= print_r($aData['_hosts'], 1);
            $sReturn.= '<div class="hosts">'
                    . '<br><strong>' . t('hosts') . ':</strong><br>'
            ;
            foreach ($aData['_hosts'] as $sHostname => $aHostinfos) {
                $oUpdateDate = date("U", strtotime($aHostinfos['time']));
                $iAgeUpdate = round((date("U") - $oUpdateDate) / 60);
                $sAge = $iAgeUpdate < 60 * 60 * 13 ? $iAgeUpdate . " min" : "??";

                $sReturn.= '<div class="host">'
                        . $this->_getChecksumDiv(
                            $aHostinfos['_data']['revision'],
                            $this->_oHtml->getIcon('host').'<br>' . $sHostname
                        )
                        . "($sAge)"
                        . '</div>'
                ;
            }
            $sReturn.= '</div><div style="clear: both;"></div>';
        }
        return $sReturn;
    }

    /**
     * get html code for list of hosts in a phase
     * @param string $sPhase  phase of a project
     * @return string
     */
    private function _renderHosts($sPhase) {
        $aDataPhase = $this->getPhaseInfos($sPhase);
        if (is_array($aDataPhase) && array_key_exists('deployed', $aDataPhase)) {
            return $this->_renderHostsData($aDataPhase['deployed']);
        }
        return '';
    }

    /**
     * get html code for list of files in a phase
     * @param string $sPhase  phase of a project
     * @return string
     */
    private function _renderFiles($sPhase) {
        $sReturn = '';
        $aFiles = $this->getBuildfilesByPlace($sPhase, 'ready2install');
        if (!$aFiles || !$aFiles['filecount']) {
            return '';
        }
        $sReturn.='<strong>' . t("filelist") . '</strong> (' . $aFiles['filecount'] . '):<br>';
        foreach ($aFiles['files'] as $sFilename => $aData) {
            $sReturn.='<div class="file file-' . $aData['type'] . ' fileext-' . $aData['extension'] . '" title="' . $sFilename . ' (' . $aData['type'] . ')">'
                    . $aData['icon'] . $sFilename
                    // . ' ('.$aData['type'].')'
                    . '</div>'
            ;
        }
        $sReturn.='(' . $aFiles['totalsize-hr'] . ')';
        return $sReturn;
    }

    // ----------------------------------------------------------------------
    // RENDERING
    // ----------------------------------------------------------------------


    /**
     * 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="td-phase-' . $sPhase . ' td-phase-inactive ' . $this->_aConfig["id"] . '" colspan="' . count($this->_aPlaces) . '">
                            <div class="versioninfo center inactive">' . $this->_oHtml->getIcon('sign-info').t('inactive') . '</div>
                        </td>';
        }
        $sRow2 = false;

        $aRows = array();
        $sLastPlace = '';
        
        foreach (array_keys($this->_aPlaces) as $sPlace) {
            $aRows[$sPlace] = $this->renderPhaseDetail($sPhase, $sPlace, $bActions, $bLong);
            
            // generate ">>" sign for lastly generated td
            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"] 
                    && !$bLong
            ) {
                $aRows[$sLastPlace] = $this->_renderBar($sPhase, $sPlace) . "&raquo;";
            }
            $sLastPlace = $sPlace;
        }
        
        foreach (array_keys($this->_aPlaces) as $sPlace) {
            $sRow2.='<td class=" td-phase-'.$sPhase.' td-place-'.$sPlace.' td' . $this->_aConfig["id"] . '">' . $aRows[$sPlace] . '</td>';
        }
        return $sRow2;
    }

    /**
     * return html code for current project errors by rendering a box per error in $this->_errors
     * @return string
     */
    public function renderErrorBoxes(){
        $sReturn='';
        if(count($this->_errors)){
            foreach($this->_errors as $sError){
                $sReturn.=$this->_oHtml->getBox("error", $sError);
            }
        }
        return $sReturn;
    }


    /**
     * fix output of commit message as html
     * This is a compatibility function for older builds
     * 
     * @param  string  $sMessage  git commit message
     * @return string
     */
    public function transformCommitMessage($sMessage){
        if(strstr($sMessage, '<br>Date:')){
            $_aReplace=[
                '<br>Author:' => "\nAuthor:",
                '<br>Date:' => "\nDate:",
                '<br><br>' => "\n\n",
            ];
            $sMessage=str_replace(array_keys($_aReplace), array_values($_aReplace), $sMessage)." *";
        }
        return htmlentities($sMessage);
    }
    /**
     * render html code for info link that shows popup with metadata on mouseover
     * @param array $aInfos   metainfos of the package (from json file)
     *                   one of ok=1|error=message - status key
     *                   date      - timestamp of build
     *                   revision  - revision
     *                   branch    - branch
     *                   message   - commit message
     * @param array $aOptions options
     *                   title - tile in popover; default: empty
     *                   label - link label; default: empty (results in Infos|ERROR)
     *                   more  - additional infos in popover; default: empty
     *                   hpos  - horizontal position; one of left|right; default: right
     * @return string
     */
    public function renderInfoLink($aInfos, $aOptions = array()) {
        $sReturn = '';
        $bIsError = false;
        $this->_oHtml = new htmlguielements();

        $sInfos='';
        $sTitle='';
        if (array_key_exists("title", $aOptions) && $aOptions["title"]) {
            $sTitle.=$aOptions["title"];
        }
        if (array_key_exists("ok", $aInfos)) {
            $sLinktitle = t('infos');
            if (array_key_exists("message", $aInfos)) {
                $sInfos.=$this->_getChecksumDiv($aInfos["revision"],
                        $this->_oHtml->getIconByType('calendar') . t('build-from') . ' ' . date("d.m.Y H:i:s", strtotime($aInfos["date"])) . '<br>'
                        . $this->_oHtml->getIconByType('branch') . t('branch') . ': ' . $aInfos["branch"] . '<br>'
                        . $this->_oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aInfos["revision"]) . '<br>'
                        . $this->_oHtml->getIconByType('comment') . t('commitmessage') . ': '
                        )
                        . '<pre>' . $this->transformCommitMessage($aInfos["message"]) . '</pre>';
                if (array_key_exists("more", $aOptions)) {
                    $sInfos.=$aOptions["more"];
                }
            }
        } else {
            $bIsError = true;
            if (!$sTitle) {
                $sTitle.=' ' . t('error');
            }
            $sLinktitle = t('error');
            $sInfos = $this->_oHtml->getBox('error', '') . '<p>'.$aInfos["error"].'</p>';
        }
        $sInfos.=$this->_renderHostsData($aInfos, '');

        if (array_key_exists("label", $aOptions) && $aOptions["label"]) {
            $sLinktitle.=$aOptions["label"];
        }

        // render html
        $sId = 'info' . md5($sInfos);
        $sReturn = '<a href="#" class="btn ' . ($bIsError ? 'btn-danger' : 'btn-default') . '" title="" 
                onclick="showIdAsModalMessage(\'' . $sId . '\', \''.$sTitle.'\'); return false;"
                >'
                . $this->_oHtml->getIcon($bIsError ? 'sign-error' : 'sign-info') // ... '<i class="fa fa-info"></i> '
                . $sLinktitle
                . '</a><div id="' . $sId . '" style="display: none;" '
                ;
        if (array_key_exists("hpos", $aOptions)) {
            $sReturn.=' class="' . $aOptions["hpos"] . '"';
        }
        $sReturn.='>';

        if ($sTitle) {
            // $sReturn.='<span class="title">' . $sTitle . '</span><br><br>';
        }

        $sReturn.=$sInfos . '</div>';

        if ($bIsError) {
            // $sReturn = '<div class="error">' . $sReturn . '</div>';
        }

        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|new|overview|phase|rollback|setup
     * @param string $sPhase    current phase where to place the link
     * @return string
     */
    public function renderLink($sFunction, $sPhase = false, $sVersion = false) {
        $sFirst = $this->getNextPhase();
        $sNext = $this->getNextPhase($sPhase);
        $aLinkdata = array(
            'default' => array('class' => ''),
            'accept' => array('class' => $sNext,
                'hint' => sprintf(t("accept-hint"), $sPhase, $sNext),
                'label' => t('accept'),
            ),
            'build' => array('class' => $sFirst,
                'hint' => sprintf(t("build-hint"), $sFirst),
                'label' => t('build'),
                'role' => 'buildProject'
            ),
            'cleanup' => array('class' => ''),
            'deploy' => array('class' => $sPhase,
                'hint' => sprintf(t("deploy-hint"), $sPhase),
                'label' => t('deploy'),
            ),
            'new' => array(
                'hint' => t("new-project-hint"),
                'label' => t('new-project'),
            ),
            'overview' => array('class' => '',
                'hint' => t('menu-project-home') . ' [' . $this->getLabel() . ']',
                'label' => $this->getLabel()
            ),
            'phase' => array('icon' => $this->_oHtml->getIcon('phase'), 'class' => $sPhase,
                'hint' => sprintf(t('phase-details-hint'), $sPhase),
                'label' => sprintf(t('phase-details'), $sPhase),
            ),
            'rollback' => array('class' => $sPhase,
                'hint' => sprintf(t('rollback-hint'), $sPhase, $sVersion),
                'label' => t('rollback')
            ),
            'setup' => array('class' => $sPhase,
                'hint' => sprintf(t('setup-hint'), $sPhase, $sVersion),
                'label' => t('setup'),
                'class' => 'btn'
            ),
        );
        /*
          if (!$this->oUser->hasRole("project-action-$sFunction")){
          // $sClass .= ' disabled';
          // return '<span title="no permission [project-action-'.$sFunction.']">[ ]</span>';
          }
         * 
         */
        // fuer wen ist der Link
        $sRole = '';
        $sOnMouseover = '';
        $sOnMouseout = '';
        switch($sFunction){
            case 'accept';
                $sRole = 'developer';
                if ($sNext == "live") {
                    $sRole = 'pl';
                    // $aLinkdata[$sFunction]['icon']='glyphicon glyphicon-star';
                }
                $sOnMouseover = '$(\'.td-phase-' . $sNext . '.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');';
                $sOnMouseout = '$(\'.td-phase-' . $sNext . '.td' . $this->_aConfig["id"] . '\').removeClass(\'highlight\');';
                break;
            case 'build';
                $sRole = 'developer';
                $sOnMouseover = '$(\'.td-phase-' . $sNext . '.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');';
                $sOnMouseout = '$(\'.td-phase-' . $sNext . '.td' . $this->_aConfig["id"] . '\').removeClass(\'highlight\');';
                break;
            case 'deploy';
                $sRole = 'developer';
                $sOnMouseover = '$(\'.td-phase-' . $sPhase . '.td-place-ready2install.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');'
                        .'$(\'.td-phase-' . $sPhase . '.td-place-deployed.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');'
                        ;
                $sOnMouseout = '$(\'.td-phase-' . $sPhase . '.td-place-ready2install.td' . $this->_aConfig["id"] . '\').removeClass(\'highlight\');'
                        .'$(\'.td-phase-' . $sPhase . '.td-place-deployed.td' . $this->_aConfig["id"] . '\').removeClass(\'highlight\');'
                        ;
                break;
        }

        // $sClass = $sPhase;
        $sIconClass = (array_key_exists($sFunction, $aLinkdata)) ? $aLinkdata[$sFunction]['icon'] : $aLinkdata['default']['icon'];
        $sHint = isset($aLinkdata[$sFunction]['hint']) ? $aLinkdata[$sFunction]['hint'] : "";
        $sLabel = isset($aLinkdata[$sFunction]['label']) ? $aLinkdata[$sFunction]['label'] : $sFunction;
        $sClass = isset($aLinkdata[$sFunction]['class']) ? $aLinkdata[$sFunction]['class'] : '';
        if ($sRole) {
            $sClass .= " role role" . $sRole;
        }

        $sLink = "/deployment/" . ($this->_aConfig["id"] ? $this->_aConfig["id"] : 'all/setup') . "/";
        if ($sFunction != "overview") {
            $sLink.="$sFunction/";
        }
        if ($sPhase) {
            $sLink.="$sPhase/";
        }
        if ($sVersion) {
            $sLink.="$sVersion/";
        }
        if (!$this->oUser->hasPermission("project-action-$sFunction")) {
            // $sClass .= ' disabled';
            return '<span class="btn disabled btn-default" title="no permission [project-action-' . $sFunction . '] for user [' . $this->oUser->getUsername() . ']"><i class="' . $sIconClass . '"></i> ' . $sLabel . '</span>';
        }

        // $sClass='btn ' . (strstr('btn-', $sClass) ? '': 'btn-default ') .$sClass;
        return $this->_oHtml->getLinkButton(array(
                    'href' => $sLink,
                    'title' => $sHint,
                    'class' => $sClass,
                    'type' => $sFunction,
                    'onmouseover' => $sOnMouseover,
                    'onmouseout' => $sOnMouseout,
                    'label' => $sLabel,
        ));
        // return '<a href="' . $sLink . '" ' . $sOnMouseover . ' title="' . $sHint . '" class="btn  btn-default ' . $sClass . '"><i class="' . $sIconClass . '"></i> ' . $sLabel . '</a>';
    }

    /**
     * return html code for the setup form for a new project
     * @return string
     */
    public function renderNewProject() {
        global $aParams;
        if (!$this->oUser->hasPermission("project-action-create")) {
            return $this->oUser->showDenied();
        }

        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' => t("class-project-info-setup-projectId"),
                        'value' => $sID,
                        'required' => 'required',
                        'validate' => 'isastring',
                        'size' => 100,
                        'pattern' => '[a-z0-9\-_]*',
                        'placeholder' => t("class-project-info-setup-projectId-placeholder"),
                    ),
                ),
            ),
        );
        $aForms["setup"]["form"]['input' . $i++] = array(
            'type' => 'submit',
            'name' => 'btnsave',
            'label' => t("save"),
            'value' => $this->_oHtml->getIcon('sign-ok') . t("save"),
        );

        $oForm = new formgen($aForms);
        return $oForm->renderHtml("setup");
    }
    /**
     * render html for a place of a phase
     * @param string  $sPhase    phase
     * @param string  $sPlace    name of the place; one of onhold|ready2install|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 = '';
        $aDataPhase = $this->getPhaseInfos($sPhase);
        $aData = $aDataPhase[$sPlace];
        // foreach($aDataPhase[$sPlace] as $aData) {
        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
            // time
            $sDateFormat = "d.m.Y H:i";
            $oPkgDate = date("U", strtotime($aData["date"]));
            /*
              $iAge=date("U")-$oPkgDate;
              $sAgeClass="";
              if ($iAge< 60*60*24*3){
              $sAgeClass="last1d";
              }
              if ($iAge< 60*60){
              $sAgeClass="last1h";
              }
             */

            if ($bLong) {
                // long display of the revision
                // $sJsonUrl = $this->_getInfofile($sPhase, $sPlace);
                    $sReturn .=$this->_getChecksumDiv(
                        $aData["revision"], 
                        $this->_oHtml->getIconByType('calendar') .' ' . date($sDateFormat, $oPkgDate) . '<br>'
                        . $this->_oHtml->getIconByType('branch') . t('branch') . ': ' . $aData["branch"] . '<br>'
                        . $this->_oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aData["revision"]) . '<br>'
                        . $this->_oHtml->getIconByType('comment') . t('commitmessage') . ':<br>'
                    )
                    . '<pre>' . $this->transformCommitMessage($aData["message"]) . '</pre>'
                // . '<i class="glyphicon glyphicon-globe"></i> ' . t('url') . ': <a href="' . $sJsonUrl . '">' . $sJsonUrl . '</a><br>'
                ;
                if ($sPlace == "deployed" && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) {
                    $sUrl = $this->_aPrjConfig["phases"][$sPhase]["url"];
                    $sReturn.=$this->_oHtml->getIconByType('link-extern') . ' '. t('url') . ': <a href="' . $sUrl . '">' . $sUrl . '</a><br>';
                }
            } else {
                $sReturn .= $this->_getChecksumDiv(
                            $aData["revision"], 
                            $this->_oHtml->getIconByType('calendar') .' ' . date($sDateFormat, $oPkgDate)
                    );
                if ($sPlace == "deployed" && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) {
                    $sMore = $this->_oHtml->getIconByType('link-extern').' '
                            . t('url')
                            . ': <a href="' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '">' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '</a><br>';
                }

                $sReturn.=' ' . $this->renderInfoLink(
                                $aData, 
                                [
                                    'title' => $this->getLabel() . " :: $sPhase :: $sPlace",
                                    'more' => $sMore,
                                ]
                );
            }

            switch ($sPlace) {
                case "onhold":
                    if (array_key_exists("phases", $this->_aConfig) && array_key_exists($sPhase, $this->_aConfig["phases"])) {
                        // $sReturn .= print_r($this->_aConfig["phases"][$sPhase], true);
                        if (count($this->_getDeploytimes($sPhase))) {
                            $sReturn .= '<br>'.$this->_oHtml->getIcon('time').t('deploytimes') . ':<br>'
                                    . implode("<br>", array_values($this->_getDeploytimes($sPhase)))
                                    . '<br>';
                        }
                        if ($bActions) {
                            $sReturn .= ' ' . $this->renderLink("deploy", $sPhase);
                        }
                    }
                    break;

                case "ready2install":
                    /*
                    // IDEA: try to install the same phase again. Needs update of method project->deploy() 
                    if ($bActions) {
                        $sReturn .= ' ' . $this->renderLink("deploy", $sPhase);
                    }
                    */
                    break;

                case "deployed":
                    if ($bActions && $this->canAcceptPhase($sPhase)) {
                        $sReturn .= ' ' . $this->renderLink("accept", $sPhase);
                    }
                    break;
                default:
                    break;
            }
            // $this->_getChecksumDiv($aData["revision"])
        } else {
            if (array_key_exists("error", $aData)) {
                $sReturn.=''
                        . $this->renderInfoLink(array('error' => $aData["error"]), array())
                ;
            } else if (array_key_exists("warning", $aData)) {
                $sReturn.= '<div class="warning">'.$this->_oHtml->getIcon('sign-info'). t('warning') . ':<br>' . $aData["warning"] . '</div>';
            } else {
                
                // OK = 1 ... for the queue we show no hint
                return '';
                /*
                return $sPlace=='onhold' 
                    ? t('class-project-queue-empty')
                    : ''
                    ;
                */
            }
        } // if
        // } // for
        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;

        $renderAdminLTE=new renderadminlte();                    

        $iWidth=min(12 / count($this->getActivePhases()), 4);
        foreach ($this->getActivePhases() as $sPhase) {
            $sRow1.=$renderAdminLTE->addCol(
                '<table class="nomargin"><tr><th class="' . $sPhase . ' tdphase">' . $sPhase . '</th></tr></table>'
                ,
                $iWidth);
              
            $sDetails=t('url') . ': <a href="' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '">' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '</a><br>'
            . '<br>' . t('deploytimes') . ':<br>';
            if (count($this->_getDeploytimes($sPhase))) {
                $sDetails.=implode("<br>", $this->_getDeploytimes($sPhase));
            } else {
                $sDetails.=t('deploytimes-immediately');
            }
            $sDetails.='<br>' . $this->renderLink("phase", $sPhase)
                    . $this->_renderHosts($sPhase)
                    . '<br>'
                    . $this->_renderFiles($sPhase)
                    ;

            $sRow2.=$renderAdminLTE->addCol(
                $renderAdminLTE->getCard(array (
                    'class' => $sPhase,
                    'variant' => '',
                    'tb-remove' => 1,
                    'tb-collapse' => 1,
                    'title' => '',
                    'tools' => '',
                    'text' => $sDetails,
                    'footer' => '',
                )), 
                $iWidth
            );
        }
        return ''
            .$renderAdminLTE->addRow($sRow1)
            .$renderAdminLTE->addRow($sRow2)
            ;

    }

    /**
     * render html for a row with td for all places (first row)
     * @param string $sPhase  phase (just needed for coloring)
     * @return string
     */
    public function renderPlacesAsTd($sPhase) {
        $sRow1 = '';
        foreach (array_keys($this->_aPlaces) as $sPlace) {
            $sRow1.='<td class="' . $sPhase . ' ' . $this->_aConfig["id"] . ' tdphase">' . t($sPlace) . '</td>';
        }
        return $sRow1;
    }

    /**
     * return html code for the setup form of an exsiting project
     * @return string
     */
    public function renderProjectSetup() {
        if (!$this->oUser->hasPermission("project-action-setup")) {
            return $this->oUser->showDenied();
        }
        $sMessages = '';
        require_once ("formgen.class.php");


            $aSelectProjectGroup = array(
                'type' => 'select',
                'name' => 'projectgroup',
                'label' => t("projectgroup"),
                'options' => array(
                    OPTION_NONE => array(
                        'label' => t('none'),
                    ),
                    '' => array(
                        'label' => '- - - - - - - - - - - - - - - - - - - - ',
                    ),
                ),
            );
            foreach($this->_aConfig['projectgroups'] as $sGroupid=>$sGroupLabel){
                $bActive=$this->getProjectGroup() === $sGroupid;
                $aSelectProjectGroup['options'][$sGroupid] = array(
                    'label' => $sGroupLabel,
                    'selected' => $bActive ? 'selected' : false,
                );
            }
            
        $aSelectSlack = array(
                'type' => 'hidden',
                'name' => 'messenger[slack]',
                'value' => false,
        );
        if (
                isset($this->_aConfig['messenger']['slack']['presets'])
                && count($this->_aConfig['messenger']['slack']['presets'])
        ) {
            $aSelectSlack = array(
                'type' => 'select',
                'name' => 'messenger[slack]',
                'label' => t("messenger-slack"),
                'options' => array(
                    OPTION_NONE => array(
                        'label' => t('none'),
                    ),
                    '' => array(
                        'label' => '- - - - - - - - - - - - - - - - - - - - ',
                    ),
                ),
            );
            foreach($this->_aConfig['messenger']['slack']['presets'] as $sSlackUrl=>$aSlackCfg){
                $bActive=$this->_aPrjConfig['messenger']['slack'] === $sSlackUrl;
                $aSelectSlack['options'][$sSlackUrl] = array(
                    'label' => array_key_exists('label', $aSlackCfg) ? $aSlackCfg['label'] : $sSlackUrl,
                    'selected' => $bActive ? 'selected' : false,
                );
            }
            
        }
        // ---------- Build plugins
        /*
        
        $aPluginsBuild = array(
            'select' => array(
                'type' => 'checkbox',
                'name' => 'build[enabled_build_plugins]',
                'label' => t("build-plugins"),
                'options' => [],
            ),
            // 'project-config' => '',
        );
        foreach (array_keys($this->getConfiguredPlugins('build')) as $sPluginName){

            $sPluginFile=$this->_getPluginFilename('build', $sPluginName);
            $TmpRolloutPlugin = false;
            $sMyClassname='build_'. $sPluginName;
            if(file_exists($sPluginFile)){
            try{
                include_once $this->_getPluginFilename('build', $sPluginName);
                $TmpRolloutPlugin = new $sMyClassname([]);
                echo "FOUND $sMyClassname<br>";
                $aPluginsBuild['select']['options'][$sPluginName]=array(
                        'label' => $TmpRolloutPlugin->getName(),
                        'checked' => $bActive,
                        // 'onclick' => '$(\'.'.$sMyDivClass.'\').hide(); $(\'.' . $sMyDivClassActive . '\').show();',
                    );
                } catch (Exception $ex) {

                }
            } else {
                $aRollout['project-select']['options'][$sPluginName]=array(
                        'label' => 'not found: <span class="error">' . $sMyClassname . '</span>',
                        'checked' => false,
                        'disabled' => "disabled",
                    );

                
            }
        }
        echo '<pre>'; print_r($aPluginsBuild); die(__METHOD__);
        */

        // ---------- /Build plugins
        
        // ---------- Rollout plugins
        $aRollout = array(
            'project-select' => array(
                'type' => 'radio',
                'name' => 'deploy[enabled_rollout_plugin]',
                'label' => t("deploy-rollout-plugin"),
            ),
            'project-config' => '',
        );
        foreach (array_keys($this->getConfiguredPlugins('rollout')) as $sPluginName){

            $sPluginFile=$this->_getPluginFilename('rollout', $sPluginName);
            $TmpRolloutPlugin = false;
            $sMyClassname='rollout_'. $sPluginName;
            $sMyDivId='rollout-'. $sPluginName.'-config';
            $sMyDivClass='rolloutconfigdiv';
            $sMyDivClassActive='rolloutconfigdiv-'. $sPluginName;
            $bActive=$sPluginName === $this->oRolloutPlugin->getId();

            if(file_exists($sPluginFile)){
                try{
                    include_once $this->_getPluginFilename('rollout', $sPluginName);
                    $TmpRolloutPlugin = new $sMyClassname(array(
                        'lang'=>$this->_aConfig['lang'],
                        'phase'=>false,
                        'globalcfg'=>$this->_aConfig['plugins']['rollout'][$sPluginName],
                        'projectcfg'=>$this->_aPrjConfig,
                    ));
                    $aRollout['project-select']['options'][$sPluginName]=array(
                            'label' => $TmpRolloutPlugin->getName(),
                            'checked' => $bActive,
                            'onclick' => '$(\'.'.$sMyDivClass.'\').hide(); $(\'.' . $sMyDivClassActive . '\').show();',
                        );
                    
                    $aRollout['project-config'].=''
                            . '<div id="'.$sMyDivId.'" class="'.$sMyDivClass.' '.$sMyDivClassActive.'"'
                            . ($bActive ? '' : ' style="display: none;"' )
                            . '>'
                                . $TmpRolloutPlugin->renderFormdata4Project()
                            . '</div>'
                            ;
                    
                    // generate form firlds for each phase
                    foreach(array_keys($this->getPhases()) as $sMyPhase){
                        $aRollout[$sMyPhase].=''
                            . '<div id="'.$sMyDivId.'-'.$sMyPhase.'" class="'.$sMyDivClass.' '.$sMyDivClassActive.'"'
                            . ($bActive ? '' : ' style="display: none;"' )
                            . '>'
                                . $TmpRolloutPlugin->renderFormdata4Phase($sMyPhase)
                            . '</div>'
                            ;
                    }
                } catch (Exception $ex) {

                }
            } else {
                $aRollout['project-select']['options'][$sPluginName]=array(
                        'label' => 'not found: <span class="error">' . $sMyClassname . '</span>',
                        'checked' => false,
                        'disabled' => "disabled",
                    );

                
            }
        }
        // ---------- /Rollout plugins
        
        $aForemanHostgroups = false;
        $iForemanHostgroupDefault = false;
        $sForemanHostgroupDefault = false;
        if (array_key_exists('foreman', $this->_aConfig)) {
            // echo '<pre>' . print_r($this->_aPrjConfig, 1) . '</pre>';
            $iForemanHostgroupDefault = (int) $this->_aPrjConfig['deploy']['foreman']['hostgroup'];
            require_once('foremanapi.class.php');
            $oForeman = new ForemanApi($this->_aConfig['foreman']);
            // $oForeman->setDebug(1);
            // $oForeman->selfcheck(); die(__FUNCTION__);

            $aForemanHostgroups = $oForeman->read(array(
                'request' => array(
                    array('hostgroups'),
                // array('operatingsystems',4),
                ),
                'response' => array(
                    'id', 'title'
                ),
            ));
            $aSelectForemanGroups = array(
                'type' => 'select',
                'name' => 'deploy[foreman][hostgroup]',
                'label' => $this->_oHtml->getIcon('foreman') . t("foreman-hostgroup"),
                'options' => array(
                    OPTION_NONE => array(
                        'label' => t('none'),
                    ),
                    '' => array(
                        'label' => '- - - - - - - - - - - - - - - - - - - - ',
                    ),
                ),
            );
            if ($aForemanHostgroups && count($aForemanHostgroups)) {
                foreach ($aForemanHostgroups as $aItem) {
                    $bActive=$iForemanHostgroupDefault === (int) $aItem['id'];
                    $aSelectForemanGroups['options'][$aItem['id']] = array(
                        'label' => $aItem['title'],
                        'selected' => $bActive ? 'selected' : false,
                    );
                    $sForemanHostgroupDefault = $bActive ? $aItem['title'] : $sForemanHostgroupDefault;
                }
            }
        }


        $i = 0;

        $aPrefixItem = count($this->getVersions()) ?
                array(
            'type' => 'markup',
            'value' => '<div class="form-group">
                        <label class="col-sm-2">' . t('fileprefix') . '</label>
                        <div class="col-sm-10">
                            <input id="inputprefix" type="hidden" name="fileprefix" value="' . $this->_aPrjConfig["fileprefix"] . '">
                            ' . $this->_aPrjConfig["fileprefix"] . '
                        </div></div>
                            ',
                ) : array(
            'type' => 'text',
            'name' => 'fileprefix',
            // 'disabled' => 'disabled',
            'label' => t('fileprefix-label'),
            'value' => $this->_aPrjConfig["fileprefix"],
            'required' => 'required',
            'validate' => 'isastring',
            'pattern' => '[a-z0-9\-_]*',
            'size' => 100,
            'placeholder' => '',
        );

        // detect access to repo url
        $aBranches=$this->getRemoteBranches(true);
        // $aRepodata = $this->getRepoRevision();

        // if (is_array($aRepodata) && array_key_exists("message", $aRepodata)) {
        if (is_array($aBranches) && count($aBranches)) {
            $sRepoCheck = '<span class="ok">' . sprintf(t('class-project-info-repoaccess'), count($aBranches)) . '</span>';
        } else {
            $sRepoError=sprintf(t('class-project-error-no-repoaccess'), $aRepodata["error"]);
            $sRepoCheck = '<span class="error">' . $sRepoError . '</span>';
            $sMessages.=$this->_oHtml->getBox("error", $sRepoError);
        }

        // generate datalist with exisating ssh keys for auth field
        $sAuthListitems = '';
        foreach ($this->_getSshKeys() as $sKey) {
            $sAuthListitems.='<option value="' . $sKey . '">';
        }
        $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" class="nav-link active" data-toggle="tab" aria-selected="1">' . $this->_oHtml->getIcon('list').t('setup-metadata') . '</a></li>
                                <li><a href="#tab2" class="nav-link" data-toggle="tab">' . $this->_oHtml->getIcon('repository').t('repositoryinfos') . '</a></li>

                                <li><a href="#tab3" class="nav-link" data-toggle="tab">' . $this->_oHtml->getIcon('deploy-configfile').t('deploy-configfile') . '</a></li>
                                <li><a href="#tab4" class="nav-link" data-toggle="tab">' . $this->_oHtml->getIcon('deploy-rollout-plugin').t('deploy-rollout-plugin') . '</a></li>
                                <li><a href="#tab5" class="nav-link" data-toggle="tab">' . $this->_oHtml->getIcon('phase').t('phases') . '</a></li>
                                <li><a href="#tab6" class="nav-link" data-toggle="tab">' . $this->_oHtml->getIcon('raw-data').t('raw-data') . '</a></li>
                            </ul>
                            <div class="tab-content">
                            <div class="tab-pane fade active show" id="tab1">
                            <br>
                            
                            ',
                    ),

                    // --------------------------------------------------
                    // Tab for metadata
                    // -------------------------------------------------
                    'input' . $i++ => array(
                        'type' => 'text',
                        'name' => 'label',
                        'label' => t('projectname'),
                        'value' => $this->_aPrjConfig["label"],
                        'required' => 'required',
                        'validate' => 'isastring',
                        'size' => 100,
                        'placeholder' => 'Projekt',
                    ),
                    'input' . $i++ => array(
                        'type' => 'text',
                        'name' => 'description',
                        'label' => t('projectdescription'),
                        'value' => $this->_aPrjConfig["description"],
                        'required' => 'required',
                        'validate' => 'isastring',
                        'size' => 100,
                        'placeholder' => '',
                    ),
                    'input' . $i++ => array(
                        'type' => 'text',
                        'name' => 'contact',
                        'label' => t('contact'),
                        'value' => $this->_aPrjConfig["contact"],
                        'required' => 'required',
                        'validate' => 'isastring',
                        'size' => 100,
                        'placeholder' => '',
                    ),

                    'input' . $i++ => $aSelectProjectGroup,

                    'input' . $i++ => array(
                        'type' => 'markup',
                        'value' => '<p>' . t('messenger') . '</p>',
                    ),
                    'input' . $i++ => array(
                        'type' => 'text',
                        'name' => 'messenger[email]',
                        'label' => t("messenger-email"),
                        'value' => $this->_aPrjConfig["messenger"]["email"],
                        'validate' => 'isastring',
                        'size' => 100,
                        'placeholder' => '',
                        'autocomplete' => 'off',
                    ),
                    
                    'input' . $i++ => $aSelectSlack,
                    
                    // --------------------------------------------------
                    // Tab soources repository & build
                    // --------------------------------------------------
                    'input' . $i++ => array(
                        'type' => 'markup',
                        'value' => ' </div><div class="tab-pane fade" id="tab2">
                            <p>' . t('setup-hint-build') . '</p>',
                    ),
                    'input' . $i++ => array(
                        'type' => 'text',
                        'name' => 'build[type]',
                        'label' => t("build-type"),
                        'value' => $this->_aPrjConfig["build"]["type"],
                        'required' => 'required',
                        'validate' => 'isastring',
                        'size' => 100,
                        'placeholder' => '',
                    ),
                    'input' . $i++ => array(
                        'type' => 'text',
                        'name' => 'build[url]',
                        'label' => t("repository-url"),
                        'value' => $this->_aPrjConfig["build"]["url"],
                        // 'required' => 'required',
                        'validate' => 'isastring',
                        'size' => 100,
                        'placeholder' => '',
                    ),
                    'input' . $i++ => array(
                        'type' => 'text',
                        'name' => 'build[auth]',
                        'label' => t("repository-auth"),
                        'value' => $this->_aPrjConfig["build"]["auth"],
                        // 'required' => 'required',
                        'list' => 'listauth', // listauth is the next form id below
                        'validate' => 'isastring',
                        'size' => 100,
                        'placeholder' => '',
                    ),
                    'input' . $i++ => array(
                        'type' => 'markup',
                        'value' => '<datalist id="listauth">' . $sAuthListitems . '</datalist>',
                    ),
                    'input' . $i++ => array(
                        'type' => 'markup',
                        'value' => '<div class="form-group">'
                        . '<label class="col-sm-2"> </label><div class="col-sm-10">'
                        . $sRepoCheck
                        . '</div></div>',
                    ),
                    'input' . $i++ => array(
                        'type' => 'text',
                        'name' => 'build[webaccess]',
                        'label' => t("repository-urlwebgui"),
                        'value' => $this->_aPrjConfig["build"]["webaccess"],
                        'validate' => 'isastring',
                        'size' => 100,
                        'placeholder' => '',
                    ),
                    'input' . $i++ => $aPrefixItem,
                    'input' . $i++ => array(
                        'type' => 'markup',
                        'value' => '<div style="clear: both"></div>',
                    ),
                    // task#1498 - handle project without "public" directory
                    'input' . $i++ => array(
                        'type' => 'checkbox',
                        'name' => 'build[haspublic]',
                        'label' => t("repository-has-public-dir"),
                        'required' => false,
                        'validate' => 'isastring',
                        'options' => array(
                            '1' => array(
                                'label' => t("yes"),
                                'checked' => (array_key_exists('haspublic', $this->_aPrjConfig["build"]) ? $this->_aPrjConfig["build"]["haspublic"] : 0),
                            ),
                        ),
                    ),

                    // --------------------------------------------------
                    // Tab for config and API key
                    // --------------------------------------------------
                    'input' . $i++ => array(
                        'type' => 'markup',
                        'value' => ' </div><div class="tab-pane fade" id="tab3">
                            <p>' . t('deploy-configfile-hint') . '</p>',
                    ),
                    'textarea' . $i++ => array(
                        'type' => 'textarea',
                        'name' => 'deploy[configfile]',
                        'label' => t("deploy-configfile"),
                        'value' => $this->_aPrjConfig['deploy']["configfile"],
                        // 'required' => 'required',
                        'validate' => 'isastring',  
                        'cols' => 100,
                        'rows' => 10,
                        'placeholder' => 'export myvariable=&quot;hello world&quot;',
                    ),
   
                    'input' . $i++ => array(
                        'type' => 'text',
                        'name' => 'api[secret]',
                        'label' => t("api-secret"),
                        'value' => $this->_aPrjConfig["api"]["secret"],
                        'validate' => 'isastring',
                        'size' => 100,
                        'placeholder' => '',
                    ),                    
                    'input' . $i++ => array(
                        'type' => 'markup',
                        'value' => '<div class="col-sm-12">'
                        . '<p>' . t('api-secret-hint') . '<br>'
                            . '<a href="#" class="btn btn-default" onclick="$(\'#input'.($i-2).'\').val(generateSecret(64)); return false">'.t("api-secret-generate").'</a>'
                        . '</p></div>',
                    ),
                    
                    // --------------------------------------------------
                    // Tab rollout plugin
                    // --------------------------------------------------
                    'input' . $i++ => array(
                        'type' => 'markup',
                        'value' => ' </div><div class="tab-pane fade" id="tab4">
                            <p>' . t('deploy-rollout-plugin-hint') . '</p>',
                    ),
                    // select box for active rollout plugin
                    $aRollout['project-select'],
                    
                    // project based config 
                    'input' . $i++ => array(
                        'type' => 'markup',
                        'value' => ''
                            . '<hr>'
                                .'<label class="col-sm-2">'.t('deploy-rollout-plugin-config') .'</label>'
                                .'<div class="col-sm-10">'. $aRollout['project-config'].'</div>'
                    ),
                    // --------------------------------------------------
                    'input' . $i++ => array(
                        'type' => 'markup',
                        'value' => ' </div><div class="tab-pane fade" id="tab5">
                            <p>' . sprintf(t("class-project-info-setup-phaseinfos"), $this->getNextPhase()) . '</p>',
                    ),
                ),
            ),
        );
        // --------------------------------------------------
        // Tab for phases
        // --------------------------------------------------
        if ($aSelectForemanGroups) {
            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'markup',
                'value' => '<strong>'.t("defaults-all-phases").'</strong><br><br>',
            );
            $aForms["setup"]["form"]['input' . $i++] = $aSelectForemanGroups;
            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'markup',
                'value' => '<br><br>',
            );
        }
        foreach (array_keys($this->getPhases()) as $sPhase) {

            $bActivePhase = $this->isActivePhase($sPhase);
            $sUrl = array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["url"] : "";
            $sDeploymethod = array_key_exists("deploymethod", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["deploymethod"] : "";
            $sDeployhosts = array_key_exists("hosts", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["hosts"] : "";

            /*
             * task-1847 - reove adding ssh key
            if($sDeployhosts){
                echo "$sDeployhosts<br>";
                if(!strpos($sDeployhosts, ",")){
                    $sCmd=sprintf($this->_aConfig["installPackages"]["addkeycommand"], $sDeployhosts, $sDeployhosts);
                    exec($sCmd . " 2>&1", $aOut);
                    echo "<pre>\$ $sCmd<br>"
                        . implode('<br>', $aOut)
                        ."</pre>"
                        ;
                }
            }
             */
            $sDeploytimes = array_key_exists("deploytimes", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["deploytimes"] : "";
            $sDivId4PhaseSettings = 'divSettings' . $sPhase;
            $sDivId4TargetHosts = 'divSettings' . $sPhase . 'hosts';

            if ($aSelectForemanGroups) {
                $iForemanHostgroup = (int) $this->_aPrjConfig['phases'][$sPhase]['foreman-hostgroup'];
                $aSelectForemanHostGroup = array(
                    'type' => 'select',
                    'name' => 'phases[' . $sPhase . '][foreman-hostgroup]',
                    'label' => $this->_oHtml->getIcon('foreman') . t("foreman-hostgroup"),
                    'options' => array(
                        OPTION_DEFAULT => array(
                            'label' => t('default') . ' (' . $sForemanHostgroupDefault . ')',
                            'selected' => $iForemanHostgroup === OPTION_DEFAULT ? 'selected' : false,
                        ),
                        OPTION_NONE => array(
                            'label' => t('none'),
                            'selected' => $iForemanHostgroup === OPTION_NONE ? 'selected' : false,
                        ),
                        '' => array(
                            'label' => '- - - - - - - - - - - - - - - - - - - - ',
                        ),
                    ),
                );
                if (is_array($aForemanHostgroups) && count($aForemanHostgroups)) {
                    foreach ($aForemanHostgroups as $aItem) {
                        $aSelectForemanHostGroup['options'][$aItem['id']] = array(
                            'label' => $aItem['title'],
                            'selected' => ($iForemanHostgroup === $aItem['id']) ? 'selected' : false,
                        );
                    }
                }
            }
            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'markup',
                'value' => ''
                // .'<pre>'.print_r($this->_aPrjConfig["phases"][$sPhase], 1).'</pre>'
                /*
                  . '<a class="'.$sPhase.'">'
                  . t("phase") . ' ' . $sPhase
                  . '</a>'
                 */
                . '<table class="table">'
                . '<tbody>'
                . '<tr><th class="' . $sPhase . '">' . $this->_oHtml->getIcon('phase') . t("phase") . ' ' . $sPhase . '</th></tr>'
                . '<tr><td class="' . ($bActivePhase ? $sPhase : '') . '">'
                . ''
            );

            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'checkbox',
                'name' => 'phases[' . $sPhase . '][active]',
                'label' => t("phase-is-active"),
                // 'value' => $bUsePuppet,
                'required' => false,
                'validate' => 'isastring',
                // 'size' => 100,
                // 'placeholder' => '...',
                'options' => array(
                    '1' => array(
                        'label' => t("yes"),
                        'checked' => $bActivePhase,
                        'onclick' => '$(\'#' . $sDivId4PhaseSettings . '\').css(\'display\', (this.checked ? \'block\' : \'none\') )',
                    ),
                ),
            );
            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'markup',
                'value' => ''
                . '<div id="' . $sDivId4PhaseSettings . '" ' . ($bActivePhase ? '' : ' style="display: none;"') . '>'
            );
            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'text',
                'name' => 'phases[' . $sPhase . '][url]',
                'label' => $this->_oHtml->getIcon('url') . t("url-project-website"),
                'value' => $sUrl,
                // 'required' => 'required',
                'validate' => 'isastring',
                'size' => 100,
                'placeholder' => 'https://' . $sPhase . '.[' . t("project") . '].[...]/',
            );
            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'radio',
                'name' => 'phases[' . $sPhase . '][deploymethod]',
                'label' => $this->_oHtml->getIcon('method') . t("deploymethod"),
                // 'value' => $bUsePuppet,
                // 'required' => 'required',
                'validate' => 'isastring',
                // 'size' => 100,
                // 'placeholder' => '...',
                'options' => array(
                    'none' => array(
                        'label' => t("deploymethod-none"),
                        'checked' => $sDeploymethod === "none",
                        'onclick' => '$(\'#' . $sDivId4TargetHosts . '\').css(\'display\', (this.checked ? \'none\' : \'block\') )',
                    ),
                    'rolloutplugin' => array(
                        // 'label' => t("deploymethod-puppet").' - '.  $this->oRolloutPlugin->getName(),
                        'label' => t("deploymethod-rolloutplugin"),
                        'checked' => $sDeploymethod === "rolloutplugin",
                        'onclick' => '$(\'#' . $sDivId4TargetHosts . '\').css(\'display\', (this.checked ? \'block\' : \'none\') )',
                    ),
                /*
                 * see deploy method to handle an action
                  'sshproxy' => array(
                  'label' => t("deploymethod-sshproxy"),
                  'checked' => $sDeploymethod==="sshproxy",
                  'onclick' => '$(\'#'.$sDivId4TargetHosts.'\').css(\'display\', (this.checked ? \'block\' : \'none\') )',
                  ),
                 */
                ),
            );
            

            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'markup',
                'value' => ''
                . '<div id="' . $sDivId4TargetHosts . '" ' . ($sDeploymethod !== "none" ? '' : ' style="display: none;"') . '>'
            );
            
            // rollout plugin: phase specific overrides
            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'markup',
                'value' => ''
                    // . '<hr>'
                    .'<label class="col-sm-2">'.t('deploy-rollout-plugin-config') .'</label>'
                    .'<div class="col-sm-10">'.$aRollout[$sPhase].'</div>'
            ); 
            
            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'text',
                'name' => 'phases[' . $sPhase . '][hosts]',
                'label' => $this->_oHtml->getIcon('host') . t("phase-targethosts"),
                'value' => $sDeployhosts,
                // 'required' => 'required',
                'validate' => 'isastring',
                'size' => 100,
                'placeholder' => 'FQDN1,FQDN2',
            );

            /*
              if ($sPuppethost) {

              // add ssh host key
              $sOut0 = shell_exec(sprintf($this->_aConfig["installPackages"]["addkeycommand"], $sPuppethost, $sPuppethost));

              $sCmd2 = 'ssh ' . $this->_aConfig["installPackages"]["user"]
              . '@' . $sPuppethost
              . ' ' . $this->_aConfig["installPackages"]["testcommand"];
              $sOut = 'skip';
              // $sOut = shell_exec($sCmd2);
              // Check auf Versionsnummer - mehr als n Zeichen ist mutmasslich eine Fehlermeldung
              if (strlen($sOut) > 7) {
              $sMessages.=$this->getBox("error", sprintf(t("class-project-error-setup-sudo-pupet-agent-failed"), $sPhase, $sCmd, $sOut));
              $sOut = '<span class="error" title="' . $sCmd . '">' . $sOut . '</span>';
              } else {
              $sOut = '<span class="ok">' . sprintf(t("class-project-info-setup-ssh-and-puppet-ok"), $sPuppethost) . '</span>';
              }
              $aForms["setup"]["form"]['input' . $i++] = array(
              'type' => 'markup',
              'value' => '<div class="form-group">'
              . '<label class="col-sm-2"> </label><div class="col-sm-10">'
              . $sOut
              . '</div></div>',
              );
              }
             */

            // when to deploy
            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'text',
                'name' => 'phases[' . $sPhase . '][deploytimes]',
                'label' => $this->_oHtml->getIcon('time') . t("deploytimes"),
                'value' => $sDeploytimes,
                // 'required' => 'required',
                'validate' => 'isastring',
                'size' => 100,
                'placeholder' => isset($this->_aConfig["phases"][$sPhase]["deploytimes"]) ? implode(", ", $this->_aConfig["phases"][$sPhase]["deploytimes"]) : '',
            );
            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'markup',
                'value' => ''
                . '</div>'
            );

            if ($aSelectForemanGroups) {
                $aForms["setup"]["form"]['input' . $i++] = $aSelectForemanHostGroup;
            }

            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'markup',
                'value' => ''
                . '</div>'
            ); // close div for active phase


            $aForms["setup"]["form"]['input' . $i++] = array(
                'type' => 'markup',
                'value' => '</td></tr></tbody></table>',
            );
        } // END: loop over phases

        // --------------------------------------------------
        // Tab for raw data
        // --------------------------------------------------
        
        $sRolloutDebug='<hr>DEBUG:<br>';
        foreach (array_keys($this->getPhases()) as $sPhase) {
            if ($this->isActivePhase($sPhase)){
                $sRolloutDebug.='<strong>'.$sPhase.'</strong>'
                . '<pre>Config = '.print_r($this->oRolloutPlugin->getConfig($sPhase, 1), 1).'</pre>'
                . '<pre>Commands = '.print_r($this->oRolloutPlugin->getDeployCommands($sPhase, 1), 1).'</pre>'
                ;
            }
        }

        $aForms["setup"]["form"]['input' . $i++] = array(
            'type' => 'markup',
            'value' => '</div>'
            
                . '<div class="tab-pane fade" id="tab6">'
                . '<br><pre>'.print_r($this->_aPrjConfig, 1).'</pre>'
                . $sRolloutDebug
                . '</div>'
            
            . '</div>'
            . '</div>'
            . '<div style="clear: both; margin-bottom: 1em;"></div>'
            
            
            . '<hr>',
        );
        $aForms["setup"]["form"]['input' . $i++] = array(
            'type' => 'submit',
            'name' => 'btnsave',
            'label' => t("save"),
            'value' => $this->_oHtml->getIcon('sign-ok').t("save"),
        );

        $oForm = new formgen($aForms);
        return $sMessages . $oForm->renderHtml("setup");
    }

    /**
     * return html code for the installed version in the repository
     * @param boolean  $bRefresh  optional: refresh flag; default: use cached information
     * @return string
     */
    public function renderRepoInfo($bRefresh=false) {
        $sReturn = "";
        switch ($this->_aPrjConfig["build"]["type"]) {
            case "git":

                $aRepodata = $this->getRepoRevision($bRefresh);
                if (array_key_exists("revision", $aRepodata)) {
                    $sReturn.=$this->_getChecksumDiv($aRepodata["revision"],
                        $this->_oHtml->getIconByType('branch') . t('branch') . ': ' . (array_key_exists("branch", $aRepodata) ? $aRepodata["branch"] : '-') . '<br>'
                        . $this->_oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aRepodata["revision"]) . '<br>'
                        . $this->_oHtml->getIconByType('comment') . t('commitmessage') . ':<br>'
                        )
                        ."<pre>" . htmlentities($aRepodata["message"]). "</pre>";
                } else {
                    $sReturn .= $this->_oHtml->getBox("error", sprintf(t('class-project-error-no-repoinfo'), $aRepodata["error"]))
                            . $this->renderLink("setup") . '<br>';
                }

                break;

            default:
                $sReturn .= $this->_oHtml->getBox("error", sprintf(t('class-project-error-wrong-buildtype'), $this->_aPrjConfig["build"]["type"]));
        }
        if (array_key_exists("url", $this->_aPrjConfig["build"])) {
            $sReturn.=t('repository-url') . ': ' . $this->_aPrjConfig["build"]["url"] . '<br>';
        }
        if (array_key_exists("webaccess", $this->_aPrjConfig["build"])) {
            $sReturn.=t('repository-access-browser') . ':<br><a href="' . $this->_aPrjConfig["build"]["webaccess"] . '">' . $this->_aPrjConfig["build"]["webaccess"] . '</a><br>';
        }
        return $sReturn;
    }

    /**
     * get html code for a link to the commit
     * (works for guithub and gitlab instances)
     * 
     * @param string $sRevision
     * @return string
     */
    public function _renderRevision($sRevision) {
        $sUrl = str_replace('/tree/master', '', $this->_aPrjConfig["build"]["webaccess"]) . '/commit/' . $sRevision;
        return '<a href="' . $sUrl . '">' . $sRevision . '</a>';
        return $sUrl;
    }
    /**
     * get html form with selectr for remote branches
     * @param string $sActiveBranchname  force active branch name
     * @param bool $bIgnoreCache  flag to ignore exiting cached data
     * @return string
     */
    public function renderSelectRemoteBranches($sActiveBranchname = false, $bIgnoreCache=false) {
        $this->log(__FUNCTION__."(sActiveBranchname = $sActiveBranchname, bIgnoreCache = ".($bIgnoreCache ? 'true' : 'false').") start");
        $aReturn = array();
        $aRadios = array();
        $bFoundActive = false;
        $i = 0;
        if (!$this->_oVcs) {
            $this->_initVcs();
        }
        require_once("formgen.class.php");
        if (!$sActiveBranchname) {
            $sActiveBranchname = $this->_sBranchname;
        }
        if ($this->_oVcs) {
            if (!method_exists($this->_oVcs, "getRemoteBranches")) {
                // the version control class does not have this method
                return '';
            }
            foreach ($this->_oVcs->getRemoteBranches($bIgnoreCache) as $aBranch) {
                $sBranch = $aBranch['name'];
                $aRadios[$sBranch] = array(
                    'value' => $sBranch,
                    'label' => $aBranch['label'],
                );
                // if no param was given the first branch will be marked
                if (!$sActiveBranchname) {
                    $sActiveBranchname = $sBranch;
                }
                if ($sBranch == $sActiveBranchname) {
                    $bFoundActive = true;
                    // $aRadios[$sBranch]['checked'] = 'checked';
                    $aRadios[$sBranch]['selected'] = 'selected';
                } else {
                    // for SELECT we need the onclick even on select element
                    // not on the option (Chrome)
                    // $aRadios[$sBranch]['onclick'] = 'document.getElementById(\'submitBranch\').click()';
                }
            };
        }
        // no branches were found
        if (count($aRadios) == 0) {
            return '';
        }

        $aForms = array(
            'frmSelectBranch' => array(
                'meta' => array(
                    'method' => 'POST',
                    'action' => '?',
                    'id' => 'frmSelectBranch',
                ),
                'validate' => array(),
                'form' => array(
                    'branchname' => array(
                        'inline' => true,
                        'type' => 'select',
                        'onchange' => 'document.getElementById(\'submitBranch\').click()',
                        'name' => 'branchname',
                        'label' => '<strong>' . t('branch-select') . '</strong>',
                        'validate' => 'isastring',
                        'options' => $aRadios,
                    ),
                ),
            ),
        );

        // submit to switch branches - only if a selection is available
        if (count($aRadios) > 1 || !$bFoundActive) {
            $aForms['frmSelectBranch']['form']['submitBranch'] = array(
                'type' => 'submit',
                'name' => 'btnsave',
                'onclick' => 'showModalMessage(\'' . t('branch-switch') . '\'); ',
                'label' => t("change"),
                'value' => $this->_oHtml->getIcon('sign-ok').t("change"),
            );
        }

        $oFrm = new formgen($aForms);
        return $oFrm->renderHtml('frmSelectBranch')
                . '<script>$("#submitBranch").hide();</script>';
        // return $oFrm->renderHtmlElement('dummy',$aFormData);
    }

    /**
     * return html code for a list of all built packages and their usage
     * @return string
     */
    public function renderVersionUsage() {
        $sReturn = false;
        $sRowHead1 = false;
        $sRowHead2 = '<td></td>';

        $aAllVersions = $this->_getVersionUsage();
        if (!count($aAllVersions)) {
            return $this->_oHtml->getBox("info", t('class-project-info-no-package'));
        }

        foreach ($this->getActivePhases() as $sPhase) {
            $sRowHead1.='<th class="' . $sPhase . ' tdphase" colspan="' . (count($this->_aPlaces) + 1) . '">' . $sPhase . '</th>';
            $sRowHead2.='<td></td>' . $this->renderPlacesAsTd($sPhase);
        }
        
        krsort($aAllVersions);
        foreach ($aAllVersions as $sVersion => $aData) {
            $sReturn.='<tr>';

            $sInfos = $this->renderInfoLink($aData["info"], array('hpos' => 'left'));
            $sReturn.='<td>'
                        . $this->_getChecksumDiv(
                            $aData['info']['revision'],
                            $this->_oHtml->getIconByType('calendar') . t('build-from') . ': ' . $sVersion .'<br>'
                                . $this->_oHtml->getIconByType('branch') . t('branch') . ': ' . $aData['info']["branch"] . '<br>'
                                . $this->_oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aData['info']["revision"]) . '<br>'
                          )
                    . '</td><td>'
                    . '&nbsp;&nbsp;' . $sInfos . '&nbsp;&nbsp;'
                    . '</td>'
                    ;

            foreach ($this->getActivePhases() as $sPhase) {
                $sTLine = '';
                $bCanRollback = $aData["rollback"][$sPhase];

                // $sReturn.=$aData["rollback"][$sPhase] ? '<td>'.$this->renderLink("rollback", $sPhase, $sVersion).'</td>' : '<td>Rollback NOT possible</td>';
                // $sReturn.=$aData["rollback"][$sPhase] ? '<td> Y </td>' : '<td> N </td>';
                $sReturn.='<td>  </td>';

                foreach (array_keys($this->_aPlaces) as $sPlace) {
                    $bFound = false;
                    $sReturn.=$aData["usage"][$sPhase][$sPlace] 
                            ? '<td class="' . $sPhase . '" style="text-align: center;">'
                            . $this->_getChecksumDiv($aData['info']['revision'], 'X')
                            . '</td>' 
                            : '<td> </td>'
                        ;
                }
            }
            $sReturn.='</tr>';
        }

        $sReturn = t('class-project-info-table-packages') . '<br><br>'
                . '<table>'
                . '<thead><tr><td>Version</td><td></td>'
                . $sRowHead1
                . '</tr><tr><td>'
                . $sRowHead2
                . '</tr></thead>'
                . '<tbody>'
                . $sReturn
                . '</tbody>'
                . '</table>';
        return $sReturn;
    }

    /**
     * render graphical overview of process (in project overview)
     * @return string
     */
    public function renderVisual() {
        $sReturn = '';

        $renderAdminLTE=new renderadminlte();

        $sBarHeightBg = '1.0em';
        $sBarHeight = '0.8em';
        $sContinue = '<span style="font-size: 300%; color:#ace;">&raquo;&raquo;</span><br>';

        $aBranches=$this->getRemoteBranches();
        if(!is_array($aBranches)){
            return '<br>'.$this->_oHtml->getBox("error", t("project-setup-incomplete"));
        }

        $sRepoBar = '';
        /*
            Speedup:
            
        $aRepodata = $this->getRepoRevision();
        if (array_key_exists("revision", $aRepodata)) {
            $sRepoBar = $this->_getChecksumDiv($aRepodata["revision"]);
        } else {
            $sRepoBar = '<span class="error">' . t("error") . '</span>';
        }
        */

        $sPackagebar = '';
        $aVersions = $this->_getVersionUsage();
        foreach ($aVersions as $sVersion => $aData) {
            $sBar = $aData["info"]["revision"] ? $this->_getChecksumDiv($aData["info"]["revision"], '' , $sBarHeight) : '';
            $sPackagebar.='<span title="' . $sVersion . '" style="float: left; background:#eee; height: '.$sBarHeightBg.'; width:' . (100 / count($aVersions)) . '%">' . $sBar . '&nbsp;</span>';
        }

        $sPhaseImg = '';
        $sLastPhase = '';
        foreach ($this->getActivePhases() as $sPhase) {
            if ($sPhaseImg) {
                $sAction = $sContinue;
                if ($this->canAcceptPhase($sLastPhase)) {
                    $sAction .= $this->renderLink("accept", $sLastPhase);
                }
                $sPhaseImg.='<div class="action">' . $sAction . '</div>';
            }
            $sLastPhase = $sPhase;

            $sFullbar = '';
            foreach (array_keys($this->_aPlaces) as $sPlace) {
                $sFullbar.='<span title="' . $this->_aPlaces[$sPlace] . '" style="float: left; background:#eee; height: '.$sBarHeightBg.'; width:' . (100 / count($this->_aPlaces)) . '%">' . $this->_renderBar($sPhase, $sPlace, $sBarHeight) . '&nbsp;</span>';
            }
            // $sDetail = $sFullbar . '<br><a href="#h3phases" class="scroll-link">' . $sPhase . '</a>';
            $sDetail = $sFullbar . '<br>' . $sPhase;

            $sPhaseImg.='
            <div class="process">
                <div class="details">' . $sDetail . ' </div>
            </div>';
        }

        $sReturn .= ''
            . '<div class="visualprocess">'
            . $renderAdminLTE->addRow(
                ''
                . $renderAdminLTE->addCol(
                    $renderAdminLTE->getCard([
                        'type'=>'',
                        'variant'=>'outline',
                        'title'=>'',
                        'text'=> '<h3>'.t("versioncontrol"). '</h3>' . $sRepoBar . '<br>
                            ' . t("repositoryinfos") . '<br>
                            ' // . $this->_aPrjConfig["build"]["type"] 
                            . preg_replace('/.*\@(.*):.*/', '$1', $this->_aPrjConfig["build"]["url"])
                            . '<br>(<strong title="' . t('branch-select') . '">' . count($aBranches) . '</strong>)',
                        ]
                    ), 2
                    )
                . $renderAdminLTE->addCol(
                    '<div class="action">' . $sContinue . t("build-hint-overview") . '<br><br>' . ($this->canAcceptPhase() ? $this->renderLink("build") : '') . '</div>',
                    1
                )
                . $renderAdminLTE->addCol(
                    $renderAdminLTE->getCard([
                        'type'=>'',
                        'variant'=>'outline',
                        'title'=>'',
                        'text'=> '<h3>'.$this->_oHtml->getIcon('package') . t("archive"). '</h3>' 
                            . '<div class="archive">'
                                . '<div class="details">'
                                . $sPackagebar . '<br>' 
                                . '</div>'
                                . t("packages") . '<br>'
                                .'(<strong>' . count($this->_getVersionUsage()) . '</strong>)'
                            . '</div>'
                        ]
                    ), 2
                    )
                . $renderAdminLTE->addCol(
                    '<div class="action">'.$sContinue . sprintf(t("queue-hint-overview"), $this->getNextPhase()).'</div>',
                    1
                )
                . $renderAdminLTE->addCol(
                    $renderAdminLTE->getCard([
                        'type'=>'',
                        'variant'=>'outline',
                        'title'=>'',
                        'text'=> '<h3>'.$this->_oHtml->getIcon('phase') . t("phases"). '</h3>' 
                            . ($sPhaseImg ? $sPhaseImg : '<div class="process">' . t("none") . '</div>')
                        ]
                    ), 6
                    )
                )
                .'</div>'
            ;

        return $sReturn;
    }

}