<?php

/**
 * Project: 
 * Contenido Content Management System
 * 
 * Description: 
 * We use super class Version to create a new Version.
 * 
 * Requirements: 
 * @con_php_req 5.0
 * 
 *
 * @package    Contenido Backend classes
 * @version    1.0.2
 * @author     Bilal Arslan, Timo Trautmann
 * @copyright  four for business AG <info@contenido.org>
 * @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 '<br>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() . '<div style="margin-top:20px;"></div>';
        } 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?") . "<br><br>" . 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?") . "<br><br>" . 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?") . "<br><br>" . 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?") . "<br><br>" . 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?") . "<br><br>" . 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?") . "<br><br>" . 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
?>