<?php
/*
 * This file is part of the easy-CMS project.
 * easy-CMS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * easy-CMS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with easy-CMS; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
include_once('config.inc.php');
include_once('classes/core/DBAccessorObject.class.php');
include_once('classes/core/parser/XMLParser.class.php');
include_once('classes/core/versions/ContentVersion.class.php');


/**
 * @author     vinadelmar <vinadelmar@users.sourceforge.net>
 * @package    Core
 * @subpackage Versions
 */
class ContentVersionsManager extends DBAccessorObject {
	/**
	 * @var object
	 */
	var $typesManager;
	
	/**
	 * @var object
	 */
	var $xmlParsersTable;
	
	/**
	 * @var array
	 */
	var $fieldsArraysTable;
	
	/**
	 * @var array
	 */
	var $contentsTable;
	// [contentId_contentType]

	/**
	 * @var array
	 */
	var $contentVersionsTable;
	
	/**
	 * @var array
	 */
	var $xmlFilesTable;
	/**#@-*/
	
	/**
	 * Constructor
	 *
	 * @param integer $contentId
	 * @param string  $contentType
	 */
	function ContentVersionsManager(&$dbManipulator, &$typesManager) {
		parent::DBAccessorObject($dbManipulator);
		$this->typesManager = $typesManager;
		$this->contentVersionsTable = array();
		$this->fieldsArraysTable = array();
		$this->contentsTable = array();
		$this->xmlParsersTable = array();
	}

	/**
	 * @access private
	 * @return boolean
	 */
	function _loadXml($contentId, $contentType) {
		if(!isset($this->xmlParsersTable[$contentId.'_'.$contentType])) {
			$this->xmlParsersTable[$contentId.'_'.$contentType] = &new XMLParser($this->getXmlFile($contentId, $contentType));
			if (!$this->xmlParsersTable[$contentId.'_'.$contentType]->parse()) {
				$this->debug('ContentVersionsManager::_loadXml, error : unable to load "'.$this->getXmlFile($contentId, $contentType).'"', 3);
				return false;
			}
		}
		return true;
	}
	
	/**
	 * @access private_loadFieldsArray
	 * @return boolean
	 */
	function _loadFieldsArray($contentType) {
		if(!isset($this->fieldsArrayTable[$contentType])) {
			if(!($this->fieldsArrayTable[$contentType] = $this->dbManipulator->getFieldsArray(TYPE_TABLES_PREFIX.$contentType))) {
				$this->debug('ContentVersionsManager::_loadFieldsArray, error : unable to load fields array', 3);
				return false;
			}
		}
		return true;
	}
	
	/**
	 * @access private
	 * @return boolean
	 */
	function _load($contentId, $contentType) {
		return ($this->_loadXml($contentId, $contentType) && $this->_loadFieldsArray($contentType));
	}
	
	/**
	 * @access public
	 * @return integer
	 */
	function getNumVersions($contentId, $contentType) {
		if(isset($this->contentVersionsTable[$contentId.'_'.$contentType])) {
			return count($this->contentVersionsTable[$contentId.'_'.$contentType]);
		}
		return 0;
	}
	
	/**
	 * @return array
	 */
	function &getContentVersionsTable($contentId, $contentType) {
		return $this->contentVersionsTable[$contentId.'_'.$contentType];
	}
	
	/**
	 * Get specific version of content
	 *
	 * @param integer $contentId
	 * @param string $contenttype
	 * @param integer $versionId
	 * @return mixed object or null
	 */
	function &getContentVersion($contentId, $contentType, $versionId) {
		if (!$this->contentVersionExists($contentId, $contentType, $versionId)) {
			// e.g. "{VERSIONS_CONTENTS}/article-00026.xml"
			$this->xmlFilesTable[$contentId.'_'.$contentType] = VERSIONS_CONTENTS.'/'.$contentType.'-'.str_pad($contentId, 5, '0', STR_PAD_LEFT).'.xml';

			if (!$this->loadContentVersion($contentId, $contentType, $versionId)) {
				return null;
			}
		}
		return $this->contentVersionsTable[$contentId.'_'.$contentType][$versionId];
	}
	
