Skip to content
Snippets Groups Projects
rollout_base.class.php 19.21 KiB
<?php
require_once 'rollout.interface.php';

/**
 * rollout_base class that will beextended in a rollout plugin
 *
 * 
 * @author axel
 */
class rollout_base implements iRolloutplugin{
    
    // ---------------------------------------------------------------
    // VARIABLES
    // ---------------------------------------------------------------
    /**
     * identifier for current plugin; it us used to find the current plugin
     * settings in the config structore for global and project based config
     * @var string
     */
    protected $_sPluginId='UNSET';
    /**
     * data with plugin infos (read from info.json)
     * @var array
     */
    protected $_aPlugininfos=false;
    
    /**
     * array with translation texts
     * @var type
     */
    protected $_aLang=false;
    
    /**
     * set language; 2 letter code, i.e. "de"; default language is "en" ; a 
     * file "lang_en.json" is required in the plugin dir
     * @var string
     */
    protected $_sLang = 'en';
    
    /**
     * string with phase of project; one of preview|stage|live
     * @var type
     */
    protected $_sPhase = false;
    
    /**
     * global configuration of the rollout plugin
     * @var array
     */
    protected $_aCfgGlobal = false;
    /**
     * configuration of the project
     * @var array
     */
    protected $_aCfgProject = false;
    
    protected $_sNamePrefix4Project=false; // set in constructor
    protected $_sNamePrefix4Phase=false; // set in constructor
    
    // ---------------------------------------------------------------
    // CONSTRUCTOR
    // ---------------------------------------------------------------

    /**
     * initialize rollout plugin
     * @param array $aParams  hash with those possible keys
     *                  lang         string   language, i.e. 'de'
     *                  phase        string   name of phase in a project
     *                  globalcfg    array    given global config $aConfig
     *                  projectcfg   array    project config to generate config 
     *                                        for project and all phases
     * @return boolean
     */
    public function __construct($aParams) {
        
        // set current plugin id - taken from plugin directory name above
        $oReflection=new ReflectionClass($this);
        $this->_sPluginId=basename(dirname($oReflection->getFileName()));
   
        // ----- init language
        if (isset($aParams['lang'])){
            $this->setLang($aParams['lang']);
        } else {
            $this->setLang();
        }
        
        // ----- init phase
        if (isset($aParams['phase'])){
            $this->setPhase($aParams['phase']);
        }

        // ----- init global config
        if (isset($aParams['globalcfg'])){
            $this->setGlobalConfig($aParams['globalcfg']);
        }
        // ----- init project config
        if (isset($aParams['projectcfg'])){
            $this->setProjectConfig($aParams['projectcfg']);
        }
        return true;
    }
    
    // ---------------------------------------------------------------
    // FORM HELPER
    // ---------------------------------------------------------------

    /**
     * get a string for a prefix for name attribute in form vars. 
     * It is important to store the value in the wanted structure.
     * 
     * @param type $sPhase
     * @return type
     */
    protected function _getNamePrefix($sPhase=false){
        return ($sPhase
            ? 'phases['.$sPhase.'][plugins][rollout]['.$this->getId().']'
            : 'plugins[rollout]['.$this->getId().']'
        );
    }
    /**
     * render a form by given form elementes 
     * @param  array   $aFormdata  array of form elements
     * @param  string  $sKey       part of the identifier used in id of the input field
     * @return string
     */
    protected function _renderForm($aFormdata, $sKey){
        static $i;
        if (!isset($i)){
            $i=0;
        }

        $sReturn='';
        $sKeyPrefix=$this->getId().'_'.$sKey;
        
        $oForm = new formgen($aForms);
        foreach ($aFormdata as $elementData) {
            $elementKey=$sKeyPrefix.'_'.$i++;
            $sReturn.=$oForm->renderHtmlElement($elementKey, $elementData);
        }
        return $sReturn;
    }
    
