<?php require_once 'rollout.interface.php'; require_once 'cache.class.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().']' ); } /** * get Data from a callback function and store it in a cache * The response type depends on the callback function * * @param string $sFunctionname name of the callback function * @param string $sKey name of the key; "project" or name of phase * @param integr $iTtl ttl value = how many seconds to use cache * @param integr $iTtlOnError ttl value = how many seconds to use cache if there was no response * @return type */ protected function _getCallback($sFunctionname, $sKey, $iTtl=15,$iTtlOnError=10){ $oCache=new AhCache('rollout-'.$this->getId(), 'callback-'.$sFunctionname.'-'.$sKey); if($oCache->isExpired()){ $aMydata= call_user_func(array($this, $sFunctionname)); // echo "$sFunctionname fresh ".($aMydata ? "OK": "false")." - storing for $iTtl sec<br>"; $oCache->write($aMydata, ($aMydata ? $iTtl : $iTtlOnError)); } else { // echo "$sFunctionname from cache ... ".$oCache->iExpired()." sec <br>"; $aMydata=$oCache->read(); } // echo '<pre>'.print_r($aMydata, 1).'</pre>'; die(__METHOD__); return $aMydata; } /** * 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.') ); // if a callback was set for this variable if(isset($aVarinfos['callback'])){ $aCallbackData=$this->_getCallback( $aVarinfos['callback'], (isset($aVarinfos['per_scope']) && $aVarinfos['per_scope'] ? $sKey : ''), (isset($aVarinfos['ttl']) ? $aVarinfos['ttl'] : 60) ); if(!$aCallbackData){ $aVarinfos['type']='text'; } else { $aEffectiveConfig=$this->getConfig($sPhase); // echo $sKey.' ... '; print_r($aEffectiveConfig[$sVarname]); echo '<br>'; if(isset($aEffectiveConfig[$sVarname]) && isset($aCallbackData[$aEffectiveConfig[$sVarname]])){ $aCallbackData[$aEffectiveConfig[$sVarname]]['selected']='selected'; // wenn value = defaultvalue, dann value auf '' setzen } // print_r($aCallbackData[$sVarname]); echo "<br>"; } // echo '<pre>'.$sCallbackfunktion .' = '. print_r($aMydata,1 ).'</pre>'; } 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 "select": $aOptions=$aCallbackData; $aFormdata[]=array( 'type' => $aVarinfos['type'], 'name' => $sPrefixName.'['.$sVarname.']', 'label' => $this->_t($sVarname.'-label'), 'title' => $this->_t($sVarname.'-hint'), 'validate' => 'isastring', 'options' => $aOptions, '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>' ; } }