* @license http://www.contenido.org/license/LIZENZ.txt * @link http://www.4fb.de * @link http://www.contenido.org * @since file available since contenido release >= 4.8.8 * * {@internal * created 2008-08-12 * modified 2009-11-06, Murat Purc, replaced deprecated functions (PHP 5.3 ready) * modified 2011-02-07, Dominik Ziegler, fixed max number of revisions check and prevent displaying error and creating folders if versioning is not activated * * }} * */ if (!defined('CON_FRAMEWORK')) { die('Illegal call'); } class Version { /** * Id of Type * @access protected */ protected $sType; /** * md5 coded name of author * @access protected */ protected $sAuthor; /** * Time of created * @access protected */ protected $dCreated; /** * Time of last modified * @access protected */ protected $dLastModified; /** * Body data of xml file * @access protected */ protected $aBodyData = array(); /** * For init global variable * @access protected */ protected $aCfg; /** * For init global variable $cfgClient * @access protected */ protected $aCfgClient; /** * Database object * @access protected */ protected $oDB; /** * For init global variable $client * @access protected */ protected $iClient; /** * Revision files of current file * @access public */ public $aRevisionFiles = array(); /** * Number of Revision * @access private */ protected $iRevisionNumber; /** * Timestamp * @access protected */ protected $dTimestamp; /** * For init global variable $area * @access protected */ protected $sArea; /** * For init global variable $frame * @access protected */ protected $iFrame; /** * For init variables * @access protected */ protected $aVarForm; /** * Identity the Id of Content Type * @access protected */ protected $iIdentity; /** * To take control versioning is switched off * @access private */ private $bVersionCreatActive; /** * Timestamp * @access protected */ protected $dActualTimestamp; /** * Alternative Path for save version files * @access protected */ protected $sAlternativePath; /** * Displays Notification only onetimes per object * */ public static $iDisplayNotification; protected $_bPathError = false; /** * The Version object constructor, initializes class variables * * @param array $aCfg * @param array $aCfgClient * @param object $oDB * @param integer $iClient * @param object $sArea * @param object $iFrame * * @return void */ public function __construct($aCfg, $aCfgClient, DB_ConLite $oDB, $iClient, $sArea, $iFrame) { $this->aCfg = $aCfg; $this->aCfgClient = $aCfgClient; $this->oDB = $oDB; $this->iClient = $iClient; $this->iRevisionNumber = 0; $this->sArea = $sArea; $this->iFrame = $iFrame; $this->dActualTimestamp = time(); $this->aVarForm = array(); self::$iDisplayNotification++; // Look if versioning is allowed, default is false if (function_exists('getEffectiveSetting')) { $this->bVersionCreatActive = getEffectiveSetting('versioning', 'activated', 'true'); $this->sAlternativePath = getEffectiveSetting('versioning', 'path'); } else { $this->bVersionCreatActive = true; $this->sAlternativePath = ""; } if ($this->bVersionCreatActive == "true") { if (!is_dir($this->sAlternativePath)) { // Alternative Path is not true or is not exist, we use the frontendpath if ($this->sAlternativePath != "" AND self::$iDisplayNotification < 2) { $oNotification = new Contenido_Notification(); $sNotification = i18n('Alternative path %s does not exist. Version was saved in frondendpath.'); $oNotification->displayNotification("warning", sprintf($sNotification, $this->sAlternativePath)); } $this->sAlternativePath = ""; } // Look if versioning is set alternative path to save $this->_checkPaths(); } } /** * This function looks if maximum number of stored versions is achieved. If true, it will be delete the first version. * * @return void */ protected function prune() { $this->initRevisions(); if (function_exists('getEffectiveSetting')) { $sVar = getEffectiveSetting('versioning', 'prune_limit', '0'); } else { $sVar = 0; } $bDelete = true; while (count($this->aRevisionFiles) >= $sVar AND $bDelete AND (int) $sVar > 0) { $iIndex = end(array_keys($this->aRevisionFiles)); $bDelete = $this->deleteFile($this->getFirstRevision()); unset($this->aRevisionFiles[$iIndex]); } } /** * This function checks if needed version paths exists and were created if neccessary * @return boolean */ protected function _checkPaths() { $aPath = array('', '/css', '/js', '/layout', '/module', '/templates'); $sFrontEndPath = ""; if ($this->sAlternativePath == "") { $sFrontEndPath = $this->aCfgClient[$this->iClient]["path"]["frontend"] . "version"; } else { $sFrontEndPath = $this->sAlternativePath . "/" . $this->iClient; } if (is_dir($sFrontEndPath) && is_writable($sFrontEndPath)) { foreach ($aPath as $sSubPath) { if (!is_dir($sFrontEndPath . $sSubPath)) { mkdir($sFrontEndPath . $sSubPath, 0777); chmod($sFrontEndPath . $sSubPath, 0777); } } } else { trigger_error("Version path " . $sFrontEndPath . " not writable in " . __FILE__ . " on line " . __LINE__, E_USER_WARNING); $this->_bPathError = true; return false; } return true; } /** * This function initialize the body node of xml file * * @param string $sKey * @param string $sValue * * @return array returns an array for body node */ public function setData($sKey, $sValue) { $this->aBodyData[$sKey] = $sValue; } /** * This function creats an xml file. XML Writer helps for create this file. * * @return string returns content of xml file */ public function createNewXml() { $oWriter = new cXmlWriter(); $oRootElement = $oWriter->addElement('version', '', NULL, array( 'xml:lang' => 'de' )); $oHeadElement = $oWriter->addElement('head', '', $oRootElement); $oWriter->addElement('version_id', $this->iIdentity . '_' . $this->iVersion, $oHeadElement); $oWriter->addElement('type', $this->sType, $oHeadElement); $oWriter->addElement('date', date('Y-m-d H:i:s'), $oHeadElement); $oWriter->addElement('author', $this->sAuthor, $oHeadElement); $oWriter->addElement('client', $this->iClient, $oHeadElement); $oWriter->addElement('created', $this->dCreated, $oHeadElement); $oWriter->addElement('lastmodified', $this->dLastModified, $oHeadElement); $oBodyElement = $oWriter->addElement('body', '', $oRootElement); foreach ($this->aBodyData as $sKey => $sValue) { $oWriter->addElement($sKey, $sValue, $oBodyElement, array(), true); } return $oWriter->saveToString(); } /** * This function creats new version in right folder. * * @return void */ public function createNewVersion() { if ($this->_bPathError) return false; $bCreate = false; if ($this->bVersionCreatActive == "true") { try { // Get version Name $sRevisionName = $this->getRevision(); // Create xml version file $sXmlFile = $this->createNewXml(); if (!is_dir($this->getFilePath())) { $bCreate = mkdir($this->getFilePath(), 0777); chmod($this->getFilePath(), 0777); } $sHandle = fopen($this->getFilePath() . $sRevisionName . '.xml', "w"); fputs($sHandle, $sXmlFile); $bCreate = fclose($sHandle); if ($bCreate == false) { throw new Exception('Couldnt Create New Version'); } } catch (Exception $e) { $bCreate = false; echo '
Some error occured: ' . $e->getMessage() . ': ' . $e->getFile() . ' at line ' . $e->getLine() . ' (' . $e->getTraceAsString() . ')'; } } return $bCreate; } /** * This function inits version files. Its filter also timestamp and version files * * @return array returns xml file names */ protected function initRevisions() { $this->aRevisionFiles = array(); $this->dTimestamp = array(); // Open this Filepath and read then the content. $sDir = $this->getFilePath(); if (is_dir($sDir)) { if ($dh = opendir($sDir)) { while (($file = readdir($dh)) !== false) { if ($file != '.' && $file != '..') { $aData = explode('.', $file); $aValues = explode('_', $aData[0]); if ($aValues[0] > $this->iRevisionNumber) { $this->iRevisionNumber = $aValues[0]; } $this->dTimestamp[$aValues[0]] = $aValues[1]; $this->aRevisionFiles[$aValues[0]] = $file; } } closedir($dh); } } return krsort($this->aRevisionFiles); } /** * This function deletes files and the the folder, for given path. * * @return bool return true if successful */ public function deleteFile($sFirstFile = "") { // Open this Filepath and read then the content. $sDir = $this->getFilePath(); $bDelet = false; if (is_dir($sDir) AND $sFirstFile == "") { if ($dh = opendir($sDir)) { while (($sFile = readdir($dh)) !== false) { if ($sFile != "." && $sFile != "..") { // Delete the files $bDelete = unlink($sDir . $sFile); } } // if the files be cleared, the delete the folder $bDelete = rmdir($sDir); } } else if ($sFirstFile != "") { $bDelete = unlink($sDir . $sFirstFile); } if ($bDelete) { return true; } else { return false; } } /** * Get the frontendpath to revision * * @return string returns path to revision file */ public function getFilePath() { if ($this->sAlternativePath == "") { $sFrontEndPath = $this->aCfgClient[$this->iClient]["path"]["frontend"] . "version/"; } else { $sFrontEndPath = $this->sAlternativePath . "/" . $this->iClient . "/"; } return $sFrontEndPath . $this->sType . '/' . $this->iIdentity . '/'; } /** * Get the last revision file * * @return array returns Last Revision */ public function getLastRevision() { return reset($this->aRevisionFiles); } /** * Makes new and init Revision Name * * @return integer returns number of Revison File */ private function getRevision() { $this->iVersion = ($this->iRevisionNumber + 1 ) . '_' . $this->dActualTimestamp; return $this->iVersion; } /** * Inits the first element of revision files * * @return string the name of xml files */ protected function getFirstRevision() { $aKey = array(); $this->initRevisions(); $aKey = $this->aRevisionFiles; $sFirstRevision = ""; // to take first element, we use right sort ksort($aKey); foreach ($aKey as $value) { return $sFirstRevision = $value; } return $sFirstRevision; } /** * Revision Files * * @return array returns all Revison File */ public function getRevisionFiles() { return $this->aRevisionFiles; } /** * This function generate version names for select-box * * @return array returns an array of revision file names */ public function getFormatTimestamp() { $aTimes = array(); if (count($this->dTimestamp) > 0) { krsort($this->dTimestamp); foreach ($this->dTimestamp as $iKey => $sTimeValue) { $aTimes[$this->aRevisionFiles[$iKey]] = date('d.m.Y H:i:s', $sTimeValue) . " - Revision: " . $iKey; } } return $aTimes; } /** * This function generate version names for select-box * * @return array returns an array of revision file names */ public function setVarForm($sKey, $sValue) { $this->aVarForm[$sKey] = $sValue; } /** * The general SelectBox function for get Revision. * * @param string $sTableForm The name of Table_Form class * @param string $sAddHeader The Header Label of SelectBox Widget * @param string $sLabelOfSelectBox The Label of SelectBox Widget * @param string $sIdOfSelectBox Id of Select Box * * return string if is exists Revision, then returns HTML Code of full SelectBox else returns empty string */ public function buildSelectBox($sTableForm, $sAddHeader, $sLabelOfSelectBox, $sIdOfSelectBox) { $oForm = new UI_Table_Form("lay_history"); $aMessage = array(); // if exists xml files if (count($this->dTimestamp) > 0) { foreach ($this->aVarForm as $sKey => $sValue) { $oForm->setVar($sKey, $sValue); } $aMessage = $this->getMessages(); $oForm->addHeader(i18n($sAddHeader)); $oForm->add(i18n($sLabelOfSelectBox), $this->getSelectBox($this->getFormatTimestamp(), $sIdOfSelectBox)); $oForm->setActionButton("clearhistory", "images/but_delete.gif", $aMessage["alt"], "c", "history_truncate"); $oForm->setConfirm("clearhistory", $aMessage["alt"], $aMessage["popup"]); $oForm->setActionButton("submit", "images/but_refresh.gif", i18n("Refresh"), "s"); return $oForm->render() . '
'; } else { return ''; } } /** * Messagebox for build selectBox. Dynamic allocation for type. * return array the attributes alt and poput returns */ private function getMessages() { $aMessage = array(); switch ($this->sType) { case 'layout': $aMessage["alt"] = i18n("Clear layout history"); $aMessage["popup"] = i18n("Do you really want to clear layout history?") . "

