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

Source for file MDB2nested.php

Documentation is available at MDB2nested.php

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors:                                                             |
  17. // +----------------------------------------------------------------------+
  18. //
  19. //  $Id: MDB2nested.php 320703 2011-12-08 22:08:40Z danielc $
  20.  
  21. require_once 'Tree/OptionsMDB2.php';
  22.  
  23. /**
  24. * This class implements methods to work on a tree saved using the nested
  25. * tree model.
  26. * explaination: http://research.calacademy.org/taf/proceedings/ballew/index.htm
  27. *
  28. @access     public
  29. @package    Tree
  30. */
  31. {
  32.  
  33.     // {{{ properties
  34.     var $debug = 0;
  35.  
  36.     var $options = array(
  37.         // FIXXME to be implemented
  38.         // add on for the where clause, this string is simply added
  39.         // behind the WHERE in the select so you better make sure
  40.         // its correct SQL :-), i.e. 'uid=3'
  41.         // this is needed i.e. when you are saving many trees in one db-table
  42.         'whereAddOn'=>'',
  43.         'table'     =>'',
  44.         // since the internal names are fixed, to be portable between different
  45.         // DB tables with different column namings, we map the internal name
  46.         // to the real column name using this array here, if it stays empty
  47.         // the internal names are used, which are:
  48.         // id, left, right
  49.         'columnNameMaps'=>array(
  50.                             // since mysql at least doesnt support 'left' ...
  51.                             'left'      =>  'l',
  52.                             // ...as a column name we set default to the first
  53.                             //letter only
  54.                             'right'     =>  'r',
  55.                             // parent id
  56.                             'parentId'  =>  'parent'
  57.                        ),
  58.         // needed for sorting the tree, currently only used in Memory_DBnested
  59.         'order'    => ''
  60.        );
  61.  
  62.     // }}}
  63.     // {{{ __construct()
  64.  
  65.     // the defined methods here are proposals for the implementation,
  66.     // they are named the same, as the methods in the "Memory" branch.
  67.     // If possible it would be cool to keep the same naming. And
  68.     // if the same parameters would be possible too then it would be
  69.     // even better, so one could easily change from any kind
  70.     // of tree-implementation to another, without changing the source
  71.     // code, only the setupXXX would need to be changed
  72.     /**
  73.       *
  74.       *
  75.       * @access     public
  76.       * @version    2002/03/02
  77.       * @param      string  the DSN for the DB connection
  78.       * @return     void 
  79.       */
  80.     function __construct($dsn$options = array())
  81.     {
  82.         $this->Tree_Dynamic_MDB2nested($dsn$options);
  83.     }
  84.  
  85.     // }}}
  86.     // {{{ Tree_Dynamic_DBnested()
  87.  
  88.     /**
  89.      *
  90.      *
  91.      * @access     public
  92.      * @version    2002/03/02
  93.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  94.      * @param      string  the DSN for the DB connection
  95.      * @return     void 
  96.      */
  97.     function Tree_Dynamic_MDB2nested($dsn$options = array())
  98.     {
  99.         parent::Tree_OptionsMDB2($dsn$options)// instanciate DB
  100.         $this->table $this->getOption('table');
  101.     }
  102.  
  103.     // }}}
  104.     // {{{ add()
  105.  
  106.     /**
  107.      * add a new element to the tree
  108.      * there are three ways to use this method
  109.      * Method 1:
  110.      * Give only the $parentId and the $newValues will be inserted
  111.      * as the first child of this parent
  112.      * <code>
  113.      * // insert a new element under the parent with the ID=7
  114.      * $tree->add(array('name'=>'new element name'), 7);
  115.      * </code>
  116.      *
  117.      * Method 2:
  118.      * Give the $prevId ($parentId will be dismissed) and the new element
  119.      * will be inserted in the tree after the element with the ID=$prevId
  120.      * the parentId is not necessary because the prevId defines exactly where
  121.      * the new element has to be place in the tree, and the parent is
  122.      * the same as for the element with the ID=$prevId
  123.      * <code>
  124.      * // insert a new element after the element with the ID=5
  125.      * $tree->add(array('name'=>'new'), 0, 5);
  126.      * </code>
  127.      *
  128.      * Method 3:
  129.      * neither $parentId nor prevId is given, then the root element will be
  130.      * inserted. This requires that programmer is responsible to confirm this.
  131.      * This method does not yet check if there is already a root element saved!
  132.      *
  133.      * @access     public
  134.      * @param   array   $newValues  this array contains the values that shall
  135.      *                               be inserted in the db-table
  136.      * @param   integer $parentId   the id of the element which shall be
  137.      *                               the parent of the new element
  138.      * @param   integer $prevId     the id of the element which shall preceed
  139.      *                               the one to be inserted use either
  140.      *                               'parentId' or 'prevId'.
  141.      * @return   integer the ID of the element that had been inserted
  142.      */
  143.     function add($newValues$parentId = 0$prevId = 0)
  144.     {
  145.         $lName $this->_getColName('left');
  146.         $rName $this->_getColName('right');
  147.         $prevVisited = 0;
  148.  
  149.         // check the DB-table if the columns which are given as keys
  150.         // in the array $newValues do really exist, if not remove them
  151.         // from the array
  152.         // FIXXME do the above described
  153.         // if no parent and no prevId is given the root shall be added
  154.         if ($parentId || $prevId{
  155.             if ($prevId{
  156.                 $element $this->getElement($prevId);
  157.                 // we also need the parent id of the element
  158.                 // to write it in the db
  159.                 $parentId $element['parentId'];
  160.             else {
  161.                 $element $this->getElement($parentId);
  162.             }
  163.             $newValues['parentId'$parentId;
  164.  
  165.             if (Tree::isError($element)) {
  166.                 return $element;
  167.             }
  168.  
  169.             // get the "visited"-value where to add the new element behind
  170.             // if $prevId is given, we need to use the right-value
  171.             // if only the $parentId is given we need to use the left-value
  172.             // look at it graphically, that made me understand it :-)
  173.             // See:
  174.             // http://research.calacademy.org/taf/proceedings/ballew/sld034.htm
  175.             $prevVisited $prevId $element['right'$element['left'];
  176.  
  177.             // FIXXME start transaction here
  178.             if (Tree::isError($err $this->_add($prevVisited1))) {
  179.                 // FIXXME rollback
  180.                 //$this->dbh->rollback();
  181.                 return $err;
  182.             }
  183.         }
  184.  
  185.         // inserting _one_ new element in the tree
  186.         $newData = array();
  187.         // quote the values, as needed for the insert
  188.         foreach ($newValues as $key => $value{
  189.  
  190.             ///////////FIX ME: Add proper quote handling
  191.  
  192.             $newData[$this->_getColName($key)$this->dbh->quote($value'text');
  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->quote($nextId'integer'),
  206.                             implode(','$newData)
  207.                         );
  208.         if (MDB2::isError($res $this->dbh->exec($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 (MDB2::isError($res $this->dbh->exec($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 (MDB2::isError($res $this->dbh->exec($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 (MDB2::isError($res $this->dbh->exec($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 (MDB2::isError($res $this->dbh->exec($query))) {
  339.             // the rollback shall be done by the method calling this one
  340.             // since it is only private we can do that
  341.             return $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 (MDB2::isError($res $this->dbh->exec($query))) {
  357.             // the rollback shall be done by the method calling this one
  358.             // since it is only private
  359.             return $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 (MDB2::isError($res $this->dbh->exec($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->quote($value'text');
  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 (MDB2::isError($res $this->dbh->exec($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 (MDB2::isError($res $this->dbh->queryRow($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 (MDB2::isError($res $this->dbh->queryRow($query))) {
  675.             return $this->_throwError($res->getMessage()__LINE__);
  676.         }
  677.         if (!$res{
  678.             return $this->_throwError("Element with id $id does not exist!"__LINE__);
  679.         }
  680.         return $this->_prepareResult($res);
  681.     }
  682.  
  683.     // }}}
  684.     // {{{ getChild()
  685.  
  686.     /**
  687.      *
  688.      *
  689.      * @access     public
  690.      * @version    2002/03/02
  691.      * @param      integer  the ID of the element for which the children
  692.      *                       shall be returned
  693.      * @return     mixed   either the data of the requested element or an Tree_Error
  694.      */
  695.     function getChild($id)
  696.     {
  697.         // subqueries would be cool :-)
  698.         $curElement $this->getElement($id);
  699.         if (Tree::isError($curElement)) {
  700.             return $curElement;
  701.         }
  702.  
  703.         $query sprintf('SELECT * FROM %s WHERE%s %s=%s',
  704.                             $this->table,
  705.                             $this->_getWhereAddOn(),
  706.                             $this->_getColName('left'),
  707.                             $curElement['left']+1);
  708.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  709.             return $this->_throwError($res->getMessage()__LINE__);
  710.         }
  711.         return $this->_prepareResult($res);
  712.     }
  713.  
  714.     // }}}
  715.     // {{{ getPath()
  716.  
  717.     /**
  718.      * gets the path from the element with the given id down
  719.      * to the root. The returned array is sorted to start at root
  720.      * for simply walking through and retreiving the path
  721.      *
  722.      * @access public
  723.      * @param integer the ID of the element for which the path shall be returned
  724.      * @return mixed  either the data of the requested elements
  725.      *                       or an Tree_Error
  726.      */
  727.     function getPath($id)
  728.     {
  729.         $res $this->dbh->queryAll($this->_getPathQuery($id));
  730.         if (MDB2::isError($res)) {
  731.             return $this->_throwError($res->getMessage()__LINE__);
  732.         }
  733.         return $this->_prepareResults($res);
  734.     }
  735.  
  736.     // }}}
  737.     // {{{ _getPathQuery()
  738.  
  739.     function _getPathQuery($id)
  740.     {
  741.         // subqueries would be cool :-)
  742.         $curElement $this->getElement($id);
  743.         $query sprintf('SELECT * FROM %s '.
  744.                             'WHERE %s %s<=%s AND %s>=%s '.
  745.                             'ORDER BY %s',
  746.                             // set the FROM %s
  747.                             $this->table,
  748.                             // set the additional where add on
  749.                             $this->_getWhereAddOn(),
  750.                             // render 'left<=curLeft'
  751.                             $this->_getColName('left'),  $curElement['left'],
  752.                             // render right>=curRight'
  753.                             $this->_getColName('right')$curElement['right'],
  754.                             // set the order column
  755.                             $this->_getColName('left'));
  756.         return $query;
  757.     }
  758.  
  759.     // }}}
  760.     // {{{ getLevel()
  761.  
  762.     function getLevel($id)
  763.     {
  764.         $query $this->_getPathQuery($id);
  765.         // i know this is not really beautiful ...
  766.         $query preg_replace('/^select \* /i','SELECT COUNT(*) ',$query);
  767.         if (MDB2::isError($res $this->dbh->queryOne($query))) {
  768.             return $this->_throwError($res->getMessage()__LINE__);
  769.         }
  770.         return $res-1;
  771.     }
  772.  
  773.     // }}}
  774.     // {{{ getLeft()
  775.  
  776.     /**
  777.      * gets the element to the left, the left visit
  778.      *
  779.      * @access     public
  780.      * @version    2002/03/07
  781.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  782.      * @param      integer  the ID of the element
  783.      * @return     mixed    either the data of the requested element
  784.      *                       or an Tree_Error
  785.      */
  786.     function getLeft($id)
  787.     {
  788.         $element $this->getElement($id);
  789.         if (Tree::isError($element)) {
  790.             return $element;
  791.         }
  792.  
  793.         $query sprintf('SELECT * FROM %s WHERE%s (%s=%s OR %s=%s)',
  794.                             $this->table,
  795.                             $this->_getWhereAddOn(),
  796.                             $this->_getColName('right')$element['left'- 1,
  797.                             $this->_getColName('left'),  $element['left'- 1);
  798.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  799.             return $this->_throwError($res->getMessage()__LINE__);
  800.         }
  801.         return $this->_prepareResult($res);
  802.     }
  803.  
  804.     // }}}
  805.     // {{{ getRight()
  806.  
  807.     /**
  808.      * gets the element to the right, the right visit
  809.      *
  810.      * @access     public
  811.      * @version    2002/03/07
  812.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  813.      * @param      integer  the ID of the element
  814.      * @return     mixed    either the data of the requested element
  815.      *                       or an Tree_Error
  816.      */
  817.     function getRight($id)
  818.     {
  819.         $element $this->getElement($id);
  820.         if (Tree::isError($element))
  821.             return $element;
  822.  
  823.         $query sprintf('SELECT * FROM %s WHERE%s (%s=%s OR %s=%s)',
  824.                             $this->table,
  825.                             $this->_getWhereAddOn(),
  826.                             $this->_getColName('left'),  $element['right'+ 1,
  827.                             $this->_getColName('right')$element['right'+ 1);
  828.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  829.             return $this->_throwError($res->getMessage()__LINE__);
  830.         }
  831.         return $this->_prepareResult($res);
  832.     }
  833.  
  834.     // }}}
  835.     // {{{ getParent()
  836.  
  837.     /**
  838.      * get the parent of the element with the given id
  839.      *
  840.      * @access     public
  841.      * @version    2002/04/15
  842.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  843.      * @param      integer the ID of the element
  844.      * @return     mixed    the array with the data of the parent element
  845.      *                       or false, if there is no parent, if the element is
  846.      *                       the root or an Tree_Error
  847.      */
  848.     function getParent($id)
  849.     {
  850.         $query sprintf('SELECT
  851.                                 p.*
  852.                             FROM
  853.                                 %s p,%s e
  854.                             WHERE
  855.                                 %s e.%s=p.%s
  856.                                 AND
  857.                                 e.%s=%s',
  858.                             $this->table,$this->table,
  859.                             $this->_getWhereAddOn(' AND ''p'),
  860.                             $this->_getColName('parentId'),
  861.                             $this->_getColName('id'),
  862.                             $this->_getColName('id'),
  863.                             $id);
  864.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  865.             return $this->_throwError($res->getMessage()__LINE__);
  866.         }
  867.         return $this->_prepareResult($res);
  868.     }
  869.  
  870.     // }}}
  871.     // {{{ getChildren()
  872.  
  873.     /**
  874.      * get the children of the given element or if the parameter is an array.
  875.      * It gets the children of all the elements given by their ids
  876.      * in the array.
  877.      *
  878.      * @access     public
  879.      * @version    2002/04/15
  880.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  881.      * @param      mixed   (1) int     the id of one element
  882.      *                      (2) array   an array of ids for which
  883.      *                                  the children will be returned
  884.      * @param      integer the children of how many levels shall be returned
  885.      * @return     mixed   the array with the data of all children
  886.      *                      or false, if there are none
  887.      */
  888.     function getChildren($ids$levels = 1)
  889.     {
  890.         $res = array();
  891.         for ($i = 1; $i $levels + 1; $i++{
  892.             // if $ids is an array implode the values
  893.             $getIds is_array($idsimplode(','$ids$ids;
  894.  
  895.             $query sprintf('SELECT
  896.                                     c.*
  897.                                 FROM
  898.                                     %s c,%s e
  899.                                 WHERE
  900.                                     %s e.%s=c.%s
  901.                                     AND
  902.                                     e.%s IN (%s) '.
  903.                                 'ORDER BY
  904.                                     c.%s',
  905.                                 $this->table,$this->table,
  906.                                 $this->_getWhereAddOn(' AND ''c'),
  907.                                 $this->_getColName('id'),
  908.                                 $this->_getColName('parentId'),
  909.                                 $this->_getColName('id'),
  910.                                 $getIds,
  911.                                 // order by left, so we have it in the order
  912.                                 // as it is in the tree if no 'order'-option
  913.                                 // is given
  914.                                 $this->getOption('order')?
  915.                                     $this->getOption('order')
  916.                                     : $this->_getColName('left')
  917.                        );
  918.             if (MDB2::isError($_res $this->dbh->queryAll($query))) {
  919.                 return $this->_throwError($_res->getMessage()__LINE__);
  920.             }
  921.  
  922.             // Column names are now unmapped
  923.             $_res $this->_prepareResults($_res);
  924.  
  925.             // we use the id as the index, to make the use easier esp.
  926.             // for multiple return-values
  927.             $tempRes = array();
  928.             foreach ($_res as $aRes{
  929.                 $tempRes[$aRes['id']] $aRes;
  930.             }
  931.             $_res $tempRes;
  932.  
  933.             if ($levels > 1{
  934.                 $ids = array();
  935.                 foreach ($_res as $aRes{
  936.                     $ids[$aRes[$this->_getColName('id')];
  937.                 }
  938.             }
  939.             $res array_merge($res$_res);
  940.  
  941.             // quit the for-loop if there are no children in the current level
  942.             if (!sizeof($ids)) {
  943.                 break;
  944.             }
  945.         }
  946.         return $res;
  947.     }
  948.  
  949.     // }}}
  950.     // {{{ getNext()
  951.  
  952.     /**
  953.      * get the next element on the same level
  954.      * if there is none return false
  955.      *
  956.      * @access     public
  957.      * @version    2002/04/15
  958.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  959.      * @param      integer the ID of the element
  960.      * @return     mixed   the array with the data of the next element
  961.      *                      or false, if there is no next
  962.      *                      or Tree_Error
  963.      */
  964.     function getNext($id)
  965.     {
  966.         $query sprintf('SELECT
  967.                                 n.*
  968.                             FROM
  969.                                 %s n,%s e
  970.                             WHERE
  971.                                 %s e.%s=n.%s-1
  972.                             AND
  973.                                 e.%s=n.%s
  974.                             AND
  975.                                 e.%s=%s',
  976.                             $this->table$this->table,
  977.                             $this->_getWhereAddOn(' AND ''n'),
  978.                             $this->_getColName('right'),
  979.                             $this->_getColName('left'),
  980.                             $this->_getColName('parentId'),
  981.                             $this->_getColName('parentId'),
  982.                             $this->_getColName('id'),
  983.                             $id);
  984.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  985.             return $this->_throwError($res->getMessage()__LINE__);
  986.         }
  987.         return !$res ? false : $this->_prepareResult($res);
  988.     }
  989.  
  990.     // }}}
  991.     // {{{ getPrevious()
  992.  
  993.     /**
  994.      * get the previous element on the same level
  995.      * if there is none return false
  996.      *
  997.      * @access     public
  998.      * @version    2002/04/15
  999.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1000.      * @param      integer the ID of the element
  1001.      * @return     mixed   the array with the data of the previous element
  1002.      *                      or false, if there is no previous
  1003.      *                      or a Tree_Error
  1004.      */
  1005.     function getPrevious($id)
  1006.     {
  1007.         $query sprintf('SELECT
  1008.                                 p.*
  1009.                             FROM
  1010.                                 %s p,%s e
  1011.                             WHERE
  1012.                                 %s e.%s=p.%s+1
  1013.                                 AND
  1014.                                     e.%s=p.%s
  1015.                                 AND
  1016.                                     e.%s=%s',
  1017.                             $this->table,$this->table,
  1018.                             $this->_getWhereAddOn(' AND ''p'),
  1019.                             $this->_getColName('left'),
  1020.                             $this->_getColName('right'),
  1021.                             $this->_getColName('parentId'),
  1022.                             $this->_getColName('parentId'),
  1023.                             $this->_getColName('id'),
  1024.                             $id);
  1025.         if (MDB2::isError($res $this->dbh->queryRow($query))) {
  1026.             return $this->_throwError($res->getMessage()__LINE__);
  1027.         }
  1028.         return !$res ? false : $this->_prepareResult($res);
  1029.     }
  1030.  
  1031.     // }}}
  1032.     // {{{ isChildOf()
  1033.  
  1034.     /**
  1035.      * returns if $childId is a child of $id
  1036.      *
  1037.      * @abstract
  1038.      * @version    2002/04/29
  1039.      * @access     public
  1040.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1041.      * @param      int     id of the element
  1042.      * @param      int     id of the element to check if it is a child
  1043.      * @return     boolean true if it is a child
  1044.      */
  1045.     function isChildOf($id$childId)
  1046.     {
  1047.         // check simply if the left and right of the child are within the
  1048.         // left and right of the parent, if so it definitly is a child :-)
  1049.         $parent $this->getElement($id);
  1050.         $child  $this->getElement($childId);
  1051.  
  1052.         if ($parent['left'$child['left']
  1053.             && $parent['right'$child['right'])
  1054.         {
  1055.             return true;
  1056.         }
  1057.         return false;
  1058.     }
  1059.  
  1060.     // }}}
  1061.     // {{{ getDepth()
  1062.  
  1063.     /**
  1064.      * return the maximum depth of the tree
  1065.      *
  1066.      * @version    2003/02/25
  1067.      * @access     public
  1068.      * @author "Denis Joloudov" <dan@aitart.ru>, Wolfram Kriesing <wolfram@kriesing.de>
  1069.      * @return integer the depth of the tree
  1070.      */
  1071.     function getDepth()
  1072.     {
  1073.         // FIXXXME TODO!!!
  1074.         $query sprintf('SELECT COUNT(*) FROM %s p, %s e '.
  1075.                             'WHERE %s (e.%s BETWEEN p.%s AND p.%s) AND '.
  1076.                             '(e.%s BETWEEN p.%s AND p.%s)',
  1077.                             $this-> table,$this->table,
  1078.                             // first line in where
  1079.                             $this->_getWhereAddOn(' AND ','p'),
  1080.                             $this->_getColName('left'),$this->_getColName('left'),
  1081.                             $this->_getColName('right'),
  1082.                             // second where line
  1083.                             $this->_getColName('right'),$this->_getColName('left'),
  1084.                             $this->_getColName('right')
  1085.                             );
  1086.         if (MDB2::isError($res=$this->dbh->queryOne($query))) {
  1087.             return $this->_throwError($res->getMessage()__LINE__);
  1088.         }
  1089.         if (!$res{
  1090.             return false;
  1091.         }
  1092.         return $this->_prepareResult($res);
  1093.     }
  1094.  
  1095.     // }}}
  1096.     // {{{ hasChildren()
  1097.  
  1098.     /**
  1099.      * Tells if the node with the given ID has children.
  1100.      *
  1101.      * @version    2003/03/04
  1102.      * @access     public
  1103.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1104.      * @param      integer the ID of a node
  1105.      * @return     boolean if the node with the given id has children
  1106.      */
  1107.     function hasChildren($id)
  1108.     {
  1109.         $element $this->getElement($id);
  1110.         // if the diff between left and right > 1 then there are children
  1111.         return ($element['right'$element['left']> 1;
  1112.     }
  1113.  
  1114.     // }}}
  1115.     // {{{ getIdByPath()
  1116.  
  1117.     /**
  1118.      * return the id of the element which is referenced by $path
  1119.      * this is useful for xml-structures, like: getIdByPath('/root/sub1/sub2')
  1120.      * this requires the structure to use each name uniquely
  1121.      * if this is not given it will return the first proper path found
  1122.      * i.e. there should only be one path /x/y/z
  1123.      * experimental: the name can be non unique if same names are in different levels
  1124.      *
  1125.      * @version    2003/05/11
  1126.      * @access     public
  1127.      * @author     Pierre-Alain Joye <paj@pearfr.org>
  1128.      * @param      string   $path       the path to search for
  1129.      * @param      integer  $startId    the id where to start the search
  1130.      * @param      string   $nodeName   the name of the key that contains
  1131.      *                                   the node name
  1132.      * @param      string   $seperator  the path seperator
  1133.      * @return     integer  the id of the searched element
  1134.      */
  1135.     function getIdByPath($path$startId = 0$nodeName 'name'$separator '/')
  1136.     // should this method be called getElementIdByPath ????
  1137.     // Yes, with an optional private paramater to get the whole node
  1138.     // in preference to only the id?
  1139.     {
  1140.         if ($separator == ''{
  1141.             return $this->_throwError(
  1142.                 'getIdByPath: Empty separator not allowed'__LINE__);
  1143.         }
  1144.         if ($path == $separator{
  1145.             $root $this->getRoot();
  1146.             if (Tree::isError($root)) {
  1147.                 return $root;
  1148.             }
  1149.             return $root['id'];
  1150.         }
  1151.         if (!($colname=$this->_getColName($nodeName))) {
  1152.             return $this->_throwError(
  1153.                 'getIdByPath: Invalid node name'__LINE__);
  1154.         }
  1155.         if ($startId != 0{
  1156.             // If the start node has no child, returns false
  1157.             // hasChildren calls getElement. Not very good right
  1158.             // now. See the TODO
  1159.             $startElem $this->getElement($startId);
  1160.             if (!is_array($startElem|| Tree::isError($startElem)) {
  1161.                 return $startElem;
  1162.             }
  1163.             // No child? return
  1164.             if (!is_array($startElem)) {
  1165.                 return null;
  1166.             }
  1167.             $rangeStart $startElem['left'];
  1168.             $rangeEnd   $startElem['right'];
  1169.             // Not clean, we should call hasChildren, but I do not
  1170.             // want to call getELement again :). See TODO
  1171.             $startHasChild ($rangeEnd-$rangeStart> 1 ? true : false;
  1172.             $cwd '/'.$this->getPathAsString($startId);
  1173.         else {
  1174.             $cwd '/';
  1175.             $startHasChild = false;
  1176.         }
  1177.         $t $this->_preparePath($path$cwd$separator);
  1178.         if (Tree::isError($t)) {
  1179.             return $t;
  1180.         }
  1181.         list($elems$sublevels$t;
  1182.         $cntElems sizeof($elems);
  1183.         $where '';
  1184.  
  1185.         $query 'SELECT '
  1186.                 .$this->_getColName('id')
  1187.                 .' FROM '
  1188.                 .$this->table
  1189.                 .' WHERE '
  1190.                 .$colname;
  1191.         if ($cntElems == 1{
  1192.             $query .= "='".$elems[0]."'";
  1193.         else {
  1194.             $query .= "='".$elems[$cntElems-1]."'";
  1195.         }
  1196.         if ($startHasChild{
  1197.             $where  .= ' AND ('.
  1198.                         $this->_getColName('left').'>'.$rangeStart.
  1199.                         ' AND '.
  1200.                         $this->_getColName('right').'<'.$rangeEnd.')';
  1201.         }
  1202.         $res $this->dbh->queryOne($query);
  1203.         if (MDB2::isError($res)) {
  1204.             return $this->_throwError($res->getMessage()__LINE__);
  1205.         }
  1206.         return ($res ? (int)$res : false);
  1207.     }
  1208.  
  1209.     // }}}
  1210.  
  1211.     //
  1212.     //  PRIVATE METHODS
  1213.     //
  1214.  
  1215.     // {{{ _getWhereAddOn()
  1216.     /**
  1217.      *
  1218.      *
  1219.      * @access     private
  1220.      * @version    2002/04/20
  1221.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1222.      * @param      string  the current where clause
  1223.      * @return     string  the where clause we want to add to a query
  1224.      */
  1225.     function _getWhereAddOn($addAfter ' AND '$tableName '')
  1226.     {
  1227.         if ($where=$this->getOption('whereAddOn')) {
  1228.             return ' '.($tableName $tableName.'.' '')." $where$addAfter ";
  1229.         }
  1230.         return '';
  1231.     }
  1232.  
  1233.     // }}}
  1234.     // {{{ getFirstRoot()
  1235.  
  1236.     // for compatibility to Memory methods
  1237.     function getFirstRoot()
  1238.     {
  1239.         return $this->getRoot();
  1240.     }
  1241.  
  1242.     // }}}
  1243.     // {{{ getNode()
  1244.  
  1245.     /**
  1246.      * gets the tree under the given element in one array, sorted
  1247.      * so you can go through the elements from begin to end and list them
  1248.      * as they are in the tree, where every child (until the deepest) is retreived
  1249.      *
  1250.      * @see        &_getNode()
  1251.      * @access     public
  1252.      * @version    2001/12/17
  1253.      * @author     Wolfram Kriesing <wolfram@kriesing.de>
  1254.      * @param      integer  $startId    the id where to start walking
  1255.      * @param      integer  $depth      this number says how deep into
  1256.      *                                   the structure the elements shall
  1257.      *                                   be retreived
  1258.      * @return     array    sorted as listed in the tree
  1259.      */
  1260.     function &getNode($startId = 0$depth = 0)
  1261.     {
  1262. //FIXXXME use getChildren()
  1263.         if ($startId{
  1264.             $startNode $this->getElement($startId);
  1265.             if (Tree::isError($startNode)) {
  1266.                 return $startNode;
  1267.             }
  1268.  
  1269.         else {
  1270.         }
  1271.     }
  1272. }
  1273.  
  1274. /*
  1275. * Local Variables:
  1276. * mode: php
  1277. * tab-width: 4
  1278. * c-basic-offset: 4
  1279. * End:
  1280. */

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