diff --git a/config/inc_projects_config.php b/config/inc_projects_config.php deleted file mode 100644 index 926ce3cd6d9bd303a29dbfb00fbb4a4abd642d0a..0000000000000000000000000000000000000000 --- a/config/inc_projects_config.php +++ /dev/null @@ -1,118 +0,0 @@ -<?php - -// ---------------------------------------------------------------------- -// fetch status infos von den einzelnen Phasen -// ---------------------------------------------------------------------- - -$aConfig = array( - // Basispfad: - 'workDir' => '/var/imldeployment', - 'tmpDir' => '/var/tmp/imldeployment', - 'versionsToKeep' => 10, // for cleanup: keep n unused versions - 'builtsToKeep' => 3, // for cleanup: keep n failed builds - 'hooks' => array( - 'build-postclone' => 'hooks/onbuild-postclone', - 'build-precompress' => 'hooks/onbuild', // TODO: remove this - ), - 'lang' => 'de', // for available languages see ./config/lang/*.json - // rsync of archives - 'mirrorPackages' => array(), - // ssh install - if a host is given - 'installPackages' => array( - 'user' => 'imldeployment', - - // command to update ssh hostkey in known_hosts file - // %s is name of the server (2x) - 'addkeycommand' => '/usr/bin/ssh-keygen -R %s; /usr/bin/ssh-keyscan -t rsa %s >> /home/www-data/.ssh/known_hosts', - - // command to verify if puppet host is correct - 'testcommand' => 'sudo puppet --version', - - // puppet agent liefert 0 oder 2 zurueck, wenn OK - // http://docs.puppetlabs.com/references/3.4.0/man/apply.html - 'command' => 'sudo puppet agent -t --detailed-exitcodes ; rc=$?; if [ $rc -eq 2 ]; then rc=0; fi ; exit $rc', - ), - 'phases' => array( - "preview" => array( - 'css' => array( - 'bgdark' => 'background:#89a; color:#f8f8f8;', - 'bglight' => 'background:#e8e8f8; color:#333; background: rgba(180,180,230, 0.3);', - 'bgbutton' => 'background:#89a; color:#fcfcfc; border: 1px solid rgba(0,0,0,0.15);', - ), - ), - "stage" => array( - 'css' => array( - 'bgdark' => 'background:#8aa; color:#f8f8f8;', - 'bglight' => 'background:#e4f8f8; color:#333; background: rgba(180,210,210, 0.3);', - 'bgbutton' => 'background:#8aa; color:#fcfcfc; border: 1px solid rgba(0,0,0,0.15);', - ), - ), - "live" => array( - 'css' => array( - 'bgdark' => 'background:#8a8; color:#f8f8f8;', - 'bglight' => 'background:#e0f8e0; color:#333; background: rgba(180,250,180, 0.3);', - 'bgbutton' => 'background:#8a8; color:#fcfcfc; border: 1px solid rgba(0,0,0,0.15);', - ), - // wenn deploytimes existiert, dann wird nach dem Deploy das Paket - // in einer Queue zurueckgehalten - "deploytimes" => array('/(Mon|Tue|Wed|Thu)\ 14\:/'), - ), - ), - 'showdebug' => array( - 'ip'=>array('10.0.2.2', '127.0.0.1', '130.92.79.49'), - ), // for available languages see ./config/lang/*.json -); - -// ---------------------------------------------------------------------- -// override for local development -// ---------------------------------------------------------------------- - - -switch (php_uname("n")) { - case "USER": - case "AAE49": - case "dev.ci.iml.unibe.ch": - $aConfig['workDir'] = "D:\imldeployment"; - break; - - case "ci.iml.unibe.ch": - - // synch der Pakete nur auf dem Livesystem - $aConfig['mirrorPackages'] = array( - 'puppet' => array( - 'type' => 'rsync', - 'runas' => 'www-data', // nur fuer commandline - 'target' => 'ladmin@calcium.iml.unibe.ch:/share/imldeployment', - ), - 'puppet.one' => array( - 'type' => 'rsync', - 'runas' => 'www-data', // nur fuer commandline - 'target' => 'copy-deployment@puppet.one.iml.unibe.ch:/var/shared/imldeployment', - ), - ); - break; - - default: - break; -} -if (!array_key_exists('tmpDir', $aConfig) || !$aConfig["tmpDir"]){ - $aConfig["tmpDir"] = (getenv("temp") ? getenv("temp") : "/var/tmp") . '/imldeployment'; -} -// ---------------------------------------------------------------------- -// TODO: include custom settings that were saved in the GUI -// ---------------------------------------------------------------------- - - -// ---------------------------------------------------------------------- -// generate some vars -// ---------------------------------------------------------------------- - -$aConfig = array_merge($aConfig, array( - 'appRootDir' => dirname(dirname(__FILE__)), - 'configDir' => dirname(__FILE__), - 'dataDir' => $aConfig['workDir'] . '/data', // to write data: ssh keys, projects, database - 'buildDir' => $aConfig['workDir'] . '/build', - 'buildDefaultsDir' => $aConfig['workDir'] . '/defaults', - 'packageDir' => $aConfig['workDir'] . '/packages', - 'archiveDir' => $aConfig['workDir'] . '/packages/_files', - )); diff --git a/config/lang/de.json b/config/lang/de.json index 8eb22a043914edb6fd9a4ddebe47a5d98b22064f..c2edacd85be49f6cb187af326eca678176db8f67 100644 --- a/config/lang/de.json +++ b/config/lang/de.json @@ -1,8 +1,10 @@ { + "menu": "Menü", "menu-brand": "Deployment GUI", "menu-overview": "Übersicht", "menu-projects": "Projekte", + "menu-checklang": "Vergleich Sprachtexte", "menu-settings": "Einstellungen", "menu-logs": "Logs", "menu-new-project": "Neues Projekt anlegen", @@ -20,6 +22,7 @@ "overview-label": "Übersicht", "overview-hint":"Alle Projekte und Versionen in den einzelnen Phasen", + "overview-hint-dblclick":"Doppelklick um das Projekt [%s] zu öffnen", "overview-filter":"Filter", "overview-filter-hint":"in der erweiterten Ansicht nur dieses Projekt anzeigen", "overview-simpleview":"zur einfachen Ansicht", @@ -218,14 +221,17 @@ "build-type": "Build Typ", "commitmessage": "Commit-Message", "change": "Wechseln", + "cleanup": "Cleanup", "contact": "Kontakt", "creating-directory": "Lege das Verzeichnis %s an.", "creating-file": "Lege Datei %s an.", "defaultbranch": "[default: Master oder Trunk]", + "defaults-all-phases": "Default-Werte (für alle Phasen)", + "delete": "Löschen", "deploy": "Deploy", "deploy-hint": "Deploy der Queue von Phase [%s]", "deploy-impossible": "Deploy der Queue von Phase [%s] ist nicht möglich.", - "deploy-settings": "Deployment - Einstellungen", + "deploy-settings": "Deployment-Einstellungen", "deploytimes": "Deployment Zeitpunkte", "deploytimes-immediately": "Ein Archiv in der Queue wird sofort ins Repo gestellt.", "deploymethod": "Deployment-Methode", @@ -237,6 +243,7 @@ "dir-archive": "Archiv-Verzeichnis", "dir-cache": "Cache-Verzeichnis", "dir-builds": "Build-Verzeichnis", + "edit": "Bearbeiten", "empty": "[leer]", "error": "FEHLER", "error-no-phase": "Es wurde keine Phase mitgegeben.", @@ -245,7 +252,19 @@ "fileprefix": "File Prefix", "fileprefix-label": "File-Prefix <span class=\"error\"><br>Nach dem ersten Build nicht mehr änderbar!</span>", "finished": "Beendet", + "foreman-error-missing-template": "In Foreman wurde das Templatefile [%s] definiert, aber dieses existiert nicht im Build.", + "foreman-error-no-replacement-for-id": "In Foreman wurde keine noch Ersetzung für [%s] definiert.", + "foreman-error-no-target": "In Foreman wurde keine Ziel-Datei definiert", + "foreman-error-no-replacement-in-templatefile": "Es wurde kein Platzhalter zum Ersetzen in diesem Template gefunden.", + "foreman-error-template-unknown": "Das Template wurde in Foreman nicht aufgenommen", + "foreman-error-replacement-unknown": "Das Replacement [%s] in Foreman existiert im Template nicht.", + "foreman-hostgroup": "Foreman Hostgruppe", + "foreman-hostgroup-override": "Foreman Hostgruppe (override)", + "foreman-hostgroup-id": "Foreman Hostgruppe (%s)", + "foreman-no-host": "Es wurde kein Host in Foreman konfiguriert. Es werden die Platzhalter in Templates erkannt, aber nicht deren Ziel und Ersetzungen.", + "foreman-targetfile": "Name der Zieldatei", "gotop": "Seitenanfang", + "host": "Host", "hosts": "Hosts", "hostname4puppet": "Hostname für Puppet Agent", "inactive": "inaktiv", @@ -253,11 +272,14 @@ "infos": "Infos", "login": "Anmelden", "logoff": "Abmelden", - "ok": "OK", + "messenger": "Benachrichtigungen", + "messenger-email": "Email-Adressen", + "messenger-slack": "Slack WebHook", "new-project": "neues Projekt", "new-project-hint": "ein neues Projekt anlegen", "no": "nein", "none": "kein(e)", + "ok": "OK", "packages": "Pakete", "phase": "Phase", "phase-details": "Details", @@ -266,15 +288,16 @@ "phase-targethosts": "Zielsysteme", "phases": "Phasen", "project": "Projekt", + "project-home": "Projekt Startseite", "projectdescription": "Kurzbeschreibung", "projectname": "Projektname", "projectmanager": "Projektleiter", "queue": "Queue", + "raw-data": "Raw data", "replacement-fields": "erkannte Platzhalter:", "replacement-fields-not-found": "Es wurden keine Platzhalter im Format <em>@replace["Name"]</em> im Template gefunden.", "replacement-targetfile": "Ziel-Datei", "replacements": "Ersetzungen mit Template-Dateien", - "replacements-info": "Beim Build-Prozess gefundene Templates werden aufgelistet und darin vorhandene Platzhalter erkannt. Die Felder sind ausschliesslich für die neue Infrastruktur relevant.", "repositoryinfos": "Quell-Repository", "repository-access-browser": "Browserzugriff auf das Repo", "repository-auth": "Authentifizierung/ Dateiname zum SSH-Private-Key", diff --git a/config/lang/en.json b/config/lang/en.json index dcdee60ff5d07827840426fed5627d61a42e8606..f3bcd4dac9f15d40b9102f0a0ccf5ca00380bb55 100644 --- a/config/lang/en.json +++ b/config/lang/en.json @@ -1,7 +1,9 @@ { + "menu": "Menu", "menu-brand": "IML Deployment GUI", "menu-overview": "back to overview", "menu-projects": "Projects", + "menu-checklang": "Compare language texts", "menu-settings": "Settings", "menu-logs": "Logs", "menu-new-project": "Create new project", @@ -19,6 +21,7 @@ "overview-label": "Overview", "overview-hint":"all projects and versions in all phases", + "overview-hint-dblclick":"Double click to open the project [%s]", "overview-filter":"Filter", "overview-filter-hint":"Show only this project in the extended view", "overview-simpleview":"Show simple view", @@ -221,13 +224,16 @@ "change": "Change", "commitmessage": "Commit message", "contact": "contact", + "cleanup": "Cleanup", "creating-directory": "Creating directory %s", "creating-file": "Creating file %s", "defaultbranch": "[default: master or trunk]", + "defaults-all-phases": "Default values (for all phases)", + "delete": "Delete", "deploy": "Deploy", "deploy-hint": "Deploy queue of phase [%s]", "deploy-impossible": "Deploy queue of phase [%s] is not possible.", - "deploy-settings": "Deployment - settings", + "deploy-settings": "Deployment settings", "deploymethod": "Deployment method", "deploymethod-none": "None. Do not trigger any action.", "deploymethod-puppet": "Run Puppet on target host(s)", @@ -239,6 +245,7 @@ "dir-archive": "Archive directory", "dir-cache": "Cache directory", "dir-builds": "Build directory", + "edit": "Edit", "empty": "[empty]", "error": "ERROR", "error-no-phase": "There no phase was given as parameter.", @@ -247,7 +254,19 @@ "fileprefix": "File prefix", "fileprefix-label": "File prefix <span class=\"error\"><br>It cannot be changed after the first build!</span>", "finished": "Fisnished", + "foreman-error-missing-template": "In Foreman the templatefile [%s] was defined but it does not exist in the build.", + "foreman-error-no-replacement-for-id": "There is no replacement for [%s] in Foreman.", + "foreman-error-no-target": "No target file was set in Foreman", + "foreman-error-no-replacement-in-templatefile": "No placeholder for a replacement was found in this template.", + "foreman-error-template-unknown": "The template was not added in Foreman.", + "foreman-error-replacement-unknown": "The replacement [%s] from Foreman has no plceholder in the template file.", + "foreman-hostgroup": "Foreman hostgroup", + "foreman-hostgroup-override": "Foreman Hostgroup (override)", + "foreman-hostgroup-id": "Foreman Hostgroup (%s)", + "foreman-no-host": "No host was configured in Foreman.", + "foreman-targetfile": "Target file", "gotop": "top", + "host": "Host", "hosts": "Hosts", "hostname4puppet": "Hostname to start puppet agent", "inactive": "inactive", @@ -255,11 +274,14 @@ "infos": "Infos", "login": "Login", "logoff": "Logoff", - "ok": "OK", + "messenger": "Messenger/ notifications", + "messenger-email": "Email", + "messenger-slack": "Slack", "new-project": "new project", "new-project-hint": "create a new project", "no": "no", "none": "none", + "ok": "OK", "packages": "packages", "phase": "Phase", "phase-details": "Details", @@ -268,15 +290,16 @@ "phase-targethosts": "Target hosts", "phases": "Phases", "project": "Project", + "project-home": "Projekt home", "projectdescription": "Short description", "projectname": "Project name", "projectmanager": "Projekt manager", "queue": "Queue", + "raw-data": "Raw data", "replacement-fields": "Detected placeholders:", "replacement-fields-not-found": "No placeholder in the syntax <em>@replace["name"]</em> were found in the template.", "replacement-targetfile": "Target file", "replacements": "Replacements in template files", - "replacements-info": "List of detected templates with its placeholders.", "repositoryinfos": "Sourcecode Repository", "repository-access-browser": "Browser access to sources", "repository-auth": "Authentication/ filename of ssh private key", diff --git a/hooks/templates/inc_projects_config.php.erb b/hooks/templates/inc_projects_config.php.erb index 507a21350006779e1636d753f37d239d6ac3d91d..a91d697f841da2bd8827a5a945b71c9abed34dcd 100644 --- a/hooks/templates/inc_projects_config.php.erb +++ b/hooks/templates/inc_projects_config.php.erb @@ -74,6 +74,13 @@ $aConfig = array( 'debugLevel' => 0, ) ), + 'foreman' => array( + 'api'=>'<%= @replace["foreman-url"] %>', // with ending "/" + 'user'=>'<%= @replace["foreman-user"] %>', + 'password'=>'<%= @replace["foreman-password"] %>', + 'ignore-ssl-error'=><%= @replace["ignore-ssl-error"] %>, + // 'varname-replace'=>'ci-replacement', + ), // where to store project data 'projects' => array( 'json' => array( @@ -83,6 +90,8 @@ $aConfig = array( 'active' => false, ), ), + // notifications to messengers ... + 'messenger'=>array(<%= @replace["messenger"] %>), ); // ---------------------------------------------------------------------- diff --git a/public_html/deployment/classes/config-replacement.class.php b/public_html/deployment/classes/config-replacement.class.php index 5415757c16d5b8a3c6ab164bdba47f0087177d62..bab9519431cd67f9044b0c0aaad900aa4ab3c183 100644 --- a/public_html/deployment/classes/config-replacement.class.php +++ b/public_html/deployment/classes/config-replacement.class.php @@ -18,6 +18,8 @@ class configreplacement { * @var type */ protected $_oProject = false; + protected $_sPhase = false; + protected $_aForemanReplacements = false; /** @@ -35,18 +37,17 @@ class configreplacement { /** * get an array with a flat list of all templatefiles of a build - * @param string $sPhase name of phase * @return boolean|array */ - public function getTemplatefiles($sPhase=false){ - $aReturn = array(); - if (!$sPhase){ - $sPhase=$this->_oProject->getNextPhase(false); + public function getTemplatefiles(){ + if (!$this->_sPhase){ + return false; } + $aReturn = array(); - $aBuildfiles=$this->_oProject->getBuildfilesByPlace($sPhase, 'onhold'); + $aBuildfiles=$this->_oProject->getBuildfilesByPlace($this->_sPhase, 'onhold'); if (!$aBuildfiles){ - $aBuildfiles=$this->_oProject->getBuildfilesByPlace($sPhase, 'ready2install'); + $aBuildfiles=$this->_oProject->getBuildfilesByPlace($this->_sPhase, 'ready2install'); } if (!$aBuildfiles || !array_key_exists('types', $aBuildfiles) || !array_key_exists('templates', $aBuildfiles['types'])){ @@ -60,11 +61,13 @@ class configreplacement { /** * get an array with all template files (basename) and its replacement fields - * @param string $sPhase name of phase * @return array */ - public function getReplacements($sPhase=false){ - $aFiles=$this->getTemplatefiles($sPhase); + public function getReplacements(){ + if (!$this->_sPhase){ + return false; + } + $aFiles=$this->getTemplatefiles($this->_sPhase); if (!$aFiles){ return false; } @@ -80,11 +83,256 @@ class configreplacement { return $aReturn; } + /** + * get effective hostgroup id of the current phase + * @return integer + */ + public function getForemanHostgroup(){ + $aPrjConfig=$this->_oProject->getConfig(); + $iForemanHostgroupDefault = (int) $aPrjConfig['deploy']['foreman']['hostgroup']; + $iForemanHostgroup = (int) $aPrjConfig['phases'][$this->_sPhase]['foreman-hostgroup']; + return ($iForemanHostgroup===OPTION_DEFAULT) ? $iForemanHostgroupDefault : $iForemanHostgroup; + } + + /** + * get replacement definitions from foreman + * @global type $aConfig + * @return boolean + */ + protected function _getForemanReplacement(){ + global $aConfig; + if (!$this->_sPhase){ + return false; + } + + // abort if no foreman connection was configured + if (!array_key_exists('foreman', $aConfig)) { + return false; + } + + // return already cached result + if (array_key_exists($this->_sPhase, $this->_aForemanReplacements)){ + return $this->_aForemanReplacements[$this->_sPhase]; + } + + // rebuilt + $this->_aForemanReplacements[$this->_sPhase]=false; + $iEffectiveHostgroup=$this->getForemanHostgroup(); + + // abort if no hostgroup was set + if($iEffectiveHostgroup<=0){ + return false; + } + + require_once 'foremanapi.class.php'; + $oForeman=new ForemanApi($aConfig['foreman']); + + // get a host of this phase + $aHosts=$oForeman->read(array( + 'request' => array( + array('hosts' ), + ), + 'filter' => array( + 'hostgroup_id' => $iEffectiveHostgroup, + ), + 'response' => array('name', 'id'), + )); + + $sHost=''; + $aHostsOfPhase=array(); + + // HACK: phases are part of the hostname .. but not "live" ... and special handling for demo + $aPrjConfig=$this->_oProject->getConfig(); + $bIsDemo=(array_key_exists('fileprefix', $aPrjConfig) + && !strpos($aPrjConfig['fileprefix'], 'demo')===false); + $sPhase=$bIsDemo ? 'demo' : $this->_sPhase; + foreach($aHosts as $aName){ + $sName=$aName['name']; + if (($sPhase==='live' && + ( + strpos($sName, 'preview')===false + && strpos($sName, 'stage')===false + && strpos($sName, 'demo')===false + ) + ) + || (strpos($sName, $sPhase)) + ){ + $sHost=$sHost ? $sHost : $sName; + $aHostsOfPhase[]=$sName; + } + } + + // get created yaml of this host + $sUrl=$aConfig['foreman']['api']."hosts/$sHost/externalNodes?name=$sHost"; + $aData=$oForeman->makeRequest(array( + 'url'=>$sUrl, + 'method'=>'GET', + )); + + // HACK: minify YAML of the host ... because it is too large for SPYC parser + $sPart="---\n"; + $bCopy=false; + foreach(explode("\n", $aData['body']) as $sLine){ + if($bCopy){ + if (strpos($sLine, ' ')===false){ + $bCopy=false; + } else { + // remove leading spaces and html entities... + $sNewLine= html_entity_decode(preg_replace('/^\ \ \ \ /', '', $sLine, 1)); + $sNewLine=str_replace(''{', "'{", $sNewLine); + $sNewLine=str_replace('}'', "}'", $sNewLine); + + // fix json errors + $sNewLine=str_replace(', }', " }", $sNewLine); + $sNewLine=str_replace(',}', "}", $sNewLine); + $sPart.=$sNewLine."\n"; + } + } + if($sLine===' iml-deployment-config:'){ + $bCopy=true; + } + // echo 'DEBUG: '.($bCopy ? 'COPY':'SKIP').$sLine.'<br>'; + } + + require_once __DIR__.'./../../vendor/spyc/spyc.php'; + $aYaml=spyc_load($sPart); + + $aReturn=array(); + foreach ($aYaml as $aProject){ + foreach ($aProject as $sFile=>$aParams){ + $aReturn[$sFile]=array(); + if (array_key_exists('target', $aParams)){ + $aReturn[$sFile]['target']=$aParams['target']; + } + if (array_key_exists('replace', $aParams)){ + $aReplace=json_decode($aParams['replace'], 1); + $aReturn[$sFile]['replace_source']=$aReplace; + foreach ($aReplace as $sVarname=>$value){ + if (is_array($value) && array_key_exists('_'.$this->_sPhase.'_', $value)){ + $value=$value['_'.$this->_sPhase.'_']; + } + $aReturn[$sFile]['replace'][$sVarname]=$value; + } + } + } + } + + $this->_aForemanReplacements[$this->_sPhase]=array( + 'rules'=>$aReturn, + 'host'=>$sHost, + 'yaml'=>$sPart, + 'hostgroup'=>$iEffectiveHostgroup, + 'hostsall'=>$aHosts, + 'hostsphase'=>$aHostsOfPhase, + ); + return $this->_aForemanReplacements[$this->_sPhase]; + } + + /** + * get foreman base url ... if foreman was activated in the setup + * + * @global array $aConfig ci config + * @return string + */ + private function _getForemanBaseUrl(){ + global $aConfig; + if (!array_key_exists('foreman', $aConfig) + || !array_key_exists('api', $aConfig['foreman']) + || !$aConfig['foreman']['api'] + ){ + return false; + } + return $aConfig['foreman']['api']; + } + + /** + * get html code for links to edit each host of the current phase in foreman + * + * @return boolean + */ + public function getForemanlink2Host(){ + $sForemanurl=$this->_getForemanBaseUrl(); + if (!$sForemanurl){ + return false; + } + $aTmp=$this->_getForemanReplacement(); + if (!array_key_exists('hostsphase', $aTmp)){ + return false; + } + require_once 'htmlguielements.class.php'; + $oHtml=new htmlguielements(); + $sReturn=''; + foreach ($aTmp['hostsphase'] as $sHost){ + $sReturn.=$oHtml->getLinkButton(array( + 'href'=>$sForemanurl.'hosts/'.$aTmp['host'], + 'target'=>'_foreman', + 'title'=>t('edit'), + 'icon'=>'host', + 'label'=>t('host').' '.$aTmp['host'], + )).' '; + } + return $sReturn; + } + + /** + * get html code for a link to edit hostgroup in foreman + * + * @return boolean + */ + public function getForemanlink2Hostgroup(){ + $iEffectiveHostgroup=$this->getForemanHostgroup(); + if($iEffectiveHostgroup<=0){ + return false; + } + $sForemanurl=$this->_getForemanBaseUrl(); + if (!$sForemanurl){ + return false; + } + require_once 'htmlguielements.class.php'; + $oHtml=new htmlguielements(); + return $oHtml->getLinkButton(array( + 'href'=>$sForemanurl.'hostgroups/'.$iEffectiveHostgroup.'/edit', + 'target'=>'_foreman', + 'title'=>t('edit'), + 'icon'=>'hostgroup', + 'label'=>sprintf(t('foreman-hostgroup-id'), $iEffectiveHostgroup), + )); + + } + + /** + * get replacements in foreman + * @return type + */ + public function getForemanReplacements(){ + return $this->_getForemanReplacement(); + } + /** * switch to a project * @param type $sProject */ - public function setProject($sProject){ + public function setProject($sProject, $sPhase=false){ $this->_oProject = new project($sProject); + $this->_aForemanReplacements=false; + $this->setPhase($sPhase); + return true; + } + + /** + * set a phase of a project + * @param string $sPhase name of valid phase + * @return boolean + */ + public function setPhase($sPhase=false){ + $this->_sPhase=false; + if (!$sPhase){ + $sPhase=$this->_oProject->getNextPhase(false); + } + if (!$sPhase || !$this->_oProject->isActivePhase($sPhase)) { + return false; + } + $this->_sPhase=$sPhase; + return true; } } diff --git a/public_html/deployment/classes/foremanapi.class.php b/public_html/deployment/classes/foremanapi.class.php new file mode 100644 index 0000000000000000000000000000000000000000..31fd9ddc8204005a40258e695b49ba405409dd24 --- /dev/null +++ b/public_html/deployment/classes/foremanapi.class.php @@ -0,0 +1,516 @@ +<?php +/** + * ForemanApi + * + * foreman access to API + * + * @example + * in project class + * $oForeman=new ForemanApi($this->_aConfig['foreman']); + * + * // enable debugging + * $oForeman->setDebug(1); + * + * // self check + * $oForeman->selfcheck(); die(__FUNCTION__); + * + * // read operating systems and get id and title only + * $aForemanHostgroups=$oForeman->read(array( + * 'request'=>array( + * array('operatingsystems'), + * ), + * 'response'=>array( + * 'id','title' + * ), + * )); + * + * // read details for operating systems #4 + * $aForemanHostgroups=$oForeman->read(array( + * 'request'=>array( + * array('operatingsystems', 4), + * ), + * )); + * + * + * $aOptions ... can contain optional subkeys + * - request + * [] list of array(keyword [,id]) + * - filter (array) + * - search (string) + * - page (string) + * - per_page (string) + * - response (array) + * - list of keys, i.e. array('id', 'title') + + * @author hahn + */ +class ForemanApi { + + protected $_aCfg=array(); + protected $_bDebug = false; + + protected $_aAllowedUrls=array( + 'api'=>array( + ''=>array(), + 'architectures'=>array(), + 'audits'=>array('methods'=>array('GET')), + 'auth_source_ldaps'=>array(), + 'bookmarks'=>array(), + 'common_parameters'=>array(), + 'compliance'=>array(), + 'compute_attributes'=>array(), + 'compute_profiles'=>array(), + 'compute_resources'=>array(), + 'config_groups'=>array(), + 'config_reports'=>array(), + 'config_templates'=>array(), + 'dashboard'=>array('methods'=>array('GET')), + 'domains'=>array(), + 'environments'=>array(), + 'fact_values'=>array(), + 'filters'=>array(), + 'hosts'=>array(), + 'hostgroups'=>array(), + 'job_invocations'=>array(), + 'job_templates'=>array(), + 'locations'=>array(), + 'mail_notifications'=>array(), + 'media'=>array(), + 'models'=>array(), + 'operatingsystems'=>array('methods'=>array('GET')), + 'orchestration'=>array(), + 'organizations'=>array(), + 'permissions'=>array(), + 'plugins'=>array(), + 'provisioning_templates'=>array(), + 'ptables'=>array(), + 'puppetclasses'=>array(), + 'realms'=>array(), + 'remote_execution_features'=>array(), + 'reports'=>array(), + 'roles'=>array(), + 'settings'=>array(), + 'smart_class_parameters'=>array(), + 'smart_proxies'=>array(), + 'smart_variables'=>array(), + 'statistics'=>array('methods'=>array('GET')), + 'status'=>array('methods'=>array('GET')), + 'subnets'=>array(), + 'template_combinations'=>array(), + 'template_kinds'=>array('methods'=>array('GET')), + 'templates'=>array(), + 'usergroups'=>array(), + 'users'=>array(), + // ... + ), + 'api/v2'=>array( + 'discovered_hosts'=>array(), + 'discovery_rules'=>array(), + ), + 'foreman_tasks/api'=>array( + 'recurring_logics'=>array(), + 'tasks'=>array(), + ), + ); + + + protected $_aRequest=array(); + + + // ---------------------------------------------------------------------- + // constructor + // ---------------------------------------------------------------------- + + + public function __construct($aCfg) { + if(!is_array($aCfg) || !count($aCfg) || !array_key_exists('api', $aCfg)){ + die("ERROR: class ".__CLASS__." must be initialized with an array containing api config for foreman."); + return false; + } + $this->_aCfg=$aCfg; + + return true; + } + + // ---------------------------------------------------------------------- + // private functions + // ---------------------------------------------------------------------- + /** + * add a log messsage + * @global object $oLog + * @param string $sMessage messeage text + * @param string $sLevel warnlevel of the given message + * @return bool + */ + protected function log($sMessage, $sLevel = "info") { + global $oCLog; + return $oCLog->add(basename(__FILE__) . " class " . __CLASS__ . " - " . $sMessage, $sLevel); + } + + /** + * search url prefix in $this->_aAllowedUrls by given key + * @param type $sFunction + * @return type + */ + protected function _guessPrefixUrl($sFunction=false){ + $sReturn=''; + /* + if (!$sFunction){ + $sFunction=$this->_aRequest['request'][0][0]; + } + * + */ + foreach($this->_aAllowedUrls as $sPrefix=>$aUrls){ + foreach(array_keys($aUrls) as $sKeyword){ + if ($sFunction==$sKeyword){ + $sReturn=$sPrefix; + break; + } + } + } + return $sReturn; + } + + /** + * generate an url for foreman API request based on option keys + * @return string + */ + protected function _generateUrl(){ + if(!array_key_exists('request', $this->_aRequest)){ + die('ERROR: missing key [request]'); + } + $sReturn=$this->_aCfg['api']; + + $sPrefix=$this->_guessPrefixUrl(); + $sReturn.=$sPrefix.'/'; + + foreach($this->_aRequest['request'] as $aReqItem){ + if (!array_key_exists($aReqItem[0], $this->_aAllowedUrls[$sPrefix])){ + echo 'WARNING: wrong item: [' . $aReqItem[0]."]<br>\n"; + } + $sReturn.=$aReqItem[0] ? $aReqItem[0].'/' : ''; + if(count($aReqItem)>1){ + $sReturn.=(int)$aReqItem[1].'/'; + } + } + return $sReturn; + } + + /** + * add parameter for search and paging in an foreman API URL + * + * @return string + */ + protected function _generateParams(){ + if (!array_key_exists('filter', $this->_aRequest)){ + return ''; + } + $sReturn='?'; + + foreach ($this->_aRequest['filter'] as $sKey=>$value){ + $sReturn.='&'.$sKey.'='.urlencode($value); + } + return $sReturn; + } + + /** + * make an http get request and return the response body + * it is called by _makeRequest + * $aRequest contains subkeys + * - url + * - method; one of GET|POST|PUT|DELETE + * - postdata; for POST only + * + * @param array $aRequest arrayurl for Foreman API + * @return string + */ + protected function _httpCall($aRequest=false, $iTimeout = 15) { + if ($aRequest){ + $this->_aRequest=$aRequest; + } + $this->log(__FUNCTION__ . " start <pre>".print_r($this->_aRequest,1)."</pre>"); + if (!function_exists("curl_init")) { + die("ERROR: PHP CURL module is not installed."); + } + + $sApiUser=array_key_exists('user', $this->_aCfg) ? $this->_aCfg['user'] : false; + $sApiPassword=array_key_exists('password', $this->_aCfg) ? $this->_aCfg['password'] : false; + + $sFullUrl=$sApiUrl.$this->_aRequest['url']; + $this->log(__FUNCTION__ . " ".$this->_aRequest['method']." " . $this->_aRequest['url']); + $ch = curl_init($this->_aRequest['url']); + + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->_aRequest['method']); + if ($this->_aRequest['method']==='POST'){ + curl_setopt($ch, CURLOPT_POSTFIELDS, $this->_aRequest['postdata']); + } + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + if (array_key_exists('ignore-ssl-error', $this->_aCfg) && $this->_aCfg['ignore-ssl-error']){ + $this->log(__FUNCTION__ . " WARNING: SSL errors will be ignored by config.", 'warning'); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + } + curl_setopt($ch, CURLOPT_TIMEOUT, $iTimeout); + curl_setopt($ch, CURLOPT_USERAGENT, 'IML Deployment GUI :: ' . __CLASS__); + if ($sApiUser){ + curl_setopt($ch, CURLOPT_USERPWD, $sApiUser.":".$sApiPassword); + } + + $res = curl_exec($ch); + $aReturn=array('info'=>curl_getinfo($ch), 'error'=>curl_error($ch)); + curl_close($ch); + $this->log(__FUNCTION__ . " status ".$aReturn['info']['http_code'].' '.$this->_aRequest['method']." $sFullUrl"); + + $sHeader=substr($res, 0, $aReturn['info']['header_size']); + $aReturn['header']=explode("\n", $sHeader); + $aReturn['body']=str_replace($sHeader, "", $res); + + return $aReturn; + } + + /** + * write debug infos if enabled + * @param string $sMessage + * @return boolean + */ + protected function _writeDebug($sMessage){ + if ($this->_bDebug){ + echo "DEBUG :: ".__CLASS__." :: $sMessage<br>\n"; + } + return true; + } + + // ---------------------------------------------------------------------- + // public functions :: low level + // ---------------------------------------------------------------------- + + /** + * make an http(s) request to foreman and scan result for http code and + * content in response json; method returns an array with subkeys + * - info: curl info array + * - error: curl error message + * - header: http response headers + * - body: http response body + * - _json: parsed json data from response body + * - _OK: flag if result is OK and complete + * - _status: info + * + * * $aRequest contains subkeys + * - function --> to extract method and generate url + * - method; one of GET|POST|PUT|DELETE + * - postdata; for POST only + + * @param array $aRequest arrayurl for Foreman API + * @return array + */ + public function makeRequest($aRequest=false) { + if ($aRequest){ + $this->_aRequest=$aRequest; + } + $sStatus='unknown'; + $bOk=false; + + + // prevent missing data because of paging + if ($this->_aRequest['method']==='GET' && !array_key_exists('per_page', $this->_aRequest['filter'])){ + $this->_aRequest['filter']['per_page']=1000; + } + // TODO check postdata + if ($this->_aRequest['method']==='POST' && (!array_key_exists('postdata',$this->_aRequest) || !count($this->_aRequest['postdata']))){ + die("ERROR in ".__CLASS__."::".__FUNCTION__.": missing data to make a POST request"); + } + if (!array_key_exists('url', $this->_aRequest)){ + $this->_aRequest['url']=$this->_generateUrl().$this->_generateParams(); + } + + // ----- request + $this->_writeDebug(__FUNCTION__ . ' start request <pre>'.print_r($this->_aRequest,1).'</pre>'); + $aReturn=$this->_httpCall(); + + // ----- check result + // check status + $iStatuscode=$aReturn['info']['http_code']; + if ($iStatuscode===0){ + $sStatus='wrong host'; + } + if ($iStatuscode>=200 && $iStatuscode<400){ + $sStatus='OK'; + $bOk=true; + } + if ($iStatuscode>=400 && $iStatuscode<500){ + $sStatus='error'; + } + if ($iStatuscode===404){ + $sStatus='wrong url'; + } + + // check result json + if($bOk){ + $aJson=json_decode($aReturn['body'], 1); + if (is_array($aJson)){ + if (array_key_exists('total', $aJson) && $aJson['total'] > $aJson['per_page']){ + $bOk=false; + $sStatus='Http OK, but incomplete results (paging)'; + } + $aReturn['_json']=$aJson; + } else { + $bOk=false; + $sStatus='Http OK, but wrong response'; + } + } + $aReturn['_OK']=$bOk; + $aReturn['_status']=$sStatus; + $this->_writeDebug(__FUNCTION__ . ' result of request <pre>'.print_r($aReturn,1).'</pre>'); + + return $aReturn; + } + + /** + * filter output for the response based on values $this->_aRequest['response'] + * @param array $aData response of $this->makeRequest(); + * @return array + */ + protected function _filterOutput($aData){ + if(!array_key_exists('response', $this->_aRequest)){ + return $aData; + } + $bIsList=array_key_exists('results', $aData['_json']); + $aTmp=$bIsList ? $aData['_json']['results'] : array($aData['_json']); + if(!count($aTmp)){ + return array(); + } + $aReturn=array(); + foreach($aTmp as $aItem){ + $aReturnitem=array(); + foreach($this->_aRequest['response'] as $sKey){ + if (array_key_exists($sKey, $aItem)){ + $aReturnitem[$sKey]=$aItem[$sKey]; + } + } + $aReturn[] = $aReturnitem; + } + return ($bIsList==1) + ? $aReturn + : (count($aReturn) ? $aReturn[0] : array()); + } + + + // ---------------------------------------------------------------------- + // public foreman functions + // ---------------------------------------------------------------------- + + /** + * enable/ disable debugging + * @param boolean $bNewDebugFlag new value; true|false + * @return boolean + */ + public function setDebug($bNewDebugFlag){ + return $this->_bDebug=$bNewDebugFlag; + } + + /** + * check for missing config entries + * @return type + */ + public function selfcheck() { + $sOut=''; + $sWarning=''; + $sOut.="<h1>selfcheck</h1>"; + $aApi=$this->read(array('request'=>array(array('')))); + if($aApi['_OK']){ + foreach($aApi['_json']['links'] as $sKey=>$aCalls){ + $sOut.="<h2>$sKey</h2><ul>"; + foreach ($aCalls as $sLabel=>$sUrl){ + $sOut.="<li>$sLabel .. $sUrl "; + $aTmp=preg_split('#\/#', $sUrl); + $sDir2=count($aTmp)>2 ? $aTmp[2] : '??'; + $sDir3=count($aTmp)>3 ? $aTmp[3] : '??'; + $sOut.="..... " + . ($this->_guessPrefixUrl($sDir2).$this->_guessPrefixUrl($sDir3) + ?'<span style="background:#cfc">OK</span>' + :'<span style="background:#fcc">miss</span>' + ) . ' ' . $sDir2.', '.$sDir3 . "</li>\n"; + if (!($this->_guessPrefixUrl($sDir2).$this->_guessPrefixUrl($sDir3))){ + $sWarning.="<li>$sKey - $sLabel - $sUrl</li>"; + } + } + $sOut.="</ul>"; + } + } else { + $sOut.='ERROR: unable to connect to foreman or missing permissions.<br>'; + } + if ($sWarning){ + echo 'WARNINGS:<ol>'.$sWarning.'</ol>'; + } + echo $sOut; + return true; + } + + // ---------------------------------------------------------------------- + // public foreman API CRUD functions + // ---------------------------------------------------------------------- + + /** + * TODO: create + * @param array $aOptions + */ + public function create($aOptions){ + /* + $this->_aRequest=$aOptions; + $this->_aRequest['method']='POST'; + return $this->makeRequest(); + */ + } + /** + * GETTER + * $aOptions ... can contain optional subkeys + * - request + * [] list of array(keyword [,id]) + * - filter (array) + * - search (string) + * - page (string) + * - per_page (string) + * - any attribute in the return resultset + * - response (array) + * - list of keys, i.e. array('id', 'title') + * + * @param array $aOptions + * @return array + */ + public function read($aOptions){ + $this->_aRequest=$aOptions; + $this->_aRequest['method']='GET'; + $aData=$this->makeRequest(); + if (!$aData['_OK']){ + return false; + } + return $this->_filterOutput($aData); + } + + /** + * TODO + * @param type $aOptions + */ + public function update($aOptions){ + /* + $this->_aRequest=$aOptions; + $this->_aRequest['method']='PUT'; + return $this->makeRequest(); + */ + } + + /** + * TODO + * @param type $aOptions + */ + public function delete($aOptions){ + /* + $this->_aRequest=$aOptions; + $this->_aRequest['method']='DELETE'; + return $this->makeRequest(); + */ + } + +} diff --git a/public_html/deployment/classes/formgen.class.php b/public_html/deployment/classes/formgen.class.php index 61d841a64b645e9d77f595538fb87d11951f5ac4..282ae6d5de3656f2632b0f85600b633386b10f5b 100644 --- a/public_html/deployment/classes/formgen.class.php +++ b/public_html/deployment/classes/formgen.class.php @@ -215,7 +215,8 @@ class formgen { case "select": // HINWEIS optgroups werden nicht unterstuezt - nur einfache Listen $this->_checkReqiredKeys($elementData, array("name")); - $sFormElement.='<select id="' . $sId . '" class="form-control" '; + $sDivClass=(array_key_exists("inline", $elementData) && $elementData["inline"])?"form-group":"col-sm-10"; + $sFormElement.='<div class="'.$sDivClass.'"><select id="' . $sId . '" class="form-control" '; $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,name,onchange"), $elementData); $sFormElement.=">\n"; foreach ($elementData["options"] as $idOption => $aOptionData) { @@ -225,11 +226,11 @@ class formgen { $sFormElement.=$this->_addHtmlAtrributes(explode(",", "$sDefaultAttributes,selected"), $aOptionData); $sFormElement.='>' . $aOptionData["label"] . '</option>' . "\n"; } - $sFormElement.="</select>\n"; + $sFormElement.="</select></div>\n"; if ($sLabelText) { // $sLabelElement.='<span class="help-block">' . $sLabelText . '</span>' . "\n"; - $sLabelClass=(array_key_exists("inline", $elementData) && $elementData["inline"])?"":"control-label"; + $sLabelClass=(array_key_exists("inline", $elementData) && $elementData["inline"])?"":"col-sm-2"; $sLabelElement = $this->_addLabel($sLabelText, $sId, $sLabelClass); } diff --git a/public_html/deployment/classes/htmlguielements.class.php b/public_html/deployment/classes/htmlguielements.class.php index 8c9e7afe99eee9f33b78b2b3a41207d762c47acf..3264208cb40bede5e2322743a5bff86d4249da0c 100644 --- a/public_html/deployment/classes/htmlguielements.class.php +++ b/public_html/deployment/classes/htmlguielements.class.php @@ -43,22 +43,61 @@ class htmlguielements{ 'ok'=>array('class'=>'btn-primary', 'icon'=>'fa-check'), // deploy actions and buttons - 'accept'=>array('class'=>'', 'icon'=>'glyphicon-forward'), - 'build'=>array('class'=>'', 'icon'=>'glyphicon-equalizer'), - 'cleanup'=>array('class'=>'', 'icon'=>'glyphicon-chevron-right'), + 'accept'=>array('class'=>''), + 'build'=>array('class'=>''), + 'cleanup'=>array('class'=>''), 'deploy'=>array('class'=>'', 'icon'=>'glyphicon-forward'), 'new'=>array('class'=>'', 'icon'=>'glyphicon-star-empty'), - 'overview'=>array('class'=>'', 'icon'=>'glyphicon-book'), + 'overview'=>array('class'=>''), 'phase'=>array('class'=>'', 'icon'=>'glyphicon-chevron-right'), 'rollback'=>array('class'=>'', 'icon'=>'glyphicon-forward'), - 'setup'=>array('class'=>'', 'icon'=>'glyphicon-cog'), + 'setup'=>array('class'=>''), ), 'icons'=>array( + + 'menu'=>'fa-chevron-right', + 'overview'=>'fa-list', + 'project'=>'fa-book', + 'project-home'=>'fa-home', + 'projects'=>'fa-folder-o', + 'actions'=>'fa-check', + + 'actionlog'=>'fa-list-ul', + 'accept'=>'glyphicon-forward', + 'build'=>'glyphicon-equalizer', + 'cleanup'=>'fa-trash', + 'checklang'=>'fa-check', + 'delete'=>'fa-close', + 'deploy'=>'glyphicon-forward', + 'filter'=>'glyphicon-filter', + 'new'=>'glyphicon-star-empty', + 'phase'=>'glyphicon-chevron-right', + 'rollback'=>'glyphicon-forward', + 'setup'=>'fa-cog', + 'login'=>'fa-lock', + 'user'=>'fa-user', + + 'workflow'=>'fa-angle-double-right', + 'repository'=>'fa-database', + 'phase'=>'fa-flag', + 'package'=>'fa-cubes', + 'version'=>'fa-tag', + 'list'=>'fa-list', + 'raw-data'=>'fa-file-o', + + 'back'=>'fa-chevron-left', + 'branch'=>'glyphicon-bookmark', 'calendar'=>'glyphicon-calendar', 'comment'=>'glyphicon-comment', 'revision'=>'glyphicon-tag', + + 'host'=>'fa-hdd-o', + 'hostgroup'=>'fa-sitemap', + 'templatefile'=>'fa-file-code-o', + 'targetfile'=>'fa-file-o', + 'replace'=>'fa-random', ), ); @@ -94,6 +133,20 @@ class htmlguielements{ return ($sValue ? ' '.$sAttribute.'="'.$sValue.'"' : '' ); } + /** + * get html attributes as string from all keys of given hash + * + * @param array $aItem + * @return string + */ + public function addAllAttributes($aItem){ + $sReturn=''; + foreach (array_keys($aItem) as $sKey){ + $sReturn.=$this->addAttributeFromKey($sKey, $aItem); + } + return $sReturn; + } + // ---------------------------------------------------------------------- // low level // ---------------------------------------------------------------------- @@ -113,7 +166,7 @@ class htmlguielements{ : ( strpos($sLabel, 'fa-')===0 ? 'fa' : '') ); if(!$sPrefix){ - return ''; + return $this->getIconByType($sLabel); } return '<i'.$this->addAttribute('class', ($sPrefix ? $sPrefix . ' ' : '').$sLabel).'></i> '; } @@ -149,9 +202,7 @@ class htmlguielements{ } $sReturn='<a'.$sHref; - foreach (array_keys($aItem) as $sKey){ - $sReturn.=$this->addAttributeFromKey($sKey, $aItem); - } + $sReturn.=$this->addAllAttributes($aItem); $sReturn.='>' .$sLabel .'</a>'; @@ -159,7 +210,9 @@ class htmlguielements{ } /** - * add default css classes and colors based on $aItem['type'] + * add default css classes and colors based on $aItem['type'] and the + * local default settings in $this->aCfg + * * @param array $aItem * @return array */ @@ -168,7 +221,14 @@ class htmlguielements{ if (array_key_exists($aItem['type'], $this->aCfg['buttons'])){ $sClass=$this->aCfg['buttons'][$aItem['type']]['class']; $aReturn['class'].=$sClass ? ' '.$sClass : ''; - $aReturn['icon']=$aReturn['icon'] ? $aReturn['icon'] : $this->aCfg['buttons'][$aItem['type']]['icon']; + + // icon priority: + // given in param --> icon in button config --> icon in icon config + $aReturn['icon']=$aReturn['icon'] ? $aReturn['icon'] : + ( $this->aCfg['buttons'][$aItem['type']]['icon'] + ? $this->aCfg['buttons'][$aItem['type']]['icon'] + : ( array_key_exists($aItem['type'], $this->aCfg['icons']) ? $this->aCfg['icons'][$aItem['type']] : '') + ); } return $aReturn; } @@ -269,5 +329,35 @@ class htmlguielements{ . '<div class="tab-content">'.$sContent.'</div>' . '</div>'; } + + /** + * get html code for a table + * + * @param array $aTabledata array with subkeys "header" and "body" + * @return string + */ + public function getTable($aTabledata) { + $sTHead=''; + $sTBody=''; + if (array_key_exists('body', $aTabledata)){ + foreach ($aTabledata['body'] as $aRow){ + $sTBody.='<tr>'; + foreach ($aRow as $sItem){ + $sTBody.='<td>'.$sItem.'</td>'; + } + $sTBody.='</tr>'; + } + } + if (array_key_exists('header', $aTabledata)){ + foreach ($aTabledata['header'] as $sItem){ + $sTHead.='<th>'.$sItem.'</th>'; + } + } + return '<table class="table" style="width: auto;">' + .($sTHead ? '<thead>'.$sTHead.'</thead>' : '') + .($sTBody ? '<tbody>'.$sTBody.'</tbody>' : '') + .'</table>' + ; + } } diff --git a/public_html/deployment/classes/messenger.class.php b/public_html/deployment/classes/messenger.class.php new file mode 100644 index 0000000000000000000000000000000000000000..91eda6c5775410c95b63272ddf72ea36033a82d1 --- /dev/null +++ b/public_html/deployment/classes/messenger.class.php @@ -0,0 +1,87 @@ +<?php + +/** + * send messenger notifications + * + * @author hahn + */ +class messenger { + + /** + * config array for messengers + * @var type + */ + protected $_aCfg = array(); + + /** + * content of messagetext to send + * @var string + */ + protected $_sMessage = ''; + + /** + * + * @example + * $oMessenger = new messenger(array( + * 'slack'=>array( + * 'incomingurl'=>[WebHook url], + * 'user'=>[visible username in slack], + * 'icon'=>[Slack Icon], // hm, does not seem to work + * ), + * 'email'=>array( + * 'from'=>[senders e-mail] + * 'to'=>[email-address(es)] // multiple emails must be delimited with ";" + * ) + * )); + * @param array $aCfg config array for notification targets + * @return boolean + */ + public function __construct($aCfg) { + $this->_aCfg = $aCfg; + return true; + } + + /** + * send an email if _aCfg['email'] exists + */ + private function _sendEmail(){ + if (array_key_exists('email', $this->_aCfg) && array_key_exists('to', $this->_aCfg['email']) + ) { + preg_match('/^(.*)\n/', $this->_sMessage, $aTmp); + $sSubject = $aTmp[0]; + return mail($this->_aCfg['email']['to'], $sSubject, $this->_sMessage, "From: " . $this->_aCfg['email']['from'] . "\r\n" . + "Reply-To: " . $this->_aCfg['email']['from'] . "\r\n" + ); + } + return false; + } + + /** + * send a message to slack if _aCfg['slack'] exists + */ + private function _sendToSlack(){ + if (array_key_exists('slack', $this->_aCfg)) { + require_once('./../vendor/shooker/shooker.php'); + $shkr = new Shooker(); + $shkr->setupIncoming($this->_aCfg['slack']['incomingurl']); + $sUser=(array_key_exists('user', $this->_aCfg['slack'])? $this->_aCfg['slack']['user']: false); + $sIcon=(array_key_exists('icon', $this->_aCfg['slack'])? $this->_aCfg['slack']['icon']: false); + return $shkr->sendMessage($this->_sMessage, $sUser, $sIcon); + } + return false; + } + + + /** + * send a message to all targets + * @param string $sMessage + */ + public function sendMessage($sMessage) { + $this->_sMessage=$sMessage; + // echo '<pre>'.print_r($this->_aCfg, 1).'</pre>'.$sMessage.'<br>'; + $this->_sendEmail(); + $this->_sendToSlack(); + + } + +} diff --git a/public_html/deployment/classes/project.class.php b/public_html/deployment/classes/project.class.php index 8699f44a5b54e89ec526a570b1b1efd2ac46e0ac..55bfe498a69ef615bb3b8165dfb5e78022fabf82 100644 --- a/public_html/deployment/classes/project.class.php +++ b/public_html/deployment/classes/project.class.php @@ -1,7 +1,11 @@ <?php +define("OPTION_DEFAULT", -999); +define("OPTION_NONE", -1); + require_once 'base.class.php'; require_once 'htmlguielements.class.php'; +require_once 'messenger.class.php'; /* ###################################################################### @@ -82,6 +86,12 @@ class project extends base { */ private $_oVcs = false; private $_sBranchname = false; + + /** + * send messages + * @var messengerobject + */ + protected $oMessenger = false; // ---------------------------------------------------------------------- // constructor @@ -92,7 +102,7 @@ class project extends base { * @param string $sId id of the project */ public function __construct($sId = false) { - $this->oUser=new user(); + $this->oUser = new user(); $this->_readConfig(); if ($sId) { $this->setProjectById($sId); @@ -114,6 +124,53 @@ class project extends base { return $oCLog->add(basename(__FILE__) . " class " . __CLASS__ . " - " . $sMessage, $sLevel); } + /** + * send info messages to project specific targets (Slack, E-Mail) + * @param string $sMessage + * @return boolean + */ + private function _sendMessage($sMessage){ + $aConfig=array(); + + if (array_key_exists('messenger', $this->_aPrjConfig) + && array_key_exists('slack', $this->_aPrjConfig['messenger']) + ){ + $sSlack=$this->_aPrjConfig['messenger']['slack']; + $aConfig['slack']=array('incomingurl'=>$sSlack); + foreach(array('user', 'icon') as $sKey){ + if (array_key_exists($sKey, $this->_aConfig['messenger']['slack']['presets'][$sSlack])){ + $aConfig['slack'][$sKey]=$this->_aConfig['messenger']['slack']['presets'][$sSlack][$sKey]; + } + } + } + + if (array_key_exists('messenger', $this->_aPrjConfig) + && array_key_exists('email', $this->_aPrjConfig['messenger']) + && $this->_aPrjConfig['messenger']['email'] + ){ + $aConfig['email']=$this->_aConfig['messenger']['email']; + $aConfig['email']['to']=$this->_aPrjConfig['messenger']['email']; + } + if(!count($aConfig)){ + return false; + } + + // init on first usage + if (!$this->oMessenger){ + $this->oMessenger=new messenger($aConfig); + } + + // add some metadata to the message body + $sText=$this->getLabel().': '.html_entity_decode($sMessage)."\n" + . t('page-login-username'). ": ".$this->oUser->getUsername()."\n" + ; + if (isset($_SERVER) && is_array($_SERVER)) { + if(array_key_exists('HTTP_ORIGIN', $_SERVER)){ + $sText.= t('project-home').": <".$_SERVER['HTTP_ORIGIN'].'/deployment/'.$this->getId()."/>\n"; + } + } + return $this->oMessenger->sendMessage($sText); + } /** * read default config file * @return boolean @@ -363,7 +420,7 @@ 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 ... @@ -371,56 +428,56 @@ class project extends base { * @return array */ public function _getBuildfilesByDir($sBase) { - $aReturn=array(); - if(!$sBase || !is_dir($sBase)){ + $aReturn = array(); + if (!$sBase || !is_dir($sBase)) { return false; } - $iTotalSize=0; - $aReturn=array( - 'dir'=>$sBase, - 'filecount'=>false, - 'totalsize'=>false, - 'totalsize-hr'=>false, + $iTotalSize = 0; + $aReturn = array( + 'dir' => $sBase, + 'filecount' => false, + 'totalsize' => false, + 'totalsize-hr' => false, ); - - foreach (glob($sBase . '/*') as $sFile){ - $sFileBase=basename($sFile); + + foreach (glob($sBase . '/*') as $sFile) { + $sFileBase = basename($sFile); $sExt = pathinfo($sFile, PATHINFO_EXTENSION); - $aStat=stat($sFile); - switch($sExt){ + $aStat = stat($sFile); + switch ($sExt) { case 'erb': - $sType='templates'; - $sIcon='fa fa-file-code-o'; + $sType = 'templates'; + $sIcon = 'fa fa-file-code-o'; break; case 'tgz': - $sType='package'; - $sIcon='fa fa-file-archive-o'; + $sType = 'package'; + $sIcon = 'fa fa-file-archive-o'; break; case 'json': - $sType='metadata'; - $sIcon='fa fa-file-text-o'; + $sType = 'metadata'; + $sIcon = 'fa fa-file-text-o'; break; default: - $sType='any'; - $sIcon='fa fa-file-o'; + $sType = 'any'; + $sIcon = 'fa fa-file-o'; break; } $iTotalSize+=$aStat['size']; - $aReturn['files'][$sFileBase]=array( - 'type'=>$sType, - 'icon'=>$sIcon ? '<i class="'.$sIcon.'"></i> ' : '', - 'extension'=>$sExt, - 'size'=>$aStat['size'], + $aReturn['files'][$sFileBase] = array( + 'type' => $sType, + 'icon' => $sIcon ? '<i class="' . $sIcon . '"></i> ' : '', + 'extension' => $sExt, + 'size' => $aStat['size'], ); - $aReturn['types'][$sType][]=$sFileBase; + $aReturn['types'][$sType][] = $sFileBase; } - $aReturn['totalsize']=$iTotalSize; - $aReturn['totalsize-hr']=(round($iTotalSize/1024/102.4)/10).' MB'; - $aReturn['filecount']=count($aReturn['files']); - + $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 ... @@ -431,16 +488,15 @@ class project extends base { $sBase = $this->_getFileBase($sPhase, $sPlace); return $this->_getBuildfilesByDir($sBase); } + /** * list of files of a given version number * @param string $sVersion name of version * @return array */ public function getBuildfilesByVersion($sVersion) { - return $this->_getBuildfilesByDir($this->_getProjectArchiveDir().'/'.$sVersion); + return $this->_getBuildfilesByDir($this->_getProjectArchiveDir() . '/' . $sVersion); } - - /** * get full path of a packed project archive @@ -624,7 +680,7 @@ class project extends base { * @return array */ public function cleanupArchive($bDeleteAll = false) { - if (!$this->oUser->hasPermission("project-action-cleanup")){ + if (!$this->oUser->hasPermission("project-action-cleanup")) { return $this->oUser->showDenied(); } $aDelete = array(); @@ -632,10 +688,10 @@ class project extends base { $sDir = $this->_getProjectArchiveDir(); $this->getVersions(); - if (!$this->_aVersions){ + if (!$this->_aVersions) { return $aDelete; } - + // find unused versions foreach ($this->_aVersions as $sVersion => $aUsage) { if (!$aUsage || count($aUsage) == 0) { @@ -648,15 +704,15 @@ class project extends base { while (count($aUnused) && count($aUnused) > $iKeep) { $sVersion = array_shift($aUnused); $sDir2 = $sDir . '/' . $sVersion; - if (is_dir($sDir2)){ + if (is_dir($sDir2)) { if ($this->_rmdir($sDir2)) { $aDelete[] = $sDir2; - echo t('ok').': '.$sDir2; + echo t('ok') . ': ' . $sDir2; } else { echo sprintf(t("class-project-warning-cannot-delete-archive-dir"), $sDir2); } } else { - echo t('skip').': '.$sDir2; + echo t('skip') . ': ' . $sDir2; } echo '<br>'; } @@ -676,7 +732,7 @@ class project extends base { */ public function cleanupBuilds() { $this->log(__FUNCTION__ . " start"); - if (!$this->oUser->hasPermission("project-action-cleanup")){ + if (!$this->oUser->hasPermission("project-action-cleanup")) { return $this->oUser->showDenied(); } $sDir = $this->_getBuildDir(); @@ -709,7 +765,7 @@ class project extends base { */ public function cleanupVcsCache($iAge = 0) { $this->log(__FUNCTION__ . " start"); - if (!$this->oUser->hasPermission("project-action-cleanup")){ + if (!$this->oUser->hasPermission("project-action-cleanup")) { return $this->oUser->showDenied(); } $this->_initVcs(); @@ -747,6 +803,13 @@ class project extends base { return $this->_aPrjConfig["description"]; } + /** + * get the id of the current project + * @return string + */ + public function getId(){ + return $this->_aConfig["id"]; + } /** * get deploy and queue infos for all phases * @return type @@ -828,39 +891,39 @@ class project extends base { $aTmp[$sKey] = array(); // use version cache - require_once(__DIR__. '/../../valuestore/classes/valuestore.class.php'); + require_once(__DIR__ . '/../../valuestore/classes/valuestore.class.php'); $oVersion = new valuestore(); $oVersion->setProject("", $this->_aPrjConfig["fileprefix"], $sPhase, $sKey); - $aVersions=$oVersion->getVersion(); + $aVersions = $oVersion->getVersion(); // echo "Place: <pre>" . print_r($oVersion->whereiam(), 1) . "</pre>"; // echo "Versionen: <pre>" . print_r($aVersions, 1) . "</pre>"; - foreach ($aVersions as $sHostname=>$aHostdata){ - $aTmp[$sKey]=array(); - $aTmp[$sKey]=$aHostdata['_data']; + foreach ($aVersions as $sHostname => $aHostdata) { + $aTmp[$sKey] = array(); + $aTmp[$sKey] = $aHostdata['_data']; $aTmp[$sKey]["infofile"] = '[versioncache]'; - - $aTmp[$sKey]['_hosts']=array(); - $aTmp[$sKey]['_hosts'][$aHostdata['host']]=$aHostdata; - + + $aTmp[$sKey]['_hosts'] = array(); + $aTmp[$sKey]['_hosts'][$aHostdata['host']] = $aHostdata; + // first host only continue; } $aTmp[$sKey]["ok"] = 1; $aTmp[$sKey]["infofile"] = '[versioncache]'; /* - $sJsonData = $this->_httpGet($sJsonfile); - if ($sJsonData) { - $aJson = json_decode($sJsonData, true); - if (is_array($aJson) && array_key_exists("version", $aJson)) { - $aTmp[$sKey] = $aJson; - $aTmp[$sKey]["infofile"] = $sJsonfile; - $aTmp[$sKey]["ok"] = 1; - } else { - $aTmp[$sKey]["error"] = sprintf(t("class-project-error-metafile-has-no-version"), $sJsonfile, print_r($aJson, true)); - } - } else { - $aTmp[$sKey]["error"] = sprintf(t("class-project-error-metafile-wrong-format"), $sJsonfile); - } + $sJsonData = $this->_httpGet($sJsonfile); + if ($sJsonData) { + $aJson = json_decode($sJsonData, true); + if (is_array($aJson) && array_key_exists("version", $aJson)) { + $aTmp[$sKey] = $aJson; + $aTmp[$sKey]["infofile"] = $sJsonfile; + $aTmp[$sKey]["ok"] = 1; + } else { + $aTmp[$sKey]["error"] = sprintf(t("class-project-error-metafile-has-no-version"), $sJsonfile, print_r($aJson, true)); + } + } else { + $aTmp[$sKey]["error"] = sprintf(t("class-project-error-metafile-wrong-format"), $sJsonfile); + } * */ } else { @@ -959,9 +1022,8 @@ class project extends base { * @param type $sPhase current phase */ public function canAcceptPhase($sPhase = false) { - if (!$this->oUser->hasPermission("project-action-accept") - && !$this->oUser->hasPermission("project-action-accept-$sPhase") - ){ + if (!$this->oUser->hasPermission("project-action-accept") && !$this->oUser->hasPermission("project-action-accept-$sPhase") + ) { // echo $this->oUser->showDenied(); return false; } @@ -997,14 +1059,7 @@ class project extends base { // array key "ok" must be in the ready2install and deployed info // and a version must be installed if ( - array_key_exists($sPhase, $this->_aData["phases"]) - && array_key_exists("onhold", $this->_aData["phases"][$sPhase]) - && array_key_exists("ready2install", $this->_aData["phases"][$sPhase]) - && array_key_exists("deployed", $this->_aData["phases"][$sPhase]) - && array_key_exists("ok", $this->_aData["phases"][$sPhase]["onhold"]) - && array_key_exists("ok", $this->_aData["phases"][$sPhase]["ready2install"]) - && array_key_exists("ok", $this->_aData["phases"][$sPhase]["deployed"]) - && array_key_exists("version", $this->_aData["phases"][$sPhase]["deployed"]) + array_key_exists($sPhase, $this->_aData["phases"]) && array_key_exists("onhold", $this->_aData["phases"][$sPhase]) && array_key_exists("ready2install", $this->_aData["phases"][$sPhase]) && array_key_exists("deployed", $this->_aData["phases"][$sPhase]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["onhold"]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["ready2install"]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["deployed"]) && array_key_exists("version", $this->_aData["phases"][$sPhase]["deployed"]) ) { return true; } @@ -1224,76 +1279,72 @@ class project extends base { $this->_sProcessTempOut = $sNewTempfile; return $this->_sProcessTempOut; } - - /** - * get projects from ldap; it returns ldap search items with cn as - * array key. - * - * @return array - */ - private function _ldapProjectSearch($sSearchFilter) { - $aReturn = array(); - require_once("ldap.class.php"); - $oLdapIML = new imlldap($this->_aConfig['projects']['ldap']); - // $oLdapIML->debugOn(); - $aResultsIml = $oLdapIML->searchDn( - $this->_aConfig['projects']['ldap']['DnProjects'], - $sSearchFilter, - array("*") - ); - if (!$aResultsIml['count']) { - return false; - } - $oLdapIML->close(); - /* - unset($aResultsIml['count']); - foreach ($aResultsIml as $aItem) { - $aReturn[$aItem['cn'][0]] = array( - 'dn' => $aItem['dn'], - 'cn' => $aItem['cn'][0], - '_description' => $aItem['description'][0], - 'title' => $sTitle, - 'description' => $sDescription, - ); - } - $oLdapIML->close(); - ksort($aReturn); - return $aReturn; - * - */ - return $aResultsIml; + /** + * get projects from ldap; it returns ldap search items with cn as + * array key. + * + * @return array + */ + private function _ldapProjectSearch($sSearchFilter) { + $aReturn = array(); + require_once("ldap.class.php"); + $oLdapIML = new imlldap($this->_aConfig['projects']['ldap']); + // $oLdapIML->debugOn(); + $aResultsIml = $oLdapIML->searchDn( + $this->_aConfig['projects']['ldap']['DnProjects'], $sSearchFilter, array("*") + ); + if (!$aResultsIml['count']) { + return false; } + $oLdapIML->close(); + + /* + unset($aResultsIml['count']); + foreach ($aResultsIml as $aItem) { + $aReturn[$aItem['cn'][0]] = array( + 'dn' => $aItem['dn'], + 'cn' => $aItem['cn'][0], + '_description' => $aItem['description'][0], + 'title' => $sTitle, + 'description' => $sDescription, + ); + } + $oLdapIML->close(); + ksort($aReturn); + return $aReturn; + * + */ + return $aResultsIml; + } /** * load config of a project * @return boolean */ public function setProjectById($sId) { - if ($sId !== preg_replace('/[^a-z0-9\-\_]/i', '', $sId)){ + if ($sId !== preg_replace('/[^a-z0-9\-\_]/i', '', $sId)) { echo "ERROR: invalid syntax in project ID: $sId<br>"; return false; } $this->_aPrjConfig = array(); $this->_aConfig["id"] = $sId; - - if ($this->_aConfig['projects']['json']['active']){ + + if ($this->_aConfig['projects']['json']['active']) { $this->_aPrjConfig = json_decode(file_get_contents($this->_getConfigFile($sId)), true); } - if ($this->_aConfig['projects']['ldap']['active']){ + if ($this->_aConfig['projects']['ldap']['active']) { // TODO: read project after saving it - @see $this->saveConfig() - $sQuery='(&(objectclass=hieraSource)(documentIdentifier='.$sId.'))'; - $aResult=$this->_ldapProjectSearch($sQuery); + $sQuery = '(&(objectclass=hieraSource)(documentIdentifier=' . $sId . '))'; + $aResult = $this->_ldapProjectSearch($sQuery); // echo '<pre>$aResult = ' . print_r($aResult, 1) . '</pre>'; - if (is_array($aResult) - && $aResult[0] - && array_key_exists('hieradata',$aResult[0]) - ){ - foreach ($aResult[0]['hieradata'] as $sLine){ + if (is_array($aResult) && $aResult[0] && array_key_exists('hieradata', $aResult[0]) + ) { + foreach ($aResult[0]['hieradata'] as $sLine) { // echo $sLine.'<br>'; - if(preg_match('/^cfg=/', $sLine)){ - + if (preg_match('/^cfg=/', $sLine)) { + // echo $sLine.'<br>'; $this->_aPrjConfig = json_decode(preg_replace('/^cfg=/', '', $sLine), 1); } @@ -1373,7 +1424,6 @@ class project extends base { return file_exists($this->_sProcessTempOut); } - /** * get the name of the current branch (or default branch) * @return string @@ -1399,12 +1449,12 @@ class project extends base { */ public function build($sTmpFile = false) { $this->log(__FUNCTION__ . " start"); - if (!$this->oUser->hasPermission("project-action-build")){ + if (!$this->oUser->hasPermission("project-action-build")) { return $this->oUser->showDenied(); } global $aParams; $sReturn = false; - $oHtml=new htmlguielements(); + $oHtml = new htmlguielements(); $aActionList = array( 'iActive' => 0, @@ -1502,7 +1552,7 @@ class project extends base { $sReturn.=$this->_execAndSend('chmod 755 ' . $filename); $sReturn.=$this->_execAndSend('ls -l ' . $filename); } - + $sReturn.=$oHtml->getBox("success", t('class-project-info-build-checkout-ok')); $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); @@ -1730,7 +1780,7 @@ class project extends base { $this->_logaction(t('starting') . " queue($sPhase, $sVersion)", __FUNCTION__); $sReturn = "<h2> " . t("queue") . " " . $this->getLabel() . " :: $sPhase</h2>"; $this->_TempFill($sReturn, $aActionList); - $oHtml=new htmlguielements(); + $oHtml = new htmlguielements(); if (!$this->isActivePhase($sPhase)) { $sError = sprintf(t("class-project-warning-phase-not-active"), $sPhase); @@ -1803,11 +1853,10 @@ class project extends base { * @return boolean|string */ public function deploy($sPhase, $bIgnoreDeploytimes = false) { - $oHtml=new htmlguielements(); + $oHtml = new htmlguielements(); $this->log(__FUNCTION__ . " start"); - if (!$this->oUser->hasPermission("project-action-deploy") - && !$this->oUser->hasPermission("project-action-deploy-$sPhase") - ){ + if (!$this->oUser->hasPermission("project-action-deploy") && !$this->oUser->hasPermission("project-action-deploy-$sPhase") + ) { return $this->oUser->showDenied(); } $aActionList = array( @@ -1860,6 +1909,8 @@ class project extends base { // $this->_logaction($sError, __FUNCTION__); $sReturn.=$oHtml->getBox("info", $sError); $this->_TempDelete(); + // removed: cronjob sends this message too + // $this->_sendMessage($sError."\n".t('phase').': '.$sPhase); return $sReturn; } else { $sReturn.=t("class-project-info-deploy-time-not-reached-and-ignored") . "<br>"; @@ -1875,6 +1926,7 @@ class project extends base { $this->_logaction($sError, __FUNCTION__, "error"); $sReturn.=$oHtml->getBox("info", $sError); $this->_TempDelete(); + $this->_sendMessage($sError."\n".t('phase').': '.$sPhase); return $sReturn; } $this->_TempFill($sReturn); @@ -1900,6 +1952,7 @@ class project extends base { $sError = t("class-project-error-command-failed"); $this->_logaction($sError, __FUNCTION__, "error"); $sReturn.=$oHtml->getBox("error", $sError . $sReturn); + $this->_sendMessage($sError."\n".t('phase').': '.$sPhase); return $sReturn; } @@ -1948,39 +2001,40 @@ class project extends base { // run action to install // -------------------------------------------------- $sDeploymethod = array_key_exists("deploymethod", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["deploymethod"] : "none"; - $sTargethosts=array_key_exists("hosts", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["hosts"] : ''; - - $sReturn.='<h3>' . t("class-project-info-deploy-start-by-method") . ' :: '.$sDeploymethod.'</h3>' - .'<p>' - .t("deploymethod-$sDeploymethod").'<br>' - .t("phase-targethosts").': '.($sTargethosts ? $sTargethosts : t("none")) - .'</p>' - ; - if ($sDeploymethod==="none" || !$sTargethosts){ + $sTargethosts = array_key_exists("hosts", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["hosts"] : ''; + + $sReturn.='<h3>' . t("class-project-info-deploy-start-by-method") . ' :: ' . $sDeploymethod . '</h3>' + . '<p>' + . t("deploymethod-$sDeploymethod") . '<br>' + . t("phase-targethosts") . ': ' . ($sTargethosts ? $sTargethosts : t("none")) + . '</p>' + ; + if ($sDeploymethod === "none" || !$sTargethosts) { $sReturn.=t("class-project-info-deploy-start-by-method-skip") . "<br>"; } else { - $aTargethosts=explode(',', $sTargethosts); - foreach ($aTargethosts as $sTargethost){ - $sReturn.='<h4>' . $sDeploymethod . ' - '.$sTargethost.'</h4>'; - $sCmd=''; - switch($sDeploymethod){ + $aTargethosts = explode(',', $sTargethosts); + foreach ($aTargethosts as $sTargethost) { + $sReturn.='<h4>' . $sDeploymethod . ' - ' . $sTargethost . '</h4>'; + $sCmd = ''; + switch ($sDeploymethod) { case 'puppet': $sCmd = 'ssh ' . $this->_aConfig["installPackages"]["user"] . '@' . $sTargethost . ' ' . $this->_aConfig["installPackages"]["command"]; - break;; + break; + ; // TODO: we don't have any proxy yet case 'sshproxy__AS_EXAMPLE_ONLY': $sCmd = 'ssh ' . $this->_aConfig["installPackages"]["sshproxy"]["user"] . '@' . $this->_aConfig["installPackages"]["sshproxy"]["host"] . ' ' . sprintf($this->_aConfig["installPackages"]["sshproxy"]["command"], $sTargethost); - break;; + break; + ; } - if ($sCmd){ + if ($sCmd) { // $sReturn.=$sCmd.'<br>'; $sReturn.=$this->_execAndSend("$sCmd"); } - } } $aActionList['iActive'] ++; @@ -1988,6 +2042,8 @@ class project extends base { $sReturn.="<br>"; $sReturn.=$oHtml->getBox("success", t("class-project-info-deploy-successful")); + + $this->_sendMessage(t("class-project-info-deploy-successful")."\n"."phase: $sPhase"); $this->_logaction(t('finished') . " deploy($sPhase, $bIgnoreDeploytimes) " . t("class-project-info-deploy-successful"), __FUNCTION__, "success"); $this->_TempDelete(); return $sReturn; @@ -2000,11 +2056,10 @@ class project extends base { * @return type */ public function accept($sPhase) { - $oHtml=new htmlguielements(); + $oHtml = new htmlguielements(); $this->log(__FUNCTION__ . " start"); - if (!$this->oUser->hasPermission("project-action-accept") - && !$this->oUser->hasPermission("project-action-accept-$sPhase") - ){ + if (!$this->oUser->hasPermission("project-action-accept") && !$this->oUser->hasPermission("project-action-accept-$sPhase") + ) { return $this->oUser->showDenied(); } @@ -2040,16 +2095,16 @@ class project extends base { */ public function saveConfig($aData = false) { $this->log(__FUNCTION__ . " start"); - if (!$this->oUser->hasPermission("project-action-setup")){ + if (!$this->oUser->hasPermission("project-action-setup")) { return $this->oUser->showDenied(); } $this->_logaction(t('starting') . " saveConfig(...)", __FUNCTION__); if (!$aData) { $aData = $_POST; } - - foreach(array('id', 'label', 'description', 'contact', 'build', 'fileprefix', 'phases') as $sKey){ - if (!array_key_exists($sKey, $aData)){ + + foreach (array('id', 'label', 'description', 'contact', 'build', 'fileprefix', 'phases') as $sKey) { + if (!array_key_exists($sKey, $aData)) { $this->_logaction(t('abortet') . " missing key $sKey in savedata", __FUNCTION__, "error"); return false; } @@ -2058,13 +2113,13 @@ class project extends base { // remove unwanted items foreach (array("setupaction", "prj", "id") as $s) { - if (array_key_exists($s, $aData)){ + if (array_key_exists($s, $aData)) { unset($aData[$s]); } } // save json file - if ($this->_aConfig['projects']['json']['active']){ + if ($this->_aConfig['projects']['json']['active']) { // echo "IST <pre>" . print_r($this->_aPrjConfig, true) . "</pre>"; echo "NEU <pre>" . print_r($aData, true) . "</pre>"; die(); // make a backup of a working config $sCfgFile = $this->_getConfigFile($sId); @@ -2075,51 +2130,55 @@ class project extends base { $this->_aPrjConfig = json_decode(file_get_contents($this->_getConfigFile($sId)), true); } // save in ldap - if ($this->_aConfig['projects']['ldap']['active']){ + if ($this->_aConfig['projects']['ldap']['active']) { // TODO: - echo "TODO: save in LDAP<br><pre>" . print_r($aData, 1) ."</pre>"; - - - $sDn='documentIdentifier='.$sId.','.$this->_aConfig['projects']['ldap']['DnProjects']; - $aItem=array( + echo "TODO: save in LDAP<br><pre>" . print_r($aData, 1) . "</pre>"; + + + $sDn = 'documentIdentifier=' . $sId . ',' . $this->_aConfig['projects']['ldap']['DnProjects']; + $aItem = array( 'objectClass' => array( 'document', 'hieraSource', 'top', ), 'hieraData' => array( - 'cfg='.json_encode($aData), - 'updated='.date("Y-m-d H:i:s") . ' by ' . $this->oUser->getUsername(), + 'cfg=' . json_encode($aData), + 'updated=' . date("Y-m-d H:i:s") . ' by ' . $this->oUser->getUsername(), ) ); - + require_once("ldap.class.php"); $oLdapIML = new imlldap($this->_aConfig['projects']['ldap']); // $oLdapIML->debugOn(); - if (!$oLdapIML->DnExists($sDn)){ - if ($oLdapIML->objAdd($sDn,$aItem)){ + if (!$oLdapIML->DnExists($sDn)) { + if ($oLdapIML->objAdd($sDn, $aItem)) { echo 'OK, created in LDAP.<br>'; - $bReturn=true; + $bReturn = true; } else { - echo 'ERROR, DN '.$sDn.' was not created in LDAP :-/<br>'; - $bReturn=false; + echo 'ERROR, DN ' . $sDn . ' was not created in LDAP :-/<br>'; + $bReturn = false; } } else { - if ($oLdapIML->objUpdate($sDn,$aItem)){ + if ($oLdapIML->objUpdate($sDn, $aItem)) { echo 'OK, updated in LDAP.<br>'; - $bReturn=true; + $bReturn = true; } else { - echo 'ERROR, DN '.$sDn.' was not updated in LDAP :-/<br>'; - $bReturn=false; + echo 'ERROR, DN ' . $sDn . ' was not updated in LDAP :-/<br>'; + $bReturn = false; } } $oLdapIML->close(); - } - + $this->_logaction(t('finished') . " saveConfig(...)", __FUNCTION__, "success"); $this->setProjectById($sId); + $sMessage=($bReturn + ? t("page-setup-info-settings-were-saved") + : t("page-setup-error-settings-were-not-saved") + ); + $this->_sendMessage($sMessage); return $bReturn; } @@ -2131,7 +2190,7 @@ class project extends base { */ public function create($sId) { $this->log(__FUNCTION__ . " start"); - if (!$this->oUser->hasPermission("project-action-create")){ + if (!$this->oUser->hasPermission("project-action-create")) { return $this->oUser->showDenied(); } $this->_logaction(t('starting') . " create($sId)", __FUNCTION__); @@ -2199,7 +2258,7 @@ class project extends base { */ public function delete($aOptions = array()) { $this->log(__FUNCTION__ . " start"); - if (!$this->oUser->hasPermission("project-action-delete")){ + if (!$this->oUser->hasPermission("project-action-delete")) { return $this->oUser->showDenied(); } $sCfgfile = $this->_getConfigFile($this->_aConfig["id"]); @@ -2247,6 +2306,7 @@ class project extends base { } } } + $this->_sendMessage(t('finished') . " delete()"); $this->_logaction(t('finished') . " delete()", __FUNCTION__, "success"); return false; } @@ -2303,62 +2363,66 @@ class project extends base { } return $this->_getChecksumDiv($aData["revision"]); } - - private function _renderHostsData($aData){ - $sReturn=''; - if (array_key_exists('_hosts', $aData)){ + + private function _renderHostsData($aData) { + $sReturn = ''; + if (array_key_exists('_hosts', $aData)) { + $oHtml = new htmlguielements(); + // $sReturn.= print_r($aData['_hosts'], 1); $sReturn.= '<div class="hosts">' - . '<br><strong>'.t('hosts').':</strong><br>' - ; - 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" : "??"; + . '<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']) - . '<i class="fa fa-cube"></i><br>' + . $oHtml->getIcon('host').'<br>' . $sHostname . "<br>($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){ + private function _renderHosts($sPhase) { $aDataPhase = $this->getPhaseInfos($sPhase); - if (is_array($aDataPhase) && array_key_exists('deployed', $aDataPhase)){ + 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']){ + 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 + $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'].')'; + $sReturn.='(' . $aFiles['totalsize-hr'] . ')'; return $sReturn; } @@ -2371,7 +2435,7 @@ class project extends base { public function renderLink($sFunction, $sPhase = false, $sVersion = false) { $sFirst = $this->getNextPhase(); $sNext = $this->getNextPhase($sPhase); - $oHtml=new htmlguielements(); + $oHtml = new htmlguielements(); $aLinkdata = array( 'default' => array('class' => ''), 'accept' => array('class' => $sNext, @@ -2410,19 +2474,19 @@ class project extends base { ), ); /* - if (!$this->oUser->hasRole("project-action-$sFunction")){ - // $sClass .= ' disabled'; - // return '<span title="no permission [project-action-'.$sFunction.']">[ ]</span>'; - } + 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 = ''; if ($sFunction == "build") { $sRole = 'developer'; - $sOnMouseover = '$(\'.' . $sNext . '.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');'; + $sOnMouseover = '$(\'.' . $sNext . '.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');'; $sOnMouseout = '$(\'.' . $sNext . '.td' . $this->_aConfig["id"] . '\').removeClass(\'highlight\');'; } if ($sFunction == "accept") { @@ -2431,8 +2495,8 @@ class project extends base { $sRole = 'pl'; // $aLinkdata[$sFunction]['icon']='glyphicon glyphicon-star'; } - $sOnMouseover = '$(\'.' . $sNext . '.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');'; - $sOnMouseout ='$(\'.' . $sNext . '.td' . $this->_aConfig["id"] . '\').removeClass(\'highlight\');'; + $sOnMouseover = '$(\'.' . $sNext . '.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');'; + $sOnMouseout = '$(\'.' . $sNext . '.td' . $this->_aConfig["id"] . '\').removeClass(\'highlight\');'; } // $sClass = $sPhase; @@ -2460,19 +2524,19 @@ class project extends base { if ($sVersion) { $sLink.="$sVersion/"; } - if (!$this->oUser->hasPermission("project-action-$sFunction")){ + if (!$this->oUser->hasPermission("project-action-$sFunction")) { // $sClass .= ' disabled'; - return '<span title="no permission [project-action-'.$sFunction.'] for '.$this->oUser->getUsername().'">[ <i class="' . $sIconClass . '"></i> ' . $sLabel . ' ]</span>'; - } + return '<span title="no permission [project-action-' . $sFunction . '] for ' . $this->oUser->getUsername() . '">[ <i class="' . $sIconClass . '"></i> ' . $sLabel . ' ]</span>'; + } return $oHtml->getLinkButton(array( - 'href'=>$sLink, - 'title'=>$sHint, - 'class'=>'btn btn-default ' . $sClass, - 'type'=>$sFunction, - 'onmouseover'=>$sOnMouseover, - 'onmouseout'=>$sOnMouseout, - 'label'=>$sLabel, + 'href' => $sLink, + 'title' => $sHint, + 'class' => 'btn btn-default ' . $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>'; } @@ -2489,21 +2553,21 @@ class project extends base { $sRow1.='<th class="' . $sPhase . '">' . $sPhase . '</th>'; $sRow2.='<td class="' . $sPhase . '">' . t('url') . ': <a href="' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '">' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '</a><br>' - . '<br>' . t('deploytimes') . ':<br>'; + . '<br>' . t('deploytimes') . ':<br>'; if (count($this->_getDeploytimes($sPhase))) { $sRow2.=implode("<br>", $this->_getDeploytimes($sPhase)); } else { $sRow2.=t('deploytimes-immediately'); } $sRow2.='<br>' . $this->renderLink("phase", $sPhase) - . $this->_renderHosts($sPhase) - .'<br>' - . $this->_renderFiles($sPhase) - . '</td>'; + . $this->_renderHosts($sPhase) + . '<br>' + . $this->_renderFiles($sPhase) + . '</td>'; } return '<table><thead><tr>' . $sRow1 . '</tr></thead><tbody><tr>' . $sRow2 . '</tr></tbody></table>'; } - + /** * render html for a place of a phase * @param string $sPhase phase @@ -2531,93 +2595,92 @@ class project extends base { $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"; - } - */ - $sReturn .= ' + 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"; + } + */ + $sReturn .= ' ' . $this->_renderBar($sPhase, $sPlace) . ' <i class="glyphicon glyphicon-calendar"></i> ' . date($sDateFormat, $oPkgDate); - - if ($bLong) { - // long display of the revision - // $sJsonUrl = $this->_getInfofile($sPhase, $sPlace); - $sReturn.='<br> + + if ($bLong) { + // long display of the revision + // $sJsonUrl = $this->_getInfofile($sPhase, $sPlace); + $sReturn.='<br> <i class="glyphicon glyphicon-bookmark"></i> ' . t('branch') . ': ' . $aData["branch"] . '<br> <i class="glyphicon glyphicon-tag"></i> ' . t('revision') . ': ' . $this->_renderRevision($aData["revision"]) . '<br> <i class="glyphicon glyphicon-comment"></i> ' . t('commitmessage') . ':<br><pre>' . strip_tags($aData["message"], '<br>') . '</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.='<i class="glyphicon glyphicon-globe"></i> ' . t('url') . ': <a href="' . $sUrl . '">' . $sUrl . '</a><br>'; - } - } else { - if ($sPlace == "deployed" && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) { - $sMore = '<i class="glyphicon glyphicon-globe"></i> ' - . t('url') - . ': <a href="' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '">' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '</a><br>'; - } - - $sReturn.=' ' . $this->renderInfoLink( - $aData, array( - 'title' => $this->getLabel() . " :: $sPhase :: $sPlace", - 'more' => $sMore, - ) - ); + // . '<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.='<i class="glyphicon glyphicon-globe"></i> ' . t('url') . ': <a href="' . $sUrl . '">' . $sUrl . '</a><br>'; + } + } else { + if ($sPlace == "deployed" && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) { + $sMore = '<i class="glyphicon glyphicon-globe"></i> ' + . t('url') + . ': <a href="' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '">' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '</a><br>'; } - 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><i class="glyphicon glyphicon-time"></i> ' . t('deploytimes') . ':<br>' - . implode("<br>", array_values($this->_getDeploytimes($sPhase))) - . '<br>'; - } - if ($bActions) { - $sReturn .= ' ' . $this->renderLink("deploy", $sPhase); - } + $sReturn.=' ' . $this->renderInfoLink( + $aData, array( + '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><i class="glyphicon glyphicon-time"></i> ' . t('deploytimes') . ':<br>' + . implode("<br>", array_values($this->_getDeploytimes($sPhase))) + . '<br>'; } - break; + if ($bActions) { + $sReturn .= ' ' . $this->renderLink("deploy", $sPhase); + } + } + break; - case "ready2install": - break; + case "ready2install": + break; - case "deployed": - if ($bActions && $this->canAcceptPhase($sPhase)) { - $sReturn .= ' ' . $this->renderLink("accept", $sPhase); - } - break; - default: - break; - } - // $this->_getChecksumDiv($aData["revision"]) + 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"><i class="glyphicon glyphicon-info-sign"></i> ' . t('warning') . ':<br>' . $aData["warning"] . '</div>'; } 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"><i class="glyphicon glyphicon-info-sign"></i> ' . t('warning') . ':<br>' . $aData["warning"] . '</div>'; - } else { - return false; - // $sReturn.= t('empty'); - } - } // if + return false; + // $sReturn.= t('empty'); + } + } // if // } // for return $sReturn; } @@ -2675,7 +2738,7 @@ class project extends base { * @return string */ public function renderRepoInfo() { - $oHtml=new htmlguielements(); + $oHtml = new htmlguielements(); $sReturn = ""; switch ($this->_aPrjConfig["build"]["type"]) { case "git": @@ -2712,12 +2775,12 @@ class project extends base { * @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>'; + public function _renderRevision($sRevision) { + $sUrl = str_replace('/tree/master', '', $this->_aPrjConfig["build"]["webaccess"]) . '/commit/' . $sRevision; + return '<a href="' . $sUrl . '">' . $sRevision . '</a>'; return $sUrl; } - + /** * render html code for info link that shows popup with metadata on mouseover * @param array $aInfos metainfos of the package (from json file) @@ -2731,7 +2794,7 @@ class project extends base { public function renderInfoLink($aInfos, $aOptions = array()) { $sReturn = ''; $bIsError = false; - $oHtml=new htmlguielements(); + $oHtml = new htmlguielements(); $sInfos.=''; if (array_key_exists("title", $aOptions) && $aOptions["title"]) { @@ -2741,10 +2804,10 @@ class project extends base { $sLinktitle = t('infos'); if (array_key_exists("message", $aInfos)) { $sInfos.=$this->_getChecksumDiv($aInfos["revision"]) - . $oHtml->getIconByType('calendar') . t('build-from') . ' ' . date("d.m.Y H:i:s", strtotime($aInfos["date"])) . '<br>' - . $oHtml->getIconByType('branch') . t('branch') . ': ' . $aInfos["branch"] . '<br>' - . $oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aInfos["revision"]) . '<br>' - . $oHtml->getIconByType('comment') . t('commitmessage') . ':<br><span class="pre">' . strip_tags($aInfos["message"], '<br>') . '</span>'; + . $oHtml->getIconByType('calendar') . t('build-from') . ' ' . date("d.m.Y H:i:s", strtotime($aInfos["date"])) . '<br>' + . $oHtml->getIconByType('branch') . t('branch') . ': ' . $aInfos["branch"] . '<br>' + . $oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aInfos["revision"]) . '<br>' + . $oHtml->getIconByType('comment') . t('commitmessage') . ':<br><span class="pre">' . strip_tags($aInfos["message"], '<br>') . '</span>'; if (array_key_exists("more", $aOptions)) { $sInfos.=$aOptions["more"]; } @@ -2764,11 +2827,11 @@ class project extends base { } // render html - $sId='info'.md5($sInfos); - $sReturn = '<a href="#" class="btn '.($bIsError ? 'btn-danger': 'btn-default').'" onclick="showIdAsModalMessage(\''.$sId.'\'); return false;">' + $sId = 'info' . md5($sInfos); + $sReturn = '<a href="#" class="btn ' . ($bIsError ? 'btn-danger' : 'btn-default') . '" title="" onclick="showIdAsModalMessage(\'' . $sId . '\'); return false;">' // . '<i class="fa fa-info"></i> ' . $sLinktitle - . '</a><div id="'.$sId.'" style="display: none;" '; + . '</a><div id="' . $sId . '" style="display: none;" '; if (array_key_exists("hpos", $aOptions)) { $sReturn.=' class="' . $aOptions["hpos"] . '"'; } @@ -2777,7 +2840,7 @@ class project extends base { if ($sTitle) { $sReturn.='<span class="title">' . $sTitle . '</span><br><br>'; } - + $sReturn.=$sInfos . '</div>'; if ($bIsError) { @@ -2792,7 +2855,7 @@ class project extends base { * @return string */ public function renderVersionUsage() { - $oHtml=new htmlguielements(); + $oHtml = new htmlguielements(); $sReturn = false; $sRowHead1 = false; $sRowHead2 = ''; @@ -2950,17 +3013,94 @@ class project extends base { * @return string */ public function renderProjectSetup() { - if (!$this->oUser->hasPermission("project-action-setup")){ + if (!$this->oUser->hasPermission("project-action-setup")) { return $this->oUser->showDenied(); } - $oHtml=new htmlguielements(); + $oHtml = new htmlguielements(); $sMessages = ''; require_once ("formgen.class.php"); + - require_once("./classes/config-replacement.class.php"); - $oConfig = new configreplacement(); - $oConfig->setProject($this->_aConfig["id"]); + $aSelectSlack = array( + 'type' => 'hidden', + 'name' => 'messenger[slack]', + 'value' => false, + ); + if ( + array_key_exists('messenger', $this->_aConfig) + && array_key_exists('slack', $this->_aConfig['messenger']) + && array_key_exists('presets', $this->_aConfig['messenger']['slack']) + && count(array_key_exists('presets', $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, + ); + } + + } + $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' => t("foreman-hostgroup"), + 'options' => array( + OPTION_NONE => array( + 'label' => t('none'), + ), + '' => array( + 'label' => '- - - - - - - - - - - - - - - - - - - - ', + ), + ), + ); + if (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()) ? @@ -3021,12 +3161,10 @@ class project extends base { 'type' => 'markup', 'value' => '<div class="tabbable"> <ul class="nav nav-tabs"> - <li class="active"><a href="#tab1" data-toggle="tab">' . t('setup-metadata') . '</a></li> - <li><a href="#tab2" data-toggle="tab">' . t('build') . '</a></li> - <li><a href="#tab3" data-toggle="tab">' . t('phases') . '</a></li> - <!-- - <li><a href="#tab3" data-toggle="tab">' . t('deployment') . '</a></li> - --> + <li class="active"><a href="#tab1" data-toggle="tab">' . $oHtml->getIcon('list').t('setup-metadata') . '</a></li> + <li><a href="#tab2" data-toggle="tab">' . $oHtml->getIcon('repository').t('repositoryinfos') . '</a></li> + <li><a href="#tab3" data-toggle="tab">' . $oHtml->getIcon('phase').t('phases') . '</a></li> + <li><a href="#tab4" data-toggle="tab">' . $oHtml->getIcon('raw-data').t('raw-data') . '</a></li> </ul> <div class="tab-content"> <div class="tab-pane active" id="tab1"> @@ -3063,6 +3201,22 @@ class project extends base { 'size' => 100, 'placeholder' => '', ), + + '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' => '', + ), + + 'input' . $i++ => $aSelectSlack, // -------------------------------------------------- 'input' . $i++ => array( 'type' => 'markup', @@ -3130,33 +3284,73 @@ class project extends base { ), ), ); + 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"] : ""; - - $sDeploytimes = array_key_exists("deploytimes", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["deploytimes"] : ""; - $sDivId4PhaseSettings='divSettings'.$sPhase; - $sDivId4TargetHosts='divSettings'.$sPhase.'hosts'; + $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' => 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 (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 . '">'. t("phase") . ' ' . $sPhase.'</th></tr>' - . '<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( 'type' => 'checkbox', 'name' => 'phases[' . $sPhase . '][active]', @@ -3170,19 +3364,14 @@ class project extends base { '1' => array( 'label' => t("yes"), 'checked' => $bActivePhase, - 'onclick' => '$(\'#'.$sDivId4PhaseSettings.'\').css(\'display\', (this.checked ? \'block\' : \'none\') )', + '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' => 'markup', - 'value' => '<div style="clear: both"></div><div class="form-group"><h3>'.t("deploy-settings").'</h3></div>' + . '<div id="' . $sDivId4PhaseSettings . '" ' . ($bActivePhase ? '' : ' style="display: none;"') . '">' ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'text', @@ -3206,29 +3395,28 @@ class project extends base { 'options' => array( 'none' => array( 'label' => t("deploymethod-none"), - 'checked' => $sDeploymethod==="none", - 'onclick' => '$(\'#'.$sDivId4TargetHosts.'\').css(\'display\', (this.checked ? \'none\' : \'block\') )', + 'checked' => $sDeploymethod === "none", + 'onclick' => '$(\'#' . $sDivId4TargetHosts . '\').css(\'display\', (this.checked ? \'none\' : \'block\') )', ), 'puppet' => array( 'label' => t("deploymethod-puppet"), - 'checked' => $sDeploymethod==="puppet", - 'onclick' => '$(\'#'.$sDivId4TargetHosts.'\').css(\'display\', (this.checked ? \'block\' : \'none\') )', + 'checked' => $sDeploymethod === "puppet", + '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\') )', - ), - */ + /* + * 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;"').'">' + . '<div id="' . $sDivId4TargetHosts . '" ' . ($sDeploymethod !== "none" ? '' : ' style="display: none;"') . '">' ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'text', @@ -3242,37 +3430,37 @@ class project extends base { ); /* - 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>', - ); - } - */ - + 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>', + ); + } + */ + $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '' - .'</div>' + . '</div>' ); // when to deploy $aForms["setup"]["form"]['input' . $i++] = array( @@ -3285,95 +3473,18 @@ class project extends base { 'size' => 100, 'placeholder' => implode(", ", $this->_aConfig["phases"][$sPhase]["deploytimes"]), ); - - $aReplacements=$oConfig->getReplacements($sPhase); - $sDivIdReplacement='divreplacements-'.$sPhase; - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<div style="clear: both; height: 2em;"></div>' - . '<div class="form-group">' - . ($aReplacements - ? '<a class="expandable closed" href="#" onclick="$(\'#'.$sDivIdReplacement.'\').slideToggle(); $(this).toggleClass(\'closed\'); return false;">' - : '' - ) - . '<h3>'.t("replacements").' ('.($aReplacements ? count($aReplacements):0).')</h3>' - . ($aReplacements ? '</a>' : '') - . t('replacements-info') - . '</div>' - ); - - if ($aReplacements){ - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<div id="'.$sDivIdReplacement.'" style="display: none;">' - ); - foreach($aReplacements as $sFile=>$aFields){ - $tTplFile=basename($sFile); - $aValues = (array_key_exists("replace", $this->_aPrjConfig["phases"][$sPhase]) - && array_key_exists($tTplFile, $this->_aPrjConfig["phases"][$sPhase]["replace"]) - ) - ? $this->_aPrjConfig["phases"][$sPhase]["replace"][$tTplFile] : false; - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<div class="form-group"><br><h4><i class="fa fa-file-code-o"></i> '.$tTplFile.'</h4>' - // . '<textarea cols="100" rows="7" >'.file_get_contents($sFile).'</textarea>' - . '</div>' - ); - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'text', - 'name' => 'phases[' . $sPhase . '][replace]['.$tTplFile.'][targetfile]', - 'label' => t("replacement-targetfile"), - 'value' => $aValues && array_key_exists('targetfile', $aValues) ? $aValues['targetfile'] : '', - // 'required' => 'required', - 'validate' => 'isastring', - 'size' => 100, - 'placeholder' => strip_tags(t("replacement-targetfile")), - ); - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<br>'.t("replacement-fields") - ); - if(count($aFields)){ - foreach ($aFields as $sField){ - - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'text', - 'disabled' => $this->oUser->hasPermission("project-action-setup-edit-replacements") ? '' : 'disabled', - 'name' => 'phases[' . $sPhase . '][replace]['.$tTplFile.']['.$sField.']', - 'label' => $sField, - 'value' => $aValues && array_key_exists($sField, $aValues) ? $aValues[$sField] : '', - // 'required' => 'required', - 'validate' => 'isastring', - 'size' => 100, - 'placeholder' => $sField, - ); - } - } else { - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<br>'.$oHtml->getBox("error", t("replacement-fields-not-found")) - // . '<pre>'.print_r($aValues, 1).'</pre>' - ); - } - } - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '</div>' - ); - } else { - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<div class="form-group"><h4>'.t("none").'</h4></div>' - ); + + if ($aSelectForemanGroups) { + $aForms["setup"]["form"]['input' . $i++] = $aSelectForemanHostGroup; } - + $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '' - .'</div>' + . '</div>' ); // close div for active phase - - + + $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '</td></tr></tbody></table>', @@ -3381,8 +3492,18 @@ class project extends base { } // END: loop over phases $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', - 'value' => '</div></div></div>' - . '<div style="clear: both; margin-bottom: 1em;"></div><hr>', + 'value' => '</div>' + + . '<div class="tab-pane" id="tab4">' + . '<br><pre>'.print_r($this->_aPrjConfig, 1).'</pre>' + . '</div>' + + . '</div>' + . '</div>' + . '<div style="clear: both; margin-bottom: 1em;"></div>' + + + . '<hr>', ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'submit', @@ -3401,7 +3522,7 @@ class project extends base { */ public function renderNewProject() { global $aParams; - if (!$this->oUser->hasPermission("project-action-create")){ + if (!$this->oUser->hasPermission("project-action-create")) { return $this->oUser->showDenied(); } diff --git a/public_html/deployment/classes/projectlist.class.php b/public_html/deployment/classes/projectlist.class.php index c403b9a18f5405af2e633d809f71a138ee8f9c4b..cc0e59083a7baa6d61bd1bb7a85c01aa246348c3 100644 --- a/public_html/deployment/classes/projectlist.class.php +++ b/public_html/deployment/classes/projectlist.class.php @@ -58,6 +58,7 @@ class projectlist extends base{ $sColClass = "tdphase"; $oPrj1 = new project(); + $oHtml=new htmlguielements(); $sPrjFilter = ''; $sPhaseFilter = ''; @@ -84,13 +85,24 @@ class projectlist extends base{ } $sOut2 .= '<div class="' . $sPrj . ' ' . $sTrClass . ' prjbox"><div class="title">' - . '<a href="" onclick="$(\'#prjfilter\').val(\'' . $sPrj . '\'); window.setTimeout(\'filterOverviewTable();\', 10);setview(\'extended\'); return false;" ' - . 'style="float: right;" ' - . 'title="' . t("overview-filter-hint") . '"><i class="glyphicon glyphicon-filter"></i> ' . t("overview-filter") . '</a>' - . '<strong>' - . '<a href="/deployment/' . $sPrj . '/" title="' . t("menu-project-home") . ' ' . $oPrj->getLabel() . '"><i class="glyphicon glyphicon-book"></i> ' . $oPrj->getLabel() . '</a>' - . '</strong>' - . '</div><div class="box" ' + .$oHtml->getLink(array( + 'href'=>'#', + 'onclick'=>'$(\'#prjfilter\').val(\'' . $sPrj . '\'); window.setTimeout(\'filterOverviewTable();\', 10);setview(\'extended\'); return false;', + 'style'=>'float: right;', + 'title'=>t("overview-filter-hint"), + 'icon'=>'filter', + 'label'=>t("overview-filter") + )) + .'<strong>' + .$oHtml->getLink(array( + 'href'=>'/deployment/' . $sPrj . '/', + 'title'=>t("menu-project-home"). ' ' . $oPrj->getLabel(), + 'icon'=>'project', + 'label'=>'<strong>'.$oPrj->getLabel().'</strong>' + )) + .'</strong>' + . '</div>' + . '<div class="box" ' . 'onclick="location.href=\'/deployment/' . $sPrj . '/\'">' . $oPrj->getDescription() . '<br>' @@ -98,22 +110,30 @@ class projectlist extends base{ // render output $sOut.=' - <tr class="' . $sPrj . ' ' . $sTrClass . '"> + <tr class="' . $sPrj . ' ' . $sTrClass . '" ' + . 'ondblclick="location.href=\'/deployment/' . $sPrj . '/\'" ' + . 'title="'.sprintf(t("overview-hint-dblclick"),$sPrj).'"> <td class="prj"> - <strong> - <a href="/deployment/' . $sPrj . '/" title="'.$oPrj->getDescription().'"> - <i class="glyphicon glyphicon-book"></i> - ' . $oPrj->getLabel() . ' - </a> - </strong>' + <strong>' + .$oHtml->getLink(array( + 'href'=>'/deployment/' . $sPrj . '/', + 'title'=>$oPrj->getDescription(), + 'icon'=>'project', + 'label'=>$oPrj->getLabel() + )) + .'</strong>' . ' <br>' // . $oPrj->getDescription() . '</td>' . '<td class="prj">' - . '<a href="#" onclick="setProjectFilter(\'' . $sPrj . '\'); return false;" ' - . 'style="float: right;" ' - . 'class="btn btn-default" ' - . 'title="' . t("overview-filter-hint") . '"><i class="glyphicon glyphicon-filter"></i> ' . t("overview-filter") . '</a>' + . $oHtml->getLinkButton(array( + 'href'=>'#', + 'onclick'=>'setProjectFilter(\'' . $sPrj . '\'); return false;', + 'style'=>'float: right', + 'title'=>t("overview-filter-hint"), + 'icon'=>'filter', + 'label'=>t("overview-filter"), + )) . '</td>' . '<td class="prj">'; if ($oPrj->canAcceptPhase()) { diff --git a/public_html/deployment/inc_functions.php b/public_html/deployment/inc_functions.php index 61f43c33d3b23c372202a8554df0d00afff88e1a..6bdcbf9990f22588dfdbe1171f833dfe29036208 100644 --- a/public_html/deployment/inc_functions.php +++ b/public_html/deployment/inc_functions.php @@ -120,22 +120,19 @@ if (isset($_SERVER) && is_array($_SERVER) && array_key_exists("REQUEST_URI", $_S $aParams[$sKey] = str_replace(array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $aParams[$sKey]); } } -$sImageBase = "/deployment/images/nuvola64x64/"; -$aImages = array( - 'overview' => 'apps/fsview.png', - 'project' => 'filesystems/desktop.png', - 'action' => 'apps/aktion.png', - 'build' => 'apps/kthememgr.png', -); /** * get home link as button * @return string */ function aHome($sClass = "btn btn-default") { - global $aParams; + global $oHtml; // if (!array_key_exists("prj", $aParams)) return false; - return '<a href="/deployment/?" class="' . $sClass . '"><i class=" glyphicon glyphicon-home"></i> ' . t("menu-overview") . '</a>'; + return $oHtml->getLinkButton(array( + 'href'=>'/deployment/?', + 'icon'=>'overview', + 'label'=>t("menu-overview"), + )); } /** @@ -143,16 +140,22 @@ function aHome($sClass = "btn btn-default") { * @return string */ function aPrjHome($sClass = "btn btn-default") { - global $aParams, $aConfig; - if (!array_key_exists("prj", $aParams)) + global $aParams, $oHtml; + if (!array_key_exists("prj", $aParams)){ return false; - if ($aParams["prj"] == "all") + } + if ($aParams["prj"] == "all"){ return aHome($sClass); + } // if (!array_key_exists("action", $aParams)) return false; require_once("./classes/project.class.php"); $oPrj = new project($aParams["prj"]); - return '<a href="/deployment/' . $aParams["prj"] . '/" class="' . $sClass . '"><i class=" glyphicon glyphicon-book"></i> ' . t("menu-project-home") . ' <strong>' . $oPrj->getLabel() . '</strong></a>'; + return $oHtml->getLinkButton(array( + 'href'=>'/deployment/' . $aParams["prj"] . '/', + 'icon'=>'project-home', + 'label'=>$oPrj->getLabel(), + )); } /** @@ -160,14 +163,28 @@ function aPrjHome($sClass = "btn btn-default") { * @return string */ function aGoback($sClass = "btn btn-default") { - return '<a href="#" onclick="history.back();" class="' . $sClass . '" title="' . t("back") . '"><i class="glyphicon glyphicon-chevron-left"></i> ' . t("back") . '</a> '; + global $oHtml; + return $oHtml->getLinkButton(array( + 'href'=>'#', + 'onclick'=>'history.back();', + 'title'=>t("back"), + 'icon'=>'back', + 'label'=>t("back") + )); } /** * get go top link as button * @return string */ function aGotop($sClass = "scroll-link btn btn-default") { - return '<a href="#top" class="' . $sClass . '" title="' . t("gotop") . '"><i class=" glyphicon glyphicon-eject"></i> </a> '; + global $oHtml; + return $oHtml->getLinkButton(array( + 'href'=>'#top', + 'class'=>$sClass, + 'title'=>t("gotop"), + 'icon'=>'glyphicon-eject', + 'label'=>' ' + )); } /** @@ -176,7 +193,7 @@ function aGotop($sClass = "scroll-link btn btn-default") { * @return type */ function getTopArea() { - global $aParams, $sImageBase, $aImages, $aConfig; + global $aParams, $oHtml; $sReturn = ''; require_once("./classes/project.class.php"); require_once("./classes/user.class.php"); @@ -219,30 +236,67 @@ function getTopArea() { <ul class="nav navbar-nav"> <li class="dropdown'; - if (!array_key_exists("prj", $aParams)){ + if (array_key_exists("prj", $aParams) && $aParams['prj']==='all'){ $sReturn.=' active'; } $sReturn.='">' - . '<a href="' . $sBaseUrl . '" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" - >'.t("menu-overview").' <span class="caret"></span></a> - <ul class="dropdown-menu" role="menu"> - <li><a href="' . $sBaseUrl . 'all/setup/">' . t("menu-settings") . '</a></li> - <li><a href="' . $sBaseUrl . 'all/setup/actionlog/">' . t("menu-logs") . '</a></li> - <li><a href="' . $sBaseUrl . 'all/setup/new/">' . t("menu-new-project") . '</a></li> + . $oHtml->getLink(array( + 'href'=>$sBaseUrl, + 'class'=>'dropdown-toggle', + 'data-toggle'=>'dropdown', + 'role'=>'button', + 'aria-expanded'=>'false', + 'icon'=>'menu', + 'label'=>t("menu").' <span class="caret"></span>', + )) + .'<ul class="dropdown-menu" role="menu"> + <li>'.$oHtml->getLink(array( + 'href'=>$sBaseUrl . 'all/setup/new/', + 'icon'=>'new', + 'label'=>t("menu-new-project"), + )).'</li> + <li>'.$oHtml->getLink(array( + 'href'=>$sBaseUrl . 'all/setup/actionlog/', + 'icon'=>'actionlog', + 'label'=>t("menu-logs"), + )).'</li> + <li>'.$oHtml->getLink(array( + 'href'=>$sBaseUrl . 'all/setup/checklang/', + 'icon'=>'checklang', + 'label'=>t("menu-checklang"), + )).'</li> + <li>'.$oHtml->getLink(array( + 'href'=>$sBaseUrl . 'all/setup/', + 'icon'=>'setup', + 'label'=>t("menu-settings"), + )).'</li> </ul> </li> - <li class="dropdown"> - <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" - >' . t("menu-projects") . ' <span class="caret"></span></a> - <ul class="dropdown-menu" role="menu"> - '; + <li class="dropdown">' + . $oHtml->getLink(array( + 'href'=>'#', + 'class'=>'dropdown-toggle', + 'data-toggle'=>'dropdown', + 'role'=>'button', + 'aria-expanded'=>'false', + 'icon'=>'projects', + 'label'=>t("menu-projects") . ' <span class="caret"></span>', + )) + .'<ul class="dropdown-menu" role="menu">' + ; $oPrj1 = new project(); foreach ($oPrj1->getProjects() as $sPrj) { $oPrj = new project($sPrj); - $sReturn.='<li><a href="' . $sBaseUrl . $sPrj . '/">' . $oPrj->getLabel() . '</a></li>'; + $sReturn.='<li>' + . $oHtml->getLink(array( + 'href'=>$sBaseUrl . $sPrj .'/', + 'icon'=>'project', + 'label'=>$oPrj->getLabel(), + )) + . '</li>'; } $sReturn.=' </ul> @@ -252,55 +306,106 @@ function getTopArea() { if (array_key_exists("prj", $aParams) && $aParams["prj"] <> "all") { $oPrj = new project($aParams["prj"]); $sReturn.=' - <li class="active">' . aPrjHome(" ") . '</li> - '; - if (array_key_exists("action", $aParams) and FALSE) { - $sReturn.='<li><a href="#">Aktion: ' . $aParams["action"] . '</a></li>'; - } else { - $sReturn.=' - <li class="dropdown"> - <a href="' . $sBaseUrl . '" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" - >'.t("menu-project-actions").' <span class="caret"></span></a> - - <ul class="dropdown-menu" role="menu"> - <!-- - <li><a href="' . $sBaseUrl . $aParams["prj"] . '/build/">' . t("menu-project-build") . '</a></li> - --> - <li><a href="' . $sBaseUrl . $aParams["prj"] . '/setup/">' . t("menu-project-settings") . '</a></li> - <li><a href="' . $sBaseUrl . $aParams["prj"] . '/cleanup/">' . t("menu-project-cleanup") . '</a></li> - <li><a href="' . $sBaseUrl . $aParams["prj"] . '/delete/">' . t("menu-project-delete") . '</a></li> - '; - $sReturn.='</ul></li>'; - + <li class="dropdown active">' + .$oHtml->getLink(array( + 'href'=>'#', + 'class'=>'dropdown-toggle', + 'data-toggle'=>'dropdown', + 'role'=>'button', + 'aria-expanded'=>'false', + 'icon'=>'project', + 'label'=>$oPrj->getLabel() .' <span class="caret"></span>', + )) + .' + <ul class="dropdown-menu" role="menu"> + <li>' + . $oHtml->getLink(array( + 'href'=>$sBaseUrl . $sPrj .'/', + 'icon'=>'project-home', + 'label'=>t("project-home"), + )) + .'</li> + '; $aPhases = $oPrj->getActivePhases(); if (count($aPhases)) { - $sReturn.='<li class="dropdown""> - <a href="#" class="dropdown-toggle" data-toggle="dropdown">' . t("menu-project-phases") . '<b class="caret"></b></a> - <ul class="dropdown-menu" role="menu">'; + $sReturn.='<li role="separator" class="divider"></li> + <li>' + .$oHtml->getLink(array( + 'href'=>$sBaseUrl . $aParams["prj"] . '/build/', + 'icon'=>'build', + 'label'=>t("build"), + )) + .'</li> + <li role="separator" class="divider"></li> + <li class="dropdown-header">'.t("menu-project-phases").'</li>'; foreach ($aPhases as $sPhase) { - $sReturn.='<li><a href="' . $sBaseUrl . $aParams["prj"] . '/phase/' . $sPhase . '/">' . $sPhase . '</a></li>'; + $sReturn.='<li><a href="' . $sBaseUrl . $aParams["prj"] . '/phase/' . $sPhase . '/">' . $oHtml->getIcon('phase').$sPhase . '</a></li>'; } - $sReturn.='</ul></li>'; } - } + $sReturn.=' + <li role="separator" class="divider"></li> + <li>' + .$oHtml->getLink(array( + 'href'=>$sBaseUrl . $aParams["prj"] . '/setup/', + 'icon'=>'setup', + 'label'=>t("menu-project-settings"), + )) + .'</li> + <li>' + .$oHtml->getLink(array( + 'href'=>$sBaseUrl . $aParams["prj"] . '/cleanup/', + 'icon'=>'cleanup', + 'label'=>t("menu-project-cleanup"), + )) + .'</li> + <li>' + .$oHtml->getLink(array( + 'href'=>$sBaseUrl . $aParams["prj"] . '/delete/', + 'icon'=>'delete', + 'label'=>t("menu-project-delete"), + )) + .'</li> + </ul></li>'; } } $sReturn.=' </ul> - <ul class="nav navbar-nav navbar-right"> + <ul class="nav navbar-nav navbar-right">' + . ($oUser->getUsername() + ? ' <!-- userdata --> - <li class="dropdown"> - <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" - ><span class="glyphicon glyphicon-user"></span> ' . $oUser->getUsername() . ' <b class="caret"></b></a> - <ul class="dropdown-menu" role="menu"> - <li><a href="' . $sBaseUrl . 'all/login/">' . t("menu-login") . '</a></li> + <li class="dropdown">' + .$oHtml->getLink(array( + 'href'=>'#', + 'class'=>'dropdown-toggle', + 'data-toggle'=>'dropdown', + 'role'=>'button', + 'aria-expanded'=>'false', + 'icon'=>'user', + 'label'=>$oUser->getUsername() .' <span class="caret"></span>', + )) + .'<ul class="dropdown-menu" role="menu"> + <li>' + .$oHtml->getLink(array( + 'href'=>$sBaseUrl . 'all/login/', + 'icon'=>'login', + 'label'=>t("menu-login"), + )) + .'</li> </ul> - </li> - - - <li class="dropdown"> + </li>' + + : '<li>' + .$oHtml->getLink(array( + 'href'=>$sBaseUrl . 'all/login/', + 'icon'=>'login', + 'label'=>t("login"), + )) + .'</li>' + ) + .'<li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" ><span class="glyphicon glyphicon-question-sign"></span> ' . t("menu-help") . ' <b class="caret"></b></a> <ul class="dropdown-menu"> @@ -320,13 +425,12 @@ function getTopArea() { </div><div id="header2">'; if (!array_key_exists("prj", $aParams)) { - $sReturn.='<!-- <img src="' . $sImageBase . $aImages['overview'] . '" id="imgtop" alt=""> -->' - . '<h1>' . t("overview-label") . '</h1><span class="description">' . t("overview-hint") . '</span>'; + $sReturn.='<h1>' . t("overview-label") . '</h1><span class="description">' . t("overview-hint") . '</span>'; } else { if ($aParams["prj"] <> "all") { $oPrj = new project($aParams["prj"]); - $sReturn.='<!-- <img src="' . $sImageBase . $aImages['project'] . '" id="imgtop" alt="">--> - <h1>' . $oPrj->getLabel() . '</h1><span class="description">' . $oPrj->getDescription() . '</span>'; + $sReturn.='<h1>' . $oPrj->getLabel() . '</h1>' + . '<span class="description">' . $oPrj->getDescription() . '</span>'; if (array_key_exists("action", $aParams)) { // $sReturn.='<h2>Aktion: '.$aParams["action"].'</h2>'; } @@ -340,22 +444,38 @@ function getTopArea() { /** * get h2 headline with action * @global type $aParams - * @global string $sImageBase - * @global array $aImages * @return string */ function getAction() { - global $aParams, $sImageBase, $aImages; + global $aParams, $oHtml; $sReturn = ''; + $sNav = ''; + $sLabel = ''; + // $sDelim=$oHtml->getIcon('fa-chevron-right'); + $sDelim=' / '; if (array_key_exists("action", $aParams)) { - $sLabel = $aParams["action"]; + $sLabel .= $oHtml->getIcon($aParams["action"]).t($aParams["action"]); + $sNav .= aHome(); + if ($aParams['prj']==='all'){ + // $sNav .= ' ' . $oHtml->getIcon('fa-chevron-right') . '[ci-GUI]'; + } else { + $sNav .= ' ' . $sDelim . aPrjHome(); + } + $sClass='action '.$aParams["action"]; if (array_key_exists("par3", $aParams)) { - $sLabel.=' :: ' . $aParams["par3"]; + $sLabel.=' :: ' . $oHtml->getIcon($aParams["par3"]). $aParams["par3"]; + $sClass='action ' . $aParams["action"]; } - $sReturn.='<h2 class="action ' . $aParams["action"] . '">' . $sLabel . '</h2>'; + // $sReturn.='<h2 class="action ' . $aParams["action"] . '">' . $sLabel . '</h2>'; } else if (array_key_exists("prj", $aParams)) { - $sReturn.='<h2 class="action prjhome"> Projekt-Home</h2>'; + $sNav .= aHome(); + $sClass='action prjhome'; + $sLabel.=$oHtml->getIcon('project-home').t('project-home'); + // $sReturn.='<h2 class="action prjhome"> Projekt-Home</h2>'; } + $sReturn.=($sNav ? '<div id="navtop">' . $sNav . ' ' . $sDelim . ' <span class="current">'.$sLabel.'</span></div>':'') + // .'<h2 class="'.$sClass.'"> '.$sLabel.'</h2>' + ; return $sReturn; } diff --git a/public_html/deployment/main.css b/public_html/deployment/main.css index 342dc40172d9fd7b0910a0f25b699b68aef26434..57a224df6125ef7abe49ee29e0d9a40aed7f3a39 100644 --- a/public_html/deployment/main.css +++ b/public_html/deployment/main.css @@ -42,11 +42,18 @@ body{padding-top: 0;} border-left: 0px solid #ccc; padding: 1em; box-shadow: 0 0 15px #eee; + + padding: 0.5em; + box-shadow: 0 0 7em #ddd; } +div#navtop, div#navbuttom{background: #dde; padding: 0.5em;} +div#navtop{margin-bottom: 2em;} +div#navtop .current{font-size: 170%; color: #667; padding: 0 0.2em; position: absolute;} div#navbuttom{ - margin-top: 4em; padding: 1em 0; - border-top: 2px solid #ddd;} + margin-top: 4em; + border-top: 2px solid #ddd; +} #divmodal{ position: fixed; height: 100%; width: 100%; top:0; left:0; @@ -80,7 +87,7 @@ h2.phase{background-image: url("/deployment/images/nuvola64x64/apps/kreversi.png h2.setup{background-image: url("/deployment/images/nuvola64x64/apps/kcmsystem.png");} h2.login{background-image: url("/deployment/images/nuvola64x64/apps/kgpg.png");} */ -h3{color:#444; margin-top: 3em;} +h3{background: #f0f0f4; color:#456; margin: 3em 0 1em; padding: 0.5em;} h3:first-child{margin-top: 0;} h4{color:#666;} @@ -238,6 +245,8 @@ input[type="radio"]:checked+label, input[type="checkbox"]:checked+label{ .visualprocess .process .title{margin-bottom: 2em; font-weight: bold; font-size: 150%; color:#aaa;} .visualprocess .process .details{background: #fff; min-height: 6em;} +/* ----- replacemets with templates ----- */ +span.replace{background:#fea; font-weight: bold;} /* ----- log table ----- */ .loglevel-info{} diff --git a/public_html/deployment/pages/act_build.php b/public_html/deployment/pages/act_build.php index 9fbeee728a192211fbdbf7502b53fbeaa5eb5ee0..996de19faa9b3eb9f29caa7529950ef6a86ec2ea 100644 --- a/public_html/deployment/pages/act_build.php +++ b/public_html/deployment/pages/act_build.php @@ -57,9 +57,9 @@ if (!array_key_exists("confirm", $aParams)) { <table> <thead> <tr> - <th class="versioncontrol" colspan="2">' . t("versioncontrol") . '</th> + <th class="versioncontrol" colspan="2">' . $oHtml->getIcon('repository').t("versioncontrol") . '<br></th> <th> </th> - <th class="' . $sNext . '" colspan="2">' . $sNext . '</th> + <th class="' . $sNext . '" colspan="2">' . $oHtml->getIcon('phase').$sNext . '</th> </tr> </thead> <tbody> diff --git a/public_html/deployment/pages/act_htmltest.php b/public_html/deployment/pages/act_htmltest.php index 51ac13853360d4c169b5862836f018321b043d94..3188f31ceaee2d260204a4d9c95bd33f22e6ee99 100644 --- a/public_html/deployment/pages/act_htmltest.php +++ b/public_html/deployment/pages/act_htmltest.php @@ -96,6 +96,16 @@ $sRows='' ), ) );") + . addHtmltestTest("Tabelle", "\$oHtml->getTable( + array( + 'header'=>array('A', 'B'), + 'body'=>array( + array('Zelle A 1', 'Zelle B 1'), + array('Zelle A 2', 'Zelle B 2'), + array('Zelle A 3', 'Zelle B 3'), + ), +));" +) ; // ---------------------------------------------------------------------- diff --git a/public_html/deployment/pages/act_login.php b/public_html/deployment/pages/act_login.php index f0ca82f02574cebe7a0a7b91f220c0c1f8435553..0c6634a96435eb818f82947df5c8b9c220ea8f60 100644 --- a/public_html/deployment/pages/act_login.php +++ b/public_html/deployment/pages/act_login.php @@ -127,7 +127,7 @@ if ($oUser->getUsername()) { ); $oForm = new formgen($aForms); - $sOut.='<div style="width: 50%; margin-left: 25%;">' + $sOut.='<div style="width: 50%; min-width: 60em; margin-left: 15%;">' . '<h2>' . t("page-login-info") . '</h2>' . '<p>' . t("page-login-info-introtext") . '</p>'; if (array_key_exists('user', $aParams)) { diff --git a/public_html/deployment/pages/act_overview.php b/public_html/deployment/pages/act_overview.php index 48ccb45ac70c8f4ef775d7f6f43a3a013e760f0c..8bff0dd4ec78ddbfade22fbce36ca92fc5e0062b 100644 --- a/public_html/deployment/pages/act_overview.php +++ b/public_html/deployment/pages/act_overview.php @@ -47,20 +47,20 @@ if (!array_key_exists("prj", $aParams)) { <br> <ul class="nav nav-tabs"> - <li class="active"><a href="#tab1" data-toggle="tab">' . t("way-of-packages") . '</a></li> - <li><a href="#tab2" data-toggle="tab">' . t("repositoryinfos") . '</a></li> - <li><a href="#tab3" data-toggle="tab">' . t("packages") . '</a></li> - <li><a href="#tab4" data-toggle="tab">' . t('phases') . '</a></li> + <li class="active"><a href="#tab1" data-toggle="tab">' . $oHtml->getIcon('workflow') . t("way-of-packages") . '</a></li> + <li><a href="#tab2" data-toggle="tab">' . $oHtml->getIcon('repository') . t("repositoryinfos") . '</a></li> + <li><a href="#tab3" data-toggle="tab">' . $oHtml->getIcon('package') . t("packages") . '</a></li> + <li><a href="#tab4" data-toggle="tab">' . $oHtml->getIcon('phase') . t('phases') . '</a></li> </ul> <br> <div class="tab-content"> <div class="tab-pane active" id="tab1"> ' - . '<h3 id="h3visual">' . t("way-of-packages") . '</h3> + . '<h3 id="h3visual">' . $oHtml->getIcon('workflow') . t("way-of-packages") . '</h3> ' . $oPrj->renderVisual() . ' </div> <div class="tab-pane" id="tab2"> - <h3 id="h3repo">' . t("repositoryinfos") . '</h3> + <h3 id="h3repo">' . $oHtml->getIcon('repository') . t("repositoryinfos") . '</h3> <div style="max-width: 40em;"> ' . $oPrj->renderRepoInfo() . ' ' . $sListOfBranches . ' @@ -69,13 +69,13 @@ if (!array_key_exists("prj", $aParams)) { </div> <div class="tab-pane" id="tab3"> - <h3 id="h3versions">' . t("packages") . '</h3> + <h3 id="h3versions">' . $oHtml->getIcon('package') . t("packages") . '</h3> ' . $oPrj->renderVersionUsage() .' </div> <div class="tab-pane" id="tab4"> - <h3 id="h3phases">' . t("phases") . '</h3> + <h3 id="h3phases">' . $oHtml->getIcon('phase') . t("phases") . '</h3> '.($oPrj->getActivePhases() ? '<p>' . t("page-overview-phase-infos") diff --git a/public_html/deployment/pages/act_phase.php b/public_html/deployment/pages/act_phase.php index c93965e3c38f98e5187a7143348b80cccccb75ac..ff97695c3fb5cfe4ec2186bd31b4838ccd47b187 100644 --- a/public_html/deployment/pages/act_phase.php +++ b/public_html/deployment/pages/act_phase.php @@ -13,6 +13,7 @@ require_once("./classes/project.class.php"); require_once("./inc_functions.php"); +require_once("./classes/config-replacement.class.php"); // --- Checks @@ -24,11 +25,209 @@ if (array_key_exists("par3", $aParams)) { } if ($sPhase) { - $sPhase = $aParams["par3"]; - // $sFirst = $oPrj->getNextPhase(); + $aWarnings=array(); + $sOutReplace=''; + + // ---------------------------------------------------------------------- + // replacement + // ---------------------------------------------------------------------- + + $oConfig = new configreplacement(); + $oConfig->setProject($aParams["prj"], $sPhase); + + $aReplacements=$oConfig->getReplacements(); + + $sOut.='<h3>' . $oHtml->getIcon('replace') . t("replacements") . '</h3>'; + + // ---------------------------------------------------------------------- + // Links to foreman + // ---------------------------------------------------------------------- + $aForeman=$oConfig->getForemanReplacements(); + $aReplacementsForeman=$aForeman ? $aForeman['rules'] : false; + + // echo '<pre>$aReplacements = '.print_r($aReplacements, 1) . '</pre>'; + // echo '<pre>$aForeman = '.print_r($aForeman, 1) . '</pre>'; + + $sOut.=($aReplacementsForeman + ? $oConfig->getForemanlink2Hostgroup().' ' + .$oConfig->getForemanlink2Host() + : t('foreman-no-host') + ).'<br><br>' + ; + + // ---------------------------------------------------------------------- + // Loop over files + // ---------------------------------------------------------------------- + + if ($aReplacements) { + + // open all/ close all + if(count($aReplacements)>1){ + $sOutReplace.=$oHtml->getLinkButton(array( + 'onclick'=>'$(\'.divfileinfos\').slideDown(); $(\'.expandable\').removeClass(\'closed\'); this.blur(); return false;', + 'icon'=>'fa-chevron-down', + )) + .$oHtml->getLinkButton(array( + 'onclick'=>'$(\'.divfileinfos\').slideUp(); $(\'.expandable\').addClass(\'closed\'); this.blur(); return false;', + 'icon'=>'fa-chevron-up', + )) + ; + } + + foreach ($aReplacements as $sFile => $aFields) { + $tTplFile = basename($sFile); + $bFileInForeman = $aReplacementsForeman && array_key_exists($tTplFile, $aReplacementsForeman); + $sDivIdFile='div4file-'.md5($sFile); + + $sOutReplace.='<h4>' . + $oHtml->getLink(array( + 'onclick'=>'$(\'#'.$sDivIdFile.'\').slideToggle(); $(this).toggleClass(\'closed\'); return false;', + 'class'=>'expandable closed', + 'icon'=>'templatefile', + 'label'=>$tTplFile, + )) . '</h4>' + . '<div id="'.$sDivIdFile.'" class="divfileinfos" style="display: none;">' + ; + + // --- check: does this file in foreman exist? + if (!array_key_exists($tTplFile, $aReplacementsForeman)){ + if ($aReplacementsForeman){ + $sOutReplace.=$oHtml->getBox('error', t('foreman-error-template-unknown')); + $aWarnings[]=$tTplFile.': '.t('foreman-error-template-unknown'); + } + } + + + // --- check: target file was set? + $aTable=array('header'=>array(), 'body'=>array()); + if ($bFileInForeman){ + if ($aReplacementsForeman && array_key_exists('target', $aReplacementsForeman[$tTplFile])){ + $sTd='<strong>'.$oHtml->getIcon('targetfile') . $aReplacementsForeman[$tTplFile]['target'].'</strong>'; + } else { + if ($bFileInForeman){ + $sTd=t('foreman-error-no-target'); + $aWarnings[]=$tTplFile.': '.t('foreman-error-no-target'); + } else { + $sTd='-'; + } + } + $aTable['body'][]=array( + $oHtml->getIcon('templatefile') . t('foreman-targetfile'), + $sTd + ); + } + + // --- loop over all replacement items of template file + // and check if they exist in foreman + if (count($aFields)) { + foreach ($aFields as $sField){ + if ($aReplacementsForeman && array_key_exists($sField, $aReplacementsForeman[$tTplFile]['replace'])){ + $sTd=$aReplacementsForeman[$tTplFile]['replace'][$sField]; + } else { + if ($bFileInForeman){ + $sTd=$oHtml->getBox('error', sprintf(t('foreman-error-no-replacement-for-id'), $sField)); + $aWarnings[]=$tTplFile.': '.sprintf(t('foreman-error-no-replacement-for-id'), $sField); + } else { + $sTd='-'; + } + + } + $aTable['body'][]=array( + $oHtml->getIcon('replace') . $sField, + $sTd + ); + } + } else { + $aTable['body'][]=array( + $oHtml->getBox('error', t('foreman-error-no-replacement-in-templatefile')), + '-' + ); + $aWarnings[]=$tTplFile.': '.t('foreman-error-no-replacement-in-templatefile'); + } + + // --- reverse check ... loop over all replacement items of foreman + // and check if they exist in template + if ($aReplacementsForeman){ + // for testing: create a testvalue + // $aReplacementsForeman[$tTplFile]['replace']['testentry']='dummy value'; + foreach ($aReplacementsForeman[$tTplFile]['replace'] as $sField=>$sValue){ + if (array_search($sField, $aFields)===false){ + $aTable['body'][]=array( + $oHtml->getIcon('replace') . + $oHtml->getBox('error', sprintf(t('foreman-error-replacement-unknown'), $sField)), + $sValue + ); + $aWarnings[]=$tTplFile.': '. sprintf(t('foreman-error-replacement-unknown'), $sField); + } + } + } + + // --- filecontent of template + $ContentFile='<br>'.$sFile.'<br><br>' + .'<pre style="max-height:35em;">' + . preg_replace('/(@replace\[.*\])/U', '<span class="replace">${1}</span>' , htmlentities(file_get_contents($sFile))) + .'</pre>'; + + // --- output with tabs for a template file + $sOutReplace.=$oHtml->getNav( + array( + 'options'=>array('type'=>'tabs'), + 'tabs'=>array( + $oHtml->getIcon('list') . t('replacement-fields') => '<br>'.$oHtml->getTable($aTable), + $oHtml->getIcon('templatefile') . $tTplFile => $ContentFile, + ) + ) + ) + .'<br></div>'; + } + } else { + $sOutReplace.=t('none'); + } + + + // --- reverse check - do templates in foreman physically exist? + // for testing: create a testfile + // $aReplacementsForeman['dummytemplate.erb']=array(); + if($aReplacementsForeman){ + foreach(array_keys($aReplacementsForeman) as $sForemanTemplate){ + + $bFound=false; + foreach (array_keys($aReplacements) as $sFile) { + if (basename($sFile)===$sForemanTemplate){ + $bFound=true; + continue; + } + } + if (!$bFound){ + $aWarnings[]=sprintf(t('foreman-error-missing-template'), $sForemanTemplate); + $sDivIdFile='div4file-'.md5($sForemanTemplate); + + $sOutReplace.='<h4>' . + $oHtml->getLink(array( + 'onclick'=>'$(\'#'.$sDivIdFile.'\').slideToggle(); $(this).toggleClass(\'closed\'); return false;', + 'class'=>'expandable closed', + 'icon'=>'fa-warning', + 'label'=>$sForemanTemplate, + )) . '</h4>' + . '<div id="'.$sDivIdFile.'" class="divfileinfos" style="display: none;">' + .$oHtml->getBox('error', sprintf(t('foreman-error-missing-template'), $sForemanTemplate)) + . '</div>' + ; + + } + } + } + if (count($aWarnings)){ + $sOut.=$oHtml->getBox('warning', '<ul><li>'.implode('<li>', $aWarnings).'</ul>'); + } + $sOut.=$sOutReplace; + + // ---------------------------------------------------------------------- + // versions + // ---------------------------------------------------------------------- $sOut.=' - <h3>' . t("versions") . '</h3> + <h3>' . $oHtml->getIcon('version').t("versions") . '</h3> <table> <thead> <tr> @@ -42,10 +241,15 @@ if ($sPhase) { </tr> </tbody> </table> - ' + '; + + // ---------------------------------------------------------------------- + // list of phases + // ---------------------------------------------------------------------- + // show all phases if there are more than one - . (count($oPrj->getActivePhases())>1 - ? '<h3>' . t("phases") . '</h3>' . $oPrj->renderPhaseInfo() + $sOut.=(count($oPrj->getActivePhases())>1 + ? '<h3>' . $oHtml->getIcon('phase').t("phases") . '</h3>' . $oPrj->renderPhaseInfo() : '' ) ; diff --git a/public_html/deployment/pages/act_setup.php b/public_html/deployment/pages/act_setup.php index 7fc8f3110dde77f47babf10876c38581b0ef3480..b231c42b0508915c5375d61aff3675a16813a8fd 100644 --- a/public_html/deployment/pages/act_setup.php +++ b/public_html/deployment/pages/act_setup.php @@ -16,6 +16,45 @@ require_once("./classes/project.class.php"); require_once("./inc_functions.php"); $sOut = ''; +$sFakePassword='********************'; + +// items to mask +$aMask=array( + 'auth'=>array( + 'ldap'=>array( + 'PwLdapUser'=>$sFakePassword + ) + ), + 'foreman'=>array( + 'password'=>$sFakePassword + ), + 'projects'=>array( + 'ldap'=>array( + 'PwLdapUser'=>$sFakePassword + ) + ), +); + +/** + * hide entries in config array + * @param type $aMask + * @param type $aConfig + * @return type + */ +function maskEntries($aMask, $aConfig){ + foreach ($aMask as $sKey=>$aValue){ + + if (array_key_exists($sKey, $aConfig)){ + $aConfig[$sKey]=(is_array($aValue) + ? maskEntries($aMask[$sKey], $aConfig[$sKey]) + : $aMask[$sKey] + ) + ; + } + } + return $aConfig; +} + if ($aParams["prj"] == "all") { // ------------------------------------------------------------ @@ -23,14 +62,10 @@ if ($aParams["prj"] == "all") { // ------------------------------------------------------------ if (!array_key_exists("par3", $aParams)) { $oPrj = new project(); - $sOut.='<h2>'.t("page-setup-info").'</h2>' - . '<p>'.t("page-setup-info-introtext").'</p>' - . '<p>' - . '<a href="./checklang/">'.t("page-setup-info-check-lang").'</a><br>' - . '<a href="./actionlog/">'.t("class-actionlog-title").'</a><br>' - . '</p>'; - + $aTmp=maskEntries($aMask, $aConfig); + $sOut.= '<pre>'.print_r($aTmp, 1).'</pre>'; + // print_r($aConfig); $i = 0; require_once ("./classes/formgen.class.php"); @@ -258,7 +293,7 @@ if ($aParams["prj"] == "all") { if (array_key_exists("setupaction", $aParams) && $aParams["setupaction"] == "save") { if ($oPrj->saveConfig()) { - $sOut.=$oHtml->getBox("success", t("page-setup-info-settings-were-saved")) . aPrjHome() . '<br><br>'; + $sOut.=$oHtml->getBox("success", t("page-setup-info-settings-were-saved")); } else { $sOut.=$oHtml->getBox("error", t("page-setup-error-settings-were-not-saved")); } diff --git a/public_html/vendor/shooker/shooker.php b/public_html/vendor/shooker/shooker.php new file mode 100644 index 0000000000000000000000000000000000000000..b042bb7956478e32379fdc7e94cd9772da314b1e --- /dev/null +++ b/public_html/vendor/shooker/shooker.php @@ -0,0 +1,164 @@ +<?php +/*------------------------------------------------------------------------ + * shooker.php + * Created by: John Wenzler (nnullandvoidd@gmail.com) + * Available under the MIT License + * FOR MORE INFO AND EXAMPLES, VISIT: + * https://github.com/jwenzler/Shooker + * + ------------------------------------------------------------------------*/ + +class Shooker { + + //No error by default + private $error = false; + private $errorMessages = array(); + + //Token to make sure the request is coming from Slack + private $token = null; + + //Incoming Slack endpoint + private $incomingURL = null; + + //Text to send back to Slack client + private $responseText = null; + + //Trigger array + private $triggers; + + function __constructNoToken() { + $this->triggers = new stdClass; + } + + function setupIncoming($incomingURL) { + $this->incomingURL = $incomingURL; + } + + function setupOutgoing($token) { + $this->token = $token; + } + + function addTrigger($triggerWord) { + $trigger = new ShookerTrigger($triggerWord); + $this->triggers->{$triggerWord} = $trigger; + return $trigger; + } + + function sendMessage($message, $username, $icon) { + $data = array( + 'text' => $message + ); + + if (isset($username)) { + $data['username'] = $username; + } + + if (isset($icon)) { + $data['icon_emoji'] = $icon; + } + + $options = array( + 'http' => array( + 'header' => 'Content-type: application/x-www-form-urlencoded\r\n', + 'method' => 'POST', + 'content' => json_encode($data) + ) + ); + + $context = stream_context_create($options); + $result = file_get_contents($this->incomingURL, false, $context); + + return $result; + } + + //Calculate response text and send it back to the Slack client + function listen() { + + //Retrieve the text inbound to the webhook + $inText = $_POST['text']; + //Retrieve the user name for the sent in message + $inUser = $_POST['user_name']; + //Retrieve the channel name message was sent in + $inChannel = $_POST['channel_name']; + + //Get the first word sent in (should match trigger) + $firstWord = explode(" ",$inText); + $firstWord = $firstWord[0]; + + $inText = substr($inText, strlen($firstWord)+1); + + if (isset($this->triggers->{$firstWord})) { + + //Retrieve the trigger + $trigger = $this->triggers->{$firstWord}; + + //Make sure there is an attached action to the trigger word + if (sizeof($trigger->actions) > 0) { + + //Loop through and complete any actions attached to trigger + foreach ($trigger->actions as $action) { + + //Check for token, this is a requirement for security reasons + if (isset($this->token)) { + //Make sure we have a POSTed token and that the token matches the token we have supplied + if (isset($_POST['token']) && $_POST['token'] == $this->token) { + + $this->responseText = $action($inText, $inUser, $inChannel); + + } else { + $this->error = true; + array_push($this->errorMessages, "Mismatched tokens, please make sure the token you supplied matches the one setup with Slack webhook"); + } + + } else { + $this->error = true; + array_push($this->errorMessages, "Please set a token with the constructor or using setToken function"); + } + + } + + } else { + $this->error = true; + array_push($this->errorMessages, "No action provided for the given trigger word: ".$firstWord); + } + + $ret = new stdClass; + + //If we are free from errors, return the response text we calculated + if (!$this->error) { + //Create a return for the Slack client + $ret->text = $this->responseText; + + //Otherwise we have an error, return all applicable messages + } else { + //Add error info + $ret->text = "`Error(s): ".implode(", ",$this->errorMessages)."`"; + } + + //Convert to a JSON string + $ret = json_encode($ret); + + echo $ret; + + } + } +} + + +//class Trigger class +class ShookerTrigger { + + private $triggerWord; + public $actions = array(); + + //Create a trigger with given triggerword + function __construct($triggerWord) { + $this->triggerWord = $triggerWord; + } + + function addAction($fxn) { + array_push($this->actions, $fxn); + } +} + +?> \ No newline at end of file diff --git a/public_html/vendor/spyc/spyc.php b/public_html/vendor/spyc/spyc.php new file mode 100644 index 0000000000000000000000000000000000000000..2cf1bbc263db23da335fa1d74c8184493d5f8e5a --- /dev/null +++ b/public_html/vendor/spyc/spyc.php @@ -0,0 +1,1161 @@ +<?php +/** + * Spyc -- A Simple PHP YAML Class + * @version 0.6.2 + * @author Vlad Andersen <vlad.andersen@gmail.com> + * @author Chris Wanstrath <chris@ozmm.org> + * @link https://github.com/mustangostang/spyc/ + * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @package Spyc + */ + +if (!function_exists('spyc_load')) { + /** + * Parses YAML to array. + * @param string $string YAML string. + * @return array + */ + function spyc_load ($string) { + return Spyc::YAMLLoadString($string); + } +} + +if (!function_exists('spyc_load_file')) { + /** + * Parses YAML to array. + * @param string $file Path to YAML file. + * @return array + */ + function spyc_load_file ($file) { + return Spyc::YAMLLoad($file); + } +} + +if (!function_exists('spyc_dump')) { + /** + * Dumps array to YAML. + * @param array $data Array. + * @return string + */ + function spyc_dump ($data) { + return Spyc::YAMLDump($data, false, false, true); + } +} + +if (!class_exists('Spyc')) { + +/** + * The Simple PHP YAML Class. + * + * This class can be used to read a YAML file and convert its contents + * into a PHP array. It currently supports a very limited subsection of + * the YAML spec. + * + * Usage: + * <code> + * $Spyc = new Spyc; + * $array = $Spyc->load($file); + * </code> + * or: + * <code> + * $array = Spyc::YAMLLoad($file); + * </code> + * or: + * <code> + * $array = spyc_load_file($file); + * </code> + * @package Spyc + */ +class Spyc { + + // SETTINGS + + const REMPTY = "\0\0\0\0\0"; + + /** + * Setting this to true will force YAMLDump to enclose any string value in + * quotes. False by default. + * + * @var bool + */ + public $setting_dump_force_quotes = false; + + /** + * Setting this to true will forse YAMLLoad to use syck_load function when + * possible. False by default. + * @var bool + */ + public $setting_use_syck_is_possible = false; + + + + /**#@+ + * @access private + * @var mixed + */ + private $_dumpIndent; + private $_dumpWordWrap; + private $_containsGroupAnchor = false; + private $_containsGroupAlias = false; + private $path; + private $result; + private $LiteralPlaceHolder = '___YAML_Literal_Block___'; + private $SavedGroups = array(); + private $indent; + /** + * Path modifier that should be applied after adding current element. + * @var array + */ + private $delayedPath = array(); + + /**#@+ + * @access public + * @var mixed + */ + public $_nodeId; + +/** + * Load a valid YAML string to Spyc. + * @param string $input + * @return array + */ + public function load ($input) { + return $this->_loadString($input); + } + + /** + * Load a valid YAML file to Spyc. + * @param string $file + * @return array + */ + public function loadFile ($file) { + return $this->_load($file); + } + + /** + * Load YAML into a PHP array statically + * + * The load method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. Pretty + * simple. + * Usage: + * <code> + * $array = Spyc::YAMLLoad('lucky.yaml'); + * print_r($array); + * </code> + * @access public + * @return array + * @param string $input Path of YAML file or string containing YAML + */ + public static function YAMLLoad($input) { + $Spyc = new Spyc; + return $Spyc->_load($input); + } + + /** + * Load a string of YAML into a PHP array statically + * + * The load method, when supplied with a YAML string, will do its best + * to convert YAML in a string into a PHP array. Pretty simple. + * + * Note: use this function if you don't want files from the file system + * loaded and processed as YAML. This is of interest to people concerned + * about security whose input is from a string. + * + * Usage: + * <code> + * $array = Spyc::YAMLLoadString("---\n0: hello world\n"); + * print_r($array); + * </code> + * @access public + * @return array + * @param string $input String containing YAML + */ + public static function YAMLLoadString($input) { + $Spyc = new Spyc; + return $Spyc->_loadString($input); + } + + /** + * Dump YAML from PHP array statically + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as nothing.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array|\stdClass $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + * @param bool $no_opening_dashes Do not start YAML file with "---\n" + */ + public static function YAMLDump($array, $indent = false, $wordwrap = false, $no_opening_dashes = false) { + $spyc = new Spyc; + return $spyc->dump($array, $indent, $wordwrap, $no_opening_dashes); + } + + + /** + * Dump PHP array to YAML + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as tasteful.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + public function dump($array,$indent = false,$wordwrap = false, $no_opening_dashes = false) { + // Dumps to some very clean YAML. We'll have to add some more features + // and options soon. And better support for folding. + + // New features and options. + if ($indent === false or !is_numeric($indent)) { + $this->_dumpIndent = 2; + } else { + $this->_dumpIndent = $indent; + } + + if ($wordwrap === false or !is_numeric($wordwrap)) { + $this->_dumpWordWrap = 40; + } else { + $this->_dumpWordWrap = $wordwrap; + } + + // New YAML document + $string = ""; + if (!$no_opening_dashes) $string = "---\n"; + + // Start at the base of the array and move through it. + if ($array) { + $array = (array)$array; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array); + $previous_key = $key; + } + } + return $string; + } + + /** + * Attempts to convert a key / value array item to YAML + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) { + if(is_object($value)) $value = (array)$value; + if (is_array($value)) { + if (empty ($value)) + return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array); + // It has children. What to do? + // Make it the right kind of item + $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array); + // Add the indent + $indent += $this->_dumpIndent; + // Yamlize the array + $string .= $this->_yamlizeArray($value,$indent); + } elseif (!is_array($value)) { + // It doesn't have children. Yip. + $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array); + } + return $string; + } + + /** + * Attempts to convert an array to YAML + * @access private + * @return string + * @param $array The array you want to convert + * @param $indent The indent of the current level + */ + private function _yamlizeArray($array,$indent) { + if (is_array($array)) { + $string = ''; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array); + $previous_key = $key; + } + return $string; + } else { + return false; + } + } + + /** + * Returns YAML from a key and a value + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) { + // do some folding here, for blocks + if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false || + strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, '%') !== false || strpos ($value, ' ') !== false || + strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 || + substr ($value, -1, 1) == ':') + ) { + $value = $this->_doLiteralBlock($value,$indent); + } else { + $value = $this->_doFolding($value,$indent); + } + + if ($value === array()) $value = '[ ]'; + if ($value === "") $value = '""'; + if (self::isTranslationWord($value)) { + $value = $this->_doLiteralBlock($value, $indent); + } + if (trim ($value) != $value) + $value = $this->_doLiteralBlock($value,$indent); + + if (is_bool($value)) { + $value = $value ? "true" : "false"; + } + + if ($value === null) $value = 'null'; + if ($value === "'" . self::REMPTY . "'") $value = null; + + $spaces = str_repeat(' ',$indent); + + //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) { + if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) { + // It's a sequence + $string = $spaces.'- '.$value."\n"; + } else { + // if ($first_key===0) throw new Exception('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"'); + // It's mapped + if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; } + $string = rtrim ($spaces.$key.': '.$value)."\n"; + } + return $string; + } + + /** + * Creates a literal block for dumping + * @access private + * @return string + * @param $value + * @param $indent int The value of the indent + */ + private function _doLiteralBlock($value,$indent) { + if ($value === "\n") return '\n'; + if (strpos($value, "\n") === false && strpos($value, "'") === false) { + return sprintf ("'%s'", $value); + } + if (strpos($value, "\n") === false && strpos($value, '"') === false) { + return sprintf ('"%s"', $value); + } + $exploded = explode("\n",$value); + $newValue = '|'; + if (isset($exploded[0]) && ($exploded[0] == "|" || $exploded[0] == "|-" || $exploded[0] == ">")) { + $newValue = $exploded[0]; + unset($exploded[0]); + } + $indent += $this->_dumpIndent; + $spaces = str_repeat(' ',$indent); + foreach ($exploded as $line) { + $line = trim($line); + if (strpos($line, '"') === 0 && strrpos($line, '"') == (strlen($line)-1) || strpos($line, "'") === 0 && strrpos($line, "'") == (strlen($line)-1)) { + $line = substr($line, 1, -1); + } + $newValue .= "\n" . $spaces . ($line); + } + return $newValue; + } + + /** + * Folds a string of text, if necessary + * @access private + * @return string + * @param $value The string you wish to fold + */ + private function _doFolding($value,$indent) { + // Don't do anything if wordwrap is set to 0 + + if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) { + $indent += $this->_dumpIndent; + $indent = str_repeat(' ',$indent); + $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); + $value = ">\n".$indent.$wrapped; + } else { + if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY) + $value = '"' . $value . '"'; + if (is_numeric($value) && is_string($value)) + $value = '"' . $value . '"'; + } + + + return $value; + } + + private function isTrueWord($value) { + $words = self::getTranslations(array('true', 'on', 'yes', 'y')); + return in_array($value, $words, true); + } + + private function isFalseWord($value) { + $words = self::getTranslations(array('false', 'off', 'no', 'n')); + return in_array($value, $words, true); + } + + private function isNullWord($value) { + $words = self::getTranslations(array('null', '~')); + return in_array($value, $words, true); + } + + private function isTranslationWord($value) { + return ( + self::isTrueWord($value) || + self::isFalseWord($value) || + self::isNullWord($value) + ); + } + + /** + * Coerce a string into a native type + * Reference: http://yaml.org/type/bool.html + * TODO: Use only words from the YAML spec. + * @access private + * @param $value The value to coerce + */ + private function coerceValue(&$value) { + if (self::isTrueWord($value)) { + $value = true; + } else if (self::isFalseWord($value)) { + $value = false; + } else if (self::isNullWord($value)) { + $value = null; + } + } + + /** + * Given a set of words, perform the appropriate translations on them to + * match the YAML 1.1 specification for type coercing. + * @param $words The words to translate + * @access private + */ + private static function getTranslations(array $words) { + $result = array(); + foreach ($words as $i) { + $result = array_merge($result, array(ucfirst($i), strtoupper($i), strtolower($i))); + } + return $result; + } + +// LOADING FUNCTIONS + + private function _load($input) { + $Source = $this->loadFromSource($input); + return $this->loadWithSource($Source); + } + + private function _loadString($input) { + $Source = $this->loadFromString($input); + return $this->loadWithSource($Source); + } + + private function loadWithSource($Source) { + if (empty ($Source)) return array(); + if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) { + $array = syck_load (implode ("\n", $Source)); + return is_array($array) ? $array : array(); + } + + $this->path = array(); + $this->result = array(); + + $cnt = count($Source); + for ($i = 0; $i < $cnt; $i++) { + $line = $Source[$i]; + + $this->indent = strlen($line) - strlen(ltrim($line)); + $tempPath = $this->getParentPathByIndent($this->indent); + $line = self::stripIndent($line, $this->indent); + if (self::isComment($line)) continue; + if (self::isEmpty($line)) continue; + $this->path = $tempPath; + + $literalBlockStyle = self::startsLiteralBlock($line); + if ($literalBlockStyle) { + $line = rtrim ($line, $literalBlockStyle . " \n"); + $literalBlock = ''; + $line .= ' '.$this->LiteralPlaceHolder; + $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1])); + while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) { + $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent); + } + $i--; + } + + // Strip out comments + if (strpos ($line, '#')) { + $line = preg_replace('/\s*#([^"\']+)$/','',$line); + } + + while (++$i < $cnt && self::greedilyNeedNextLine($line)) { + $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t"); + } + $i--; + + $lineArray = $this->_parseLine($line); + + if ($literalBlockStyle) + $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock); + + $this->addArray($lineArray, $this->indent); + + foreach ($this->delayedPath as $indent => $delayedPath) + $this->path[$indent] = $delayedPath; + + $this->delayedPath = array(); + + } + return $this->result; + } + + private function loadFromSource ($input) { + if (!empty($input) && strpos($input, "\n") === false && file_exists($input)) + $input = file_get_contents($input); + + return $this->loadFromString($input); + } + + private function loadFromString ($input) { + $lines = explode("\n",$input); + foreach ($lines as $k => $_) { + $lines[$k] = rtrim ($_, "\r"); + } + return $lines; + } + + /** + * Parses YAML code and returns an array for a node + * @access private + * @return array + * @param string $line A line from the YAML file + */ + private function _parseLine($line) { + if (!$line) return array(); + $line = trim($line); + if (!$line) return array(); + + $array = array(); + + $group = $this->nodeContainsGroup($line); + if ($group) { + $this->addGroup($line, $group); + $line = $this->stripGroup ($line, $group); + } + + if ($this->startsMappedSequence($line)) + return $this->returnMappedSequence($line); + + if ($this->startsMappedValue($line)) + return $this->returnMappedValue($line); + + if ($this->isArrayElement($line)) + return $this->returnArrayElement($line); + + if ($this->isPlainArray($line)) + return $this->returnPlainArray($line); + + + return $this->returnKeyValuePair($line); + + } + + /** + * Finds the type of the passed value, returns the value as the new type. + * @access private + * @param string $value + * @return mixed + */ + private function _toType($value) { + if ($value === '') return ""; + $first_character = $value[0]; + $last_character = substr($value, -1, 1); + + $is_quoted = false; + do { + if (!$value) break; + if ($first_character != '"' && $first_character != "'") break; + if ($last_character != '"' && $last_character != "'") break; + $is_quoted = true; + } while (0); + + if ($is_quoted) { + $value = str_replace('\n', "\n", $value); + if ($first_character == "'") + return strtr(substr ($value, 1, -1), array ('\'\'' => '\'', '\\\''=> '\'')); + return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\\\''=> '\'')); + } + + if (strpos($value, ' #') !== false && !$is_quoted) + $value = preg_replace('/\s+#(.+)$/','',$value); + + if ($first_character == '[' && $last_character == ']') { + // Take out strings sequences and mappings + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $value = array(); + foreach ($explode as $v) { + $value[] = $this->_toType($v); + } + return $value; + } + + if (strpos($value,': ')!==false && $first_character != '{') { + $array = explode(': ',$value); + $key = trim($array[0]); + array_shift($array); + $value = trim(implode(': ',$array)); + $value = $this->_toType($value); + return array($key => $value); + } + + if ($first_character == '{' && $last_character == '}') { + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + // Inline Mapping + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $array = array(); + foreach ($explode as $v) { + $SubArr = $this->_toType($v); + if (empty($SubArr)) continue; + if (is_array ($SubArr)) { + $array[key($SubArr)] = $SubArr[key($SubArr)]; continue; + } + $array[] = $SubArr; + } + return $array; + } + + if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') { + return null; + } + + if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){ + $intvalue = (int)$value; + if ($intvalue != PHP_INT_MAX && $intvalue != ~PHP_INT_MAX) + $value = $intvalue; + return $value; + } + + if ( is_string($value) && preg_match('/^0[xX][0-9a-fA-F]+$/', $value)) { + // Hexadecimal value. + return hexdec($value); + } + + $this->coerceValue($value); + + if (is_numeric($value)) { + if ($value === '0') return 0; + if (rtrim ($value, 0) === $value) + $value = (float)$value; + return $value; + } + + return $value; + } + + /** + * Used in inlines to check for more inlines or quoted strings + * @access private + * @return array + */ + private function _inlineEscape($inline) { + // There's gotta be a cleaner way to do this... + // While pure sequences seem to be nesting just fine, + // pure mappings and mappings with sequences inside can't go very + // deep. This needs to be fixed. + + $seqs = array(); + $maps = array(); + $saved_strings = array(); + $saved_empties = array(); + + // Check for empty strings + $regex = '/("")|(\'\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_empties = $strings[0]; + $inline = preg_replace($regex,'YAMLEmpty',$inline); + } + unset($regex); + + // Check for strings + $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_strings = $strings[0]; + $inline = preg_replace($regex,'YAMLString',$inline); + } + unset($regex); + + // echo $inline; + + $i = 0; + do { + + // Check for sequences + while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) { + $seqs[] = $matchseqs[0]; + $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1); + } + + // Check for mappings + while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) { + $maps[] = $matchmaps[0]; + $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1); + } + + if ($i++ >= 10) break; + + } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false); + + $explode = explode(',',$inline); + $explode = array_map('trim', $explode); + $stringi = 0; $i = 0; + + while (1) { + + // Re-add the sequences + if (!empty($seqs)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + foreach ($seqs as $seqk => $seq) { + $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value); + $value = $explode[$key]; + } + } + } + } + + // Re-add the mappings + if (!empty($maps)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLMap') !== false) { + foreach ($maps as $mapk => $map) { + $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value); + $value = $explode[$key]; + } + } + } + } + + + // Re-add the strings + if (!empty($saved_strings)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLString') !== false) { + $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1); + unset($saved_strings[$stringi]); + ++$stringi; + $value = $explode[$key]; + } + } + } + + + // Re-add the empties + if (!empty($saved_empties)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLEmpty') !== false) { + $explode[$key] = preg_replace('/YAMLEmpty/', '', $value, 1); + $value = $explode[$key]; + } + } + } + + $finished = true; + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLMap') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLString') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLEmpty') !== false) { + $finished = false; break; + } + } + if ($finished) break; + + $i++; + if ($i > 10) + break; // Prevent infinite loops. + } + + + return $explode; + } + + private function literalBlockContinues ($line, $lineIndent) { + if (!trim($line)) return true; + if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true; + return false; + } + + private function referenceContentsByAlias ($alias) { + do { + if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; } + $groupPath = $this->SavedGroups[$alias]; + $value = $this->result; + foreach ($groupPath as $k) { + $value = $value[$k]; + } + } while (false); + return $value; + } + + private function addArrayInline ($array, $indent) { + $CommonGroupPath = $this->path; + if (empty ($array)) return false; + + foreach ($array as $k => $_) { + $this->addArray(array($k => $_), $indent); + $this->path = $CommonGroupPath; + } + return true; + } + + private function addArray ($incoming_data, $incoming_indent) { + + // print_r ($incoming_data); + + if (count ($incoming_data) > 1) + return $this->addArrayInline ($incoming_data, $incoming_indent); + + $key = key ($incoming_data); + $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null; + if ($key === '__!YAMLZero') $key = '0'; + + if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values. + if ($key || $key === '' || $key === '0') { + $this->result[$key] = $value; + } else { + $this->result[] = $value; end ($this->result); $key = key ($this->result); + } + $this->path[$incoming_indent] = $key; + return; + } + + + + $history = array(); + // Unfolding inner array tree. + $history[] = $_arr = $this->result; + foreach ($this->path as $k) { + $history[] = $_arr = $_arr[$k]; + } + + if ($this->_containsGroupAlias) { + $value = $this->referenceContentsByAlias($this->_containsGroupAlias); + $this->_containsGroupAlias = false; + } + + + // Adding string or numeric key to the innermost level or $this->arr. + if (is_string($key) && $key == '<<') { + if (!is_array ($_arr)) { $_arr = array (); } + + $_arr = array_merge ($_arr, $value); + } else if ($key || $key === '' || $key === '0') { + if (!is_array ($_arr)) + $_arr = array ($key=>$value); + else + $_arr[$key] = $value; + } else { + if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; } + else { $_arr[] = $value; end ($_arr); $key = key ($_arr); } + } + + $reverse_path = array_reverse($this->path); + $reverse_history = array_reverse ($history); + $reverse_history[0] = $_arr; + $cnt = count($reverse_history) - 1; + for ($i = 0; $i < $cnt; $i++) { + $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i]; + } + $this->result = $reverse_history[$cnt]; + + $this->path[$incoming_indent] = $key; + + if ($this->_containsGroupAnchor) { + $this->SavedGroups[$this->_containsGroupAnchor] = $this->path; + if (is_array ($value)) { + $k = key ($value); + if (!is_int ($k)) { + $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k; + } + } + $this->_containsGroupAnchor = false; + } + + } + + private static function startsLiteralBlock ($line) { + $lastChar = substr (trim($line), -1); + if ($lastChar != '>' && $lastChar != '|') return false; + if ($lastChar == '|') return $lastChar; + // HTML tags should not be counted as literal blocks. + if (preg_match ('#<.*?>$#', $line)) return false; + return $lastChar; + } + + private static function greedilyNeedNextLine($line) { + $line = trim ($line); + if (!strlen($line)) return false; + if (substr ($line, -1, 1) == ']') return false; + if ($line[0] == '[') return true; + if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true; + return false; + } + + private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) { + $line = self::stripIndent($line, $indent); + if ($literalBlockStyle !== '|') { + $line = self::stripIndent($line); + } + $line = rtrim ($line, "\r\n\t ") . "\n"; + if ($literalBlockStyle == '|') { + return $literalBlock . $line; + } + if (strlen($line) == 0) + return rtrim($literalBlock, ' ') . "\n"; + if ($line == "\n" && $literalBlockStyle == '>') { + return rtrim ($literalBlock, " \t") . "\n"; + } + if ($line != "\n") + $line = trim ($line, "\r\n ") . " "; + return $literalBlock . $line; + } + + function revertLiteralPlaceHolder ($lineArray, $literalBlock) { + foreach ($lineArray as $k => $_) { + if (is_array($_)) + $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock); + else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder) + $lineArray[$k] = rtrim ($literalBlock, " \r\n"); + } + return $lineArray; + } + + private static function stripIndent ($line, $indent = -1) { + if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line)); + return substr ($line, $indent); + } + + private function getParentPathByIndent ($indent) { + if ($indent == 0) return array(); + $linePath = $this->path; + do { + end($linePath); $lastIndentInParentPath = key($linePath); + if ($indent <= $lastIndentInParentPath) array_pop ($linePath); + } while ($indent <= $lastIndentInParentPath); + return $linePath; + } + + + private function clearBiggerPathValues ($indent) { + + + if ($indent == 0) $this->path = array(); + if (empty ($this->path)) return true; + + foreach ($this->path as $k => $_) { + if ($k > $indent) unset ($this->path[$k]); + } + + return true; + } + + + private static function isComment ($line) { + if (!$line) return false; + if ($line[0] == '#') return true; + if (trim($line, " \r\n\t") == '---') return true; + return false; + } + + private static function isEmpty ($line) { + return (trim ($line) === ''); + } + + + private function isArrayElement ($line) { + if (!$line || !is_scalar($line)) return false; + if (substr($line, 0, 2) != '- ') return false; + if (strlen ($line) > 3) + if (substr($line,0,3) == '---') return false; + + return true; + } + + private function isHashElement ($line) { + return strpos($line, ':'); + } + + private function isLiteral ($line) { + if ($this->isArrayElement($line)) return false; + if ($this->isHashElement($line)) return false; + return true; + } + + + private static function unquote ($value) { + if (!$value) return $value; + if (!is_string($value)) return $value; + if ($value[0] == '\'') return trim ($value, '\''); + if ($value[0] == '"') return trim ($value, '"'); + return $value; + } + + private function startsMappedSequence ($line) { + return (substr($line, 0, 2) == '- ' && substr ($line, -1, 1) == ':'); + } + + private function returnMappedSequence ($line) { + $array = array(); + $key = self::unquote(trim(substr($line,1,-1))); + $array[$key] = array(); + $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key); + return array($array); + } + + private function checkKeysInValue($value) { + if (strchr('[{"\'', $value[0]) === false) { + if (strchr($value, ': ') !== false) { + throw new Exception('Too many keys: '.$value); + } + } + } + + private function returnMappedValue ($line) { + $this->checkKeysInValue($line); + $array = array(); + $key = self::unquote (trim(substr($line,0,-1))); + $array[$key] = ''; + return $array; + } + + private function startsMappedValue ($line) { + return (substr ($line, -1, 1) == ':'); + } + + private function isPlainArray ($line) { + return ($line[0] == '[' && substr ($line, -1, 1) == ']'); + } + + private function returnPlainArray ($line) { + return $this->_toType($line); + } + + private function returnKeyValuePair ($line) { + $array = array(); + $key = ''; + if (strpos ($line, ': ')) { + // It's a key/value pair most likely + // If the key is in double quotes pull it out + if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { + $value = trim(str_replace($matches[1],'',$line)); + $key = $matches[2]; + } else { + // Do some guesswork as to the key and the value + $explode = explode(': ', $line); + $key = trim(array_shift($explode)); + $value = trim(implode(': ', $explode)); + $this->checkKeysInValue($value); + } + // Set the type of the value. Int, string, etc + $value = $this->_toType($value); + if ($key === '0') $key = '__!YAMLZero'; + $array[$key] = $value; + } else { + $array = array ($line); + } + return $array; + + } + + + private function returnArrayElement ($line) { + if (strlen($line) <= 1) return array(array()); // Weird %) + $array = array(); + $value = trim(substr($line,1)); + $value = $this->_toType($value); + if ($this->isArrayElement($value)) { + $value = $this->returnArrayElement($value); + } + $array[] = $value; + return $array; + } + + + private function nodeContainsGroup ($line) { + $symbolsForReference = 'A-z0-9_\-'; + if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-) + if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1]; + if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1]; + if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1]; + return false; + + } + + private function addGroup ($line, $group) { + if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1); + if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1); + //print_r ($this->path); + } + + private function stripGroup ($line, $group) { + $line = trim(str_replace($group, '', $line)); + return $line; + } +} +} + +// Enable use of Spyc from command line +// The syntax is the following: php Spyc.php spyc.yaml + +do { + if (PHP_SAPI != 'cli') break; + if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break; + if (empty ($_SERVER['PHP_SELF']) || FALSE === strpos ($_SERVER['PHP_SELF'], 'Spyc.php') ) break; + $file = $argv[1]; + echo json_encode (spyc_load_file ($file)); +} while (0); \ No newline at end of file