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 @@
+
+ *   example minimal config:
+ *   

+ *    $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.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 "
BackTrace:\n"; + print_r($err->backtrace); + echo "
\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: - *

- *    $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)