	/**
	 * @access private
	 * @param  boolean $full_path full path or just file name
	 * @return string
	 */
	function getXmlFile($contentId, $contentType, $full_path=true) {
		if(!isset($this->xmlFilesTable[$contentId.'_'.$contentType])) {
			$this->xmlFilesTable[$contentId.'_'.$contentType] = VERSIONS_CONTENTS.'/'.$contentType.'-'.str_pad($contentId, 5, '0', STR_PAD_LEFT).'.xml';
		}
		if (!$full_path) {
			return basename($this->xmlFilesTable[$contentId.'_'.$contentType]);
		}
		return $this->xmlFilesTable[$contentId.'_'.$contentType];
	}
	
	/**
	 * Find Content Version 
	 *
	 * @access private
	 * @return boolean
	 */
	function loadContentVersion($contentId, $contentType, $versionId) {
		if(!$this->_load($contentId, $contentType, $versionId)) {
			return false;
		}
		
		if($this->contentVersionExists($contentId, $contentType, $versionId)) {
			return true;
		}
		
		$tags = $this->xmlParsersTable[$contentId.'_'.$contentType]->getTags();
		$values = $this->xmlParsersTable[$contentId.'_'.$contentType]->getValues();
		$num_values = $this->xmlParsersTable[$contentId.'_'.$contentType]->getNumValues();
		
		$i = 0;
		$stop = false;
		
		while(!$stop && $i < $num_values) {
			if ($values[$i]['tag'] == 'version' && $values[$i]['type'] == 'open' 
				&& $values[$i]['attributes']['id'] == $versionId) {
				$stop = true;
			}
			$i++;
		}
		
		if(!$stop) {
			return false;
		}
		else {
			$i--;
		}
		
		
		$j = array_search($i, $tags['version']);
		// version start
		$vs = $tags['version'][$j];
		// version end
		$ve = $tags['version'][$j+1];
		
		//echo 'vs=',$vs,', ve=',$ve,'<br/>';
		
		$versionDate = strtotime($values[$vs]['attributes']['date']);
		
		$ls = array_search($vs+1, $tags['lang']);
		// all langs start
		//$ls = $i;//$tags['lang'][$i];
		
		$le = array_search($ve-1, $tags['lang']);
		// all langs end
		//$le = $i; //$tags['lang'][$i];
		
		$contentVersion = &new ContentVersion($versionId, $versionDate);
		
		for($i = $ls; $i < $le; $i+=2) {
			$valuesTable = array();

			foreach($this->fieldsArrayTable[$contentType] as $name => $type) {
				$value = $this->_xml2val($contentId, $contentType, $name, $type, $tags['lang'][$i], $tags['lang'][$i+1]);
				$valuesTable[$name] = &$this->typesManager->createTypeObject($type, $value);
			}
			
			$currLang = $tags['lang'][$i];
			$langId = (int)$values[$currLang]['attributes']['id'];
			
			$contentVersion->addValuesTable($langId, $valuesTable);
		}
		
		$this->contentVersionsTable[$contentId.'_'.$contentType][$versionId] = &$contentVersion;
		
		return true;
	}
	
	function loadAll($contentId, $contentType) {
		if (!$this->_load($contentId, $contentType)) {
			return false;
		}
		
		$tags = $this->xmlParsersTable[$contentId.'_'.$contentType]->getTags();
		$values = $this->xmlParsersTable[$contentId.'_'.$contentType]->getValues();

    if(isset($tags['version'])) {
  		$num_versions = count($tags['version']);

  		for ($i = 0; $i < $num_versions; $i = $i+2) {
  			$currVer = $tags['version'][$i];

  			if (!isset($values[$currVer]['attributes']['id'])) {
  				echo 'not set, i=', $i, ' num_versions=', $num_versions;
  			}

  			$versionId = (int)$values[$currVer]['attributes']['id'];

  			if (!$this->contentVersionExists($contentId, $contentType, $versionId)) {
  				if (!$this->loadContentVersion($contentId, $contentType, $versionId)) {
  					$this->debug('ContentVersionsManager::loadAll, error : unable to load version #'.$versionId, 3);
  					return false;
  				}
  			}
  		}
    }
		return true;
	}
	
