Location: PHPKode > projects > Tv.2 CMS > tv2engine/tv2Xdb.class.php
<?php

// tv2 document DTD version
define('TV2_XDB_DOC_DTD_VERSION', '1.01');

// load tv2 Xdb document class:
require_once(ENGINE_DIR.'tv2Document.class.php');


/** 
 * Tv2 X database (tv2Xdb) -- a simple but powerful flat-file XML database
 * 
 * @package tv2-engine
 * @author Emilis Dambauskas (hide@address.com)
 * @copyright 2002–2003 Emilis Dambauskas under {@link http://opensource.org/licenses/artistic-license.php Artistic license}
 * @version $Id: tv2Xdb.class.php,v 1.6 2003/07/27 16:02:43 lunaticlt Exp $
 * @class tv2Xdb 
 */
class Tv2Xdb
{
	/**
	 * Array with document properties
	 * @attribute private array $fat
	 */
	var $fat;
	
	/**
	 * Array (ident => id)
	 * @attribute private array $fident
	 */
	var $fident;
	
	/**
	 * Db table name for fat
	 * @attribute private string $fat_table
	 */
	var $fat_table;

	/**
	 * Directory in which xdb files are stored
	 * @attribute private string $dir
	 */
	var $dir;
	
	/**
	 * Experimental. Default version (serves as versioning indicator)
	 * @attribute private array $ver
	 */
	var $ver;
	
	/**
	 * Experimental. Version information db table
	 * @attribute private string $ver_table
	 */
	var $ver_table;
	
	/**
	 * Experimental. Srray with document version info
	 * @attribute private array $ver_index
	 */
	var $ver_index;
	
	/**
	 * Experimental. Run-time version filter
	 * @attribute private array $ver_filter
	 */
	var $ver_filter;
	
	
	/**
	 * Constructor
	 *
	 * @constructor tv2Xdb
	 * @use $protocol
	 * @use $var_cache
	 * @use $security
	 * @use $db
	 */
	function tv2Xdb()
	{
		global $protocol;
		
		$this->db = &$GLOBALS['db'];
		$this->fat_table = 'tv2_fat';
		$this->ver = FALSE;
		$this->ver_context = NULL;
		$this->ver_table = 'tv2_versions';
		$this->ver_filter = NULL;
		$this->dir = DATA_DIR.'xdata/';
		
		if (!@$GLOBALS['var_cache'])
		{
			require_once(ENGINE_DIR.'ctlRtVarCache.class.php');
			$GLOBALS['var_cache'] = &new ctlRTVarCache();
		}
		$this->doc_cache = &$GLOBALS['var_cache'];
		
		if (!@$GLOBALS['security'])
		{
			require_once(ENGINE_DIR.'ctlSecurity.class.php');
			$GLOBALS['security'] = &new ctlSecurity();
			$GLOBALS['security']->load();
			$GLOBALS['security']->setCurrentUser($protocol->getSessionVar('uid'));
		}
		$this->security = &$GLOBALS['security'];
	}
	
//----------------- MAPPABLE METHODS -----------------------------------------
	
	/**
	 * Error reporting
	 *
	 * @method private error
	 * @param string $msg - error message 
   * @return boolean always returns FALSE
   * @use $errors
   */
	function error($msg)
	{
		$GLOBALS['errors']->add(get_class($this).': '.$msg);
		return FALSE;
	}
	
	/**
	 * Permission checking
	 *
	 * @method private isPermitted
	 * @param int $doc_id - tv2 Xdb document id 
	 * @param string $access - access type (one of: r/w/x/o/d) 
	 * @return boolean TRUE if permission granted, FALSE otherwise
	 */
	function isPermitted($doc_id, $access)
	{
		return $this->security->check(get_class($this), $doc_id, $access);
	}	

//----------------- PRIVATE METHODS -----------------------------------------

	
	/**
	 * Recursively creates directories
	 *
	 * @method private cDir
	 * @param string $path - path to a document 
	 * @return boolean TRUE if action succeeded
	 */
	function cDir($path) 
	{
		if ( strlen( $path) == 0)
		  	return 0;
		if ( strlen( $path) < 3)
		  	return 1;
		elseif ( is_dir( $path))
		  	return 1; // avoid 'xyz:\' problem.
		elseif   ( dirname( $path) == $path)
		  return 1; // avoid 'xyz:\' problem.
		
		return ( $this->cDir(dirname($path)) and mkdir($path, 0757));
	}
	
