Source for file nested.php

Documentation is available at nested.php

  1. <?php
  2. /**
  3.  * @package     Joomla.Platform
  4.  * @subpackage  Table
  5.  *
  6.  * @copyright   Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  7.  * @license     GNU General Public License version 2 or later; see LICENSE
  8.  */
  9.  
  10. defined('JPATH_PLATFORM'or die;
  11.  
  12. /**
  13.  * Table class supporting modified pre-order tree traversal behavior.
  14.  *
  15.  * @package     Joomla.Platform
  16.  * @subpackage  Table
  17.  * @link        http://docs.joomla.org/JTableNested
  18.  * @since       11.1
  19.  */
  20. class JTableNested extends JTable
  21. {
  22.     /**
  23.      * Object property holding the primary key of the parent node.  Provides
  24.      * adjacency list data for nodes.
  25.      *
  26.      * @var    integer 
  27.      * @since  11.1
  28.      */
  29.     public $parent_id;
  30.  
  31.     /**
  32.      * Object property holding the depth level of the node in the tree.
  33.      *
  34.      * @var    integer 
  35.      * @since  11.1
  36.      */
  37.     public $level;
  38.  
  39.     /**
  40.      * Object property holding the left value of the node for managing its
  41.      * placement in the nested sets tree.
  42.      *
  43.      * @var    integer 
  44.      * @since  11.1
  45.      */
  46.     public $lft;
  47.  
  48.     /**
  49.      * Object property holding the right value of the node for managing its
  50.      * placement in the nested sets tree.
  51.      *
  52.      * @var    integer 
  53.      * @since  11.1
  54.      */
  55.     public $rgt;
  56.  
  57.     /**
  58.      * Object property holding the alias of this node used to constuct the
  59.      * full text path, forward-slash delimited.
  60.      *
  61.      * @var    string 
  62.      * @since  11.1
  63.      */
  64.     public $alias;
  65.  
  66.     /**
  67.      * Object property to hold the location type to use when storing the row.
  68.      * Possible values are: ['before', 'after', 'first-child', 'last-child'].
  69.      *
  70.      * @var    string 
  71.      * @since  11.1
  72.      */
  73.     protected $_location;
  74.  
  75.     /**
  76.      * Object property to hold the primary key of the location reference node to
  77.      * use when storing the row.  A combination of location type and reference
  78.      * node describes where to store the current node in the tree.
  79.      *
  80.      * @var    integer 
  81.      * @since  11.1
  82.      */
  83.     protected $_location_id;
  84.  
  85.     /**
  86.      * An array to cache values in recursive processes.
  87.      *
  88.      * @var    array 
  89.      * @since  11.1
  90.      */
  91.     protected $_cache = array();
  92.  
  93.     /**
  94.      * Debug level
  95.      *
  96.      * @var    integer 
  97.      * @since  11.1
  98.      */
  99.     protected $_debug = 0;
  100.  
  101.     /**
  102.      * Sets the debug level on or off
  103.      *
  104.      * @param   integer  $level  0 = off, 1 = on
  105.      *
  106.      * @return  void 
  107.      *
  108.      * @since   11.1
  109.      */
  110.     public function debug($level)
  111.     {
  112.         $this->_debug = (int) $level;
  113.     }
  114.  
  115.     /**
  116.      * Method to get an array of nodes from a given node to its root.
  117.      *
  118.      * @param   integer  $pk          Primary key of the node for which to get the path.
  119.      * @param   boolean  $diagnostic  Only select diagnostic data for the nested sets.
  120.      *
  121.      * @return  mixed    An array of node objects including the start node.
  122.      *
  123.      * @since   11.1
  124.      * @throws  RuntimeException on database error
  125.      */
  126.     public function getPath($pk null$diagnostic false)
  127.     {
  128.         $k $this->_tbl_key;
  129.         $pk (is_null($pk)) $this->$k $pk;
  130.  
  131.         // Get the path from the node to the root.
  132.         $select ($diagnostic'p.' $k ', p.parent_id, p.level, p.lft, p.rgt' 'p.*';
  133.         $query $this->_db->getQuery(true)
  134.             ->select($select)
  135.             ->from($this->_tbl . ' AS n, ' $this->_tbl . ' AS p')
  136.             ->where('n.lft BETWEEN p.lft AND p.rgt')
  137.             ->where('n.' $k ' = ' . (int) $pk)
  138.             ->order('p.lft');
  139.  
  140.         $this->_db->setQuery($query);
  141.  
  142.         return $this->_db->loadObjectList();
  143.     }
  144.  
  145.     /**
  146.      * Method to get a node and all its child nodes.
  147.      *
  148.      * @param   integer  $pk          Primary key of the node for which to get the tree.
  149.      * @param   boolean  $diagnostic  Only select diagnostic data for the nested sets.
  150.      *
  151.      * @return  mixed    Boolean false on failure or array of node objects on success.
  152.      *
  153.      * @since   11.1
  154.      * @throws  RuntimeException on database error.
  155.      */
  156.     public function getTree($pk null$diagnostic false)
  157.     {
  158.         $k $this->_tbl_key;
  159.         $pk (is_null($pk)) $this->$k $pk;
  160.  
  161.         // Get the node and children as a tree.
  162.         $select ($diagnostic'n.' $k ', n.parent_id, n.level, n.lft, n.rgt' 'n.*';
  163.         $query $this->_db->getQuery(true)
  164.             ->select($select)
  165.             ->from($this->_tbl . ' AS n, ' $this->_tbl . ' AS p')
  166.             ->where('n.lft BETWEEN p.lft AND p.rgt')
  167.             ->where('p.' $k ' = ' . (int) $pk)
  168.             ->order('n.lft');
  169.  
  170.         return $this->_db->setQuery($query)->loadObjectList();
  171.     }
  172.  
  173.     /**
  174.      * Method to determine if a node is a leaf node in the tree (has no children).
  175.      *
  176.      * @param   integer  $pk  Primary key of the node to check.
  177.      *
  178.      * @return  boolean  True if a leaf node, false if not or null if the node does not exist.
  179.      *
  180.      * @note    Since 12.1 this method returns null if the node does not exist.
  181.      * @since   11.1
  182.      * @throws  RuntimeException on database error.
  183.      */
  184.     public function isLeaf($pk null)
  185.     {
  186.         $k $this->_tbl_key;
  187.         $pk (is_null($pk)) $this->$k $pk;
  188.         $node $this->_getNode($pk);
  189.  
  190.         // Get the node by primary key.
  191.         if (empty($node))
  192.         {
  193.             // Error message set in getNode method.
  194.             return null;
  195.         }
  196.  
  197.         // The node is a leaf node.
  198.         return (($node->rgt $node->lft== 1);
  199.     }
  200.  
  201.     /**
  202.      * Method to set the location of a node in the tree object.  This method does not
  203.      * save the new location to the database, but will set it in the object so
  204.      * that when the node is stored it will be stored in the new location.
  205.      *
  206.      * @param   integer  $referenceId  The primary key of the node to reference new location by.
  207.      * @param   string   $position     Location type string. ['before', 'after', 'first-child', 'last-child']
  208.      *
  209.      * @return  void 
  210.      *
  211.      * @note    Since 12.1 this method returns void and throws an InvalidArgumentException when an invalid position is passed.
  212.      * @since   11.1
  213.      * @throws  InvalidArgumentException
  214.      */
  215.     public function setLocation($referenceId$position 'after')
  216.     {
  217.         // Make sure the location is valid.
  218.         if (($position != 'before'&& ($position != 'after'&& ($position != 'first-child'&& ($position != 'last-child'))
  219.         {
  220.             throw new InvalidArgumentException(sprintf('%s::setLocation(%d, *%s*)'get_class($this)$referenceId$position));
  221.         }
  222.  
  223.         // Set the location properties.
  224.         $this->_location = $position;
  225.         $this->_location_id = $referenceId;
  226.     }
  227.  
  228.     /**
  229.      * Method to move a row in the ordering sequence of a group of rows defined by an SQL WHERE clause.
  230.      * Negative numbers move the row up in the sequence and positive numbers move it down.
  231.      *
  232.      * @param   integer  $delta  The direction and magnitude to move the row in the ordering sequence.
  233.      * @param   string   $where  WHERE clause to use for limiting the selection of rows to compact the
  234.      *                            ordering values.
  235.      *
  236.      * @return  mixed    Boolean true on success.
  237.      *
  238.      * @link    http://docs.joomla.org/JTable/move
  239.      * @since   11.1
  240.      */
  241.     public function move($delta$where '')
  242.     {
  243.         $k $this->_tbl_key;
  244.         $pk $this->$k;
  245.  
  246.         $query $this->_db->getQuery(true)
  247.             ->select($k)
  248.             ->from($this->_tbl)
  249.             ->where('parent_id = ' $this->parent_id);
  250.  
  251.         if ($where)
  252.         {
  253.             $query->where($where);
  254.         }
  255.  
  256.         if ($delta 0)
  257.         {
  258.             $query->where('rgt > ' $this->rgt)
  259.                 ->order('rgt ASC');
  260.             $position 'after';
  261.         }
  262.         else
  263.         {
  264.             $query->where('lft < ' $this->lft)
  265.                 ->order('lft DESC');
  266.             $position 'before';
  267.         }
  268.  
  269.         $this->_db->setQuery($query);
  270.         $referenceId $this->_db->loadResult();
  271.  
  272.         if ($referenceId)
  273.         {
  274.             return $this->moveByReference($referenceId$position$pk);
  275.         }
  276.         else
  277.         {
  278.             return false;
  279.         }
  280.     }
  281.  
  282.     /**
  283.      * Method to move a node and its children to a new location in the tree.
  284.      *
  285.      * @param   integer  $referenceId  The primary key of the node to reference new location by.
  286.      * @param   string   $position     Location type string. ['before', 'after', 'first-child', 'last-child']
  287.      * @param   integer  $pk           The primary key of the node to move.
  288.      *
  289.      * @return  boolean  True on success.
  290.      *
  291.      * @link    http://docs.joomla.org/JTableNested/moveByReference
  292.      * @since   11.1
  293.      * @throws  RuntimeException on database error.
  294.      */
  295.     public function moveByReference($referenceId$position 'after'$pk null)
  296.     {
  297.         // @codeCoverageIgnoreStart
  298.         if ($this->_debug)
  299.         {
  300.             echo "\nMoving ReferenceId:$referenceId, Position:$position, PK:$pk";
  301.         }
  302.         // @codeCoverageIgnoreEnd
  303.  
  304.         $k $this->_tbl_key;
  305.         $pk (is_null($pk)) $this->$k $pk;
  306.  
  307.         // Get the node by id.
  308.         if (!$node $this->_getNode($pk))
  309.         {
  310.             // Error message set in getNode method.
  311.             return false;
  312.         }
  313.  
  314.         // Get the ids of child nodes.
  315.         $query $this->_db->getQuery(true)
  316.             ->select($k)
  317.             ->from($this->_tbl)
  318.             ->where('lft BETWEEN ' . (int) $node->lft ' AND ' . (int) $node->rgt);
  319.  
  320.         $children $this->_db->setQuery($query)->loadColumn();
  321.  
  322.         // @codeCoverageIgnoreStart
  323.         if ($this->_debug)
  324.         {
  325.             $this->_logtable(false);
  326.         }
  327.         // @codeCoverageIgnoreEnd
  328.  
  329.         // Cannot move the node to be a child of itself.
  330.         if (in_array($referenceId$children))
  331.         {
  332.             $e new UnexpectedValueException(
  333.                 sprintf('%s::moveByReference(%d, %s, %d) parenting to child.'get_class($this)$referenceId$position$pk)
  334.             );
  335.             $this->setError($e);
  336.  
  337.             return false;
  338.         }
  339.  
  340.         // Lock the table for writing.
  341.         if (!$this->_lock())
  342.         {
  343.             return false;
  344.         }
  345.  
  346.         /*
  347.          * Move the sub-tree out of the nested sets by negating its left and right values.
  348.          */
  349.         $query->clear()
  350.             ->update($this->_tbl)
  351.             ->set('lft = lft * (-1), rgt = rgt * (-1)')
  352.             ->where('lft BETWEEN ' . (int) $node->lft ' AND ' . (int) $node->rgt);
  353.         $this->_db->setQuery($query);
  354.  
  355.         $this->_runQuery($query'JLIB_DATABASE_ERROR_MOVE_FAILED');
  356.  
  357.         /*
  358.          * Close the hole in the tree that was opened by removing the sub-tree from the nested sets.
  359.          */
  360.         // Compress the left values.
  361.         $query->clear()
  362.             ->update($this->_tbl)
  363.             ->set('lft = lft - ' . (int) $node->width)
  364.             ->where('lft > ' . (int) $node->rgt);
  365.         $this->_db->setQuery($query);
  366.  
  367.         $this->_runQuery($query'JLIB_DATABASE_ERROR_MOVE_FAILED');
  368.  
  369.         // Compress the right values.
  370.         $query->clear()
  371.             ->update($this->_tbl)
  372.             ->set('rgt = rgt - ' . (int) $node->width)
  373.             ->where('rgt > ' . (int) $node->rgt);
  374.         $this->_db->setQuery($query);
  375.  
  376.         $this->_runQuery($query'JLIB_DATABASE_ERROR_MOVE_FAILED');
  377.  
  378.         // We are moving the tree relative to a reference node.
  379.         if ($referenceId)
  380.         {
  381.             // Get the reference node by primary key.
  382.             if (!$reference $this->_getNode($referenceId))
  383.             {
  384.                 // Error message set in getNode method.
  385.                 $this->_unlock();
  386.  
  387.                 return false;
  388.             }
  389.  
  390.             // Get the reposition data for shifting the tree and re-inserting the node.
  391.             if (!$repositionData $this->_getTreeRepositionData($reference$node->width$position))
  392.             {
  393.                 // Error message set in getNode method.
  394.                 $this->_unlock();
  395.  
  396.                 return false;
  397.             }
  398.         }
  399.         // We are moving the tree to be the last child of the root node
  400.         else
  401.         {
  402.             // Get the last root node as the reference node.
  403.             $query->clear()
  404.                 ->select($this->_tbl_key . ', parent_id, level, lft, rgt')
  405.                 ->from($this->_tbl)
  406.                 ->where('parent_id = 0')
  407.                 ->order('lft DESC');
  408.             $this->_db->setQuery($query01);
  409.             $reference $this->_db->loadObject();
  410.  
  411.             // @codeCoverageIgnoreStart
  412.             if ($this->_debug)
  413.             {
  414.                 $this->_logtable(false);
  415.             }
  416.             // @codeCoverageIgnoreEnd
  417.  
  418.             // Get the reposition data for re-inserting the node after the found root.
  419.             if (!$repositionData $this->_getTreeRepositionData($reference$node->width'last-child'))
  420.             {
  421.                 // Error message set in getNode method.
  422.                 $this->_unlock();
  423.  
  424.                 return false;
  425.             }
  426.         }
  427.  
  428.         /*
  429.          * Create space in the nested sets at the new location for the moved sub-tree.
  430.          */
  431.  
  432.         // Shift left values.
  433.         $query->clear()
  434.             ->update($this->_tbl)
  435.             ->set('lft = lft + ' . (int) $node->width)
  436.             ->where($repositionData->left_where);
  437.         $this->_db->setQuery($query);
  438.  
  439.         $this->_runQuery($query'JLIB_DATABASE_ERROR_MOVE_FAILED');
  440.  
  441.         // Shift right values.
  442.         $query->clear()
  443.             ->update($this->_tbl)
  444.             ->set('rgt = rgt + ' . (int) $node->width)
  445.             ->where($repositionData->right_where);
  446.         $this->_db->setQuery($query);
  447.  
  448.         $this->_runQuery($query'JLIB_DATABASE_ERROR_MOVE_FAILED');
  449.  
  450.         /*
  451.          * Calculate the offset between where the node used to be in the tree and
  452.          * where it needs to be in the tree for left ids (also works for right ids).
  453.          */
  454.         $offset $repositionData->new_lft $node->lft;
  455.         $levelOffset $repositionData->new_level $node->level;
  456.  
  457.         // Move the nodes back into position in the tree using the calculated offsets.
  458.         $query->clear()
  459.             ->update($this->_tbl)
  460.             ->set('rgt = ' . (int) $offset ' - rgt')
  461.             ->set('lft = ' . (int) $offset ' - lft')
  462.             ->set('level = level + ' . (int) $levelOffset)
  463.             ->where('lft < 0');
  464.         $this->_db->setQuery($query);
  465.  
  466.         $this->_runQuery($query'JLIB_DATABASE_ERROR_MOVE_FAILED');
  467.  
  468.         // Set the correct parent id for the moved node if required.
  469.         if ($node->parent_id != $repositionData->new_parent_id)
  470.         {
  471.             $query $this->_db->getQuery(true)
  472.                 ->update($this->_tbl);
  473.  
  474.             // Update the title and alias fields if they exist for the table.
  475.             $fields $this->getFields();
  476.  
  477.             if (property_exists($this'title'&& $this->title !== null)
  478.             {
  479.                 $query->set('title = ' $this->_db->quote($this->title));
  480.             }
  481.  
  482.             if (array_key_exists('alias'$fields)  && $this->alias !== null)
  483.             {
  484.                 $query->set('alias = ' $this->_db->quote($this->alias));
  485.             }
  486.  
  487.             $query->set('parent_id = ' . (int) $repositionData->new_parent_id)
  488.                 ->where($this->_tbl_key . ' = ' . (int) $node->$k);
  489.             $this->_db->setQuery($query);
  490.  
  491.             $this->_runQuery($query'JLIB_DATABASE_ERROR_MOVE_FAILED');
  492.         }
  493.  
  494.         // Unlock the table for writing.
  495.         $this->_unlock();
  496.  
  497.         // Set the object values.
  498.         $this->parent_id = $repositionData->new_parent_id;
  499.         $this->level = $repositionData->new_level;
  500.         $this->lft = $repositionData->new_lft;
  501.         $this->rgt = $repositionData->new_rgt;
  502.  
  503.         return true;
  504.     }
  505.  
  506.     /**
  507.      * Method to delete a node and, optionally, its child nodes from the table.
  508.      *
  509.      * @param   integer  $pk        The primary key of the node to delete.
  510.      * @param   boolean  $children  True to delete child nodes, false to move them up a level.
  511.      *
  512.      * @return  boolean  True on success.
  513.      *
  514.      * @since   11.1
  515.      */
  516.     public function delete($pk null$children true)
  517.     {
  518.         $k $this->_tbl_key;
  519.         $pk (is_null($pk)) $this->$k $pk;
  520.  
  521.         // Implement JObservableInterface: Pre-processing by observers
  522.         $this->_observers->update('onBeforeDelete'array($pk));
  523.  
  524.         // Lock the table for writing.
  525.         if (!$this->_lock())
  526.         {
  527.             // Error message set in lock method.
  528.             return false;
  529.         }
  530.  
  531.         // If tracking assets, remove the asset first.
  532.         if ($this->_trackAssets)
  533.         {
  534.             $name $this->_getAssetName();
  535.             $asset JTable::getInstance('Asset');
  536.  
  537.             // Lock the table for writing.
  538.             if (!$asset->_lock())
  539.             {
  540.                 // Error message set in lock method.
  541.                 return false;
  542.             }
  543.  
  544.             if ($asset->loadByName($name))
  545.             {
  546.                 // Delete the node in assets table.
  547.                 if (!$asset->delete(null$children))
  548.                 {
  549.                     $this->setError($asset->getError());
  550.                     $asset->_unlock();
  551.  
  552.                     return false;
  553.                 }
  554.                 $asset->_unlock();
  555.             }
  556.             else
  557.             {
  558.                 $this->setError($asset->getError());
  559.                 $asset->_unlock();
  560.  
  561.                 return false;
  562.             }
  563.         }
  564.  
  565.         // Get the node by id.
  566.         $node $this->_getNode($pk);
  567.  
  568.         if (empty($node))
  569.         {
  570.             // Error message set in getNode method.
  571.             $this->_unlock();
  572.  
  573.             return false;
  574.         }
  575.  
  576.         $query $this->_db->getQuery(true);
  577.  
  578.         // Should we delete all children along with the node?
  579.         if ($children)
  580.         {
  581.             // Delete the node and all of its children.
  582.             $query->clear()
  583.                 ->delete($this->_tbl)
  584.                 ->where('lft BETWEEN ' . (int) $node->lft ' AND ' . (int) $node->rgt);
  585.             $this->_runQuery($query'JLIB_DATABASE_ERROR_DELETE_FAILED');
  586.  
  587.             // Compress the left values.
  588.             $query->clear()
  589.                 ->update($this->_tbl)
  590.                 ->set('lft = lft - ' . (int) $node->width)
  591.                 ->where('lft > ' . (int) $node->rgt);
  592.             $this->_runQuery($query'JLIB_DATABASE_ERROR_DELETE_FAILED');
  593.  
  594.             // Compress the right values.
  595.             $query->clear()
  596.                 ->update($this->_tbl)
  597.                 ->set('rgt = rgt - ' . (int) $node->width)
  598.                 ->where('rgt > ' . (int) $node->rgt);
  599.             $this->_runQuery($query'JLIB_DATABASE_ERROR_DELETE_FAILED');
  600.         }
  601.         // Leave the children and move them up a level.
  602.         else
  603.         {
  604.             // Delete the node.
  605.             $query->clear()
  606.                 ->delete($this->_tbl)
  607.                 ->where('lft = ' . (int) $node->lft);
  608.             $this->_runQuery($query'JLIB_DATABASE_ERROR_DELETE_FAILED');
  609.  
  610.             // Shift all node's children up a level.
  611.             $query->clear()
  612.                 ->update($this->_tbl)
  613.                 ->set('lft = lft - 1')
  614.                 ->set('rgt = rgt - 1')
  615.                 ->set('level = level - 1')
  616.                 ->where('lft BETWEEN ' . (int) $node->lft ' AND ' . (int) $node->rgt);
  617.             $this->_runQuery($query'JLIB_DATABASE_ERROR_DELETE_FAILED');
  618.  
  619.             // Adjust all the parent values for direct children of the deleted node.
  620.             $query->clear()
  621.                 ->update($this->_tbl)
  622.                 ->set('parent_id = ' . (int) $node->parent_id)
  623.                 ->where('parent_id = ' . (int) $node->$k);
  624.             $this->_runQuery($query'JLIB_DATABASE_ERROR_DELETE_FAILED');
  625.  
  626.             // Shift all of the left values that are right of the node.
  627.             $query->clear()
  628.                 ->update($this->_tbl)
  629.                 ->set('lft = lft - 2')
  630.                 ->where('lft > ' . (int) $node->rgt);
  631.             $this->_runQuery($query'JLIB_DATABASE_ERROR_DELETE_FAILED');
  632.  
  633.             // Shift all of the right values that are right of the node.
  634.             $query->clear()
  635.                 ->update($this->_tbl)
  636.                 ->set('rgt = rgt - 2')
  637.                 ->where('rgt > ' . (int) $node->rgt);
  638.             $this->_runQuery($query'JLIB_DATABASE_ERROR_DELETE_FAILED');
  639.         }
  640.  
  641.         // Unlock the table for writing.
  642.         $this->_unlock();
  643.  
  644.         // Implement JObservableInterface: Post-processing by observers
  645.         $this->_observers->update('onAfterDelete'array($pk));
  646.  
  647.         return true;
  648.     }
  649.  
  650.     /**
  651.      * Checks that the object is valid and able to be stored.
  652.      *
  653.      * This method checks that the parent_id is non-zero and exists in the database.
  654.      * Note that the root node (parent_id = 0) cannot be manipulated with this class.
  655.      *
  656.      * @return  boolean  True if all checks pass.
  657.      *
  658.      * @since   11.1
  659.      * @throws  Exception
  660.      * @throws  RuntimeException on database error.
  661.      * @throws  UnexpectedValueException
  662.      */
  663.     public function check()
  664.     {
  665.         $this->parent_id = (int) $this->parent_id;
  666.  
  667.         // Set up a mini exception handler.
  668.         try
  669.         {
  670.             // Check that the parent_id field is valid.
  671.             if ($this->parent_id == 0)
  672.             {
  673.                 throw new UnexpectedValueException(sprintf('Invalid `parent_id` [%d] in %s'$this->parent_idget_class($this)));
  674.             }
  675.  
  676.             $query $this->_db->getQuery(true)
  677.                 ->select('COUNT(' $this->_tbl_key . ')')
  678.                 ->from($this->_tbl)
  679.                 ->where($this->_tbl_key . ' = ' $this->parent_id);
  680.  
  681.             if (!$this->_db->setQuery($query)->loadResult())
  682.             {
  683.                 throw new UnexpectedValueException(sprintf('Invalid `parent_id` [%d] in %s'$this->parent_idget_class($this)));
  684.             }
  685.         }
  686.         catch (UnexpectedValueException $e)
  687.         {
  688.             // Validation error - record it and return false.
  689.             $this->setError($e);
  690.  
  691.             return false;
  692.         }
  693.         // @codeCoverageIgnoreStart
  694.         catch (Exception $e)
  695.         {
  696.             // Database error - rethrow.
  697.             throw $e;
  698.         }
  699.         // @codeCoverageIgnoreEnd
  700.  
  701.         return true;
  702.     }
  703.  
  704.     /**
  705.      * Method to store a node in the database table.
  706.      *
  707.      * @param   boolean  $updateNulls  True to update null values as well.
  708.      *
  709.      * @return  boolean  True on success.
  710.      *
  711.      * @link    http://docs.joomla.org/JTableNested/store
  712.      * @since   11.1
  713.      */
  714.     public function store($updateNulls false)
  715.     {
  716.         $k $this->_tbl_key;
  717.  
  718.         // Implement JObservableInterface: Pre-processing by observers
  719.         $this->_observers->update('onBeforeStore'array($updateNulls$k));
  720.  
  721.         // @codeCoverageIgnoreStart
  722.         if ($this->_debug)
  723.         {
  724.             echo "\n" get_class($this"::store\n";
  725.             $this->_logtable(truefalse);
  726.         }
  727.         // @codeCoverageIgnoreEnd
  728.  
  729.         /*
  730.          * If the primary key is empty, then we assume we are inserting a new node into the
  731.          * tree.  From this point we would need to determine where in the tree to insert it.
  732.          */
  733.         if (empty($this->$k))
  734.         {
  735.             /*
  736.              * We are inserting a node somewhere in the tree with a known reference
  737.              * node.  We have to make room for the new node and set the left and right
  738.              * values before we insert the row.
  739.              */
  740.             if ($this->_location_id >= 0)
  741.             {
  742.                 // Lock the table for writing.
  743.                 if (!$this->_lock())
  744.                 {
  745.                     // Error message set in lock method.
  746.                     return false;
  747.                 }
  748.  
  749.                 // We are inserting a node relative to the last root node.
  750.                 if ($this->_location_id == 0)
  751.                 {
  752.                     // Get the last root node as the reference node.
  753.                     $query $this->_db->getQuery(true)
  754.                         ->select($this->_tbl_key . ', parent_id, level, lft, rgt')
  755.                         ->from($this->_tbl)
  756.                         ->where('parent_id = 0')
  757.                         ->order('lft DESC');
  758.                     $this->_db->setQuery($query01);
  759.                     $reference $this->_db->loadObject();
  760.  
  761.                     // @codeCoverageIgnoreStart
  762.                     if ($this->_debug)
  763.                     {
  764.                         $this->_logtable(false);
  765.                     }
  766.                     // @codeCoverageIgnoreEnd
  767.                 }
  768.                 // We have a real node set as a location reference.
  769.                 else
  770.                 {
  771.                     // Get the reference node by primary key.
  772.                     if (!$reference $this->_getNode($this->_location_id))
  773.                     {
  774.                         // Error message set in getNode method.
  775.                         $this->_unlock();
  776.  
  777.                         return false;
  778.                     }
  779.                 }
  780.  
  781.                 // Get the reposition data for shifting the tree and re-inserting the node.
  782.                 if (!($repositionData $this->_getTreeRepositionData($reference2$this->_location)))
  783.                 {
  784.                     // Error message set in getNode method.
  785.                     $this->_unlock();
  786.  
  787.                     return false;
  788.                 }
  789.  
  790.                 // Create space in the tree at the new location for the new node in left ids.
  791.                 $query $this->_db->getQuery(true)
  792.                     ->update($this->_tbl)
  793.                     ->set('lft = lft + 2')
  794.                     ->where($repositionData->left_where);
  795.                 $this->_runQuery($query'JLIB_DATABASE_ERROR_STORE_FAILED');
  796.  
  797.                 // Create space in the tree at the new location for the new node in right ids.
  798.                 $query->clear()
  799.                     ->update($this->_tbl)
  800.                     ->set('rgt = rgt + 2')
  801.                     ->where($repositionData->right_where);
  802.                 $this->_runQuery($query'JLIB_DATABASE_ERROR_STORE_FAILED');
  803.  
  804.                 // Set the object values.
  805.                 $this->parent_id = $repositionData->new_parent_id;
  806.                 $this->level = $repositionData->new_level;
  807.                 $this->lft = $repositionData->new_lft;
  808.                 $this->rgt = $repositionData->new_rgt;
  809.             }
  810.             else
  811.             {
  812.                 // Negative parent ids are invalid
  813.                 $e new UnexpectedValueException(sprintf('%s::store() used a negative _location_id'get_class($this)));
  814.                 $this->setError($e);
  815.  
  816.                 return false;
  817.             }
  818.         }
  819.         /*
  820.          * If we have a given primary key then we assume we are simply updating this
  821.          * node in the tree.  We should assess whether or not we are moving the node
  822.          * or just updating its data fields.
  823.          */
  824.         else
  825.         {
  826.             // If the location has been set, move the node to its new location.
  827.             if ($this->_location_id > 0)
  828.             {
  829.                 if (!$this->moveByReference($this->_location_id$this->_location$this->$k))
  830.                 {
  831.                     // Error message set in move method.
  832.                     return false;
  833.                 }
  834.             }
  835.  
  836.             // Lock the table for writing.
  837.             if (!$this->_lock())
  838.             {
  839.                 // Error message set in lock method.
  840.                 return false;
  841.             }
  842.         }
  843.  
  844.         // Implement JObservableInterface: We do not want parent::store to update observers,
  845.         // since tables are locked and we are updating it from this level of store():
  846.         $oldCallObservers $this->_observers->doCallObservers(false);
  847.  
  848.         $result parent::store($updateNulls);
  849.  
  850.         // Implement JObservableInterface: Restore previous callable observers state:
  851.         $this->_observers->doCallObservers($oldCallObservers);
  852.  
  853.         if ($result)
  854.         {
  855.             // @codeCoverageIgnoreStart
  856.             if ($this->_debug)
  857.             {
  858.                 $this->_logtable();
  859.             }
  860.             // @codeCoverageIgnoreEnd
  861.         }
  862.  
  863.         // Unlock the table for writing.
  864.         $this->_unlock();
  865.  
  866.         // Implement JObservableInterface: Post-processing by observers
  867.         $this->_observers->update('onAfterStore'array(&$result));
  868.  
  869.         return $result;
  870.     }
  871.  
  872.     /**
  873.      * Method to set the publishing state for a node or list of nodes in the database
  874.      * table.  The method respects rows checked out by other users and will attempt
  875.      * to checkin rows that it can after adjustments are made. The method will not
  876.      * allow you to set a publishing state higher than any ancestor node and will
  877.      * not allow you to set a publishing state on a node with a checked out child.
  878.      *
  879.      * @param   mixed    $pks     An optional array of primary key values to update.  If not
  880.      *                             set the instance property value is used.
  881.      * @param   integer  $state   The publishing state. eg. [0 = unpublished, 1 = published]
  882.      * @param   integer  $userId  The user id of the user performing the operation.
  883.      *
  884.      * @return  boolean  True on success.
  885.      *
  886.      * @link    http://docs.joomla.org/JTableNested/publish
  887.      * @since   11.1
  888.      * @throws UnexpectedValueException
  889.      */
  890.     public function publish($pks null$state 1$userId 0)
  891.     {
  892.         $k $this->_tbl_key;
  893.         $query $this->_db->getQuery(true);
  894.  
  895.         // Sanitize input.
  896.         JArrayHelper::toInteger($pks);
  897.         $userId = (int) $userId;
  898.         $state = (int) $state;
  899.  
  900.         // If $state > 1, then we allow state changes even if an ancestor has lower state
  901.         // (for example, can change a child state to Archived (2) if an ancestor is Published (1)
  902.         $compareState ($state 1$state;
  903.  
  904.         // If there are no primary keys set check to see if the instance key is set.
  905.         if (empty($pks))
  906.         {
  907.             if ($this->$k)
  908.             {
  909.                 $pks explode(','$this->$k);
  910.             }
  911.             // Nothing to set publishing state on, return false.
  912.             else
  913.             {
  914.                 $e new UnexpectedValueException(sprintf('%s::publish(%s, %d, %d) empty.'get_class($this)$pks$state$userId));
  915.                 $this->setError($e);
  916.  
  917.                 return false;
  918.             }
  919.         }
  920.  
  921.         // Determine if there is checkout support for the table.
  922.         $checkoutSupport (property_exists($this'checked_out'|| property_exists($this'checked_out_time'));
  923.  
  924.         // Iterate over the primary keys to execute the publish action if possible.
  925.         foreach ($pks as $pk)
  926.         {
  927.             // Get the node by primary key.
  928.             if (!$node $this->_getNode($pk))
  929.             {
  930.                 // Error message set in getNode method.
  931.                 return false;
  932.             }
  933.  
  934.             // If the table has checkout support, verify no children are checked out.
  935.             if ($checkoutSupport)
  936.             {
  937.                 // Ensure that children are not checked out.
  938.                 $query->clear()
  939.                     ->select('COUNT(' $k ')')
  940.                     ->from($this->_tbl)
  941.                     ->where('lft BETWEEN ' . (int) $node->lft ' AND ' . (int) $node->rgt)
  942.                     ->where('(checked_out <> 0 AND checked_out <> ' . (int) $userId ')');
  943.                 $this->_db->setQuery($query);
  944.  
  945.                 // Check for checked out children.
  946.                 if ($this->_db->loadResult())
  947.                 {
  948.                     // TODO Convert to a conflict exception when available.
  949.                     $e new RuntimeException(sprintf('%s::publish(%s, %d, %d) checked-out conflict.'get_class($this)$pks$state$userId));
  950.  
  951.                     $this->setError($e);
  952.  
  953.                     return false;
  954.                 }
  955.             }
  956.  
  957.             // If any parent nodes have lower published state values, we cannot continue.
  958.             if ($node->parent_id)
  959.             {
  960.                 // Get any ancestor nodes that have a lower publishing state.
  961.                 $query->clear()
  962.                     ->select('n.' $k)
  963.                     ->from($this->_db->quoteName($this->_tbl' AS n')
  964.                     ->where('n.lft < ' . (int) $node->lft)
  965.                     ->where('n.rgt > ' . (int) $node->rgt)
  966.                     ->where('n.parent_id > 0')
  967.                     ->where('n.published < ' . (int) $compareState);
  968.  
  969.                 // Just fetch one row (one is one too many).
  970.                 $this->_db->setQuery($query01);
  971.  
  972.                 $rows $this->_db->loadColumn();
  973.  
  974.                 if (!empty($rows))
  975.                 {
  976.                     $e new UnexpectedValueException(
  977.                         sprintf('%s::publish(%s, %d, %d) ancestors have lower state.'get_class($this)$pks$state$userId)
  978.                     );
  979.                     $this->setError($e);
  980.  
  981.                     return false;
  982.                 }
  983.             }
  984.  
  985.             // Update and cascade the publishing state.
  986.             $query->clear()
  987.                 ->update($this->_db->quoteName($this->_tbl))
  988.                 ->set('published = ' . (int) $state)
  989.                 ->where('(lft > ' . (int) $node->lft ' AND rgt < ' . (int) $node->rgt ') OR ' $k ' = ' . (int) $pk);
  990.             $this->_db->setQuery($query)->execute();
  991.  
  992.             // If checkout support exists for the object, check the row in.
  993.             if ($checkoutSupport)
  994.             {
  995.                 $this->checkin($pk);
  996.             }
  997.         }
  998.  
  999.         // If the JTable instance value is in the list of primary keys that were set, set the instance.
  1000.         if (in_array($this->$k$pks))
  1001.         {
  1002.             $this->published $state;
  1003.         }
  1004.  
  1005.         $this->setError('');
  1006.  
  1007.         return true;
  1008.     }
  1009.  
  1010.     /**
  1011.      * Method to move a node one position to the left in the same level.
  1012.      *
  1013.      * @param   integer  $pk  Primary key of the node to move.
  1014.      *
  1015.      * @return  boolean  True on success.
  1016.      *
  1017.      * @since   11.1
  1018.      * @throws  RuntimeException on database error.
  1019.      */
  1020.     public function orderUp($pk)
  1021.     {
  1022.         $k $this->_tbl_key;
  1023.         $pk (is_null($pk)) $this->$k $pk;
  1024.  
  1025.         // Lock the table for writing.
  1026.         if (!$this->_lock())
  1027.         {
  1028.             // Error message set in lock method.
  1029.             return false;
  1030.         }
  1031.  
  1032.         // Get the node by primary key.
  1033.         $node $this->_getNode($pk);
  1034.  
  1035.         if (empty($node))
  1036.         {
  1037.             // Error message set in getNode method.
  1038.             $this->_unlock();
  1039.  
  1040.             return false;
  1041.         }
  1042.  
  1043.         // Get the left sibling node.
  1044.         $sibling $this->_getNode($node->lft 1'right');
  1045.  
  1046.         if (empty($sibling))
  1047.         {
  1048.             // Error message set in getNode method.
  1049.             $this->_unlock();
  1050.  
  1051.             return false;
  1052.         }
  1053.  
  1054.         try
  1055.         {
  1056.             // Get the primary keys of child nodes.
  1057.             $query $this->_db->getQuery(true)
  1058.                 ->select($this->_tbl_key)
  1059.                 ->from($this->_tbl)
  1060.                 ->where('lft BETWEEN ' . (int) $node->lft ' AND ' . (int) $node->rgt);
  1061.  
  1062.             $children $this->_db->setQuery($query)->loadColumn();
  1063.  
  1064.             // Shift left and right values for the node and its children.
  1065.             $query->clear()
  1066.                 ->update($this->_tbl)
  1067.                 ->set('lft = lft - ' . (int) $sibling->width)
  1068.                 ->set('rgt = rgt - ' . (int) $sibling->width)
  1069.                 ->where('lft BETWEEN ' . (int) $node->lft ' AND ' . (int) $node->rgt);
  1070.             $this->_db->setQuery($query)->execute();
  1071.  
  1072.             // Shift left and right values for the sibling and its children.
  1073.             $query->clear()
  1074.                 ->update($this->_tbl)
  1075.                 ->set('lft = lft + ' . (int) $node->width)
  1076.                 ->set('rgt = rgt + ' . (int) $node->width)
  1077.                 ->where('lft BETWEEN ' . (int) $sibling->lft ' AND ' . (int) $sibling->rgt)
  1078.                 ->where($this->_tbl_key . ' NOT IN (' implode(','$children')');
  1079.             $this->_db->setQuery($query)->execute();
  1080.         }
  1081.         catch (RuntimeException $e)
  1082.         {
  1083.             $this->_unlock();
  1084.             throw $e;
  1085.         }
  1086.  
  1087.         // Unlock the table for writing.
  1088.         $this->_unlock();
  1089.  
  1090.         return true;
  1091.     }
  1092.  
  1093.     /**
  1094.      * Method to move a node one position to the right in the same level.
  1095.      *
  1096.      * @param   integer  $pk  Primary key of the node to move.
  1097.      *
  1098.      * @return  boolean  True on success.
  1099.      *
  1100.      * @since   11.1
  1101.      * @throws  RuntimeException on database error.
  1102.      */
  1103.     public function orderDown($pk)
  1104.     {
  1105.         $k $this->_tbl_key;
  1106.         $pk (is_null($pk)) $this->$k $pk;
  1107.  
  1108.         // Lock the table for writing.
  1109.         if (!$this->_lock())
  1110.         {
  1111.             // Error message set in lock method.
  1112.             return false;
  1113.         }
  1114.  
  1115.         // Get the node by primary key.
  1116.         $node $this->_getNode($pk);
  1117.  
  1118.         if (empty($node))
  1119.         {
  1120.             // Error message set in getNode method.
  1121.             $this->_unlock();
  1122.  
  1123.             return false;
  1124.         }
  1125.  
  1126.         $query $this->_db->getQuery(true);
  1127.  
  1128.         // Get the right sibling node.
  1129.         $sibling $this->_getNode($node->rgt 1'left');
  1130.  
  1131.         if (empty($sibling))
  1132.         {
  1133.             // Error message set in getNode method.
  1134.             $query->_unlock($this->_db);
  1135.             $this->_locked = false;
  1136.  
  1137.             return false;
  1138.         }
  1139.  
  1140.         try
  1141.         {
  1142.             // Get the primary keys of child nodes.
  1143.             $query->clear()
  1144.                 ->select($this->_tbl_key)
  1145.                 ->from($this->_tbl)
  1146.                 ->where('lft BETWEEN ' . (int) $node->lft ' AND ' . (int) $node->rgt);
  1147.             $this->_db->setQuery($query);
  1148.             $children $this->_db->loadColumn();
  1149.  
  1150.             // Shift left and right values for the node and its children.
  1151.             $query->clear()
  1152.                 ->update($this->_tbl)
  1153.                 ->set('lft = lft + ' . (int) $sibling->width)
  1154.                 ->set('rgt = rgt + ' . (int) $sibling->width)
  1155.                 ->where('lft BETWEEN ' . (int) $node->lft ' AND ' . (int) $node->rgt);
  1156.             $this->_db->setQuery($query)->execute();
  1157.  
  1158.             // Shift left and right values for the sibling and its children.
  1159.             $query->clear()
  1160.                 ->update($this->_tbl)
  1161.                 ->set('lft = lft - ' . (int) $node->width)
  1162.                 ->set('rgt = rgt - ' . (int) $node->width)
  1163.                 ->where('lft BETWEEN ' . (int) $sibling->lft ' AND ' . (int) $sibling->rgt)
  1164.                 ->where($this->_tbl_key . ' NOT IN (' implode(','$children')');
  1165.             $this->_db->setQuery($query)->execute();
  1166.         }
  1167.         catch (RuntimeException $e)
  1168.         {
  1169.             $this->_unlock();
  1170.             throw $e;
  1171.         }
  1172.  
  1173.         // Unlock the table for writing.
  1174.         $this->_unlock();
  1175.  
  1176.         return true;
  1177.     }
  1178.  
  1179.     /**
  1180.      * Gets the ID of the root item in the tree
  1181.      *
  1182.      * @return  mixed  The primary id of the root row, or false if not found and the internal error is set.
  1183.      *
  1184.      * @since   11.1
  1185.      */
  1186.     public function getRootId()
  1187.     {
  1188.         // Get the root item.
  1189.         $k $this->_tbl_key;
  1190.  
  1191.         // Test for a unique record with parent_id = 0
  1192.         $query $this->_db->getQuery(true)
  1193.             ->select($k)
  1194.             ->from($this->_tbl)
  1195.             ->where('parent_id = 0');
  1196.  
  1197.         $result $this->_db->setQuery($query)->loadColumn();
  1198.  
  1199.         if (count($result== 1)
  1200.         {
  1201.             return $result[0];
  1202.         }
  1203.  
  1204.         // Test for a unique record with lft = 0
  1205.         $query->clear()
  1206.             ->select($k)
  1207.             ->from($this->_tbl)
  1208.             ->where('lft = 0');
  1209.  
  1210.         $result $this->_db->setQuery($query)->loadColumn();
  1211.  
  1212.         if (count($result== 1)
  1213.         {
  1214.             return $result[0];
  1215.         }
  1216.  
  1217.         $fields $this->getFields();
  1218.  
  1219.         if (array_key_exists('alias'$fields))
  1220.         {
  1221.             // Test for a unique record alias = root
  1222.             $query->clear()
  1223.                 ->select($k)
  1224.                 ->from($this->_tbl)
  1225.                 ->where('alias = ' $this->_db->quote('root'));
  1226.  
  1227.             $result $this->_db->setQuery($query)->loadColumn();
  1228.  
  1229.             if (count($result== 1)
  1230.             {
  1231.                 return $result[0];
  1232.             }
  1233.         }
  1234.  
  1235.         $e new UnexpectedValueException(sprintf('%s::getRootId'get_class($this)));
  1236.         $this->setError($e);
  1237.  
  1238.         return false;
  1239.     }
  1240.  
  1241.     /**
  1242.      * Method to recursively rebuild the whole nested set tree.
  1243.      *
  1244.      * @param   integer  $parentId  The root of the tree to rebuild.
  1245.      * @param   integer  $leftId    The left id to start with in building the tree.
  1246.      * @param   integer  $level     The level to assign to the current nodes.
  1247.      * @param   string   $path      The path to the current nodes.
  1248.      *
  1249.      * @return  integer  1 + value of root rgt on success, false on failure
  1250.      *
  1251.      * @link    http://docs.joomla.org/JTableNested/rebuild
  1252.      * @since   11.1
  1253.      * @throws  RuntimeException on database error.
  1254.      */
  1255.     public function rebuild($parentId null$leftId 0$level 0$path '')
  1256.     {
  1257.         // If no parent is provided, try to find it.
  1258.         if ($parentId === null)
  1259.         {
  1260.             // Get the root item.
  1261.             $parentId $this->getRootId();
  1262.  
  1263.             if ($parentId === false)
  1264.             {
  1265.                 return false;
  1266.             }
  1267.         }
  1268.  
  1269.         $query $this->_db->getQuery(true);
  1270.  
  1271.         // Build the structure of the recursive query.
  1272.         if (!isset($this->_cache['rebuild.sql']))
  1273.         {
  1274.             $query->clear()
  1275.                 ->select($this->_tbl_key . ', alias')
  1276.                 ->from($this->_tbl)
  1277.                 ->where('parent_id = %d');
  1278.  
  1279.             // If the table has an ordering field, use that for ordering.
  1280.             if (property_exists($this'ordering'))
  1281.             {
  1282.                 $query->order('parent_id, ordering, lft');
  1283.             }
  1284.             else
  1285.             {
  1286.                 $query->order('parent_id, lft');
  1287.             }
  1288.             $this->_cache['rebuild.sql'= (string) $query;
  1289.         }
  1290.  
  1291.         // Make a shortcut to database object.
  1292.  
  1293.         // Assemble the query to find all children of this node.
  1294.         $this->_db->setQuery(sprintf($this->_cache['rebuild.sql'](int) $parentId));
  1295.  
  1296.         $children $this->_db->loadObjectList();
  1297.  
  1298.         // The right value of this node is the left value + 1
  1299.         $rightId $leftId 1;
  1300.  
  1301.         // Execute this function recursively over all children
  1302.         foreach ($children as $node)
  1303.         {
  1304.             /*
  1305.              * $rightId is the current right value, which is incremented on recursion return.
  1306.              * Increment the level for the children.
  1307.              * Add this item's alias to the path (but avoid a leading /)
  1308.              */
  1309.             $rightId $this->rebuild($node->{$this->_tbl_key}$rightId$level 1$path (empty($path'' '/'$node->alias);
  1310.  
  1311.             // If there is an update failure, return false to break out of the recursion.
  1312.             if ($rightId === false)
  1313.             {
  1314.                 return false;
  1315.             }
  1316.         }
  1317.  
  1318.         // We've got the left value, and now that we've processed
  1319.         // the children of this node we also know the right value.
  1320.         $query->clear()
  1321.             ->update($this->_tbl)
  1322.             ->set('lft = ' . (int) $leftId)
  1323.             ->set('rgt = ' . (int) $rightId)
  1324.             ->set('level = ' . (int) $level)
  1325.             ->set('path = ' $this->_db->quote($path))
  1326.             ->where($this->_tbl_key . ' = ' . (int) $parentId);
  1327.         $this->_db->setQuery($query)->execute();
  1328.  
  1329.         // Return the right value of this node + 1.
  1330.         return $rightId 1;
  1331.     }
  1332.  
  1333.     /**
  1334.      * Method to rebuild the node's path field from the alias values of the
  1335.      * nodes from the current node to the root node of the tree.
  1336.      *
  1337.      * @param   integer  $pk  Primary key of the node for which to get the path.
  1338.      *
  1339.      * @return  boolean  True on success.
  1340.      *
  1341.      * @link    http://docs.joomla.org/JTableNested/rebuildPath
  1342.      * @since   11.1
  1343.      */
  1344.     public function rebuildPath($pk null)
  1345.     {
  1346.         $fields $this->getFields();
  1347.  
  1348.         // If there is no alias or path field, just return true.
  1349.         if (!array_key_exists('alias'$fields|| !array_key_exists('path'$fields))
  1350.         {
  1351.             return true;
  1352.         }
  1353.  
  1354.         $k $this->_tbl_key;
  1355.         $pk (is_null($pk)) $this->$k $pk;
  1356.  
  1357.         // Get the aliases for the path from the node to the root node.
  1358.         $query $this->_db->getQuery(true)
  1359.             ->select('p.alias')
  1360.             ->from($this->_tbl . ' AS n, ' $this->_tbl . ' AS p')
  1361.             ->where('n.lft BETWEEN p.lft AND p.rgt')
  1362.             ->where('n.' $this->_tbl_key . ' = ' . (int) $pk)
  1363.             ->order('p.lft');
  1364.         $this->_db->setQuery($query);
  1365.  
  1366.         $segments $this->_db->loadColumn();
  1367.  
  1368.         // Make sure to remove the root path if it exists in the list.
  1369.         if ($segments[0== 'root')
  1370.         {
  1371.             array_shift($segments);
  1372.         }
  1373.  
  1374.         // Build the path.
  1375.         $path trim(implode('/'$segments)' /\\');
  1376.  
  1377.         // Update the path field for the node.
  1378.         $query->clear()
  1379.             ->update($this->_tbl)
  1380.             ->set('path = ' $this->_db->quote($path))
  1381.             ->where($this->_tbl_key . ' = ' . (int) $pk);
  1382.  
  1383.         $this->_db->setQuery($query)->execute();
  1384.  
  1385.         // Update the current record's path to the new one:
  1386.         $this->path $path;
  1387.  
  1388.         return true;
  1389.     }
  1390.  
  1391.     /**
  1392.      * Method to update order of table rows
  1393.      *
  1394.      * @param   array  $idArray    id numbers of rows to be reordered.
  1395.      * @param   array  $lft_array  lft values of rows to be reordered.
  1396.      *
  1397.      * @return  integer  1 + value of root rgt on success, false on failure.
  1398.      *
  1399.      * @since   11.1
  1400.      * @throws  Exception on database error.
  1401.      */
  1402.     public function saveorder($idArray null$lft_array null)
  1403.     {
  1404.         try
  1405.         {
  1406.             $query $this->_db->getQuery(true);
  1407.  
  1408.             // Validate arguments
  1409.             if (is_array($idArray&& is_array($lft_array&& count($idArray== count($lft_array))
  1410.             {
  1411.                 for ($i 0$count count($idArray)$i $count$i++)
  1412.                 {
  1413.                     // Do an update to change the lft values in the table for each id
  1414.                     $query->clear()
  1415.                         ->update($this->_tbl)
  1416.                         ->where($this->_tbl_key . ' = ' . (int) $idArray[$i])
  1417.                         ->set('lft = ' . (int) $lft_array[$i]);
  1418.  
  1419.                     $this->_db->setQuery($query)->execute();
  1420.  
  1421.                     // @codeCoverageIgnoreStart
  1422.                     if ($this->_debug)
  1423.                     {
  1424.                         $this->_logtable();
  1425.                     }
  1426.                     // @codeCoverageIgnoreEnd
  1427.                 }
  1428.  
  1429.                 return $this->rebuild();
  1430.             }
  1431.             else
  1432.             {
  1433.                 return false;
  1434.             }
  1435.         }
  1436.         catch (Exception $e)
  1437.         {
  1438.             $this->_unlock();
  1439.             throw $e;
  1440.         }
  1441.     }
  1442.  
  1443.     /**
  1444.      * Method to get nested set properties for a node in the tree.
  1445.      *
  1446.      * @param   integer  $id   Value to look up the node by.
  1447.      * @param   string   $key  An optional key to look up the node by (parent | left | right).
  1448.      *                          If omitted, the primary key of the table is used.
  1449.      *
  1450.      * @return  mixed    Boolean false on failure or node object on success.
  1451.      *
  1452.      * @since   11.1
  1453.      * @throws  RuntimeException on database error.
  1454.      */
  1455.     protected function _getNode($id$key null)
  1456.     {
  1457.         // Determine which key to get the node base on.
  1458.         switch ($key)
  1459.         {
  1460.             case 'parent':
  1461.                 $k 'parent_id';
  1462.                 break;
  1463.  
  1464.             case 'left':
  1465.                 $k 'lft';
  1466.                 break;
  1467.  
  1468.             case 'right':
  1469.                 $k 'rgt';
  1470.                 break;
  1471.  
  1472.             default:
  1473.                 $k $this->_tbl_key;
  1474.                 break;
  1475.         }
  1476.  
  1477.         // Get the node data.
  1478.         $query $this->_db->getQuery(true)
  1479.             ->select($this->_tbl_key . ', parent_id, level, lft, rgt')
  1480.             ->from($this->_tbl)
  1481.             ->where($k ' = ' . (int) $id);
  1482.  
  1483.         $row $this->_db->setQuery($query01)->loadObject();
  1484.  
  1485.         // Check for no $row returned
  1486.         if (empty($row))
  1487.         {
  1488.             $e new UnexpectedValueException(sprintf('%s::_getNode(%d, %s) failed.'get_class($this)$id$key));
  1489.             $this->setError($e);
  1490.  
  1491.             return false;
  1492.         }
  1493.  
  1494.         // Do some simple calculations.
  1495.         $row->numChildren = (int) ($row->rgt $row->lft 12;
  1496.         $row->width = (int) $row->rgt $row->lft 1;
  1497.  
  1498.         return $row;
  1499.     }
  1500.  
  1501.     /**
  1502.      * Method to get various data necessary to make room in the tree at a location
  1503.      * for a node and its children.  The returned data object includes conditions
  1504.      * for SQL WHERE clauses for updating left and right id values to make room for
  1505.      * the node as well as the new left and right ids for the node.
  1506.      *
  1507.      * @param   object   $referenceNode  A node object with at least a 'lft' and 'rgt' with
  1508.      *                                    which to make room in the tree around for a new node.
  1509.      * @param   integer  $nodeWidth      The width of the node for which to make room in the tree.
  1510.      * @param   string   $position       The position relative to the reference node where the room
  1511.      *                                    should be made.
  1512.      *
  1513.      * @return  mixed    Boolean false on failure or data object on success.
  1514.      *
  1515.      * @since   11.1
  1516.      */
  1517.     protected function _getTreeRepositionData($referenceNode$nodeWidth$position 'before')
  1518.     {
  1519.         // Make sure the reference an object with a left and right id.
  1520.         if (!is_object($referenceNode|| !(isset($referenceNode->lft&& isset($referenceNode->rgt)))
  1521.         {
  1522.             return false;
  1523.         }
  1524.  
  1525.         // A valid node cannot have a width less than 2.
  1526.         if ($nodeWidth 2)
  1527.         {
  1528.             return false;
  1529.         }
  1530.  
  1531.         $k $this->_tbl_key;
  1532.         $data new stdClass;
  1533.  
  1534.         // Run the calculations and build the data object by reference position.
  1535.         switch ($position)
  1536.         {
  1537.             case 'first-child':
  1538.                 $data->left_where 'lft > ' $referenceNode->lft;
  1539.                 $data->right_where 'rgt >= ' $referenceNode->lft;
  1540.  
  1541.                 $data->new_lft $referenceNode->lft 1;
  1542.                 $data->new_rgt $referenceNode->lft $nodeWidth;
  1543.                 $data->new_parent_id $referenceNode->$k;
  1544.                 $data->new_level $referenceNode->level 1;
  1545.                 break;
  1546.  
  1547.             case 'last-child':
  1548.                 $data->left_where 'lft > ' ($referenceNode->rgt);
  1549.                 $data->right_where 'rgt >= ' ($referenceNode->rgt);
  1550.  
  1551.                 $data->new_lft $referenceNode->rgt;
  1552.                 $data->new_rgt $referenceNode->rgt $nodeWidth 1;
  1553.                 $data->new_parent_id $referenceNode->$k;
  1554.                 $data->new_level $referenceNode->level 1;
  1555.                 break;
  1556.  
  1557.             case 'before':
  1558.                 $data->left_where 'lft >= ' $referenceNode->lft;
  1559.                 $data->right_where 'rgt >= ' $referenceNode->lft;
  1560.  
  1561.                 $data->new_lft $referenceNode->lft;
  1562.                 $data->new_rgt $referenceNode->lft $nodeWidth 1;
  1563.                 $data->new_parent_id $referenceNode->parent_id;
  1564.                 $data->new_level $referenceNode->level;
  1565.                 break;
  1566.  
  1567.             default:
  1568.             case 'after':
  1569.                 $data->left_where 'lft > ' $referenceNode->rgt;
  1570.                 $data->right_where 'rgt > ' $referenceNode->rgt;
  1571.  
  1572.                 $data->new_lft $referenceNode->rgt 1;
  1573.                 $data->new_rgt $referenceNode->rgt $nodeWidth;
  1574.                 $data->new_parent_id $referenceNode->parent_id;
  1575.                 $data->new_level $referenceNode->level;
  1576.                 break;
  1577.         }
  1578.  
  1579.         // @codeCoverageIgnoreStart
  1580.         if ($this->_debug)
  1581.         {
  1582.             echo "\nRepositioning Data for $position"\n-----------------------------------" "\nLeft Where:    $data->left_where"
  1583.                 . "\nRight Where:   $data->right_where"\nNew Lft:       $data->new_lft"\nNew Rgt:       $data->new_rgt"
  1584.                 . "\nNew Parent ID: $data->new_parent_id"\nNew Level:     $data->new_level"\n";
  1585.         }
  1586.         // @codeCoverageIgnoreEnd
  1587.  
  1588.         return $data;
  1589.     }
  1590.  
  1591.     /**
  1592.      * Method to create a log table in the buffer optionally showing the query and/or data.
  1593.      *
  1594.      * @param   boolean  $showData   True to show data
  1595.      * @param   boolean  $showQuery  True to show query
  1596.      *
  1597.      * @return  void 
  1598.      *
  1599.      * @codeCoverageIgnore
  1600.      * @since   11.1
  1601.      */
  1602.     protected function _logtable($showData true$showQuery true)
  1603.     {
  1604.         $sep "\n" str_pad(''40'-');
  1605.         $buffer '';
  1606.  
  1607.         if ($showQuery)
  1608.         {
  1609.             $buffer .= "\n" $this->_db->getQuery($sep;
  1610.         }
  1611.  
  1612.         if ($showData)
  1613.         {
  1614.             $query $this->_db->getQuery(true)
  1615.                 ->select($this->_tbl_key . ', parent_id, lft, rgt, level')
  1616.                 ->from($this->_tbl)
  1617.                 ->order($this->_tbl_key);
  1618.             $this->_db->setQuery($query);
  1619.  
  1620.             $rows $this->_db->loadRowList();
  1621.             $buffer .= sprintf("\n| %4s | %4s | %4s | %4s |"$this->_tbl_key'par''lft''rgt');
  1622.             $buffer .= $sep;
  1623.  
  1624.             foreach ($rows as $row)
  1625.             {
  1626.                 $buffer .= sprintf("\n| %4s | %4s | %4s | %4s |"$row[0]$row[1]$row[2]$row[3]);
  1627.             }
  1628.             $buffer .= $sep;
  1629.         }
  1630.         echo $buffer;
  1631.     }
  1632.  
  1633.     /**
  1634.      * Runs a query and unlocks the database on an error.
  1635.      *
  1636.      * @param   mixed   $query         A string or JDatabaseQuery object.
  1637.      * @param   string  $errorMessage  Unused.
  1638.      *
  1639.      * @return  boolean  void
  1640.      *
  1641.      * @note    Since 12.1 this method returns void and will rethrow the database exception.
  1642.      * @since   11.1
  1643.      * @throws  Exception on database error.
  1644.      */
  1645.     protected function _runQuery($query$errorMessage)
  1646.     {
  1647.         // Prepare to catch an exception.
  1648.         try
  1649.         {
  1650.             $this->_db->setQuery($query)->execute();
  1651.  
  1652.             // @codeCoverageIgnoreStart
  1653.             if ($this->_debug)
  1654.             {
  1655.                 $this->_logtable();
  1656.             }
  1657.             // @codeCoverageIgnoreEnd
  1658.         }
  1659.         catch (Exception $e)
  1660.         {
  1661.             // Unlock the tables and rethrow.
  1662.             $this->_unlock();
  1663.  
  1664.             throw $e;
  1665.         }
  1666.     }
  1667. }

Documentation generated on Tue, 19 Nov 2013 15:09:22 +0100 by phpDocumentor 1.4.3