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

Source for file MDBnested.php

Documentation is available at MDBnested.php

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 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: MDBnested.php 320703 2011-12-08 22:08:40Z danielc $
  20.  
  21. require_once 'Tree/OptionsMDB.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_MDBnested($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_MDBnested($dsn$options = array())
  98.     {
  99.         parent::Tree_OptionsMDB($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 (MDB::isError($res $this->dbh->query($query))) {
  209.             // rollback
  210.             return $this->_throwError($res->getMessage()__LINE__);
  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 (MDB::isError($res $this->dbh->query($query))) {
  249.             // FIXXME rollback
  250.             return $this->_throwError($res->getMessage()__LINE__);
  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 (MDB::isError($res $this->dbh->query($query))) {
  261.             // FIXXME rollback
  262.             return $this->_throwError($res->getMessage()__LINE__);
  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 (MDB::isError($res $this->dbh->query($query))) {
  294.             // FIXXME rollback
  295.             //$this->dbh->rollback();
  296.             return $this->_throwError($res->getMessage()__LINE__);
  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 (MDB::isError($res $this->dbh->query($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 $this->_throwError($res->getMessage()__LINE__);
  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 (MDB::isError($res $this->dbh->query($query))) {
  357.             // the rollback shall be done by the method calling this one
  358.             // since it is only private
  359.             return $this->_throwError($res->getMessage()__LINE__);
  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 $this->_throwError(serialize($errors)__LINE__);
  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 $this->_throwError('no parent id given'__LINE__);
  451.             }
  452.             // if the element shall be moved under one of its children
  453.             // return false
  454.             if ($this->isChildOf($idToMove,$newParentId)) {
  455.                 return $this->_throwError(
  456.                             'can not move an element under one of its children' ,
  457.                             __LINE__
  458.                         );
  459.             }
  460.             // dont do anything to let an element be moved under itself
  461.             // which is bullshit
  462.             if ($newParentId==$idToMove{
  463.                 return true;
  464.             }
  465.             // try to retreive the data of the parent element
  466.             if (Tree::isError($newParent=$this->getElement($newParentId))) {
  467.                 return $newParent;
  468.             }
  469.         }
  470.         // get the data of the element itself
  471.         if (Tree::isError($element=$this->getElement($idToMove))) {
  472.             return $element;
  473.         }
  474.  
  475.         $numberOfElements ($element['right'$element['left'+ 1/ 2;
  476.         $prevVisited $newPrevId $newPrevious['right'$newParent['left'];
  477.  
  478.         // FIXXME start transaction
  479.  
  480.         // add the left/right values in the new parent, to have the space
  481.         // to move the new values in
  482.         $err=$this->_add($prevVisited$numberOfElements);
  483.         if (Tree::isError($err)) {
  484.             // FIXXME rollback
  485.             //$this->dbh->rollback();
  486.             return $err;
  487.         }
  488.  
  489.         // update the parentId of the element with $idToMove
  490.         $err $this->update($idToMovearray('parentId' => $newParentId));
  491.         if (Tree::isError($err)) {
  492.             // FIXXME rollback
  493.             //$this->dbh->rollback();
  494.             return $err;
  495.         }
  496.  
  497.         // update the lefts and rights of those elements that shall be moved
  498.  
  499.         // first get the offset we need to add to the left/right values
  500.         // if $newPrevId is given we need to get the right value,
  501.         // otherwise the left since the left/right has changed
  502.         // because we already updated it up there. We need to get them again.
  503.         // We have to do that anyway, to have the proper new left/right values
  504.         if ($newPrevId{
  505.             if (Tree::isError($temp $this->getElement($newPrevId))) {
  506.                 // FIXXME rollback
  507.                 //$this->dbh->rollback();
  508.                 return $temp;
  509.             }
  510.             $calcWith $temp['right'];
  511.         else {
  512.             if (Tree::isError($temp $this->getElement($newParentId))) {
  513.                 // FIXXME rollback
  514.                 //$this->dbh->rollback();
  515.                 return $temp;
  516.             }
  517.             $calcWith $temp['left'];
  518.         }
  519.  
  520.         // get the element that shall be moved again, since the left and
  521.         // right might have changed by the add-call
  522.         if (Tree::isError($element=$this->getElement($idToMove))) {
  523.             return $element;
  524.         }
  525.         // calc the offset that the element to move has
  526.         // to the spot where it should go
  527.         $offset $calcWith $element['left'];
  528.         // correct the offset by one, since it needs to go inbetween!
  529.         $offset++;
  530.  
  531.         $lName $this->_getColName('left');
  532.         $rName $this->_getColName('right');
  533.         $query sprintf("UPDATE
  534.                                 %s
  535.                             SET
  536.                                 %s=%s+$offset,
  537.                                 %s=%s+$offset
  538.                             WHERE
  539.                                 %s %s>%s
  540.                                 AND
  541.                                 %s < %s",
  542.                             $this->table,
  543.                             $rName$rName,
  544.                             $lName$lName,
  545.                             $this->_getWhereAddOn(),
  546.                             $lName$element['left']-1,
  547.                             $rName$element['right']+1);
  548.         if (MDB::isError($res=$this->dbh->query($query))) {
  549.             // FIXXME rollback
  550.             //$this->dbh->rollback();
  551.             return $this->_throwError($res->getMessage()__LINE__);
  552.         }
  553.  
  554.         // remove the part of the tree where the element(s) was/were before
  555.         if (Tree::isError($err=$this->_remove($element))) {
  556.             // FIXXME rollback
  557.             //$this->dbh->rollback();
  558.             return $err;
  559.         }
  560.         // FIXXME commit all changes
  561.         //$this->dbh->commit();
  562.  
  563.         return true;
  564.     }
  565.  
  566.     // }}}
  567.     // {{{ update()
  568.  
  569.     /**
  570.      * update the tree element given by $id with the values in $newValues
  571.      *
  572.      * @access     public
  573.      * @param      int     the id of the element to update
  574.      * @param      array   the new values, the index is the col name
  575.      * @return     mixed   either true or an Tree_Error
  576.      */
  577.     function update($id$newValues)
  578.     {
  579.         // just to be sure nothing gets screwed up :-)
  580.         unset($newValues[$this->_getColName('left')]);
  581.         unset($newValues[$this->_getColName('right')]);
  582.         unset($newValues[$this->_getColName('parentId')]);
  583.  
  584.         // updating _one_ element in the tree
  585.         $values = array();
  586.         foreach ($newValues as $key=>$value{
  587.  
  588.  
  589.             ///////////FIX ME: Add proper quote handling
  590.  
  591.  
  592.             $values[$this->_getColName($key).'='.$this->dbh->getTextValue($value);
  593.         }
  594.         $query sprintf('UPDATE %s SET %s WHERE%s %s=%s',
  595.                             $this->table,
  596.                             implode(',',$values),
  597.                             $this->_getWhereAddOn(),
  598.                             $this->_getColName('id'),
  599.                             $id);
  600.         if (MDB::isError($res=$this->dbh->query($query))) {
  601.             return $this->_throwError($res->getMessage()__LINE__);
  602.         }
  603.  
  604.         return true;
  605.     }
  606.  
  607.     // }}}
  608.     // {{{ update()
  609.  
  610.     /**
  611.      * copy a subtree/node/... under a new parent or/and behind a given element
  612.      *
  613.      *
  614.      * @access     public
  615.      * @param      integer the ID of the node that shall be copied
  616.      * @param      integer the new parent ID
  617.      * @param      integer the new previous ID, if given parent ID will be omitted
  618.      * @return     boolean true on success
  619.      */
  620.     function copy($id$parentId = 0$prevId = 0)
  621.     {
  622.         return $this->_throwError(
  623.                         'copy-method is not implemented yet!' ,
  624.                         __LINE__
  625.                         );
  626.         // get element tree
  627.         // $this->addTree
  628.     }
  629.  
  630.     // }}}
  631.     // {{{ getRoot()
  632.  
  633.     /**
  634.      * get the root
  635.      *
  636.      * @access     public
  637.      * @version    2002/03/02
  638.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  639.      * @return     mixed   either the data of the root element or an Tree_Error
  640.      */
  641.     function getRoot()
  642.     {
  643.         $query sprintf('SELECT * FROM %s WHERE%s %s=1',
  644.                             $this->table,
  645.                             $this->_getWhereAddOn(),
  646.                             $this->_getColName('left'));
  647.         if (MDB::isError($res $this->dbh->getRow($query))) {
  648.             return $this->_throwError($res->getMessage()__LINE__);
  649.         }
  650.         return !$res ? false : $this->_prepareResult($res);
  651.     }
  652.  
  653.     // }}}
  654.     // {{{ getElement()
  655.  
  656.     /**
  657.      *
  658.      *
  659.      * @access     public
  660.      * @version    2002/03/02
  661.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  662.      * @param      integer  the ID of the element to return
  663.      *
  664.      * @return  mixed    either the data of the requested element
  665.      *                       or an Tree_Error
  666.      */
  667.     function getElement($id)
  668.     {
  669.         $query sprintf('SELECT * FROM %s WHERE %s %s=%s',
  670.                             $this->table,
  671.                             $this->_getWhereAddOn(),
  672.                             $this->_getColName('id'),
  673.                             $id);
  674.         if (MDB::isError($res $this->dbh->getRow($query))) {
  675.             return $this->_throwError($res->getMessage()__LINE__);
  676.         }
  677.         if (!$res{
  678.             return $this->_throwError("Element with id $id does not exist!,
  679.                                         __LINE__);
  680.         }
  681.         return $this->_prepareResult($res);
  682.     }
  683.  
  684.     // }}}
  685.     // {{{ getChild()
  686.  
  687.     /**
  688.      *
  689.      *
  690.      * @access     public
  691.      * @version    2002/03/02
  692.      * @param      integer  the ID of the element for which the children
  693.      *                       shall be returned
  694.      * @return     mixed   either the data of the requested element or an Tree_Error
  695.      */
  696.     function getChild($id)
  697.     {
  698.         // subqueries would be cool :-)
  699.         $curElement $this->getElement($id);
  700.         if (Tree::isError($curElement)) {
  701.             return $curElement;
  702.         }
  703.  
  704.         $query sprintf('SELECT * FROM %s WHERE%s %s=%s',
  705.                             $this->table,
  706.                             $this->_getWhereAddOn(),
  707.                             $this->_getColName('left'),
  708.                             $curElement['left']+1);
  709.         if (MDB::isError($res $this->dbh->getRow($query))) {
  710.             return $this->_throwError($res->getMessage()__LINE__);
  711.         }
  712.         return $this->_prepareResult($res);
  713.     }
  714.  
  715.     // }}}
  716.     // {{{ getPath()
  717.  
  718.     /**
  719.      * gets the path from the element with the given id down
  720.      * to the root. The returned array is sorted to start at root
  721.      * for simply walking through and retreiving the path
  722.      *
  723.      * @access public
  724.      * @param integer the ID of the element for which the path shall be returned
  725.      * @return mixed  either the data of the requested elements
  726.      *                       or an Tree_Error
  727.      */
  728.     function getPath($id)
  729.     {
  730.         $res $this->dbh->getAll($this->_getPathQuery($id));
  731.         if (MDB::isError($res)) {
  732.             return $this->_throwError($res->getMessage()__LINE__);
  733.         }
  734.         return $this->_prepareResults($res);
  735.     }
  736.  
  737.     // }}}
  738.     // {{{ _getPathQuery()
  739.  
  740.     function _getPathQuery($id)
  741.     {
  742.         // subqueries would be cool :-)
  743.         $curElement $this->getElement($id);
  744.         $query sprintf('SELECT * FROM %s '.
  745.                             'WHERE %s %s<=%s AND %s>=%s '.
  746.                             'ORDER BY %s',
  747.                             // set the FROM %s
  748.                             $this->table,
  749.                             // set the additional where add on
  750.                             $this->_getWhereAddOn(),
  751.                             // render 'left<=curLeft'
  752.                             $this->_getColName('left'),  $curElement['left'],
  753.                             // render right>=curRight'
  754.                             $this->_getColName('right')$curElement['right'],
  755.                             // set the order column
  756.                             $this->_getColName('left'));
  757.         return $query;
  758.     }
  759.  
  760.     // }}}
  761.     // {{{ getLevel()
  762.  
  763.     function getLevel($id)
  764.     {
  765.         $query $this->_getPathQuery($id);
  766.         // i know this is not really beautiful ...
  767.         $query preg_replace('/^select \* /i','SELECT COUNT(*) ',$query);
  768.         if (MDB::isError($res $this->dbh->getOne($query))) {
  769.             return $this->_throwError($res->getMessage()__LINE__);
  770.         }
  771.         return $res-1;
  772.     }
  773.  
  774.     // }}}
  775.     // {{{ getLeft()
  776.  
  777.     /**
  778.      * gets the element to the left, the left visit
  779.      *
  780.      * @access     public
  781.      * @version    2002/03/07
  782.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  783.      * @param      integer  the ID of the element
  784.      * @return     mixed    either the data of the requested element
  785.      *                       or an Tree_Error
  786.      */
  787.     function getLeft($id)
  788.     {
  789.         $element $this->getElement($id);
  790.         if (Tree::isError($element)) {
  791.             return $element;
  792.         }
  793.  
  794.         $query sprintf('SELECT * FROM %s WHERE%s (%s=%s OR %s=%s)',
  795.                             $this->table,
  796.                             $this->_getWhereAddOn(),
  797.                             $this->_getColName('right')$element['left'- 1,
  798.                             $this->_getColName('left'),  $element['left'- 1);
  799.         if (MDB::isError($res $this->dbh->getRow($query))) {
  800.             return $this->_throwError($res->getMessage()__LINE__);
  801.         }
  802.         return $this->_prepareResult($res);
  803.     }
  804.  
  805.     // }}}
  806.     // {{{ getRight()
  807.  
  808.     /**
  809.      * gets the element to the right, the right visit
  810.      *
  811.      * @access     public
  812.      * @version    2002/03/07
  813.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  814.      * @param      integer  the ID of the element
  815.      * @return     mixed    either the data of the requested element
  816.      *                       or an Tree_Error
  817.      */
  818.     function getRight($id)
  819.     {
  820.         $element $this->getElement($id);
  821.         if (Tree::isError($element))
  822.             return $element;
  823.  
  824.         $query sprintf('SELECT * FROM %s WHERE%s (%s=%s OR %s=%s)',
  825.                             $this->table,
  826.                             $this->_getWhereAddOn(),
  827.                             $this->_getColName('left'),  $element['right'+ 1,
  828.                             $this->_getColName('right')$element['right'+ 1);
  829.         if (MDB::isError($res $this->dbh->getRow($query))) {
  830.             return $this->_throwError($res->getMessage()__LINE__);
  831.         }
  832.         return $this->_prepareResult($res);
  833.     }
  834.  
  835.     // }}}
  836.     // {{{ getParent()
  837.  
  838.     /**
  839.      * get the parent of the element with the given id
  840.      *
  841.      * @access     public
  842.      * @version    2002/04/15
  843.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  844.      * @param      integer the ID of the element
  845.      * @return     mixed    the array with the data of the parent element
  846.      *                       or false, if there is no parent, if the element is
  847.      *                       the root or an Tree_Error
  848.      */
  849.     function getParent($id)
  850.     {
  851.         $query sprintf('SELECT
  852.                                 p.*
  853.                             FROM
  854.                                 %s p,%s e
  855.                             WHERE
  856.                                 %s e.%s=p.%s
  857.                                 AND
  858.                                 e.%s=%s',
  859.                             $this->table,$this->table,
  860.                             $this->_getWhereAddOn(' AND ''p'),
  861.                             $this->_getColName('parentId'),
  862.                             $this->_getColName('id'),
  863.                             $this->_getColName('id'),
  864.                             $id);
  865.         if (MDB::isError($res $this->dbh->getRow($query))) {
  866.             return $this->_throwError($res->getMessage()__LINE__);
  867.         }
  868.         return $this->_prepareResult($res);
  869.     }
  870.  
  871.     // }}}
  872.     // {{{ getChildren()
  873.  
  874.     /**
  875.      * get the children of the given element or if the parameter is an array.
  876.      * It gets the children of all the elements given by their ids
  877.      * in the array.
  878.      *
  879.      * @access     public
  880.      * @version    2002/04/15
  881.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  882.      * @param      mixed   (1) int     the id of one element
  883.      *                      (2) array   an array of ids for which
  884.      *                                  the children will be returned
  885.      * @param      integer the children of how many levels shall be returned
  886.      * @return     mixed   the array with the data of all children
  887.      *                      or false, if there are none
  888.      */
  889.     function getChildren($ids$levels = 1)
  890.     {
  891.         $res = array();
  892.         for ($i = 1; $i $levels + 1; $i++{
  893.             // if $ids is an array implode the values
  894.             $getIds is_array($idsimplode(','$ids$ids;
  895.  
  896.             $query sprintf('SELECT
  897.                                     c.*
  898.                                 FROM
  899.                                     %s c,%s e
  900.                                 WHERE
  901.                                     %s e.%s=c.%s
  902.                                     AND
  903.                                     e.%s IN (%s) '.
  904.                                 'ORDER BY
  905.                                     c.%s',
  906.                                 $this->table,$this->table,
  907.                                 $this->_getWhereAddOn(' AND ''c'),
  908.                                 $this->_getColName('id'),
  909.                                 $this->_getColName('parentId'),
  910.                                 $this->_getColName('id'),
  911.                                 $getIds,
  912.                                 // order by left, so we have it in the order
  913.                                 // as it is in the tree if no 'order'-option
  914.                                 // is given
  915.                                 $this->getOption('order')?
  916.                                     $this->getOption('order')
  917.                                     : $this->_getColName('left')
  918.                        );
  919.             if (MDB::isError($_res $this->dbh->getAll($query))) {
  920.                 return $this->_throwError($_res->getMessage()__LINE__);
  921.             }
  922.  
  923.             // Column names are now unmapped
  924.             $_res $this->_prepareResults($_res);
  925.  
  926.             // we use the id as the index, to make the use easier esp.
  927.             // for multiple return-values
  928.             $tempRes = array();
  929.             foreach ($_res as $aRes{
  930.                 $tempRes[$aRes['id']] $aRes;
  931.             }
  932.             $_res $tempRes;
  933.  
  934.             if ($levels > 1{
  935.                 $ids = array();
  936.                 foreach ($_res as $aRes{
  937.                     $ids[$aRes[$this->_getColName('id')];
  938.                 }
  939.             }
  940.             $res array_merge($res$_res);
  941.  
  942.             // quit the for-loop if there are no children in the current level
  943.             if (!sizeof($ids)) {
  944.                 break;
  945.             }
  946.         }
  947.         return $res;
  948.     }
  949.  
  950.     // }}}
  951.     // {{{ getNext()
  952.  
  953.     /**
  954.      * get the next element on the same level
  955.      * if there is none return false
  956.      *
  957.      * @access     public
  958.      * @version    2002/04/15
  959.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  960.      * @param      integer the ID of the element
  961.      * @return     mixed   the array with the data of the next element
  962.      *                      or false, if there is no next
  963.      *                      or Tree_Error
  964.      */
  965.     function getNext($id)
  966.     {
  967.         $query sprintf('SELECT
  968.                                 n.*
  969.                             FROM
  970.                                 %s n,%s e
  971.                             WHERE
  972.                                 %s e.%s=n.%s-1
  973.                             AND
  974.                                 e.%s=n.%s
  975.                             AND
  976.                                 e.%s=%s',
  977.                             $this->table$this->table,
  978.                             $this->_getWhereAddOn(' AND ''n'),
  979.                             $this->_getColName('right'),
  980.                             $this->_getColName('left'),
  981.                             $this->_getColName('parentId'),
  982.                             $this->_getColName('parentId'),
  983.                             $this->_getColName('id'),
  984.                             $id);
  985.         if (MDB::isError($res $this->dbh->getRow($query))) {
  986.             return $this->_throwError($res->getMessage()__LINE__);
  987.         }
  988.         return !$res ? false : $this->_prepareResult($res);
  989.     }
  990.  
  991.     // }}}
  992.     // {{{ getPrevious()
  993.  
  994.     /**
  995.      * get the previous element on the same level
  996.      * if there is none return false
  997.      *
  998.      * @access     public
  999.      * @version    2002/04/15
  1000.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1001.      * @param      integer the ID of the element
  1002.      * @return     mixed   the array with the data of the previous element
  1003.      *                      or false, if there is no previous
  1004.      *                      or a Tree_Error
  1005.      */
  1006.     function getPrevious($id)
  1007.     {
  1008.         $query sprintf('SELECT
  1009.                                 p.*
  1010.                             FROM
  1011.                                 %s p,%s e
  1012.                             WHERE
  1013.                                 %s e.%s=p.%s+1
  1014.                                 AND
  1015.                                     e.%s=p.%s
  1016.                                 AND
  1017.                                     e.%s=%s',
  1018.                             $this->table,$this->table,
  1019.                             $this->_getWhereAddOn(' AND ''p'),
  1020.                             $this->_getColName('left'),
  1021.                             $this->_getColName('right'),
  1022.                             $this->_getColName('parentId'),
  1023.                             $this->_getColName('parentId'),
  1024.                             $this->_getColName('id'),
  1025.                             $id);
  1026.         if (MDB::isError($res $this->dbh->getRow($query))) {
  1027.             return $this->_throwError($res->getMessage()__LINE__);
  1028.         }
  1029.         return !$res ? false : $this->_prepareResult($res);
  1030.     }
  1031.  
  1032.     // }}}
  1033.     // {{{ isChildOf()
  1034.  
  1035.     /**
  1036.      * returns if $childId is a child of $id
  1037.      *
  1038.      * @abstract
  1039.      * @version    2002/04/29
  1040.      * @access     public
  1041.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1042.      * @param      int     id of the element
  1043.      * @param      int     id of the element to check if it is a child
  1044.      * @return     boolean true if it is a child
  1045.      */
  1046.     function isChildOf($id$childId)
  1047.     {
  1048.         // check simply if the left and right of the child are within the
  1049.         // left and right of the parent, if so it definitly is a child :-)
  1050.         $parent $this->getElement($id);
  1051.         $child  $this->getElement($childId);
  1052.  
  1053.         if ($parent['left'$child['left']
  1054.             && $parent['right'$child['right'])
  1055.         {
  1056.             return true;
  1057.         }
  1058.         return false;
  1059.     }
  1060.  
  1061.     // }}}
  1062.     // {{{ getDepth()
  1063.  
  1064.     /**
  1065.      * return the maximum depth of the tree
  1066.      *
  1067.      * @version    2003/02/25
  1068.      * @access     public
  1069.      * @author "Denis Joloudov" <dan@aitart.ru>, Wolfram Kriesing <wolfram@kriesing.de>
  1070.      * @return integer the depth of the tree
  1071.      */
  1072.     function getDepth()
  1073.     {
  1074.         // FIXXXME TODO!!!
  1075.         $query sprintf('SELECT COUNT(*) FROM %s p, %s e '.
  1076.                             'WHERE %s (e.%s BETWEEN p.%s AND p.%s) AND '.
  1077.                             '(e.%s BETWEEN p.%s AND p.%s)',
  1078.                             $this-> table,$this->table,
  1079.                             // first line in where
  1080.                             $this->_getWhereAddOn(' AND ','p'),
  1081.                             $this->_getColName('left'),$this->_getColName('left'),
  1082.                             $this->_getColName('right'),
  1083.                             // second where line
  1084.                             $this->_getColName('right'),$this->_getColName('left'),
  1085.                             $this->_getColName('right')
  1086.                             );
  1087.         if (MDB::isError($res=$this->dbh->getOne($query))) {
  1088.             return $this->_throwError($res->getMessage()__LINE__);
  1089.         }
  1090.         if (!$res{
  1091.             return false;
  1092.         }
  1093.         return $this->_prepareResult($res);
  1094.     }
  1095.  
  1096.     // }}}
  1097.     // {{{ hasChildren()
  1098.  
  1099.     /**
  1100.      * Tells if the node with the given ID has children.
  1101.      *
  1102.      * @version    2003/03/04
  1103.      * @access     public
  1104.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1105.      * @param      integer the ID of a node
  1106.      * @return     boolean if the node with the given id has children
  1107.      */
  1108.     function hasChildren($id)
  1109.     {
  1110.         $element $this->getElement($id);
  1111.         // if the diff between left and right > 1 then there are children
  1112.         return ($element['right'$element['left']> 1;
  1113.     }
  1114.  
  1115.     // }}}
  1116.     // {{{ getIdByPath()
  1117.  
  1118.     /**
  1119.      * return the id of the element which is referenced by $path
  1120.      * this is useful for xml-structures, like: getIdByPath('/root/sub1/sub2')
  1121.      * this requires the structure to use each name uniquely
  1122.      * if this is not given it will return the first proper path found
  1123.      * i.e. there should only be one path /x/y/z
  1124.      * experimental: the name can be non unique if same names are in different levels
  1125.      *
  1126.      * @version    2003/05/11
  1127.      * @access     public
  1128.      * @author     Pierre-Alain Joye <paj@pearfr.org>
  1129.      * @param      string   $path       the path to search for
  1130.      * @param      integer  $startId    the id where to start the search
  1131.      * @param      string   $nodeName   the name of the key that contains
  1132.      *                                   the node name
  1133.      * @param      string   $seperator  the path seperator
  1134.      * @return     integer  the id of the searched element
  1135.      */
  1136.     function getIdByPath($path$startId = 0$nodeName 'name'$separator '/')
  1137.     // should this method be called getElementIdByPath ????
  1138.     // Yes, with an optional private paramater to get the whole node
  1139.     // in preference to only the id?
  1140.     {
  1141.         if ($separator == ''{
  1142.             return $this->_throwError(
  1143.                 'getIdByPath: Empty separator not allowed'__LINE__);
  1144.         }
  1145.         if ($path == $separator{
  1146.             $root $this->getRoot();
  1147.             if (Tree::isError($root)) {
  1148.                 return $root;
  1149.             }
  1150.             return $root['id'];
  1151.         }
  1152.         if (!($colname=$this->_getColName($nodeName))) {
  1153.             return $this->_throwError(
  1154.                 'getIdByPath: Invalid node name'__LINE__);
  1155.         }
  1156.         if ($startId != 0{
  1157.             // If the start node has no child, returns false
  1158.             // hasChildren calls getElement. Not very good right
  1159.             // now. See the TODO
  1160.             $startElem $this->getElement($startId);
  1161.             if (!is_array($startElem|| Tree::isError($startElem)) {
  1162.                 return $startElem;
  1163.             }
  1164.             // No child? return
  1165.             if (!is_array($startElem)) {
  1166.                 return null;
  1167.             }
  1168.             $rangeStart $startElem['left'];
  1169.             $rangeEnd   $startElem['right'];
  1170.             // Not clean, we should call hasChildren, but I do not
  1171.             // want to call getELement again :). See TODO
  1172.             $startHasChild ($rangeEnd-$rangeStart> 1 ? true : false;
  1173.             $cwd '/'.$this->getPathAsString($startId);
  1174.         else {
  1175.             $cwd '/';
  1176.             $startHasChild = false;
  1177.         }
  1178.         $t $this->_preparePath($path$cwd$separator);
  1179.         if (Tree::isError($t)) {
  1180.             return $t;
  1181.         }
  1182.         list($elems$sublevels$t;
  1183.         $cntElems sizeof($elems);
  1184.         $where '';
  1185.  
  1186.         $query 'SELECT '
  1187.                 .$this->_getColName('id')
  1188.                 .' FROM '
  1189.                 .$this->table
  1190.                 .' WHERE '
  1191.                 .$colname;
  1192.         if ($cntElems == 1{
  1193.             $query .= "='".$elems[0]."'";
  1194.         else {
  1195.             $query .= "='".$elems[$cntElems-1]."'";
  1196.         }
  1197.         if ($startHasChild{
  1198.             $where  .= ' AND ('.
  1199.                         $this->_getColName('left').'>'.$rangeStart.
  1200.                         ' AND '.
  1201.                         $this->_getColName('right').'<'.$rangeEnd.')';
  1202.         }
  1203.         $res $this->dbh->getOne($query);
  1204.         if (MDB::isError($res)) {
  1205.             return $this->_throwError($res->getMessage(),
  1206.                         __LINE__);
  1207.         }
  1208.         return ($res ? (int)$res : false);
  1209.     }
  1210.  
  1211.     // }}}
  1212.  
  1213.     //
  1214.     //  PRIVATE METHODS
  1215.     //
  1216.  
  1217.     // {{{ _getWhereAddOn()
  1218.     /**
  1219.      *
  1220.      *
  1221.      * @access     private
  1222.      * @version    2002/04/20
  1223.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1224.      * @param      string  the current where clause
  1225.      * @return     string  the where clause we want to add to a query
  1226.      */
  1227.     function _getWhereAddOn($addAfter ' AND '$tableName '')
  1228.     {
  1229.         if ($where=$this->getOption('whereAddOn')) {
  1230.             return ' '.($tableName $tableName.'.' '')." $where$addAfter ";
  1231.         }
  1232.         return '';
  1233.     }
  1234.  
  1235.     // }}}
  1236.     // {{{ getFirstRoot()
  1237.  
  1238.     // for compatibility to Memory methods
  1239.     function getFirstRoot()
  1240.     {
  1241.         return $this->getRoot();
  1242.     }
  1243.  
  1244.     // }}}
  1245.     // {{{ getNode()
  1246.  
  1247.     /**
  1248.      * gets the tree under the given element in one array, sorted
  1249.      * so you can go through the elements from begin to end and list them
  1250.      * as they are in the tree, where every child (until the deepest) is retreived
  1251.      *
  1252.      * @see        &_getNode()
  1253.      * @access     public
  1254.      * @version    2001/12/17
  1255.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1256.      * @param      integer  $startId    the id where to start walking
  1257.      * @param      integer  $depth      this number says how deep into
  1258.      *                                   the structure the elements shall
  1259.      *                                   be retreived
  1260.      * @return     array    sorted as listed in the tree
  1261.      */
  1262.     function &getNode($startId = 0$depth = 0)
  1263.     {
  1264. //FIXXXME use getChildren()
  1265.         if ($startId{
  1266.             $startNode $this->getElement($startId);
  1267.             if (Tree::isError($startNode)) {
  1268.                 return $startNode;
  1269.             }
  1270.  
  1271.         else {
  1272.         }
  1273.     }
  1274. }
  1275.  
  1276. /*
  1277. * Local Variables:
  1278. * mode: php
  1279. * tab-width: 4
  1280. * c-basic-offset: 4
  1281. * End:
  1282. */

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