	/**
	 * Encode string as CDATA (removes < > &)
	 *
	 * @method private encodeCdata
	 * @param string $string - string to be encoded 
	 * @return string encoded string
	 */
	function encodeCdata($string)
	{
		$patt = array('&', '<', '>');
		$repl = array('&amp;', '&lt;', '&gt;');
		return str_replace($patt, $repl, $string);
	}
	
	/**
	 * Decode string from CDATA
	 *
	 * @method private decodeCdata
	 * @param string $string - string to be decoded 
	 * @return decoded string
	 */
	function decodeCdata($string)
	{
		$patt = array('&lt;', '&gt;', '&amp;');
		$repl = array('<', '>', '&');
		return str_replace($patt, $repl, $string);
	}

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

	
	/**
	 * Fills fat and ver_info arrays
	 *
	 * @method public connect
	 */  
	function connect()
	{
		$tfat = $this->db->getAll('SELECT * FROM '.$this->fat_table);
		foreach ($tfat as $ttfat)
		{
			$this->fat[$ttfat['id']] = array('id'=>$ttfat['id'], 'type'=>$ttfat['type'], 'name'=>$ttfat['name'], 'path'=>$ttfat['path'], 'ctime'=>@$ttfat['ctime']);
			$this->fident[$ttfat['path'].$ttfat['name']] = $ttfat['id'];
		}
		
		if (!is_array($this->fat))
				$this->fat = array();
		
		if ($this->ver)
		{
			$tvi = $this->db->getAll('SELECT * FROM '.$this->ver_table);
			foreach ($tvi as $ttvi)
			{
				$this->ver_index[$ttvi['obj_id']][] = array(
							'obj_id'=>$ttvi['obj_id'],
							'vtime'=>$ttvi['vtime'],
							'language'=>$ttvi['language'],
							'context'=>$ttvi['context'],
							'status'=>$ttvi['status']);
			} // end of foreach
		} // end of versioning
	} // end of method connect()
	
	
	/**
	 * Checks if a document exists
	 *
	 * @method public documentExists
	 * @param mixed $id - document ID or ident 
	 * @return boolean TRUE if exists, FALSE otherwise
	 */
	function documentExists($id)
	{
		return (boolean) (isset($this->fat[$id]) || isset($this->fident[$id]));
	}
	
	
	/**
	 * Creates {@link tv2Document} object reference
	 *
	 * @method public readDocument
	 * @param mixed $id - document ID or ident 
	 * @param optional mixed $vinfo Experimental. Specifies with version of the document to open.
	 * @return ref object {@link tv2Document} object
	 */
	function &readDocument($id, $vinfo = NULL)
	{
		if (!is_numeric($id))
				if (!isset($this->fident[$id]))
						return $this->error('Ident '.$id.' passed to readDocument is not available.');
				else
						$id = $this->fident[$id];
		
		// check ver_index
		if ($this->ver)
		{
			$vfound = FALSE;
			if ($vinfo === NULL)
			{
				if (!$this->ver_filter)
						$vfound = TRUE;
				else
						$vinfo = $this->ver_filter;
			}
			if (!$vfound)
			{
				if (!isset($vinfo[0]))
						$vinfo = array($vinfo);
				foreach ($vinfo as $vinf)
				{
					if (is_array(@$this->ver_index[$id]))
					{
						foreach ($this->ver_index[$id] as $ver)
								if ($ver['status'] == @$vinf['status'] && $ver['language'] == @$vinf['language'])
										$vfound = TRUE;
					}
					if (!isset($this->ver_index[$id]) && $vinf == $this->ver || $vinf === NULL)
							$vfound = TRUE;
				} // end of foreach $vinfo
			}
			if (!$vfound)
					return $this->error('The version '.$vinfo[0]['language'].':'.$vinfo[0]['status'].' of the document you are trying to open does not exist.');
		}	// end of check ver_index
		
		if (!$this->isPermitted($id, 'r'))
				return $this->error('Unauthorised user '.$this->security->getActiveUserName().' tried to access document '.$this->fat[$id]['path'].$this->fat[$id]['name'].' ['.$id.'].');
		
		if ($doc = &$this->doc_cache->find(get_class($this), $id))
				return $doc;
		
		$fname = $this->dir.$this->fat[$id]['path'].$this->fat[$id]['name'].'.xml';
		
		if (!$fp = @fopen($fname, 'r'))
				return $this->error('File '.$fname.' of document '.$this->fat[$id]['path'].$this->fat[$id]['name'].' ['.$id.'] not found.');
		
		$contents = fread($fp, filesize($fname));
		fclose($fp);
		
		// check Doc/vDoc
		$dtfound = FALSE;
		if (preg_match('/<document name="([^"\'\:]+)" type="([^"]{1,16})">/', $contents, $temp))
				$dtfound = TRUE;
		elseif ($this->ver && preg_match('/<vdocument name="([^"\'\:]+)" type="([^"]{1,16})" context="([^"]{1,16})">/', $contents, $temp))
		{
			preg_match_all('/<version language="([^"]{2,6})" status="([^"]{1,16})">/', $contents, $vers, PREG_SET_ORDER);
			$versions = $vers;

			if (!$vinfo)
			{
				$dtfound = TRUE;
				if (preg_match('/<version language="'.$this->ver['language'].'" status="'.$this->ver['status'].'">/', $contents))
						$contents = preg_replace('/^.*<version language="'.$this->ver['language'].'" status="'.$this->ver['status'].'">(.*)<\/version>.*$/sU', '\1', $contents);
				else
						$contents = preg_replace('/^.*<version language="([^"]{2,6})" status="([^"]{1,16})">(.*)<\/version>.*$/sU', '\1', $contents);
			}
			else
			{
				reset($vinfo);
				while (!$dtfound && $vinf = each($vinfo))
				{
					$vinf = $vinf[1];
					foreach ($vers as $v)
							if ($v[1] == @$vinf['language'] && $v[2] == @$vinf['status'])
									$dtfound = TRUE;
					if ($vinf === NULL)
							$dtfound = TRUE;
				}
				if ($vinf !== NULL)
						$vinfo = $vinf;
				else
						$vinfo = array('language'=>$vers[0][1], 'status'=>$vers[0][2]);
				if ($dtfound)
					$contents = preg_replace('/^.*<version language="'.$vinfo['language'].'" status="'.$vinfo['status'].'">(.*)<\/version>.*$/sU', '\1', $contents);
			} // end else (!$vinfo)
		} // end of elseif vdocument
		if (!$dtfound)
				return $this->error('Bad xml schema in file '.$fname.': document not matched.');
		// end of Doc/vDoc check

		$doc = &new Tv2Document();
		$doc->xdb = &$this;
		$doc->fname = $fname;
		$doc->id = $id;
		$doc->type = $temp[2];
		$doc->ctime = $this->fat[$id]['ctime'];
		$doc->name = $temp[1];
		$doc->path = $this->fat[$id]['path'];
		$doc->ident = $this->fat[$id]['path'].$temp[1];
		if (isset($temp[3]))
		{
			$doc->vcontext = $temp[3];
			$doc->vinfo = $vinfo;
			$doc->versions = $versions;
		}
		unset($temp);
		
		preg_match_all('/<field name="([^"\'\.]+)" type="([^"\'\.]+)">([^<>]*)<\/field>/ims', $contents, $temp, PREG_SET_ORDER);
		foreach ($temp as $field)
				$doc->addField($field[1], $this->decodeCdata($field[3]), $field[2]);
		
		$perm = $this->security->get(get_class($this), $doc->id);
		if (!is_array($perm))
				$perm = array();
		foreach ($perm as $p)
				if ($p['ug'] == 'u')
						$pr['users'][$p['name']] = $p['permission'];
				else
						$pr['groups'][$p['name']] = $p['permission'];
		$doc->permissions = @$pr;
		
		if ($this->ver)
				$doc->versions = @$this->ver_index[$doc->id];
		
		$this->doc_cache->add(get_class($this), $doc->id, $doc);

		return $doc;
	}
	