    /**
     * render form fields for global plugin variables
     * @param string  $sScope       scope of vars ... one of global|project|phase
     * @param string  $sPhase       optional: render global vars in a phase; if no phase was set it renders form fields for project based settings
     * @return string
     */
    protected function _renderForm4Vars($sScope, $sPhase=false){
        $sReturn='';
        
        // test vars from info.json file
        $aInfos=$this->getPluginInfos();
        if(!isset($aInfos['vars'][$sScope]) || !count($aInfos['vars'][$sScope])){
            return '';
        }
        
        $sKey=($sPhase ? 'phase_'.$sPhase : 'project');
        $sPrefixName=$this->_getNamePrefix($sPhase);
        
        // set defaults - to be used in placeholder attribute
        // no phase = project level - take global defaults of ci config
        // on a phase - fetch merged cofig data of project
        $aDefaultValues=($sPhase ? $this->getConfig() : $this->_aCfgGlobal);
        $aDefaultSource=($sPhase ? 'project' : 'global');
                
        // set defaults - to be used in value attribute
        $aValues=($sPhase 
                ? $this->_aCfgProject['phases'][$sPhase]['plugins']['rollout'][$this->getId()] 
                : $this->_aCfgProject['plugins']['rollout'][$this->getId()]
        );
        
 
        // create form fields
        // $aFormdata[]=array('type' => 'markup','value' => '<br>'.$this->_t('section-override-'.$sScope.'-vars').':');
        $aFormdata[]=array('type' => 'markup','value' => '<div style="style: clear: left;"></div><h4>'.$this->getId() .' :: '. $sScope.'</h4>');
        
        $sMiss='';
        foreach ($aInfos['vars'][$sScope] as $sVarname=>$aVarinfos){
            if ($sScope==='global' && !isset($this->_aCfgGlobal[$sVarname])){
                $sMiss.='- plugin var was not set in global CI config: "'.$sVarname.'".<br>';
            }
            
            
            $sMyPlaceholder=(isset($aDefaultValues[$sVarname]) 
                                ? htmlentities($aDefaultValues[$sVarname]) 
                                : (isset($aVarinfos['default']) ? $aVarinfos['default'] : 'N.A.')
                );
            switch ($aVarinfos['type']) {
                case "password":
                    $sMyPlaceholder=(isset($aDefaultValues[$sVarname]) 
                                        ? '******************************'
                                        : $sMyPlaceholder
                        );
                    $aFormdata[]=array(
                        'type' => $aVarinfos['type'],
                        'name' => $sPrefixName.'['.$sVarname.']',
                        'label' => $this->_t($sVarname.'-label'),
                        'title' => $this->_t($sVarname.'-hint'),
                        'value' => (isset($aValues[$sVarname]) ? htmlentities($aValues[$sVarname]) : ''),
                        // 'required' => 'required',
                        'validate' => 'isastring',
                        // 'size' => 25,
                        // 'placeholder' => (isset($this->_aCfgGlobal[$sVarname]) ? $this->_aCfgGlobal[$sVarname] : '') . ' | '.$aDefaultValues[$sVarname],
                        'placeholder' => $sMyPlaceholder       
                    );
                    break;
                case "text":
                    $aFormdata[]=array(
                        'type' => $aVarinfos['type'],
                        'name' => $sPrefixName.'['.$sVarname.']',
                        'label' => $this->_t($sVarname.'-label'),
                        'ondblclick' => ($aDefaultValues[$sVarname] ? 'if (this.value==\'\') { this.value=\''.$aDefaultValues[$sVarname].'\' }' : ''),
                        'onfocusout' => ($aDefaultValues[$sVarname] ? 'if (this.value==\''.$aDefaultValues[$sVarname].'\') { this.value=\'\' }' : ''),
                        'title' => htmlentities($this->_t($sVarname.'-hint')."\n"
                            . ($this->_aCfgGlobal[$sVarname] ? '- global: '.$this->_aCfgGlobal[$sVarname]."\n" : '')
                            . ($this->_aCfgProject['plugins']['rollout'][$this->getId()][$sVarname] ? '- project: '.$this->_aCfgProject['plugins']['rollout'][$this->getId()][$sVarname]."\n" : '')
                            )
                            ,
                        'value' => (isset($aValues[$sVarname]) ? htmlentities($aValues[$sVarname]) : ''),
                        // 'required' => 'required',
                        'validate' => 'isastring',
                        // 'size' => 25,
                        // 'placeholder' => (isset($this->_aCfgGlobal[$sVarname]) ? $this->_aCfgGlobal[$sVarname] : '') . ' | '.$aDefaultValues[$sVarname],
                        'placeholder' => $sMyPlaceholder       
                    );
                    break;

                default:
                   $sMiss.='- plugin var "'.$sVarname.'" was not rendered - its type "'.$aVarinfos['type'].'" is not supported in the general form renderer.<br>';
                   break;
            }
        }
        // $aFormdata[]=array('type' => 'markup','value' => '<div style="style: clear: left;"></div><br><br>');
        return $this->_renderForm($aFormdata, $sKey)
            . ($sMiss 
                ? '<pre>WARNINGS:<br>'.$sMiss.'</pre>' . ($sScope==='global' ? $this -> renderCfgExample() : '' )
                : ''
            )
            ;
    }
    