" . i18n("Note: This only affects the current layout."); break; case 'module': $aMessage["alt"] = i18n("Clear module history"); $aMessage["popup"] = i18n("Do you really want to clear module history?") . "

" . i18n("Note: This only affects the current module."); break; case 'css': $aMessage["alt"] = i18n("Clear style history"); $aMessage["popup"] = i18n("Do you really want to clear style history?") . "

" . i18n("Note: This only affects the current style."); break; case 'js': $aMessage["alt"] = i18n("Clear Java-Script history"); $aMessage["popup"] = i18n("Do you really want to clear Java-Script history?") . "

" . i18n("Note: This only affects the current Java-Script."); break; case 'templates': $aMessage["alt"] = i18n("Clear HTML-Template history"); $aMessage["popup"] = i18n("Do you really want to clear HTML-Template history?") . "

" . i18n("Note: This only the affects current HTML-Template."); break; default: $aMessage["alt"] = i18n("Clear history"); $aMessage["popup"] = i18n("Do you really want to clear history?") . "

" . i18n("Note: This only affects the current history."); break; } return $aMessage; } /** * A Class Function for fill version files * * @param string $sTableForm The name of Table_Form class * @param string $sAddHeader The Header Label of SelectBox Widget * * return string returns select-box with filled files */ private function getSelectBox($aTempVesions, $sIdOfSelectBox) { $sSelected = $_POST[$sIdOfSelectBox]; $oSelectMenue = new cHTMLSelectElement($sIdOfSelectBox); $oSelectMenue->autoFill($aTempVesions); if ($sSelected != "") { $oSelectMenue->setDefault($sSelected); } return $oSelectMenue->render(); } /** * Build new Textarea with below parameters * * @param string $sName The name of Textarea. * @param string $sValue The value of Input Textarea * @param integer $iWidth width of Textarea * @param integer $iHeight height of Textarea * * @return string HTML Code of Textarea */ public function getTextarea($sName, $sInitValue, $iWidth, $iHeight, $sId = "") { if ($sId != "") { $oHTMLTextarea = new cHTMLTextarea($sName, $sInitValue, $iWidth, $iHeight, $sId); } else { $oHTMLTextarea = new cHTMLTextarea($sName, $sInitValue, $iWidth, $iHeight); } $oHTMLTextarea->setStyle("font-family: monospace; width: 100%;"); $oHTMLTextarea->updateAttributes(array("wrap" => "off")); return $oHTMLTextarea->render(); } /** * Build new Textfield with below parameters * * @param string $sName The name of Input Textfield. * @param string $sValue The value of Input Textfield * @param integer $iWidth width of Input Textfield * * @return string HTML Code of Input Textfield */ public function getTextBox($sName, $sInitValue, $iWidth, $bDisabled = false) { $oHTMLTextbox = new cHTMLTextbox($sName, clHtmlEntityDecode($sInitValue), $iWidth, "", "", $bDisabled); $oHTMLTextbox->setStyle("font-family: monospace; width: 100%;"); $oHTMLTextbox->updateAttributes(array("wrap" => "off")); return $oHTMLTextbox->render(); } /** * Displays your notification * * @param string $sOutPut * * @return void */ public function displayNotification($sOutPut) { if ($sOutPut != "") { print $sOutPut; } } /** * Set new node for xml file of description * * @param string $sDesc Content of node * */ public function setBodyNodeDescription($sDesc) { if ($sDesc != "") { $this->sDescripion = clHtmlEntities($sDesc); $this->setData("description", $this->sDescripion); } } } // end of class ?>