	/**
	 * Writes tv2 document information into database and disk. Used both for updating and creating new documents
	 *
	 * @method public writeDocument
	 * @param ref object $doc - {@link tv2Document} object
	 * @return boolean TRUE on success, FALSE on failure
	 */
	function writeDocument(&$doc)
	{
		if (!is_object($doc))
				return $this->error('Document should be an object.');
		
		// if new document:
		if (!isset($doc->id) || !$doc->id)
		{
			$doc->id = @max(array_keys($this->fat))+1;
			if (isset($this->fident[$doc->ident]))
					return $this->error('Can\'t write new document on top of old one.');
			
			$doc->name = preg_replace('|^.*/([^/]+)$|', '\1', $doc->ident);
			$doc->path = preg_replace('|/([^/]+)$|', '/', $doc->ident);
			
			$query = 'INSERT INTO '.$this->fat_table.' (id, type, name, path, ctime) VALUES('.$doc->id.', \''.$doc->type.'\', \''.$doc->name.'\', \''.$doc->path.'\', '.time().')';
			if (!$this->db->query($query))
					return $this->error('Can\'t insert new value into fat table.');
			
			$this->security->add(get_class($this), $doc->id, 'o');
			if (!$this->changeDocPermissions($doc))
							return $this->error('Couldnot change document permissions.');
		}
		// if existing document:
		else
		{
			
			if (!$odoc = $this->readDocument($doc->id))
					return $this->error('The document you are trying to change doesnot exist or you don\'t have permission to open it.');
			
			if ($odoc->ident != $doc->ident || $odoc->name != $doc->name)
					if (!$this->moveDocument($odoc, $doc->ident))
							return $this->error('Couldnot move document.');
			
			if (serialize($odoc->permissions) != serialize($doc->permissions))
					if (!$this->changeDocPermissions($doc))
							return $this->error('Couldnot change document permissions.');
			
			if ($odoc->type != $doc->type)
					if (!$this->changeDocType($doc))
							return $this->error('Couldnot change document type.');
		
			if (!$this->isPermitted($doc->id, 'w'))
					return $this->error('Unauthorised user '.$this->security->getActiveUserName().' tried to access document '.$doc->ident.' ['.$doc->id.'] for writing.');

		}
		
		$contents = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
		$contents .= '<!DOCTYPE document SYSTEM "tv2xdb-'.TV2_XDB_DOC_DTD_VERSION.'.dtd">'."\n";

		if (!$doc->vcontext)
		{
			$contents .= '<document name="'.$doc->name.'" type="'.$doc->type.'">'."\n";
			if ($this->ver)
					$this->db->query('DELETE FROM '.$this->ver_table.' WHERE obj_id='.$doc->id);
		}
		elseif (!$this->ver)
				return $this->error('Versioning support is not enabled. Unable to write versioned document.');
		else
			$contents .= '<vdocument name="'.$doc->name.'" type="'.$doc->type.'" context="'.$doc->vcontext.'">'."\n";
		
		$fields = '';
		
		if (is_array($doc->fields))
			foreach ($doc->fields as $field)
				$fields .= '<field name="'.$field['name'].'" type="'.$field['type'].'">'.$this->encodeCdata($field['value']).'</field>'."\n";

		if (!$doc->vcontext)
				$contents .= $fields.'</document>';
		else
		{
			$fname = $this->dir.$doc->ident.'.xml';
			if (!file_exists($fname) || !@$this->ver_index[$doc->id])
			{
				$contents .= '<version language="'.$doc->vinfo['language'].'" status="'.$doc->vinfo['status'].'">'."\n$fields</version>\n";
				if (!$this->db->query('INSERT INTO '.$this->ver_table.' (obj_id, vtime, context, status, language) VALUES('.$doc->id.', '.time().', \''.$doc->vcontext.'\', \''.$doc->vinfo['status'].'\', \''.$doc->vinfo['language'].'\')'))
						return $this->error('Could not insert version information into database.');
			}
			else
			{
				if (!$this->db->query('UPDATE '.$this->ver_table.' SET context=\''.$doc->vcontext.'\' WHERE obj_id='.$doc->id))
						return $this->error('Could not update version context in database.');
				
				$fp = fopen($fname, 'rb');
				$fc = fread($fp, filesize($fname));
				fclose($fp);

				if ($doc->vchanged['status'] == $doc->vinfo['status'] && $doc->vchanged['language'] == $doc->vinfo['language'])
						$doc->vchanged = FALSE;
				if ($doc->vchanged)
				{
					$this->db->query('DELETE FROM '.$this->ver_table.' WHERE obj_id='.$doc->id.' AND status=\''.$doc->vinfo['status'].'\' AND language=\''.$doc->vinfo['language'].'\'');
					if (!$this->db->query('UPDATE '.$this->ver_table.' SET status=\''.$doc->vinfo['status'].'\', language=\''.$doc->vinfo['language'].'\' WHERE obj_id='.$doc->id.' AND status=\''.$doc->vchanged['status'].'\' AND language=\''.$doc->vchanged['language'].'\''))
							return $this->error('Could not update version information in database.');
					$fc = preg_replace('/<version language="'.$doc->vinfo['language'].'" status="'.$doc->vinfo['status'].'">.*<\/version>/sU', '', $fc);
					$vtag = '<version language="'.$doc->vchanged['language'].'" status="'.$doc->vchanged['status'].'">';
				}
				else
				{
					if (!$this->db->getOne('SELECT obj_id FROM '.$this->ver_table.' WHERE obj_id='.$doc->id.' AND status=\''.$doc->vinfo['status'].'\' AND language=\''.$doc->vinfo['language'].'\''))
							if (!$this->db->query('INSERT INTO '.$this->ver_table.' (obj_id, vtime, context, status, language) VALUES('.$doc->id.', '.time().', \''.$doc->vcontext.'\', \''.$doc->vinfo['status'].'\', \''.$doc->vinfo['language'].'\')'))
									return $this->error('Could not insert version information into database.');
					$vtag = '<version language="'.$doc->vinfo['language'].'" status="'.$doc->vinfo['status'].'">';
					if (!preg_match('/'.$vtag.'/', $fc))
							$fc = preg_replace('/<\/vdocument>/', "$vtag\n</version>\n</vdocument>" ,$fc);
				}
				
				$fc = preg_replace('/^.*<vdocument[^>]+>\n(.*)<\/vdocument>$/s', '\1', $fc);
				$fc = preg_replace('/'.$vtag.'.*<\/version>/sU', '<version language="'.$doc->vinfo['language'].'" status="'.$doc->vinfo['status'].'">'."\n$fields".'</version>', $fc);
				$contents .= $fc;
				if (!$this->db->query('UPDATE '.$this->ver_table.' SET vtime='.time().' WHERE obj_id='.$doc->id.' AND status=\''.$doc->vinfo['status'].'\' AND language=\''.$doc->vinfo['language'].'\''))
						return $this->error('Could not update vtime in database.');
			}
			
			$contents .= '</vdocument>';
		}

		$dirname = $this->dir.preg_replace('|/[^/]+$|', '', $doc->ident);
		$this->cDir($dirname);
		$fname = $this->dir.$doc->ident.'.xml';

		$fp = fopen($fname, 'wb');
		fwrite($fp, $contents, strlen($contents));
		fclose($fp);

		if (!stristr($_SERVER['SERVER_SOFTWARE'], 'win'))
			passthru('chmod 757 '.$fname);
		
		$this->fat[$doc->id] = array('name' => $doc->name, 'path' => $doc->path, 'type' => $doc->type, 'ctime'=> time());
		$this->fident[$doc->ident] = $doc->id;
		
		if (!$this->db->query('UPDATE '.$this->fat_table.' SET type=\''.$doc->type.'\', name=\''.$doc->name.'\', path=\''.$doc->path.'\', ctime='.time().' WHERE id='.$doc->id))
				return $this->error('Can\'t update fat table.');
		
		// update indices:
		if (!$this->updateIndex($doc))
				$this->error('Couldnot update indices for the document '.$doc->ident.'.');
		
		return $contents;
	}
	
	
	/**
	 * Changes document type
	 *
	 * @method public changeDocType
	 * @param ref object $doc - {@link tv2Document} reference 
	 * @param optional string $type - type to change to 
	 * @return boolean TRUE on success, FALSE otherwise
	 */
	function changeDocType(&$doc, $type = NULL)
	{
		if (!$this->isPermitted($doc->id, 'o'))
				return FALSE;
		
		if ($type !== NULL && $type != $doc->type)
				$doc->type = $type;
		
		return TRUE;
	}
	
	
	/**
	 * Updates document permissions
	 *
	 * @method public changeDocPermissions
	 * @param ref object $doc - {@link tv2Document} object
	 * @return boolean TRUE on success, FALSE otherwise
	 */
	function changeDocPermissions(&$doc)
	{
		if (!$this->isPermitted($doc->id, 'o'))
				return FALSE;
		
		$perm = $doc->permissions;
		$pa = array();
		if (!is_array($perm))
				$perm = array();
		if (sizeof(@$perm['users']))
				foreach ($perm['users'] as $un=>$up)
						if ($un && $up)
								$pa[] = array('ug'=>'u', 'user'=>$un, 'access'=>$up);
		if (sizeof(@$perm['groups']))
				foreach ($perm['groups'] as $gn=>$gp)
						if ($gn && $gp)
								$pa[] = array('ug'=>'g', 'user'=>$gn, 'access'=>$gp);
		
		return $this->security->reset(get_class($this), $doc->id, $pa);
	}
	
	
	/**
	 * Moves document from one path to another
	 *
	 * @method public moveDocument
	 * @param ref object $doc - {@link tv2Document} object
	 * @param string $ident - new document ident 
	 * @param optional boolean $overwrite - overwrite confirmation flag (default FALSE)
	 * @return boolean TRUE on success, FALSE on failure
	 */
	function moveDocument(&$doc, $ident, $overwrite = FALSE)
	{
		if (!isset($this->fat[$doc->id]) || $this->fat[$doc->id]['path'].$this->fat[$doc->id]['name'] != $doc->ident)
				return $this->error('The document you are trying to move does not exist.');
		
		if (!$this->isPermitted($doc->id,'o'))
				return $this->error('You don\'t have permissions to move document '.$doc->ident.'.');
		
		if ((!$overwrite) && $this->documentExists($ident))
				return $this->error('Destination '.$ident.' not empty, can\'t overwrite.');
		
		if (!$this->loadXQuery())
				return $this->error('Couldnot load XQuery in moveDocument.');
				
		$ids = $this->query("path matches '^$doc->ident/'");
		if (sizeof($ids))
		{
			foreach ($ids as $id)
					if (!$this->isPermitted($id,'o'))
							return $this->error('You don\'t have permissions to move document '.$doc->ident.' child '.$id.'.');
			
			foreach ($ids as $id)
			{
				$td = &new tv2Document();
				$td->open($id);
				
				$ntd = $td;
				unset($ntd->id);
				$ntd->ident = preg_replace('|^'.preg_quote($doc->ident,'|').'|',$ident,$td->ident);
				$ntd->path = preg_replace('|/([^/]+)$|', '/', $ntd->ident);
				
				if (!$this->writeDocument($ntd))
						return $this->error('Couldnot save new child document '.$ntd->ident.' while moving '.$doc->ident.'.');
				
				if (!$this->removeDocument($td))
						return $this->error('Couldnot remove old child document '.$td->ident.' while moving '.$doc->ident.'.');
			}
		}
		
		$newdoc = $doc;
		unset($newdoc->id);
		$name = preg_replace('|^.*/([^/]+)$|', '\1', $ident);
		$path = preg_replace('|/([^/]+)$|', '/', $ident);
		$newdoc->name = $name;
		$newdoc->path = $path;
		$newdoc->ident = $ident;
		
		// save new document:
		if (!$this->writeDocument($newdoc))
				return $this->error('Couldnot save new document while moving.');
		
		$olddoc = $doc;
		$doc = $newdoc;
		
		// delete file
		if (!$this->removeDocument($olddoc))
				return $this->error('Couldnot remove old document while moving.');
		
		return TRUE;
	}
	
	
	/**
	 * Removes document
	 *
	 * @method public removeDocument
	 * @param ref object $doc - {@link tv2Document} object
	 * @param optional mixed $vinfo Experimental. If specified only the version of the document will be removed 
	 * @return boolean TRUE on success, FALSE on failure
	 */
	function removeDocument(&$doc, $vinfo = NULL)
	{
		if (!$this->isPermitted($doc->id, 'o'))
				return $this->error('Unauthorised user '.$this->security->getActiveUserName().' tried to remove document '.$doc->ident.' ['.$doc->id.'].');
		
		if (!$this->db->query('DELETE FROM '.$this->fat_table.' WHERE id='.$doc->id))
				return $this->error('Couldnot remove document from db.');
		
		if ($this->ver)
				$this->db->query("DELETE FROM $this->ver_table WHERE obj_id=$doc->id");
		
		if ($this->loadXIndex())
				$this->xi->removeItem($doc->id);
		
		
		unset($this->fat[$doc->id]);
		unset($this->fident[$doc->ident]);
		unset($this->ver_index[$doc->id]);
		unlink($this->dir.$doc->ident.'.xml');
		unset($doc);
		return TRUE;
	}
	
