From 11825448fdc2066c6cd4f72e8cd8fbf29ef7add9 Mon Sep 17 00:00:00 2001
From: "Hahn Axel (hahn)" <axel.hahn@iml.unibe.ch>
Date: Wed, 10 May 2017 16:59:41 +0200
Subject: [PATCH] CI Webgui: ad feature in project setup: input for
 replacements

---
 config/inc_roles.php                          |   1 +
 config/lang/de.json                           |   5 +
 config/lang/en.json                           |   5 +
 .../classes/config-replacement.class.php      |  91 +++++++++
 .../deployment/classes/project.class.php      | 175 ++++++++++++++++--
 public_html/deployment/pages/act_overview.php |   6 +-
 6 files changed, 269 insertions(+), 14 deletions(-)
 create mode 100644 public_html/deployment/classes/config-replacement.class.php

diff --git a/config/inc_roles.php b/config/inc_roles.php
index 9375e00d..4fc52c01 100644
--- a/config/inc_roles.php
+++ b/config/inc_roles.php
@@ -57,6 +57,7 @@ return array(
         "project-action-overview",
         "project-action-phase",
         "project-action-setup",
+        "project-action-edit-replacements",
     ),
     
     // ----- wenn es mal eine feinere Granulierung braucht, muss man eine
diff --git a/config/lang/de.json b/config/lang/de.json
index 2456673f..aed7b29c 100644
--- a/config/lang/de.json
+++ b/config/lang/de.json
@@ -224,6 +224,7 @@
     "defaultbranch": "[default: Master oder Trunk]",
     "deploy": "Deploy",
     "deploy-hint": "Deploy der Queue von Phase [%s]",
+    "deploy-settings": "Deployment - Einstellungen",
     "deploytimes": "Deployment Zeitpunkte",
     "deploytimes-immediately": "Ein Archiv in der Queue wird sofort ins Repo gestellt.",
     "deploymethod": "Deployment-Methode",
@@ -267,6 +268,10 @@
     "projectname": "Projektname",
     "projectmanager": "Projektleiter",
     "queue": "Queue",
+    "replacement-fields": "erkannte Platzhalter:",
+    "replacement-targetfile": "Ziel-Datei",
+    "replacements": "Ersetzungen mit Template-Dateien",
+    "replacements-info": "Beim Build-Prozess gefundene Templates werden aufgelistet und darin erkannte Platzhalter erkannt. Die Felder sind ausschliesslich für die neue Infrastruktur relevant.",
     "repositoryinfos": "Quell-Repository",
     "repository-access-browser": "Browserzugriff auf das Repo",
     "repository-auth": "Authentifizierung/ Dateiname zum SSH-Private-Key",
diff --git a/config/lang/en.json b/config/lang/en.json
index 0cdc4aa3..4b3132a6 100644
--- a/config/lang/en.json
+++ b/config/lang/en.json
@@ -226,6 +226,7 @@
     "defaultbranch": "[default: master or trunk]",
     "deploy": "Deploy",
     "deploy-hint": "Deploy queue of phase [%s]",
+    "deploy-settings": "Deployment - settings",
     "deploymethod": "Deployment method",
     "deploymethod-none": "None. Do not trigger any action.",
     "deploymethod-puppet": "Run Puppet on target host(s)",
@@ -269,6 +270,10 @@
     "projectname": "Project name",
     "projectmanager": "Projekt manager",
     "queue": "Queue",
+    "replacement-fields": "Detected placeholders:",
+    "replacement-targetfile": "Target file",
+    "replacements": "Replacements in template files",
+    "replacements-info": "List of detected templates with its placeholders.",
     "repositoryinfos": "Sourcecode Repository",
     "repository-access-browser": "Browser access to sources",
     "repository-auth": "Authentication/ filename of ssh private key",