	/**
	 * @param string $name xml tag name
	 * @param integer $type typeof value
	 * @param integer $min bov position in xml file
	 * @param integer $max eov position in xml file
	 * @return mixed simple type object or null
	 */
	function _xml2val($contentId, $contentType, $name, $type, $min, $max) {
		$stop = false;
		$i = $min+1;
		
		$values = $this->xmlParsersTable[$contentId.'_'.$contentType]->getValues();

		while (!$stop && $i<$max) {
			$stop = ($values[$i]['tag'] == $name);
			$i++;
		}
		
		if ($i>$max) {
			$this->debug('ContentVersionsManager::_xml2val, error : <'.$name.'>missing value</'.$name.'>', 3);
			return null;
		}
		
		$pos = --$i;
    $value = null;

		switch ($type) {
  		case EASY_CMS_TYPE_BOOL:
  		case EASY_CMS_TYPE_INTEGER:
  		case EASY_CMS_TYPE_FLOAT:
  		case EASY_CMS_TYPE_LINK:
  		case EASY_CMS_TYPE_DOCUMENT:
  		case EASY_CMS_TYPE_IMAGE:
  		case EASY_CMS_TYPE_STRING:
  		case EASY_CMS_TYPE_SHORTTEXT:
  		case EASY_CMS_TYPE_TEXT:
  			$value = isset($values[$pos]['value']) ? $values[$pos]['value'] : '';
  			break;
  		case EASY_CMS_TYPE_DATE:
  			$year  = isset($values[$pos+1]['value']) ? $values[$pos+1]['value'] : '0000';
  			$month = isset($values[$pos+2]['value']) ? $values[$pos+2]['value'] : '00';
  			$day   = isset($values[$pos+3]['value']) ? $values[$pos+3]['value'] : '00';
  			$value = $year.'-'.$month.'-'.$day;
  			break;
  		case EASY_CMS_TYPE_COLOR:
  			break;
  		case EASY_CMS_TYPE_TIME:
  			break;
  		case EASY_CMS_TYPE_REFERENCE:
  			break;
		}
		return $value;
	}
	
	/**
	 * Save all versions
	 *
	 * Write versions into XML file. Update table `versions` if necessary.
	 *
	 * @access public
	 * @return boolean
	 */
	function save($contentId, $contentType) {
		$xmlFile = $this->getXmlFile($contentId, $contentType);
		
		if (file_exists($xmlFile)) {
			if (!$this->loadAll($contentId, $contentType)) {
				return false;
			}
		}
		
		if(isset($this->contentVersionsTable[$contentId.'_'.$contentType])) {
			ksort($this->contentVersionsTable[$contentId.'_'.$contentType]);
		}
		
		$oldVersId = array();
		
		for ($i = $this->getNumVersions($contentId, $contentType); $i > MAX_NUM_SAV_VERS; $i--) {
			$contentVersion = array_shift($this->contentVersionsTable[$contentId.'_'.$contentType]);
			$oldVersId[] = $contentVersion->getVersionId();
		}
		
		if ($oldVersId) {
			$result = $this->dbManipulator->delRecord(VERSIONS_TABLE, $oldVersId);
			
			if (!$result || !$this->dbManipulator->getAffectedRows()) {
				$this->debug('ContentVersionsManager::save, error : unable to delete deprecated versions #('.implode(',', $oldVersId).') in "'.VERSIONS_TABLE.'"', 3);
				//return false;
			}
		}
		
		$xml = $this->toXml($contentId, $contentType);
		
		if (!($fp = fopen($xmlFile, 'w'))) {
			$this->debug('ContentVersionsManager::save, error : unable to open file "'.$xmlFile.'"', 4);
			return false;
		}
		
		if (!fwrite($fp, $xml)) {
			$this->debug('ContentVersionsManager::save, error : unable to write into file "'.$xmlFile.'"', 4);
			
			fclose($fp);
			return false;
		}
		
		fclose($fp);

		chmod($xmlFile, 0666);

		$this->debug('ContentVersionsManager::save, success : versions for content #'.$contentId.' of type "'.$contentType.'" saved', 9);
		
		return true;
	}
	