    /**
     * get a translated text from lang_XX.json in plugin dir;
     * If the key is missed it returns "[KEY :: LANG]"
     * 
     * @see setLang()
     * @param string $sKey  key to find in lang file
     * @return string
     */
    protected function _t($sKey){
        return (isset($this->_aLang[$sKey]) && $this->_aLang[$sKey])
                ? $this->_aLang[$sKey]
                : "[ $sKey :: $this->_sLang ]"
        ;
    }

    // ---------------------------------------------------------------
    // PUBLIC METHODS
    // ---------------------------------------------------------------

    /**
     * set language for output of formdata and other texts.
     * This method loads the language file into a hash. The output of 
     * translated texts can be done with $this->_t("your_key")
     * 
     * @see _t()
     * @param string   $sLang  language code, i.e. "de"
     * @return boolean
     */
    public function setLang($sLang=false){
        $this->_sLang=$sLang ? $sLang : $this->_sLang;
        
        $oReflection=new ReflectionClass($this);
        $sFile=dirname($oReflection->getFileName()) . '/lang_'.$this->_sLang.'.json';
        $this->_aLang=(file_exists($sFile)) ? json_decode(file_get_contents($sFile), 1) : $this->_aLang;
        return true;
    }
    
    /**
     * set a phase for automatic use GETTER methods
     */
    public function setPhase($sPhase){
        $this->_sPhase=$sPhase;
        return true;
    }
    

    // ----------------------------------------------------------------------
    // INTERFACE :: CHECKS
    // ----------------------------------------------------------------------

    /**
     * check requirements if the plugin could work
     */
    public function checkRequirements(){
        // no specific checks needed ... always true
        return true;
    }

    /**
     * check access to a deploy target
     */
    public function checkConnectionToTarget(){
        // do nothing ... always true
        return true;
    }

    // ----------------------------------------------------------------------
    // INTERFACE :: SETTER
    // ----------------------------------------------------------------------


    /**
     * set Config ... by given global config of the current plugin
     * @param array $aConfigArray 
     */
    public function setGlobalConfig($aConfigArray){
        return $this->_aCfgGlobal=$aConfigArray;
    }



    /**
     * set Config ... by given project config
     */
    public function setProjectConfig($aProjectConfigArray){
        $this->_aCfgProject=$aProjectConfigArray;
        // echo '<pre>'.print_r($aProjectConfigArray, 1).'</pre>';
        // ----- ensure that the config structure exists 
        // (it is easier fo handling in getConfig())
        if (!isset($this->_aCfgProject['plugins']['rollout'][$this->_sPluginId])){
            /*
            if (!isset($this->_aCfgProject['plugins']['rollout'])){
                if (!isset($this->_aCfgProject['plugins'])){
                    $this->_aCfgProject['plugins']=array();
                }
                $this->_aCfgProject['plugins']['rollout']=array();
            }
             * 
             */
            $this->_aCfgProject['plugins']['rollout'][$this->_sPluginId]=array('INFO'=>'created');
        }
        
        // unset empty project values to get global values

        foreach ($this->_aCfgProject['plugins']['rollout'][$this->_sPluginId] as $sVarname=>$value){
            if ($value===''){
                unset($this->_aCfgProject['plugins']['rollout'][$this->_sPluginId][$sVarname]);
            }
        }
        foreach (array_keys($this->_aCfgProject['phases']) as $sMyPhase){
            if (isset($this->_aCfgProject['phases'][$sMyPhase]['plugins']['rollout'][$this->getId()])){
                foreach($this->_aCfgProject['phases'][$sMyPhase]['plugins']['rollout'][$this->getId()] as $sVarname=>$value){
                    if ($value===''){
                        unset($this->_aCfgProject['phases'][$sMyPhase]['plugins']['rollout'][$this->getId()][$sVarname]);
                    }
                }
            }
        }
        // TODO: 
        return $this->_aCfgProject;
    }
    
    // ----------------------------------------------------------------------
    // INTERFACE :: GETTER
    // ----------------------------------------------------------------------

    /**
     * get a hash with the merged config for project or mo specific: of a given 
     * phase
     * @param  string  $sPhase
     * @return array
     */
    public function getConfig($sPhase=false){

        return ($sPhase && isset($this->_aCfgProject['phases'][$sPhase]['plugins']['rollout'][$this->getId()]))
            ? array_merge($this->_aCfgGlobal, $this->_aCfgProject['plugins']['rollout'][$this->getId()], $this->_aCfgProject['phases'][$sPhase]['plugins']['rollout'][$this->getId()])
            : array_merge($this->_aCfgGlobal, $this->_aCfgProject['plugins']['rollout'][$this->getId()])
        ;
    }
    
    /**
     * get an array with shell commands to execute
     * @param  string  $sPhase
     * @return array
     */
    public function getDeployCommands($sPhase){
        return [
            'echo "ERROR: The method getDeployCommamds($sPhase) was not implemented in the rollout plugin ['.$this->getId().']"',
            'exit 1'
            ];
    }
    
