diff --git a/public_html/deployment/classes/deploy-foreman.class.php b/public_html/deployment/classes/deploy-foreman.class.php
new file mode 100644
index 0000000000000000000000000000000000000000..b99127d115155e3d06941b29762f9786ca57f49d
--- /dev/null
+++ b/public_html/deployment/classes/deploy-foreman.class.php
@@ -0,0 +1,490 @@
+<?php
+/**
+ * deployForeman
+ * 
+ * foreman access to API
+ * 
+ * @example
+ * in project class
+ * $oForeman=new deployForeman($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 deployForeman {
+
+    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='?';
+        
+        // TODO: "sort by" and "sort order" ... need to be added here ... somehow
+        foreach(array('page', 'per_page', 'search') as $sParam){
+            if (array_key_exists($sParam, $this->_aRequest['filter'])){
+                $sReturn.='&'.$sParam.'='.urlencode($this->_aRequest['filter'][$sParam]);
+            }
+        }
+        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 = 5) {
+        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);
+        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");
+        }
+        
+        $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;
+        }
+        $aTmp=array_key_exists('results', $aData['_json']) ? $aData['_json']['results'] : array($aData['_json']);
+        if(!count($aTmp)){
+            return array();
+        }
+        $aReturn=array();
+        foreach($aTmp as $aItem){
+            $aReturn[] = array_filter($aItem, function($key) {
+                return array_search($key, $this->_aRequest['response']) !==false;
+                }, ARRAY_FILTER_USE_KEY
+            );
+        }
+        return $aReturn;
+    }
+
+
+    // ----------------------------------------------------------------------
+    // 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){
+    }
+    /**
+     * GETTER
+     * $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')
+     * 
+     * @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){
+        
+    }
+    
+    /**
+     * TODO
+     * @param type $aOptions
+     */
+    public function delete($aOptions){
+        
+    }
+    
+}