diff --git a/livesupport/modules/alib/var/alib.php b/livesupport/modules/alib/var/alib.php
index c6d72d5f8..85b4d9d98 100644
--- a/livesupport/modules/alib/var/alib.php
+++ b/livesupport/modules/alib/var/alib.php
@@ -23,11 +23,14 @@
Author : $Author: tomas $
- Version : $Revision: 1.9 $
+ Version : $Revision: 1.10 $
Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/alib/var/alib.php,v $
------------------------------------------------------------------------------*/
+
require_once 'subj.php';
+
+define('USE_ALIB_CLASSES', TRUE);
define('ALIBERR_NOTLOGGED', 30);
define('ALIBERR_NOTEXISTS', 31);
@@ -37,7 +40,7 @@ define('ALIBERR_NOTEXISTS', 31);
* authentication/authorization class
*
* @author $Author: tomas $
- * @version $Revision: 1.9 $
+ * @version $Revision: 1.10 $
* @see Subjects
* @see GreenBox
*/
@@ -182,68 +185,89 @@ class Alib extends Subjects{
}
/**
- * Check if specified subject have permission to specified action
- * on specified object - huh ;)
- * One of the most important method in this class hierarchy ...
+ * Check if specified subject have permission to specified action
+ * on specified object
*
- * @param sid int
- * @param action string
- * @param oid int OPT
- * @return boolean/err
+ * Look for sequence of correnponding permissions and order it by
+ * relevence, then test the most relevant for result.
+ * High relevence have direct permission (directly for specified subject
+ * and object. Relevance order is done by level distance in the object
+ * tree, level distance in subjects (user/group system).
+ * Similar way is used for permissions related to object classes.
+ * But class-related permissions have lower priority then
+ * object-tree-related.
+ * Support for object classes can be disabled by USE_ALIB_CLASSES const.
+ *
+ * @param sid int, subject id (user or group id)
+ * @param action string, from set defined in config
+ * @param oid int, object id, optional (default: root node)
+ * @return boolean/err
*/
function checkPerm($sid, $action, $oid=NULL)
{
if(!is_numeric($sid)) return FALSE;
- if(is_null($oid)) $oid = $this->getObjId($this->RootNode);
+ if(is_null($oid) or $oid=='') $oid = $this->getObjId($this->RootNode);
if(PEAR::isError($oid)) return $oid;
if(!is_numeric($oid)) return FALSE;
- // query elements
- $q_flds = "m.level as S_lvl, p.subj, s.login, action, p.type, p.obj";
- $q_from = "{$this->subjTable} s, {$this->permTable} p";
- $q_join = "LEFT JOIN {$this->smembTable} m ON p.subj=m.gid ";
+ // query construction
+ // shortcuts:
+ // p: permTable,
+ // s: subjTable, m smembTable,
+ // t: treeTable ts: structTable,
+ // c: classTable, cm: cmembTable
+ // main query elements:
+ $q_flds = "m.level , p.subj, s.login, action, p.type, p.obj";
+ $q_from = "{$this->permTable} p ";
+ // joins for solving users/groups:
+ $q_join = "LEFT JOIN {$this->subjTable} s ON s.id=p.subj ";
+ $q_join .= "LEFT JOIN {$this->smembTable} m ON m.gid=p.subj ";
$q_cond = "p.action in('_all', '$action') AND
- (m.uid=$sid OR p.subj=$sid) AND s.id=p.subj";
+ (s.id=$sid OR m.uid=$sid) ";
+ // coalesce -1 for higher priority of nongroup rows:
+ // action DESC order for lower priority of '_all':
+ $q_ordb = "ORDER BY coalesce(m.level,-1), action DESC, p.type DESC";
+ $q_flds0 = $q_flds;
+ $q_from0 = $q_from;
+ $q_join0 = $q_join;
+ $q_cond0 = $q_cond;
+ $q_ordb0 = $q_ordb;
+ // joins for solving object tree:
+ $q_flds .= ", t.name, ts.level as tlevel";
+ $q_join .= "LEFT JOIN {$this->treeTable} t ON t.id=p.obj ";
+ $q_join .= "LEFT JOIN {$this->structTable} ts ON ts.parid=p.obj ";
+ $q_cond .= " AND (t.id=$oid OR ts.objid=$oid)";
// action DESC order is hack for lower priority of '_all':
- $q_ordb = "ORDER BY S_lvl, action DESC, p.type DESC";
- $qc0 = $q_cond;
- // test if object is class:
- $iscls = $this->isClass($oid);
- if(PEAR::isError($iscls)) return $iscls;
- if($iscls){
- $q_from .= ", {$this->classTable} c";
- $q_cond .= " AND c.id=p.obj AND c.id=$oid";
- }else{
- // obj is normal node => path search => retrieve L/R values for it:
- $r1 = $this->dbc->getRow("SELECT lft, rgt, level
- FROM {$this->treeTable} WHERE id=$oid");
- if(is_null($r1))
- return PEAR::raiseError("Alib::checkPerm: object not exists ($oid)",
- ALIBERR_NOTEXISTS, PEAR_ERROR_RETURN
- );
- if(PEAR::isError($r1)) return($r1);
- // fetch all path to oid + join with perms
- $q_flds .= ", t.name, ({$r1['level']}-t.level)as T_lvl";
- $q_from = "{$this->treeTable} t, ".$q_from;
- $q_cond .= " AND t.id=p.obj AND t.lft<={$r1['lft']} AND
- t.rgt>={$r1['rgt']}";
- // action DESC order is hack for lower priority of '_all':
- $q_ordb = "ORDER BY T_lvl, S_lvl, action DESC, p.type DESC";
- }
- $query="SELECT $q_flds FROM $q_from $q_join WHERE $q_cond $q_ordb";
- $r2 = $this->dbc->getAll($query);
- if(PEAR::isError($r2)) return($r2);
- if(!$iscls && !(is_array($r2) && count($r2)>0)){
- // no perm found, search in classes:
- $q_from = "{$this->cmembTable} cm, ".$q_from;
- $q_cond = $qc0.
- " AND t.lft<={$r1['lft']} AND t.rgt>={$r1['rgt']}".
- " AND cm.cid=p.obj AND cm.objid=t.id";
- $query="SELECT $q_flds FROM $q_from $q_join WHERE $q_cond $q_ordb";
- $r2 = $this->dbc->getAll($query);
- if(PEAR::isError($r2)) return($r2);
- }
+ $q_ordb = "ORDER BY coalesce(ts.level,0), m.level, action DESC, p.type DESC";
+ // query by tree:
+ $query1 = "SELECT $q_flds FROM $q_from $q_join WHERE $q_cond $q_ordb";
+ $r1 = $this->dbc->getAll($query1);
+ if(PEAR::isError($r1)) return($r1);
// if there is row with type='A' on the top => permit
- return (is_array($r2) && count($r2)>0 && $r2[0]['type']=='A');
+ $AllowedByTree =
+ (is_array($r1) && count($r1)>0 && $r1[0]['type']=='A');
+ $DeniedByTree =
+ (is_array($r1) && count($r1)>0 && $r1[0]['type']=='D');
+
+ if(!USE_ALIB_CLASSES) return $AllowedbyTree;
+
+ // joins for solving object classes:
+ $q_flds = $q_flds0.", c.cname ";
+ $q_join = $q_join0."LEFT JOIN {$this->classTable} c ON c.id=p.obj ";
+ $q_join .= "LEFT JOIN {$this->cmembTable} cm ON cm.cid=p.obj ";
+ $q_cond = $q_cond0." AND (c.id=$oid OR cm.objid=$oid)";
+ $q_ordb = $q_ordb0;
+ // query by class:
+ $query2 = "SELECT $q_flds FROM $q_from $q_join WHERE $q_cond $q_ordb";
+ $r2 = $this->dbc->getAll($query2);
+ if(PEAR::isError($r2)) return($r2);
+ $AllowedByClass =
+ (is_array($r2) && count($r2)>0 && $r2[0]['type']=='A');
+ // not used now:
+ // $DeniedByClass =
+ // (is_array($r2) && count($r2)>0 && $r2[0]['type']=='D');
+ $res = ($AllowedByTree || (!$DeniedByTree && $AllowedByClass));
+# echo"
\nsid=$sid, action=$action, oid=$oid\n"; var_dump($r1); echo"\n---\n$query1\n---\n\n"; var_dump($r2); echo"\n---\n$query2\n---\n\n"; exit; + return $res; } /* ---------------------------------------------------------- object tree */ @@ -392,9 +416,17 @@ class Alib extends Subjects{ */ function dumpPerms($indstr=' ', $ind='') { - $r = $ind.join(', ', array_map( - create_function('$v', 'return "{$v[\'action\']}/{$v[\'type\']}";'), - $this->dbc->getAll("SELECT action, type FROM {$this->permTable}") + $arr = $this->dbc->getAll(" + SELECT s.login, p.action, p.type + FROM {$this->permTable} p, {$this->subjTable} s + WHERE s.id=p.subj + ORDER BY p.permid + "); + if(PEAR::isError($arr)) return $arr; + $r = $ind.join(', ', array_map(create_function('$v', + 'return "{$v[\'login\']}/{$v[\'action\']}/{$v[\'type\']}";' + ), + $arr ))."\n"; return $r; } @@ -421,24 +453,27 @@ class Alib extends Subjects{ $t =& $this->tdata['tree']; $c =& $this->tdata['classes']; $s =& $this->tdata['subjects']; - $o[] = $this->addPerm($s[0], '_all', $t[0], 'A'); - $o[] = $this->addPerm($s[1], '_all', $t[4], 'A'); - $o[] = $this->addPerm($s[1], '_all', $t[7], 'D'); -# $o[] = $this->addPerm($s[2], 'addChilds', $t[6], 'A'); - $o[] = $this->addPerm($s[2], 'read', $t[5], 'A'); - $o[] = $this->addPerm($s[2], 'edit', $t[6], 'A'); - $o[] = $this->addPerm($s[3], 'read', $c[0], 'A'); - $o[] = $this->addPerm($s[4], 'editPerms', $c[1], 'A'); - $o[] = $this->addPerm($s[4], 'editPerms', $t[7], 'D'); - - $o[] = $this->addPerm($s[1], 'addChilds', $t[3], 'A'); - $o[] = $this->addPerm($s[1], 'addChilds', $t[1], 'A'); - $o[] = $this->addPerm($s[5], 'addChilds', $t[3], 'A'); - $o[] = $this->addPerm($s[5], 'addChilds', $t[1], 'A'); - $o[] = $this->addPerm($s[6], 'addChilds', $t[3], 'A'); - $o[] = $this->addPerm($s[6], 'addChilds', $t[1], 'A'); - $o[] = $this->addPerm($s[7], 'addChilds', $t[3], 'A'); - $o[] = $this->addPerm($s[7], 'addChilds', $t[1], 'A'); + $this->dbc->setErrorHandling(PEAR_ERROR_PRINT); + $perms = array( + array($s['root'], '_all', $t['root'], 'A'), + array($s['test1'], '_all', $t['pa'], 'A'), + array($s['test1'], 'read', $t['s2b'], 'D'), + array($s['test2'], 'addChilds', $t['pa'], 'D'), + array($s['test2'], 'read', $t['i2'], 'A'), + array($s['test2'], 'edit', $t['s1a'], 'A'), + array($s['test1'], 'addChilds', $t['s2a'], 'D'), + array($s['test1'], 'addChilds', $t['s2c'], 'D'), + array($s['gr2'], 'addChilds', $t['i2'], 'A'), + array($s['test3'], '_all', $t['t1'], 'D'), + ); + if(USE_ALIB_CLASSES){ + $perms[] = array($s['test3'], 'read', $c['cl_sa'], 'D'); + $perms[] = array($s['test4'], 'editPerms', $c['cl2'], 'A'); + } + foreach($perms as $p){ + $o[] = $r = $this->addPerm($p[0], $p[1], $p[2], $p[3]); + if(PEAR::isError($r)) return $r; + } $this->tdata['perms'] = $o; } @@ -451,24 +486,38 @@ class Alib extends Subjects{ { if(PEAR::isError($p = parent::test())) return $p; $this->deleteData(); - $this->testData(); - $this->test_correct = "_all/A, _all/A, _all/D, read/A, edit/A, read/A,". - " editPerms/A, editPerms/D, addChilds/A, addChilds/A, addChilds/A,". - " addChilds/A, addChilds/A, addChilds/A, addChilds/A, addChilds/A". - "\nno, yes\n"; - $this->test_dump = $this->dumpPerms(). + $r = $this->testData(); + if(PEAR::isError($r)) return $r; + $this->test_correct = "root/_all/A, test1/_all/A, test1/read/D,". + " test2/addChilds/D, test2/read/A, test2/edit/A,". + " test1/addChilds/D, test1/addChilds/D, gr2/addChilds/A,". + " test3/_all/D"; + if(USE_ALIB_CLASSES){ + $this->test_correct .= ", test3/read/D, test4/editPerms/A"; + } + $this->test_correct .= "\nno, yes\n"; + $r = $this->dumpPerms(); + if(PEAR::isError($r)) return $r; + $this->test_dump = $r. ($this->checkPerm( - $this->tdata['subjects'][1], 'edit', $this->tdata['tree'][7] + $this->tdata['subjects']['test1'], 'read', + $this->tdata['tree']['t1'] )? 'yes':'no').", ". ($this->checkPerm( - $this->tdata['subjects'][2], 'read', $this->tdata['tree'][5] + $this->tdata['subjects']['test1'], 'addChilds', + $this->tdata['tree']['i2'] )? 'yes':'no')."\n" ; $this->removePerm($this->tdata['perms'][1]); $this->removePerm($this->tdata['perms'][3]); - $this->test_correct .= "_all/A, _all/D, edit/A, read/A, editPerms/A,". - " editPerms/D, addChilds/A, addChilds/A, addChilds/A, addChilds/A,". - " addChilds/A, addChilds/A, addChilds/A, addChilds/A\n"; + $this->test_correct .= "root/_all/A, test1/read/D,". + " test2/read/A, test2/edit/A,". + " test1/addChilds/D, test1/addChilds/D, gr2/addChilds/A,". + " test3/_all/D"; + if(USE_ALIB_CLASSES){ + $this->test_correct .= ", test3/read/D, test4/editPerms/A"; + } + $this->test_correct .= "\n"; $this->test_dump .= $this->dumpPerms(); $this->deleteData(); if($this->test_dump==$this->test_correct) diff --git a/livesupport/modules/alib/var/class.php b/livesupport/modules/alib/var/class.php index cad24d107..5fefa4144 100644 --- a/livesupport/modules/alib/var/class.php +++ b/livesupport/modules/alib/var/class.php @@ -23,11 +23,11 @@ Author : $Author: tomas $ - Version : $Revision: 1.4 $ + Version : $Revision: 1.5 $ Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/alib/var/class.php,v $ ------------------------------------------------------------------------------*/ -require_once "mtree.php"; +require_once "m2tree.php"; /** * ObjClass class @@ -35,11 +35,11 @@ require_once "mtree.php"; * class for 'object classes' handling - i.e. groups of object in tree * * @author $Author: tomas $ - * @version $Revision: 1.4 $ + * @version $Revision: 1.5 $ * @see Mtree * @see Subj */ -class ObjClasses extends Mtree{ +class ObjClasses extends M2tree{ var $classTable; var $cmembTable; /** @@ -51,7 +51,7 @@ class ObjClasses extends Mtree{ */ function ObjClasses(&$dbc, $config) { - parent::MTree($dbc, $config); + parent::M2Tree($dbc, $config); $this->classTable = $config['tblNamePrefix'].'classes'; $this->cmembTable = $config['tblNamePrefix'].'cmemb'; } @@ -174,7 +174,7 @@ class ObjClasses extends Mtree{ function getClassName($id) { return $this->dbc->getOne( - $query = "SELECT cname FROM {$this->classTable}WHERE id=$id"); + $query = "SELECT cname FROM {$this->classTable} WHERE id=$id"); } /** @@ -244,7 +244,7 @@ class ObjClasses extends Mtree{ { $this->dbc->query("DELETE FROM {$this->cmembTable}"); $this->dbc->query("DELETE FROM {$this->classTable}"); - parent::deleteData(); + parent::reset(); } /** * Insert test data @@ -253,10 +253,12 @@ class ObjClasses extends Mtree{ function testData() { parent::testData(); - $o[] = $this->addClass('Sections b'); - $o[] = $this->addClass('Class 2'); - $this->addObj2Class($o[1], $this->tdata['tree'][4]); - $this->addObj2Class($o[1], $this->tdata['tree'][9]); + $o['cl_sa'] = $this->addClass('Sections a'); + $o['cl2'] = $this->addClass('Class 2'); + $this->addObj2Class($o['cl_sa'], $this->tdata['tree']['s1a']); + $this->addObj2Class($o['cl_sa'], $this->tdata['tree']['s2a']); + $this->addObj2Class($o['cl2'], $this->tdata['tree']['t1']); + $this->addObj2Class($o['cl2'], $this->tdata['tree']['pb']); $this->tdata['classes'] = $o; } @@ -269,11 +271,11 @@ class ObjClasses extends Mtree{ if(PEAR::isError($p = parent::test())) return $p; $this->deleteData(); $this->testData(); - $this->test_correct = "Sections b (0), Class 2 (2)\n"; + $this->test_correct = "Sections a (2), Class 2 (2)\n"; $this->test_dump = $this->dumpClasses(); - $this->removeClass('Sections b'); - $this->removeObjFromClass($this->tdata['tree'][4], - $this->tdata['classes'][1]); + $this->removeClass('Sections a'); + $this->removeObjFromClass($this->tdata['tree']['pb'], + $this->tdata['classes']['cl2']); $this->test_correct .= "Class 2 (1)\n"; $this->test_dump .= $this->dumpClasses(); $this->deleteData(); diff --git a/livesupport/modules/alib/var/m2tree.php b/livesupport/modules/alib/var/m2tree.php new file mode 100644 index 000000000..4f9942384 --- /dev/null +++ b/livesupport/modules/alib/var/m2tree.php @@ -0,0 +1,736 @@ + +define('ALIBERR_MTREE', 10); + +/** + * M2tree class + * + * class for tree hierarchy stored in db + * + * example config: example/conf.php\n"; + exit; +} + +$dbc = DB::connect($config['dsn'], TRUE); +$dbc->setFetchMode(DB_FETCHMODE_ASSOC); + +$m2 = &new M2treeTest($dbc, $config); +#$m2->uninstall(); +#exit; +#$r = $m2->install(); if($dbc->isError($r)){ echo $r->getMessage()."\n".$r->getUserInfo()."\n"; exit; } + +$m2->reset(); +#$r = $m2->_test_addObj(); if($dbc->isError($r)){ echo $r->getMessage()."\n".$r->getUserInfo()."\n"; exit; } +$r = $m2->_test(); if($dbc->isError($r)){ echo $r->getMessage()."\n".$r->getUserInfo()."\n"; exit; } + +/* +$parid = $m2->_t['s1']; +for($i=1; $i<=20; $i++){ + $r = $m2->addObj("X$i", "XX", $parid); + if($m2->dbc->isError($r)) return $r; + $parid = $r; + //$m2->_t["p$i"] = $r; +} +$r = $m2->dumpTree(); echo "$r\n"; +*/ + + +#$r = $m2->getSubTree($m2->_t['i1'], TRUE); var_dump($r); +#$r = $m2->getPath($m2->_t['r1'], 'id, name, level'); var_dump($r); +#$r = $m2->getPath($m2->_t['r1'], 'id, name, level', TRUE); var_dump($r); +/* +foreach($m2->getAllObjects() as $k=>$obj){ + $r = $m2->isChildOf($m2->_t['r1'], $obj['id'], TRUE); + echo "{$obj['name']}: $r\n"; +} +*/ +#$r = $m2->getDir($m2->_t['i1'], 'id, name, level'); var_dump($r); +#$r = $m2->getPath($m2->_t['s3'], 'name'); var_dump($r); +#$r = $m2->addObj("Issue1", "XX", $m2->_t["s4"]); var_dump($r); +#$r = $m2->moveObj($m2->_t['i1'], $m2->_t['s4']); var_dump($r); +#$r = $m2->copyObj($m2->_t['i1'], $m2->_t['s4']); var_dump($r); +#$r = $m2->removeObj($m2->_t['p2']); var_dump($r); +#$r = $m2->renameObj($m2->_t['s1'], 'Section2'); var_dump($r); +#$r = $m2->renameObj($m2->_t['s3'], 'Section2'); var_dump($r); + +$r = $m2->dumpTree(); echo "$r\n"; +?> \ No newline at end of file diff --git a/livesupport/modules/alib/var/mtree.php b/livesupport/modules/alib/var/mtree.php deleted file mode 100644 index f0da87b18..000000000 --- a/livesupport/modules/alib/var/mtree.php +++ /dev/null @@ -1,627 +0,0 @@ - - * example minimal config: - *
+ * example minimal config: + *+ * @author $Author: tomas $ + * @version $Revision: 1.1 $ + * @see ObjClasses + * Original author Tom Hlava + */ +class M2tree{ + /** + * Database object container + */ + var $dbc; + /** + * Configuration tree + */ + var $config; + /** + * Tree table name + */ + var $treeTable; + /** + * Structure table name + */ + var $structTable; + /** + * Root node name + */ + var $rootNodeName; + /** + * Constructor + * + * @param dbc object + * @param config array + * @return this + */ + function M2tree(&$dbc, $config) + { + $this->dbc =& $dbc; + $this->config = $config; + $this->treeTable = $config['tblNamePrefix'].'tree'; + $this->structTable = $config['tblNamePrefix'].'struct'; + $this->rootNodeName = $config['RootNode']; + } + + /* ======================================================= public methods */ + /** + * Add new object of specified type to the tree under specified parent + * node + * + * @param name string, mnemonic name for new object + * @param type string, type of new object + * @param parid int, optional, parent id + * @return int/err - new id of inserted object or PEAR::error + */ + function addObj($name, $type, $parid=NULL) + { + if($name=='' || $type=='') return $this->dbc->raiseError( + "M2tree::addObj: Wrong name or type", ALIBERR_MTREE + ); + if(is_null($parid)) $parid = $this->getRootNode(); + // changing name if the same is in the dest. folder: + for( ; + $xid = $this->getObjId($name, $parid), + !is_null($xid) && !$this->dbc->isError($xid); + $name .= "_" + ); + if($this->dbc->isError($xid)) return $xid; + // insert new object record: + $this->dbc->query("BEGIN"); + $oid = $this->dbc->nextId("{$this->treeTable}_id_seq"); + if($this->dbc->isError($oid)) return $this->_dbRollback($oid); + $r = $this->dbc->query(" + INSERT INTO {$this->treeTable} (id, name, type) + VALUES ($oid, '$name', '$type') + "); + if($this->dbc->isError($r)) return $this->_dbRollback($r); + $dataArr = array(); + // build data ($dataArr) for INSERT of structure records: + for($p=$parid, $l=1; !is_null($p); $p=$this->getParent($p), $l++){ + $rid = $this->dbc->nextId("{$this->structTable}_id_seq"); + if($this->dbc->isError($rid)) return $this->_dbRollback($rid); + $dataArr[] = array($rid, $oid, $p, $l); + } + // build and prepare INSERT command automatically: + $pr = $this->dbc->autoPrepare($this->structTable, + array('rid', 'objid', 'parid', 'level'), DB_AUTOQUERY_INSERT); + if($this->dbc->isError($pr)) return $this->_dbRollback($pr); + // execute INSERT command for $dataArr: + $r = $this->dbc->executeMultiple($pr, $dataArr); + if($this->dbc->isError($r)) return $this->_dbRollback($r); + $r = $this->dbc->query("COMMIT"); + if(PEAR::isError($r)) return $this->_dbRollback($r); + return $oid; + } + + /** + * Remove specified object + * + * @param oid int, object id to remove + * @return boolean/err - TRUE or PEAR::error + */ + function removeObj($oid) + { + if($oid == $this->getRootNode()){ + return $this->dbc->raiseError( + "M2tree::removeObj: Can't remove root" + ); + } + $dir = $this->getDir($oid); + if($this->dbc->isError($dir)) return $dir; + foreach($dir as $k=>$ch){ + $r = $this->removeObj($ch['id']); + if($this->dbc->isError($r)) return $r; + } + $r = $this->dbc->query(" + DELETE FROM {$this->treeTable} + WHERE id=$oid + "); + if($this->dbc->isError($r)) return $r; + /* done by automatic reference trigger: + $r = $this->dbc->query(" + DELETE FROM {$this->structTable} + WHERE objid=$oid + "); + if($this->dbc->isError($r)) return $r; + */ + return TRUE; + } + + + /** + * Create copy of specified object and insert copy to new position + * recursively + * + * @param oid int, source object id + * @param newParid int, destination parent id + * @param after null, dummy argument for back-compatibility + * @return int/err - new id of inserted object or PEAR::error + */ + function copyObj($oid, $newParid, $after=NULL) + { + if(TRUE === ($r = $this->isChildOf($newParid, $oid, TRUE))){ + return $this->dbc->raiseError( + "M2tree::copyObj: Can't copy into itself" + ); + } + if($this->dbc->isError($r)) return $r; + // get name: + $name = $this->getObjName($oid); + if($this->dbc->isError($name)) return $name; + // get parent id: + $parid = $this->getParent($oid); + if($this->dbc->isError($parid)) return $parid; + if($parid == $newParid) $name .= "_copy"; + // get type: + $type = $this->getObjType($oid); + if($this->dbc->isError($type)) return $type; + // look for children: + $dir = $this->getDir($oid, $flds='id'); + if($this->dbc->isError($dir)) return $dir; + // insert aktual object: + $nid = $this->addObj($name, $type, $newParid); + if($this->dbc->isError($nid)) return $nid; + // if no children: + if(is_null($dir)) return $nid; + // optionally insert children recursively: + foreach($dir as $k=>$item){ + $r = $this->copyObj($item['id'], $nid); + if($this->dbc->isError($r)) return $r; + } + return $nid; + } + + /** + * Move subtree to another node without removing/adding + * + * @param oid int + * @param newParid int + * @param after null, dummy argument for back-compatibility + * @return boolean/err + */ + function moveObj($oid, $newParid, $after=NULL) + { + if(TRUE === ( + $r = $this->isChildOf($newParid, $oid, TRUE) + || $oid == $newParid + )){ + return $this->dbc->raiseError( + "M2tree::moveObj: Can't move into itself" + ); + } + if($this->dbc->isError($r)) return $r; + // get name: + $name0 = $name = $this->getObjName($oid); + if($this->dbc->isError($name)) return $name; + $this->dbc->query("BEGIN"); + // cut it from source: + $r = $this->_cutSubtree($oid); + if($this->dbc->isError($r)) return $this->_dbRollback($r); + // changing name if the same is in the dest. folder: + for( ; + $xid = $this->getObjId($name, $newParid), + !is_null($xid) && !$this->dbc->isError($xid); + $name .= "_" + ); + if($this->dbc->isError($xid)) return $this->_dbRollback($xid); + if($name != $name0){ + $r = $this->renameObj($oid, $name); + if($this->dbc->isError($r)) return $this->_dbRollback($r); + } + // paste it to dest.: + $r = $this->_pasteSubtree($oid, $newParid); + if($this->dbc->isError($r)) return $this->_dbRollback($r); + $r = $this->dbc->query("COMMIT"); + if(PEAR::isError($r)) return $this->_dbRollback($r); + return TRUE; + } + + /** + * Rename of specified object + * + * @param oid int, object id to rename + * @param newName string, new name + * @return boolean/err - True or PEAR::error + */ + function renameObj($oid, $newName) + { + // get parent id: + $parid = $this->getParent($oid); + if($this->dbc->isError($parid)) return $parid; + // changing name if the same is in the folder: + for( ; + $xid = $this->getObjId($newName, $parid), + !is_null($xid) && !$this->dbc->isError($xid); + $newName .= "_" + ); + if($this->dbc->isError($xid)) return $xid; + $r = $this->dbc->query(" + UPDATE {$this->treeTable} + SET name='$newName' + WHERE id=$oid + "); + if($this->dbc->isError($r)) return $r; + return TRUE; + } + + /* --------------------------------------------------------- info methods */ + /** + * Search for child id by name in sibling set + * + * @param name string, searched name + * @param parId int, optional, parent id (default is root node) + * @return int/null/err - child id (if found) or null or PEAR::error + */ + function getObjId($name, $parId=NULL) + { + if($name=='' && is_null($parId)) $name = $this->rootNodeName; + $parcond = (is_null($parId) ? "parid is null" : "parid='$parId'"); + $r = $this->dbc->getOne(" + SELECT id FROM {$this->treeTable} t + LEFT JOIN {$this->structTable} s ON id=objid + WHERE name='$name' AND $parcond" + ); + if($this->dbc->isError($r)) return $r; + return $r; + } + + /** + * Get one value for object by id (default: get name) + * + * @param oid int + * @param fld string, optional, requested field (default: name) + * @return string/err + */ + function getObjName($oid, $fld='name') + { + $r = $this->dbc->getOne(" + SELECT $fld FROM {$this->treeTable} + WHERE id=$oid + "); + return $r; + } + + /** + * Get object type by id + * + * @param oid int + * @return string/err + */ + function getObjType($oid) + { + return $this->getObjName($oid, 'type'); + } + + /** + * Get parent id + * + * @param oid int + * @return int/err + */ + function getParent($oid) + { + $r = $this->dbc->getOne(" + SELECT parid FROM {$this->structTable} + WHERE objid=$oid AND level=1 + "); + return $r; + } + + /** + * Get array of nodes in object's path from root node + * + * @param oid int + * @param flds string, optional + * @return array/err + */ + function getPath($oid, $flds='id', $withSelf=TRUE) + { + $path = $this->dbc->getAll(" + SELECT $flds + FROM {$this->treeTable} + LEFT JOIN {$this->structTable} s ON id=parid + WHERE objid=$oid + ORDER BY coalesce(level, 0) DESC + "); + if($this->dbc->isError($path)) return $path; + if($withSelf){ + $r = $this->dbc->getRow(" + SELECT $flds FROM {$this->treeTable} + WHERE id=$oid + "); + if($this->dbc->isError($r)) return $r; + } + array_push($path, $r); + return $path; + } + + /** + * Get array of childnodes + * + * @param oid int + * @param flds string, optional, comma separated list of requested fields + * @param order string, optional, fieldname for order by clause + * @return array/err + */ + function getDir($oid, $flds='id', $order='name') + { + $r = $this->dbc->getAll(" + SELECT $flds + FROM {$this->treeTable} + INNER JOIN {$this->structTable} ON id=objid AND level=1 + WHERE parid=$oid + ORDER BY $order + "); + return $r; + } + + /** + * Get level of object relatively to specified root + * + * @param oid int, object id + * @param flds string, list of field names for select + * (optional - default: 'level') + * @param rootId int, root for relative levels + * (optional - default: NULL - use root of whole tree) + * @return hash-array with field name/value pairs + */ + function getObjLevel($oid, $flds='level', $rootId=NULL) + { + if(is_null($rootId)) $rootId = $this->getRootNode(); + $re = $this->dbc->getRow(" + SELECT $flds + FROM {$this->treeTable} + LEFT JOIN {$this->structTable} s ON id=objid AND parid=$rootId + WHERE id=$oid + "); + $re['level'] = intval($re['level']); + return $re; + } + + /** + * Get subtree of specified node + * + * @param oid int, optional, default: root node + * @param withRoot boolean, optional, include/exclude specified node + * @param rootId int, root for relative levels, optional + * @return array/err + */ + function getSubTree($oid=NULL, $withRoot=FALSE, $rootId=NULL) + { + if(is_null($oid)) $oid = $this->getRootNode(); + if(is_null($rootId)) $rootId = $oid; + $r = array(); + if($withRoot){ + $r[] = $re = $this->getObjLevel($oid, 'id, name, level', $rootId); + } + if($this->dbc->isError($re)) return $re; + $dirarr = $this->getDir($oid, 'id, level'); + if($this->dbc->isError($dirarr)) return $dirarr; + foreach($dirarr as $k=>$snod) + { + $re = $this->getObjLevel($snod['id'], 'id, name, level', $rootId); + if($this->dbc->isError($re)) return $re; +# $re['level'] = intval($re['level'])+1; + $r[] = $re; + $r = array_merge($r, + $this->getSubTree($snod['id'], FALSE, $rootId)); + } + return $r; + } + + /** + * Returns true if first object if child of second one + * + * @param oid int, object id of tested object + * @param parid int, object id of parent + * @param indirect boolean, test indirect or only direct relation + * @return boolean + */ + function isChildOf($oid, $parid, $indirect=FALSE) + { + if(!$indirect){ + $paridD = $this->getParent($oid); + if($this->dbc->isError($paridD)) return $paridD; + return ($paridD == $parid); + } + $path = $this->getPath($oid, 'id', FALSE); + if($this->dbc->isError($path)) return $path; + $res = FALSE; + foreach($path as $k=>$item){ if($item['id'] == $parid) $res = TRUE; } + return $res; + } + + /** + * Get id of root node + * + * @return int/err + */ + function getRootNode() + { + return $this->getObjId($this->rootNodeName); + } + + /** + * Get all objects in the tree as array of hashes + * + * @return array/err + */ + function getAllObjects() + { + return $this->dbc->getAll( + "SELECT * FROM {$this->treeTable}" + ); + } + + /* ------------------------ info methods related to application structure */ + /* (this part should be redefined in extended class to allow + * defining/modifying/using application structure) + * (only very simple structure definition - in $config - supported now) + */ + + /** + * Get child types allowed by application definition + * + * @param type string + * @return array + */ + function getAllowedChildTypes($type) + { + return $this->config['objtypes'][$type]; + } + + /* ==================================================== "private" methods */ + + /** + * Cut subtree of specified object from tree. + * Preserve subtree structure. + * + * @param oid int, object id + * @return boolean + */ + function _cutSubtree($oid) + { + $lvl = $this->getObjLevel($oid); + if($this->dbc->isError($lvl)) return $lvl; + $lvl = $lvl['level']; + // release downside structure + $r = $this->dbc->query(" + DELETE FROM {$this->structTable} + WHERE rid IN ( + SELECT s3.rid FROM {$this->structTable} s1 + INNER JOIN {$this->structTable} s2 ON s1.objid=s2.objid + INNER JOIN {$this->structTable} s3 ON s3.objid=s1.objid + WHERE (s1.parid=$oid OR s1.objid=$oid) + AND s2.parid=1 AND s3.level>(s2.level-$lvl) + ) + "); + if($this->dbc->isError($r)) return $r; + return TRUE; + } + + /** + * Paste subtree previously cut by _cutSubtree method into main tree + * + * @param oid int, object id + * @param newParid int, destination object id + * @return boolean + */ + function _pasteSubtree($oid, $newParid) + { + $dataArr = array(); + // build data ($dataArr) for INSERT: + foreach($this->getSubTree($oid, TRUE) as $o){ + $l=intval($o['level'])+1; + for($p=$newParid; !is_null($p); $p=$this->getParent($p), $l++){ + $rid = $this->dbc->nextId("{$this->structTable}_id_seq"); + if($this->dbc->isError($rid)) return $rid; + $dataArr[] = array($rid, $o['id'], $p, $l); + } + } + // build and prepare INSERT command automatically: + $pr = $this->dbc->autoPrepare($this->structTable, + array('rid', 'objid', 'parid', 'level'), DB_AUTOQUERY_INSERT); + if($this->dbc->isError($pr)) return $pr; + // execute INSERT command for $dataArr: + $r = $this->dbc->executeMultiple($pr, $dataArr); + if($this->dbc->isError($r)) return $r; + return TRUE; + } + + /** + * Do SQL rollback and return PEAR::error + * + * @param r object/string, error object or error message + * @return err + */ + function _dbRollback($r) + { + $this->dbc->query("ROLLBACK"); + if($this->dbc->isError($r)) return $r; + elseif(is_string($r)){ + $msg = basename(__FILE__)."::".get_class($this).": $r"; + }else{ + $msg = basename(__FILE__)."::".get_class($this).": unknown error"; + } + return $this->dbc->raiseError($msg, ALIBERR_MTREE, PEAR_ERROR_RETURN); + } + + /* ==================================================== auxiliary methods */ + + /** + * Human readable dump of subtree - for debug + * + * @param oid int, start object id + * @param indstr string, indentation string + * @param ind string, aktual indentation + * @return string + */ + function dumpTree($oid=NULL, $indstr=' ', $ind='', + $format='{name}({id})', $withRoot=TRUE) + { + $r=''; + foreach($st = $this->getSubTree($oid, $withRoot) as $o){ + if($this->dbc->isError($st)) return $st; + $r .= $ind.str_repeat($indstr, $o['level']). + preg_replace(array('|\{name\}|', '|\{id\}|'), + array($o['name'], $o['id']), $format). + "\n"; + } + return $r; + } + + /** + * Create tables + initialize root node + * + */ + function install() + { + $this->dbc->query("BEGIN"); + if(PEAR::isError($r)) return $r; + $r = $this->dbc->query("CREATE TABLE {$this->treeTable} ( + id int not null PRIMARY KEY, + name varchar(255) not null default'', + -- parid int, + type varchar(255) not null default'', + param varchar(255) + )"); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->createSequence("{$this->treeTable}_id_seq"); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->query("CREATE UNIQUE INDEX {$this->treeTable}_id_idx + ON {$this->treeTable} (id)"); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->query("CREATE INDEX {$this->treeTable}_name_idx + ON {$this->treeTable} (name)"); + if($this->dbc->isError($r)) return $r; + + $r = $this->dbc->query("CREATE TABLE {$this->structTable} ( + rid int not null PRIMARY KEY, + objid int not null REFERENCES {$this->treeTable} ON DELETE CASCADE, + parid int not null REFERENCES {$this->treeTable} ON DELETE CASCADE, + level int + )"); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->createSequence("{$this->structTable}_id_seq"); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->query("CREATE UNIQUE INDEX {$this->structTable}_rid_idx + ON {$this->structTable} (rid)"); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->query("CREATE INDEX {$this->structTable}_objid_idx + ON {$this->structTable} (objid)"); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->query("CREATE INDEX {$this->structTable}_parid_idx + ON {$this->structTable} (parid)"); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->query("CREATE INDEX {$this->structTable}_level_idx + ON {$this->structTable} (level)"); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->query(" + CREATE UNIQUE INDEX {$this->structTable}_objid_level_idx + ON {$this->structTable} (objid, level) + "); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->query(" + CREATE UNIQUE INDEX {$this->structTable}_objid_parid_idx + ON {$this->structTable} (objid, parid) + "); + if($this->dbc->isError($r)) return $r; + + $oid = $this->dbc->nextId("{$this->treeTable}_id_seq"); + if($this->dbc->isError($oid)) return $oid; + $r = $this->dbc->query(" + INSERT INTO {$this->treeTable} + (id, name, type) + VALUES + ($oid, '{$this->rootNodeName}', 'RootNode') + "); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->query("COMMIT"); + if(PEAR::isError($r)) return $r; + } + + /** + * Drop all tables and sequencies + * + */ + function uninstall() + { + $this->dbc->query("DROP TABLE {$this->structTable}"); + $this->dbc->dropSequence("{$this->structTable}_id_seq"); + $this->dbc->query("DROP TABLE {$this->treeTable}"); + $this->dbc->dropSequence("{$this->treeTable}_id_seq"); + } + + /** + * Uninstall and install + * + */ + function reinstall() + { + $this->uninstall(); + $this->install(); + } + + /** + * Clean up tree - delete all except root node + * + */ + function reset() + { + $rid = $this->getRootNode(); + if($this->dbc->isError($rid)) return $rid; + $r = $this->dbc->query("DELETE FROM {$this->structTable}"); + if($this->dbc->isError($r)) return $r; + $r = $this->dbc->query("DELETE FROM {$this->treeTable} WHERE id<>$rid"); + if($this->dbc->isError($r)) return $r; + } + + /** + * Insert test data to the tree. + * Only for compatibility with previous mtree - will be removed. + * + * @return array + */ + function test() + { + require_once"m2treeTest.php"; + $mt = &new M2treeTest($this->dbc, $this->config); + $r = $mt->_test(); + return $r; + } + + /** + * Insert test data to the tree. + * Only for compatibility with previous mtree - will be removed. + * + * @return array + */ + function testData() + { + $o['root'] = $this->getRootNode(); + $o['pa'] = $this->addObj('Publication A', 'Publication', $o['root']); + $o['i1'] = $this->addObj('Issue 1', 'Issue', $o['pa']); + $o['s1a'] = $this->addObj('Section a', 'Section', $o['i1']); + $o['s1b'] = $this->addObj('Section b', 'Section', $o['i1']); + $o['i2'] = $this->addObj('Issue 2', 'Issue', $o['pa']); + $o['s2a'] = $this->addObj('Section a', 'Section', $o['i2']); + $o['s2b'] = $this->addObj('Section b', 'Section', $o['i2']); + $o['t1'] = $this->addObj('Title', 'Title', $o['s2b']); + $o['s2c'] = $this->addObj('Section c', 'Section', $o['i2']); + $o['pb'] = $this->addObj('Publication B', 'Publication', $o['root']); + $this->tdata['tree'] = $o; + } +} +?> diff --git a/livesupport/modules/alib/var/m2treeTest.php b/livesupport/modules/alib/var/m2treeTest.php new file mode 100644 index 000000000..5f8b53211 --- /dev/null +++ b/livesupport/modules/alib/var/m2treeTest.php @@ -0,0 +1,210 @@ +addObj("Publication$i", "Publication"); + if($this->dbc->isError($r)) return $r; + $this->_t["p$i"] = $r; + } + for($i=1; $i<=3; $i++){ + $r = $this->addObj("Issue$i", "Issue", + $this->_t[$i<=2 ? 'p1' : 'p2']); + if($this->dbc->isError($r)) return $r; + $this->_t["i$i"] = $r; + } + for($i=1; $i<=4; $i++){ + $r = $this->addObj("Section$i", "Section", + $this->_t[$i<=3 ? 'i1' : 'i3']); + if($this->dbc->isError($r)) return $r; + $this->_t["s$i"] = $r; + } + $r = $this->addObj("Par1", "Par", $this->_t["s2"]); + if($this->dbc->isError($r)) return $r; + $this->_t["r1"] = $r; + } + function _test_check($title, $expected, $returned) + { + if($expected != $returned){ + return $this->dbc->raiseError( + "m2tree::$title FAILED:\n". + " ###expected:\n$expected\n ---\n". + " ###returned:\n$returned\n ---\n" + ); + } + return "# ".get_class($this)."::$title: OK\n"; + } + function _test() + { + echo "# M2tree test:\n"; + + // addObj/dumpTree test: + $r = $this->_test_addObj(); + if($this->dbc->isError($r)) return $r; + $expected = "RootNode + Publication1 + Issue1 + Section1 + Section2 + Par1 + Section3 + Issue2 + Publication2 + Issue3 + Section4 + Publication3 +"; + $returned = $this->dumpTree(NULL, ' ', '', '{name}'); + if($this->dbc->isError($returned)) return $returned; + $r = $this->_test_check('addObj/dumpTree', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + // shaking test: + $nid = $this->copyObj($this->_t['s2'], $this->_t['s4']); + if($this->dbc->isError($nid)) return $nid; + $r = $this->removeObj($this->_t['s2']); + if($this->dbc->isError($r)) return $r; + $r = $this->moveObj($nid, $this->_t['i1']); + if($this->dbc->isError($r)) return $r; + $returned = $this->dumpTree(NULL, ' ', '', '{name}'); + if($this->dbc->isError($returned)) return $returned; + $r = $this->_test_check('shaking test', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + + // removeObj test: + $r = $this->removeObj($this->_t['p2']); + if($this->dbc->isError($r)) return $r; + $expected = "RootNode + Publication1 + Issue1 + Section1 + Section2 + Par1 + Section3 + Issue2 + Publication3 +"; + $returned = $this->dumpTree(NULL, ' ', '', '{name}'); + $r = $this->_test_check('removeObj', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + // renameObj/getObjName test: + $original = $this->getObjName($this->_t['i2']); + if($this->dbc->isError($original)) return $original; + $changed = 'Issue2_changed'; + $expected = $original.$changed; + $r = $this->renameObj($this->_t['i2'], $changed); + if($this->dbc->isError($r)) return $r; + $r = $this->getObjName($this->_t['i2']); + if($this->dbc->isError($r)) return $r; + $returned = $r; + $r = $this->renameObj($this->_t['i2'], $original); + if($this->dbc->isError($r)) return $r; + $r = $this->getObjName($this->_t['i2']); + $returned = $r.$returned; + if($this->dbc->isError($r)) return $r; + $r = $this->_test_check('renameObj/getObjName', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + // getPath test: + $expected = "RootNode, Publication1, Issue1, Section3"; + $r = $this->getPath($this->_t['s3'], 'name'); + $returned = join(', ', array_map(create_function('$it', 'return $it["name"];'), $r)); + $r = $this->_test_check('getPath', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + // getObjType test: + $expected = 'Issue'; + $returned = $this->getObjType($this->_t['i2']); + $r = $this->_test_check('getObjType', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + // getParent test: + $expected = $this->_t['p1']; + $returned = $this->getParent($this->_t['i2']); + $r = $this->_test_check('getParent', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + // getDir test: + $expected = "Issue1, Issue2"; + $r = $this->getDir($this->_t['p1'], 'name'); + $returned = join(', ', array_map(create_function('$it', 'return $it["name"];'), $r)); + $r = $this->_test_check('getDir', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + // getObjId test: + $expected = $this->_t['i2']; + $returned = $this->getObjId('Issue2', $this->_t['p1']); + $r = $this->_test_check('getObjId', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + // getObjLevel test: + $expected = 2; + $r = $this->getObjLevel($this->_t['i2']); + if($this->dbc->isError($r)) return $r; + $returned = $r['level']; + $r = $this->_test_check('getObjLevel', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + // copyObj test: + $expected = "RootNode + Publication1 + Issue1 + Section1 + Section2 + Par1 + Section3 + Issue2 + Publication3 + Issue1 + Section1 + Section2 + Par1 + Section3 +"; + $nid = $this->copyObj($this->_t['i1'], $this->_t['p3']); + if($this->dbc->isError($nid)) return $nid; + $returned = $this->dumpTree(NULL, ' ', '', '{name}'); + $r = $this->_test_check('copyObj', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + $this->removeObj($nid); + + // moveObj test: + $expected = "RootNode + Publication1 + Issue2 + Publication3 + Issue1 + Section1 + Section2 + Par1 + Section3 +"; + $r = $this->moveObj($this->_t['i1'], $this->_t['p3']); + if($this->dbc->isError($r)) return $r; + $returned = $this->dumpTree(NULL, ' ', '', '{name}'); + $r = $this->_test_check('moveObj', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + // _cutSubtree test: + // _pasteSubtree test: + + echo $this->dumpTree(); + + // reset test: + $expected = "RootNode\n"; + $r = $this->reset(); + if($this->dbc->isError($r)) return $r; + $returned = $this->dumpTree(NULL, ' ', '', '{name}'); + $r = $this->_test_check('reset', $expected, $returned); + if($this->dbc->isError($r)) return $r; else echo $r; + + echo "# OK\n"; + return TRUE; + } + +} +?> \ No newline at end of file diff --git a/livesupport/modules/alib/var/m2treeTestRunner.php b/livesupport/modules/alib/var/m2treeTestRunner.php new file mode 100644 index 000000000..b86e8ebb5 --- /dev/null +++ b/livesupport/modules/alib/var/m2treeTestRunner.php @@ -0,0 +1,63 @@ +\n"; + echo "request: "; print_r($_REQUEST); + echo "\ngm:\n".$err->getMessage()."\nui:\n".$err->getUserInfo()."\n"; + echo "+ * $config = array( + * 'dsn' => array( // data source definition + * 'username' => DBUSER, + * 'password' => DBPASSWORD, + * 'hostspec' => 'localhost', + * 'phptype' => 'pgsql', + * 'database' => DBNAME + * ), + * 'tblNamePrefix' => 'al_', + * 'RootNode' =>'RootNode', + * ); + *
BackTrace:\n"; + print_r($err->backtrace); + echo "
- * $config = array(
- * 'dsn' => array( // data source definition
- * 'username' => DBUSER,
- * 'password' => DBPASSWORD,
- * 'hostspec' => 'localhost',
- * 'phptype' => 'pgsql',
- * 'database' => DBNAME
- * ),
- * 'tblNamePrefix' => 'al_',
- * 'RootNode' =>'RootNode',
- * );
- *
- * @author $Author: tomas $
- * @version $Revision: 1.5 $
- * @see ObjClasses
- */
-class Mtree{
- var $dbc;
- var $config;
- var $treeTable;
- var $rootNodeName;
- /** Constructor
- *
- * @param dbc object
- * @param config array
- * @return this
- */
- function Mtree(&$dbc, $config)
- {
- $this->dbc =& $dbc;
- $this->config = $config;
- $this->treeTable = $config['tblNamePrefix'].'tree';
- $this->rootNodeName = $config['RootNode'];
- }
-
- /* ======================================================= public methods */
-
- /**
- * Add new object of specified type to the tree under specified parent
- * node as last child or after specified sibling
- *
- * @param name string
- * @param type string
- * @param parid int, optional, parent id
- * @param aftid int, optional, after id
- * @return int/err - new id of inserted object or PEAR::error
- */
- function addObj($name, $type, $parid=1, $aftid=NULL)
- {
- if($name=='' || $type=='') return PEAR::raiseError(
- "Mtree::addObj: Wrong name or type", ALIBERR_MTREE
- );
- if(!is_numeric($parid)) return PEAR::raiseError(
- "Mtree::addObj: Wrong parid ($parid)", ALIBERR_MTREE
- );
- if(!is_numeric($aftid) && !is_null($aftid)) return PEAR::raiseError(
- "Mtree::addObj: Wrong aftid ($aftid)", ALIBERR_MTREE
- );
- $this->dbc->query("BEGIN");
- $r = $this->dbc->query("LOCK TABLE {$this->treeTable}");
- if(PEAR::isError($r)) return $r;
- // position resolving:
- if(is_null($aftid)){ // add object as last child
- $after = $this->dbc->getOne("
- SELECT max(rgt) FROM {$this->treeTable} WHERE parid='$parid'
- ");
- }else{ // use 'aftid'
- $after = $this->dbc->getOne("
- SELECT ".($aftid == $parid ? 'lft' : 'rgt')."
- FROM {$this->treeTable} WHERE id='$aftid'");
- }
- if(PEAR::isError($after)) return $this->_dbRollback($after);
- if(is_null($after)){ // position not specified - add as first child
- $after = $this->dbc->getOne("
- SELECT lft FROM {$this->treeTable} WHERE id='$parid'
- ");
- }
- if(PEAR::isError($after)) return $this->_dbRollback($after);
- $after = intval($after);
- // tree level resolving:
- $level = $this->dbc->getOne("SELECT level FROM {$this->treeTable}
- WHERE id='$parid'");
- if(is_null($level))
- return $this->_dbRollback('addObj: parent does not exist');
- if(PEAR::isError($level)) return $this->_dbRollback($level);
- $id = $this->dbc->nextId("{$this->treeTable}_id_seq");
- if(PEAR::isError($id)) return $this->_dbRollback($id);
- // creating space in rgt/lft sequencies:
- $r = $this->dbc->query("UPDATE {$this->treeTable} SET rgt=rgt+2
- WHERE rgt>$after");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query("UPDATE {$this->treeTable} SET lft=lft+2
- WHERE lft>$after");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- // inserting object:
- $r = $this->dbc->query("
- INSERT INTO {$this->treeTable}
- (id, name, type, parid, level, lft, rgt)
- VALUES
- ('$id', '$name', '$type', $parid,
- ".($level+1).", ".($after+1).", ".($after+2)."
- )
- ");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query("COMMIT");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- return $id;
- }
-
- /**
- * Create copy of specified object and insert copy to new position
- *
- * @param id int, source object id
- * @param newParid int, destination parent id
- * @param after int, optional, destinantion after id
- * @return int/err - new id of inserted object or PEAR::error
- */
- function copyObj($id, $newParid, $after=NULL)
- {
- $o = $this->dbc->getRow("SELECT * FROM {$this->treeTable}
- WHERE id='$id'");
- if(PEAR::isError($o)) return $o;
- $parid = $this->getParent($id);
- if($newParid == $parid) $o['name'] .= "_copy";
- $nid = $this->addObj(
- $o['name'], $o['type'], $newParid, $after, $o['param']
- );
- return $nid;
- }
-
- /**
- * Rename of specified object
- *
- * @param id int, object id to rename
- * @param newName string, new name
- * @return boolean/err - True or PEAR::error
- */
- function renameObj($id, $newName)
- {
- $r = $this->dbc->query("UPDATE {$this->treeTable} SET name='$newName'
- WHERE id='$id'");
- if(PEAR::isError($r)) return $r;
- return TRUE;
- }
-
- /**
- * Remove of specified object
- *
- * @param id int, object id to remove
- * @return boolean/err - TRUE or PEAR::error
- */
- function removeObj($id)
- {
- $dirarr = $this->getDir($id); if(PEAR::isError($dirarr)) return $dirarr;
- foreach($dirarr as $k=>$snod)
- {
- $this->removeObj($snod['id']);
- }
- $this->dbc->query("BEGIN");
- $r = $this->dbc->query("LOCK TABLE {$this->treeTable}");
- if(PEAR::isError($r)) return $r;
- $rgt = $this->dbc->getOne("SELECT rgt FROM {$this->treeTable}
- WHERE id='$id'");
- if(is_null($rgt))
- return $this->_dbRollback('removeObj: object not exists');
- // deleting object:
- $r = $this->dbc->query("DELETE FROM {$this->treeTable} WHERE id='$id'");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- // closing the space in rgt/lft sequencies:
- $r = $this->dbc->query("UPDATE {$this->treeTable} SET rgt=rgt-2
- WHERE rgt>$rgt");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query("UPDATE {$this->treeTable} SET lft=lft-2
- WHERE lft>$rgt");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query("COMMIT");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- return TRUE;
- }
-
- /* --------------------------------------------------------- info methods */
-
- /**
- * Search for child id by name
- *
- * @param name string, searched name
- * @param parId int, optional, parent id (default is root node)
- * @return int/null/err - child id (if found) or null or PEAR::error
- */
- function getObjId($name, $parId=NULL)
- {
- if($name=='' && is_null($parId)) $name = $this->rootNodeName;
- $r = $this->dbc->getOne(
- "SELECT id FROM {$this->treeTable}
- WHERE name='$name' and ".(is_null($parId) ? "parid is null" : "parid='$parId'")
- );
- if(PEAR::isError($r)) return $r;
- return $r;
- }
-
- /**
- * Get one value for object (default: get name) by id
- *
- * @param oid int
- * @param fld string, optional, requested field (default: name)
- * @return string/err
- */
- function getObjName($oid, $fld='name')
- {
- return $this->dbc->getOne("SELECT $fld FROM {$this->treeTable}
- WHERE id='$oid'");
- }
-
- /**
- * Get object type by id
- *
- * @param oid int
- * @return string/err
- */
- function getObjType($oid)
- {
- return $this->getObjName($oid, 'type');
- }
-
- /**
- * Get parent id
- *
- * @param oid int
- * @return int/err
- */
- function getParent($oid)
- {
- return $this->getObjName($oid, 'parid');
- }
-
- /**
- * Get array of nodes in object's path from root node
- *
- * @param id int
- * @param flds string, optional
- * @return array/err
- */
- function getPath($id, $flds='id')
- {
- $this->dbc->query("BEGIN");
- $a = $this->dbc->getRow("SELECT name, lft, rgt FROM {$this->treeTable}
- WHERE id='$id'");
- $res = $this->dbc->getAll("
- SELECT $flds FROM {$this->treeTable}
- WHERE lft<={$a['lft']} AND rgt>={$a['rgt']}
- ORDER by lft
- ");
- $this->dbc->query("COMMIT");
- return $res;
- }
-
- /**
- * Get array of childnodes
- *
- * @param id int
- * @param flds string, optional, comma separated list of requested fields
- * @param order string, optional, fieldname for order by clause
- * @return array/err
- */
- function getDir($id, $flds='id', $order='lft')
- {
- return $this->dbc->getAll("
- SELECT $flds FROM {$this->treeTable}
- WHERE parid='$id' ORDER BY $order
- ");
- }
-
- /**
- * Get subtree of specified node
- *
- * @param id int, optional, default: root node
- * @param withRoot boolean, optional, include/exclude specified node
- * @return array/err
- */
- function getSubTree($id=NULL, $withRoot=FALSE)
- {
- if(is_null($id)) $id = $this->getRootNode();
- $r = array();
- if($withRoot) $r[] = $re = $this->dbc->getRow(
- "SELECT id, name, level FROM {$this->treeTable} WHERE id='$id'"
- );
- if(PEAR::isError($r)) return $r;
- $dirarr = $this->getDir($id); if(PEAR::isError($dirarr)) return $dirarr;
- foreach($dirarr as $k=>$snod)
- {
- $r[] = $re = $this->dbc->getRow("SELECT id, name, level
- FROM {$this->treeTable} WHERE id={$snod['id']}");
- if(PEAR::isError($re)) return $re;
- $r = array_merge($r, $this->getSubTree($snod['id']));
- }
- return $r;
- }
-
- /**
- * Get id of root node
- *
- * @return int/err
- */
- function getRootNode()
- {
- return $this->getObjId($this->rootNodeName);
- }
-
- /**
- * Get all objects in the tree
- *
- * @return array/err
- */
- function getAllObjects()
- {
- return $this->dbc->getAll(
- "SELECT * FROM {$this->treeTable} ORDER BY lft"
- );
- }
-
- /* ------------------------ info methods related to application structure */
- /* (this part should be added/rewritten to allow defining/modifying/using
- * application structure)
- * (only very simple structure definition - in $config - supported now)
- */
-
- /**
- * Get child types allowed by application definition
- *
- * @param type string
- * @return array
- */
- function getAllowedChildTypes($type)
- {
- return $this->config['objtypes'][$type];
- }
-
-
- /* ==================================================== "private" methods */
-
-
- /**
- * Do SQL rollback and return PEAR::error
- *
- * @param r object/string
- * @return err
- */
- function _dbRollback($r)
- {
- $this->dbc->query("ROLLBACK");
- if(PEAR::isError($r)) return $r;
- elseif(is_string($r)) return PEAR::raiseError(
- "ERROR: ".get_class($this).": $r", ALIBERR_MTREE, PEAR_ERROR_RETURN
- );
- else return PEAR::raiseError(
- "ERROR: ".get_class($this).": unknown error",
- ALIBERR_MTREE, PEAR_ERROR_RETURN
- );
- }
-
- /**
- * Move subtree to another node without removing/adding
- * Little bit complicated - sorry - it probably should be simlified ... ;)
- *
- * @param id int
- * @param newParid int
- * @param after int
- * @return boolean/err
- */
- function _relocateSubtree($id, $newParid, $after=NULL)
- {
- $this->dbc->query("BEGIN");
- $r = $this->dbc->query("LOCK TABLE {$this->treeTable}");
- if(PEAR::isError($r)) return $r;
- // obtain values for source node:
- $a1 = $this->dbc->getRow("SELECT lft, rgt, level FROM {$this->treeTable}
- WHERE id='$id'");
- if(is_null($a1))
- return $this->_dbRollback('_relocateSubtree: object not exists');
- extract($a1);
- // values for destination node:
- $a2 = $this->dbc->getRow("SELECT rgt, level FROM {$this->treeTable}
- WHERE id='$newParid'");
- if(is_null($a2))return $this->_dbRollback(
- '_relocateSubtree: new parent not exists'
- );
- $nprgt = $a2['rgt']; $newLevel = $a2['level'];
- // calculate differencies:
- if(is_null($after)) $after = $nprgt-1;
- $dif1 = $rgt-$lft+1;
- $dif2 = $after-$lft+1;
- $dif3 = $newLevel-$level+1;
- // relocate the object"
- $r = $this->dbc->query(
- "UPDATE {$this->treeTable} SET parid='$newParid' WHERE id='$id'");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- if($after>$rgt){
- // relocate subtree to the right:
- $r = $this->dbc->query(
- "UPDATE {$this->treeTable} SET rgt=rgt+$dif1 WHERE rgt>$after");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query(
- "UPDATE {$this->treeTable} SET lft=lft+$dif1 WHERE lft>$after");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query("UPDATE {$this->treeTable}
- SET lft=lft+$dif2, rgt=rgt+$dif2, level=level+$dif3
- WHERE lft>=$lft AND rgt <=$rgt");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query(
- "UPDATE {$this->treeTable} SET rgt=rgt-$dif1 WHERE rgt>$rgt");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query(
- "UPDATE {$this->treeTable} SET lft=lft-$dif1 WHERE lft>$rgt");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- }else{
- // relocate subtree to the left:
- $r = $this->dbc->query(
- "UPDATE {$this->treeTable} SET rgt=rgt+$dif1 WHERE rgt>$after");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query(
- "UPDATE {$this->treeTable} SET lft=lft+$dif1 WHERE lft>$after");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query("UPDATE {$this->treeTable}
- SET lft=lft+$dif2-$dif1, rgt=rgt+$dif2-$dif1, level=level+$dif3
- WHERE lft>=$lft+$dif1 AND rgt <=$rgt+$dif1");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query("UPDATE {$this->treeTable} SET rgt=rgt-$dif1
- WHERE rgt>$rgt+$dif1");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- $r = $this->dbc->query("UPDATE {$this->treeTable} SET lft=lft-$dif1
- WHERE lft>$rgt+$dif1");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- }
- $r = $this->dbc->query("COMMIT");
- if(PEAR::isError($r)) return $this->_dbRollback($r);
- return TRUE;
- }
-
- /**
- * Recursive copyObj - copy of whole subtree
- *
- * @param id int
- * @param newParid int
- * @param after int
- * @return array
- */
- function _copySubtree($id, $newParid, $after=NULL)
- {
- $nid = $this->copyObj($id, $newParid, $after);
- if(PEAR::isError($nid)) return $nid;
- $dirarr = $this->getDir($id); if(PEAR::isError($dirarr)) return $dirarr;
- foreach($dirarr as $k=>$snod)
- {
- $r = $this->_copySubtree($snod['id'], $nid);
- if(PEAR::isError($r)) return $r;
- }
- return TRUE;
- }
-
- /* =============================================== test and debug methods */
-
- /**
- * Human readable dump of subtree - for debug
- *
- * @param id int
- * @param indstr string, indentation string
- * @param ind string, aktual indentation
- * @return string
- */
- function dumpTree($id=NULL, $indstr=' ', $ind='',
- $format='{name}', $withRoot=TRUE)
- {
- $r='';
- foreach($this->getSubTree($id, $withRoot) as $o)
- $r .= str_repeat($indstr, intval($o['level'])).
- preg_replace(array('|\{name\}|', '|\{id\}|'),
- array($o['name'], $o['id']), $format).
- "\n";
- return $r;
- }
-
- /**
- * Delete all nodes except the root
- *
- */
- function deleteData()
- {
- $this->dbc->query("DELETE FROM {$this->treeTable}
- WHERE parid is not null");
- }
-
- /**
- * Insert test data to the tree
- *
- * @return array
- */
- function testData()
- {
- $o[] = $rootId = $this->getRootNode();
- $o[] = $p1 = $this->addObj('Publication A', 'Publication', $rootId); //1
- $o[] = $p2 = $this->addObj('Publication B', 'Publication', $rootId); //2
- $o[] = $i1 = $this->addObj('Issue 1', 'Issue', $p1); //3
- $o[] = $i2 = $this->addObj('Issue 2', 'Issue', $p1); //4
- $o[] = $s1 = $this->addObj('Section a', 'Section', $i2);
- $o[] = $s2 = $this->addObj('Section b', 'Section', $i2); //6
- $o[] = $s3 = $this->addObj('Section c', 'Section', $i2);
- $o[] = $t1 = $this->addObj('Title', 'Title', $s2);
- $o[] = $s4 = $this->addObj('Section a', 'Section', $i1);
- $o[] = $s5 = $this->addObj('Section b', 'Section', $i1);
- $this->tdata['tree'] = $o;
- }
-
- /**
- * Make basic test
- *
- */
- function test()
- {
- $this->deleteData();
- $this->testData();
- $rootId = $this->getRootNode();
- $this->test_correct ="RootNode\n Publication A\n Issue 1\n".
- " Section a\n Section b\n Issue 2\n".
- " Section a\n Section b\n".
- " Title\n Section c\n Publication B\n".
- "RootNode\n";
- $this->test_dump = $this->dumpTree();
- $this->removeObj($this->tdata['tree'][1]);
- $this->removeObj($this->tdata['tree'][2]);
- $this->test_dump .= $this->dumpTree();
- $this->deleteData();
- if($this->test_dump == $this->test_correct){
- $this->test_log.="tree: OK\n"; return TRUE;
- }else return PEAR::raiseError('Mtree::test:', 1, PEAR_ERROR_DIE, '%s'.
- "\ncorrect:\n.{$this->test_correct}.\n". - "dump:\n.{$this->test_dump}.\n\n"); - } - - /** - * Create tables + initialize - * - */ - function install() - { - $this->dbc->query("CREATE TABLE {$this->treeTable} ( - id int not null PRIMARY KEY, - name varchar(255) not null default'', - parid int, - lft int, - rgt int, - level int, - type varchar(255) not null default'', - param varchar(255) - )"); - $this->dbc->query("CREATE UNIQUE INDEX {$this->treeTable}_id_idx - ON {$this->treeTable} (id)"); - $this->dbc->query("CREATE INDEX {$this->treeTable}_name_idx - ON {$this->treeTable} (name)"); - $this->dbc->createSequence("{$this->treeTable}_id_seq"); - - $id = $this->dbc->nextId("{$this->treeTable}_id_seq"); - $this->dbc->query("INSERT INTO {$this->treeTable} - (id, name, parid, level, lft, rgt, type) - VALUES - ($id, '{$this->rootNodeName}', NULL, 0, 1, 2, 'RootNode')"); - } - - /** - * Drop tables etc. - * - */ - function uninstall() - { - $this->dbc->query("DROP TABLE {$this->treeTable}"); - $this->dbc->dropSequence("{$this->treeTable}_id_seq"); - } - - /** - * Uninstall and install - * - */ - function reinstall() - { - $this->uninstall(); - $this->install(); - } -} -?> \ No newline at end of file diff --git a/livesupport/modules/alib/var/subj.php b/livesupport/modules/alib/var/subj.php index c365df2d3..316c369c1 100644 --- a/livesupport/modules/alib/var/subj.php +++ b/livesupport/modules/alib/var/subj.php @@ -23,7 +23,7 @@ Author : $Author: tomas $ - Version : $Revision: 1.5 $ + Version : $Revision: 1.6 $ Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/alib/var/subj.php,v $ ------------------------------------------------------------------------------*/ @@ -39,7 +39,7 @@ define('ALIBERR_BADSMEMB', 21); * (allow adding users to groups or groups to groups) * * @author $Author: tomas $ - * @version $Revision: 1.5 $ + * @version $Revision: 1.6 $ * @see ObjClasses * @see Alib */ @@ -71,6 +71,9 @@ class Subjects extends ObjClasses{ */ function addSubj($login, $pass=NULL) { + if(!$login) return $this->dbc->raiseError( + get_class($this)."::addSubj: empty login" + ); $id = $this->dbc->nextId("{$this->subjTable}_id_seq"); if(PEAR::isError($id)) return $id; $r = $this->dbc->query(" @@ -465,21 +468,22 @@ class Subjects extends ObjClasses{ function testData() { parent::testData(); - $o[] = $this->addSubj('root', 'q'); - $o[] = $this->addSubj('test1', 'a'); - $o[] = $this->addSubj('test2', 'a'); - $o[] = $this->addSubj('test3', 'a'); - $o[] = $this->addSubj('test4', 'a'); - $o[] = $this->addSubj('gr1'); - $o[] = $this->addSubj('gr2'); -# $this->addSubj2Gr('test1', 'gr1'); - $this->addSubj2Gr('test2', 'gr1'); - $this->addSubj2Gr('test3', 'gr1'); - $this->addSubj2Gr('test4', 'gr2'); - $this->addSubj2Gr('gr2', 'gr1'); - $o[] = $this->addSubj('gr3'); + $o['root'] = $this->addSubj('root', 'q'); + $o['test1'] = $this->addSubj('test1', 'a'); + $o['test2'] = $this->addSubj('test2', 'a'); + $o['test3'] = $this->addSubj('test3', 'a'); + $o['test4'] = $this->addSubj('test4', 'a'); + $o['test5'] = $this->addSubj('test5', 'a'); + $o['gr1'] = $this->addSubj('gr1'); + $o['gr2'] = $this->addSubj('gr2'); + $o['gr3'] = $this->addSubj('gr3'); + $o['gr4'] = $this->addSubj('gr4'); + $this->addSubj2Gr('test1', 'gr1'); + $this->addSubj2Gr('test2', 'gr2'); $this->addSubj2Gr('test3', 'gr3'); - $this->addSubj2Gr('test1', 'gr3'); + $this->addSubj2Gr('test4', 'gr4'); + $this->addSubj2Gr('test5', 'gr1'); + $this->addSubj2Gr('gr4', 'gr3'); $this->addSubj2Gr('gr3', 'gr2'); return $this->tdata['subjects'] = $o; } @@ -494,14 +498,14 @@ class Subjects extends ObjClasses{ $this->deleteData(); $this->testData(); $this->test_correct = "root(0), test1(0), test2(0), test3(0),". - " test4(0), gr1(3), gr2(2), gr3(2)\n"; + " test4(0), test5(0), gr1(2), gr2(2), gr3(2), gr4(1)\n"; $this->test_dump = $this->dumpSubjects(); $this->removeSubj('test1'); $this->removeSubj('test3'); - $this->removeSubjFromGr('test2', 'gr1'); + $this->removeSubjFromGr('test5', 'gr1'); $this->removeSubjFromGr('gr3', 'gr2'); - $this->test_correct .= "root(0), test2(0), test4(0), gr1(1),". - " gr2(1), gr3(0)\n"; + $this->test_correct .= "root(0), test2(0), test4(0), test5(0),". + " gr1(0), gr2(1), gr3(1), gr4(1)\n"; $this->test_dump .= $this->dumpSubjects(); $this->deleteData(); if($this->test_dump == $this->test_correct)