diff --git a/public_html/deployment/classes/config-replacement.class.php b/public_html/deployment/classes/config-replacement.class.php
new file mode 100644
index 00000000..d56557c9
--- /dev/null
+++ b/public_html/deployment/classes/config-replacement.class.php
@@ -0,0 +1,91 @@
+<?php
+
+// namespace imldeployment;
+
+require_once 'project.class.php';
+
+
+/**
+ * config-replacement class
+ * reads templatefiles and scans its placeholders for replacements
+ *
+ * @author hahn
+ */
+class configreplacement {
+    
+    /**
+     * project class
+     * @var type 
+     */
+    protected $_oProject = false;
+
+    
+    /**
+     * init
+     * @param string  $sProject  optional: project id; you can use setProject() too
+     * @return boolean
+     */
+    public function __construct($sProject = false) {
+        if ($sProject){
+            $this->setProject($sProject);
+        }
+        return true;
+    }
+    
+
+    /**
+     * get an array with a flat list of all templatefiles of a build
+     * @param string $sPhase  name of phase
+     * @return boolean|array
+     */
+    public function getTemplatefiles($sPhase=false){
+        $aReturn = array();
+        if (!$sPhase){
+            $sPhase=$this->_oProject->getNextPhase(false);
+        }
+
+        $aBuildfiles=$this->_oProject->_getBuildfilesByPlace($sPhase, 'onhold');
+        if (!$aBuildfiles){
+            $aBuildfiles=$this->_oProject->_getBuildfilesByPlace($sPhase, 'ready2install');
+        }
+        
+        if (!$aBuildfiles || !array_key_exists('types', $aBuildfiles) || !array_key_exists('templates', $aBuildfiles['types'])){
+            return false;
+        }
+        foreach ($aBuildfiles['types']['templates'] as $sFile){
+            $aReturn[]=$aBuildfiles['dir'].'/'.$sFile;
+        }
+        return $aReturn;
+    }
+
+    /**
+     * get an array with all template files (basename) and its replacement fields
+     * @param string $sPhase  name of phase
+     * @return array
+     */
+    public function getReplacements($sPhase=false){
+        $aFiles=$this->getTemplatefiles();
+        if (!$aFiles){
+            return false;
+        }
+        
+        $aReturn=array();
+        foreach ($aFiles as $sFile){
+            // $sFile always exists because it was read from filesystem
+            $sContent=file_get_contents($sFile);
+
+            preg_match_all('/replace\[[\'\"](.*)[\'\"]\]/U', $sContent, $aMatches);
+            // echo '<strong>'.$sFile.'</strong><br>'.print_r($aMatches, 1).'<br>'.$sContent.'<br><br>';
+            $aReturn[$sFile]=$aMatches[1];
+       }
+        return $aReturn;
+    }
+    
+    /**
+     * switch to a project
+     * @param type $sProject
+     */
+    public function setProject($sProject){
+        $this->_oProject = new project($sProject);
+    }
+}
diff --git a/public_html/deployment/classes/project.class.php b/public_html/deployment/classes/project.class.php
index 6eecaf10..fbfe6eda 100644
--- a/public_html/deployment/classes/project.class.php
+++ b/public_html/deployment/classes/project.class.php
@@ -362,6 +362,80 @@ class project extends base {
         $sBase = $this->_getFileBase($sPhase, $sPlace);
         return $sBase ? $sBase . '/' . $this->_aPrjConfig["fileprefix"] . '.tgz' : false;
     }