	/**
	 * Get FAT entries for documents in some path
	 *
	 * @method public getList
	 * @param string $path - path
	 * @return array array containing FAT records
	 * @deprecated You should use {@link tv2Xdb::query()} and {@link tv2Xdb::getFat()} methods instead
	 */
	function getList($path)
	{
		$rez = array();
		foreach ($this->fat as $id => $doc)
				if ($doc['path'] == $path)
						$rez[] = array_merge(array('id' => $id, 'ident' => $doc['path'].$doc['name']), $doc);
		return $rez;
	}
	
	
	/**
	 * Get FAT entries for a specified list of document IDs
	 *
	 * @method public getFat
	 * @param optional mixed $ids ID array or single ID/ident
	 * @param optional array $o Order: array('field', 'ASC'/'DESC') 
	 * @return array array containing FAT records
	 */
	function getFat($ids = NULL, $o = NULL)
	{
		if ($ids === NULL)
				$ids = array_keys($this->fat);
		
		if (is_string($ids))
				$ids = $this->fident[$ids];
		
		if (is_int($ids) || is_numeric($ids))
				$ids = (array) $ids;
		
		if (!is_array($ids))
				return $this->error('Argument passed to getFat is not an array.');
		
		// ORDER results
		if (is_array($o) && sizeof($o))
		{
			if (@$o[1] == 'DESC')
					$order = SORT_DESC;
			else
					$order = SORT_ASC;
			
			if ($o[0] == 'name' || $o[0] == 'path' || $o[0] == 'type' || $o[0] == 'ctime')
					foreach ($ids as $id)
							$sa[] = $this->fat[$id][$o[0]];
			elseif ($o[0] == 'ident')
					foreach ($ids as $id)
							$sa[] = $this->fat[$id]['path'].$this->fat[$id]['name'];
			else
					return $this->error('Ordering results by non-system fields not implemented yet.');
			
			array_multisort($sa, $order, $ids);
		}
		
		$ret = array();
		foreach ($ids as $id)
				$ret[] = $this->fat[$id];
		
		return $ret;
	}
	
	
	/**
	 * Returns FAT array for all documents in Xdb
	 *
	 * @method public getAllDocs
	 * @param optional string mode index by id ('id') or ident('ident').
	 * @return array array containing FAT entries
	 * @deprecated This function is subject to removal when it will not be used in xadmin module
	 */
	function getAllDocs($mode = 'ident')
	{
		if ($mode == 'id')
				return $this->fat;
		elseif ($mode == 'ident')
		{
			$rez = array();
			foreach ($this->fident as $ident => $id)
					$rez[$ident] = array_merge(array('id' => $id, 'ident' => $ident), $this->fat[$id]);
			
			return $rez;
		}
	}
	