	/**
	 * Save All Versions Into XML String
	 *
	 * @access private
	 * @return string
	 */
	function toXml($contentId, $contentType) {
		$xml  = '';
		$xml .= '<?xml version="1.0" encoding="utf-8" ?'.'>'."\n";
		$xml .= '<content-type id="'.$contentId.'" type="'.$contentType.'">'."\n";
		if(isset($this->contentVersionsTable[$contentId.'_'.$contentType])) {
			foreach ($this->contentVersionsTable[$contentId.'_'.$contentType] as $contentVersion) {
				$xml .= $contentVersion->toXml();
				$xml .= "\n";
			}
		}
		$xml .= '</content-type>';
		
		return utf8_encode($xml);
	}
	
	/**
	 * Checks if version exists
	 *
	 * @param integer $versionId
	 * @return boolean
	 */
	function contentVersionExists($contentId, $contentType, $versionId) {
		return isset($this->contentVersionsTable[$contentId.'_'.$contentType][$versionId]);
	}
	
	/**
	 * Create New Version of Content
	 *
	 * @param integer $contentId
	 * @param string  $contentType
	 * @param integer $userId
	 * @return boolean
	 */
	function createNewContentVersion($contentId, $contentType, $userId) {
    $result = true;
		if (!$this->_loadFieldsArray($contentType)) {
			$result = false;
		}
    else {
  		$versionDate = time();

  		$fieldsArray = array(
  			 'date'       => date('Y-m-d H:i:s', $versionDate)
  			,'content_id' => $contentId
  			,'type_name'  => $contentType
  			,'user_id'    => $userId
  		);

  		$result_array = $this->dbManipulator->addRecord(VERSIONS_TABLE, $fieldsArray);

  		if ($result_array === false || $this->dbManipulator->getAffectedRows() == 0) {
  			$result = false;
  		}
      else {
    		if (($versionId = $this->dbManipulator->getLastId()) == 0) {
    			$result = false;
    		}
        else {
      		$result_array = $this->dbManipulator->getRecord(TYPE_TABLES_PREFIX.$contentType, $contentId, '', 'id', 'lang_id');

      		if ($result_array === false || $this->dbManipulator->getNumRows() == 0) {
      			$this->dbManipulator->delRecord(VERSIONS_TABLE, $versionId);
      			$result = false;
      		}
          else {
        		$contentVersion = &new ContentVersion($versionId, $versionDate);

        		while($record = $this->dbManipulator->fetchArray($result_array)) {
        			$valuesTable = array();

        			foreach($this->fieldsArrayTable[$contentType] as $fieldName => $fieldType) {
        				$valuesTable[$fieldName] = &$this->typesManager->createTypeObject($fieldType, $record[$fieldName]);
        			}

        			$langId = (int)$record['lang_id'];

        			$contentVersion->addValuesTable($langId, $valuesTable);
        		}

        		$this->contentVersionsTable[$contentId.'_'.$contentType][$versionId] = $contentVersion;
          }
        }
      }
		}
		return $result;
	}
	

	/**
	 * List of versions available
	 *
	 * @return string
	 */
	function getVersionsList($contentId, $contentType) {
    $dbManipulator = &$this->getDBManipulator();
		$versionsList = array();

		$site = &Site::getInstance();

		// get current lang
		if (!($lid = $site->getSubmittedVar('lid', 0))) {
			if (!($lid = $site->getPersistentVar('lid', 0))) {
				return null;
			}
		}

		if(!($result = $dbManipulator->queryGetContentVersions($contentId, $contentType))) {
			return null;
		}

		while ($rec = $dbManipulator->fetchArray($result)) {
			$versionsList[] = $rec;
		}

		return $versionsList;
	}

	function removeContentVersion($contentId, $contentType, $versionId) {
		if (!$this->contentVersionExists($contentId, $contentType, $versionId)) {
			$this->debug('ContentVersionsManager::removeContentVersion, warning : unknown version #'.$versionId.' for content "'.$this->getContentType().'" #'.$this->getContentId(), 8);
		}
		
		unset($this->contentVersionsTable[$contentId.'_'.$contentType][$versionId]);
		$this->debug('ContentVersionsManager::removeContentVersion, success : version #'.$versionId.' for content "'.$this->getContentType().'" #'.$this->getContentId(), 9);
		return true;
	}
}
?>