*/
require_once 'Varien/Db/Tree/Exception.php';
Zend_Loader::loadClass('Zend_Db_Select');
Zend_Loader::loadClass('Varien_Db_Tree_Node');
Zend_Loader::loadClass('Varien_Db_Tree_NodeSet');
class Varien_Db_Tree
{
private $_id;
private $_left;
private $_right;
private $_level;
private $_pid;
private $_nodesInfo = array();
/**
* Array of additional tables
*
* array(
* [$tableName] => array(
* ['joinCondition']
* ['fields']
* )
* )
*
* @var array
*/
private $_extTables = array();
/**
* Zend_Db_Adapter
*
* @var Zend_Db_Adapter_Abstract
*/
private $_db;
private $_table;
function __construct($config = array())
{
// set a Zend_Db_Adapter connection
if (! empty($config['db'])) {
// convenience variable
$db = $config['db'];
// use an object from the registry?
if (is_string($db)) {
$db = Zend::registry($db);
}
// make sure it's a Zend_Db_Adapter
if (! $db instanceof Zend_Db_Adapter_Abstract) {
throw new Varien_Db_Tree_Exception('db object does not implement Zend_Db_Adapter_Abstract');
}
// save the connection
$this->_db = $db;
$conn = $this->_db->getConnection();
if ($conn instanceof PDO) {
$conn->setAttribute (PDO::ATTR_EMULATE_PREPARES, true);
} elseif ($conn instanceof mysqli) {
//TODO: ???
}
} else {
throw new Varien_Db_Tree_Exception('db object is not set in config');
}
if (!empty($config['table'])) {
$this->setTable($config['table']);
}
if (!empty($config['id'])) {
$this->setIdField($config['id']);
} else {
$this->setIdField('id');
}
if (!empty($config['left'])) {
$this->setLeftField($config['left']);
} else {
$this->setLeftField('left_key');
}
if (!empty($config['right'])) {
$this->setRightField($config['right']);
} else {
$this->setRightField('right_key');
}
if (!empty($config['level'])) {
$this->setLevelField($config['level']);
} else {
$this->setLevelField('level');
}
if (!empty($config['pid'])) {
$this->setPidField($config['pid']);
} else {
$this->setPidField('parent_id');
}
}
/**
* set name of id field
*
* @param string $name
* @return Varien_Db_Tree
*/
public function setIdField($name) {
$this->_id = $name;
return $this;
}
/**
* set name of left field
*
* @param string $name
* @return Varien_Db_Tree
*/
public function setLeftField($name) {
$this->_left = $name;
return $this;
}
/**
* set name of right field
*
* @param string $name
* @return Varien_Db_Tree
*/
public function setRightField($name) {
$this->_right = $name;
return $this;
}
/**
* set name of level field
*
* @param string $name
* @return Varien_Db_Tree
*/
public function setLevelField($name) {
$this->_level = $name;
return $this;
}
/**
* set name of pid Field
*
* @param string $name
* @return Varien_Db_Tree
*/
public function setPidField($name) {
$this->_pid = $name;
return $this;
}
/**
* set table name
*
* @param string $name
* @return Varien_Db_Tree
*/
public function setTable($name) {
$this->_table = $name;
return $this;
}
public function getKeys() {
$keys = array();
$keys['id'] = $this->_id;
$keys['left'] = $this->_left;
$keys['right'] = $this->_right;
$keys['pid'] = $this->_pid;
$keys['level'] = $this->_level;
return $keys;
}
/**
* Cleare table and add root element
*
*/
public function clear($data = array())
{
// clearing table
$this->_db->query('TRUNCATE '. $this->_table);
//$this->_db->delete($this->_table,'');
// prepare data for root element
$data[$this->_pid] = 0;
$data[$this->_left] = 1;
$data[$this->_right] = 2;
$data[$this->_level] = 0;
try {
$this->_db->insert($this->_table, $data);
} catch (PDOException $e) {
echo $e->getMessage();
}
return $this->_db->lastInsertId();
}
public function getNodeInfo($ID) {
if (empty($this->_nodesInfo[$ID])) {
$sql = 'SELECT * FROM '.$this->_table.' WHERE '.$this->_id.'=:id';
$res = $this->_db->query($sql, array('id' => $ID));
$data = $res->fetch();
$this->_nodesInfo[$ID] = $data;
} else {
$data = $this->_nodesInfo[$ID];
}
return $data;
}
public function appendChild($ID, $data) {
if (!$info = $this->getNodeInfo($ID)) {
return false;
}
$data[$this->_left] = $info[$this->_right];
$data[$this->_right] = $info[$this->_right] + 1;
$data[$this->_level] = $info[$this->_level] + 1;
$data[$this->_pid] = $ID;
// creating a place for the record being inserted
if($ID) {
$this->_db->beginTransaction();
try {
$sql = 'UPDATE '.$this->_table.' SET'
. ' `'.$this->_left.'` = IF( `'.$this->_left.'` > :left, `'.$this->_left.'`+2, `'.$this->_left.'`),'
. ' `'.$this->_right.'` = IF( `'.$this->_right.'`>= :right, `'.$this->_right.'`+2, `'.$this->_right.'`)'
. ' WHERE `'.$this->_right.'` >= :right';
$this->_db->query($sql, array('left'=>$info[$this->_left], 'right'=>$info[$this->_right]));
$this->_db->insert($this->_table, $data);
$this->_db->commit();
} catch (PDOException $p) {
$this->_db->rollBack();
echo $p->getMessage();
exit();
} catch (Exception $e) {
$this->_db->rollBack();
echo $e->getMessage();
echo $sql;
var_dump($data);
exit();
}
// TODO: change to ZEND LIBRARY
$res = $this->_db->fetchOne('select last_insert_id()');
return $res;
//return $this->_db->fetchOne('select last_insert_id()');
//return $this->_db->lastInsertId();
}
return false;
}
public function checkNodes() {
$sql = $this->_db->select();
$sql->from(array('t1'=>$this->_table), array('t1.'.$this->_id, new Zend_Db_Expr('COUNT(t1.'.$this->_id.') AS rep')))
->from(array('t2'=>$this->_table))
->from(array('t3'=>$this->_table), new Zend_Db_Expr('MAX(t3.'.$this->_right.') AS max_right'));
$sql->where('t1.'.$this->_left.' <> t2.'.$this->_left)
->where('t1.'.$this->_left.' <> t2.'.$this->_right)
->where('t1.'.$this->_right.' <> t2.'.$this->_right);
$sql->group('t1.'.$this->_id);
$sql->having('max_right <> SQRT(4 * rep + 1) + 1');
return $this->_db->fetchAll($sql);
}
public function insertBefore($ID, $data) {
}
public function removeNode($ID) {
if (!$info = $this->getNodeInfo($ID)) {
return false;
}
if($ID) {
$this->_db->beginTransaction();
try {
// DELETE FROM my_tree WHERE left_key >= $left_key AND right_key <= $right_key
$this->_db->delete($this->_table, $this->_left.' >= '.$info[$this->_left].' AND '.$this->_right.' <= '.$info[$this->_right]);
// UPDATE my_tree SET left_key = IF(left_key > $left_key, left_key – ($right_key - $left_key + 1), left_key), right_key = right_key – ($right_key - $left_key + 1) WHERE right_key > $right_key
$sql = 'UPDATE '.$this->_table.'
SET
'.$this->_left.' = IF('.$this->_left.' > '.$info[$this->_left].', '.$this->_left.' - '.($info[$this->_right] - $info[$this->_left] + 1).', '.$this->_left.'),
'.$this->_right.' = '.$this->_right.' - '.($info[$this->_right] - $info[$this->_left] + 1).'
WHERE
'.$this->_right.' > '.$info[$this->_right];
$this->_db->query($sql);
$this->_db->commit();
return new Varien_Db_Tree_Node($info, $this->getKeys());;
} catch (Exception $e) {
$this->_db->rollBack();
echo $e->getMessage();
}
}
}
public function moveNode($eId, $pId, $aId = 0) {
$eInfo = $this->getNodeInfo($eId);
$pInfo = $this->getNodeInfo($pId);
$leftId = $eInfo[$this->_left];
$rightId = $eInfo[$this->_right];
$level = $eInfo[$this->_level];
$leftIdP = $pInfo[$this->_left];
$rightIdP = $pInfo[$this->_right];
$levelP = $pInfo[$this->_level];
if ($eId == $pId || $leftId == $leftIdP || ($leftIdP >= $leftId && $leftIdP <= $rightId) || ($level == $levelP+1 && $leftId > $leftIdP && $rightId < $rightIdP)) {
echo "alert('cant_move_tree');";
return FALSE;
}
if ($leftIdP < $leftId && $rightIdP > $rightId && $levelP < $level - 1) {
$sql = 'UPDATE '.$this->_table.' SET '
. $this->_level . ' = CASE WHEN ' . $this->_left . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_level.sprintf('%+d', -($level-1)+$levelP) . ' ELSE ' . $this->_level . ' END, '
. $this->_right . ' = CASE WHEN ' . $this->_right . ' BETWEEN ' . ($rightId+1) . ' AND ' . ($rightIdP-1) . ' THEN ' . $this->_right . '-' . ($rightId-$leftId+1) . ' '
. 'WHEN ' . $this->_left . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_right . '+' . ((($rightIdP-$rightId-$level+$levelP)/2)*2+$level-$levelP-1) . ' ELSE ' . $this->_right . ' END, '
. $this->_left . ' = CASE WHEN ' . $this->_left . ' BETWEEN ' . ($rightId+1) . ' AND ' . ($rightIdP-1) . ' THEN ' . $this->_left . '-' . ($rightId-$leftId+1) . ' '
. 'WHEN ' . $this->_left . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_left . '+' . ((($rightIdP-$rightId-$level+$levelP)/2)*2+$level-$levelP-1) . ' ELSE ' . $this->_left . ' END '
. 'WHERE ' . $this->_left . ' BETWEEN ' . ($leftIdP+1) . ' AND ' . ($rightIdP-1);
} elseif ($leftIdP < $leftId) {
$sql = 'UPDATE ' . $this->_table . ' SET '
. $this->_level . ' = CASE WHEN ' . $this->_left . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_level.sprintf('%+d', -($level-1)+$levelP) . ' ELSE ' . $this->_level . ' END, '
. $this->_left . ' = CASE WHEN ' . $this->_left . ' BETWEEN ' . $rightIdP . ' AND ' . ($leftId-1) . ' THEN ' . $this->_left . '+' . ($rightId-$leftId+1) . ' '
. 'WHEN ' . $this->_left . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_left . '-' . ($leftId-$rightIdP) . ' ELSE ' . $this->_left . ' END, '
. $this->_right . ' = CASE WHEN ' . $this->_right . ' BETWEEN ' . $rightIdP . ' AND ' . $leftId . ' THEN ' . $this->_right . '+' . ($rightId-$leftId+1) . ' '
. 'WHEN ' . $this->_right . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_right . '-' . ($leftId-$rightIdP) . ' ELSE ' . $this->_right . ' END '
. 'WHERE (' . $this->_left . ' BETWEEN ' . $leftIdP . ' AND ' . $rightId. ' '
. 'OR ' . $this->_right . ' BETWEEN ' . $leftIdP . ' AND ' . $rightId . ')';
} else {
$sql = 'UPDATE ' . $this->_table . ' SET '
. $this->_level . ' = CASE WHEN ' . $this->_left . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_level.sprintf('%+d', -($level-1)+$levelP) . ' ELSE ' . $this->_level . ' END, '
. $this->_left . ' = CASE WHEN ' . $this->_left . ' BETWEEN ' . $rightId . ' AND ' . $rightIdP . ' THEN ' . $this->_left . '-' . ($rightId-$leftId+1) . ' '
. 'WHEN ' . $this->_left . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_left . '+' . ($rightIdP-1-$rightId) . ' ELSE ' . $this->_left . ' END, '
. $this->_right . ' = CASE WHEN ' . $this->_right . ' BETWEEN ' . ($rightId+1) . ' AND ' . ($rightIdP-1) . ' THEN ' . $this->_right . '-' . ($rightId-$leftId+1) . ' '
. 'WHEN ' . $this->_right . ' BETWEEN ' . $leftId . ' AND ' . $rightId . ' THEN ' . $this->_right . '+' . ($rightIdP-1-$rightId) . ' ELSE ' . $this->_right . ' END '
. 'WHERE (' . $this->_left . ' BETWEEN ' . $leftId . ' AND ' . $rightIdP . ' '
. 'OR ' . $this->_right . ' BETWEEN ' . $leftId . ' AND ' . $rightIdP . ')';
}
$this->_db->beginTransaction();
try {
$this->_db->query($sql);
$this->_db->commit();
echo "alert('node moved');";
return true;
} catch (Exception $e) {
$this->_db->rollBack();
echo "alert('node not moved: fatal error');";
echo $e->getMessage();
echo "
\r\n";
echo $sql;
echo "
\r\n";
exit();
}
}
public function __moveNode($eId, $pId, $aId = 0) {
$eInfo = $this->getNodeInfo($eId);
if ($pId != 0) {
$pInfo = $this->getNodeInfo($pId);
}
if ($aId != 0) {
$aInfo = $this->getNodeInfo($aId);
}
$level = $eInfo[$this->_level];
$left_key = $eInfo[$this->_left];
$right_key = $eInfo[$this->_right];
if ($pId == 0) {
$level_up = 0;
} else {
$level_up = $pInfo[$this->_level];
}
$right_key_near = 0;
$left_key_near = 0;
if ($pId == 0) { //move to root
$right_key_near = $this->_db->fetchOne('SELECT MAX('.$this->_right.') FROM '.$this->_table);
} elseif ($aId != 0 && $pID == $eInfo[$this->_pid]) { // if we have after ID
$right_key_near = $aInfo[$this->_right];
$left_key_near = $aInfo[$this->_left];
} elseif ($aId == 0 && $pId == $eInfo[$this->_pid]) { // if we do not have after ID
$right_key_near = $pInfo[$this->_left];
} elseif ($pId != $eInfo[$this->_pid]) {
$right_key_near = $pInfo[$this->_right] - 1;
}
$skew_level = $pInfo[$this->_level] - $eInfo[$this->_level] + 1;
$skew_tree = $eInfo[$this->_right] - $eInfo[$this->_left] + 1;
echo "alert('".$right_key_near."');";
if ($right_key_near > $right_key) { // up
echo "alert('move up');";
$skew_edit = $right_key_near - $left_key + 1;
$sql = 'UPDATE '.$this->_table.'
SET
'.$this->_right.' = IF('.$this->_left.' >= '.$eInfo[$this->_left].', '.$this->_right.' + '.$skew_edit.', IF('.$this->_right.' < '.$eInfo[$this->_left].', '.$this->_right.' + '.$skew_tree.', '.$this->_right.')),
'.$this->_level.' = IF('.$this->_left.' >= '.$eInfo[$this->_left].', '.$this->_level.' + '.$skew_level.', '.$this->_level.'),
'.$this->_left.' = IF('.$this->_left.' >= '.$eInfo[$this->_left].', '.$this->_left.' + '.$skew_edit.', IF('.$this->_left.' > '.$right_key_near.', '.$this->_left.' + '.$skew_tree.', '.$this->_left.'))
WHERE '.$this->_right.' > '.$right_key_near.' AND '.$this->_left.' < '.$eInfo[$this->_right];
} elseif ($right_key_near < $right_key) { // down
echo "alert('move down');";
$skew_edit = $right_key_near - $left_key + 1 - $skew_tree;
$sql = 'UPDATE '.$this->_table.'
SET
'.$this->_left.' = IF('.$this->_right.' <= '.$right_key.', '.$this->_left.' + '.$skew_edit.', IF('.$this->_left.' > '.$right_key.', '.$this->_left.' - '.$skew_tree.', '.$this->_left.')),
'.$this->_level.' = IF('.$this->_right.' <= '.$right_key.', '.$this->_level.' + '.$skew_level.', '.$this->_level.'),
'.$this->_right.' = IF('.$this->_right.' <= '.$right_key.', '.$this->_right.' + '.$skew_edit.', IF('.$this->_right.' <= '.$right_key_near.', '.$this->_right.' - '.$skew_tree.', '.$this->_right.'))
WHERE
'.$this->_right.' > '.$left_key.' AND '.$this->_left.' <= '.$right_key_near;
}
$this->_db->beginTransaction();
try {
$this->_db->query($sql);
//$afrows = $this->_db->get
$this->_db->commit();
} catch (Exception $e) {
$this->_db->rollBack();
echo $e->getMessage();
echo "
\r\n";
echo $sql;
echo "
\r\n";
exit();
}
echo "alert('node added')";
}
public function addTable($tableName, $joinCondition, $fields='*')
{
$this->_extTables[$tableName] = array(
'joinCondition' => $joinCondition,
'fields' => $fields
);
}
protected function _addExtTablesToSelect(Zend_Db_Select &$select)
{
foreach ($this->_extTables as $tableName=>$info) {
$select->joinInner($tableName, $info['joinCondition'], $info['fields']);
}
}
public function getChildren($ID, $start_level = 0, $end_level = 0)
{
try {
$info = $this->getNodeInfo($ID);
} catch (Exception $e) {
echo $e->getMessage();
exit;
}
$dbSelect = new Zend_Db_Select($this->_db);
$dbSelect->from($this->_table)
->where($this->_left . ' >= :left')
->where($this->_right . ' <= :right')
->order($this->_left);
$this->_addExtTablesToSelect($dbSelect);
$data = array();
$data['left'] = $info[$this->_left];
$data['right'] = $info[$this->_right];
if (!empty($start_level) && empty($end_level)) {
$dbSelect->where($this->_level . ' = :minLevel');
$data['minLevel'] = $info[$this->_level] + $start_level;
}
//echo $dbSelect->__toString();
$data = $this->_db->fetchAll($dbSelect, $data);
$nodeSet = new Varien_Db_Tree_NodeSet();
foreach ($data as $node) {
$nodeSet->addNode(new Varien_Db_Tree_Node($node, $this->getKeys()));
}
return $nodeSet;
}
public function getNode($nodeId)
{
$dbSelect = new Zend_Db_Select($this->_db);
$dbSelect->from($this->_table)
->where($this->_table.'.'.$this->_id . ' >= :id');
$this->_addExtTablesToSelect($dbSelect);
$data = array();
$data['id'] = $nodeId;
$data = $this->_db->fetchRow($dbSelect, $data);
return new Varien_Db_Tree_Node($data, $this->getKeys());
}
}