	/**
	 * Queries Xdb using SQL-like synthax
	 *
	 * @method public query
	 * @param string $q SQL like query 
	 * @param optional array $o order by: array('field', 'ASC'/'DESC') 
	 * @param optional int $s start from (default 0) 
	 * @param optional int $l limit result number to 
	 * @return array document ID array
	 */
	function query($q, $o = NULL, $s = 0, $l = NULL)
	{
		if (!$this->loadXQuery())
				return $this->error('Couldnot load XQuery.');
		
		return $this->xq->query($q,$o,$s,$l);
	}

//----------------- VERSIONING METHODS ----------------------------------------

	
	/**
	 * Experimental. Sets strict version filter (Xdb will see only documents with this version)
	 *
	 * @method public setVersionFilter
	 * @param array $filter array of version arrays 
	 */
	function setVersionFilter($filter)
	{
		if (!isset($filter[0]))
				$filter = array($filter);
		$this->ver_filter = $filter;
	}
	
	
	/**
	 * Experimental. Set a version preference: Xdb will try opening specified versions over default version
	 *
	 * @method public setVersionPreference
	 * @param array $filter array of version arrays 
	 */
	function setVersionPreference($filter)
	{
		if (!isset($filter[0]))
				$filter = array($filter);
		$this->ver_filter = $filter;
		$this->ver_filter[] = $this->ver;
		$this->ver_filter[] = NULL;
	}

//----------------- LOADING ADDITIONAL OBJECTS --------------------------------

	
	/**
	 * Loads {@link tv2XQuery} object into parameter $xq
	 *
	 * @method private loadXQuery
	 * @return boolean TRUE on success, FALSE on failure
	 */
	function loadXQuery()
	{
		if (isset($this->xq))
				return TRUE;
		
		include_once(ENGINE_DIR.'tv2Xquery.class.php');
		$this->xq = &new tv2XQuery();
		$this->xq->ver = &$this->ver;
		$this->xq->ver_context = &$this->ver_context;
		$this->xq->ver_index = &$this->ver_index;
		return $this->xq->load($this->fat, $this->ver_index, $this->ver_context);
	}
	
