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

Source for file DBnested.php

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

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