Tree
[ class tree: Tree ] [ index: Tree ] [ all elements ]

Source for file MDB2nested.php

Documentation is available at MDB2nested.php

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors:                                                             |
  17. // +----------------------------------------------------------------------+
  18. //
  19. //  $Id: MDB2nested.php,v 1.1.2.1 2007/06/01 23:05:38 dufuz Exp $
  20.  
  21. require_once 'Tree/OptionsMDB2.php';
  22.  
  23. /**
  24. * This class implements methods to work on a tree saved using the nested
  25. * tree model.
  26. * explaination: http://research.calacademy.org/taf/proceedings/ballew/index.htm
  27. *
  28. @access     public
  29. @package    Tree
  30. */
  31. {
  32.  
  33.     // {{{ properties
  34.     var $debug = 0;
  35.  
  36.     var $options = array(
  37.         // FIXXME to be implemented
  38.         // add on for the where clause, this string is simply added
  39.         // behind the WHERE in the select so you better make sure
  40.         // its correct SQL :-), i.e. 'uid=3'
  41.         // this is needed i.e. when you are saving many trees in one db-table
  42.         'whereAddOn'=>'',
  43.         'table'     =>'',
  44.         // since the internal names are fixed, to be portable between different
  45.         // DB tables with different column namings, we map the internal name
  46.         // to the real column name using this array here, if it stays empty
  47.         // the internal names are used, which are:
  48.         // id, left, right
  49.         'columnNameMaps'=>array(
  50.                             // since mysql at least doesnt support 'left' ...
  51.                             'left'      =>  'l',
  52.                             // ...as a column name we set default to the first
  53.                             //letter only
  54.                             'right'     =>  'r',
  55.                             // parent id
  56.                             'parentId'  =>  'parent'
  57.                        ),
  58.         // needed for sorting the tree, currently only used in Memory_DBnested
  59.         'order'    => ''
  60.        );
  61.  
  62.     // }}}
  63.     // {{{ __construct()
  64.  
  65.     // the defined methods here are proposals for the implementation,
  66.     // they are named the same, as the methods in the "Memory" branch.
  67.     // If possible it would be cool to keep the same naming. And
  68.     // if the same parameters would be possible too then it would be
  69.     // even better, so one could easily change from any kind
  70.     // of tree-implementation to another, without changing the source
  71.     // code, only the setupXXX would need to be changed
  72.     /**
  73.       *
  74.       *
  75.       * @access     public
  76.       * @version    2002/03/02
  77.       * @param      string  the DSN for the DB connection
  78.       * @return     void 
  79.       */
  80.     function __construct($dsn$options = array())
  81.     {
  82.         $this->Tree_Dynamic_MDB2nested($dsn$options);
  83.     }
  84.  
  85.     // }}}
  86.     // {{{ Tree_Dynamic_DBnested()
  87.  
  88.     /**
  89.      *
  90.      *
  91.      * @access     public
  92.      * @version    2002/03/02
  93.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  94.      * @param      string  the DSN for the DB connection
  95.      * @return     void 
  96.      */
  97.     function Tree_Dynamic_MDB2nested($dsn$options = array())
  98.     {
  99.         parent::Tree_OptionsMDB2($dsn$options)// instanciate DB
  100.         $this->table $this->getOption('table');
  101.     }
  102.  
  103.     // }}}
  104.     // {{{ add()
  105.  
  106.     /**
  107.      * add a new element to the tree
  108.      * there are three ways to use this method
  109.      * Method 1:
  110.      * Give only the $parentId and the $newValues will be inserted
  111.      * as the first child of this parent
  112.      * <code>
  113.      * // insert a new element under the parent with the ID=7
  114.      * $tree->add(array('name'=>'new element name'), 7);
  115.      * </code>
  116.      *
  117.      * Method 2:
  118.      * Give the $prevId ($parentId will be dismissed) and the new element
  119.      * will be inserted in the tree after the element with the ID=$prevId
  120.      * the parentId is not necessary because the prevId defines exactly where
  121.      * the new element has to be place in the tree, and the parent is
  122.      * the same as for the element with the ID=$prevId
  123.      * <code>
  124.      * // insert a new element after the element with the ID=5
  125.      * $tree->add(array('name'=>'new'), 0, 5);
  126.      * </code>
  127.      *
  128.      * Method 3:
  129.      * neither $parentId nor prevId is given, then the root element will be
  130.      * inserted. This requires that programmer is responsible to confirm this.
  131.      * This method does not yet check if there is already a root element saved!
  132.      *
  133.      * @access     public
  134.      * @param   array   $newValues  this array contains the values that shall
  135.      *                               be inserted in the db-table
  136.      * @param   integer $parentId   the id of the element which shall be
  137.      *                               the parent of the new element
  138.      * @param   integer $prevId     the id of the element which shall preceed
  139.      *                               the one to be inserted use either
  140.      *                               'parentId' or 'prevId'.
  141.      * @return   integer the ID of the element that had been inserted
  142.      */
  143.     function add($newValues$parentId = 0$prevId = 0)
  144.     {
  145.         $lName $this->_getColName('left');
  146.         $rName $this->_getColName('right');
  147.         $prevVisited = 0;
  148.  
  149.         // check the DB-table if the columns which are given as keys
  150.         // in the array $newValues do really exist, if not remove them
  151.         // from the array
  152.         // FIXXME do the above described
  153.         // if no parent and no prevId is given the root shall be added
  154.         if ($parentId || $prevId{
  155.             if ($prevId{
  156.                 $element $this->getElement($prevId);
  157.                 // we also need the parent id of the element
  158.                 // to write it in the db
  159.                 $parentId $element['parentId'];
  160.             else {
  161.                 $element $this->getElement($parentId);
  162.             }
  163.             $newValues['parentId'$parentId;
  164.  
  165.             if (Tree::isError($element)) {
  166.                 return $element;
  167.             }
  168.  
  169.             // get the "visited"-value where to add the new element behind
  170.             // if $prevId is given, we need to use the right-value
  171.             // if only the $parentId is given we need to use the left-value
  172.             // look at it graphically, that made me understand it :-)
  173.             // See:
  174.             // http://research.calacademy.org/taf/proceedings/ballew/sld034.htm
  175.             $prevVisited $prevId $element['right'$element['left'];
  176.  
  177.             // FIXXME start transaction here
  178.             if (Tree::isError($err $this->_add($prevVisited1))) {
  179.                 // FIXXME rollback
  180.                 //$this->dbh->rollback();
  181.                 return $err;
  182.             }
  183.         }
  184.  
  185.         // inserting _one_ new element in the tree
  186.         $newData = array();
  187.         // quote the values, as needed for the insert
  188.         foreach ($newValues as $key => $value{
  189.  
  190.             ///////////FIX ME: Add proper quote handling
  191.  
  192.             $newData[$this->_getColName($key)$this->dbh->getTextValue($value);
  193.         }
  194.  
  195.         // set the proper right and left values
  196.         $newData[$lName$prevVisited + 1;
  197.         $newData[$rName$prevVisited + 2;
  198.  
  199.         // use sequences to create a new id in the db-table
  200.         $nextId $this->dbh->nextId($this->table);
  201.         $query sprintf('INSERT INTO %s (%s,%s) VALUES (%s,%s)',
  202.                             $this->table,
  203.                             $this->_getColName('id'),
  204.                             implode(','array_keys($newData)) ,
  205.                             $this->dbh->getIntegerValue($nextId),
  206.                             implode(','$newData)
  207.                         );
  208.         if (MDB2::isError($res $this->dbh->exec($query))) {
  209.             // rollback
  210.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  211.         }
  212.         // commit here
  213.  
  214.         return $nextId;
  215.     }
  216.  
  217.     // }}}
  218.     // {{{ _add()
  219.  
  220.     /**
  221.      * this method only updates the left/right values of all the
  222.      * elements that are affected by the insertion
  223.      * be sure to set the parentId of the element(s) you insert
  224.      *
  225.      * @param  int     this parameter is not the ID!!!
  226.      *                  it is the previous visit number, that means
  227.      *                  if you are inserting a child, you need to use the left-value
  228.      *                  of the parent
  229.      *                  if you are inserting a "next" element, on the same level
  230.      *                  you need to give the right value !!
  231.      * @param  int     the number of elements you plan to insert
  232.      * @return mixed   either true on success or a Tree_Error on failure
  233.      */
  234.     function _add($prevVisited$numberOfElements = 1)
  235.     {
  236.         $lName $this->_getColName('left');
  237.         $rName $this->_getColName('right');
  238.  
  239.         // update the elements which will be affected by the new insert
  240.         $query sprintf('UPDATE %s SET %s=%s+%s WHERE%s %s>%s',
  241.                             $this->table,
  242.                             $lName,
  243.                             $lName,
  244.                             $numberOfElements*2,
  245.                             $this->_getWhereAddOn(),
  246.                             $lName,
  247.                             $prevVisited);
  248.         if (MDB2::isError($res $this->dbh->exec($query))) {
  249.             // FIXXME rollback
  250.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  251.         }
  252.  
  253.         $query sprintf('UPDATE %s SET %s=%s+%s WHERE%s %s>%s',
  254.                             $this->table,
  255.                             $rName,$rName,
  256.                             $numberOfElements*2,
  257.                             $this->_getWhereAddOn(),
  258.                             $rName,
  259.                             $prevVisited);
  260.         if (MDB2::isError($res $this->dbh->exec($query))) {
  261.             // FIXXME rollback
  262.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  263.         }
  264.         return true;
  265.     }
  266.  
  267.     // }}}
  268.     // {{{ remove()
  269.  
  270.     /**
  271.      * remove a tree element
  272.      * this automatically remove all children and their children
  273.      * if a node shall be removed that has children
  274.      *
  275.      * @access     public
  276.      * @param      integer $id the id of the element to be removed
  277.      * @return     boolean returns either true or throws an error
  278.      */
  279.     function remove($id)
  280.     {
  281.         $element $this->getElement($id);
  282.         if (Tree::isError($element)) {
  283.             return $element;
  284.         }
  285.  
  286.         // FIXXME start transaction
  287.         //$this->dbh->autoCommit(false);
  288.         $query sprintf(  'DELETE FROM %s WHERE%s %s BETWEEN %s AND %s',
  289.                             $this->table,
  290.                             $this->_getWhereAddOn(),
  291.                             $this->_getColName('left'),
  292.                             $element['left'],$element['right']);
  293.         if (MDB2::isError($res $this->dbh->exec($query))) {
  294.             // FIXXME rollback
  295.             //$this->dbh->rollback();
  296.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  297.         }
  298.  
  299.         if (Tree::isError($err $this->_remove($element))) {
  300.             // FIXXME rollback
  301.             //$this->dbh->rollback();
  302.             return $err;
  303.         }
  304.         return true;
  305.     }
  306.  
  307.     // }}}
  308.     // {{{ _remove()
  309.  
  310.     /**
  311.      * removes a tree element, but only updates the left/right values
  312.      * to make it seem as if the given element would not exist anymore
  313.      * it doesnt remove the row(s) in the db itself!
  314.      *
  315.      * @see        getElement()
  316.      * @access     private
  317.      * @param      array   the entire element returned by "getElement"
  318.      * @return     boolean returns either true or throws an error
  319.      */
  320.     function _remove($element)
  321.     {
  322.         $delta $element['right'$element['left'+ 1;
  323.         $lName $this->_getColName('left');
  324.         $rName $this->_getColName('right');
  325.  
  326.         // update the elements which will be affected by the remove
  327.         $query sprintf("UPDATE
  328.                                 %s
  329.                             SET
  330.                                 %s=%s-$delta,
  331.                                 %s=%s-$delta
  332.                             WHERE%s %s>%s",
  333.                             $this->table,
  334.                             $lName$lName,
  335.                             $rName$rName,
  336.                             $this->_getWhereAddOn(),
  337.                             $lName$element['left']);
  338.         if (MDB2::isError($res $this->dbh->exec($query))) {
  339.             // the rollback shall be done by the method calling this one
  340.             // since it is only private we can do that
  341.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  342.         }
  343.  
  344.         $query sprintf("UPDATE
  345.                                 %s
  346.                             SET %s=%s-$delta
  347.                             WHERE
  348.                                 %s %s < %s
  349.                                 AND
  350.                                 %s>%s",
  351.                             $this->table,
  352.                             $rName$rName,
  353.                             $this->_getWhereAddOn(),
  354.                             $lName$element['left'],
  355.                             $rName$element['right']);
  356.         if (MDB2::isError($res $this->dbh->exec($query))) {
  357.             // the rollback shall be done by the method calling this one
  358.             // since it is only private
  359.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  360.         }
  361.         // FIXXME commit:
  362.         // should that not also be done in the method calling this one?
  363.         // like when an error occurs?
  364.         //$this->dbh->commit();
  365.         return true;
  366.     }
  367.  
  368.     // }}}
  369.     // {{{ move()
  370.  
  371.     /**
  372.      * move an entry under a given parent or behind a given entry.
  373.      * If a newPrevId is given the newParentId is dismissed!
  374.      * call it either like this:
  375.      *  $tree->move(x, y)
  376.      *  to move the element (or entire tree) with the id x
  377.      *  under the element with the id y
  378.      * or
  379.      *  $tree->move(x, 0, y);   // ommit the second parameter by setting
  380.      *  it to 0
  381.      *  to move the element (or entire tree) with the id x
  382.      *  behind the element with the id y
  383.      * or
  384.      *  $tree->move(array(x1,x2,x3), ...
  385.      *  the first parameter can also be an array of elements that shall
  386.      *  be moved. the second and third para can be as described above.
  387.      *
  388.      * If you are using the Memory_DBnested then this method would be invain,
  389.      * since Memory.php already does the looping through multiple elements.
  390.      * But if Dynamic_DBnested is used we need to do the looping here
  391.      *
  392.      * @version    2002/06/08
  393.      * @access     public
  394.      * @param      integer  the id(s) of the element(s) that shall be moved
  395.      * @param      integer  the id of the element which will be the new parent
  396.      * @param      integer  if prevId is given the element with the id idToMove
  397.      *                       shall be moved _behind_ the element with id=prevId
  398.      *                       if it is 0 it will be put at the beginning
  399.      * @return     mixed    true for success, Tree_Error on failure
  400.      */
  401.     function move($idsToMove$newParentId$newPrevId = 0)
  402.     {
  403.         settype($idsToMove,'array');
  404.         $errors = array();
  405.         foreach ($idsToMove as $idToMove{
  406.             $ret $this->_move($idToMove$newParentId$newPrevId);
  407.             if (Tree::isError($ret)) {
  408.                 $errors[$ret;
  409.             }
  410.         }
  411.         // FIXXME the error in a nicer way, or even better
  412.         // let the throwError method do it!!!
  413.         if (sizeof($errors)) {
  414.             return Tree::raiseError('TREE_ERROR_UNKOWN_ERROR'serialize($errors));
  415.         }
  416.         return true;
  417.     }
  418.  
  419.     // }}}
  420.     // {{{ _move()
  421.  
  422.     /**
  423.      * this method moves one tree element
  424.      *
  425.      * @see     move()
  426.      * @version 2002/04/29
  427.      * @access  public
  428.      * @param   integer the id of the element that shall be moved
  429.      * @param   integer the id of the element which will be the new parent
  430.      * @param   integer if prevId is given the element with the id idToMove
  431.      *                   shall be moved _behind_ the element with id=prevId
  432.      *                   if it is 0 it will be put at the beginning
  433.      * @return  mixed    true for success, Tree_Error on failure
  434.      */
  435.     function _move($idToMove$newParentId$newPrevId = 0)
  436.     {
  437.         // do some integrity checks first
  438.         if ($newPrevId{
  439.             // dont let people move an element behind itself, tell it
  440.             // succeeded, since it already is there :-)
  441.             if ($newPrevId == $idToMove{
  442.                 return true;
  443.             }
  444.             if (Tree::isError($newPrevious=$this->getElement($newPrevId))) {
  445.                 return $newPrevious;
  446.             }
  447.             $newParentId $newPrevious['parentId'];
  448.         else {
  449.             if ($newParentId == 0{
  450.                 return Tree::raiseError('TREE_ERROR_UNKOWN_ERROR''no parent id given');
  451.             }
  452.             // if the element shall be moved under one of its children
  453.             // return false
  454.             if ($this->isChildOf($idToMove,$newParentId)) {
  455.                 return Tree::raiseError('TREE_ERROR_UNKOWN_ERROR'
  456.                             'can not move an element under one of its children'
  457.                         );
  458.             }
  459.             // dont do anything to let an element be moved under itself
  460.             // which is bullshit
  461.             if ($newParentId==$idToMove{
  462.                 return true;
  463.             }
  464.             // try to retreive the data of the parent element
  465.             if (Tree::isError($newParent $this->getElement($newParentId))) {
  466.                 return $newParent;
  467.             }
  468.         }
  469.         // get the data of the element itself
  470.         if (Tree::isError($element $this->getElement($idToMove))) {
  471.             return $element;
  472.         }
  473.  
  474.         $numberOfElements ($element['right'$element['left'+ 1/ 2;
  475.         $prevVisited $newPrevId $newPrevious['right'$newParent['left'];
  476.  
  477.         // FIXXME start transaction
  478.  
  479.         // add the left/right values in the new parent, to have the space
  480.         // to move the new values in
  481.         $err=$this->_add($prevVisited$numberOfElements);
  482.         if (Tree::isError($err)) {
  483.             // FIXXME rollback
  484.             //$this->dbh->rollback();
  485.             return $err;
  486.         }
  487.  
  488.         // update the parentId of the element with $idToMove
  489.         $err $this->update($idToMovearray('parentId' => $newParentId));
  490.         if (Tree::isError($err)) {
  491.             // FIXXME rollback
  492.             //$this->dbh->rollback();
  493.             return $err;
  494.         }
  495.  
  496.         // update the lefts and rights of those elements that shall be moved
  497.  
  498.         // first get the offset we need to add to the left/right values
  499.         // if $newPrevId is given we need to get the right value,
  500.         // otherwise the left since the left/right has changed
  501.         // because we already updated it up there. We need to get them again.
  502.         // We have to do that anyway, to have the proper new left/right values
  503.         if ($newPrevId{
  504.             if (Tree::isError($temp $this->getElement($newPrevId))) {
  505.                 // FIXXME rollback
  506.                 //$this->dbh->rollback();
  507.                 return $temp;
  508.             }
  509.             $calcWith $temp['right'];
  510.         else {
  511.             if (Tree::isError($temp $this->getElement($newParentId))) {
  512.                 // FIXXME rollback
  513.                 //$this->dbh->rollback();
  514.                 return $temp;
  515.             }
  516.             $calcWith $temp['left'];
  517.         }
  518.  
  519.         // get the element that shall be moved again, since the left and
  520.         // right might have changed by the add-call
  521.         if (Tree::isError($element $this->getElement($idToMove))) {
  522.             return $element;
  523.         }
  524.         // calc the offset that the element to move has
  525.         // to the spot where it should go
  526.         $offset $calcWith $element['left'];
  527.         // correct the offset by one, since it needs to go inbetween!
  528.         $offset++;
  529.  
  530.         $lName $this->_getColName('left');
  531.         $rName $this->_getColName('right');
  532.         $query sprintf("UPDATE
  533.                                 %s
  534.                             SET
  535.                                 %s=%s+$offset,
  536.                                 %s=%s+$offset
  537.                             WHERE
  538.                                 %s %s>%s
  539.                                 AND
  540.                                 %s < %s",
  541.                             $this->table,
  542.                             $rName$rName,
  543.                             $lName$lName,
  544.                             $this->_getWhereAddOn(),
  545.                             $lName$element['left']-1,
  546.                             $rName$element['right']+1);
  547.         if (MDB2::isError($res $this->dbh->exec($query))) {
  548.             // FIXXME rollback
  549.             //$this->dbh->rollback();
  550.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  551.         }
  552.  
  553.         // remove the part of the tree where the element(s) was/were before
  554.         if (Tree::isError($err $this->_remove($element))) {
  555.             // FIXXME rollback
  556.             //$this->dbh->rollback();
  557.             return $err;
  558.         }
  559.         // FIXXME commit all changes
  560.         //$this->dbh->commit();
  561.  
  562.         return true;
  563.     }
  564.  
  565.     // }}}
  566.     // {{{ update()
  567.  
  568.     /**
  569.      * update the tree element given by $id with the values in $newValues
  570.      *
  571.      * @access     public
  572.      * @param      int     the id of the element to update
  573.      * @param      array   the new values, the index is the col name
  574.      * @return     mixed   either true or an Tree_Error
  575.      */
  576.     function update($id$newValues)
  577.     {
  578.         // just to be sure nothing gets screwed up :-)
  579.         unset($newValues[$this->_getColName('left')]);
  580.         unset($newValues[$this->_getColName('right')]);
  581.         unset($newValues[$this->_getColName('parentId')]);
  582.  
  583.         // updating _one_ element in the tree
  584.         $values = array();
  585.         foreach ($newValues as $key=>$value{
  586.  
  587.  
  588.             ///////////FIX ME: Add proper quote handling
  589.  
  590.  
  591.             $values[$this->_getColName($key).'='.$this->dbh->getTextValue($value);
  592.         }
  593.         $query sprintf('UPDATE %s SET %s WHERE%s %s=%s',
  594.                             $this->table,
  595.                             implode(',',$values),
  596.                             $this->_getWhereAddOn(),
  597.                             $this->_getColName('id'),
  598.                             $id);
  599.         if (MDB2::isError($res $this->dbh->exec($query))) {
  600.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  601.         }
  602.  
  603.         return true;
  604.     }
  605.  
  606.     // }}}
  607.     // {{{ update()
  608.  
  609.     /**
  610.      * copy a subtree/node/... under a new parent or/and behind a given element
  611.      *
  612.      *
  613.      * @access     public
  614.      * @param      integer the ID of the node that shall be copied
  615.      * @param      integer the new parent ID
  616.      * @param      integer the new previous ID, if given parent ID will be omitted
  617.      * @return     boolean true on success
  618.      */
  619.     function copy($id$parentId = 0$prevId = 0)
  620.     {
  621.         return Tree::raiseError('TREE_ERROR_NOT_IMPLANTED'
  622.                         'copy-method is not implemented yet!'
  623.                         );
  624.         // get element tree
  625.         // $this->addTree
  626.     }
  627.  
  628.     // }}}
  629.     // {{{ getRoot()
  630.  
  631.     /**
  632.      * get the root
  633.      *
  634.      * @access     public
  635.      * @version    2002/03/02
  636.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  637.      * @return     mixed   either the data of the root element or an Tree_Error
  638.      */
  639.     function getRoot()
  640.     {
  641.         $query sprintf('SELECT * FROM %s WHERE%s %s=1',
  642.                             $this->table,
  643.                             $this->_getWhereAddOn(),
  644.                             $this->_getColName('left'));
  645.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  646.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  647.         }
  648.         return !$res ? false : $this->_prepareResult($res);
  649.     }
  650.  
  651.     // }}}
  652.     // {{{ getElement()
  653.  
  654.     /**
  655.      *
  656.      *
  657.      * @access     public
  658.      * @version    2002/03/02
  659.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  660.      * @param      integer  the ID of the element to return
  661.      *
  662.      * @return  mixed    either the data of the requested element
  663.      *                       or an Tree_Error
  664.      */
  665.     function getElement($id)
  666.     {
  667.         $query sprintf('SELECT * FROM %s WHERE %s %s=%s',
  668.                             $this->table,
  669.                             $this->_getWhereAddOn(),
  670.                             $this->_getColName('id'),
  671.                             $id);
  672.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  673.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  674.         }
  675.         if (!$res{
  676.             return Tree::raiseError('TREE_ERROR_UNKOWN_ERROR'"Element with id $id does not exist!");
  677.         }
  678.         return $this->_prepareResult($res);
  679.     }
  680.  
  681.     // }}}
  682.     // {{{ getChild()
  683.  
  684.     /**
  685.      *
  686.      *
  687.      * @access     public
  688.      * @version    2002/03/02
  689.      * @param      integer  the ID of the element for which the children
  690.      *                       shall be returned
  691.      * @return     mixed   either the data of the requested element or an Tree_Error
  692.      */
  693.     function getChild($id)
  694.     {
  695.         // subqueries would be cool :-)
  696.         $curElement $this->getElement($id);
  697.         if (Tree::isError($curElement)) {
  698.             return $curElement;
  699.         }
  700.  
  701.         $query sprintf('SELECT * FROM %s WHERE%s %s=%s',
  702.                             $this->table,
  703.                             $this->_getWhereAddOn(),
  704.                             $this->_getColName('left'),
  705.                             $curElement['left']+1);
  706.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  707.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  708.         }
  709.         return $this->_prepareResult($res);
  710.     }
  711.  
  712.     // }}}
  713.     // {{{ getPath()
  714.  
  715.     /**
  716.      * gets the path from the element with the given id down
  717.      * to the root. The returned array is sorted to start at root
  718.      * for simply walking through and retreiving the path
  719.      *
  720.      * @access public
  721.      * @param integer the ID of the element for which the path shall be returned
  722.      * @return mixed  either the data of the requested elements
  723.      *                       or an Tree_Error
  724.      */
  725.     function getPath($id)
  726.     {
  727.         $res $this->dbh->queryAll($this->_getPathQuery($id));
  728.         if (MDB2::isError($res)) {
  729.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  730.         }
  731.         return $this->_prepareResults($res);
  732.     }
  733.  
  734.     // }}}
  735.     // {{{ _getPathQuery()
  736.  
  737.     function _getPathQuery($id)
  738.     {
  739.         // subqueries would be cool :-)
  740.         $curElement $this->getElement($id);
  741.         $query sprintf('SELECT * FROM %s '.
  742.                             'WHERE %s %s<=%s AND %s>=%s '.
  743.                             'ORDER BY %s',
  744.                             // set the FROM %s
  745.                             $this->table,
  746.                             // set the additional where add on
  747.                             $this->_getWhereAddOn(),
  748.                             // render 'left<=curLeft'
  749.                             $this->_getColName('left'),  $curElement['left'],
  750.                             // render right>=curRight'
  751.                             $this->_getColName('right')$curElement['right'],
  752.                             // set the order column
  753.                             $this->_getColName('left'));
  754.         return $query;
  755.     }
  756.  
  757.     // }}}
  758.     // {{{ getLevel()
  759.  
  760.     function getLevel($id)
  761.     {
  762.         $query $this->_getPathQuery($id);
  763.         // i know this is not really beautiful ...
  764.         $query preg_replace('/^select \* /i','SELECT COUNT(*) ',$query);
  765.         if (MDB2::isError($res $this->dbh->queryOne($query))) {
  766.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  767.         }
  768.         return $res-1;
  769.     }
  770.  
  771.     // }}}
  772.     // {{{ getLeft()
  773.  
  774.     /**
  775.      * gets the element to the left, the left visit
  776.      *
  777.      * @access     public
  778.      * @version    2002/03/07
  779.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  780.      * @param      integer  the ID of the element
  781.      * @return     mixed    either the data of the requested element
  782.      *                       or an Tree_Error
  783.      */
  784.     function getLeft($id)
  785.     {
  786.         $element $this->getElement($id);
  787.         if (Tree::isError($element)) {
  788.             return $element;
  789.         }
  790.  
  791.         $query sprintf('SELECT * FROM %s WHERE%s (%s=%s OR %s=%s)',
  792.                             $this->table,
  793.                             $this->_getWhereAddOn(),
  794.                             $this->_getColName('right')$element['left'- 1,
  795.                             $this->_getColName('left'),  $element['left'- 1);
  796.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  797.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  798.         }
  799.         return $this->_prepareResult($res);
  800.     }
  801.  
  802.     // }}}
  803.     // {{{ getRight()
  804.  
  805.     /**
  806.      * gets the element to the right, the right visit
  807.      *
  808.      * @access     public
  809.      * @version    2002/03/07
  810.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  811.      * @param      integer  the ID of the element
  812.      * @return     mixed    either the data of the requested element
  813.      *                       or an Tree_Error
  814.      */
  815.     function getRight($id)
  816.     {
  817.         $element $this->getElement($id);
  818.         if (Tree::isError($element))
  819.             return $element;
  820.  
  821.         $query sprintf('SELECT * FROM %s WHERE%s (%s=%s OR %s=%s)',
  822.                             $this->table,
  823.                             $this->_getWhereAddOn(),
  824.                             $this->_getColName('left'),  $element['right'+ 1,
  825.                             $this->_getColName('right')$element['right'+ 1);
  826.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  827.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  828.         }
  829.         return $this->_prepareResult($res);
  830.     }
  831.  
  832.     // }}}
  833.     // {{{ getParent()
  834.  
  835.     /**
  836.      * get the parent of the element with the given id
  837.      *
  838.      * @access     public
  839.      * @version    2002/04/15
  840.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  841.      * @param      integer the ID of the element
  842.      * @return     mixed    the array with the data of the parent element
  843.      *                       or false, if there is no parent, if the element is
  844.      *                       the root or an Tree_Error
  845.      */
  846.     function getParent($id)
  847.     {
  848.         $query sprintf('SELECT
  849.                                 p.*
  850.                             FROM
  851.                                 %s p,%s e
  852.                             WHERE
  853.                                 %s e.%s=p.%s
  854.                                 AND
  855.                                 e.%s=%s',
  856.                             $this->table,$this->table,
  857.                             $this->_getWhereAddOn(' AND ''p'),
  858.                             $this->_getColName('parentId'),
  859.                             $this->_getColName('id'),
  860.                             $this->_getColName('id'),
  861.                             $id);
  862.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  863.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  864.         }
  865.         return $this->_prepareResult($res);
  866.     }
  867.  
  868.     // }}}
  869.     // {{{ getChildren()
  870.  
  871.     /**
  872.      * get the children of the given element or if the parameter is an array.
  873.      * It gets the children of all the elements given by their ids
  874.      * in the array.
  875.      *
  876.      * @access     public
  877.      * @version    2002/04/15
  878.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  879.      * @param      mixed   (1) int     the id of one element
  880.      *                      (2) array   an array of ids for which
  881.      *                                  the children will be returned
  882.      * @param      integer the children of how many levels shall be returned
  883.      * @return     mixed   the array with the data of all children
  884.      *                      or false, if there are none
  885.      */
  886.     function getChildren($ids$levels = 1)
  887.     {
  888.         $res = array();
  889.         for ($i = 1; $i $levels + 1; $i++{
  890.             // if $ids is an array implode the values
  891.             $getIds is_array($idsimplode(','$ids$ids;
  892.  
  893.             $query sprintf('SELECT
  894.                                     c.*
  895.                                 FROM
  896.                                     %s c,%s e
  897.                                 WHERE
  898.                                     %s e.%s=c.%s
  899.                                     AND
  900.                                     e.%s IN (%s) '.
  901.                                 'ORDER BY
  902.                                     c.%s',
  903.                                 $this->table,$this->table,
  904.                                 $this->_getWhereAddOn(' AND ''c'),
  905.                                 $this->_getColName('id'),
  906.                                 $this->_getColName('parentId'),
  907.                                 $this->_getColName('id'),
  908.                                 $getIds,
  909.                                 // order by left, so we have it in the order
  910.                                 // as it is in the tree if no 'order'-option
  911.                                 // is given
  912.                                 $this->getOption('order')?
  913.                                     $this->getOption('order')
  914.                                     : $this->_getColName('left')
  915.                        );
  916.             if (MDB2::isError($_res $this->dbh->queryAll($query))) {
  917.                 return Tree::raiseError('TREE_ERROR_DB_ERROR'$_res->getMessage());
  918.             }
  919.  
  920.             // Column names are now unmapped
  921.             $_res $this->_prepareResults($_res);
  922.  
  923.             // we use the id as the index, to make the use easier esp.
  924.             // for multiple return-values
  925.             $tempRes = array();
  926.             foreach ($_res as $aRes{
  927.                 $tempRes[$aRes['id']] $aRes;
  928.             }
  929.             $_res $tempRes;
  930.  
  931.             if ($levels > 1{
  932.                 $ids = array();
  933.                 foreach ($_res as $aRes{
  934.                     $ids[$aRes[$this->_getColName('id')];
  935.                 }
  936.             }
  937.             $res array_merge($res$_res);
  938.  
  939.             // quit the for-loop if there are no children in the current level
  940.             if (!sizeof($ids)) {
  941.                 break;
  942.             }
  943.         }
  944.         return $res;
  945.     }
  946.  
  947.     // }}}
  948.     // {{{ getNext()
  949.  
  950.     /**
  951.      * get the next element on the same level
  952.      * if there is none return false
  953.      *
  954.      * @access     public
  955.      * @version    2002/04/15
  956.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  957.      * @param      integer the ID of the element
  958.      * @return     mixed   the array with the data of the next element
  959.      *                      or false, if there is no next
  960.      *                      or Tree_Error
  961.      */
  962.     function getNext($id)
  963.     {
  964.         $query sprintf('SELECT
  965.                                 n.*
  966.                             FROM
  967.                                 %s n,%s e
  968.                             WHERE
  969.                                 %s e.%s=n.%s-1
  970.                             AND
  971.                                 e.%s=n.%s
  972.                             AND
  973.                                 e.%s=%s',
  974.                             $this->table$this->table,
  975.                             $this->_getWhereAddOn(' AND ''n'),
  976.                             $this->_getColName('right'),
  977.                             $this->_getColName('left'),
  978.                             $this->_getColName('parentId'),
  979.                             $this->_getColName('parentId'),
  980.                             $this->_getColName('id'),
  981.                             $id);
  982.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  983.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  984.         }
  985.         return !$res ? false : $this->_prepareResult($res);
  986.     }
  987.  
  988.     // }}}
  989.     // {{{ getPrevious()
  990.  
  991.     /**
  992.      * get the previous element on the same level
  993.      * if there is none return false
  994.      *
  995.      * @access     public
  996.      * @version    2002/04/15
  997.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  998.      * @param      integer the ID of the element
  999.      * @return     mixed   the array with the data of the previous element
  1000.      *                      or false, if there is no previous
  1001.      *                      or a Tree_Error
  1002.      */
  1003.     function getPrevious($id)
  1004.     {
  1005.         $query sprintf('SELECT
  1006.                                 p.*
  1007.                             FROM
  1008.                                 %s p,%s e
  1009.                             WHERE
  1010.                                 %s e.%s=p.%s+1
  1011.                                 AND
  1012.                                     e.%s=p.%s
  1013.                                 AND
  1014.                                     e.%s=%s',
  1015.                             $this->table,$this->table,
  1016.                             $this->_getWhereAddOn(' AND ''p'),
  1017.                             $this->_getColName('left'),
  1018.                             $this->_getColName('right'),
  1019.                             $this->_getColName('parentId'),
  1020.                             $this->_getColName('parentId'),
  1021.                             $this->_getColName('id'),
  1022.                             $id);
  1023.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  1024.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  1025.         }
  1026.         return !$res ? false : $this->_prepareResult($res);
  1027.     }
  1028.  
  1029.     // }}}
  1030.     // {{{ isChildOf()
  1031.  
  1032.     /**
  1033.      * returns if $childId is a child of $id
  1034.      *
  1035.      * @abstract
  1036.      * @version    2002/04/29
  1037.      * @access     public
  1038.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1039.      * @param      int     id of the element
  1040.      * @param      int     id of the element to check if it is a child
  1041.      * @return     boolean true if it is a child
  1042.      */
  1043.     function isChildOf($id$childId)
  1044.     {
  1045.         // check simply if the left and right of the child are within the
  1046.         // left and right of the parent, if so it definitly is a child :-)
  1047.         $parent $this->getElement($id);
  1048.         $child  $this->getElement($childId);
  1049.  
  1050.         if ($parent['left'$child['left']
  1051.             && $parent['right'$child['right'])
  1052.         {
  1053.             return true;
  1054.         }
  1055.         return false;
  1056.     }
  1057.  
  1058.     // }}}
  1059.     // {{{ getDepth()
  1060.  
  1061.     /**
  1062.      * return the maximum depth of the tree
  1063.      *
  1064.      * @version    2003/02/25
  1065.      * @access     public
  1066.      * @author "Denis Joloudov" <dan@aitart.ru>, Wolfram Kriesing <wolfram@kriesing.de>
  1067.      * @return integer the depth of the tree
  1068.      */
  1069.     function getDepth()
  1070.     {
  1071.         // FIXXXME TODO!!!
  1072.         $query sprintf('SELECT COUNT(*) FROM %s p, %s e '.
  1073.                             'WHERE %s (e.%s BETWEEN p.%s AND p.%s) AND '.
  1074.                             '(e.%s BETWEEN p.%s AND p.%s)',
  1075.                             $this-> table,$this->table,
  1076.                             // first line in where
  1077.                             $this->_getWhereAddOn(' AND ','p'),
  1078.                             $this->_getColName('left'),$this->_getColName('left'),
  1079.                             $this->_getColName('right'),
  1080.                             // second where line
  1081.                             $this->_getColName('right'),$this->_getColName('left'),
  1082.                             $this->_getColName('right')
  1083.                             );
  1084.         if (MDB2::isError($res=$this->dbh->queryOne($query))) {
  1085.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  1086.         }
  1087.         if (!$res{
  1088.             return false;
  1089.         }
  1090.         return $this->_prepareResult($res);
  1091.     }
  1092.  
  1093.     // }}}
  1094.     // {{{ hasChildren()
  1095.  
  1096.     /**
  1097.      * Tells if the node with the given ID has children.
  1098.      *
  1099.      * @version    2003/03/04
  1100.      * @access     public
  1101.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1102.      * @param      integer the ID of a node
  1103.      * @return     boolean if the node with the given id has children
  1104.      */
  1105.     function hasChildren($id)
  1106.     {
  1107.         $element $this->getElement($id);
  1108.         // if the diff between left and right > 1 then there are children
  1109.         return ($element['right'$element['left']> 1;
  1110.     }
  1111.  
  1112.     // }}}
  1113.     // {{{ getIdByPath()
  1114.  
  1115.     /**
  1116.      * return the id of the element which is referenced by $path
  1117.      * this is useful for xml-structures, like: getIdByPath('/root/sub1/sub2')
  1118.      * this requires the structure to use each name uniquely
  1119.      * if this is not given it will return the first proper path found
  1120.      * i.e. there should only be one path /x/y/z
  1121.      * experimental: the name can be non unique if same names are in different levels
  1122.      *
  1123.      * @version    2003/05/11
  1124.      * @access     public
  1125.      * @author     Pierre-Alain Joye <paj@pearfr.org>
  1126.      * @param      string   $path       the path to search for
  1127.      * @param      integer  $startId    the id where to start the search
  1128.      * @param      string   $nodeName   the name of the key that contains
  1129.      *                                   the node name
  1130.      * @param      string   $seperator  the path seperator
  1131.      * @return     integer  the id of the searched element
  1132.      */
  1133.     function getIdByPath($path$startId = 0$nodeName 'name'$separator '/')
  1134.     // should this method be called getElementIdByPath ????
  1135.     // Yes, with an optional private paramater to get the whole node
  1136.     // in preference to only the id?
  1137.     {
  1138.         if ($separator == ''{
  1139.             return Tree::raiseError('TREE_ERROR_UNKOWN_ERROR'
  1140.                 'getIdByPath: Empty separator not allowed');
  1141.         }
  1142.         if ($path == $separator{
  1143.             $root $this->getRoot();
  1144.             if (Tree::isError($root)) {
  1145.                 return $root;
  1146.             }
  1147.             return $root['id'];
  1148.         }
  1149.         if (!($colname=$this->_getColName($nodeName))) {
  1150.             return Tree::raiseError('TREE_ERROR_UNKOWN_ERROR'
  1151.                 'getIdByPath: Invalid node name');
  1152.         }
  1153.         if ($startId != 0{
  1154.             // If the start node has no child, returns false
  1155.             // hasChildren calls getElement. Not very good right
  1156.             // now. See the TODO
  1157.             $startElem $this->getElement($startId);
  1158.             if (!is_array($startElem|| Tree::isError($startElem)) {
  1159.                 return $startElem;
  1160.             }
  1161.             // No child? return
  1162.             if (!is_array($startElem)) {
  1163.                 return null;
  1164.             }
  1165.             $rangeStart $startElem['left'];
  1166.             $rangeEnd   $startElem['right'];
  1167.             // Not clean, we should call hasChildren, but I do not
  1168.             // want to call getELement again :). See TODO
  1169.             $startHasChild ($rangeEnd-$rangeStart> 1 ? true : false;
  1170.             $cwd '/'.$this->getPathAsString($startId);
  1171.         else {
  1172.             $cwd '/';
  1173.             $startHasChild = false;
  1174.         }
  1175.         $t $this->_preparePath($path$cwd$separator);
  1176.         if (Tree::isError($t)) {
  1177.             return $t;
  1178.         }
  1179.         list($elems$sublevels$t;
  1180.         $cntElems sizeof($elems);
  1181.         $where '';
  1182.  
  1183.         $query 'SELECT '
  1184.                 .$this->_getColName('id')
  1185.                 .' FROM '
  1186.                 .$this->table
  1187.                 .' WHERE '
  1188.                 .$colname;
  1189.         if ($cntElems == 1{
  1190.             $query .= "='".$elems[0]."'";
  1191.         else {
  1192.             $query .= "='".$elems[$cntElems-1]."'";
  1193.         }
  1194.         if ($startHasChild{
  1195.             $where  .= ' AND ('.
  1196.                         $this->_getColName('left').'>'.$rangeStart.
  1197.                         ' AND '.
  1198.                         $this->_getColName('right').'<'.$rangeEnd.')';
  1199.         }
  1200.         $res $this->dbh->queryOne($query);
  1201.         if (MDB2::isError($res)) {
  1202.             return Tree::raiseError('TREE_ERROR_DB_ERROR'$res->getMessage());
  1203.         }
  1204.         return ($res ? (int)$res : false);
  1205.     }
  1206.  
  1207.     // }}}
  1208.  
  1209.     //
  1210.     //  PRIVATE METHODS
  1211.     //
  1212.  
  1213.     // {{{ _getWhereAddOn()
  1214.     /**
  1215.      *
  1216.      *
  1217.      * @access     private
  1218.      * @version    2002/04/20
  1219.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1220.      * @param      string  the current where clause
  1221.      * @return     string  the where clause we want to add to a query
  1222.      */
  1223.     function _getWhereAddOn($addAfter ' AND '$tableName '')
  1224.     {
  1225.         if ($where=$this->getOption('whereAddOn')) {
  1226.             return ' '.($tableName $tableName.'.' '')." $where$addAfter ";
  1227.         }
  1228.         return '';
  1229.     }
  1230.  
  1231.     // }}}
  1232.     // {{{ getFirstRoot()
  1233.  
  1234.     // for compatibility to Memory methods
  1235.     function getFirstRoot()
  1236.     {
  1237.         return $this->getRoot();
  1238.     }
  1239.  
  1240.     // }}}
  1241.     // {{{ getNode()
  1242.  
  1243.     /**
  1244.      * gets the tree under the given element in one array, sorted
  1245.      * so you can go through the elements from begin to end and list them
  1246.      * as they are in the tree, where every child (until the deepest) is retreived
  1247.      *
  1248.      * @see        &_getNode()
  1249.      * @access     public
  1250.      * @version    2001/12/17
  1251.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1252.      * @param      integer  $startId    the id where to start walking
  1253.      * @param      integer  $depth      this number says how deep into
  1254.      *                                   the structure the elements shall
  1255.      *                                   be retreived
  1256.      * @return     array    sorted as listed in the tree
  1257.      */
  1258.     function &getNode($startId = 0$depth = 0)
  1259.     {
  1260. //FIXXXME use getChildren()
  1261.         if ($startId{
  1262.             $startNode $this->getElement($startId);
  1263.             if (Tree::isError($startNode)) {
  1264.                 return $startNode;
  1265.             }
  1266.  
  1267.         else {
  1268.         }
  1269.     }
  1270. }
  1271.  
  1272. /*
  1273. * Local Variables:
  1274. * mode: php
  1275. * tab-width: 4
  1276. * c-basic-offset: 4
  1277. * End:
  1278. */
  1279. ?>

Documentation generated on Mon, 11 Mar 2019 15:02:14 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.