	/**
	 * Loads {@link ctlIndexer} object into parameter $xi
	 *
	 * @method private loadXIndex
	 * @return boolean TRUE on success, FALSE on failure
	 * @use $config
	 */
	function loadXIndex()
	{
		if (isset($this->xi))
				return TRUE;
		
		global $config;
		include_once(ENGINE_DIR.'ctlIndexer.class.php');
		$this->xi = &new ctlIndexer();
		
		if (!$this->xi->load(@$config['xindex']))
				return $this->error('Failed loading indexer.');
		
		if (is_array(@$config['xindex']['index']))
		{
			foreach ($config['xindex']['index'] as $iname => $index)
					$this->xi->createIndex($iname,$index['index_type']);
		}
		
		return TRUE;
	}

//----------------- INDEXING METHODS ------------------------------------------
	
	
	/**
	 * Updates indexes for the specified document
	 *
	 * @method public updateIndex
	 * @param ref object $doc - tv2Document object
	 * @return boolean TRUE on success, FALSE on failure
	 */
	function updateIndex(&$doc) 
	{
		if (!$this->loadXIndex())
				return $this->error('Couldnot load indexer in updateIndex.');
		
		$this->xi->removeItem($doc->id);
		
		global $config;
		
		$fs = sizeof($doc->fields);
		for ($i=0;$i<$fs;$i++)
		{
			if (!isset($doc->fields[$i]))
				continue;
			
			$ftype = $doc->fields[$i]['type'];
			$fname = $doc->fields[$i]['name'];
			if ($ftype == 'i_int' || $ftype == 'i_boolean')
				$this->xi->createIndex($fname,'int');
			else if ($ftype == 'i_float')
				$this->xi->createIndex($fname,'float');
			else if (substr($ftype,0,2) == 'i_' || $ftype == 'keywords')
				$this->xi->createIndex($fname,'string');
			
			if (in_array($ftype,array('i_varchar','i_time','i_ctime','i_boolean','i_path','i_link')))
				$this->xi->addItem($doc->id,$fname,$doc->fields[$i]['value']);
			else if ($ftype == 'i_text')
			{
				preg_match_all('/(\w+)/',$doc->fields[$i]['value'],$temp,PREG_PATTERN_ORDER);
				$vals = $temp[1];
				unset($temp);
				foreach ($vals as $val)
					$this->xi->addItem($doc->id,$fname,trim($val));
			}
			else if ($ftype == 'i_html')
			{
				$txt = preg_replace('/<[^>]>/','',$doc->fields[$i]['value']);
				$txt = preg_replace('/&#\w+;/','',$txt);
				preg_match_all('/(\w+)/',$txt,$temp,PREG_PATTERN_ORDER);
				$vals = $temp[1];
				unset($temp);
				foreach ($vals as $val)
					$this->xi->addItem($doc->id,$fname,trim($val));
			}
			else if ($ftype == 'keywords')
			{
				$vals = explode(';',$doc->fields[$i]['value']);
				foreach ($vals as $val)
					$this->xi->addItem($doc->id,$fname,trim($val));
			}
			
		}
		
		if (is_array($doc->fv))
			$fields = array_keys($doc->fv);
		else
			$fields = array();
		
		if (is_array(@$config['xindex']['index']))
		{
			foreach ($config['xindex']['index'] as $iname => $index)
					if ((!isset($index['type']) || $index['type'] == $doc->type) && in_array($index['field'],$fields))
							$this->xi->addItem($doc->id,$iname,$doc->fv[$index['field']]);
		}
		return TRUE;
	}
	
	
	/**
	 * Checks FAT,version info and indices for errors and fixes them if needed
	 *
	 * @method public checkNfix
	 * @param optional boolean $fix (default TRUE) tells if the function should fix errors 
	 * @return string String with error messages or warnings, empty string on complete success
	 * @todo this method still needs to be finished and upgraded
	 */
	function checkNfix($fix = TRUE)
	{
		$res = '';
		$this->loadXIndex();
		set_time_limit(0);
		
		foreach ($this->fat as $id => $fat)
		{
			$doc = &new tv2Document();
			if ($doc->open($id))
					$this->updateIndex($doc);
		}
		unset($doc);
		
		set_time_limit(ini_get('max_execution_time'));
		return $res;
	}
	
}

?>
Return current item: Tv.2 CMS