    /**
     * get string with current ID
     * @return string
     */
    public function getId(){
        return $this->_sPluginId;
    }
    
    /**
     * get string with plugin name (taken from plugin language file)
     * @return string
     */
    public function getName(){
        return $this->_t('plugin_name');
    }
    
    /**
     * get string with plugin description (taken from plugin language file)
     * @return string
     */
    public function getDescription(){
        return $this->_t('description');
    }
    /**
     * get array read from info.json
     * @return type
     */
    public function getPluginInfos(){

        if ($this->_aPlugininfos){
            return $this->_aPlugininfos;
        }
        
        $oReflection=new ReflectionClass($this);
        $sFile=dirname($oReflection->getFileName()) . '/info.json';
        $this->_aPlugininfos= (file_exists($sFile))
            ? json_decode(file_get_contents($sFile), 1)
            : array('error'=> 'unable to read info file ['.$sFile.'].')
        ;
        return $this->_aPlugininfos;
    }

    // ----------------------------------------------------------------------
    // INTERFACE :: RENDERER
    // ----------------------------------------------------------------------
    public function renderCfgExample(){
        $sReturn='';
        $sPre='                ';
        
        $aInfos=$this->getPluginInfos();
        $sReturn.='<pre>$aConfig = array(
    ...
    \'plugins\'=>array(
        ...
        // enabled rollout plugins
        \'rollout\'=>array(
            ...
            <strong>
            \''.$this->getId().'\'=>array(
                // '.$this->getName().'
                // '.$this->getDescription().'
                '.PHP_EOL;
        
        // add global vars
        if(!isset($aInfos['vars']['global']) || !count($aInfos['vars']['global'])){
            $sReturn.=$sPre.'// this plugin has no global config vars'.PHP_EOL;
        } else {
            foreach ($aInfos['vars']['global'] as $sVar=>$aItem){
                $sReturn.=$sPre.'// '.$this->_t($sVar.'-hint').PHP_EOL;
                $sReturn.=$sPre.'\''.$sVar.'\'=>\''.(isset($this->_aCfgGlobal[$sVar]) ? $this->_aCfgGlobal[$sVar] : $aItem['default']).'\','.PHP_EOL;
                $sReturn.=PHP_EOL;
            }
        }
        
        $sReturn.='
            ),
            </strong>
            ...
        ),
        ...
    ),
);</pre>';
        return $sReturn;
    }
    protected function _renderMoreToggler($sContent){
        $sDivId='rollout-more-toggler-'.$this->getId().'-'.md5($sContent);
        return ''
                . '<button onclick="$(\'#'.$sDivId.'\').slideToggle(); return false;"> ... </button>'
                . '<div id="'.$sDivId.'" style="display: none;">'
                    . $sContent
                . '</div>'
                ;
        
    }
    public function renderFormdata4Project() {
        return ''
                . $this->_renderForm4Vars('project', false)
                . $this->_renderForm4Vars('global', false)
                // . $this->_renderFormProjectVars($this->_sNamePrefix4Project, false)
                // . '<pre>DEBUG: GLOBAL settings - $this->_aCfgGlobal = ' . print_r($this->_aCfgGlobal, 1) . '</pre>'
                // . '<pre>DEBUG: PROJECT settings - $this->getConfig() = ' . print_r($this->getConfig(), 1) . '</pre>'
        // .'<pre>DEBUG: $this->_aCfgProject ... plugin = '.print_r($this->_aCfgProject, 1).'</pre>'
        ;
    }
    public function renderFormdata4Phase($sPhase){
        static $iCounter;
        if(!isset($iCounter)){
            $iCounter=0;
        }
        $sDivId='rollout-override-div-'.$this->getId().'-'.$sPhase.'-'.$iCounter;
        return ''
            . $this->_renderForm4Vars('phase', $sPhase)
            . $this->_renderMoreToggler(
                $this->_renderForm4Vars('project', $sPhase)
                . $this->_renderForm4Vars('global', $sPhase)
            )
            // . $this->_renderForm($aFormdata, 'project')
            // .$sReturn
            // . '<pre>DEBUG: GLOBAL settings - $this->_aCfgGlobal = ' . print_r($this->_aCfgGlobal, 1) . '</pre>'
            // . '<pre>DEBUG: PROJECT settings - $this->getConfig() = ' . print_r($this->getConfig(), 1) . '</pre>'
            // . '<pre>DEBUG: PHASE settings - $this->getConfig("'.$sMyPhase.'") = ' . print_r($this->getConfig($sMyPhase), 1) . '</pre>'
            ;
    }
}