<?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 20022003 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('&', '<', '>');
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('<', '>', '&');
$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;
}
}
?>