+    
+    /**
+     * list of files of a given phase and place
+     * @param string $sPhase  one of preview|stage|live ...
+     * @param string $sPlace  one of onhold|ready2install|deployed
+     * @return array
+     */
+    public function _getBuildfilesByDir($sBase) {
+        $aReturn=array();
+        if(!$sBase || !is_dir($sBase)){
+            return false;
+        }
+        $iTotalSize=0;
+        $aReturn=array(
+            'dir'=>$sBase,
+            'filecount'=>false,
+            'totalsize'=>false,
+            'totalsize-hr'=>false,
+        );
+        
+        foreach (glob($sBase . '/*') as $sFile){
+            $sFileBase=basename($sFile);
+            $sExt = pathinfo($sFile, PATHINFO_EXTENSION);
+            $aStat=stat($sFile);
+            switch($sExt){
+                case 'erb':
+                    $sType='templates';
+                    break;
+                case 'tgz':
+                    $sType='package';
+                    break;
+                case 'json':
+                    $sType='metadata';
+                    break;
+                default:
+                    $sType='any';
+                    break;
+            }
+            $iTotalSize+=$aStat['size'];
+            $aReturn['files'][$sFileBase]=array(
+                'type'=>$sType,
+                'extension'=>$sExt,
+                'size'=>$aStat['size'],
+            );
+            $aReturn['types'][$sType][]=$sFileBase;
+        }
+        $aReturn['totalsize']=$iTotalSize;
+        $aReturn['totalsize-hr']=(round($iTotalSize/1024/102.4)/10).' MB';
+        $aReturn['filecount']=count($aReturn['files']);
+        
+        return $aReturn;
+    }
+    
+    /**
+     * list of files of a given phase and place
+     * @param string $sPhase  one of preview|stage|live ...
+     * @param string $sPlace  one of onhold|ready2install|deployed
+     * @return array
+     */
+    public function _getBuildfilesByPlace($sPhase, $sPlace) {
+        $sBase = $this->_getFileBase($sPhase, $sPlace);
+        return $this->_getBuildfilesByDir($sBase);
+    }
+    /**
+     * list of files of a given phase and place
+     * @param string $sPhase  one of preview|stage|live ...
+     * @param string $sPlace  one of onhold|ready2install|deployed
+     * @return array
+     */
+    public function _getBuildfilesByVersion($sVersion) {
+        return $this->_getBuildfilesByDir($this->_getProjectArchiveDir().'/'.$sVersion);
+    }
+    
+
 
     /**
      * get full path of a packed project archive
@@ -2704,8 +2778,12 @@ class project extends base {
         foreach ($aAllVersions as $sVersion => $aData) {
             $sReturn.='<tr>';
 
+            // $aFiles=$this->_getBuildfilesByVersion($sVersion);
             $sInfos = $this->renderInfoLink($aData["info"], array('hpos' => 'left'));
-            $sReturn.='<td>' . $sVersion . ' ' . $sInfos . '</td>';
+            $sReturn.='<td>' 
+                    . $sVersion . ' ' . $sInfos . '<br>'
+                    // . $aFiles['filecount'] . ' ('.$aFiles['totalsize-hr'].')' 
+                    . '</td>';
 
             foreach ($this->getActivePhases() as $sPhase) {
                 $sTLine = '';
@@ -2850,6 +2928,11 @@ class project extends base {
 
         $sMessages = '';
         require_once ("formgen.class.php");
+        
+        require_once("./classes/config-replacement.class.php");
+        $oConfig = new configreplacement();
+        $oConfig->setProject($this->_aConfig["id"]);
+        
         $i = 0;
 
         $aPrefixItem = count($this->getVersions()) ?
@@ -3033,14 +3116,17 @@ class project extends base {
             $aForms["setup"]["form"]['input' . $i++] = array(
                 'type' => 'markup',
                 'value' => ''
-                // .'<pre>'.print_r($this->_aPrjConfig["phases"][$sPhase], 1).'</pre>'
-                . '<a class="'.$sPhase.'">'
-                . t("phase") . ' ' . $sPhase
-                . ($bActivePhase ? '' : ' (' . t("inactive") . ')')
-                . '</a>'
-                . '<table class="table">'
-                . '<tbody>'
-                . '<tr><td class="' . ($bActivePhase ? $sPhase : '') . '">'
+                    // .'<pre>'.print_r($this->_aPrjConfig["phases"][$sPhase], 1).'</pre>'
+                    /*
+                    . '<a class="'.$sPhase.'">'
+                    . t("phase") . ' ' . $sPhase
+                    . '</a>'
+                     */
+                    . '<table class="table">'
+                    . '<tbody>'
+                    . '<tr><th class="' . $sPhase . '">'. t("phase") . ' ' . $sPhase.'</th></tr>'
+                    . '<tr><td class="' . ($bActivePhase ? $sPhase : '') . '">'
+                . ''
             );
             
             $aForms["setup"]["form"]['input' . $i++] = array(
@@ -3066,6 +3152,10 @@ class project extends base {
                 'value' => ''
                 .'<div id="'.$sDivId4PhaseSettings.'" '.($bActivePhase ? '' : ' style="display: none;"').'">'
             );
+            $aForms["setup"]["form"]['input' . $i++] = array(
+                'type' => 'markup',
+                'value' => '<div style="clear: both"></div><div class="form-group"><h3>'.t("deploy-settings").'</h3></div>'
+            );
             $aForms["setup"]["form"]['input' . $i++] = array(
                 'type' => 'text',
                 'name' => 'phases[' . $sPhase . '][url]',
@@ -3164,16 +3254,79 @@ class project extends base {
                 'size' => 100,
                 'placeholder' => implode(", ", $this->_aConfig["phases"][$sPhase]["deploytimes"]),
             );
+            
+            $aReplacements=$oConfig->getReplacements($sPhase);
+            $aForms["setup"]["form"]['input' . $i++] = array(
+                'type' => 'markup',
+                'value' => '<div style="clear: both; height: 2em;"></div>'
+                    . '<div class="form-group">'
+                    . '<h3>'.t("replacements").'  ('.($aReplacements ? count($aReplacements):0).')</h3>'
+                    . t('replacements-info')
+                    . '</div>'
+            );
+            
+            if ($aReplacements){
+                foreach($aReplacements as $sFile=>$aFields){
+                    $tTplFile=basename($sFile);
+                    $aValues = (array_key_exists("replace", $this->_aPrjConfig["phases"][$sPhase])
+                            && array_key_exists($tTplFile, $this->_aPrjConfig["phases"][$sPhase]["replace"])
+                            )
+                            ? $this->_aPrjConfig["phases"][$sPhase]["replace"][$tTplFile] : false;
+                    $aForms["setup"]["form"]['input' . $i++] = array(
+                        'type' => 'markup',
+                        'value' => '<div class="form-group"><br><h4><i class="fa fa-file-code-o"></i> '.$tTplFile.'</h4>'
+                        // . '<textarea cols="100" rows="7" >'.file_get_contents($sFile).'</textarea>'
+                        . '</div>'
+                    );
+                    $aForms["setup"]["form"]['input' . $i++] = array(
+                        'type' => 'text',
+                        'name' => 'phases[' . $sPhase . '][replace]['.$tTplFile.'][targetfile]',
+                        'label' => t("replacement-targetfile"),
+                        'value' => $aValues && array_key_exists('targetfile', $aValues) ? $aValues['targetfile'] : '',
+                        // 'required' => 'required',
+                        'validate' => 'isastring',
+                        'size' => 100,
+                        'placeholder' => strip_tags(t("replacement-targetfile")),
+                    );
+                    $aForms["setup"]["form"]['input' . $i++] = array(
+                        'type' => 'markup',
+                        'value' => '<br>'.t("replacement-fields") 
+                            // . '<pre>'.print_r($aValues, 1).'</pre>'
+                    );
+                    foreach ($aFields as $sField){
+                        
+                        $aForms["setup"]["form"]['input' . $i++] = array(
+                            'type' => 'text',
+                            'disabled' => $this->oUser->hasPermission("project-action-edit-replacements") ? '' : 'disabled',
+                            'name' => 'phases[' . $sPhase . '][replace]['.$tTplFile.']['.$sField.']',
+                            'label' => $sField,
+                            'value' => $aValues && array_key_exists($sField, $aValues) ? $aValues[$sField] : '',
+                            // 'required' => 'required',
+                            'validate' => 'isastring',
+                            'size' => 100,
+                            'placeholder' => $sField,
+                        );
+                    }
+                }
+            } else {
+                $aForms["setup"]["form"]['input' . $i++] = array(
+                    'type' => 'markup',
+                    'value' => '<div class="form-group"><h4>'.t("none").'</h4></div>'
+                );
+            }
+            
             $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
         $aForms["setup"]["form"]['input' . $i++] = array(
             'type' => 'markup',
             'value' => '</div></div></div>'
diff --git a/public_html/deployment/pages/act_overview.php b/public_html/deployment/pages/act_overview.php
index 9dd3c282..f40f6803 100644
--- a/public_html/deployment/pages/act_overview.php
+++ b/public_html/deployment/pages/act_overview.php
@@ -37,12 +37,12 @@ if (!array_key_exists("prj", $aParams)) {
         $sListOfBranches.='<li title="'.$aBranch['revision'].'">'.$aBranch['label'] . '</li>';
     }
     $sListOfBranches.='</ol>';
-    
+
     $sPhaseTabs='';
     $sPhaseDetails='';
- 
+
     $sOut = '
-        
+                
             ' . $oPrj->renderLink("setup") . '<br>
                 <br>
         
-- 
GitLab